diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a877008
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,123 @@
+### Java template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+.idea/artifacts
+.idea/compiler.xml
+.idea/jarRepositories.xml
+.idea/modules.xml
+.idea/*.iml
+.idea/modules
+*.iml
+*.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Gradle template
+.gradle
+**/build/
+!src/**/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+# Potentially copyrighted test APK
+*.apk
+
+# Ignore vscode config
+.vscode/
+
+# Dependency directories
+node_modules/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..877c6d9
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,9 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+/kotlinc.xml
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..d8e9561
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
new file mode 100644
index 0000000..02b915b
--- /dev/null
+++ b/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..f1854e4
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..7ddfc9e
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.releaserc b/.releaserc
new file mode 100644
index 0000000..8fd4c5b
--- /dev/null
+++ b/.releaserc
@@ -0,0 +1,45 @@
+{
+ "branches": [
+ "main",
+ {
+ "name": "dev",
+ "prerelease": true
+ }
+ ],
+ "plugins": [
+ "@semantic-release/commit-analyzer",
+ "@semantic-release/release-notes-generator",
+ "@semantic-release/changelog",
+ "gradle-semantic-release-plugin",
+ [
+ "@semantic-release/git",
+ {
+ "assets": [
+ "README.md",
+ "CHANGELOG.md",
+ "gradle.properties",
+ "patches.json"
+ ]
+ }
+ ],
+ [
+ "@semantic-release/github",
+ {
+ "assets": [
+ {
+ "path": "build/libs/*.jar"
+ },
+ {
+ "path": "patches.json"
+ }
+ ]
+ }
+ ],
+ [
+ "@saithodev/semantic-release-backmerge",
+ {
+ "branches": [{from: "main", to: "dev"}]
+ }
+ ]
+ ]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8637ee9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,2751 @@
+# [2.146.0](https://github.com/revanced/revanced-patches/compare/v2.145.0...v2.146.0) (2022-12-18)
+
+
+### Features
+
+* **predictive-back-gesture:** exclude by default ([176d34b](https://github.com/revanced/revanced-patches/commit/176d34b2ff92d0eb627dad86ad87f559df3db815))
+* **twitter/timeline-ads:** add clarifying notice to description ([0eca2af](https://github.com/revanced/revanced-patches/commit/0eca2af1a4a688903c61ff6469ffea90c60f06a6))
+* **youtube:** bump patches compatibility to v17.49.37 ([83ebc73](https://github.com/revanced/revanced-patches/commit/83ebc731130d3d216ac4b815382d8f13548cf665))
+
+# [2.146.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.145.0...v2.146.0-dev.1) (2022-12-18)
+
+
+### Features
+
+* **predictive-back-gesture:** exclude by default ([176d34b](https://github.com/revanced/revanced-patches/commit/176d34b2ff92d0eb627dad86ad87f559df3db815))
+* **twitter/timeline-ads:** add clarifying notice to description ([0eca2af](https://github.com/revanced/revanced-patches/commit/0eca2af1a4a688903c61ff6469ffea90c60f06a6))
+* **youtube:** bump patches compatibility to v17.49.37 ([83ebc73](https://github.com/revanced/revanced-patches/commit/83ebc731130d3d216ac4b815382d8f13548cf665))
+
+# [2.145.0](https://github.com/revanced/revanced-patches/compare/v2.144.0...v2.145.0) (2022-12-18)
+
+
+### Bug Fixes
+
+* **tiktok/settings:** make compatible with newer versions ([c88740d](https://github.com/revanced/revanced-patches/commit/c88740dc2bf040747d9704d5bed52a7b533c22d6))
+
+
+### Features
+
+* **crunchyroll:** remove `enable-downloads` patch ([2ac1546](https://github.com/revanced/revanced-patches/commit/2ac15460f5734fc2c6d2d78d34083a8e6b2f7012))
+* **youtube-music:** bump compatibility to 5.36.51 ([c4a3099](https://github.com/revanced/revanced-patches/commit/c4a3099c1ca39431fba9beb94cc2b43e260561d9))
+
+# [2.145.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.144.0...v2.145.0-dev.1) (2022-12-18)
+
+
+### Bug Fixes
+
+* **tiktok/settings:** make compatible with newer versions ([c88740d](https://github.com/revanced/revanced-patches/commit/c88740dc2bf040747d9704d5bed52a7b533c22d6))
+
+
+### Features
+
+* **crunchyroll:** remove `enable-downloads` patch ([2ac1546](https://github.com/revanced/revanced-patches/commit/2ac15460f5734fc2c6d2d78d34083a8e6b2f7012))
+* **youtube-music:** bump compatibility to 5.36.51 ([c4a3099](https://github.com/revanced/revanced-patches/commit/c4a3099c1ca39431fba9beb94cc2b43e260561d9))
+
+# [2.144.0](https://github.com/revanced/revanced-patches/compare/v2.143.0...v2.144.0) (2022-12-15)
+
+
+### Features
+
+* **citra:** add compatibility for canary package ([#1256](https://github.com/revanced/revanced-patches/issues/1256)) ([42ec021](https://github.com/revanced/revanced-patches/commit/42ec0218d829ea15759f83562d24588ce97cb646))
+* link packages to Google PlayStore ([#1272](https://github.com/revanced/revanced-patches/issues/1272)) ([97892e0](https://github.com/revanced/revanced-patches/commit/97892e01044c74916375aeebcc094d3304e14f4e))
+
+# [2.144.0-dev.2](https://github.com/revanced/revanced-patches/compare/v2.144.0-dev.1...v2.144.0-dev.2) (2022-12-15)
+
+
+### Features
+
+* link packages to Google PlayStore ([#1272](https://github.com/revanced/revanced-patches/issues/1272)) ([97892e0](https://github.com/revanced/revanced-patches/commit/97892e01044c74916375aeebcc094d3304e14f4e))
+
+# [2.144.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.143.0...v2.144.0-dev.1) (2022-12-15)
+
+
+### Features
+
+* **citra:** add compatibility for canary package ([#1256](https://github.com/revanced/revanced-patches/issues/1256)) ([42ec021](https://github.com/revanced/revanced-patches/commit/42ec0218d829ea15759f83562d24588ce97cb646))
+
+# [2.143.0](https://github.com/revanced/revanced-patches/compare/v2.142.0...v2.143.0) (2022-12-14)
+
+
+### Bug Fixes
+
+* **predictive-back-gesture:** create attribute, if it does not exist ([c00e771](https://github.com/revanced/revanced-patches/commit/c00e7717053f806e3b5b3f0bf0ca9c2da07c289b))
+
+
+### Features
+
+* **sleepasandroid:** remove `unlock-premium` patch ([#1253](https://github.com/revanced/revanced-patches/issues/1253)) ([414f503](https://github.com/revanced/revanced-patches/commit/414f503e493f2dc27edf134f5db723da77636d81))
+
+# [2.143.0-dev.2](https://github.com/revanced/revanced-patches/compare/v2.143.0-dev.1...v2.143.0-dev.2) (2022-12-14)
+
+
+### Bug Fixes
+
+* **predictive-back-gesture:** create attribute, if it does not exist ([c00e771](https://github.com/revanced/revanced-patches/commit/c00e7717053f806e3b5b3f0bf0ca9c2da07c289b))
+
+# [2.143.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.142.0...v2.143.0-dev.1) (2022-12-13)
+
+
+### Features
+
+* **sleepasandroid:** remove `unlock-premium` patch ([#1253](https://github.com/revanced/revanced-patches/issues/1253)) ([414f503](https://github.com/revanced/revanced-patches/commit/414f503e493f2dc27edf134f5db723da77636d81))
+
+# [2.142.0](https://github.com/revanced/revanced-patches/compare/v2.141.0...v2.142.0) (2022-12-11)
+
+
+### Features
+
+* `predictive-back-gesture` patch ([#1236](https://github.com/revanced/revanced-patches/issues/1236)) ([11b59f7](https://github.com/revanced/revanced-patches/commit/11b59f767c94514d49a84593618ff9d2aace2636))
+* **twitch/block-embedded-ads:** `block-embedded-ads` patch ([#1166](https://github.com/revanced/revanced-patches/issues/1166)) ([018606e](https://github.com/revanced/revanced-patches/commit/018606e9832c43836b6b8f4fbd623adb8876d162))
+
+# [2.141.0](https://github.com/revanced/revanced-patches/compare/v2.140.0...v2.141.0) (2022-12-09)
+
+
+### Bug Fixes
+
+* inconsistencies in preference switches ([#1207](https://github.com/revanced/revanced-patches/issues/1207)) ([b6ea5a4](https://github.com/revanced/revanced-patches/commit/b6ea5a43b3eec6a06c7514cd79569b97a2b7d333))
+
+
+### Features
+
+* **youtube/return-youtube-dislike:** simplify preference descriptions ([#1216](https://github.com/revanced/revanced-patches/issues/1216)) ([514b5da](https://github.com/revanced/revanced-patches/commit/514b5da9a7de6f8d0b0d00ec6269f836df3e9333))
+
+# [2.140.0](https://github.com/revanced/revanced-patches/compare/v2.139.0...v2.140.0) (2022-12-05)
+
+
+### Features
+
+* **music:** bump compatibility to v5.34.51 ([fc0c92e](https://github.com/revanced/revanced-patches/commit/fc0c92e2b68a2d93a02f4577aeb864955b1b5701))
+
+# [2.139.0](https://github.com/revanced/revanced-patches/compare/v2.138.0...v2.139.0) (2022-12-05)
+
+
+### Features
+
+* **youtube/return-youtube-dislike:** improve settings labels ([#1203](https://github.com/revanced/revanced-patches/issues/1203)) ([fb4a612](https://github.com/revanced/revanced-patches/commit/fb4a612592d82857b0e892a6f5d135dbd657c44d))
+
+# [2.138.0](https://github.com/revanced/revanced-patches/compare/v2.137.0...v2.138.0) (2022-12-05)
+
+
+### Features
+
+* **youtube/debugging:** print stack traces ([9cea382](https://github.com/revanced/revanced-patches/commit/9cea382750456434d0547351ee7d4013eac9de9c))
+
+# [2.137.0](https://github.com/revanced/revanced-patches/compare/v2.136.0...v2.137.0) (2022-12-03)
+
+
+### Features
+
+* **youtube/return-youtube-dislike:** show dislike as a percentage ([#1185](https://github.com/revanced/revanced-patches/issues/1185)) ([cbbab0b](https://github.com/revanced/revanced-patches/commit/cbbab0b4069534a2032258f9a09e9690fc68c291))
+
+# [2.136.0](https://github.com/revanced/revanced-patches/compare/v2.135.0...v2.136.0) (2022-12-02)
+
+
+### Bug Fixes
+
+* syntax error in compatiblity annotation ([#1181](https://github.com/revanced/revanced-patches/issues/1181)) ([0830c7e](https://github.com/revanced/revanced-patches/commit/0830c7ed1064757cf6354b426549275ece55408e))
+
+
+### Features
+
+* **youtube:** drop support for compatibility of old versions ([7413aad](https://github.com/revanced/revanced-patches/commit/7413aad6d215f24a66ba9a33917c73dbdb0b7bc3))
+
+# [2.135.0](https://github.com/revanced/revanced-patches/compare/v2.134.1...v2.135.0) (2022-12-02)
+
+
+### Features
+
+* sort README patch table by amount of patches per package ([40cb14e](https://github.com/revanced/revanced-patches/commit/40cb14e1838aee5b49b60094cd134ed7e1726c3f))
+
+## [2.134.1](https://github.com/revanced/revanced-patches/compare/v2.134.0...v2.134.1) (2022-12-02)
+
+
+### Bug Fixes
+
+* **youtube:** swipe gesture on home screen ([9294084](https://github.com/revanced/revanced-patches/commit/929408406b53d8b0db69f092c3e191791b9fa8ee)), closes [#610](https://github.com/revanced/revanced-patches/issues/610)
+
+# [2.134.0](https://github.com/revanced/revanced-patches/compare/v2.133.0...v2.134.0) (2022-12-02)
+
+
+### Bug Fixes
+
+* **anytracker:** syntax error in fingerprint ([#1165](https://github.com/revanced/revanced-patches/issues/1165)) ([46ad786](https://github.com/revanced/revanced-patches/commit/46ad786b1488814494080ca48cff27c361c47ca1))
+
+
+### Features
+
+* **anytracker:** `unlock-premium` patch ([#1165](https://github.com/revanced/revanced-patches/issues/1165)) ([e7ed324](https://github.com/revanced/revanced-patches/commit/e7ed324bb5216037be0646a698e7e808fbf03e4e))
+
+# [2.133.0](https://github.com/revanced/revanced-patches/compare/v2.132.0...v2.133.0) (2022-12-01)
+
+
+### Features
+
+* **moneymanager:** `unlock-premium` patch ([#1173](https://github.com/revanced/revanced-patches/issues/1173)) ([438c1bc](https://github.com/revanced/revanced-patches/commit/438c1bca684c7d47bd5a401b4dccbaddef604650))
+
+# [2.132.0](https://github.com/revanced/revanced-patches/compare/v2.131.0...v2.132.0) (2022-12-01)
+
+
+### Features
+
+* **sleepasandroid:** `unlock-premium` patch ([#1172](https://github.com/revanced/revanced-patches/issues/1172)) ([580ddcd](https://github.com/revanced/revanced-patches/commit/580ddcdf1f8ef4a525f772630b3c085b706e4dc7))
+* **twitch:** remove `block-embedded-ads` patch ([#1164](https://github.com/revanced/revanced-patches/issues/1164)) ([ecc1eae](https://github.com/revanced/revanced-patches/commit/ecc1eaef4daa7dfecb7913c378188b2b4685e610))
+
+# [2.131.0](https://github.com/revanced/revanced-patches/compare/v2.130.0...v2.131.0) (2022-11-30)
+
+
+### Features
+
+* **twitch/block-embedded-ads:** `block-embedded-ads` patch support ([#1160](https://github.com/revanced/revanced-patches/issues/1160)) ([641d33f](https://github.com/revanced/revanced-patches/commit/641d33fc7e213031124491bb468fb382267769aa))
+
+# [2.130.0](https://github.com/revanced/revanced-patches/compare/v2.129.0...v2.130.0) (2022-11-29)
+
+
+### Features
+
+* **youtube/enable-wide-searchbar:** bump compatibility to v17.45.36 ([66ce54c](https://github.com/revanced/revanced-patches/commit/66ce54c9921e6fbf5bbe52d73b0182352181f2b8))
+
+# [2.129.0](https://github.com/revanced/revanced-patches/compare/v2.128.0...v2.129.0) (2022-11-28)
+
+
+### Features
+
+* **youtube/hide-create-button:** bump compatibility to v17.45.36 ([751f604](https://github.com/revanced/revanced-patches/commit/751f6047ba1589593d881ad523727d00dfb2609b))
+* **youtube/hide-shorts-button:** bump compatibility to v17.45.36 ([51bfa7a](https://github.com/revanced/revanced-patches/commit/51bfa7afd1720d958847a44ae636de722311a199))
+
+# [2.128.0](https://github.com/revanced/revanced-patches/compare/v2.127.0...v2.128.0) (2022-11-28)
+
+
+### Features
+
+* **twitch:** settings for patches ([#1142](https://github.com/revanced/revanced-patches/issues/1142)) ([ed56f94](https://github.com/revanced/revanced-patches/commit/ed56f94f12c3bee46ab370b2e3b7890d1181d53d))
+
+# [2.127.0](https://github.com/revanced/revanced-patches/compare/v2.126.1...v2.127.0) (2022-11-28)
+
+
+### Features
+
+* **twitch:** `settings` patch ([#1075](https://github.com/revanced/revanced-patches/issues/1075)) ([6e95b86](https://github.com/revanced/revanced-patches/commit/6e95b86e50cb09b60d82b456d4650218436ed154))
+
+## [2.126.1](https://github.com/revanced/revanced-patches/compare/v2.126.0...v2.126.1) (2022-11-28)
+
+
+### Bug Fixes
+
+* **youtube/custom-video-speed:** fuzzy scan on fingerprint ([#1135](https://github.com/revanced/revanced-patches/issues/1135)) ([7b2cf56](https://github.com/revanced/revanced-patches/commit/7b2cf5698b85f7e2a901f6085c53d042660dc5c7))
+
+# [2.126.0](https://github.com/revanced/revanced-patches/compare/v2.125.0...v2.126.0) (2022-11-28)
+
+
+### Features
+
+* **backdrops:** `pro-unlock` patch ([#1121](https://github.com/revanced/revanced-patches/issues/1121)) ([76b5449](https://github.com/revanced/revanced-patches/commit/76b5449c61c97e8ad9f87fb7173c9819815744ba))
+
+# [2.125.0](https://github.com/revanced/revanced-patches/compare/v2.124.0...v2.125.0) (2022-11-26)
+
+
+### Features
+
+* **myexpenses:** `unlock-pro` patch ([#1120](https://github.com/revanced/revanced-patches/issues/1120)) ([e1a327a](https://github.com/revanced/revanced-patches/commit/e1a327a15d4289d9708546cc40f423597f22a66d))
+
+# [2.124.0](https://github.com/revanced/revanced-patches/compare/v2.123.0...v2.124.0) (2022-11-26)
+
+
+### Features
+
+* **citra:** `premium-unlock` patch ([#1115](https://github.com/revanced/revanced-patches/issues/1115)) ([86a3def](https://github.com/revanced/revanced-patches/commit/86a3def0f63a0529e44302472afc03cb0c8d566b))
+* **crunchyroll:** `enable-downloads` patch ([#1119](https://github.com/revanced/revanced-patches/issues/1119)) ([84d7bfd](https://github.com/revanced/revanced-patches/commit/84d7bfdbf8a6bfc25f52653a1b243f1291eb0da9))
+
+# [2.123.0](https://github.com/revanced/revanced-patches/compare/v2.122.0...v2.123.0) (2022-11-26)
+
+
+### Features
+
+* **windyapp:** `unlock-pro` patch ([#1118](https://github.com/revanced/revanced-patches/issues/1118)) ([d866634](https://github.com/revanced/revanced-patches/commit/d866634f83976df44b26848e0b70e9952d5d06ae))
+
+# [2.122.0](https://github.com/revanced/revanced-patches/compare/v2.121.0...v2.122.0) (2022-11-25)
+
+
+### Features
+
+* **tiktok:** `sim-spoof` patch ([#1088](https://github.com/revanced/revanced-patches/issues/1088)) ([53bb547](https://github.com/revanced/revanced-patches/commit/53bb547cc1003859f1d42db819a897e120b17fbe))
+
+# [2.121.0](https://github.com/revanced/revanced-patches/compare/v2.120.0...v2.121.0) (2022-11-23)
+
+
+### Features
+
+* **anytracker:** remove `unlock-premium` patch ([#1094](https://github.com/revanced/revanced-patches/issues/1094)) ([c3818be](https://github.com/revanced/revanced-patches/commit/c3818befc6e342ff04bc8e9fde84a667c123d7ab))
+
+# [2.120.0](https://github.com/revanced/revanced-patches/compare/v2.119.0...v2.120.0) (2022-11-23)
+
+
+### Features
+
+* **youtube:** `remove-player-button-background` patch ([#855](https://github.com/revanced/revanced-patches/issues/855)) ([9e14817](https://github.com/revanced/revanced-patches/commit/9e14817fd2ef29b9f3ec161d74ab972ba19066b0))
+
+# [2.119.0](https://github.com/revanced/revanced-patches/compare/v2.118.1...v2.119.0) (2022-11-23)
+
+
+### Features
+
+* **twitch:** ˋintegrationsˋ dependency patch ([#1074](https://github.com/revanced/revanced-patches/issues/1074)) ([a84ca7e](https://github.com/revanced/revanced-patches/commit/a84ca7ec04389c3665358a7d239c615214fd327d))
+
+## [2.118.1](https://github.com/revanced/revanced-patches/compare/v2.118.0...v2.118.1) (2022-11-22)
+
+# [2.118.0](https://github.com/revanced/revanced-patches/compare/v2.117.1...v2.118.0) (2022-11-22)
+
+
+### Features
+
+* **youtube:** bump patches compatibility to v17.45.36 ([ab7dfd8](https://github.com/revanced/revanced-patches/commit/ab7dfd8ee1d902095811ed91b80aa0ed362ee9c5))
+
+## [2.117.1](https://github.com/revanced/revanced-patches/compare/v2.117.0...v2.117.1) (2022-11-22)
+
+
+### Bug Fixes
+
+* **youtube/general-ads:** hide reels shelf ([a436663](https://github.com/revanced/revanced-patches/commit/a436663e7755fc714c735df626d39bbd94f83dbb))
+
+# [2.117.0](https://github.com/revanced/revanced-patches/compare/v2.116.0...v2.117.0) (2022-11-21)
+
+
+### Features
+
+* **tiktok:** `tiktok-web-login` patch ([#593](https://github.com/revanced/revanced-patches/issues/593)) ([233e578](https://github.com/revanced/revanced-patches/commit/233e578b4d1cd5e783369e0c855e8246d158f8ed))
+
+# [2.116.0](https://github.com/revanced/revanced-patches/compare/v2.115.0...v2.116.0) (2022-11-21)
+
+
+### Features
+
+* **youtube/theme:** make amoled the default color for dark theme background" ([2b68ac7](https://github.com/revanced/revanced-patches/commit/2b68ac7796e54ed07c697909a84b3847f4ed7f01))
+
+# [2.115.0](https://github.com/revanced/revanced-patches/compare/v2.114.2...v2.115.0) (2022-11-21)
+
+
+### Features
+
+* **youtube/general-ads:** normalize switch names ([7636180](https://github.com/revanced/revanced-patches/commit/763618002ab6ccd9cd23889275c0e9b23642763e))
+* **youtube/theme:** exclude the patch by default ([57358ed](https://github.com/revanced/revanced-patches/commit/57358edc4cf3ae6b05cfb51359f66fc94c71cb5c))
+* **youtube/theme:** make dark the default color for dark theme background ([db44fa6](https://github.com/revanced/revanced-patches/commit/db44fa6f5adad19384970dad58784b2bf5fe1007))
+
+## [2.114.2](https://github.com/revanced/revanced-patches/compare/v2.114.1...v2.114.2) (2022-11-20)
+
+
+### Bug Fixes
+
+* **youtube/general-ads:** hide bytecode home ad view ([4976ad4](https://github.com/revanced/revanced-patches/commit/4976ad44b1867c93e976d91075b5bcead8d69c90))
+* **youtube/general-ads:** remove unused switch ([43cddcc](https://github.com/revanced/revanced-patches/commit/43cddcc85830524cd88d87ae1178f11451f158d8))
+
+## [2.114.1](https://github.com/revanced/revanced-patches/compare/v2.114.0...v2.114.1) (2022-11-20)
+
+
+### Bug Fixes
+
+* **tiktok/settings:** make compatible with newer versions ([#1057](https://github.com/revanced/revanced-patches/issues/1057)) ([e1d5d64](https://github.com/revanced/revanced-patches/commit/e1d5d6492ed660f43c87c796a59e766ea6b8ead0))
+
+# [2.114.0](https://github.com/revanced/revanced-patches/compare/v2.113.0...v2.114.0) (2022-11-20)
+
+
+### Features
+
+* **youtube:** `disable-zoom-haptics` patch ([#1079](https://github.com/revanced/revanced-patches/issues/1079)) ([a7cfba5](https://github.com/revanced/revanced-patches/commit/a7cfba54fbb3ee0ee5511a45d0b3c4620eb0c861))
+
+# [2.113.0](https://github.com/revanced/revanced-patches/compare/v2.112.0...v2.113.0) (2022-11-19)
+
+
+### Bug Fixes
+
+* **youtube/general-ads:** hide ads with buttons ([97fd3fd](https://github.com/revanced/revanced-patches/commit/97fd3fda79ff4bb2fadd0e6c4aa80b96d71f46da))
+
+
+### Features
+
+* **youtube/general-ads:** custom component filter ([b5e81d0](https://github.com/revanced/revanced-patches/commit/b5e81d0eaff3ab67a59ccd45eac6bf9414ed1380))
+* **youtube/general-ads:** hide gray separators ([59693f0](https://github.com/revanced/revanced-patches/commit/59693f007192c195258cea3a222d7fb94b6385c2))
+
+# [2.112.0](https://github.com/revanced/revanced-patches/compare/v2.111.3...v2.112.0) (2022-11-19)
+
+
+### Features
+
+* **anytracker:** `unlock-premium` patch ([#1076](https://github.com/revanced/revanced-patches/issues/1076)) ([52b3d90](https://github.com/revanced/revanced-patches/commit/52b3d909bbe59dafaea0cb9da1c27bdd0cda502f))
+
+## [2.111.3](https://github.com/revanced/revanced-patches/compare/v2.111.2...v2.111.3) (2022-11-18)
+
+
+### Bug Fixes
+
+* **youtube/litho-filter:** use correct type for switch case ([#1068](https://github.com/revanced/revanced-patches/issues/1068)) ([ab03511](https://github.com/revanced/revanced-patches/commit/ab03511e23d07c7c40b58eae5791fb2a798289de))
+
+## [2.111.2](https://github.com/revanced/revanced-patches/compare/v2.111.1...v2.111.2) (2022-11-18)
+
+
+### Bug Fixes
+
+* **youtube/general-ads:** import correct compatibility annotation ([#1065](https://github.com/revanced/revanced-patches/issues/1065)) ([db2085c](https://github.com/revanced/revanced-patches/commit/db2085c217311a8b7d45a01c03c6162bc60a9298))
+
+## [2.111.1](https://github.com/revanced/revanced-patches/compare/v2.111.0...v2.111.1) (2022-11-18)
+
+# [2.111.0](https://github.com/revanced/revanced-patches/compare/v2.110.0...v2.111.0) (2022-11-15)
+
+
+### Features
+
+* **iconpackstudio:** `unlock-pro` patch ([#1047](https://github.com/revanced/revanced-patches/issues/1047)) ([fcbca9c](https://github.com/revanced/revanced-patches/commit/fcbca9cf0ff3940c48df53a9e5e4d06d65f9a1b1))
+
+# [2.110.0](https://github.com/revanced/revanced-patches/compare/v2.109.0...v2.110.0) (2022-11-15)
+
+
+### Features
+
+* **youtube/debugging:** include by default & add option to debug on Android ([9d6d5b2](https://github.com/revanced/revanced-patches/commit/9d6d5b29227980aacb4c1331161afb3fa265bbe4))
+* **youtube/debugging:** use more suitable preference title ([d182351](https://github.com/revanced/revanced-patches/commit/d182351e789bcbfb7982774406449e8d973765b2))
+* **youtube/general-ads:** hide chapter teaser under videos ([9cf415a](https://github.com/revanced/revanced-patches/commit/9cf415a894ab5458134ef1cd5a4c5bf7579e592c))
+* **youtube/general-ads:** hide self sponsored cards ([b627d75](https://github.com/revanced/revanced-patches/commit/b627d751b49c228e52f7f9fb5d2bcacb9d243a64))
+* **youtube/general-ads:** hide shorts in search ([c01ba5c](https://github.com/revanced/revanced-patches/commit/c01ba5cbcc694f8c9ee83a4e9eb081cf5b45753c))
+
+# [2.109.0](https://github.com/revanced/revanced-patches/compare/v2.108.0...v2.109.0) (2022-11-13)
+
+
+### Features
+
+* **twitch:** `block-audio-ads` patch ([#1041](https://github.com/revanced/revanced-patches/issues/1041)) ([3419bae](https://github.com/revanced/revanced-patches/commit/3419baed489ce231f84b3e2d600cf123cdfa7ff7))
+* **twitch:** `block-video-ads` patch ([#1040](https://github.com/revanced/revanced-patches/issues/1040)) ([b47bf24](https://github.com/revanced/revanced-patches/commit/b47bf24d1b52cfa1883a942894a6216ae81b8325))
+
+# [2.108.0](https://github.com/revanced/revanced-patches/compare/v2.107.0...v2.108.0) (2022-11-13)
+
+
+### Features
+
+* **twitch:** `debug-mode` patch ([#1031](https://github.com/revanced/revanced-patches/issues/1031)) ([c514860](https://github.com/revanced/revanced-patches/commit/c514860bc4a46295b11f09df9ffc1c556b798eb3))
+
+# [2.107.0](https://github.com/revanced/revanced-patches/compare/v2.106.1...v2.107.0) (2022-11-13)
+
+
+### Features
+
+* **ticktick:** `unlock-themes` patch ([#1028](https://github.com/revanced/revanced-patches/issues/1028)) ([7f1fedc](https://github.com/revanced/revanced-patches/commit/7f1fedcddd4330a5f884b813a20f2f8e84e2c9da))
+* **twitch/show-deleted-messages:** `show-deleted-messages` patch ([#1030](https://github.com/revanced/revanced-patches/issues/1030)) ([7e6b453](https://github.com/revanced/revanced-patches/commit/7e6b4534013a03ddc7eb11a1f911fa0564372118))
+
+## [2.106.1](https://github.com/revanced/revanced-patches/compare/v2.106.0...v2.106.1) (2022-11-11)
+
+
+### Bug Fixes
+
+* **youtube/custom-branding:** use high resolution icons ([#1018](https://github.com/revanced/revanced-patches/issues/1018)) ([1c9d1ac](https://github.com/revanced/revanced-patches/commit/1c9d1acf2b7aff4cd52d17009ff01246f74d2214))
+
+# [2.106.0](https://github.com/revanced/revanced-patches/compare/v2.105.0...v2.106.0) (2022-11-09)
+
+
+### Features
+
+* **youtube:** `open-links-directly` patch ([#999](https://github.com/revanced/revanced-patches/issues/999)) ([bf85f62](https://github.com/revanced/revanced-patches/commit/bf85f62f30242f91bdaf53b44ae6a9a2b3b6e1b4))
+
+# [2.105.0](https://github.com/revanced/revanced-patches/compare/v2.104.2...v2.105.0) (2022-11-09)
+
+
+### Features
+
+* **nyx:** `unlock-pro` patch ([#1004](https://github.com/revanced/revanced-patches/issues/1004)) ([9dd8239](https://github.com/revanced/revanced-patches/commit/9dd82391f3f97247224e09e0143b2272a3d47053))
+
+## [2.104.2](https://github.com/revanced/revanced-patches/compare/v2.104.1...v2.104.2) (2022-11-08)
+
+
+### Bug Fixes
+
+* **youtube/hide-endscreen-cards:** restore functionality ([#993](https://github.com/revanced/revanced-patches/issues/993)) ([eb1bae2](https://github.com/revanced/revanced-patches/commit/eb1bae2c55e65ad29030dce6746a18662dd0fe25))
+
+## [2.104.1](https://github.com/revanced/revanced-patches/compare/v2.104.0...v2.104.1) (2022-11-07)
+
+
+### Bug Fixes
+
+* **youtube/general-ads:** incorrect description for info panels ([77b029e](https://github.com/revanced/revanced-patches/commit/77b029e82e481a13516b1c8a888c42817507cdea))
+* **youtube/hide-info-cards:** remove initial popup of info-cards ([#992](https://github.com/revanced/revanced-patches/issues/992)) ([19f3f03](https://github.com/revanced/revanced-patches/commit/19f3f038585c313a969adf3d4095a60ab4c83ede))
+
+# [2.104.0](https://github.com/revanced/revanced-patches/compare/v2.103.0...v2.104.0) (2022-11-06)
+
+
+### Features
+
+* **hexeditor:** `disable-ads` patch ([#973](https://github.com/revanced/revanced-patches/issues/973)) ([a48e5fd](https://github.com/revanced/revanced-patches/commit/a48e5fd50dcf9ee061ffd5c5ed0b997067f40652))
+
+# [2.103.0](https://github.com/revanced/revanced-patches/compare/v2.102.1...v2.103.0) (2022-11-05)
+
+
+### Bug Fixes
+
+* apply multiple changes from integrations, refactor package structure, class names and implementations ([e756fac](https://github.com/revanced/revanced-patches/commit/e756face362e4abf2c7b17afa536feed9b0cd631))
+* **youtube/video-information:** use correct compatibility annotation ([b42e048](https://github.com/revanced/revanced-patches/commit/b42e04884bb9a60c3df30b668bfba55b1cf8eaa4))
+
+
+### Features
+
+* **debugging:** simplify patch name and description ([61762f7](https://github.com/revanced/revanced-patches/commit/61762f76c457f348ef599e146f3853e941d4c45c))
+
+## [2.102.1](https://github.com/revanced/revanced-patches/compare/v2.102.0...v2.102.1) (2022-11-04)
+
+
+### Bug Fixes
+
+* use original app name ([#977](https://github.com/revanced/revanced-patches/issues/977)) ([ffc3c08](https://github.com/revanced/revanced-patches/commit/ffc3c08d96734893121397d506a97bd50b06bc1d))
+
+# [2.102.0](https://github.com/revanced/revanced-patches/compare/v2.101.0...v2.102.0) (2022-11-03)
+
+
+### Features
+
+* **youtube-music:** bump patches compatibility to v5.31.50 ([#962](https://github.com/revanced/revanced-patches/issues/962)) ([476fcc8](https://github.com/revanced/revanced-patches/commit/476fcc87ca0d33cfbf251e4307755f02ed8f8baa))
+
+# [2.101.0](https://github.com/revanced/revanced-patches/compare/v2.100.3...v2.101.0) (2022-11-01)
+
+
+### Features
+
+* **youtube:** bump patches compatibility to v17.43.36 ([#924](https://github.com/revanced/revanced-patches/issues/924)) ([3867514](https://github.com/revanced/revanced-patches/commit/38675144dcb616474a047bcf8f7e1bf1f668ea46))
+
+## [2.100.3](https://github.com/revanced/revanced-patches/compare/v2.100.2...v2.100.3) (2022-11-01)
+
+
+### Bug Fixes
+
+* **youtube/video-ads:** add switch to temporarily fix buffering issues ([7aa3bce](https://github.com/revanced/revanced-patches/commit/7aa3bce6ccd669a66de10ef6ffe2151f27b40365))
+
+## [2.100.2](https://github.com/revanced/revanced-patches/compare/v2.100.1...v2.100.2) (2022-10-31)
+
+
+### Bug Fixes
+
+* **youtube/theme-patch:** respect app specific theme ([#946](https://github.com/revanced/revanced-patches/issues/946)) ([fe8d6c7](https://github.com/revanced/revanced-patches/commit/fe8d6c7b2c10be5cca0717b6a03cfa1bef10e2c0))
+
+## [2.100.1](https://github.com/revanced/revanced-patches/compare/v2.100.0...v2.100.1) (2022-10-31)
+
+# [2.100.0](https://github.com/revanced/revanced-patches/compare/v2.99.0...v2.100.0) (2022-10-30)
+
+
+### Features
+
+* `hide-watch-in-vr` patch ([#911](https://github.com/revanced/revanced-patches/issues/911)) ([8ea0b20](https://github.com/revanced/revanced-patches/commit/8ea0b20e96df53211039df0468b2d4a735a381f1))
+
+# [2.99.0](https://github.com/revanced/revanced-patches/compare/v2.98.0...v2.99.0) (2022-10-29)
+
+
+### Features
+
+* **youtube/sponsorblock:** skip segments once automatically ([#907](https://github.com/revanced/revanced-patches/issues/907)) ([afc7588](https://github.com/revanced/revanced-patches/commit/afc75882062a9316dbc28a9dc4f990cf88b52654))
+
+# [2.98.0](https://github.com/revanced/revanced-patches/compare/v2.97.0...v2.98.0) (2022-10-29)
+
+
+### Features
+
+* **youtube/comments:** hide shorts comments button ([#866](https://github.com/revanced/revanced-patches/issues/866)) ([c78a74e](https://github.com/revanced/revanced-patches/commit/c78a74e21b4521034f44e9bfd226c5a362c21e6c))
+
+# [2.97.0](https://github.com/revanced/revanced-patches/compare/v2.96.0...v2.97.0) (2022-10-29)
+
+
+### Features
+
+* **youtube/microg-support:** handle availability of Vanced MicroG ([bfd0d14](https://github.com/revanced/revanced-patches/commit/bfd0d1418fb68f1d37687ec2072d3b64a9c3c6ee))
+
+# [2.96.0](https://github.com/revanced/revanced-patches/compare/v2.95.0...v2.96.0) (2022-10-28)
+
+
+### Features
+
+* **youtube/return-youtube-dislike:** compatibility for old and new button layout ([6629bd7](https://github.com/revanced/revanced-patches/commit/6629bd71716a753be06617724e536b8ab4c3b69a))
+
+# [2.95.0](https://github.com/revanced/revanced-patches/compare/v2.94.0...v2.95.0) (2022-10-28)
+
+
+### Features
+
+* **youtube:** bump patches compatibility to v17.41.37 ([#888](https://github.com/revanced/revanced-patches/issues/888)) ([11ed0c0](https://github.com/revanced/revanced-patches/commit/11ed0c0fb3236d87284806d2fa957699e908cc61))
+
+# [2.94.0](https://github.com/revanced/revanced-patches/compare/v2.93.0...v2.94.0) (2022-10-28)
+
+
+### Features
+
+* remove compatibility for YouTube v17.41.37 ([#886](https://github.com/revanced/revanced-patches/issues/886)) ([0f4bc19](https://github.com/revanced/revanced-patches/commit/0f4bc197455574b84dd1cb1a43f03af90858bc39))
+
+# [2.93.0](https://github.com/revanced/revanced-patches/compare/v2.92.3...v2.93.0) (2022-10-27)
+
+
+### Features
+
+* bump YouTube patches to v17.41.37 ([#878](https://github.com/revanced/revanced-patches/issues/878)) ([9072986](https://github.com/revanced/revanced-patches/commit/9072986f99e624386ff51c7eeb1d65158bd9249a))
+
+## [2.92.3](https://github.com/revanced/revanced-patches/compare/v2.92.2...v2.92.3) (2022-10-27)
+
+
+### Bug Fixes
+
+* **youtube:** resolve fingerprints on mutable methods ([10e4af3](https://github.com/revanced/revanced-patches/commit/10e4af386652d51c621519f7a67aac8c22ed8c18))
+
+## [2.92.2](https://github.com/revanced/revanced-patches/compare/v2.92.1...v2.92.2) (2022-10-27)
+
+
+### Bug Fixes
+
+* **youtube/theme:** theme missing gray color ([#873](https://github.com/revanced/revanced-patches/issues/873)) ([5267d56](https://github.com/revanced/revanced-patches/commit/5267d56a05b4053d556171ffd2d2870f3f932e8e))
+
+## [2.92.1](https://github.com/revanced/revanced-patches/compare/v2.92.0...v2.92.1) (2022-10-26)
+
+
+### Bug Fixes
+
+* **youtube/custom-branding:** use proper scaled icons ([24b5bcd](https://github.com/revanced/revanced-patches/commit/24b5bcdd703474c940fd436a37f0ae924d8b8c74))
+
+# [2.92.0](https://github.com/revanced/revanced-patches/compare/v2.91.0...v2.92.0) (2022-10-26)
+
+
+### Features
+
+* **youtube/hide-mix-playlists:** hide in video suggestions ([#854](https://github.com/revanced/revanced-patches/issues/854)) ([acde5f0](https://github.com/revanced/revanced-patches/commit/acde5f066053ca3d58ca09fc2a6a1381ba27d84b))
+
+# [2.91.0](https://github.com/revanced/revanced-patches/compare/v2.90.0...v2.91.0) (2022-10-26)
+
+
+### Features
+
+* remove partially complete patch ([d212e19](https://github.com/revanced/revanced-patches/commit/d212e19c3276c2ba6550f03dfd9bba47b7773524))
+
+# [2.90.0](https://github.com/revanced/revanced-patches/compare/v2.89.0...v2.90.0) (2022-10-25)
+
+
+### Features
+
+* `comment` patch ([#858](https://github.com/revanced/revanced-patches/issues/858)) ([472fb4f](https://github.com/revanced/revanced-patches/commit/472fb4f3747c835be2c5069a0f65017ab42f8d7e))
+
+# [2.89.0](https://github.com/revanced/revanced-patches/compare/v2.88.0...v2.89.0) (2022-10-25)
+
+
+### Features
+
+* `hide-album-cards` patch ([#857](https://github.com/revanced/revanced-patches/issues/857)) ([bce9d0c](https://github.com/revanced/revanced-patches/commit/bce9d0c9405d7afd5c51c2a9c2c247f3ff7581f5))
+
+# [2.88.0](https://github.com/revanced/revanced-patches/compare/v2.87.0...v2.88.0) (2022-10-25)
+
+
+### Features
+
+* `hide-artist-card` patch ([#859](https://github.com/revanced/revanced-patches/issues/859)) ([1f76246](https://github.com/revanced/revanced-patches/commit/1f7624680b86e3a7afd45521bfef86f88f52c976))
+
+# [2.87.0](https://github.com/revanced/revanced-patches/compare/v2.86.0...v2.87.0) (2022-10-25)
+
+
+### Bug Fixes
+
+* **metanav/fix-scaling:** use semantic versioning in package versions ([a9445a8](https://github.com/revanced/revanced-patches/commit/a9445a823e3a4885764cea9d08b51a1584d3238f))
+* **youtube/theme:** theme litho ui components & use correct theme for settings ([#791](https://github.com/revanced/revanced-patches/issues/791)) ([91c03c5](https://github.com/revanced/revanced-patches/commit/91c03c5624ca28ac13ee761261dea423f0ac42d7))
+
+
+### Features
+
+* `fix-metanav-scaling` patch ([#831](https://github.com/revanced/revanced-patches/issues/831)) ([4808e09](https://github.com/revanced/revanced-patches/commit/4808e099856e50a6f7e66834a92e2210cc84c8bc))
+* `hide-crowdfunding-box` patch ([#856](https://github.com/revanced/revanced-patches/issues/856)) ([3704ce2](https://github.com/revanced/revanced-patches/commit/3704ce22dbbff02b2e2d6dbdf9a74254a2511d3c))
+
+# [2.86.0](https://github.com/revanced/revanced-patches/compare/v2.85.2...v2.86.0) (2022-10-25)
+
+
+### Features
+
+* **youtube/theme:** extend theming to splash screen ([#769](https://github.com/revanced/revanced-patches/issues/769)) ([f7bb937](https://github.com/revanced/revanced-patches/commit/f7bb937ef2374d1042ea3772f03627d7f0111b78))
+
+## [2.85.2](https://github.com/revanced/revanced-patches/compare/v2.85.1...v2.85.2) (2022-10-22)
+
+
+### Bug Fixes
+
+* **youtube/integrations:** set context for remaining activities ([#828](https://github.com/revanced/revanced-patches/issues/828)) ([b2b6a3d](https://github.com/revanced/revanced-patches/commit/b2b6a3d1492bc2d5a6e27c68c74e3904764dda4b))
+
+## [2.85.1](https://github.com/revanced/revanced-patches/compare/v2.85.0...v2.85.1) (2022-10-21)
+
+
+### Bug Fixes
+
+* **youtube/theme:** extend dark mode theming ([#827](https://github.com/revanced/revanced-patches/issues/827)) ([6d803c5](https://github.com/revanced/revanced-patches/commit/6d803c5386e1f6f6fb6a7fa0a9f0bb81ee1022ac))
+
+# [2.85.0](https://github.com/revanced/revanced-patches/compare/v2.84.1...v2.85.0) (2022-10-20)
+
+
+### Features
+
+* **youtube:** `hide-captions-button` patch ([#770](https://github.com/revanced/revanced-patches/issues/770)) ([478af6a](https://github.com/revanced/revanced-patches/commit/478af6ad54ffdf6f99abab35a84a4f50b59d28c8))
+
+## [2.84.1](https://github.com/revanced/revanced-patches/compare/v2.84.0...v2.84.1) (2022-10-20)
+
+
+### Bug Fixes
+
+* **youtube/hide-mix-playlists:** correct switch title, summary and defaults ([#821](https://github.com/revanced/revanced-patches/issues/821)) ([f9eea33](https://github.com/revanced/revanced-patches/commit/f9eea332cea0b8c3d5c3dfff645b60861e925b5a))
+
+# [2.84.0](https://github.com/revanced/revanced-patches/compare/v2.83.3...v2.84.0) (2022-10-20)
+
+
+### Features
+
+* **youtube:** `hide-mix-playlists` patch ([#816](https://github.com/revanced/revanced-patches/issues/816)) ([e76c73b](https://github.com/revanced/revanced-patches/commit/e76c73b455c65aa605e418f19d88b64e0e244901))
+
+## [2.83.3](https://github.com/revanced/revanced-patches/compare/v2.83.2...v2.83.3) (2022-10-20)
+
+
+### Bug Fixes
+
+* **youtube/video-ads:** revert faulty method patch ([#820](https://github.com/revanced/revanced-patches/issues/820)) ([666248a](https://github.com/revanced/revanced-patches/commit/666248ae65d1ee84d1a278714c0dcd2528c5dd14))
+
+## [2.83.2](https://github.com/revanced/revanced-patches/compare/v2.83.1...v2.83.2) (2022-10-19)
+
+
+### Bug Fixes
+
+* **youtube/video-ads:** add back initial method to block ads ([#818](https://github.com/revanced/revanced-patches/issues/818)) ([8b02acd](https://github.com/revanced/revanced-patches/commit/8b02acd3b33c4702d81ec46dccb35604ec5fbe02))
+
+## [2.83.1](https://github.com/revanced/revanced-patches/compare/v2.83.0...v2.83.1) (2022-10-19)
+
+
+### Bug Fixes
+
+* **youtube/video-ads:** block earlier in call hirarchy ([#813](https://github.com/revanced/revanced-patches/issues/813)) ([f9517d2](https://github.com/revanced/revanced-patches/commit/f9517d2058c7561cf89b59ed36f690417dc076f7))
+
+# [2.83.0](https://github.com/revanced/revanced-patches/compare/v2.82.1...v2.83.0) (2022-10-18)
+
+
+### Features
+
+* bump YouTube Music patches compatibility ([#809](https://github.com/revanced/revanced-patches/issues/809)) ([09f2d8e](https://github.com/revanced/revanced-patches/commit/09f2d8e14937b582bc4bfb7d3a8bb2b7622f772d))
+
+## [2.82.1](https://github.com/revanced/revanced-patches/compare/v2.82.0...v2.82.1) (2022-10-17)
+
+
+### Bug Fixes
+
+* **youtube/video-ads:** return empty ad list earlier ([a47ce9b](https://github.com/revanced/revanced-patches/commit/a47ce9ba4f75673de23eb6054f5652224a48f43e))
+
+# [2.82.0](https://github.com/revanced/revanced-patches/compare/v2.81.11...v2.82.0) (2022-10-13)
+
+
+### Features
+
+* **youtube/remember-video-quality:** remember the quality until changed by default ([1aebabe](https://github.com/revanced/revanced-patches/commit/1aebabefdb0b163e0020fd3b52d7d1fa2404764b))
+
+## [2.81.11](https://github.com/revanced/revanced-patches/compare/v2.81.10...v2.81.11) (2022-10-11)
+
+
+### Bug Fixes
+
+* **youtube/theme:** add missing theme condition check ([#771](https://github.com/revanced/revanced-patches/issues/771)) ([40b70b9](https://github.com/revanced/revanced-patches/commit/40b70b9f9a24cfbf76bcd69f562b87e5cfade66f))
+
+## [2.81.10](https://github.com/revanced/revanced-patches/compare/v2.81.9...v2.81.10) (2022-10-11)
+
+
+### Bug Fixes
+
+* **spotify/disable-capture-restriction:** dynamically find indices ([#759](https://github.com/revanced/revanced-patches/issues/759)) ([2cc64c6](https://github.com/revanced/revanced-patches/commit/2cc64c61d1bc18a1717354085ddc60a251173837))
+
+## [2.81.9](https://github.com/revanced/revanced-patches/compare/v2.81.8...v2.81.9) (2022-10-11)
+
+
+### Bug Fixes
+
+* **youtube/general-ads:** hide ads on wide screens ([#765](https://github.com/revanced/revanced-patches/issues/765)) ([f0d2f3e](https://github.com/revanced/revanced-patches/commit/f0d2f3e01b90bbe6d2b55b941eaf96be2295cd3c))
+* **youtube/theme:** colore the comment action bar ([#762](https://github.com/revanced/revanced-patches/issues/762)) ([4b3abe0](https://github.com/revanced/revanced-patches/commit/4b3abe0d45ac8c0c5ae6cf4955681ba76ce10309))
+
+## [2.81.8](https://github.com/revanced/revanced-patches/compare/v2.81.7...v2.81.8) (2022-10-10)
+
+
+### Bug Fixes
+
+* **youtube/video-ads:** invert condition to hide ads ([e353ed0](https://github.com/revanced/revanced-patches/commit/e353ed0b0fe78feae65f1ad2f6567c1e9aa9c71d))
+
+## [2.81.7](https://github.com/revanced/revanced-patches/compare/v2.81.6...v2.81.7) (2022-10-10)
+
+
+### Bug Fixes
+
+* **video-ads:** invert the condition to hide ads ([a32af95](https://github.com/revanced/revanced-patches/commit/a32af951566a7187ae2618ef08f9298f78f8ea8c))
+
+## [2.81.6](https://github.com/revanced/revanced-patches/compare/v2.81.5...v2.81.6) (2022-10-10)
+
+
+### Bug Fixes
+
+* **video-ads:** return empty list in correct method ([bbcf050](https://github.com/revanced/revanced-patches/commit/bbcf0501d4be0c43eddb7a9b3c32394222efc8f0))
+
+## [2.81.5](https://github.com/revanced/revanced-patches/compare/v2.81.4...v2.81.5) (2022-10-10)
+
+
+### Bug Fixes
+
+* **video-ads:** clobber unused register ([0feb607](https://github.com/revanced/revanced-patches/commit/0feb6073dd3de5dc179ffa9fde2da24439986431))
+
+## [2.81.4](https://github.com/revanced/revanced-patches/compare/v2.81.3...v2.81.4) (2022-10-10)
+
+
+### Bug Fixes
+
+* **general-ads:** invalid smali syntax ([86224c1](https://github.com/revanced/revanced-patches/commit/86224c1f601c1bd22c0ce4dcf2a8e79ea3b8b2ae))
+
+## [2.81.3](https://github.com/revanced/revanced-patches/compare/v2.81.2...v2.81.3) (2022-10-10)
+
+
+### Bug Fixes
+
+* **video-ads:** return empty ad list instead of null ([f6c5027](https://github.com/revanced/revanced-patches/commit/f6c5027981a2b55bbcd2e55d294ebfd5e6f0b527))
+
+## [2.81.2](https://github.com/revanced/revanced-patches/compare/v2.81.1...v2.81.2) (2022-10-08)
+
+
+### Bug Fixes
+
+* **theme:** include coloring the playlist action bar ([#742](https://github.com/revanced/revanced-patches/issues/742)) ([6766ec8](https://github.com/revanced/revanced-patches/commit/6766ec8f31da6dd71fff0ae4b7044af5ec4a5dfd))
+
+## [2.81.1](https://github.com/revanced/revanced-patches/compare/v2.81.0...v2.81.1) (2022-10-07)
+
+
+### Bug Fixes
+
+* **video-ads:** block remaining video ads ([fbc83a2](https://github.com/revanced/revanced-patches/commit/fbc83a22d37012394311a87d27202b7cc86e047d))
+
+# [2.81.0](https://github.com/revanced/revanced-patches/compare/v2.80.0...v2.81.0) (2022-10-06)
+
+
+### Features
+
+* **youtube/general-ads:** do not hide community posts by default ([6269638](https://github.com/revanced/revanced-patches/commit/626963819c183cbfeca4ff4ab0a16fc39ed9d8a8))
+
+# [2.80.0](https://github.com/revanced/revanced-patches/compare/v2.79.2...v2.80.0) (2022-10-04)
+
+
+### Features
+
+* **youtube/remember-video-quality:** remember current quality option ([9c510d4](https://github.com/revanced/revanced-patches/commit/9c510d472048f5bce127eb0329e53aaf259e9b1b))
+
+## [2.79.2](https://github.com/revanced/revanced-patches/compare/v2.79.1...v2.79.2) (2022-10-04)
+
+
+### Bug Fixes
+
+* **youtube/disable-startup-shorts-player:** don't affect functionality of navigation bar ([#716](https://github.com/revanced/revanced-patches/issues/716)) ([05f5cc1](https://github.com/revanced/revanced-patches/commit/05f5cc17e3fcbf255c6fe5b503c9fd20c218f5a1))
+
+## [2.79.1](https://github.com/revanced/revanced-patches/compare/v2.79.0...v2.79.1) (2022-10-03)
+
+
+### Bug Fixes
+
+* **youtube/disable-startup-shorts-player:** do not prevent playing videos on startup ([#714](https://github.com/revanced/revanced-patches/issues/714)) ([9a0d379](https://github.com/revanced/revanced-patches/commit/9a0d3798afd75bba5c63e1b8803dc25814586be9))
+
+# [2.79.0](https://github.com/revanced/revanced-patches/compare/v2.78.0...v2.79.0) (2022-10-03)
+
+
+### Features
+
+* `hide-video-buttons` patch ([584803a](https://github.com/revanced/revanced-patches/commit/584803aa900f94de1b2b2a0c41fb0884fa9270d3))
+
+# [2.78.0](https://github.com/revanced/revanced-patches/compare/v2.77.1...v2.78.0) (2022-10-02)
+
+
+### Features
+
+* **youtube/general-ads:** check for ad in identifier ([fd0506e](https://github.com/revanced/revanced-patches/commit/fd0506eb95dbf398eb7d6bfa8732f517b9867545))
+
+## [2.77.1](https://github.com/revanced/revanced-patches/compare/v2.77.0...v2.77.1) (2022-10-01)
+
+
+### Bug Fixes
+
+* **tiktok/tiktok-downloads:** add missing patch dependencies ([#686](https://github.com/revanced/revanced-patches/issues/686)) ([5c3b4ed](https://github.com/revanced/revanced-patches/commit/5c3b4ed0530e89d50db3b5bb9893e1ef4b463bae))
+
+# [2.77.0](https://github.com/revanced/revanced-patches/compare/v2.76.3...v2.77.0) (2022-10-01)
+
+
+### Features
+
+* **youtube/hdr-auto-brightness:** increase strength of fingerprint ([2379b58](https://github.com/revanced/revanced-patches/commit/2379b5892b3505c8780f475bbc56f520613fa31d))
+
+## [2.76.3](https://github.com/revanced/revanced-patches/compare/v2.76.2...v2.76.3) (2022-10-01)
+
+
+### Performance Improvements
+
+* **general-ads:** hook pathBuilder ([b9bad0b](https://github.com/revanced/revanced-patches/commit/b9bad0bb922bbd44ddf2acbfae87243beb28851a))
+
+## [2.76.2](https://github.com/revanced/revanced-patches/compare/v2.76.1...v2.76.2) (2022-09-30)
+
+
+### Bug Fixes
+
+* **twitter/monochrome-icon:** add missing `File.write` call ([#682](https://github.com/revanced/revanced-patches/issues/682)) ([de22e3f](https://github.com/revanced/revanced-patches/commit/de22e3f03ef1d6db08f9446f02e687721d9017d7))
+
+## [2.76.1](https://github.com/revanced/revanced-patches/compare/v2.76.0...v2.76.1) (2022-09-30)
+
+
+### Bug Fixes
+
+* **disable-startup-shorts-player:** remove redundant opcode pattern ([#679](https://github.com/revanced/revanced-patches/issues/679)) ([5197a24](https://github.com/revanced/revanced-patches/commit/5197a24cc5ecc1b51cdc5c00c77c873b86394994))
+
+# [2.76.0](https://github.com/revanced/revanced-patches/compare/v2.75.2...v2.76.0) (2022-09-30)
+
+
+### Features
+
+* bump YouTube Music patches compatibility to v5.26.52 ([#681](https://github.com/revanced/revanced-patches/issues/681)) ([f195734](https://github.com/revanced/revanced-patches/commit/f195734925dd41596d058f9cbf2b7e619a8d5833))
+
+## [2.75.2](https://github.com/revanced/revanced-patches/compare/v2.75.1...v2.75.2) (2022-09-30)
+
+
+### Bug Fixes
+
+* **twitter patches.:** use wider compatible Java API for writing to file ([#678](https://github.com/revanced/revanced-patches/issues/678)) ([4448de8](https://github.com/revanced/revanced-patches/commit/4448de80a6277862d81c7a29b9489e4fbec48496))
+
+## [2.75.1](https://github.com/revanced/revanced-patches/compare/v2.75.0...v2.75.1) (2022-09-29)
+
+
+### Bug Fixes
+
+* **disable-startup-shorts-player:** incorrect offsets, invert branch condition ([#672](https://github.com/revanced/revanced-patches/issues/672)) ([bfe1e38](https://github.com/revanced/revanced-patches/commit/bfe1e3808ece85fbb0785a4378ee95591115ac33))
+
+# [2.75.0](https://github.com/revanced/revanced-patches/compare/v2.74.0...v2.75.0) (2022-09-29)
+
+
+### Features
+
+* `disable-capture-restriction` patch ([#655](https://github.com/revanced/revanced-patches/issues/655)) ([3cc26c3](https://github.com/revanced/revanced-patches/commit/3cc26c31d378c27ca7f768f777daa00e3f849dff))
+
+# [2.74.0](https://github.com/revanced/revanced-patches/compare/v2.73.0...v2.74.0) (2022-09-29)
+
+
+### Features
+
+* `disable-startup-shorts-player` patch ([#670](https://github.com/revanced/revanced-patches/issues/670)) ([feb3bd0](https://github.com/revanced/revanced-patches/commit/feb3bd02aaf67379733d10988fd58b0c3924f88e))
+
+# [2.73.0](https://github.com/revanced/revanced-patches/compare/v2.72.1...v2.73.0) (2022-09-29)
+
+
+### Features
+
+* `dynamic-color` patch ([#652](https://github.com/revanced/revanced-patches/issues/652)) ([a16575b](https://github.com/revanced/revanced-patches/commit/a16575b984354138b9ab175307be8d15de60b6a6))
+
+## [2.72.1](https://github.com/revanced/revanced-patches/compare/v2.72.0...v2.72.1) (2022-09-29)
+
+
+### Bug Fixes
+
+* **hdr-brightness:** trim list of compatible versions ([#602](https://github.com/revanced/revanced-patches/issues/602)) ([3209d90](https://github.com/revanced/revanced-patches/commit/3209d902e4d1aa73b59ce059cf3f76ef2095af70))
+
+# [2.72.0](https://github.com/revanced/revanced-patches/compare/v2.71.2...v2.72.0) (2022-09-29)
+
+
+### Features
+
+* `tiktok-speed` patch ([#668](https://github.com/revanced/revanced-patches/issues/668)) ([23fff16](https://github.com/revanced/revanced-patches/commit/23fff16e6ab02bf281d46d8b5f93788425d8b525))
+* bump YouTube Music patches to v5.25.51 ([#669](https://github.com/revanced/revanced-patches/issues/669)) ([563c846](https://github.com/revanced/revanced-patches/commit/563c8466568d578de7c8a8e869fb1aa74370784a))
+
+## [2.71.2](https://github.com/revanced/revanced-patches/compare/v2.71.1...v2.71.2) (2022-09-28)
+
+
+### Bug Fixes
+
+* **hide-email-address:** invalid instruction offsets ([#654](https://github.com/revanced/revanced-patches/issues/654)) ([1a3db44](https://github.com/revanced/revanced-patches/commit/1a3db44b5bd9628b7b25cc113a3a53bf8a85bd2b))
+
+## [2.71.1](https://github.com/revanced/revanced-patches/compare/v2.71.0...v2.71.1) (2022-09-28)
+
+
+### Bug Fixes
+
+* **disable-auto-player-popup-panels:** swap switch toggle state description ([#653](https://github.com/revanced/revanced-patches/issues/653)) ([f881301](https://github.com/revanced/revanced-patches/commit/f88130143689c7a63fd67df3dff37caec5db9548))
+
+# [2.71.0](https://github.com/revanced/revanced-patches/compare/v2.70.0...v2.71.0) (2022-09-28)
+
+
+### Features
+
+* `hide-email-address` patch ([#578](https://github.com/revanced/revanced-patches/issues/578)) ([82cb632](https://github.com/revanced/revanced-patches/commit/82cb6321beace1e5feed248d3f3d6ae56cf0d96b))
+
+# [2.70.0](https://github.com/revanced/revanced-patches/compare/v2.69.4...v2.70.0) (2022-09-28)
+
+
+### Features
+
+* `monochrome-icon` patch ([#643](https://github.com/revanced/revanced-patches/issues/643)) ([127c8e5](https://github.com/revanced/revanced-patches/commit/127c8e54e54709c2716029044ae337ad53daafad))
+
+## [2.69.4](https://github.com/revanced/revanced-patches/compare/v2.69.3...v2.69.4) (2022-09-27)
+
+
+### Bug Fixes
+
+* **sponsorblock:** dynamically insert `setSponsorBarRect` call ([#644](https://github.com/revanced/revanced-patches/issues/644)) ([998a249](https://github.com/revanced/revanced-patches/commit/998a249a23d09eb752b35c4da731f4223be40a3b))
+
+## [2.69.3](https://github.com/revanced/revanced-patches/compare/v2.69.2...v2.69.3) (2022-09-27)
+
+
+### Bug Fixes
+
+* **sponsorblock:** correct class name for field reference ([#582](https://github.com/revanced/revanced-patches/issues/582)) ([7cd585c](https://github.com/revanced/revanced-patches/commit/7cd585cd78e284a82cee17d09f8049f0a5cdf2e8))
+
+## [2.69.3](https://github.com/revanced/revanced-patches/compare/v2.69.2...v2.69.3) (2022-09-27)
+
+
+### Bug Fixes
+
+* **sponsorblock:** correct class name for field reference ([#582](https://github.com/revanced/revanced-patches/issues/582)) ([7cd585c](https://github.com/revanced/revanced-patches/commit/7cd585cd78e284a82cee17d09f8049f0a5cdf2e8))
+
+## [2.69.2](https://github.com/revanced/revanced-patches/compare/v2.69.1...v2.69.2) (2022-09-26)
+
+
+### Performance Improvements
+
+* **resource-mapping:** map resources with multiple threads ([a7e4da0](https://github.com/revanced/revanced-patches/commit/a7e4da018bf939accdf6d406b471ac74f9078095))
+
+## [2.69.1](https://github.com/revanced/revanced-patches/compare/v2.69.0...v2.69.1) (2022-09-24)
+
+
+### Bug Fixes
+
+* **sponsorblock:** reflect changes to strings ([#585](https://github.com/revanced/revanced-patches/issues/585)) ([d03568a](https://github.com/revanced/revanced-patches/commit/d03568aa39195a07bec62f43d929923825c67d3f))
+
+# [2.69.0](https://github.com/revanced/revanced-patches/compare/v2.68.3...v2.69.0) (2022-09-24)
+
+
+### Features
+
+* `spotify-theme` patch ([#608](https://github.com/revanced/revanced-patches/issues/608)) ([7ee1b78](https://github.com/revanced/revanced-patches/commit/7ee1b78e8048698ef6490445dd012e2d88b4d332))
+
+## [2.68.3](https://github.com/revanced/revanced-patches/compare/v2.68.2...v2.68.3) (2022-09-23)
+
+
+### Bug Fixes
+
+* **seekbar-tapping:** do not disable seekbar when hiding it ([#600](https://github.com/revanced/revanced-patches/issues/600)) ([68a9457](https://github.com/revanced/revanced-patches/commit/68a9457464c786a61b756eb18ca5f1ce05316636))
+
+## [2.68.2](https://github.com/revanced/revanced-patches/compare/v2.68.1...v2.68.2) (2022-09-23)
+
+
+### Bug Fixes
+
+* **hide-time-and-seekbar:** don't draw the seekbar ([#594](https://github.com/revanced/revanced-patches/issues/594)) ([46e0195](https://github.com/revanced/revanced-patches/commit/46e0195f760dc11ec8d2068ffb57997eccab1aff))
+
+## [2.68.1](https://github.com/revanced/revanced-patches/compare/v2.68.0...v2.68.1) (2022-09-23)
+
+
+### Bug Fixes
+
+* **sponsorblock:** resolve unresolved fingerprint ([e7296f3](https://github.com/revanced/revanced-patches/commit/e7296f3424bed78a0149cca9bf6e29243e962150))
+
+# [2.68.0](https://github.com/revanced/revanced-patches/compare/v2.67.1...v2.68.0) (2022-09-23)
+
+
+### Bug Fixes
+
+* **hide-premium-navbar:** remove the correct instructions ([#591](https://github.com/revanced/revanced-patches/issues/591)) ([b4b0c97](https://github.com/revanced/revanced-patches/commit/b4b0c972d28cf7591ece04d154d1183c8431cb77))
+
+
+### Features
+
+* **tiktok:** adapt `tiktok-download` with `tiktok-settings`. ([#586](https://github.com/revanced/revanced-patches/issues/586)) ([876b726](https://github.com/revanced/revanced-patches/commit/876b7266109b099597dd19d6ed3fa3dac098b73b))
+
+## [2.67.1](https://github.com/revanced/revanced-patches/compare/v2.67.0...v2.67.1) (2022-09-23)
+
+# [2.67.0](https://github.com/revanced/revanced-patches/compare/v2.66.2...v2.67.0) (2022-09-23)
+
+
+### Bug Fixes
+
+* **hide-premium-nav-bar:** invalid import ([#590](https://github.com/revanced/revanced-patches/issues/590)) ([37ad223](https://github.com/revanced/revanced-patches/commit/37ad22360e6ac5e1b1dce474fe6a4e3813e03330))
+
+
+### Features
+
+* `hide-premium-nav-bar` patch ([#589](https://github.com/revanced/revanced-patches/issues/589)) ([1811416](https://github.com/revanced/revanced-patches/commit/18114168d7f8c3dd41d1f124c3524c7ca850d33d))
+
+## [2.66.2](https://github.com/revanced/revanced-patches/compare/v2.66.1...v2.66.2) (2022-09-22)
+
+
+### Bug Fixes
+
+* **custom-playback-speed:** implement own method instead of `takeWhile` ([8522d4c](https://github.com/revanced/revanced-patches/commit/8522d4cd705118bf1108ec88bbed542a0cb15943))
+
+## [2.66.1](https://github.com/revanced/revanced-patches/compare/v2.66.0...v2.66.1) (2022-09-22)
+
+
+### Bug Fixes
+
+* **sponsorblock:** broken fingerprint and invert setting `shorts_playing` ([#579](https://github.com/revanced/revanced-patches/issues/579)) ([59fb674](https://github.com/revanced/revanced-patches/commit/59fb674437207aeabed5b92ba32bda120820fb8f))
+
+# [2.66.0](https://github.com/revanced/revanced-patches/compare/v2.65.0...v2.66.0) (2022-09-22)
+
+
+### Features
+
+* **custom-playback-speed:** max, min, granularity option ([b1e423d](https://github.com/revanced/revanced-patches/commit/b1e423d9cdca51e54c154cc39a24c508ca322f60))
+
+# [2.65.0](https://github.com/revanced/revanced-patches/compare/v2.64.2...v2.65.0) (2022-09-21)
+
+
+### Features
+
+* `tiktok-feed-filter` , `tiktok-settings` and `tiktok-force-login` patch ([#501](https://github.com/revanced/revanced-patches/issues/501)) ([10e0286](https://github.com/revanced/revanced-patches/commit/10e028626f2b9d9d01279682198daf135bc82c20))
+* **theme:** arbitrary background color for light theme ([#565](https://github.com/revanced/revanced-patches/issues/565)) ([da40e7e](https://github.com/revanced/revanced-patches/commit/da40e7e0cf266edc89353e62b9821b450f8f0295))
+
+## [2.64.2](https://github.com/revanced/revanced-patches/compare/v2.64.1...v2.64.2) (2022-09-21)
+
+
+### Bug Fixes
+
+* **custom-video-buffer:** use correct offset for `getMaxBuffer` call injection ([6773122](https://github.com/revanced/revanced-patches/commit/6773122d0b1598af78d4619b8af2811be11f6b95))
+
+## [2.64.1](https://github.com/revanced/revanced-patches/compare/v2.64.0...v2.64.1) (2022-09-21)
+
+# [2.64.0](https://github.com/revanced/revanced-patches/compare/v2.63.0...v2.64.0) (2022-09-21)
+
+
+### Features
+
+* explicit support for YouTube v17.34.35 and v17.34.36 ([#527](https://github.com/revanced/revanced-patches/issues/527)) ([1b6a64b](https://github.com/revanced/revanced-patches/commit/1b6a64b49c54ab53df463981a9ad7ec67729939f))
+
+# [2.63.0](https://github.com/revanced/revanced-patches/compare/v2.62.0...v2.63.0) (2022-09-21)
+
+
+### Features
+
+* `disable-auto-player-popup-panels` ([#543](https://github.com/revanced/revanced-patches/issues/543)) ([d1fa2c3](https://github.com/revanced/revanced-patches/commit/d1fa2c3bc359018146f1f90b1ed4448b7f19b247))
+* disable sponsorblock on shorts ([#439](https://github.com/revanced/revanced-patches/issues/439)) ([47fbcba](https://github.com/revanced/revanced-patches/commit/47fbcba3f845b5c3ef35c4dded8d6f9ffafc5d9e))
+
+# [2.62.0](https://github.com/revanced/revanced-patches/compare/v2.61.1...v2.62.0) (2022-09-21)
+
+
+### Features
+
+* `hide-time-and-seekbar` patch ([#544](https://github.com/revanced/revanced-patches/issues/544)) ([ef7afc7](https://github.com/revanced/revanced-patches/commit/ef7afc7231f8fa6b9b7f84c2b5b9a9fda80aa0f4))
+
+## [2.61.1](https://github.com/revanced/revanced-patches/compare/v2.61.0...v2.61.1) (2022-09-21)
+
+
+### Bug Fixes
+
+* **sponsorblock:** reflect strings from official guidelines ([#523](https://github.com/revanced/revanced-patches/issues/523)) ([1c83959](https://github.com/revanced/revanced-patches/commit/1c83959e69bfbf5140edbf7ef4d2c8901975bfd4))
+
+# [2.61.0](https://github.com/revanced/revanced-patches/compare/v2.60.0...v2.61.0) (2022-09-20)
+
+
+### Features
+
+* section `acknowledgements` for issue templates ([f663935](https://github.com/revanced/revanced-patches/commit/f663935382036cfdccf04627ca77306c5f951b4c))
+
+# [2.60.0](https://github.com/revanced/revanced-patches/compare/v2.59.0...v2.60.0) (2022-09-19)
+
+
+### Features
+
+* **downloads:** missing package installation request ([bc9e81a](https://github.com/revanced/revanced-patches/commit/bc9e81a6c343a912cab6fe3c2560d759fbf1be8a))
+
+# [2.59.0](https://github.com/revanced/revanced-patches/compare/v2.58.0...v2.59.0) (2022-09-18)
+
+
+### Features
+
+* **sponsorblock:** string for response error code 400 ([#512](https://github.com/revanced/revanced-patches/issues/512)) ([d42569f](https://github.com/revanced/revanced-patches/commit/d42569fcc4367fa65c5c88ddc75098d4e208f7f3))
+
+# [2.58.0](https://github.com/revanced/revanced-patches/compare/v2.57.0...v2.58.0) (2022-09-18)
+
+
+### Bug Fixes
+
+* **hdr-auto-brightness:** increase patching compatibility across versions ([bc5c8c1](https://github.com/revanced/revanced-patches/commit/bc5c8c118d5f95905fb7f0fb6afaf97bdcb753bf))
+* **hide-create-button:** increase patching compatibility across versions ([b217a1e](https://github.com/revanced/revanced-patches/commit/b217a1e89f4b9e3ad030ece08a1b81e507c7cc02))
+* **hide-shorts-button:** increase patching compatibility across versions ([7f17934](https://github.com/revanced/revanced-patches/commit/7f1793490f25b9fa16539e80cdc417b3b3e0e639))
+
+
+### Features
+
+* bump patches compatibility to v17.36.37 ([4493d7e](https://github.com/revanced/revanced-patches/commit/4493d7e6c7d76760cf4bc93968bf425b23c994de))
+
+# [2.57.0](https://github.com/revanced/revanced-patches/compare/v2.56.1...v2.57.0) (2022-09-16)
+
+
+### Features
+
+* **amoled:** remove in favour of `theme` patch ([0032301](https://github.com/revanced/revanced-patches/commit/00323010ed7356a639e4e86916dcbae96a53f7f6))
+* **theme:** arbitrary background color for dark theme ([afd5502](https://github.com/revanced/revanced-patches/commit/afd5502afdb207d5f52516653869b26fae7e5d21))
+
+## [2.56.1](https://github.com/revanced/revanced-patches/compare/v2.56.0...v2.56.1) (2022-09-15)
+
+
+### Bug Fixes
+
+* `auto-captions` patch switch description ([#488](https://github.com/revanced/revanced-patches/issues/488)) ([57824fb](https://github.com/revanced/revanced-patches/commit/57824fb16c56889d8f18299e9bab2563bb13e5c2))
+
+# [2.56.0](https://github.com/revanced/revanced-patches/compare/v2.55.0...v2.56.0) (2022-09-15)
+
+
+### Features
+
+* **tiktok-download:** custom download path ([#479](https://github.com/revanced/revanced-patches/issues/479)) ([944aa48](https://github.com/revanced/revanced-patches/commit/944aa485cec513faa30a21b91887dcacf9d70b45))
+
+# [2.55.0](https://github.com/revanced/revanced-patches/compare/v2.54.1...v2.55.0) (2022-09-15)
+
+
+### Features
+
+* `disable-auto-captions` patch ([#435](https://github.com/revanced/revanced-patches/issues/435)) ([8ee12d0](https://github.com/revanced/revanced-patches/commit/8ee12d0beadfd0a8fdce0a331646e8786527e84d))
+
+## [2.54.1](https://github.com/revanced/revanced-patches/compare/v2.54.0...v2.54.1) (2022-09-14)
+
+
+### Bug Fixes
+
+* alignment of download button icon ([#477](https://github.com/revanced/revanced-patches/issues/477)) ([2093f11](https://github.com/revanced/revanced-patches/commit/2093f11e762c471a0987fb57f03c7f48d565fdce))
+
+# [2.54.0](https://github.com/revanced/revanced-patches/compare/v2.53.0...v2.54.0) (2022-09-14)
+
+
+### Features
+
+* bump YouTube Music patches to v5.23.50 ([#462](https://github.com/revanced/revanced-patches/issues/462)) ([15c10b1](https://github.com/revanced/revanced-patches/commit/15c10b1e0368a021b0f2ec46bc2e9ea84a5d5afd))
+
+# [2.53.0](https://github.com/revanced/revanced-patches/compare/v2.52.3...v2.53.0) (2022-09-13)
+
+
+### Features
+
+* patch requests issue template ([#467](https://github.com/revanced/revanced-patches/issues/467)) ([d8bb354](https://github.com/revanced/revanced-patches/commit/d8bb35472258c0634599102f4f4bf4cea587a817)), closes [revanced/revanced-patches#463](https://github.com/revanced/revanced-patches/issues/463)
+
+## [2.52.3](https://github.com/revanced/revanced-patches/compare/v2.52.2...v2.52.3) (2022-09-11)
+
+
+### Bug Fixes
+
+* path for download icon ([#465](https://github.com/revanced/revanced-patches/issues/465)) ([bff22c3](https://github.com/revanced/revanced-patches/commit/bff22c3a154fd8059f4f8995a303354633a50517))
+
+## [2.52.2](https://github.com/revanced/revanced-patches/compare/v2.52.1...v2.52.2) (2022-09-11)
+
+
+### Bug Fixes
+
+* design language for download icon ([#464](https://github.com/revanced/revanced-patches/issues/464)) ([c24dc17](https://github.com/revanced/revanced-patches/commit/c24dc175f705fd5364b8bcf01f27f3ac8ecc0e64))
+
+## [2.52.1](https://github.com/revanced/revanced-patches/compare/v2.52.0...v2.52.1) (2022-09-08)
+
+
+### Bug Fixes
+
+* multiple compatible pkgs on readme ([3f82a13](https://github.com/revanced/revanced-patches/commit/3f82a1349fb11b1152ac10d76deb930f7325b7c9))
+
+# [2.52.0](https://github.com/revanced/revanced-patches/compare/v2.51.3...v2.52.0) (2022-09-08)
+
+
+### Features
+
+* add deprecated & options to patches.json ([759315b](https://github.com/revanced/revanced-patches/commit/759315be1b23a6b3655fc87b56a8974ebac648cc))
+
+## [2.51.3](https://github.com/revanced/revanced-patches/compare/v2.51.2...v2.51.3) (2022-09-08)
+
+
+### Bug Fixes
+
+* cleanup & trigger release for ThemePatch ([8d25b21](https://github.com/revanced/revanced-patches/commit/8d25b214e297797092432b5fe1a4ede1f42115c8)), closes [#447](https://github.com/revanced/revanced-patches/issues/447) [revanced/revanced-patcher#99](https://github.com/revanced/revanced-patcher/issues/99)
+
+## [2.51.2](https://github.com/revanced/revanced-patches/compare/v2.51.1...v2.51.2) (2022-09-08)
+
+## [2.51.1](https://github.com/revanced/revanced-patches/compare/v2.51.0...v2.51.1) (2022-09-08)
+
+
+### Bug Fixes
+
+* wrap theme option ([150c173](https://github.com/revanced/revanced-patches/commit/150c173adce31d9984d6feca694b472649233056))
+
+# [2.51.0](https://github.com/revanced/revanced-patches/compare/v2.50.8...v2.51.0) (2022-09-08)
+
+
+### Features
+
+* Theme Patch ([#440](https://github.com/revanced/revanced-patches/issues/440)) ([f3b92ca](https://github.com/revanced/revanced-patches/commit/f3b92cab5a057ce605b6d9e75e3b99161bf1422c))
+
+## [2.50.8](https://github.com/revanced/revanced-patches/compare/v2.50.7...v2.50.8) (2022-09-08)
+
+## [2.50.7](https://github.com/revanced/revanced-patches/compare/v2.50.6...v2.50.7) (2022-09-08)
+
+## [2.50.6](https://github.com/revanced/revanced-patches/compare/v2.50.5...v2.50.6) (2022-09-07)
+
+## [2.50.5](https://github.com/revanced/revanced-patches/compare/v2.50.4...v2.50.5) (2022-09-07)
+
+## [2.50.4](https://github.com/revanced/revanced-patches/compare/v2.50.3...v2.50.4) (2022-09-03)
+
+
+### Bug Fixes
+
+* don't respect primary color for the download button icon ([#424](https://github.com/revanced/revanced-patches/issues/424)) ([3fa70f4](https://github.com/revanced/revanced-patches/commit/3fa70f48a206c56f6ca8aed88baed722e76281e2))
+
+## [2.50.3](https://github.com/revanced/revanced-patches/compare/v2.50.2...v2.50.3) (2022-09-01)
+
+
+### Bug Fixes
+
+* partial ad blockage in `tiktok-ads` patch ([#420](https://github.com/revanced/revanced-patches/issues/420)) ([bc6d192](https://github.com/revanced/revanced-patches/commit/bc6d19205940e3b4b228a9b5de627a2260abd00e))
+
+## [2.50.2](https://github.com/revanced/revanced-patches/compare/v2.50.1...v2.50.2) (2022-08-31)
+
+
+### Bug Fixes
+
+* wrong dependency version ([1e63a57](https://github.com/revanced/revanced-patches/commit/1e63a57b86475670442e6a24a7f24c02dabee239))
+
+## [2.50.1](https://github.com/revanced/revanced-patches/compare/v2.50.0...v2.50.1) (2022-08-31)
+
+
+### Bug Fixes
+
+* resolve fingerprint in `premium-icon-reddit` patch ([#413](https://github.com/revanced/revanced-patches/issues/413)) ([60ec2c6](https://github.com/revanced/revanced-patches/commit/60ec2c6f8a29cfc36313617a92e976f01213ce00))
+
+# [2.50.0](https://github.com/revanced/revanced-patches/compare/v2.49.0...v2.50.0) (2022-08-31)
+
+
+### Features
+
+* `tiktok-download` and `tiktok-seekbar` patch ([#405](https://github.com/revanced/revanced-patches/issues/405)) ([f36e000](https://github.com/revanced/revanced-patches/commit/f36e0007c52f4e925a3a2370cdb51bd45ee00a1b))
+
+# [2.49.0](https://github.com/revanced/revanced-patches/compare/v2.48.2...v2.49.0) (2022-08-31)
+
+
+### Features
+
+* bump YouTube Music patches to v5.22.54 ([#409](https://github.com/revanced/revanced-patches/issues/409)) ([859176e](https://github.com/revanced/revanced-patches/commit/859176e3ece415243ba887424513a961fe4a5ad8))
+
+## [2.48.2](https://github.com/revanced/revanced-patches/compare/v2.48.1...v2.48.2) (2022-08-30)
+
+
+### Bug Fixes
+
+* do not constrain `amoled` patch to versions ([#408](https://github.com/revanced/revanced-patches/issues/408)) ([4af1714](https://github.com/revanced/revanced-patches/commit/4af1714dae4da40fe7488e8b734da91c978f1bdf))
+
+## [2.48.1](https://github.com/revanced/revanced-patches/compare/v2.48.0...v2.48.1) (2022-08-30)
+
+
+### Bug Fixes
+
+* don't overwrite register in `compact-header` patch ([#406](https://github.com/revanced/revanced-patches/issues/406)) ([3da8ab5](https://github.com/revanced/revanced-patches/commit/3da8ab5702f5c2a0384ce85fcb3df3b837cdc90e))
+
+# [2.48.0](https://github.com/revanced/revanced-patches/compare/v2.47.0...v2.48.0) (2022-08-30)
+
+
+### Features
+
+* bump YouTube Music patches to v5.21.52 ([#404](https://github.com/revanced/revanced-patches/issues/404)) ([0abb196](https://github.com/revanced/revanced-patches/commit/0abb1960c10f03149e45e9e5074cafcc1801fc32))
+
+# [2.47.0](https://github.com/revanced/revanced-patches/compare/v2.46.0...v2.47.0) (2022-08-29)
+
+
+### Features
+
+* bump youtube version to v17.33.42 ([#398](https://github.com/revanced/revanced-patches/issues/398)) ([769aa25](https://github.com/revanced/revanced-patches/commit/769aa25a6fffbff4c895ae8a291d97836ba03ed9))
+
+# [2.46.0](https://github.com/revanced/revanced-patches/compare/v2.45.3...v2.46.0) (2022-08-29)
+
+
+### Bug Fixes
+
+* `seekbar-tapping` toggling ([#401](https://github.com/revanced/revanced-patches/issues/401)) ([5550244](https://github.com/revanced/revanced-patches/commit/55502446ce5967922f2e9711a5fea2758fe31e83))
+
+
+### Features
+
+* `premium-icon-reddit` patch ([#333](https://github.com/revanced/revanced-patches/issues/333)) ([6739e7b](https://github.com/revanced/revanced-patches/commit/6739e7b94c18200bb3afbc9a8465d97bd18d1945))
+* blacklist `.vsc` folder in `.gitignore` ([#331](https://github.com/revanced/revanced-patches/issues/331)) ([1922253](https://github.com/revanced/revanced-patches/commit/1922253118e670aa66b33186f5cee2ffb3c55718))
+
+## [2.45.3](https://github.com/revanced/revanced-patches/compare/v2.45.2...v2.45.3) (2022-08-29)
+
+
+### Bug Fixes
+
+* release `patches.json` file ([#402](https://github.com/revanced/revanced-patches/issues/402)) ([17e351c](https://github.com/revanced/revanced-patches/commit/17e351c99a69652f20d89e579f43c1ce110cd81d))
+
+## [2.45.2](https://github.com/revanced/revanced-patches/compare/v2.45.1...v2.45.2) (2022-08-28)
+
+
+### Bug Fixes
+
+* add `patches.json` as a release asset ([a0b2c89](https://github.com/revanced/revanced-patches/commit/a0b2c89071d64efa7629a8d6d6bc0492c9772aa9))
+
+## [2.45.1](https://github.com/revanced/revanced-patches/compare/v2.45.0...v2.45.1) (2022-08-28)
+
+
+### Bug Fixes
+
+* run meta generator ([70a2b45](https://github.com/revanced/revanced-patches/commit/70a2b456b4031b3da38a18a71ede7bf84099acd0))
+
+# [2.45.0](https://github.com/revanced/revanced-patches/compare/v2.44.0...v2.45.0) (2022-08-28)
+
+
+### Features
+
+* Add JSON meta ([4c229d3](https://github.com/revanced/revanced-patches/commit/4c229d350859903a782c43b13457628739572f09))
+
+# [2.44.0](https://github.com/revanced/revanced-patches/compare/v2.43.2...v2.44.0) (2022-08-27)
+
+
+### Features
+
+* add debug switch ([#391](https://github.com/revanced/revanced-patches/issues/391)) ([ee4b776](https://github.com/revanced/revanced-patches/commit/ee4b7761d8bb4549f2f100f721c90c664eedfe4a))
+
+## [2.43.2](https://github.com/revanced/revanced-patches/compare/v2.43.1...v2.43.2) (2022-08-26)
+
+
+### Bug Fixes
+
+* package name for Vanced ([#390](https://github.com/revanced/revanced-patches/issues/390)) ([85206b9](https://github.com/revanced/revanced-patches/commit/85206b99e1399d00e5eaebded8aadc5fb982aaa4))
+
+## [2.43.1](https://github.com/revanced/revanced-patches/compare/v2.43.0...v2.43.1) (2022-08-26)
+
+
+### Bug Fixes
+
+* `Patch` annotation for `client-spoof` patch ([6a8af47](https://github.com/revanced/revanced-patches/commit/6a8af47f2dfb319a53db9b5f9deb0392f10f4185))
+
+# [2.43.0](https://github.com/revanced/revanced-patches/compare/v2.42.1...v2.43.0) (2022-08-26)
+
+
+### Features
+
+* `client-spoof` patch ([5512c07](https://github.com/revanced/revanced-patches/commit/5512c072fa4b047849dbea0d2d382dd85e3a0827))
+
+## [2.42.1](https://github.com/revanced/revanced-patches/compare/v2.42.0...v2.42.1) (2022-08-26)
+
+
+### Bug Fixes
+
+* changed default value for autorepeat setting ([#386](https://github.com/revanced/revanced-patches/issues/386)) ([d43add7](https://github.com/revanced/revanced-patches/commit/d43add7c21f0d9f45830476704985755e37d33ef))
+
+# [2.42.0](https://github.com/revanced/revanced-patches/compare/v2.41.0...v2.42.0) (2022-08-22)
+
+
+### Bug Fixes
+
+* default values for settings ([834c4ad](https://github.com/revanced/revanced-patches/commit/834c4add71570d36b645246621ba24da3869d613))
+
+
+### Features
+
+* setting for downloader package name ([965d05c](https://github.com/revanced/revanced-patches/commit/965d05cfa55d7a51f64a11f0219e2867568ba852))
+* v17.29.34 compatibility for `downloads` patch ([#374](https://github.com/revanced/revanced-patches/issues/374)) ([d81f1af](https://github.com/revanced/revanced-patches/commit/d81f1af327e0d7471f410811af46da34ddfb1bb4))
+
+# [2.41.0](https://github.com/revanced/revanced-patches/compare/v2.40.2...v2.41.0) (2022-08-22)
+
+
+### Features
+
+* `downloads` patch ([#215](https://github.com/revanced/revanced-patches/issues/215)) ([304fbac](https://github.com/revanced/revanced-patches/commit/304fbacab22538fb0945f564e061783b47120120))
+
+## [2.40.2](https://github.com/revanced/revanced-patches/compare/v2.40.1...v2.40.2) (2022-08-22)
+
+
+### Bug Fixes
+
+* correct title for `tablet-miniplayer` setting switch ([6af6c02](https://github.com/revanced/revanced-patches/commit/6af6c02154d170153093bd846dfcb7a6205dd6fa))
+
+## [2.40.1](https://github.com/revanced/revanced-patches/compare/v2.40.0...v2.40.1) (2022-08-22)
+
+
+### Bug Fixes
+
+* add missing switch for `tablet-mini-player` patch ([244a1b2](https://github.com/revanced/revanced-patches/commit/244a1b2cb9f77272dc62287a4a34a487b0289295))
+
+# [2.40.0](https://github.com/revanced/revanced-patches/compare/v2.39.1...v2.40.0) (2022-08-22)
+
+
+### Features
+
+* `settings` patch framework ([#266](https://github.com/revanced/revanced-patches/issues/266)) ([084a99b](https://github.com/revanced/revanced-patches/commit/084a99bc6f5ed67c0b270e219c2dd75a30f302f1))
+
+## [2.39.1](https://github.com/revanced/revanced-patches/compare/v2.39.0...v2.39.1) (2022-08-21)
+
+
+### Bug Fixes
+
+* make `custom-branding` cross-platform ([#366](https://github.com/revanced/revanced-patches/issues/366)) ([02ac62b](https://github.com/revanced/revanced-patches/commit/02ac62b0ea7e47ff3aa5078ce4645421f410b154))
+
+# [2.39.0](https://github.com/revanced/revanced-patches/compare/v2.38.0...v2.39.0) (2022-08-19)
+
+
+### Features
+
+* bundle `dex` file into `jar` file ([#359](https://github.com/revanced/revanced-patches/issues/359)) ([f419252](https://github.com/revanced/revanced-patches/commit/f4192526eab1e3e0208e7460847b892e077fcf5a))
+
+# [2.38.0](https://github.com/revanced/revanced-patches/compare/v2.37.0...v2.38.0) (2022-08-17)
+
+
+### Features
+
+* bump YouTube patches to `v17.32.35` ([#347](https://github.com/revanced/revanced-patches/issues/347)) ([5292a0e](https://github.com/revanced/revanced-patches/commit/5292a0e973953225f94eed887a5d8f1ead17bb97))
+
+# [2.37.0](https://github.com/revanced/revanced-patches/compare/v2.36.2...v2.37.0) (2022-08-15)
+
+
+### Features
+
+* ˋpflotsh-ecmwf-subscription-unlockˋ patch ([#332](https://github.com/revanced/revanced-patches/issues/332)) ([ae2a1d8](https://github.com/revanced/revanced-patches/commit/ae2a1d8362e388032c3521101ff60698c4af1583))
+
+## [2.36.2](https://github.com/revanced/revanced-patches/compare/v2.36.1...v2.36.2) (2022-08-14)
+
+
+### Bug Fixes
+
+* disable cast modules in YouTube Music ([#337](https://github.com/revanced/revanced-patches/issues/337)) ([86eaba8](https://github.com/revanced/revanced-patches/commit/86eaba8248100987f46540a224956099bcf9da2c))
+
+## [2.36.1](https://github.com/revanced/revanced-patches/compare/v2.36.0...v2.36.1) (2022-08-14)
+
+
+### Bug Fixes
+
+* add missing switch for tablet mini-player ([b824d35](https://github.com/revanced/revanced-patches/commit/b824d35960df9e99c2a2d248356c3c9342cfe130))
+
+# [2.36.0](https://github.com/revanced/revanced-patches/compare/v2.35.0...v2.36.0) (2022-08-14)
+
+
+### Features
+
+* `swipe-controls` override volume button behaviour ([#285](https://github.com/revanced/revanced-patches/issues/285)) ([69465f3](https://github.com/revanced/revanced-patches/commit/69465f3a9934973a2449605a248a462510e10ff6))
+
+# [2.35.0](https://github.com/revanced/revanced-patches/compare/v2.34.0...v2.35.0) (2022-08-14)
+
+
+### Features
+
+* `tiktok-ads` patch ([#335](https://github.com/revanced/revanced-patches/issues/335)) ([81bef57](https://github.com/revanced/revanced-patches/commit/81bef57742bfaefcdfe02fd290cca6923341149a))
+
+# [2.34.0](https://github.com/revanced/revanced-patches/compare/v2.33.2...v2.34.0) (2022-08-14)
+
+
+### Features
+
+* `tablet-mini-player` patch ([dae41f1](https://github.com/revanced/revanced-patches/commit/dae41f1c59d26d00b1ba821dbe1521e68226f47c))
+
+## [2.33.2](https://github.com/revanced/revanced-patches/compare/v2.33.1...v2.33.2) (2022-08-07)
+
+## [2.33.1](https://github.com/revanced/revanced-patches/compare/v2.33.0...v2.33.1) (2022-08-07)
+
+
+### Bug Fixes
+
+* spoof `X-Android-Cert` of Firebase `authToken` api request ([#315](https://github.com/revanced/revanced-patches/issues/315)) ([c81d61f](https://github.com/revanced/revanced-patches/commit/c81d61f685449590473fa5205e7709f81872a9b9))
+
+# [2.33.0](https://github.com/revanced/revanced-patches/compare/v2.32.1...v2.33.0) (2022-08-06)
+
+
+### Features
+
+* bump music patches to 5.17.51 ([#307](https://github.com/revanced/revanced-patches/issues/307)) ([c303589](https://github.com/revanced/revanced-patches/commit/c303589bd8d1956ce2e3c4c285d45a648b5db30f))
+
+## [2.32.1](https://github.com/revanced/revanced-patches/compare/v2.32.0...v2.32.1) (2022-08-04)
+
+
+### Bug Fixes
+
+* incorrect compatibilty attribute ([#296](https://github.com/revanced/revanced-patches/issues/296)) ([0ab3e97](https://github.com/revanced/revanced-patches/commit/0ab3e9724157c628555964273c6b65f9f48f9664))
+
+# [2.32.0](https://github.com/revanced/revanced-patches/compare/v2.31.0...v2.32.0) (2022-08-04)
+
+
+### Features
+
+* `promo-code-unlock` patch ([#292](https://github.com/revanced/revanced-patches/issues/292)) ([d5df2e6](https://github.com/revanced/revanced-patches/commit/d5df2e68f47cbb3d374b8ce24769872959014051))
+
+# [2.31.0](https://github.com/revanced/revanced-patches/compare/v2.30.1...v2.31.0) (2022-08-03)
+
+
+### Features
+
+* remove `force-vp9-codec` patch ([#287](https://github.com/revanced/revanced-patches/issues/287)) ([e428978](https://github.com/revanced/revanced-patches/commit/e42897801b20e7439969954753e2c15c85eb41b5))
+
+## [2.30.1](https://github.com/revanced/revanced-patches/compare/v2.30.0...v2.30.1) (2022-08-03)
+
+
+### Bug Fixes
+
+* migrate to new patcher api ([f43446e](https://github.com/revanced/revanced-patches/commit/f43446ed189e583302fcd899f5eb7517f2a77144))
+
+# [2.30.0](https://github.com/revanced/revanced-patches/compare/v2.29.0...v2.30.0) (2022-08-03)
+
+
+### Bug Fixes
+
+* incorrect fingerprint version [skip ci] ([f8c62ae](https://github.com/revanced/revanced-patches/commit/f8c62ae16b088ff6d0e96a4ef62dc707cd83beb9))
+* rollback to `Dependencies` annotation ([36a2ae8](https://github.com/revanced/revanced-patches/commit/36a2ae886c63f1d22bb1cca9e7110af6c3f6f2d3))
+
+
+### Features
+
+* set the correct theme of the settings screen ([70d850c](https://github.com/revanced/revanced-patches/commit/70d850cf295d306b0de9419efe6a4bbf3857d3a0))
+
+# [2.29.0](https://github.com/revanced/revanced-patches/compare/v2.28.2...v2.29.0) (2022-08-02)
+
+
+### Features
+
+* add "Application Icon Path" option to branding ([1748d1e](https://github.com/revanced/revanced-patches/commit/1748d1e5badf61213d793ff7ae1d090ea2ea82f0))
+* add "Application Name" option to branding and move renaming to CustomBrandingPatch.kt ([8dafe05](https://github.com/revanced/revanced-patches/commit/8dafe05b2c5e2b0291d46b02717d910721673712))
+
+## [2.28.2](https://github.com/revanced/revanced-patches/compare/v2.28.1...v2.28.2) (2022-08-02)
+
+
+### Bug Fixes
+
+* remove requirement for solution [skip ci] ([#271](https://github.com/revanced/revanced-patches/issues/271)) ([553fad3](https://github.com/revanced/revanced-patches/commit/553fad3fe1bb79bdf34e9f91c0e1cbfda78e1054))
+
+## [2.28.1](https://github.com/revanced/revanced-patches/compare/v2.28.0...v2.28.1) (2022-07-31)
+
+
+### Bug Fixes
+
+* add missing permission to reboot app ([#260](https://github.com/revanced/revanced-patches/issues/260)) ([6ced6df](https://github.com/revanced/revanced-patches/commit/6ced6df8ed7642dea51e1acd1c12f4de4874b972))
+
+# [2.28.0](https://github.com/revanced/revanced-patches/compare/v2.27.0...v2.28.0) (2022-07-31)
+
+
+### Features
+
+* add `custom-video-buffer` patch ([9f117c7](https://github.com/revanced/revanced-patches/commit/9f117c74cdbdcf98eae97cf4c37f0baca451d695))
+
+# [2.27.0](https://github.com/revanced/revanced-patches/compare/v2.26.0...v2.27.0) (2022-07-31)
+
+
+### Features
+
+* `settings` patch ([0e229a4](https://github.com/revanced/revanced-patches/commit/0e229a46cb5b8b74183c47a6eae08d667f941406))
+
+# [2.26.0](https://github.com/revanced/revanced-patches/compare/v2.25.3...v2.26.0) (2022-07-31)
+
+
+### Features
+
+* `ResourceUtils` helper class ([e0e1144](https://github.com/revanced/revanced-patches/commit/e0e11447a7ac184d43c75955854c52c6992ff667))
+
+## [2.25.3](https://github.com/revanced/revanced-patches/compare/v2.25.2...v2.25.3) (2022-07-29)
+
+
+### Bug Fixes
+
+* actually call `VideoInformation.setCurrentVideoId` first ([5c62d0a](https://github.com/revanced/revanced-patches/commit/5c62d0a2e0217de1b9563a41b4e94ed63230440f))
+
+## [2.25.2](https://github.com/revanced/revanced-patches/compare/v2.25.1...v2.25.2) (2022-07-26)
+
+
+### Bug Fixes
+
+* rename `default-video-quality` to `remember-video-quality` ([7f6cdfd](https://github.com/revanced/revanced-patches/commit/7f6cdfd7c2b5e72742bbb92e4d584f722cb82cae))
+
+## [2.25.1](https://github.com/revanced/revanced-patches/compare/v2.25.0...v2.25.1) (2022-07-26)
+
+
+### Bug Fixes
+
+* `default-video-quality` patch crashing ([#227](https://github.com/revanced/revanced-patches/issues/227)) ([379327a](https://github.com/revanced/revanced-patches/commit/379327a6b2325ef93c7107472343dd9fd85a0f56))
+
+# [2.25.0](https://github.com/revanced/revanced-patches/compare/v2.24.0...v2.25.0) (2022-07-26)
+
+
+### Features
+
+* `general-reddit-ads` patch ([#235](https://github.com/revanced/revanced-patches/issues/235)) ([d63c016](https://github.com/revanced/revanced-patches/commit/d63c016d38d2a5b1d152abccdb582990987a1911))
+
+# [2.24.0](https://github.com/revanced/revanced-patches/compare/v2.23.0...v2.24.0) (2022-07-26)
+
+
+### Features
+
+* `music-microg-support` patch ([#208](https://github.com/revanced/revanced-patches/issues/208)) ([50863e9](https://github.com/revanced/revanced-patches/commit/50863e97cc8eba7e1ad6d11b4821baa45fdd29c4))
+
+# [2.23.0](https://github.com/revanced/revanced-patches/compare/v2.22.2...v2.23.0) (2022-07-26)
+
+
+### Features
+
+* bump compatibility of YouTube Music patches to v5.16.51 ([#238](https://github.com/revanced/revanced-patches/issues/238)) ([4fa2a12](https://github.com/revanced/revanced-patches/commit/4fa2a127d715f953a1e4efacaecbc218209a01f3))
+
+## [2.22.2](https://github.com/revanced/revanced-patches/compare/v2.22.1...v2.22.2) (2022-07-26)
+
+
+### Bug Fixes
+
+* bump youtube version to 17.29.34 ([#236](https://github.com/revanced/revanced-patches/issues/236)) ([be6494a](https://github.com/revanced/revanced-patches/commit/be6494a80989044cba961dc22fc58ffb2e8591f0))
+
+## [2.22.1](https://github.com/revanced/revanced-patches/compare/v2.22.0...v2.22.1) (2022-07-24)
+
+
+### Bug Fixes
+
+* remove broken video-quality patch ([e8d516a](https://github.com/revanced/revanced-patches/commit/e8d516af896971a09bb9aca8b00e7b7bb57c3dd9))
+
+# [2.22.0](https://github.com/revanced/revanced-patches/compare/v2.21.3...v2.22.0) (2022-07-24)
+
+
+### Features
+
+* `default-video-quality` patch ([#141](https://github.com/revanced/revanced-patches/issues/141)) ([609ad7d](https://github.com/revanced/revanced-patches/commit/609ad7dee7e29b2ecc4e95a93e9dab1646b4b050))
+
+# [2.12.0](https://github.com/Canny1913/revanced-patches/compare/v2.11.0...v2.12.0) (2022-07-24)
+
+
+### Bug Fixes
+
+* `autorepeat-by-default` patch ([#148](https://github.com/Canny1913/revanced-patches/issues/148)) ([fe628ba](https://github.com/Canny1913/revanced-patches/commit/fe628ba909d89ea0bf3d95fe94ca78ef819677da))
+* `codecs-unlock` patch and update Music patches to `5.14.53` ([2437d30](https://github.com/Canny1913/revanced-patches/commit/2437d3070f6a630d353619f642cefebd47abee20))
+* `exclusive-audio-playback` patch ([#153](https://github.com/Canny1913/revanced-patches/issues/153)) ([9beff95](https://github.com/Canny1913/revanced-patches/commit/9beff9567f1586e5c58690c1f1d2f7f204025ab7))
+* `hrd-auto-brightness` ([#152](https://github.com/Canny1913/revanced-patches/issues/152)) ([5f2e9ba](https://github.com/Canny1913/revanced-patches/commit/5f2e9ba30b7432be04bdc9f9f7ec7ac75fdc4b34))
+* `minimized-playback` patch for YouTube Kids videos ([#201](https://github.com/Canny1913/revanced-patches/issues/201)) ([31e3b42](https://github.com/Canny1913/revanced-patches/commit/31e3b42c6bbce0d00f049b8a69bafc94900bb3b4))
+* `old-quality-layout` patch ([2497425](https://github.com/Canny1913/revanced-patches/commit/2497425c9f11b8b14c861c2f0f34ff47bdbfac53))
+* `swipe-controls` with active engagement panel ([#177](https://github.com/Canny1913/revanced-patches/issues/177)) ([000ec6d](https://github.com/Canny1913/revanced-patches/commit/000ec6d8f6ecbb910a06ec852564ee9e5f03dcf8))
+* add v17.28.34 compatiblity for the `hide-shorts-button` patch ([#224](https://github.com/Canny1913/revanced-patches/issues/224)) ([76166bb](https://github.com/Canny1913/revanced-patches/commit/76166bb35f940ef661e2802f5bf93ed91f2e2913))
+* autoplay not working. ([586eed5](https://github.com/Canny1913/revanced-patches/commit/586eed515fc7ff8e3b1b150b0d34610b39480bb5))
+* autoplay still enabled when using patch ([4f96129](https://github.com/Canny1913/revanced-patches/commit/4f961298f6cb6417ee3f3d6f8ac7ce96594ed03b))
+* bump youtube version for swipe-controls patch ([ff207a5](https://github.com/Canny1913/revanced-patches/commit/ff207a57af7d3c15a8127f4465e97da23878b0d6))
+* bump youtube version to 17.27.39 ([b93401a](https://github.com/Canny1913/revanced-patches/commit/b93401a391c0ed4887dd1376ac253f74f98a8d7e))
+* bump youtube version to 17.28.34 ([#225](https://github.com/Canny1913/revanced-patches/issues/225)) ([738cb6a](https://github.com/Canny1913/revanced-patches/commit/738cb6af177e92bbff8c02d6808fe416c0ad2582))
+* check if node has attributes before accessing them ([2d2ed87](https://github.com/Canny1913/revanced-patches/commit/2d2ed870dacfe092eb6acbcaae5e51775c611322))
+* compatibility of `force-vp9-codec-parent-fingerprint` fingerprint with version `17.27.39` ([523fd86](https://github.com/Canny1913/revanced-patches/commit/523fd8627bc965a724267f725c28fba5e7a25a04))
+* constrain `old-quality-layout` to older version ([add7232](https://github.com/Canny1913/revanced-patches/commit/add72326199e90f677b450b553b9d88c0bb4c490))
+* crash when using force-vp9-codec patch ([7a35e5c](https://github.com/Canny1913/revanced-patches/commit/7a35e5c985b412d5a84083d1416d3207a40b3e97))
+* disable-fullscreen-panels patch not working ([#213](https://github.com/Canny1913/revanced-patches/issues/213)) ([5372105](https://github.com/Canny1913/revanced-patches/commit/5372105e72607f289b779e4c2a3c13f3458842ce))
+* display codename for patch names ([10c53f7](https://github.com/Canny1913/revanced-patches/commit/10c53f720df3e70b9d59e8bc3219d56b996f03db))
+* exclude `swipe-controls` by default due to instability ([bdeb8e0](https://github.com/Canny1913/revanced-patches/commit/bdeb8e04609a0ca94d9e2921bf7d486b9e229cec))
+* incorrect package name in gradle task ([152b2c9](https://github.com/Canny1913/revanced-patches/commit/152b2c90cf102170648fcc168da10f46743bdc63))
+* invalid regex ([26bf1d8](https://github.com/Canny1913/revanced-patches/commit/26bf1d818f953abc061126d8b91f17cd9008ba1d))
+* listing of wrong fingerprint class ([#147](https://github.com/Canny1913/revanced-patches/issues/147)) ([95c2bbd](https://github.com/Canny1913/revanced-patches/commit/95c2bbdd1deb1d76f1177b48286fa6a3bc9f7663))
+* make all patches toggleable with settings ([#202](https://github.com/Canny1913/revanced-patches/issues/202)) ([7e1d82f](https://github.com/Canny1913/revanced-patches/commit/7e1d82f1161237632c3a57f109db6b8903006b33))
+* make the patch work w/o settings ([7f0be5c](https://github.com/Canny1913/revanced-patches/commit/7f0be5c08b738d7baeef7ad3e167aab623c2d08e))
+* references to integrations in `return-youtube-dislike` patch ([5824c2c](https://github.com/Canny1913/revanced-patches/commit/5824c2cdfb1a2d7b8d68044388e5e0746ef2ca09))
+* remove "dependency" ([b93cb9b](https://github.com/Canny1913/revanced-patches/commit/b93cb9b8dd4b78c53bbf0298db0f532a42d46f7d))
+* rename autorepeat-by-default patch to always-autorepeat ([3606015](https://github.com/Canny1913/revanced-patches/commit/3606015d715f150cc51fbb29adf3be252b96faab))
+* stop using manually entered index ([ed0520d](https://github.com/Canny1913/revanced-patches/commit/ed0520d85c74729d7daabbfbd6cb77ad8cd62f15))
+* update patches to `17.26.35` ([#142](https://github.com/Canny1913/revanced-patches/issues/142)) ([b04112c](https://github.com/Canny1913/revanced-patches/commit/b04112c8562a7b95e7555e894b665913094b33eb))
+* use dependency in correct patch ([a2a1ee8](https://github.com/Canny1913/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637))
+* wording [skip ci] ([ba64d9e](https://github.com/Canny1913/revanced-patches/commit/ba64d9efc3ee606e9bda30ad7f8017af34b1dc3f))
+
+
+### Features
+
+* `compact-header` patch ([83753ba](https://github.com/Canny1913/revanced-patches/commit/83753bacf8f56ad16f8abafc02034f1595c12532))
+* `default-video-quality` patch ([9d30d37](https://github.com/Canny1913/revanced-patches/commit/9d30d372dd13663564286820f3e7685c0a0cee8a))
+* `force-vp9-codec` patch ([#157](https://github.com/Canny1913/revanced-patches/issues/157)) ([07806a1](https://github.com/Canny1913/revanced-patches/commit/07806a16e5368118949e0e476e901987433ad403))
+* `hide-get-premium` patch ([#195](https://github.com/Canny1913/revanced-patches/issues/195)) ([639aab4](https://github.com/Canny1913/revanced-patches/commit/639aab411ee514b50840c15c8e9a1a889854403f))
+* `minimize-playback-music` patch ([#200](https://github.com/Canny1913/revanced-patches/issues/200)) ([d4fd18b](https://github.com/Canny1913/revanced-patches/commit/d4fd18bc74c92e17cdfb21b627b2f6a8919ff2dc))
+* `music-video-ads` patch ([#172](https://github.com/Canny1913/revanced-patches/issues/172)) ([cbb71b5](https://github.com/Canny1913/revanced-patches/commit/cbb71b5faf1386ed31dd9e0387f6cf0509d92d31))
+* `return-youtube-dislikes` patch ([#175](https://github.com/Canny1913/revanced-patches/issues/175)) ([18a66d8](https://github.com/Canny1913/revanced-patches/commit/18a66d8454cf6e7cfdd4183631a6870c80d16b90))
+* `sponsorblock` patch ([#101](https://github.com/Canny1913/revanced-patches/issues/101)) ([36af4cc](https://github.com/Canny1913/revanced-patches/commit/36af4cc14ff8057c10b99019cb23acc6e5aec3f2)), closes [#89](https://github.com/Canny1913/revanced-patches/issues/89) [#90](https://github.com/Canny1913/revanced-patches/issues/90)
+* `swipe-controls` rewrite ([#131](https://github.com/Canny1913/revanced-patches/issues/131)) ([b7dba09](https://github.com/Canny1913/revanced-patches/commit/b7dba09927ba15a9eacb06dcb4bf1f268560c96e))
+* 1 line of code ([e2d28bd](https://github.com/Canny1913/revanced-patches/commit/e2d28bd576f74f1792dc959b8ed3064ec6a4cb68))
+* add version 17.26.35 ([25cd66c](https://github.com/Canny1913/revanced-patches/commit/25cd66cc6d2cd56a4170c5c69a708011fe7eab1d))
+* complete `default-video-quality` patch ([f7e153b](https://github.com/Canny1913/revanced-patches/commit/f7e153bd08b6dfe07591678195bfa6b06606f6a2))
+* issue templates ([b82b0aa](https://github.com/Canny1913/revanced-patches/commit/b82b0aad88b7ab9d86f1bcc8e007f6a76a9aa1a5))
+* twitter `timeline-ads` patch ([#222](https://github.com/Canny1913/revanced-patches/issues/222)) ([f16e67f](https://github.com/Canny1913/revanced-patches/commit/f16e67fc75c9c3505ff875a216ce7e868022075e))
+
+## [2.21.3](https://github.com/revanced/revanced-patches/compare/v2.21.2...v2.21.3) (2022-07-24)
+
+
+### Bug Fixes
+
+* rename autorepeat-by-default patch to always-autorepeat ([3606015](https://github.com/revanced/revanced-patches/commit/3606015d715f150cc51fbb29adf3be252b96faab))
+
+## [2.21.2](https://github.com/revanced/revanced-patches/compare/v2.21.1...v2.21.2) (2022-07-23)
+
+
+### Bug Fixes
+
+* bump youtube version to 17.28.34 ([#225](https://github.com/revanced/revanced-patches/issues/225)) ([738cb6a](https://github.com/revanced/revanced-patches/commit/738cb6af177e92bbff8c02d6808fe416c0ad2582))
+
+## [2.21.1](https://github.com/revanced/revanced-patches/compare/v2.21.0...v2.21.1) (2022-07-23)
+
+
+### Bug Fixes
+
+* add v17.28.34 compatiblity for the `hide-shorts-button` patch ([#224](https://github.com/revanced/revanced-patches/issues/224)) ([76166bb](https://github.com/revanced/revanced-patches/commit/76166bb35f940ef661e2802f5bf93ed91f2e2913))
+
+# [2.21.0](https://github.com/revanced/revanced-patches/compare/v2.20.5...v2.21.0) (2022-07-23)
+
+
+### Features
+
+* twitter `timeline-ads` patch ([#222](https://github.com/revanced/revanced-patches/issues/222)) ([f16e67f](https://github.com/revanced/revanced-patches/commit/f16e67fc75c9c3505ff875a216ce7e868022075e))
+
+## [2.20.5](https://github.com/revanced/revanced-patches/compare/v2.20.4...v2.20.5) (2022-07-22)
+
+
+### Bug Fixes
+
+* exclude `swipe-controls` by default due to instability ([bdeb8e0](https://github.com/revanced/revanced-patches/commit/bdeb8e04609a0ca94d9e2921bf7d486b9e229cec))
+
+## [2.20.4](https://github.com/revanced/revanced-patches/compare/v2.20.3...v2.20.4) (2022-07-21)
+
+
+### Bug Fixes
+
+* crash when using force-vp9-codec patch ([7a35e5c](https://github.com/revanced/revanced-patches/commit/7a35e5c985b412d5a84083d1416d3207a40b3e97))
+* use dependency in correct patch ([a2a1ee8](https://github.com/revanced/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637))
+
+## [2.20.4](https://github.com/revanced/revanced-patches/compare/v2.20.3...v2.20.4) (2022-07-21)
+
+
+### Bug Fixes
+
+* crash when using force-vp9-codec patch ([7a35e5c](https://github.com/revanced/revanced-patches/commit/7a35e5c985b412d5a84083d1416d3207a40b3e97))
+* use dependency in correct patch ([a2a1ee8](https://github.com/revanced/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637))
+
+## [2.20.4](https://github.com/revanced/revanced-patches/compare/v2.20.3...v2.20.4) (2022-07-21)
+
+
+### Bug Fixes
+
+* crash when using force-vp9-codec patch ([7a35e5c](https://github.com/revanced/revanced-patches/commit/7a35e5c985b412d5a84083d1416d3207a40b3e97))
+* use dependency in correct patch ([a2a1ee8](https://github.com/revanced/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637))
+
+## [2.20.4](https://github.com/revanced/revanced-patches/compare/v2.20.3...v2.20.4) (2022-07-21)
+
+
+### Bug Fixes
+
+* use dependency in correct patch ([a2a1ee8](https://github.com/revanced/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637))
+
+## [2.20.3](https://github.com/revanced/revanced-patches/compare/v2.20.2...v2.20.3) (2022-07-20)
+
+
+### Bug Fixes
+
+* disable-fullscreen-panels patch not working ([#213](https://github.com/revanced/revanced-patches/issues/213)) ([5372105](https://github.com/revanced/revanced-patches/commit/5372105e72607f289b779e4c2a3c13f3458842ce))
+
+## [2.20.2](https://github.com/revanced/revanced-patches/compare/v2.20.1...v2.20.2) (2022-07-20)
+
+
+### Bug Fixes
+
+* autoplay not working. ([586eed5](https://github.com/revanced/revanced-patches/commit/586eed515fc7ff8e3b1b150b0d34610b39480bb5))
+
+## [2.20.1](https://github.com/revanced/revanced-patches/compare/v2.20.0...v2.20.1) (2022-07-20)
+
+
+### Bug Fixes
+
+* autoplay still enabled when using patch ([4f96129](https://github.com/revanced/revanced-patches/commit/4f961298f6cb6417ee3f3d6f8ac7ce96594ed03b))
+
+# [2.20.0](https://github.com/revanced/revanced-patches/compare/v2.19.0...v2.20.0) (2022-07-18)
+
+
+### Features
+
+* `minimize-playback-music` patch ([#200](https://github.com/revanced/revanced-patches/issues/200)) ([d4fd18b](https://github.com/revanced/revanced-patches/commit/d4fd18bc74c92e17cdfb21b627b2f6a8919ff2dc))
+
+# [2.19.0](https://github.com/revanced/revanced-patches/compare/v2.18.3...v2.19.0) (2022-07-18)
+
+
+### Features
+
+* `compact-header` patch ([83753ba](https://github.com/revanced/revanced-patches/commit/83753bacf8f56ad16f8abafc02034f1595c12532))
+
+## [2.18.3](https://github.com/revanced/revanced-patches/compare/v2.18.2...v2.18.3) (2022-07-18)
+
+
+### Bug Fixes
+
+* compatibility of `force-vp9-codec-parent-fingerprint` fingerprint with version `17.27.39` ([523fd86](https://github.com/revanced/revanced-patches/commit/523fd8627bc965a724267f725c28fba5e7a25a04))
+
+## [2.18.2](https://github.com/revanced/revanced-patches/compare/v2.18.1...v2.18.2) (2022-07-18)
+
+
+### Bug Fixes
+
+* make all patches toggleable with settings ([#202](https://github.com/revanced/revanced-patches/issues/202)) ([7e1d82f](https://github.com/revanced/revanced-patches/commit/7e1d82f1161237632c3a57f109db6b8903006b33))
+
+## [2.18.1](https://github.com/revanced/revanced-patches/compare/v2.18.0...v2.18.1) (2022-07-18)
+
+
+### Bug Fixes
+
+* `minimized-playback` patch for YouTube Kids videos ([#201](https://github.com/revanced/revanced-patches/issues/201)) ([31e3b42](https://github.com/revanced/revanced-patches/commit/31e3b42c6bbce0d00f049b8a69bafc94900bb3b4))
+
+# [2.18.0](https://github.com/revanced/revanced-patches/compare/v2.17.0...v2.18.0) (2022-07-18)
+
+
+### Features
+
+* `hide-get-premium` patch ([#195](https://github.com/revanced/revanced-patches/issues/195)) ([639aab4](https://github.com/revanced/revanced-patches/commit/639aab411ee514b50840c15c8e9a1a889854403f))
+
+# [2.17.0](https://github.com/revanced/revanced-patches/compare/v2.16.1...v2.17.0) (2022-07-17)
+
+
+### Bug Fixes
+
+* check if node has attributes before accessing them ([2d2ed87](https://github.com/revanced/revanced-patches/commit/2d2ed870dacfe092eb6acbcaae5e51775c611322))
+
+
+### Features
+
+* `sponsorblock` patch ([#101](https://github.com/revanced/revanced-patches/issues/101)) ([36af4cc](https://github.com/revanced/revanced-patches/commit/36af4cc14ff8057c10b99019cb23acc6e5aec3f2)), closes [#89](https://github.com/revanced/revanced-patches/issues/89) [#90](https://github.com/revanced/revanced-patches/issues/90)
+
+## [2.16.1](https://github.com/revanced/revanced-patches/compare/v2.16.0...v2.16.1) (2022-07-17)
+
+
+### Bug Fixes
+
+* bump youtube version to 17.27.39 ([b93401a](https://github.com/revanced/revanced-patches/commit/b93401a391c0ed4887dd1376ac253f74f98a8d7e))
+
+# [2.16.0](https://github.com/revanced/revanced-patches/compare/v2.15.1...v2.16.0) (2022-07-17)
+
+
+### Features
+
+* `force-vp9-codec` patch ([#157](https://github.com/revanced/revanced-patches/issues/157)) ([07806a1](https://github.com/revanced/revanced-patches/commit/07806a16e5368118949e0e476e901987433ad403))
+
+## [2.15.1](https://github.com/revanced/revanced-patches/compare/v2.15.0...v2.15.1) (2022-07-17)
+
+
+### Bug Fixes
+
+* `old-quality-layout` patch ([2497425](https://github.com/revanced/revanced-patches/commit/2497425c9f11b8b14c861c2f0f34ff47bdbfac53))
+* references to integrations in `return-youtube-dislike` patch ([5824c2c](https://github.com/revanced/revanced-patches/commit/5824c2cdfb1a2d7b8d68044388e5e0746ef2ca09))
+
+# [2.15.0](https://github.com/revanced/revanced-patches/compare/v2.14.1...v2.15.0) (2022-07-16)
+
+
+### Features
+
+* `return-youtube-dislikes` patch ([#175](https://github.com/revanced/revanced-patches/issues/175)) ([18a66d8](https://github.com/revanced/revanced-patches/commit/18a66d8454cf6e7cfdd4183631a6870c80d16b90))
+
+## [2.14.1](https://github.com/revanced/revanced-patches/compare/v2.14.0...v2.14.1) (2022-07-15)
+
+
+### Bug Fixes
+
+* `codecs-unlock` patch and update Music patches to `5.14.53` ([2437d30](https://github.com/revanced/revanced-patches/commit/2437d3070f6a630d353619f642cefebd47abee20))
+
+# [2.14.0](https://github.com/revanced/revanced-patches/compare/v2.13.6...v2.14.0) (2022-07-15)
+
+
+### Features
+
+* `music-video-ads` patch ([#172](https://github.com/revanced/revanced-patches/issues/172)) ([cbb71b5](https://github.com/revanced/revanced-patches/commit/cbb71b5faf1386ed31dd9e0387f6cf0509d92d31))
+
+## [2.13.6](https://github.com/revanced/revanced-patches/compare/v2.13.5...v2.13.6) (2022-07-14)
+
+## [2.13.5](https://github.com/revanced/revanced-patches/compare/v2.13.4...v2.13.5) (2022-07-14)
+
+
+### Bug Fixes
+
+* `swipe-controls` with active engagement panel ([#177](https://github.com/revanced/revanced-patches/issues/177)) ([000ec6d](https://github.com/revanced/revanced-patches/commit/000ec6d8f6ecbb910a06ec852564ee9e5f03dcf8))
+
+## [2.13.4](https://github.com/revanced/revanced-patches/compare/v2.13.3...v2.13.4) (2022-07-13)
+
+
+### Bug Fixes
+
+* constrain `old-quality-layout` to older version ([add7232](https://github.com/revanced/revanced-patches/commit/add72326199e90f677b450b553b9d88c0bb4c490))
+
+## [2.13.3](https://github.com/revanced/revanced-patches/compare/v2.13.2...v2.13.3) (2022-07-11)
+
+
+### Bug Fixes
+
+* `exclusive-audio-playback` patch ([#153](https://github.com/revanced/revanced-patches/issues/153)) ([9beff95](https://github.com/revanced/revanced-patches/commit/9beff9567f1586e5c58690c1f1d2f7f204025ab7))
+
+## [2.13.2](https://github.com/revanced/revanced-patches/compare/v2.13.1...v2.13.2) (2022-07-11)
+
+
+### Bug Fixes
+
+* `hrd-auto-brightness` ([#152](https://github.com/revanced/revanced-patches/issues/152)) ([5f2e9ba](https://github.com/revanced/revanced-patches/commit/5f2e9ba30b7432be04bdc9f9f7ec7ac75fdc4b34))
+
+## [2.13.1](https://github.com/revanced/revanced-patches/compare/v2.13.0...v2.13.1) (2022-07-11)
+
+
+### Bug Fixes
+
+* bump youtube version for swipe-controls patch ([ff207a5](https://github.com/revanced/revanced-patches/commit/ff207a57af7d3c15a8127f4465e97da23878b0d6))
+
+# [2.13.0](https://github.com/revanced/revanced-patches/compare/v2.12.4...v2.13.0) (2022-07-11)
+
+
+### Features
+
+* `swipe-controls` rewrite ([#131](https://github.com/revanced/revanced-patches/issues/131)) ([b7dba09](https://github.com/revanced/revanced-patches/commit/b7dba09927ba15a9eacb06dcb4bf1f268560c96e))
+
+## [2.12.4](https://github.com/revanced/revanced-patches/compare/v2.12.3...v2.12.4) (2022-07-11)
+
+
+### Bug Fixes
+
+* `autorepeat-by-default` patch ([#148](https://github.com/revanced/revanced-patches/issues/148)) ([fe628ba](https://github.com/revanced/revanced-patches/commit/fe628ba909d89ea0bf3d95fe94ca78ef819677da))
+
+## [2.12.3](https://github.com/revanced/revanced-patches/compare/v2.12.2...v2.12.3) (2022-07-11)
+
+
+### Bug Fixes
+
+* listing of wrong fingerprint class ([#147](https://github.com/revanced/revanced-patches/issues/147)) ([95c2bbd](https://github.com/revanced/revanced-patches/commit/95c2bbdd1deb1d76f1177b48286fa6a3bc9f7663))
+
+## [2.12.2](https://github.com/revanced/revanced-patches/compare/v2.12.1...v2.12.2) (2022-07-10)
+
+
+### Bug Fixes
+
+* display codename for patch names ([10c53f7](https://github.com/revanced/revanced-patches/commit/10c53f720df3e70b9d59e8bc3219d56b996f03db))
+* incorrect package name in gradle task ([152b2c9](https://github.com/revanced/revanced-patches/commit/152b2c90cf102170648fcc168da10f46743bdc63))
+* invalid regex ([26bf1d8](https://github.com/revanced/revanced-patches/commit/26bf1d818f953abc061126d8b91f17cd9008ba1d))
+
+## [2.12.1](https://github.com/revanced/revanced-patches/compare/v2.12.0...v2.12.1) (2022-07-10)
+
+# [2.12.0](https://github.com/revanced/revanced-patches/compare/v2.11.0...v2.12.0) (2022-07-10)
+
+
+### Bug Fixes
+
+* update patches to `17.26.35` ([#142](https://github.com/revanced/revanced-patches/issues/142)) ([b04112c](https://github.com/revanced/revanced-patches/commit/b04112c8562a7b95e7555e894b665913094b33eb))
+* wording [skip ci] ([ba64d9e](https://github.com/revanced/revanced-patches/commit/ba64d9efc3ee606e9bda30ad7f8017af34b1dc3f))
+
+
+### Features
+
+* issue templates ([b82b0aa](https://github.com/revanced/revanced-patches/commit/b82b0aad88b7ab9d86f1bcc8e007f6a76a9aa1a5))
+
+# [2.11.0](https://github.com/revanced/revanced-patches/compare/v2.10.2...v2.11.0) (2022-07-09)
+
+
+### Features
+
+* `autorepeat-by-default` patch ([#106](https://github.com/revanced/revanced-patches/issues/106)) ([e0ac9f3](https://github.com/revanced/revanced-patches/commit/e0ac9f385fc360f4dd2451e26676633120356c10))
+
+## [2.10.2](https://github.com/revanced/revanced-patches/compare/v2.10.1...v2.10.2) (2022-07-08)
+
+
+### Bug Fixes
+
+* trigger release on `build` commits ([be8bd1b](https://github.com/revanced/revanced-patches/commit/be8bd1b2a4b91f9763448661a802a5dc4a6b1d1d))
+
+## [2.10.1](https://github.com/revanced/revanced-patches/compare/v2.10.0...v2.10.1) (2022-07-08)
+
+
+### Bug Fixes
+
+* patch description consistency ([#134](https://github.com/revanced/revanced-patches/issues/134)) ([da5896d](https://github.com/revanced/revanced-patches/commit/da5896dde0a2b2b9ffe65e486402e4ef92ec1ce9))
+
+# [2.10.0](https://github.com/revanced/revanced-patches/compare/v2.9.4...v2.10.0) (2022-07-07)
+
+
+### Features
+
+* implement `wide-searchbar` Patch ([#130](https://github.com/revanced/revanced-patches/issues/130)) ([332f4d1](https://github.com/revanced/revanced-patches/commit/332f4d12d06316d65db252a280fe1f263e65c3a8))
+
+## [2.9.4](https://github.com/revanced/revanced-patches/compare/v2.9.3...v2.9.4) (2022-07-06)
+
+
+### Bug Fixes
+
+* add 17.25.34 as supported version for swipe-controls patch ([4d84c19](https://github.com/revanced/revanced-patches/commit/4d84c1914f8ecf51cee25667219bc6cf635a6c1c))
+
+## [2.9.3](https://github.com/revanced/revanced-patches/compare/v2.9.2...v2.9.3) (2022-07-06)
+
+
+### Bug Fixes
+
+* Readd `swipe-controls` patch ([#123](https://github.com/revanced/revanced-patches/issues/123)) ([7f2a2b2](https://github.com/revanced/revanced-patches/commit/7f2a2b2ee4e6045d53aba4e7705431b643981107))
+
+## [2.9.2](https://github.com/revanced/revanced-patches/compare/v2.9.1...v2.9.2) (2022-07-05)
+
+
+### Bug Fixes
+
+* revert `swipe-controls` patch ([66e1f33](https://github.com/revanced/revanced-patches/commit/66e1f3384a58361737ba889d946be875b23f3163))
+
+## [2.9.1](https://github.com/revanced/revanced-patches/compare/v2.9.0...v2.9.1) (2022-07-05)
+
+
+### Bug Fixes
+
+* make `minimized-playback-manager-fingerprint` unique ([#120](https://github.com/revanced/revanced-patches/issues/120)) ([cd5e911](https://github.com/revanced/revanced-patches/commit/cd5e911f4ed9ad95b02c13c30cd9466d250e8904))
+
+# [2.9.0](https://github.com/revanced/revanced-patches/compare/v2.8.2...v2.9.0) (2022-07-05)
+
+
+### Features
+
+* `swipe-controls` patch ([#115](https://github.com/revanced/revanced-patches/issues/115)) ([1d0a7dc](https://github.com/revanced/revanced-patches/commit/1d0a7dcc0cc3ea2bcd8ce0221d5e2f53d6eb0ae5))
+
+## [2.8.2](https://github.com/revanced/revanced-patches/compare/v2.8.1...v2.8.2) (2022-07-05)
+
+
+### Bug Fixes
+
+* show minimized playback options in settings ([#118](https://github.com/revanced/revanced-patches/issues/118)) ([6e1a538](https://github.com/revanced/revanced-patches/commit/6e1a538d34291d75f19bf66a188bc69241de3a7a))
+
+## [2.8.1](https://github.com/revanced/revanced-patches/compare/v2.8.0...v2.8.1) (2022-07-05)
+
+
+### Bug Fixes
+
+* remove unnecessary version constraints ([#117](https://github.com/revanced/revanced-patches/issues/117)) ([1cddf8d](https://github.com/revanced/revanced-patches/commit/1cddf8d9063da3bbdba0fd7080c8c93768b83a4c))
+
+# [2.8.0](https://github.com/revanced/revanced-patches/compare/v2.7.0...v2.8.0) (2022-07-04)
+
+
+### Features
+
+* `enable-debugging` patch ([#116](https://github.com/revanced/revanced-patches/issues/116)) ([bb355e7](https://github.com/revanced/revanced-patches/commit/bb355e7b7e78e602a10b346fe7e5795463615a81))
+
+# [2.7.0](https://github.com/revanced/revanced-patches/compare/v2.6.0...v2.7.0) (2022-07-03)
+
+
+### Features
+
+* `hdr-max-brightness` patch ([#105](https://github.com/revanced/revanced-patches/issues/105)) ([1310573](https://github.com/revanced/revanced-patches/commit/131057366a777786d6016d3385584b4e17bc4a8b))
+* `hide-infocard-suggestions` patch ([#107](https://github.com/revanced/revanced-patches/issues/107)) ([31a767a](https://github.com/revanced/revanced-patches/commit/31a767adbb152906303ab0ae5250769fc38d0625))
+
+# [2.6.0](https://github.com/revanced/revanced-patches/compare/v2.5.0...v2.6.0) (2022-07-02)
+
+
+### Bug Fixes
+
+* freezing panels when watching video in fullscreen ([#89](https://github.com/revanced/revanced-patches/issues/89)) ([f5d4f6c](https://github.com/revanced/revanced-patches/commit/f5d4f6c3419916c6a9cf67babc6be8a64c854d3b))
+* invalid version in compatibility annotation ([#90](https://github.com/revanced/revanced-patches/issues/90)) ([df43547](https://github.com/revanced/revanced-patches/commit/df435475cdd0494a1e4ea9e2980c2998c9bc7048))
+* remove refreshing home screen not working ([6c24ebe](https://github.com/revanced/revanced-patches/commit/6c24ebef2fb4f0d58e369ac5bf63e4cab6ca0e80))
+
+
+### Features
+
+* migrate to breaking changes of patcher ([d9147cd](https://github.com/revanced/revanced-patches/commit/d9147cd60c0c25d0c5cc05409b8889dfacd89af9))
+
+# [2.6.0-dev.2](https://github.com/revanced/revanced-patches/compare/v2.6.0-dev.1...v2.6.0-dev.2) (2022-07-02)
+
+
+### Bug Fixes
+
+* remove refreshing home screen not working ([ec7ae90](https://github.com/revanced/revanced-patches/commit/ec7ae900181b6456c692adb3b5bb337e81bc5fea))
+
+# [2.6.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.5.1-dev.1...v2.6.0-dev.1) (2022-07-02)
+
+
+### Features
+
+* migrate to breaking changes of patcher ([a116852](https://github.com/revanced/revanced-patches/commit/a11685263fb2274c67684258e73c5247502cb010))
+
+## [2.5.1-dev.1](https://github.com/revanced/revanced-patches/compare/v2.5.0...v2.5.1-dev.1) (2022-06-30)
+
+
+### Bug Fixes
+
+* freezing panels when watching video in fullscreen ([#89](https://github.com/revanced/revanced-patches/issues/89)) ([f5d4f6c](https://github.com/revanced/revanced-patches/commit/f5d4f6c3419916c6a9cf67babc6be8a64c854d3b))
+* invalid version in compatibility annotation ([#90](https://github.com/revanced/revanced-patches/issues/90)) ([df43547](https://github.com/revanced/revanced-patches/commit/df435475cdd0494a1e4ea9e2980c2998c9bc7048))
+
+# [2.5.0](https://github.com/revanced/revanced-patches/compare/v2.4.0...v2.5.0) (2022-06-30)
+
+
+### Features
+
+* hide watermark support for 17.25.34 ([#87](https://github.com/revanced/revanced-patches/issues/87)) ([0cdb65b](https://github.com/revanced/revanced-patches/commit/0cdb65bbb3e7b9d75eb393ee87e3718bcd6af4b3))
+
+# [2.4.0](https://github.com/revanced/revanced-patches/compare/v2.3.1...v2.4.0) (2022-06-30)
+
+
+### Features
+
+* add youtube version 17.25.34 ([#85](https://github.com/revanced/revanced-patches/issues/85)) ([889c9d5](https://github.com/revanced/revanced-patches/commit/889c9d564d16c6e68a52095a4fc8e6d04346c9e9))
+
+## [2.3.1](https://github.com/revanced/revanced-patches/compare/v2.3.0...v2.3.1) (2022-06-30)
+
+
+### Bug Fixes
+
+* change fingerprint to work on latest youtube ([#80](https://github.com/revanced/revanced-patches/issues/80)) ([4dba323](https://github.com/revanced/revanced-patches/commit/4dba323ddf8980cd2b0908a0de41c4b4dea6b0d7))
+
+# [2.3.0](https://github.com/revanced/revanced-patches/compare/v2.2.0...v2.3.0) (2022-06-30)
+
+
+### Features
+
+* `disable-autoplay-button` patch ([#79](https://github.com/revanced/revanced-patches/issues/79)) ([0d6fb51](https://github.com/revanced/revanced-patches/commit/0d6fb51e025649aae37e230778ea367482fab0d7))
+
+# [2.2.0](https://github.com/revanced/revanced-patches/compare/v2.1.0...v2.2.0) (2022-06-29)
+
+
+### Features
+
+* make resource mapping patch aware of types ([#77](https://github.com/revanced/revanced-patches/issues/77)) ([188491a](https://github.com/revanced/revanced-patches/commit/188491a707abccc1164413f075d8a66c145a1455))
+
+# [2.1.0](https://github.com/revanced/revanced-patches/compare/v2.0.3...v2.1.0) (2022-06-28)
+
+
+### Features
+
+* `custom-playback-speed` patch ([#50](https://github.com/revanced/revanced-patches/issues/50)) ([224254b](https://github.com/revanced/revanced-patches/commit/224254bcce2b394bbfd2549089f0204ce4ed4a89))
+
+## [2.0.3](https://github.com/revanced/revanced-patches/compare/v2.0.2...v2.0.3) (2022-06-27)
+
+
+### Bug Fixes
+
+* check if resource files exist ([ba1f3af](https://github.com/revanced/revanced-patches/commit/ba1f3af99be58edc44ed1b8f1875508d5034efd8))
+
+## [2.0.2](https://github.com/revanced/revanced-patches/compare/v2.0.1...v2.0.2) (2022-06-27)
+
+
+### Bug Fixes
+
+* some more refactoring of integrations, add hide-watermark patch ([#63](https://github.com/revanced/revanced-patches/issues/63)) ([feb09c5](https://github.com/revanced/revanced-patches/commit/feb09c56f475e2537a67d3636b08737848158a8e))
+
+## [2.0.1](https://github.com/revanced/revanced-patches/compare/v2.0.0...v2.0.1) (2022-06-26)
+
+
+### Bug Fixes
+
+* invalid string in strings list ([f08b53b](https://github.com/revanced/revanced-patches/commit/f08b53b07d93bd8ac6e7da376ea6e6023e53076e))
+* migrate to new `proxy` api ([db32ffe](https://github.com/revanced/revanced-patches/commit/db32ffe56a8e73177bef724ee10eda9a28b367b8))
+
+# [2.0.0](https://github.com/revanced/revanced-patches/compare/v1.11.0...v2.0.0) (2022-06-26)
+
+
+### Code Refactoring
+
+* migrate from `Signature` to `Fingerprint` ([084078e](https://github.com/revanced/revanced-patches/commit/084078e7f1852ccd2045e3fa8aedc25a7fd5faa8))
+
+
+### BREAKING CHANGES
+
+* Not backwards compatible, since a lot of classes where renamed.
+
+# [1.11.0](https://github.com/revanced/revanced-patches/compare/v1.10.5...v1.11.0) (2022-06-26)
+
+
+### Features
+
+* add youtube version 17.24.35 ([ec626cc](https://github.com/revanced/revanced-patches/commit/ec626ccfa2bcf14b722d08110382de009a1c12b4))
+* add youtube version 17.24.35 ([adc60a6](https://github.com/revanced/revanced-patches/commit/adc60a6fa09f6a21800ba51b8ca888d11a17e870))
+
+## [1.10.5](https://github.com/revanced/revanced-patches/compare/v1.10.4...v1.10.5) (2022-06-25)
+
+
+### Bug Fixes
+
+* put back proper variable ([#61](https://github.com/revanced/revanced-patches/issues/61)) ([d26c423](https://github.com/revanced/revanced-patches/commit/d26c4233031fd418eb37c8f05e9bc1857e0572e6))
+
+## [1.10.4](https://github.com/revanced/revanced-patches/compare/v1.10.3...v1.10.4) (2022-06-23)
+
+
+### Bug Fixes
+
+* missing brackets at inlining ([8936c8a](https://github.com/revanced/revanced-patches/commit/8936c8aaedb56817cda5eec5f4a8c32f433862aa))
+
+## [1.10.3](https://github.com/revanced/revanced-patches/compare/v1.10.2...v1.10.3) (2022-06-23)
+
+
+### Bug Fixes
+
+* get create button view register by more reliable means ([#59](https://github.com/revanced/revanced-patches/issues/59)) ([6ab821e](https://github.com/revanced/revanced-patches/commit/6ab821e377176f4e9f1b7ec2b58a924fa40299db))
+
+## [1.10.2](https://github.com/revanced/revanced-patches/compare/v1.10.1...v1.10.2) (2022-06-23)
+
+
+### Bug Fixes
+
+* `disable-create-button` not working with prebuilt jar file ([#55](https://github.com/revanced/revanced-patches/issues/55)) ([78be64a](https://github.com/revanced/revanced-patches/commit/78be64accc2023281c0c376849cdb0213622dc5c))
+
+## [1.10.1](https://github.com/revanced/revanced-patches/compare/v1.10.0...v1.10.1) (2022-06-23)
+
+
+### Bug Fixes
+
+* patcher not propagating dexlib ([980c486](https://github.com/revanced/revanced-patches/commit/980c48673259496d793bc7f864ad355188dcf7b6))
+* update patcher version ([e3d0bb7](https://github.com/revanced/revanced-patches/commit/e3d0bb7ee1923ea996cf637267c62d233a74c7fa))
+
+# [1.10.0](https://github.com/revanced/revanced-patches/compare/v1.9.1...v1.10.0) (2022-06-23)
+
+
+### Features
+
+* add compatibility for YouTube v17.23.36 and v17.24.34 ([1812bc3](https://github.com/revanced/revanced-patches/commit/1812bc39e0e88f1ab02ae8127e9000780eedf49c))
+
+## [1.9.1](https://github.com/revanced/revanced-patches/compare/v1.9.0...v1.9.1) (2022-06-21)
+
+
+### Bug Fixes
+
+* update patcher version ([5f54bc9](https://github.com/revanced/revanced-patches/commit/5f54bc9aa8fd8b83448141a9b05746e3e977369d))
+
+# [1.9.0](https://github.com/revanced/revanced-patches/compare/v1.8.2...v1.9.0) (2022-06-21)
+
+
+### Features
+
+* use `install` mode by default ([c2b2993](https://github.com/revanced/revanced-patches/commit/c2b299336a984d66a2d066e5ebe9c4f9bee5c2aa))
+
+## [1.8.2](https://github.com/revanced/revanced-patches/compare/v1.8.1...v1.8.2) (2022-06-20)
+
+
+### Bug Fixes
+
+* migrate to breaking changes from patcher ([2c0a419](https://github.com/revanced/revanced-patches/commit/2c0a4196fed2fbdcd454ed882b720898d3050c51))
+* old usage of `toInstructions` extension method ([65ddd52](https://github.com/revanced/revanced-patches/commit/65ddd522dca19e0590d9cb6fdb2d85ad7b98481e))
+
+## [1.8.1](https://github.com/revanced/revanced-patches/compare/v1.8.0...v1.8.1) (2022-06-20)
+
+
+### Bug Fixes
+
+* add execute permission to `./gradlew` file ([ff7a560](https://github.com/revanced/revanced-patches/commit/ff7a5602f68428111fea6c60cbea694592039ef1))
+
+# [1.8.0](https://github.com/revanced/revanced-patches/compare/v1.7.0...v1.8.0) (2022-06-20)
+
+
+### Features
+
+* bump compatibility of patches for Youtube to v17.23.35 ([3748d05](https://github.com/revanced/revanced-patches/commit/3748d0533e62a8871ab2202ce9b61170a90dae62))
+
+# [1.7.0](https://github.com/revanced/revanced-patches/compare/v1.6.4...v1.7.0) (2022-06-20)
+
+
+### Features
+
+* `disable-fullscreen-panels` patch ([3bf0561](https://github.com/revanced/revanced-patches/commit/3bf056163500b006d1a20c5f3a3e0c92fec13bd8))
+
+## [1.6.4](https://github.com/revanced/revanced-patches/compare/v1.6.3...v1.6.4) (2022-06-19)
+
+
+### Bug Fixes
+
+* update patcher version ([#35](https://github.com/revanced/revanced-patches/issues/35)) ([1a379df](https://github.com/revanced/revanced-patches/commit/1a379dfd974b9f92d4bd0d5d7a4711eb6d1060b3)), closes [#34](https://github.com/revanced/revanced-patches/issues/34)
+
+## [1.6.3](https://github.com/revanced/revanced-patches/compare/v1.6.2...v1.6.3) (2022-06-16)
+
+
+### Bug Fixes
+
+* wrong dex path ([170fbbb](https://github.com/revanced/revanced-patches/commit/170fbbb99e4a2dbe3e0febe44d07a692aa9d7224))
+
+## [1.6.2](https://github.com/revanced/revanced-patches/compare/v1.6.1...v1.6.2) (2022-06-16)
+
+
+### Bug Fixes
+
+* broken gradle task ([91483a8](https://github.com/revanced/revanced-patches/commit/91483a8fbf92559d079dc52f846f5f871f5d6b5c))
+
+## [1.6.1](https://github.com/revanced/revanced-patches/compare/v1.6.0...v1.6.1) (2022-06-16)
+
+
+### Bug Fixes
+
+* broken gradle task ([4d07961](https://github.com/revanced/revanced-patches/commit/4d07961c8afd24da7f8879d11419147f2e100f05))
+
+# [1.6.0](https://github.com/revanced/revanced-patches/compare/v1.5.3...v1.6.0) (2022-06-16)
+
+
+### Bug Fixes
+
+* broken gradle task ([28e3f55](https://github.com/revanced/revanced-patches/commit/28e3f554ea6a7144416523fe48ce7adbb613b263))
+
+
+### Features
+
+* generate dex files using gradle task ([c34c1be](https://github.com/revanced/revanced-patches/commit/c34c1be21f50b4f720a7cd81e0dfe5ef6330caab))
+
+## [1.5.3](https://github.com/revanced/revanced-patches/compare/v1.5.2...v1.5.3) (2022-06-16)
+
+
+### Bug Fixes
+
+* forgot about this ([7102a25](https://github.com/revanced/revanced-patches/commit/7102a25dc618f19b324b01870d23f5418f375b2a))
+
+## [1.5.2](https://github.com/revanced/revanced-patches/compare/v1.5.1...v1.5.2) (2022-06-16)
+
+
+### Bug Fixes
+
+* dummy task for Gradle semantic-release plugin ([f6a8911](https://github.com/revanced/revanced-patches/commit/f6a8911906dfe52fcdb685daf7a02d6d0052cba9))
+* releases ([30d5c9a](https://github.com/revanced/revanced-patches/commit/30d5c9a67ccf88ca6ac00d0a9f2a2e330f8092dd))
+
+## [1.5.1](https://github.com/revanced/revanced-patches/compare/v1.5.0...v1.5.1) (2022-06-16)
+
+
+### Reverts
+
+* Revert "Changed app name to ReVanced (#21)" (#24) ([70a48c5](https://github.com/revanced/revanced-patches/commit/70a48c5f35cd236612352a1dbbf50487625e6e96)), closes [#21](https://github.com/revanced/revanced-patches/issues/21) [#24](https://github.com/revanced/revanced-patches/issues/24)
+
+# [1.5.0](https://github.com/revanced/revanced-patches/compare/v1.4.0...v1.5.0) (2022-06-15)
+
+
+### Bug Fixes
+
+* sync version ([6170e36](https://github.com/revanced/revanced-patches/commit/6170e3689d9c8998be94a8464352af620cccd11b))
+
+
+### Features
+
+* `hide-cast-button` patch ([2cd531e](https://github.com/revanced/revanced-patches/commit/2cd531eb5a334f3cf91cba4556f07e863cd9ec1b))
+
+# [1.4.0](https://github.com/revanced/revanced-patches/compare/v1.3.1...v1.4.0) (2022-06-15)
+
+
+### Bug Fixes
+
+* add size `48px` for `custom-branding` patch ([f81872b](https://github.com/revanced/revanced-patches/commit/f81872b8e41da215517fdb59364130d8ce681607))
+
+
+### Features
+
+* `premium-heading` patch ([78913bf](https://github.com/revanced/revanced-patches/commit/78913bf1e80f5b91d0dee506fdfe3f875e8e6988))
+
+## [1.3.1](https://github.com/revanced/revanced-patches/compare/v1.3.0...v1.3.1) (2022-06-15)
+
+
+### Bug Fixes
+
+* `custom-branding` patch failing to get resources ([efb6d4c](https://github.com/revanced/revanced-patches/commit/efb6d4c2be515185fc9bd29c40ce202f0d684cee))
+
+# [1.3.0](https://github.com/revanced/revanced-patches/compare/v1.2.2...v1.3.0) (2022-06-14)
+
+
+### Features
+
+* `custom-branding` patch ([0d65ea8](https://github.com/revanced/revanced-patches/commit/0d65ea8cdb0e02287f6be6855cd3d28823a61e70))
+
+## [1.2.2](https://github.com/revanced/revanced-patches/compare/v1.2.1...v1.2.2) (2022-06-13)
+
+
+### Bug Fixes
+
+* environment variable not found in gradle build script ([0da15fb](https://github.com/revanced/revanced-patches/commit/0da15fb0effac0566d080d7b85e9fbe46c3dd34d))
+
+## [1.2.1](https://github.com/revanced/revanced-patches/compare/v1.2.0...v1.2.1) (2022-06-12)
+
+
+### Performance Improvements
+
+* fix high battery consumption due to chromecast not working with `microg` ([dd8b01a](https://github.com/revanced/revanced-patches/commit/dd8b01a5c5d75b00ea4d04ce35bc43942c1b0409))
+
+# [1.2.0](https://github.com/revanced/revanced-patches/compare/v1.1.0...v1.2.0) (2022-06-11)
+
+
+### Bug Fixes
+
+* migrate to `include` annotation ([110bbf1](https://github.com/revanced/revanced-patches/commit/110bbf143a9cec8dce1f0416cff40f8d93055e96))
+
+
+### Features
+
+* `microg-patch` ([48bbd57](https://github.com/revanced/revanced-patches/commit/48bbd574a52c8bf6834b26facfe7384b830f944a))
+* updated all patches to support v17.22.36 of `com.android.google.youtube` ([e12dc11](https://github.com/revanced/revanced-patches/commit/e12dc11b670c2b0c414741616e0a646e8421e418))
+
+# [1.1.0](https://github.com/revanced/revanced-patches/compare/v1.0.0...v1.1.0) (2022-06-11)
+
+
+### Features
+
+* generate & upload dex files ([#18](https://github.com/revanced/revanced-patches/issues/18)) ([e6f5355](https://github.com/revanced/revanced-patches/commit/e6f53553a98c164c4eb926fb273358ed506e00a4))
+
+# 1.0.0 (2022-06-05)
+
+
+### Bug Fixes
+
+* `create-button-signature` ([a173f6e](https://github.com/revanced/revanced-patches/commit/a173f6e5a7e65943657e2072e8a72a4a680e5277))
+* `enable-seekbar-tapping` patch ([52fd726](https://github.com/revanced/revanced-patches/commit/52fd726d9b0d2efbd0f9742fc84ad01ccdcff168))
+* `Index` in wrong package ([2f9360f](https://github.com/revanced/revanced-patches/commit/2f9360f57cc8415564534fbbd8bd5e2a83a1b629))
+* `minimized-playback` & `old-quality-layout` wrong opcodes ([b45d175](https://github.com/revanced/revanced-patches/commit/b45d175c6f1ece6da894ab16128c2644d262c9c7))
+* `minimized-playback` patch ([55677a4](https://github.com/revanced/revanced-patches/commit/55677a44ff965c0b92c3f1d771bd68c12c142ad4))
+* `SignatureChecker` not handling nullable field `methodMetadata` ([17bcf78](https://github.com/revanced/revanced-patches/commit/17bcf786a85ccf1f7d9f5a66a044a3c26def09bb))
+* accidentally removed code in refactor ([0077e26](https://github.com/revanced/revanced-patches/commit/0077e26d23cc112b671a41614a55348fac2c88ca))
+* add missing `trimIndent()` to string literals ([76d3c71](https://github.com/revanced/revanced-patches/commit/76d3c71b67edebd79f2cdb1bb28e4d2969d72223))
+* add missing opcode for `create-button-method` ([0a398ef](https://github.com/revanced/revanced-patches/commit/0a398ef364f91a0dd9608df1a036a2515476ccf2))
+* attempt on all patches ([3395d69](https://github.com/revanced/revanced-patches/commit/3395d69747103a4bdf314297aa0bfa6ef6a0fc36))
+* breaking changes by `revanced-patcher` dependency ([e12e484](https://github.com/revanced/revanced-patches/commit/e12e484e3796c5c9c8505b677838cdf8432f2e79))
+* breaking changes by `revanced-patcher` dependency ([7e485b4](https://github.com/revanced/revanced-patches/commit/7e485b4ffe204d724809aeb9bd9f693a35ded94d))
+* breaking changes of the patcher ([1a49bbd](https://github.com/revanced/revanced-patches/commit/1a49bbdbc4ff6f427934259536218e161908b449))
+* breaking patcher changes ([50f9cc5](https://github.com/revanced/revanced-patches/commit/50f9cc52acfd5bc23330ecd23d8d85678a9d3eee))
+* breaking patcher changes ([cbb9e2c](https://github.com/revanced/revanced-patches/commit/cbb9e2cd1fa829e1d1dd92dbd40131b11ae6a05b))
+* breaking patcher changes ([581d1b0](https://github.com/revanced/revanced-patches/commit/581d1b0ca7d15adcdb1ab6116ef035acfe701757))
+* bugfixes in `microg` ([a43b33b](https://github.com/revanced/revanced-patches/commit/a43b33bdbb2b36e0a8f991fa11dfeeec34de01f9))
+* clean after building ([a2df3fb](https://github.com/revanced/revanced-patches/commit/a2df3fbc9761b07f3010542fa8684ade00e4dc91))
+* disable `hide-suggestions-patch` patch until fixed ([99099ea](https://github.com/revanced/revanced-patches/commit/99099ea0bc12f5f25896967db642442df69d0c4f))
+* incorrect endIndex (fixed in Patcher) ([424788e](https://github.com/revanced/revanced-patches/commit/424788edd777110cdaff97500556d18628f33385))
+* loop in `amoled` patch ([c4c86b6](https://github.com/revanced/revanced-patches/commit/c4c86b65fd8b2463c1d86ad2e46ec9f08e60d47c))
+* migrate patches to latest patcher api changes ([8a0ee03](https://github.com/revanced/revanced-patches/commit/8a0ee03a71cf4a000c9a7246d0e64ed8291a5127))
+* missing extension method `doRecursively` ([e9c9460](https://github.com/revanced/revanced-patches/commit/e9c946008ee912652d288e515b83b52ae2d239d8))
+* modified opcode for `show-video-ads-constructor` ([a0dcea3](https://github.com/revanced/revanced-patches/commit/a0dcea3a13f68cae449dfaf445b542e339c83ff0))
+* multiple bugs in patches ([e37201d](https://github.com/revanced/revanced-patches/commit/e37201d0ceef474696857a0d8845950c888194d0))
+* name for `IntegrationsPatch` ([e46ef02](https://github.com/revanced/revanced-patches/commit/e46ef02302825d62b57912b2747a25f858036bb7))
+* print instruction index of warning ([9e29aee](https://github.com/revanced/revanced-patches/commit/9e29aeeeff222412f6c45cf7e4879f8ec53ca6ee))
+* publish releases ([83916f9](https://github.com/revanced/revanced-patches/commit/83916f96d27989dcbb35c0ba6ef326a16b470501))
+* remove `HideSuggestionsPatch` from `Index` ([f32e474](https://github.com/revanced/revanced-patches/commit/f32e4747b512c675b807ff5eebfd0b8e66173fba))
+* remove unused patches ([d12e92a](https://github.com/revanced/revanced-patches/commit/d12e92aead677fefa9dcb48748d783225b65fab1))
+* signature checker with changes of patcher ([e82459d](https://github.com/revanced/revanced-patches/commit/e82459d37759e1a5a860d3e7fcdf69d95b06858e))
+* spelling mistake ([52f9147](https://github.com/revanced/revanced-patches/commit/52f9147ee8d591f786397d174dc02a141d9250a9))
+* tests failing ([102793f](https://github.com/revanced/revanced-patches/commit/102793f24f8bf7c7fd254968b29d65da7b2b962f))
+* update `HomeAdsPatch` ([62f1801](https://github.com/revanced/revanced-patches/commit/62f1801e9cbee53c0be3413c245161bd941e4aec))
+* update patches ([91b8ec8](https://github.com/revanced/revanced-patches/commit/91b8ec81f33417798546c32db708fe09ada3930c))
+* use the latest version of patcher dependency ([fe4a439](https://github.com/revanced/revanced-patches/commit/fe4a439cb2bc5e385ae13e8e155f25bb15e74633))
+* version in package metadata of music ([b299205](https://github.com/revanced/revanced-patches/commit/b299205aa7cde82f1f55fc598de3ff8d80b8bcb0))
+* **VideoAds:** remove `istore1` opcode ([dc4ec57](https://github.com/revanced/revanced-patches/commit/dc4ec574414c5df959efa0ca8f1cd39a812fedf8))
+* write while reading resources and remove checking for "." in resource extensions ([7bc6094](https://github.com/revanced/revanced-patches/commit/7bc60943cb2350e89dac091ec9c98c5effd0b8a9))
+* wrong access flag in signature for `Create button patch` ([9fbb89d](https://github.com/revanced/revanced-patches/commit/9fbb89d05336a256a0759eea6095e073946c45e5))
+* wrong annotation and signature in patches ([a0fdee8](https://github.com/revanced/revanced-patches/commit/a0fdee81a6d6773603520e7c3040ae8637642d58))
+* wrong opcode for `create-button-method` ([3214650](https://github.com/revanced/revanced-patches/commit/32146506f139aebc44cd5faffb7706b8b9c21c3d))
+* wrong opcode pattern for `create-button-method` ([f4d8a85](https://github.com/revanced/revanced-patches/commit/f4d8a8525bc64b90748b21979d463977a21dcd85))
+* wrong opcode pattern for `enable-seekbar-tapping-signature` ([1d83395](https://github.com/revanced/revanced-patches/commit/1d833957ed3e01188770c85e3d84e483419bd797))
+* wrong opcode pattern for signature in `Hide suggestions patch` ([535aee0](https://github.com/revanced/revanced-patches/commit/535aee08408b990c80f5966c13fa84666a8b35d0))
+* wrong patches in `upgrade-tab-remover` ([5182290](https://github.com/revanced/revanced-patches/commit/518229031ceca049ad790f7b77b19405d39f0ce1))
+* wrong signature for `hide-reels-signature` ([2d9ff2a](https://github.com/revanced/revanced-patches/commit/2d9ff2af0a991d7721f3741187716a3b08bb4029))
+* wrong signatures for patch `Old Quality Layout Patch` ([823e503](https://github.com/revanced/revanced-patches/commit/823e503d84037bdf27b09f17e63383f963c76854))
+* wrong versions of patches ([a112b22](https://github.com/revanced/revanced-patches/commit/a112b22ce6e685204caab6f95f511e26ef95806b))
+
+
+### Code Refactoring
+
+* Rename `net.revanced` to `app.revanced` ([68ea89f](https://github.com/revanced/revanced-patches/commit/68ea89f15e9ea077df0d0ac20a40b735bb5ae26c))
+
+
+### Features
+
+* `Dependencies` annotation ([85806bb](https://github.com/revanced/revanced-patches/commit/85806bb355e342ecb33d4ee1e76f9edf89b2c2ee))
+* `GeneralBytecodeAds` and `GeneralResourceAds` patch ([f99bbef](https://github.com/revanced/revanced-patches/commit/f99bbef4c911ac2492166c7a3792ea11ffceffab))
+* `hide-shorts-button` patch ([88352ee](https://github.com/revanced/revanced-patches/commit/88352ee6ecd23faa4a7fd9f7495e67fa1d3e33bd))
+* `tastebuilder-remover` for music ([a6aeca3](https://github.com/revanced/revanced-patches/commit/a6aeca31bd80b8c4a8acd071e22faca6e136bdb0))
+* Add (WIP) Signature Checker ([ae4c7b2](https://github.com/revanced/revanced-patches/commit/ae4c7b29f211c461de460f97f3a8656e795adafb))
+* add `amoled` patch ([d61bac4](https://github.com/revanced/revanced-patches/commit/d61bac4f8243d0ef72ca91c7c1d5facd858d515e))
+* add home ads patch ([36cddd1](https://github.com/revanced/revanced-patches/commit/36cddd1488683e19e2b927e34c80a4f0f3cace35))
+* add patches compatibility to the newest version ([799401d](https://github.com/revanced/revanced-patches/commit/799401ddf99da0aaa5f52c39d3d3d4061370fd75))
+* add publishing to package registry ([b475e09](https://github.com/revanced/revanced-patches/commit/b475e09577db4dda7bbb45dbf170d78772834a6d))
+* add semantic-release ([d60f1d0](https://github.com/revanced/revanced-patches/commit/d60f1d06798d312b158b71691ecc87e828dccbc1))
+* added `codecs-unlock` patch ([e5fd7ce](https://github.com/revanced/revanced-patches/commit/e5fd7cece94b1ff5342178f59b29576db806e0f6))
+* begin `MicroG Patch` ([91474ba](https://github.com/revanced/revanced-patches/commit/91474ba07376c13e7a71685dfd8b6e6913ed5ee9))
+* display metadata for each signature in `SignatureChecker` ([736a71f](https://github.com/revanced/revanced-patches/commit/736a71fac21a32dbb1eef9c3a9f0d3005e7d9ca0))
+* get required register dynamically ([0924ca2](https://github.com/revanced/revanced-patches/commit/0924ca2ad30ae865dcc0fd484cb0da517e827352))
+* Initial commit ([bee5f2f](https://github.com/revanced/revanced-patches/commit/bee5f2faed882271ed059b0435e6e1aa91f93dbd))
+* integrations patch ([19c0b0d](https://github.com/revanced/revanced-patches/commit/19c0b0d194bb97c7248ea7a9b081176961653b9d))
+* migrate to dalvik patches ([e088c67](https://github.com/revanced/revanced-patches/commit/e088c671081bcf75586ccc1c4bdbed9366e93874))
+* MinimizedPlayback, CreateButtonRemover ([cc08c6c](https://github.com/revanced/revanced-patches/commit/cc08c6c3d38879dd4672ec671631b34aa2e3cc77))
+* OldQualityLayout, HideSuggestions, HideReels, EnableSeekbarTapping ([04a7cff](https://github.com/revanced/revanced-patches/commit/04a7cfff20d2734b1c92713de4e7e08a3b93ee85))
+* Patches for YouTube Music ([b60c9d3](https://github.com/revanced/revanced-patches/commit/b60c9d33b611bb4d5b55bb419652bc14b0309792))
+* remove obsolete patch `Hide suggestions patch` ([e65c6f2](https://github.com/revanced/revanced-patches/commit/e65c6f240ed23a54271d20a90fc57ec65cafc02d))
+* update MicroG patch to latest version ([c24f806](https://github.com/revanced/revanced-patches/commit/c24f8063a04f89aea2d2f7087a435738de7dfeae))
+* update patches to latest version ([bad25de](https://github.com/revanced/revanced-patches/commit/bad25dec1d73137f8b7a1bf4daaceb2279b4d48c))
+* use supplier instead of KClass ([08af6e5](https://github.com/revanced/revanced-patches/commit/08af6e54af79ef9ef4fb3372a348ce9b6fba4d20))
+* use supplier instead of KClass ([91aa019](https://github.com/revanced/revanced-patches/commit/91aa019f8d3d87fbf7affeb7abc2b02ba87af5c3))
+
+
+### BREAKING CHANGES
+
+* signature of patches was changed
+* signature of patches was changed
+* Package name was changed from "net.revanced" to "app.revanced"
+
+# [1.0.0-dev.16](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.15...v1.0.0-dev.16) (2022-06-05)
+
+
+### Features
+
+* add patches compatibility to the newest version ([799401d](https://github.com/revanced/revanced-patches/commit/799401ddf99da0aaa5f52c39d3d3d4061370fd75))
+
+# [1.0.0-dev.15](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.14...v1.0.0-dev.15) (2022-06-05)
+
+
+### Bug Fixes
+
+* remove unused patches ([d12e92a](https://github.com/revanced/revanced-patches/commit/d12e92aead677fefa9dcb48748d783225b65fab1))
+
+# [1.0.0-dev.14](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.13...v1.0.0-dev.14) (2022-06-04)
+
+
+### Bug Fixes
+
+* spelling mistake ([52f9147](https://github.com/revanced/revanced-patches/commit/52f9147ee8d591f786397d174dc02a141d9250a9))
+
+
+### Features
+
+* `Dependencies` annotation ([85806bb](https://github.com/revanced/revanced-patches/commit/85806bb355e342ecb33d4ee1e76f9edf89b2c2ee))
+* `GeneralBytecodeAds` and `GeneralResourceAds` patch ([f99bbef](https://github.com/revanced/revanced-patches/commit/f99bbef4c911ac2492166c7a3792ea11ffceffab))
+
+# [1.0.0-dev.13](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.12...v1.0.0-dev.13) (2022-05-31)
+
+
+### Bug Fixes
+
+* migrate patches to latest patcher api changes ([8a0ee03](https://github.com/revanced/revanced-patches/commit/8a0ee03a71cf4a000c9a7246d0e64ed8291a5127))
+* missing extension method `doRecursively` ([e9c9460](https://github.com/revanced/revanced-patches/commit/e9c946008ee912652d288e515b83b52ae2d239d8))
+
+# [1.0.0-dev.12](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2022-05-28)
+
+
+### Bug Fixes
+
+* wrong annotation and signature in patches ([a0fdee8](https://github.com/revanced/revanced-patches/commit/a0fdee81a6d6773603520e7c3040ae8637642d58))
+
+# [1.0.0-dev.11](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2022-05-26)
+
+
+### Bug Fixes
+
+* breaking changes by `revanced-patcher` dependency ([7e485b4](https://github.com/revanced/revanced-patches/commit/7e485b4ffe204d724809aeb9bd9f693a35ded94d))
+* breaking patcher changes ([50f9cc5](https://github.com/revanced/revanced-patches/commit/50f9cc52acfd5bc23330ecd23d8d85678a9d3eee))
+* breaking patcher changes ([cbb9e2c](https://github.com/revanced/revanced-patches/commit/cbb9e2cd1fa829e1d1dd92dbd40131b11ae6a05b))
+* bugfixes in `microg` ([a43b33b](https://github.com/revanced/revanced-patches/commit/a43b33bdbb2b36e0a8f991fa11dfeeec34de01f9))
+* write while reading resources and remove checking for "." in resource extensions ([7bc6094](https://github.com/revanced/revanced-patches/commit/7bc60943cb2350e89dac091ec9c98c5effd0b8a9))
+
+
+### Features
+
+* begin `MicroG Patch` ([91474ba](https://github.com/revanced/revanced-patches/commit/91474ba07376c13e7a71685dfd8b6e6913ed5ee9))
+* update MicroG patch to latest version ([c24f806](https://github.com/revanced/revanced-patches/commit/c24f8063a04f89aea2d2f7087a435738de7dfeae))
+
+# [1.0.0-dev.11](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2022-05-26)
+
+
+### Bug Fixes
+
+* breaking changes by `revanced-patcher` dependency ([7e485b4](https://github.com/revanced/revanced-patches/commit/7e485b4ffe204d724809aeb9bd9f693a35ded94d))
+* breaking patcher changes ([50f9cc5](https://github.com/revanced/revanced-patches/commit/50f9cc52acfd5bc23330ecd23d8d85678a9d3eee))
+* breaking patcher changes ([cbb9e2c](https://github.com/revanced/revanced-patches/commit/cbb9e2cd1fa829e1d1dd92dbd40131b11ae6a05b))
+* bugfixes in `microg` ([a43b33b](https://github.com/revanced/revanced-patches/commit/a43b33bdbb2b36e0a8f991fa11dfeeec34de01f9))
+* write while reading resources and remove checking for "." in resource extensions ([7bc6094](https://github.com/revanced/revanced-patches/commit/7bc60943cb2350e89dac091ec9c98c5effd0b8a9))
+
+
+### Features
+
+* begin `MicroG Patch` ([91474ba](https://github.com/revanced/revanced-patches/commit/91474ba07376c13e7a71685dfd8b6e6913ed5ee9))
+* update MicroG patch to latest version ([c24f806](https://github.com/revanced/revanced-patches/commit/c24f8063a04f89aea2d2f7087a435738de7dfeae))
+
+# [1.0.0-dev.10](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2022-05-22)
+
+
+### Bug Fixes
+
+* `create-button-signature` ([a173f6e](https://github.com/revanced/revanced-patches/commit/a173f6e5a7e65943657e2072e8a72a4a680e5277))
+* breaking changes by `revanced-patcher` dependency ([e12e484](https://github.com/revanced/revanced-patches/commit/e12e484e3796c5c9c8505b677838cdf8432f2e79))
+* loop in `amoled` patch ([c4c86b6](https://github.com/revanced/revanced-patches/commit/c4c86b65fd8b2463c1d86ad2e46ec9f08e60d47c))
+
+
+### Features
+
+* add `amoled` patch ([d61bac4](https://github.com/revanced/revanced-patches/commit/d61bac4f8243d0ef72ca91c7c1d5facd858d515e))
+* update patches to latest version ([bad25de](https://github.com/revanced/revanced-patches/commit/bad25dec1d73137f8b7a1bf4daaceb2279b4d48c))
+
+# [1.0.0-dev.9](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2022-05-13)
+
+
+### Bug Fixes
+
+* clean after building ([a2df3fb](https://github.com/revanced/revanced-patches/commit/a2df3fbc9761b07f3010542fa8684ade00e4dc91))
+
+# [1.0.0-dev.8](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2022-05-13)
+
+
+### Bug Fixes
+
+* publish releases ([83916f9](https://github.com/revanced/revanced-patches/commit/83916f96d27989dcbb35c0ba6ef326a16b470501))
+
+# [1.0.0-dev.7](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2022-05-07)
+
+
+### Bug Fixes
+
+* use the latest version of patcher dependency ([fe4a439](https://github.com/revanced/revanced-patches/commit/fe4a439cb2bc5e385ae13e8e155f25bb15e74633))
+
+# [1.0.0-dev.6](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2022-05-07)
+
+
+### Bug Fixes
+
+* update patches ([91b8ec8](https://github.com/revanced/revanced-patches/commit/91b8ec81f33417798546c32db708fe09ada3930c))
+
+# [1.0.0-dev.5](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2022-05-07)
+
+
+### Bug Fixes
+
+* `enable-seekbar-tapping` patch ([52fd726](https://github.com/revanced/revanced-patches/commit/52fd726d9b0d2efbd0f9742fc84ad01ccdcff168))
+* `Index` in wrong package ([2f9360f](https://github.com/revanced/revanced-patches/commit/2f9360f57cc8415564534fbbd8bd5e2a83a1b629))
+* `minimized-playback` & `old-quality-layout` wrong opcodes ([b45d175](https://github.com/revanced/revanced-patches/commit/b45d175c6f1ece6da894ab16128c2644d262c9c7))
+* `minimized-playback` patch ([55677a4](https://github.com/revanced/revanced-patches/commit/55677a44ff965c0b92c3f1d771bd68c12c142ad4))
+* `SignatureChecker` not handling nullable field `methodMetadata` ([17bcf78](https://github.com/revanced/revanced-patches/commit/17bcf786a85ccf1f7d9f5a66a044a3c26def09bb))
+* accidentally removed code in refactor ([0077e26](https://github.com/revanced/revanced-patches/commit/0077e26d23cc112b671a41614a55348fac2c88ca))
+* add missing `trimIndent()` to string literals ([76d3c71](https://github.com/revanced/revanced-patches/commit/76d3c71b67edebd79f2cdb1bb28e4d2969d72223))
+* add missing opcode for `create-button-method` ([0a398ef](https://github.com/revanced/revanced-patches/commit/0a398ef364f91a0dd9608df1a036a2515476ccf2))
+* attempt on all patches ([3395d69](https://github.com/revanced/revanced-patches/commit/3395d69747103a4bdf314297aa0bfa6ef6a0fc36))
+* breaking changes of the patcher ([1a49bbd](https://github.com/revanced/revanced-patches/commit/1a49bbdbc4ff6f427934259536218e161908b449))
+* breaking patcher changes ([581d1b0](https://github.com/revanced/revanced-patches/commit/581d1b0ca7d15adcdb1ab6116ef035acfe701757))
+* disable `hide-suggestions-patch` patch until fixed ([99099ea](https://github.com/revanced/revanced-patches/commit/99099ea0bc12f5f25896967db642442df69d0c4f))
+* incorrect endIndex (fixed in Patcher) ([424788e](https://github.com/revanced/revanced-patches/commit/424788edd777110cdaff97500556d18628f33385))
+* modified opcode for `show-video-ads-constructor` ([a0dcea3](https://github.com/revanced/revanced-patches/commit/a0dcea3a13f68cae449dfaf445b542e339c83ff0))
+* multiple bugs in patches ([e37201d](https://github.com/revanced/revanced-patches/commit/e37201d0ceef474696857a0d8845950c888194d0))
+* name for `IntegrationsPatch` ([e46ef02](https://github.com/revanced/revanced-patches/commit/e46ef02302825d62b57912b2747a25f858036bb7))
+* print instruction index of warning ([9e29aee](https://github.com/revanced/revanced-patches/commit/9e29aeeeff222412f6c45cf7e4879f8ec53ca6ee))
+* remove `HideSuggestionsPatch` from `Index` ([f32e474](https://github.com/revanced/revanced-patches/commit/f32e4747b512c675b807ff5eebfd0b8e66173fba))
+* signature checker with changes of patcher ([e82459d](https://github.com/revanced/revanced-patches/commit/e82459d37759e1a5a860d3e7fcdf69d95b06858e))
+* tests failing ([102793f](https://github.com/revanced/revanced-patches/commit/102793f24f8bf7c7fd254968b29d65da7b2b962f))
+* update `HomeAdsPatch` ([62f1801](https://github.com/revanced/revanced-patches/commit/62f1801e9cbee53c0be3413c245161bd941e4aec))
+* version in package metadata of music ([b299205](https://github.com/revanced/revanced-patches/commit/b299205aa7cde82f1f55fc598de3ff8d80b8bcb0))
+* **VideoAds:** remove `istore1` opcode ([dc4ec57](https://github.com/revanced/revanced-patches/commit/dc4ec574414c5df959efa0ca8f1cd39a812fedf8))
+* wrong access flag in signature for `Create button patch` ([9fbb89d](https://github.com/revanced/revanced-patches/commit/9fbb89d05336a256a0759eea6095e073946c45e5))
+* wrong opcode for `create-button-method` ([3214650](https://github.com/revanced/revanced-patches/commit/32146506f139aebc44cd5faffb7706b8b9c21c3d))
+* wrong opcode pattern for `create-button-method` ([f4d8a85](https://github.com/revanced/revanced-patches/commit/f4d8a8525bc64b90748b21979d463977a21dcd85))
+* wrong opcode pattern for `enable-seekbar-tapping-signature` ([1d83395](https://github.com/revanced/revanced-patches/commit/1d833957ed3e01188770c85e3d84e483419bd797))
+* wrong opcode pattern for signature in `Hide suggestions patch` ([535aee0](https://github.com/revanced/revanced-patches/commit/535aee08408b990c80f5966c13fa84666a8b35d0))
+* wrong patches in `upgrade-tab-remover` ([5182290](https://github.com/revanced/revanced-patches/commit/518229031ceca049ad790f7b77b19405d39f0ce1))
+* wrong signature for `hide-reels-signature` ([2d9ff2a](https://github.com/revanced/revanced-patches/commit/2d9ff2af0a991d7721f3741187716a3b08bb4029))
+* wrong signatures for patch `Old Quality Layout Patch` ([823e503](https://github.com/revanced/revanced-patches/commit/823e503d84037bdf27b09f17e63383f963c76854))
+* wrong versions of patches ([a112b22](https://github.com/revanced/revanced-patches/commit/a112b22ce6e685204caab6f95f511e26ef95806b))
+
+
+### Features
+
+* `hide-shorts-button` patch ([88352ee](https://github.com/revanced/revanced-patches/commit/88352ee6ecd23faa4a7fd9f7495e67fa1d3e33bd))
+* `tastebuilder-remover` for music ([a6aeca3](https://github.com/revanced/revanced-patches/commit/a6aeca31bd80b8c4a8acd071e22faca6e136bdb0))
+* Add (WIP) Signature Checker ([ae4c7b2](https://github.com/revanced/revanced-patches/commit/ae4c7b29f211c461de460f97f3a8656e795adafb))
+* add home ads patch ([36cddd1](https://github.com/revanced/revanced-patches/commit/36cddd1488683e19e2b927e34c80a4f0f3cace35))
+* added `codecs-unlock` patch ([e5fd7ce](https://github.com/revanced/revanced-patches/commit/e5fd7cece94b1ff5342178f59b29576db806e0f6))
+* display metadata for each signature in `SignatureChecker` ([736a71f](https://github.com/revanced/revanced-patches/commit/736a71fac21a32dbb1eef9c3a9f0d3005e7d9ca0))
+* get required register dynamically ([0924ca2](https://github.com/revanced/revanced-patches/commit/0924ca2ad30ae865dcc0fd484cb0da517e827352))
+* integrations patch ([19c0b0d](https://github.com/revanced/revanced-patches/commit/19c0b0d194bb97c7248ea7a9b081176961653b9d))
+* migrate to dalvik patches ([e088c67](https://github.com/revanced/revanced-patches/commit/e088c671081bcf75586ccc1c4bdbed9366e93874))
+* Patches for YouTube Music ([b60c9d3](https://github.com/revanced/revanced-patches/commit/b60c9d33b611bb4d5b55bb419652bc14b0309792))
+* remove obsolete patch `Hide suggestions patch` ([e65c6f2](https://github.com/revanced/revanced-patches/commit/e65c6f240ed23a54271d20a90fc57ec65cafc02d))
+
+# [1.0.0-dev.4](https://github.com/ReVancedTeam/revanced-patches/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2022-03-22)
+
+
+### Features
+
+* use supplier instead of KClass ([08af6e5](https://github.com/ReVancedTeam/revanced-patches/commit/08af6e54af79ef9ef4fb3372a348ce9b6fba4d20))
+* use supplier instead of KClass ([91aa019](https://github.com/ReVancedTeam/revanced-patches/commit/91aa019f8d3d87fbf7affeb7abc2b02ba87af5c3))
+
+
+### BREAKING CHANGES
+
+* signature of patches was changed
+* signature of patches was changed
+
+# [1.0.0-dev.3](https://github.com/ReVancedTeam/revanced-patches/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2022-03-21)
+
+
+### Features
+
+* OldQualityLayout, HideSuggestions, HideReels, EnableSeekbarTapping ([04a7cff](https://github.com/ReVancedTeam/revanced-patches/commit/04a7cfff20d2734b1c92713de4e7e08a3b93ee85))
+
+# [1.0.0-dev.2](https://github.com/ReVancedTeam/revanced-patches/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2022-03-21)
+
+
+### Code Refactoring
+
+* Rename `net.revanced` to `app.revanced` ([68ea89f](https://github.com/ReVancedTeam/revanced-patches/commit/68ea89f15e9ea077df0d0ac20a40b735bb5ae26c))
+
+
+### BREAKING CHANGES
+
+* Package name was changed from "net.revanced" to "app.revanced"
+
+# 1.0.0-dev.1 (2022-03-21)
+
+
+### Features
+
+* add publishing to package registry ([b475e09](https://github.com/ReVancedTeam/revanced-patches/commit/b475e09577db4dda7bbb45dbf170d78772834a6d))
+* add semantic-release ([d60f1d0](https://github.com/ReVancedTeam/revanced-patches/commit/d60f1d06798d312b158b71691ecc87e828dccbc1))
+* Initial commit ([bee5f2f](https://github.com/ReVancedTeam/revanced-patches/commit/bee5f2faed882271ed059b0435e6e1aa91f93dbd))
+* MinimizedPlayback, CreateButtonRemover ([cc08c6c](https://github.com/ReVancedTeam/revanced-patches/commit/cc08c6c3d38879dd4672ec671631b34aa2e3cc77))
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..95fb049
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,66 @@
+plugins {
+ kotlin("jvm") version "1.7.0"
+}
+
+group = "app.revanced"
+
+val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
+val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+ maven {
+ url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
+ credentials {
+ username = githubUsername
+ password = githubPassword
+ }
+ }
+}
+
+dependencies {
+ implementation("app.revanced:revanced-patcher:6.3.2")
+ implementation("app.revanced:multidexlib2:2.5.2.r2")
+ // Required for meta
+ implementation("com.google.code.gson:gson:2.10")
+}
+
+tasks {
+ register("generateBundle") {
+ description = "Generate dex files from build and bundle them in the jar file"
+ dependsOn(build)
+
+ doLast {
+ val androidHome = System.getenv("ANDROID_HOME") ?: throw GradleException("ANDROID_HOME not found")
+ val d8 = "${androidHome}/build-tools/33.0.1/d8"
+ val input = configurations.archives.get().allArtifacts.files.files.first().absolutePath
+ val work = File("${buildDir}/libs")
+
+ exec {
+ workingDir = work
+ commandLine = listOf(d8, input)
+ }
+
+ exec {
+ workingDir = work
+ commandLine = listOf("zip", "-u", input, "classes.dex")
+ }
+ }
+ }
+ register("generateMeta") {
+ description = "Generate metadata for this bundle"
+ dependsOn(build)
+
+ classpath = sourceSets["main"].runtimeClasspath
+ mainClass.set("app.revanced.meta.Meta")
+ }
+ // Dummy task to fix the Gradle semantic-release plugin.
+ // Remove this if you forked it to support building only.
+ // Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
+ register("publish") {
+ group = "publish"
+ description = "Dummy task"
+ dependsOn(named("generateBundle"), named("generateMeta"))
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..cd9bb66
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,2 @@
+kotlin.code.style = official
+version = 2.146.0
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7454180
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..92f06b5
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..744e882
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MSYS* | MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..074f583
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6107 @@
+{
+ "name": "revanced-patches",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "@semantic-release/changelog": "^6.0.2",
+ "@semantic-release/git": "^10.0.1",
+ "gradle-semantic-release-plugin": "^1.7.4",
+ "semantic-release": "^19.0.5"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+ "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@colors/colors": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
+ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@octokit/auth-token": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.2.tgz",
+ "integrity": "sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/types": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@octokit/core": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.1.0.tgz",
+ "integrity": "sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/auth-token": "^3.0.0",
+ "@octokit/graphql": "^5.0.0",
+ "@octokit/request": "^6.0.0",
+ "@octokit/request-error": "^3.0.0",
+ "@octokit/types": "^8.0.0",
+ "before-after-hook": "^2.2.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@octokit/endpoint": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.3.tgz",
+ "integrity": "sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/types": "^8.0.0",
+ "is-plain-object": "^5.0.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@octokit/graphql": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.4.tgz",
+ "integrity": "sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/request": "^6.0.0",
+ "@octokit/types": "^8.0.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@octokit/openapi-types": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz",
+ "integrity": "sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==",
+ "dev": true
+ },
+ "node_modules/@octokit/plugin-paginate-rest": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz",
+ "integrity": "sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/types": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "@octokit/core": ">=4"
+ }
+ },
+ "node_modules/@octokit/plugin-request-log": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
+ "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==",
+ "dev": true,
+ "peerDependencies": {
+ "@octokit/core": ">=3"
+ }
+ },
+ "node_modules/@octokit/plugin-rest-endpoint-methods": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz",
+ "integrity": "sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/types": "^8.0.0",
+ "deprecation": "^2.3.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "@octokit/core": ">=3"
+ }
+ },
+ "node_modules/@octokit/request": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz",
+ "integrity": "sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/endpoint": "^7.0.0",
+ "@octokit/request-error": "^3.0.0",
+ "@octokit/types": "^8.0.0",
+ "is-plain-object": "^5.0.0",
+ "node-fetch": "^2.6.7",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@octokit/request-error": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.2.tgz",
+ "integrity": "sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/types": "^8.0.0",
+ "deprecation": "^2.0.0",
+ "once": "^1.4.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@octokit/rest": {
+ "version": "19.0.5",
+ "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.5.tgz",
+ "integrity": "sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/core": "^4.1.0",
+ "@octokit/plugin-paginate-rest": "^5.0.0",
+ "@octokit/plugin-request-log": "^1.0.4",
+ "@octokit/plugin-rest-endpoint-methods": "^6.7.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@octokit/types": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz",
+ "integrity": "sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/openapi-types": "^14.0.0"
+ }
+ },
+ "node_modules/@semantic-release/changelog": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.2.tgz",
+ "integrity": "sha512-jHqfTkoPbDEOAgAP18mGP53IxeMwxTISN+GwTRy9uLu58UjARoZU8ScCgWGeO2WPkEsm57H8AkyY02W2ntIlIw==",
+ "dev": true,
+ "dependencies": {
+ "@semantic-release/error": "^3.0.0",
+ "aggregate-error": "^3.0.0",
+ "fs-extra": "^11.0.0",
+ "lodash": "^4.17.4"
+ },
+ "engines": {
+ "node": ">=14.17"
+ },
+ "peerDependencies": {
+ "semantic-release": ">=18.0.0"
+ }
+ },
+ "node_modules/@semantic-release/commit-analyzer": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz",
+ "integrity": "sha512-E+dr6L+xIHZkX4zNMe6Rnwg4YQrWNXK+rNsvwOPpdFppvZO1olE2fIgWhv89TkQErygevbjsZFSIxp+u6w2e5g==",
+ "dev": true,
+ "dependencies": {
+ "conventional-changelog-angular": "^5.0.0",
+ "conventional-commits-filter": "^2.0.0",
+ "conventional-commits-parser": "^3.2.3",
+ "debug": "^4.0.0",
+ "import-from": "^4.0.0",
+ "lodash": "^4.17.4",
+ "micromatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.17"
+ },
+ "peerDependencies": {
+ "semantic-release": ">=18.0.0-beta.1"
+ }
+ },
+ "node_modules/@semantic-release/error": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz",
+ "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/@semantic-release/git": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz",
+ "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==",
+ "dev": true,
+ "dependencies": {
+ "@semantic-release/error": "^3.0.0",
+ "aggregate-error": "^3.0.0",
+ "debug": "^4.0.0",
+ "dir-glob": "^3.0.0",
+ "execa": "^5.0.0",
+ "lodash": "^4.17.4",
+ "micromatch": "^4.0.0",
+ "p-reduce": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.17"
+ },
+ "peerDependencies": {
+ "semantic-release": ">=18.0.0"
+ }
+ },
+ "node_modules/@semantic-release/github": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-8.0.7.tgz",
+ "integrity": "sha512-VtgicRIKGvmTHwm//iqTh/5NGQwsncOMR5vQK9pMT92Aem7dv37JFKKRuulUsAnUOIlO4G8wH3gPiBAA0iW0ww==",
+ "dev": true,
+ "dependencies": {
+ "@octokit/rest": "^19.0.0",
+ "@semantic-release/error": "^3.0.0",
+ "aggregate-error": "^3.0.0",
+ "bottleneck": "^2.18.1",
+ "debug": "^4.0.0",
+ "dir-glob": "^3.0.0",
+ "fs-extra": "^11.0.0",
+ "globby": "^11.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "issue-parser": "^6.0.0",
+ "lodash": "^4.17.4",
+ "mime": "^3.0.0",
+ "p-filter": "^2.0.0",
+ "p-retry": "^4.0.0",
+ "url-join": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14.17"
+ },
+ "peerDependencies": {
+ "semantic-release": ">=18.0.0-beta.1"
+ }
+ },
+ "node_modules/@semantic-release/npm": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-9.0.1.tgz",
+ "integrity": "sha512-I5nVZklxBzfMFwemhRNbSrkiN/dsH3c7K9+KSk6jUnq0rdLFUuJt7EBsysq4Ir3moajQgFkfEryEHPqiKJj20g==",
+ "dev": true,
+ "dependencies": {
+ "@semantic-release/error": "^3.0.0",
+ "aggregate-error": "^3.0.0",
+ "execa": "^5.0.0",
+ "fs-extra": "^10.0.0",
+ "lodash": "^4.17.15",
+ "nerf-dart": "^1.0.0",
+ "normalize-url": "^6.0.0",
+ "npm": "^8.3.0",
+ "rc": "^1.2.8",
+ "read-pkg": "^5.0.0",
+ "registry-auth-token": "^4.0.0",
+ "semver": "^7.1.2",
+ "tempy": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16 || ^14.17"
+ },
+ "peerDependencies": {
+ "semantic-release": ">=19.0.0"
+ }
+ },
+ "node_modules/@semantic-release/npm/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@semantic-release/release-notes-generator": {
+ "version": "10.0.3",
+ "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.3.tgz",
+ "integrity": "sha512-k4x4VhIKneOWoBGHkx0qZogNjCldLPRiAjnIpMnlUh6PtaWXp/T+C9U7/TaNDDtgDa5HMbHl4WlREdxHio6/3w==",
+ "dev": true,
+ "dependencies": {
+ "conventional-changelog-angular": "^5.0.0",
+ "conventional-changelog-writer": "^5.0.0",
+ "conventional-commits-filter": "^2.0.0",
+ "conventional-commits-parser": "^3.2.3",
+ "debug": "^4.0.0",
+ "get-stream": "^6.0.0",
+ "import-from": "^4.0.0",
+ "into-stream": "^6.0.0",
+ "lodash": "^4.17.4",
+ "read-pkg-up": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=14.17"
+ },
+ "peerDependencies": {
+ "semantic-release": ">=18.0.0-beta.1"
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/minimist": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
+ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
+ "dev": true
+ },
+ "node_modules/@types/normalize-package-data": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz",
+ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
+ "dev": true
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
+ "dev": true
+ },
+ "node_modules/@types/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
+ "dev": true
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "dev": true,
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz",
+ "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/ansicolors": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
+ "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==",
+ "dev": true
+ },
+ "node_modules/argv-formatter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz",
+ "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==",
+ "dev": true
+ },
+ "node_modules/array-ify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
+ "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
+ "dev": true
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/before-after-hook": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
+ "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
+ "dev": true
+ },
+ "node_modules/bottleneck": {
+ "version": "2.19.5",
+ "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
+ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-keys": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
+ "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "map-obj": "^4.0.0",
+ "quick-lru": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cardinal": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz",
+ "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==",
+ "dev": true,
+ "dependencies": {
+ "ansicolors": "~0.3.2",
+ "redeyed": "~2.1.0"
+ },
+ "bin": {
+ "cdl": "bin/cdl.js"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz",
+ "integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cli-table3": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz",
+ "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0"
+ },
+ "engines": {
+ "node": "10.* || >= 12.*"
+ },
+ "optionalDependencies": {
+ "@colors/colors": "1.5.0"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/compare-func": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
+ "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
+ "dev": true,
+ "dependencies": {
+ "array-ify": "^1.0.0",
+ "dot-prop": "^5.1.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/conventional-changelog-angular": {
+ "version": "5.0.13",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz",
+ "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==",
+ "dev": true,
+ "dependencies": {
+ "compare-func": "^2.0.0",
+ "q": "^1.5.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/conventional-changelog-writer": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz",
+ "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==",
+ "dev": true,
+ "dependencies": {
+ "conventional-commits-filter": "^2.0.7",
+ "dateformat": "^3.0.0",
+ "handlebars": "^4.7.7",
+ "json-stringify-safe": "^5.0.1",
+ "lodash": "^4.17.15",
+ "meow": "^8.0.0",
+ "semver": "^6.0.0",
+ "split": "^1.0.0",
+ "through2": "^4.0.0"
+ },
+ "bin": {
+ "conventional-changelog-writer": "cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/conventional-changelog-writer/node_modules/semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/conventional-commits-filter": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz",
+ "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==",
+ "dev": true,
+ "dependencies": {
+ "lodash.ismatch": "^4.4.0",
+ "modify-values": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/conventional-commits-parser": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz",
+ "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==",
+ "dev": true,
+ "dependencies": {
+ "is-text-path": "^1.0.1",
+ "JSONStream": "^1.0.4",
+ "lodash": "^4.17.15",
+ "meow": "^8.0.0",
+ "split2": "^3.0.0",
+ "through2": "^4.0.0"
+ },
+ "bin": {
+ "conventional-commits-parser": "cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/conventional-commits-parser/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/conventional-commits-parser/node_modules/split2": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
+ "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "^3.0.0"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "dev": true
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "dev": true,
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/crypto-random-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
+ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dateformat": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
+ "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decamelize-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
+ "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
+ "dev": true,
+ "dependencies": {
+ "decamelize": "^1.1.0",
+ "map-obj": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decamelize-keys/node_modules/map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/del": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz",
+ "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==",
+ "dev": true,
+ "dependencies": {
+ "globby": "^11.0.1",
+ "graceful-fs": "^4.2.4",
+ "is-glob": "^4.0.1",
+ "is-path-cwd": "^2.2.0",
+ "is-path-inside": "^3.0.2",
+ "p-map": "^4.0.0",
+ "rimraf": "^3.0.2",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/del/node_modules/p-map": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+ "dev": true,
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deprecation": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
+ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
+ "dev": true
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dot-prop": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
+ "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
+ "dev": true,
+ "dependencies": {
+ "is-obj": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/duplexer2": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/env-ci": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.5.0.tgz",
+ "integrity": "sha512-o0JdWIbOLP+WJKIUt36hz1ImQQFuN92nhsfTkHHap+J8CiI8WgGpH/a9jEGHh4/TU5BUUGjlnKXNoDb57+ne+A==",
+ "dev": true,
+ "dependencies": {
+ "execa": "^5.0.0",
+ "fromentries": "^1.3.2",
+ "java-properties": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.17"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
+ "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/figures": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-versions": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz",
+ "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==",
+ "dev": true,
+ "dependencies": {
+ "semver-regex": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ }
+ },
+ "node_modules/fromentries": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
+ "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/fs-extra": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
+ "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/git-log-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz",
+ "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==",
+ "dev": true,
+ "dependencies": {
+ "argv-formatter": "~1.0.0",
+ "spawn-error-forwarder": "~1.0.0",
+ "split2": "~1.0.0",
+ "stream-combiner2": "~1.1.1",
+ "through2": "~2.0.0",
+ "traverse": "~0.6.6"
+ }
+ },
+ "node_modules/git-log-parser/node_modules/split2": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz",
+ "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==",
+ "dev": true,
+ "dependencies": {
+ "through2": "~2.0.0"
+ }
+ },
+ "node_modules/git-log-parser/node_modules/through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.10",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
+ "dev": true
+ },
+ "node_modules/gradle-semantic-release-plugin": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.7.4.tgz",
+ "integrity": "sha512-Mm2JnmodHxQlCmn0GtSi5j8z4PS2+2VBY993b9GuNGGM+JaIrr8T3tF9uTLR2nGH/oJba6a9jx1ZqVEJdOXDPw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/KengoTODA"
+ }
+ ],
+ "dependencies": {
+ "promisified-properties": "^2.0.3",
+ "split2": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=16 || ^14.17"
+ },
+ "peerDependencies": {
+ "semantic-release": "^19.0.2"
+ }
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.7",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
+ "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.0",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/hard-rejection": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+ "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hook-std": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",
+ "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dev": true,
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
+ "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/import-fresh/node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/import-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz",
+ "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
+ "node_modules/into-stream": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz",
+ "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==",
+ "dev": true,
+ "dependencies": {
+ "from2": "^2.3.0",
+ "p-is-promise": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-path-cwd": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
+ "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-text-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz",
+ "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==",
+ "dev": true,
+ "dependencies": {
+ "text-extensions": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/issue-parser": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz",
+ "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==",
+ "dev": true,
+ "dependencies": {
+ "lodash.capitalize": "^4.2.1",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.uniqby": "^4.7.0"
+ },
+ "engines": {
+ "node": ">=10.13"
+ }
+ },
+ "node_modules/java-properties": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz",
+ "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "dev": true
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsonparse": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+ "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
+ "dev": true,
+ "engines": [
+ "node >= 0.2.0"
+ ]
+ },
+ "node_modules/JSONStream": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+ "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+ "dev": true,
+ "dependencies": {
+ "jsonparse": "^1.2.0",
+ "through": ">=2.2.7 <3"
+ },
+ "bin": {
+ "JSONStream": "bin.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/load-json-file": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+ "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^4.0.0",
+ "pify": "^3.0.0",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/load-json-file/node_modules/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
+ "dev": true,
+ "dependencies": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/lodash.capitalize": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz",
+ "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==",
+ "dev": true
+ },
+ "node_modules/lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
+ "dev": true
+ },
+ "node_modules/lodash.ismatch": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
+ "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==",
+ "dev": true
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "dev": true
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "dev": true
+ },
+ "node_modules/lodash.uniqby": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz",
+ "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/map-obj": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/marked": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz",
+ "integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==",
+ "dev": true,
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/marked-terminal": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz",
+ "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==",
+ "dev": true,
+ "dependencies": {
+ "ansi-escapes": "^5.0.0",
+ "cardinal": "^2.1.1",
+ "chalk": "^5.0.0",
+ "cli-table3": "^0.6.1",
+ "node-emoji": "^1.11.0",
+ "supports-hyperlinks": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14.13.1 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0"
+ }
+ },
+ "node_modules/meow": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
+ "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/minimist": "^1.2.0",
+ "camelcase-keys": "^6.2.2",
+ "decamelize-keys": "^1.1.0",
+ "hard-rejection": "^2.1.0",
+ "minimist-options": "4.1.0",
+ "normalize-package-data": "^3.0.0",
+ "read-pkg-up": "^7.0.1",
+ "redent": "^3.0.0",
+ "trim-newlines": "^3.0.0",
+ "type-fest": "^0.18.0",
+ "yargs-parser": "^20.2.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow/node_modules/type-fest": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
+ "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "dev": true,
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
+ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minimist-options": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+ "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+ "dev": true,
+ "dependencies": {
+ "arrify": "^1.0.1",
+ "is-plain-obj": "^1.1.0",
+ "kind-of": "^6.0.3"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/modify-values": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz",
+ "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true
+ },
+ "node_modules/nerf-dart": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz",
+ "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==",
+ "dev": true
+ },
+ "node_modules/node-emoji": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
+ "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.21"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/normalize-package-data": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
+ "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^4.0.1",
+ "is-core-module": "^2.5.0",
+ "semver": "^7.3.4",
+ "validate-npm-package-license": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm": {
+ "version": "8.19.3",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-8.19.3.tgz",
+ "integrity": "sha512-0QjmyPtDxSyMWWD8I91QGbrgx9KzbV6C9FK1liEb/K0zppiZkr5KxXc990G+LzPwBHDfRjUBlO9T1qZ08vl9mA==",
+ "bundleDependencies": [
+ "@isaacs/string-locale-compare",
+ "@npmcli/arborist",
+ "@npmcli/ci-detect",
+ "@npmcli/config",
+ "@npmcli/fs",
+ "@npmcli/map-workspaces",
+ "@npmcli/package-json",
+ "@npmcli/run-script",
+ "abbrev",
+ "archy",
+ "cacache",
+ "chalk",
+ "chownr",
+ "cli-columns",
+ "cli-table3",
+ "columnify",
+ "fastest-levenshtein",
+ "fs-minipass",
+ "glob",
+ "graceful-fs",
+ "hosted-git-info",
+ "ini",
+ "init-package-json",
+ "is-cidr",
+ "json-parse-even-better-errors",
+ "libnpmaccess",
+ "libnpmdiff",
+ "libnpmexec",
+ "libnpmfund",
+ "libnpmhook",
+ "libnpmorg",
+ "libnpmpack",
+ "libnpmpublish",
+ "libnpmsearch",
+ "libnpmteam",
+ "libnpmversion",
+ "make-fetch-happen",
+ "minimatch",
+ "minipass",
+ "minipass-pipeline",
+ "mkdirp",
+ "mkdirp-infer-owner",
+ "ms",
+ "node-gyp",
+ "nopt",
+ "npm-audit-report",
+ "npm-install-checks",
+ "npm-package-arg",
+ "npm-pick-manifest",
+ "npm-profile",
+ "npm-registry-fetch",
+ "npm-user-validate",
+ "npmlog",
+ "opener",
+ "p-map",
+ "pacote",
+ "parse-conflict-json",
+ "proc-log",
+ "qrcode-terminal",
+ "read",
+ "read-package-json",
+ "read-package-json-fast",
+ "readdir-scoped-modules",
+ "rimraf",
+ "semver",
+ "ssri",
+ "tar",
+ "text-table",
+ "tiny-relative-date",
+ "treeverse",
+ "validate-npm-package-name",
+ "which",
+ "write-file-atomic"
+ ],
+ "dev": true,
+ "dependencies": {
+ "@isaacs/string-locale-compare": "^1.1.0",
+ "@npmcli/arborist": "^5.6.3",
+ "@npmcli/ci-detect": "^2.0.0",
+ "@npmcli/config": "^4.2.1",
+ "@npmcli/fs": "^2.1.0",
+ "@npmcli/map-workspaces": "^2.0.3",
+ "@npmcli/package-json": "^2.0.0",
+ "@npmcli/run-script": "^4.2.1",
+ "abbrev": "~1.1.1",
+ "archy": "~1.0.0",
+ "cacache": "^16.1.3",
+ "chalk": "^4.1.2",
+ "chownr": "^2.0.0",
+ "cli-columns": "^4.0.0",
+ "cli-table3": "^0.6.2",
+ "columnify": "^1.6.0",
+ "fastest-levenshtein": "^1.0.12",
+ "fs-minipass": "^2.1.0",
+ "glob": "^8.0.1",
+ "graceful-fs": "^4.2.10",
+ "hosted-git-info": "^5.2.1",
+ "ini": "^3.0.1",
+ "init-package-json": "^3.0.2",
+ "is-cidr": "^4.0.2",
+ "json-parse-even-better-errors": "^2.3.1",
+ "libnpmaccess": "^6.0.4",
+ "libnpmdiff": "^4.0.5",
+ "libnpmexec": "^4.0.14",
+ "libnpmfund": "^3.0.5",
+ "libnpmhook": "^8.0.4",
+ "libnpmorg": "^4.0.4",
+ "libnpmpack": "^4.1.3",
+ "libnpmpublish": "^6.0.5",
+ "libnpmsearch": "^5.0.4",
+ "libnpmteam": "^4.0.4",
+ "libnpmversion": "^3.0.7",
+ "make-fetch-happen": "^10.2.0",
+ "minimatch": "^5.1.0",
+ "minipass": "^3.1.6",
+ "minipass-pipeline": "^1.2.4",
+ "mkdirp": "^1.0.4",
+ "mkdirp-infer-owner": "^2.0.0",
+ "ms": "^2.1.2",
+ "node-gyp": "^9.1.0",
+ "nopt": "^6.0.0",
+ "npm-audit-report": "^3.0.0",
+ "npm-install-checks": "^5.0.0",
+ "npm-package-arg": "^9.1.0",
+ "npm-pick-manifest": "^7.0.2",
+ "npm-profile": "^6.2.0",
+ "npm-registry-fetch": "^13.3.1",
+ "npm-user-validate": "^1.0.1",
+ "npmlog": "^6.0.2",
+ "opener": "^1.5.2",
+ "p-map": "^4.0.0",
+ "pacote": "^13.6.2",
+ "parse-conflict-json": "^2.0.2",
+ "proc-log": "^2.0.1",
+ "qrcode-terminal": "^0.12.0",
+ "read": "~1.0.7",
+ "read-package-json": "^5.0.2",
+ "read-package-json-fast": "^2.0.3",
+ "readdir-scoped-modules": "^1.1.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.7",
+ "ssri": "^9.0.1",
+ "tar": "^6.1.11",
+ "text-table": "~0.2.0",
+ "tiny-relative-date": "^1.3.0",
+ "treeverse": "^2.0.0",
+ "validate-npm-package-name": "^4.0.0",
+ "which": "^2.0.2",
+ "write-file-atomic": "^4.0.1"
+ },
+ "bin": {
+ "npm": "bin/npm-cli.js",
+ "npx": "bin/npx-cli.js"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/@colors/colors": {
+ "version": "1.5.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/npm/node_modules/@gar/promisify": {
+ "version": "1.1.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/@isaacs/string-locale-compare": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/@npmcli/arborist": {
+ "version": "5.6.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@isaacs/string-locale-compare": "^1.1.0",
+ "@npmcli/installed-package-contents": "^1.0.7",
+ "@npmcli/map-workspaces": "^2.0.3",
+ "@npmcli/metavuln-calculator": "^3.0.1",
+ "@npmcli/move-file": "^2.0.0",
+ "@npmcli/name-from-folder": "^1.0.1",
+ "@npmcli/node-gyp": "^2.0.0",
+ "@npmcli/package-json": "^2.0.0",
+ "@npmcli/query": "^1.2.0",
+ "@npmcli/run-script": "^4.1.3",
+ "bin-links": "^3.0.3",
+ "cacache": "^16.1.3",
+ "common-ancestor-path": "^1.0.1",
+ "hosted-git-info": "^5.2.1",
+ "json-parse-even-better-errors": "^2.3.1",
+ "json-stringify-nice": "^1.1.4",
+ "minimatch": "^5.1.0",
+ "mkdirp": "^1.0.4",
+ "mkdirp-infer-owner": "^2.0.0",
+ "nopt": "^6.0.0",
+ "npm-install-checks": "^5.0.0",
+ "npm-package-arg": "^9.0.0",
+ "npm-pick-manifest": "^7.0.2",
+ "npm-registry-fetch": "^13.0.0",
+ "npmlog": "^6.0.2",
+ "pacote": "^13.6.1",
+ "parse-conflict-json": "^2.0.1",
+ "proc-log": "^2.0.0",
+ "promise-all-reject-late": "^1.0.0",
+ "promise-call-limit": "^1.0.1",
+ "read-package-json-fast": "^2.0.2",
+ "readdir-scoped-modules": "^1.1.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.7",
+ "ssri": "^9.0.0",
+ "treeverse": "^2.0.0",
+ "walk-up-path": "^1.0.0"
+ },
+ "bin": {
+ "arborist": "bin/index.js"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/ci-detect": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/config": {
+ "version": "4.2.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/map-workspaces": "^2.0.2",
+ "ini": "^3.0.0",
+ "mkdirp-infer-owner": "^2.0.0",
+ "nopt": "^6.0.0",
+ "proc-log": "^2.0.0",
+ "read-package-json-fast": "^2.0.3",
+ "semver": "^7.3.5",
+ "walk-up-path": "^1.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/disparity-colors": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "ansi-styles": "^4.3.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/fs": {
+ "version": "2.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@gar/promisify": "^1.1.3",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/git": {
+ "version": "3.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/promise-spawn": "^3.0.0",
+ "lru-cache": "^7.4.4",
+ "mkdirp": "^1.0.4",
+ "npm-pick-manifest": "^7.0.0",
+ "proc-log": "^2.0.0",
+ "promise-inflight": "^1.0.1",
+ "promise-retry": "^2.0.1",
+ "semver": "^7.3.5",
+ "which": "^2.0.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/installed-package-contents": {
+ "version": "1.0.7",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-bundled": "^1.1.1",
+ "npm-normalize-package-bin": "^1.0.1"
+ },
+ "bin": {
+ "installed-package-contents": "index.js"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": {
+ "version": "1.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-normalize-package-bin": "^1.0.1"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/map-workspaces": {
+ "version": "2.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/name-from-folder": "^1.0.1",
+ "glob": "^8.0.1",
+ "minimatch": "^5.0.1",
+ "read-package-json-fast": "^2.0.3"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/metavuln-calculator": {
+ "version": "3.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "cacache": "^16.0.0",
+ "json-parse-even-better-errors": "^2.3.1",
+ "pacote": "^13.0.3",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/move-file": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "mkdirp": "^1.0.4",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/name-from-folder": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/@npmcli/node-gyp": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/package-json": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "json-parse-even-better-errors": "^2.3.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/promise-spawn": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "infer-owner": "^1.0.4"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/query": {
+ "version": "1.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-package-arg": "^9.1.0",
+ "postcss-selector-parser": "^6.0.10",
+ "semver": "^7.3.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@npmcli/run-script": {
+ "version": "4.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/node-gyp": "^2.0.0",
+ "@npmcli/promise-spawn": "^3.0.0",
+ "node-gyp": "^9.0.0",
+ "read-package-json-fast": "^2.0.3",
+ "which": "^2.0.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/npm/node_modules/abbrev": {
+ "version": "1.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/agent-base": {
+ "version": "6.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/agentkeepalive": {
+ "version": "4.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.0",
+ "depd": "^1.1.2",
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/npm/node_modules/aproba": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/archy": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/are-we-there-yet": {
+ "version": "3.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/asap": {
+ "version": "2.0.6",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/bin-links": {
+ "version": "3.0.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "cmd-shim": "^5.0.0",
+ "mkdirp-infer-owner": "^2.0.0",
+ "npm-normalize-package-bin": "^2.0.0",
+ "read-cmd-shim": "^3.0.0",
+ "rimraf": "^3.0.0",
+ "write-file-atomic": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/bin-links/node_modules/npm-normalize-package-bin": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/builtins": {
+ "version": "5.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/cacache": {
+ "version": "16.1.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/fs": "^2.1.0",
+ "@npmcli/move-file": "^2.0.0",
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.1.0",
+ "glob": "^8.0.1",
+ "infer-owner": "^1.0.4",
+ "lru-cache": "^7.7.1",
+ "minipass": "^3.1.6",
+ "minipass-collect": "^1.0.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "mkdirp": "^1.0.4",
+ "p-map": "^4.0.0",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^3.0.2",
+ "ssri": "^9.0.0",
+ "tar": "^6.1.11",
+ "unique-filename": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/chalk": {
+ "version": "4.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/npm/node_modules/chownr": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/cidr-regex": {
+ "version": "3.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "ip-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/clean-stack": {
+ "version": "2.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/npm/node_modules/cli-columns": {
+ "version": "4.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/npm/node_modules/cli-table3": {
+ "version": "0.6.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^4.2.0"
+ },
+ "engines": {
+ "node": "10.* || >= 12.*"
+ },
+ "optionalDependencies": {
+ "@colors/colors": "1.5.0"
+ }
+ },
+ "node_modules/npm/node_modules/clone": {
+ "version": "1.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/npm/node_modules/cmd-shim": {
+ "version": "5.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "mkdirp-infer-owner": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/color-support": {
+ "version": "1.1.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/npm/node_modules/columnify": {
+ "version": "1.6.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "strip-ansi": "^6.0.1",
+ "wcwidth": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/common-ancestor-path": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/concat-map": {
+ "version": "0.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/cssesc": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npm/node_modules/debug": {
+ "version": "4.3.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/npm/node_modules/debug/node_modules/ms": {
+ "version": "2.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/debuglog": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/npm/node_modules/defaults": {
+ "version": "1.0.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone": "^1.0.2"
+ }
+ },
+ "node_modules/npm/node_modules/delegates": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/depd": {
+ "version": "1.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/npm/node_modules/dezalgo": {
+ "version": "1.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/npm/node_modules/diff": {
+ "version": "5.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/npm/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/encoding": {
+ "version": "0.1.13",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
+ "node_modules/npm/node_modules/env-paths": {
+ "version": "2.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/npm/node_modules/err-code": {
+ "version": "2.0.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/fastest-levenshtein": {
+ "version": "1.0.12",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/fs-minipass": {
+ "version": "2.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/npm/node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/function-bind": {
+ "version": "1.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/gauge": {
+ "version": "4.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^3.0.7",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/glob": {
+ "version": "8.0.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/graceful-fs": {
+ "version": "4.2.10",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/has": {
+ "version": "1.0.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/npm/node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/has-unicode": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/hosted-git-info": {
+ "version": "5.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^7.5.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/http-cache-semantics": {
+ "version": "4.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/npm/node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/npm/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/npm/node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm/node_modules/ignore-walk": {
+ "version": "5.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/npm/node_modules/indent-string": {
+ "version": "4.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/infer-owner": {
+ "version": "1.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/inflight": {
+ "version": "1.0.6",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/npm/node_modules/inherits": {
+ "version": "2.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/ini": {
+ "version": "3.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/init-package-json": {
+ "version": "3.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-package-arg": "^9.0.1",
+ "promzard": "^0.3.0",
+ "read": "^1.0.7",
+ "read-package-json": "^5.0.0",
+ "semver": "^7.3.5",
+ "validate-npm-package-license": "^3.0.4",
+ "validate-npm-package-name": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/ip": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/ip-regex": {
+ "version": "4.3.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/is-cidr": {
+ "version": "4.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "cidr-regex": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/is-core-module": {
+ "version": "2.10.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/npm/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/is-lambda": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/isexe": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/json-stringify-nice": {
+ "version": "1.1.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/jsonparse": {
+ "version": "1.3.1",
+ "dev": true,
+ "engines": [
+ "node >= 0.2.0"
+ ],
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/just-diff": {
+ "version": "5.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/just-diff-apply": {
+ "version": "5.4.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/libnpmaccess": {
+ "version": "6.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^2.0.0",
+ "minipass": "^3.1.1",
+ "npm-package-arg": "^9.0.1",
+ "npm-registry-fetch": "^13.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmdiff": {
+ "version": "4.0.5",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/disparity-colors": "^2.0.0",
+ "@npmcli/installed-package-contents": "^1.0.7",
+ "binary-extensions": "^2.2.0",
+ "diff": "^5.1.0",
+ "minimatch": "^5.0.1",
+ "npm-package-arg": "^9.0.1",
+ "pacote": "^13.6.1",
+ "tar": "^6.1.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmexec": {
+ "version": "4.0.14",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/arborist": "^5.6.3",
+ "@npmcli/ci-detect": "^2.0.0",
+ "@npmcli/fs": "^2.1.1",
+ "@npmcli/run-script": "^4.2.0",
+ "chalk": "^4.1.0",
+ "mkdirp-infer-owner": "^2.0.0",
+ "npm-package-arg": "^9.0.1",
+ "npmlog": "^6.0.2",
+ "pacote": "^13.6.1",
+ "proc-log": "^2.0.0",
+ "read": "^1.0.7",
+ "read-package-json-fast": "^2.0.2",
+ "semver": "^7.3.7",
+ "walk-up-path": "^1.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmfund": {
+ "version": "3.0.5",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/arborist": "^5.6.3"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmhook": {
+ "version": "8.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^2.0.0",
+ "npm-registry-fetch": "^13.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmorg": {
+ "version": "4.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^2.0.0",
+ "npm-registry-fetch": "^13.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmpack": {
+ "version": "4.1.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/run-script": "^4.1.3",
+ "npm-package-arg": "^9.0.1",
+ "pacote": "^13.6.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmpublish": {
+ "version": "6.0.5",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-package-data": "^4.0.0",
+ "npm-package-arg": "^9.0.1",
+ "npm-registry-fetch": "^13.0.0",
+ "semver": "^7.3.7",
+ "ssri": "^9.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmsearch": {
+ "version": "5.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-registry-fetch": "^13.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmteam": {
+ "version": "4.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^2.0.0",
+ "npm-registry-fetch": "^13.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/libnpmversion": {
+ "version": "3.0.7",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/git": "^3.0.0",
+ "@npmcli/run-script": "^4.1.3",
+ "json-parse-even-better-errors": "^2.3.1",
+ "proc-log": "^2.0.0",
+ "semver": "^7.3.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/lru-cache": {
+ "version": "7.13.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/npm/node_modules/make-fetch-happen": {
+ "version": "10.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "agentkeepalive": "^4.2.1",
+ "cacache": "^16.1.0",
+ "http-cache-semantics": "^4.1.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "is-lambda": "^1.0.1",
+ "lru-cache": "^7.7.1",
+ "minipass": "^3.1.6",
+ "minipass-collect": "^1.0.2",
+ "minipass-fetch": "^2.0.3",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^0.6.3",
+ "promise-retry": "^2.0.1",
+ "socks-proxy-agent": "^7.0.0",
+ "ssri": "^9.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/minimatch": {
+ "version": "5.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/minipass": {
+ "version": "3.3.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-collect": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-fetch": {
+ "version": "2.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^3.1.6",
+ "minipass-sized": "^1.0.3",
+ "minizlib": "^2.1.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "encoding": "^0.1.13"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-flush": {
+ "version": "1.0.5",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-json-stream": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "jsonparse": "^1.3.1",
+ "minipass": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-pipeline": {
+ "version": "1.2.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/minipass-sized": {
+ "version": "1.0.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/minizlib": {
+ "version": "2.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/npm/node_modules/mkdirp": {
+ "version": "1.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/mkdirp-infer-owner": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "infer-owner": "^1.0.4",
+ "mkdirp": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/ms": {
+ "version": "2.1.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/mute-stream": {
+ "version": "0.0.8",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/negotiator": {
+ "version": "0.6.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/npm/node_modules/node-gyp": {
+ "version": "9.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.0",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.6",
+ "make-fetch-happen": "^10.0.3",
+ "nopt": "^5.0.0",
+ "npmlog": "^6.0.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.2",
+ "which": "^2.0.2"
+ },
+ "bin": {
+ "node-gyp": "bin/node-gyp.js"
+ },
+ "engines": {
+ "node": "^12.22 || ^14.13 || >=16"
+ }
+ },
+ "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/npm/node_modules/node-gyp/node_modules/glob": {
+ "version": "7.2.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/npm/node_modules/node-gyp/node_modules/nopt": {
+ "version": "5.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/npm/node_modules/nopt": {
+ "version": "6.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "^1.0.0"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/normalize-package-data": {
+ "version": "4.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^5.0.0",
+ "is-core-module": "^2.8.1",
+ "semver": "^7.3.5",
+ "validate-npm-package-license": "^3.0.4"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-audit-report": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-bundled": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-normalize-package-bin": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-bundled/node_modules/npm-normalize-package-bin": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-install-checks": {
+ "version": "5.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "semver": "^7.1.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-normalize-package-bin": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/npm-package-arg": {
+ "version": "9.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "hosted-git-info": "^5.0.0",
+ "proc-log": "^2.0.1",
+ "semver": "^7.3.5",
+ "validate-npm-package-name": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-packlist": {
+ "version": "5.1.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^8.0.1",
+ "ignore-walk": "^5.0.1",
+ "npm-bundled": "^2.0.0",
+ "npm-normalize-package-bin": "^2.0.0"
+ },
+ "bin": {
+ "npm-packlist": "bin/index.js"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-packlist/node_modules/npm-normalize-package-bin": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-pick-manifest": {
+ "version": "7.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-install-checks": "^5.0.0",
+ "npm-normalize-package-bin": "^2.0.0",
+ "npm-package-arg": "^9.0.0",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-profile": {
+ "version": "6.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "npm-registry-fetch": "^13.0.1",
+ "proc-log": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-registry-fetch": {
+ "version": "13.3.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "make-fetch-happen": "^10.0.6",
+ "minipass": "^3.1.6",
+ "minipass-fetch": "^2.0.3",
+ "minipass-json-stream": "^1.0.1",
+ "minizlib": "^2.1.2",
+ "npm-package-arg": "^9.0.1",
+ "proc-log": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/npm-user-validate": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/npm/node_modules/npmlog": {
+ "version": "6.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "are-we-there-yet": "^3.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^4.0.3",
+ "set-blocking": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/once": {
+ "version": "1.4.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/npm/node_modules/opener": {
+ "version": "1.5.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "(WTFPL OR MIT)",
+ "bin": {
+ "opener": "bin/opener-bin.js"
+ }
+ },
+ "node_modules/npm/node_modules/p-map": {
+ "version": "4.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm/node_modules/pacote": {
+ "version": "13.6.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/git": "^3.0.0",
+ "@npmcli/installed-package-contents": "^1.0.7",
+ "@npmcli/promise-spawn": "^3.0.0",
+ "@npmcli/run-script": "^4.1.0",
+ "cacache": "^16.0.0",
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.1.0",
+ "infer-owner": "^1.0.4",
+ "minipass": "^3.1.6",
+ "mkdirp": "^1.0.4",
+ "npm-package-arg": "^9.0.0",
+ "npm-packlist": "^5.1.0",
+ "npm-pick-manifest": "^7.0.0",
+ "npm-registry-fetch": "^13.0.1",
+ "proc-log": "^2.0.0",
+ "promise-retry": "^2.0.1",
+ "read-package-json": "^5.0.0",
+ "read-package-json-fast": "^2.0.3",
+ "rimraf": "^3.0.2",
+ "ssri": "^9.0.0",
+ "tar": "^6.1.11"
+ },
+ "bin": {
+ "pacote": "lib/bin.js"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/parse-conflict-json": {
+ "version": "2.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "json-parse-even-better-errors": "^2.3.1",
+ "just-diff": "^5.0.1",
+ "just-diff-apply": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm/node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npm/node_modules/proc-log": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/promise-all-reject-late": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/promise-call-limit": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/promise-inflight": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/promise-retry": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/promzard": {
+ "version": "0.3.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "read": "1"
+ }
+ },
+ "node_modules/npm/node_modules/qrcode-terminal": {
+ "version": "0.12.0",
+ "dev": true,
+ "inBundle": true,
+ "bin": {
+ "qrcode-terminal": "bin/qrcode-terminal.js"
+ }
+ },
+ "node_modules/npm/node_modules/read": {
+ "version": "1.0.7",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "mute-stream": "~0.0.4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/npm/node_modules/read-cmd-shim": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/read-package-json": {
+ "version": "5.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^8.0.1",
+ "json-parse-even-better-errors": "^2.3.1",
+ "normalize-package-data": "^4.0.0",
+ "npm-normalize-package-bin": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/read-package-json-fast": {
+ "version": "2.0.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "json-parse-even-better-errors": "^2.3.0",
+ "npm-normalize-package-bin": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/read-package-json/node_modules/npm-normalize-package-bin": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/npm/node_modules/readdir-scoped-modules": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "debuglog": "^1.0.1",
+ "dezalgo": "^1.0.0",
+ "graceful-fs": "^4.1.2",
+ "once": "^1.3.0"
+ }
+ },
+ "node_modules/npm/node_modules/retry": {
+ "version": "0.12.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/npm/node_modules/rimraf": {
+ "version": "3.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/npm/node_modules/rimraf/node_modules/glob": {
+ "version": "7.2.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/npm/node_modules/rimraf/node_modules/minimatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/npm/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/npm/node_modules/semver": {
+ "version": "7.3.7",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/semver/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/npm/node_modules/set-blocking": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/socks": {
+ "version": "2.7.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ip": "^2.0.0",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/socks-proxy-agent": {
+ "version": "7.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^6.0.2",
+ "debug": "^4.3.3",
+ "socks": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/npm/node_modules/spdx-correct": {
+ "version": "3.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/spdx-exceptions": {
+ "version": "2.3.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "CC-BY-3.0"
+ },
+ "node_modules/npm/node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/spdx-license-ids": {
+ "version": "3.0.11",
+ "dev": true,
+ "inBundle": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/npm/node_modules/ssri": {
+ "version": "9.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/string_decoder": {
+ "version": "1.3.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/npm/node_modules/string-width": {
+ "version": "4.2.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/supports-color": {
+ "version": "7.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npm/node_modules/tar": {
+ "version": "6.1.11",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^3.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/npm/node_modules/text-table": {
+ "version": "0.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/tiny-relative-date": {
+ "version": "1.3.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/treeverse": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/unique-filename": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "unique-slug": "^3.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/unique-slug": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npm/node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/validate-npm-package-name": {
+ "version": "4.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "builtins": "^5.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/walk-up-path": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/wcwidth": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "node_modules/npm/node_modules/which": {
+ "version": "2.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/npm/node_modules/wide-align": {
+ "version": "1.1.5",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/npm/node_modules/wrappy": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npm/node_modules/write-file-atomic": {
+ "version": "4.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/npm/node_modules/yallist": {
+ "version": "4.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-each-series": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz",
+ "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-filter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz",
+ "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==",
+ "dev": true,
+ "dependencies": {
+ "p-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-is-promise": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz",
+ "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/p-reduce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz",
+ "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-retry": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
+ "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/retry": "0.12.0",
+ "retry": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parsimmon": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz",
+ "integrity": "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw==",
+ "dev": true
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-conf": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz",
+ "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^2.0.0",
+ "load-json-file": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-conf/node_modules/find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-conf/node_modules/locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-conf/node_modules/p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-conf/node_modules/p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-conf/node_modules/p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-conf/node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true
+ },
+ "node_modules/promisified-properties": {
+ "version": "2.0.25",
+ "resolved": "https://registry.npmjs.org/promisified-properties/-/promisified-properties-2.0.25.tgz",
+ "integrity": "sha512-FtyMDSNf3IwVBjVq4uj5JD4lG+YmOMzt/Huuc+VwPHVqbzim3/U04iMBnVH3lYgOc9s5Jj9sC4G0OVGoo2KbWA==",
+ "dev": true,
+ "dependencies": {
+ "parsimmon": "^1.13.0"
+ },
+ "engines": {
+ "node": ">=14.16",
+ "npm": ">=7.12"
+ }
+ },
+ "node_modules/q": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+ "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6.0",
+ "teleport": ">=0.2.0"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/quick-lru": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/read-pkg": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+ "dev": true,
+ "dependencies": {
+ "@types/normalize-package-data": "^2.4.0",
+ "normalize-package-data": "^2.5.0",
+ "parse-json": "^5.0.0",
+ "type-fest": "^0.6.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.1.0",
+ "read-pkg": "^5.2.0",
+ "type-fest": "^0.8.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg/node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true
+ },
+ "node_modules/read-pkg/node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/read-pkg/node_modules/semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/read-pkg/node_modules/type-fest": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+ "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/redeyed": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz",
+ "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==",
+ "dev": true,
+ "dependencies": {
+ "esprima": "~4.0.0"
+ }
+ },
+ "node_modules/registry-auth-token": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz",
+ "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==",
+ "dev": true,
+ "dependencies": {
+ "rc": "1.2.8"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/semantic-release": {
+ "version": "19.0.5",
+ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz",
+ "integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==",
+ "dev": true,
+ "dependencies": {
+ "@semantic-release/commit-analyzer": "^9.0.2",
+ "@semantic-release/error": "^3.0.0",
+ "@semantic-release/github": "^8.0.0",
+ "@semantic-release/npm": "^9.0.0",
+ "@semantic-release/release-notes-generator": "^10.0.0",
+ "aggregate-error": "^3.0.0",
+ "cosmiconfig": "^7.0.0",
+ "debug": "^4.0.0",
+ "env-ci": "^5.0.0",
+ "execa": "^5.0.0",
+ "figures": "^3.0.0",
+ "find-versions": "^4.0.0",
+ "get-stream": "^6.0.0",
+ "git-log-parser": "^1.2.0",
+ "hook-std": "^2.0.0",
+ "hosted-git-info": "^4.0.0",
+ "lodash": "^4.17.21",
+ "marked": "^4.0.10",
+ "marked-terminal": "^5.0.0",
+ "micromatch": "^4.0.2",
+ "p-each-series": "^2.1.0",
+ "p-reduce": "^2.0.0",
+ "read-pkg-up": "^7.0.0",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.3.2",
+ "semver-diff": "^3.1.1",
+ "signale": "^1.2.1",
+ "yargs": "^16.2.0"
+ },
+ "bin": {
+ "semantic-release": "bin/semantic-release.js"
+ },
+ "engines": {
+ "node": ">=16 || ^14.17"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver-diff": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
+ "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/semver-diff/node_modules/semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/semver-regex": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz",
+ "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/signale": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz",
+ "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^2.3.2",
+ "figures": "^2.0.0",
+ "pkg-conf": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/signale/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/signale/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/signale/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/signale/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/signale/node_modules/figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/signale/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/signale/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/spawn-error-forwarder": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz",
+ "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==",
+ "dev": true
+ },
+ "node_modules/spdx-correct": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+ "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
+ "dev": true,
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+ "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+ "dev": true
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.12",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz",
+ "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==",
+ "dev": true
+ },
+ "node_modules/split": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
+ "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
+ "dev": true,
+ "dependencies": {
+ "through": "2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+ "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/stream-combiner2": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
+ "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==",
+ "dev": true,
+ "dependencies": {
+ "duplexer2": "~0.1.0",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-hyperlinks": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
+ "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/temp-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
+ "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tempy": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz",
+ "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==",
+ "dev": true,
+ "dependencies": {
+ "del": "^6.0.0",
+ "is-stream": "^2.0.0",
+ "temp-dir": "^2.0.0",
+ "type-fest": "^0.16.0",
+ "unique-string": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/tempy/node_modules/type-fest": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
+ "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/text-extensions": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz",
+ "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+ "dev": true
+ },
+ "node_modules/through2": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+ "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "3"
+ }
+ },
+ "node_modules/through2/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
+ "node_modules/traverse": {
+ "version": "0.6.7",
+ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz",
+ "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/trim-newlines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
+ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/uglify-js": {
+ "version": "3.17.4",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
+ "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/unique-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
+ "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
+ "dev": true,
+ "dependencies": {
+ "crypto-random-string": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/universal-user-agent": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
+ "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==",
+ "dev": true
+ },
+ "node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/url-join": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
+ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
+ "dev": true
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fbd68f2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,8 @@
+{
+ "devDependencies": {
+ "@semantic-release/changelog": "^6.0.2",
+ "@semantic-release/git": "^10.0.1",
+ "gradle-semantic-release-plugin": "^1.7.4",
+ "semantic-release": "^19.0.5"
+ }
+}
diff --git a/patches.json b/patches.json
new file mode 100644
index 0000000..934ee20
--- /dev/null
+++ b/patches.json
@@ -0,0 +1 @@
+[{"name":"always-autorepeat","description":"Always repeats the playing video again.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"background-play","description":"Enables playing music in the background.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"block-audio-ads","description":"Blocks audio ads in streams and VODs.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"block-embedded-ads","description":"Blocks embedded steam ads using services like TTV.lol or PurpleAdBlocker.","version":"0.0.1","excluded":false,"options":[],"dependencies":["block-video-ads","integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"block-video-ads","description":"Blocks video ads in streams and VODs.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"client-spoof","description":"Spoofs the YouTube or Vanced client to prevent playback issues.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]},{"name":"com.vanced.android.youtube","versions":[]}]},{"name":"codecs-unlock","description":"Adds more audio codec options. The new audio codecs usually result in better audio quality.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"comments","description":"Hides components related to comments.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","comments-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"compact-header","description":"Hides the music category bar at the top of the homepage.","version":"0.0.1","excluded":true,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"custom-branding","description":"Changes the YouTube launcher icon and name to your choice (defaults to ReVanced).","version":"0.0.1","excluded":false,"options":[{"key":"appName","title":"Application Name","description":"The name of the application it will show on your home screen.","required":true,"choices":null},{"key":"iconPath","title":"App Icon Path","description":"A path containing mipmap resource folders with icons.","required":false,"choices":null}],"dependencies":["locale-config-fix"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"custom-video-buffer","description":"Lets you change the buffers of videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":["settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"custom-video-speed","description":"Adds more video speed options.","version":"0.0.1","excluded":false,"options":[{"key":"granularity","title":"Video speed granularity","description":"The granularity of the video speeds. The higher the value, the more speeds will be available.","required":true,"choices":null},{"key":"min","title":"Minimum video speed","description":"The minimum video speed.","required":true,"choices":null},{"key":"max","title":"Maximum video speed","description":"The maximum video speed. Must be greater than the minimum video speed and smaller than 5.","required":true,"choices":null}],"dependencies":["integrations"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"debug-mode","description":"Enables Twitch\u0027s internal debugging mode.","version":"0.0.1","excluded":true,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"debugging","description":"Adds debugging options.","version":"0.0.1","excluded":false,"options":[{"key":"debuggable","title":"App debugging","description":"Whether to make the app debuggable on Android.","required":false,"choices":null}],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"disable-ads","description":"Disables ads in HexEditor.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.myprog.hexedit","versions":[]}]},{"name":"disable-auto-captions","description":"Disable forced captions from being automatically enabled.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"disable-auto-player-popup-panels","description":"Disable automatic popup panels (playlist or live chat) on video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"disable-capture-restriction","description":"Allows capturing Spotify\u0027s audio output while screen sharing or screen recording.","version":"0.0.2","excluded":false,"options":[],"dependencies":["disable-capture-restriction-resource-patch"],"compatiblePackages":[{"name":"com.spotify.music","versions":[]}]},{"name":"disable-fullscreen-panels","description":"Disables video description and comments panel in fullscreen view.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"disable-login-requirement","description":"Do not force login.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"disable-startup-shorts-player","description":"Disables playing YouTube Shorts when launching YouTube.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"disable-zoom-haptics","description":"Disables haptics when zooming.","version":"0.0.1","excluded":false,"options":[],"dependencies":["settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"downloads","description":"Enables downloading music and videos from YouTube.","version":"0.0.1","excluded":false,"options":[],"dependencies":["downloads-resource-patch","player-controls-bytecode-patch","video-id-hook"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"downloads","description":"Removes download restrictions and changes the default path to download to.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"dynamic-color","description":"Replaces the default Twitter Blue with the users Material You palette.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.twitter.android","versions":[]}]},{"name":"enable-wide-searchbar","description":"Replaces the search icon with a wide search bar. This will hide the YouTube logo when active.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"exclusive-audio-playback","description":"Enables the option to play music without video.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"feed-filter","description":"Filters tiktok videos: removing ads, removing livestreams.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"fix-google-login","description":"Allows logging in with a Google account.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"general-ads","description":"Removes general ads.","version":"0.0.1","excluded":false,"options":[],"dependencies":["GeneralAdsResourcePatch","VerticalScrollPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"general-reddit-ads","description":"Removes general ads from the Reddit frontpage and subreddits.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.reddit.frontpage","versions":[]}]},{"name":"hdr-auto-brightness","description":"Makes the brightness of HDR videos follow the system default.","version":"0.0.2","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-ads","description":"Removes ads from TikTok.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"hide-album-cards","description":"Hides the album cards below the artist description.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","hide-album-cards-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-artist-card","description":"Hides the artist card below the searchbar.","version":"0.0.1","excluded":false,"options":[],"dependencies":["resource-mapping","LithoFilterPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-autoplay-button","description":"Hides the autoplay button in the video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings","resource-mapping"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-captions-button","description":"Hides the captions button on video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-cast-button","description":"Hides the cast button in the video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"hide-create-button","description":"Hides the create button in the navigation bar.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","resource-mapping","settings","ResolvePivotBarFingerprintsPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-crowdfunding-box","description":"Hides the crowdfunding box between the player and video description.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","crowdfunding-box-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-email-address","description":"Hides the email address in the account switcher.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","hide-email-address-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-endscreen-cards","description":"Hides the suggested video cards at the end of a video in fullscreen.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","hide-endscreen-cards-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-get-premium","description":"Removes all \"Get Premium\" evidences from the avatar menu.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"hide-info-cards","description":"Hides info-cards in videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","HideInfocardsResourcePatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-my-mix","description":"Hides mix playlists.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-premium-navbar","description":"Removes the premium tab from the navbar.","version":"0.0.1","excluded":false,"options":[],"dependencies":["resource-mapping"],"compatiblePackages":[{"name":"com.spotify.music","versions":[]}]},{"name":"hide-shorts-button","description":"Hides the shorts button on the navigation bar.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings","ResolvePivotBarFingerprintsPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-time-and-seekbar","description":"Hides progress bar and time counter on videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-video-buttons","description":"Adds options to hide action buttons under a video.","version":"0.0.1","excluded":false,"options":[],"dependencies":["resource-mapping","LithoFilterPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-watch-in-vr","description":"Hides the Watch in VR option in the player settings flyout panel.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-watermark","description":"Hides creator\u0027s watermarks on videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"microg-support","description":"Allows YouTube ReVanced to run without root and under a different package name with Vanced MicroG.","version":"0.0.1","excluded":false,"options":[],"dependencies":["microg-resource-patch","hide-cast-button","client-spoof"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"minimized-playback","description":"Enables minimized and background playback.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"minimized-playback-music","description":"Enables minimized playback on Kids music.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"monochrome-icon","description":"Adds a monochrome icon.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.twitter.android","versions":[]}]},{"name":"music-microg-support","description":"Allows YouTube Music ReVanced to run without root and under a different package name.","version":"0.0.2","excluded":false,"options":[],"dependencies":["music-microg-resource-patch"],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"music-video-ads","description":"Removes ads in the music player.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"old-quality-layout","description":"Enables the original video quality flyout in the video player settings","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"open-links-directly","description":"Bypasses URL redirects and opens links directly inside YouTube app.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"playback-speed","description":"Enables the playback speed option for all videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"predictive-back-gesture","description":"Enables the predictive back gesture introduced on Android 13.","version":"0.0.1","excluded":true,"options":[],"dependencies":[],"compatiblePackages":[]},{"name":"premium-heading","description":"Shows premium branding on the home screen.","version":"0.0.1","excluded":false,"options":[],"dependencies":["locale-config-fix"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"premium-icon-reddit","description":"Unlocks premium Reddit app icons.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.reddit.frontpage","versions":[]}]},{"name":"premium-unlock","description":"Unlocks premium functions.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"org.citra.citra_emu","versions":[]},{"name":"org.citra.citra_emu.canary","versions":[]}]},{"name":"pro-unlock","description":"Unlocks pro-only functions.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.backdrops.wallpapers","versions":[]}]},{"name":"promo-code-unlock","description":"Disables the validation of promo code. Any code will work to unlock all features.","version":"0.0.1","excluded":false,"options":[],"dependencies":["spoof-cert-patch"],"compatiblePackages":[{"name":"de.dwd.warnapp","versions":[]}]},{"name":"remember-video-quality","description":"Adds the ability to remember the video quality you chose in the video quality flyout.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","video-id-hook","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"remove-player-button-background","description":"Removes the background from the video player buttons.","version":"0.0.1","excluded":false,"options":[],"dependencies":["locale-config-fix"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"return-youtube-dislike","description":"Shows the dislike count of videos using the Return YouTube Dislike API.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","video-id-hook","return-youtube-dislike-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"seekbar-tapping","description":"Enables tap-to-seek on the seekbar of the video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"settings","description":"Adds settings for ReVanced to YouTube.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"settings","description":"Adds settings menu to Twitch.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings-resource-patch"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"settings","description":"Adds ReVanced settings to TikTok.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations"],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"show-deleted-messages","description":"Shows deleted chat messages behind a clickable spoiler.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"show-seekbar","description":"Shows progress bar for all video.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"sim-spoof","description":"Spoofs the information which is retrieved from the sim-card.","version":"0.0.1","excluded":true,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"sponsorblock","description":"Integrate SponsorBlock.","version":"0.0.1","excluded":false,"options":[],"dependencies":["video-information","player-controls-bytecode-patch","integrations","sponsorblock-resource-patch","video-id-hook"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"spotify-theme","description":"Applies a custom theme.","version":"0.0.1","excluded":false,"options":[{"key":"backgroundColor","title":"Background color","description":"The background color. Can be a hex color or a resource reference.","required":false,"choices":null},{"key":"accentColor","title":"Accent color","description":"The accent color (\u0027spotify green\u0027 by default). Can be a hex color or a resource reference.","required":false,"choices":null},{"key":"accentPressedColor","title":"Pressed accent for the dark theme","description":"The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.","required":false,"choices":null}],"dependencies":["locale-config-fix"],"compatiblePackages":[{"name":"com.spotify.music","versions":[]}]},{"name":"swipe-controls","description":"Adds volume and brightness swipe controls.","version":"0.0.3","excluded":false,"options":[],"dependencies":["integrations","player-type-hook","swipe-controls-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"tablet-mini-player","description":"Enables the tablet mini player layout.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"tasteBuilder-remover","description":"Removes the \"Tell us which artists you like\" card from the home screen.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"theme","description":"Applies a custom theme.","version":"0.0.1","excluded":true,"options":[{"key":"darkThemeBackgroundColor","title":"Background color for the dark theme","description":"The background color of the dark theme. Can be a hex color or a resource reference.","required":false,"choices":null},{"key":"lightThemeBackgroundColor","title":"Background color for the light theme","description":"The background color of the light theme. Can be a hex color or a resource reference.","required":false,"choices":null}],"dependencies":["litho-components-theme","locale-config-fix"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"timeline-ads","description":"Removes ads from the Twitter timeline. Might require clearing app data to remove already cached ads.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.twitter.android","versions":[]}]},{"name":"unlock-pro","description":"Unlocks all pro features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"co.windyapp.android","versions":[]}]},{"name":"unlock-pro","description":"Unlocks all professional features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"org.totschnig.myexpenses","versions":[]}]},{"name":"unlock-pro","description":"Unlocks all pro features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.awedea.nyx","versions":[]}]},{"name":"unlock-pro","description":"Unlocks pro features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ithebk.expensemanager","versions":[]}]},{"name":"unlock-pro","description":"Unlocks all pro features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"ginlemon.iconpackstudio","versions":[]}]},{"name":"unlock-themes","description":"Unlocks all themes.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ticktick.task","versions":[]}]},{"name":"upgrade-button-remover","description":"Removes the upgrade tab from the pivot bar.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"video-ads","description":"Removes ads in the video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings","fix-playback"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]}]
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..472c476
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "revanced-patches"
diff --git a/src/main/kotlin/app/revanced/extensions/Extensions.kt b/src/main/kotlin/app/revanced/extensions/Extensions.kt
new file mode 100644
index 0000000..7c70f90
--- /dev/null
+++ b/src/main/kotlin/app/revanced/extensions/Extensions.kt
@@ -0,0 +1,44 @@
+package app.revanced.extensions
+
+import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import org.jf.dexlib2.iface.Method
+import org.jf.dexlib2.util.MethodUtil
+import org.w3c.dom.Node
+
+// TODO: populate this to all patches
+/**
+ * Convert a [MethodFingerprint] to a [PatchResultError].
+ *
+ * @return A [PatchResultError] for the [MethodFingerprint].
+ */
+fun MethodFingerprint.toErrorResult() = PatchResultError("Failed to resolve $name")
+
+/**
+ * Find the [MutableMethod] from a given [Method] in a [MutableClass].
+ *
+ * @param method The [Method] to find.
+ * @return The [MutableMethod].
+ */
+fun MutableClass.findMutableMethodOf(method: Method) = this.methods.first {
+ MethodUtil.methodSignaturesMatch(it, method)
+}
+
+/**
+ * apply a transform to all methods of the class
+ *
+ * @param transform the transformation function. original method goes in, transformed method goes out
+ */
+fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) {
+ val transformedMethods = methods.map { it.transform() }
+ methods.clear()
+ methods.addAll(transformedMethods)
+}
+
+internal fun Node.doRecursively(action: (Node) -> Unit) {
+ action(this)
+ for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/meta/Meta.kt b/src/main/kotlin/app/revanced/meta/Meta.kt
new file mode 100644
index 0000000..6be5ed0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/meta/Meta.kt
@@ -0,0 +1,27 @@
+package app.revanced.meta
+
+import app.revanced.meta.json.generateJson
+import app.revanced.meta.readme.generateText
+import app.revanced.patcher.data.Context
+import app.revanced.patcher.patch.Patch
+import app.revanced.patcher.util.patch.PatchBundle
+import java.io.File
+
+typealias Bundle = List>>
+
+object Meta {
+ @JvmStatic
+ fun main(args: Array) {
+ val patches = accumulatePatches()
+ if (patches.isEmpty()) throw IllegalStateException("No patches found")
+
+ generateText(patches)
+ generateJson(patches)
+ }
+}
+
+fun accumulatePatches() = PatchBundle.Jar(
+ File("build/libs/").listFiles()!!.first {
+ it.name.startsWith("revanced-patches-") && it.name.endsWith(".jar")
+ }.absolutePath
+).loadPatches()
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/meta/json/Generator.kt b/src/main/kotlin/app/revanced/meta/json/Generator.kt
new file mode 100644
index 0000000..cc762ee
--- /dev/null
+++ b/src/main/kotlin/app/revanced/meta/json/Generator.kt
@@ -0,0 +1,48 @@
+package app.revanced.meta.json
+
+import app.revanced.meta.Bundle
+import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
+import app.revanced.patcher.extensions.PatchExtensions.dependencies
+import app.revanced.patcher.extensions.PatchExtensions.description
+import app.revanced.patcher.extensions.PatchExtensions.include
+import app.revanced.patcher.extensions.PatchExtensions.options
+import app.revanced.patcher.extensions.PatchExtensions.patchName
+import app.revanced.patcher.extensions.PatchExtensions.version
+import app.revanced.patcher.patch.PatchOption
+import com.google.gson.GsonBuilder
+import java.io.File
+
+private val gson = GsonBuilder().serializeNulls().create()
+
+fun generateJson(bundle: Bundle) {
+ val patches = bundle.map {
+ JsonPatch(
+ it.patchName,
+ it.description ?: "This patch has no description.",
+ it.version ?: "0.0.0",
+ !it.include,
+ it.options?.map { option ->
+ Option(
+ option.key,
+ option.title,
+ option.description,
+ option.required,
+ option.let { lo ->
+ if (lo is PatchOption.ListOption<*>) {
+ lo.options.toMutableList().toTypedArray()
+ } else null
+ }
+ )
+ }?.toTypedArray() ?: emptyArray(),
+ it.dependencies?.map { dep ->
+ dep.java.patchName
+ }?.toTypedArray() ?: emptyArray(),
+ it.compatiblePackages?.map { pkg ->
+ CompatiblePackage(pkg.name, pkg.versions)
+ }?.toTypedArray() ?: emptyArray()
+ )
+ }
+
+ val json = File("patches.json")
+ json.writeText(gson.toJson(patches))
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/meta/json/JsonPatch.kt b/src/main/kotlin/app/revanced/meta/json/JsonPatch.kt
new file mode 100644
index 0000000..8a0d597
--- /dev/null
+++ b/src/main/kotlin/app/revanced/meta/json/JsonPatch.kt
@@ -0,0 +1,26 @@
+@file:Suppress("ArrayInDataClass") // We don't need it here.
+
+package app.revanced.meta.json
+
+data class JsonPatch(
+ val name: String,
+ val description: String,
+ val version: String,
+ val excluded: Boolean,
+ val options: Array,
+ val dependencies: Array,
+ val compatiblePackages: Array,
+)
+
+data class CompatiblePackage(
+ val name: String,
+ val versions: Array,
+)
+
+data class Option(
+ val key: String,
+ val title: String,
+ val description: String,
+ val required: Boolean,
+ val choices: Array<*>?,
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/meta/readme/Extensions.kt b/src/main/kotlin/app/revanced/meta/readme/Extensions.kt
new file mode 100644
index 0000000..d568eb8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/meta/readme/Extensions.kt
@@ -0,0 +1,10 @@
+package app.revanced.meta.readme
+
+import app.revanced.patcher.data.Context
+import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
+import app.revanced.patcher.patch.Patch
+
+internal fun Class>.getLatestVersion() =
+ this.compatiblePackages?.first()?.versions?.map {
+ SemanticVersion.fromString(it)
+ }?.maxWithOrNull(SemanticVersionComparator)
diff --git a/src/main/kotlin/app/revanced/meta/readme/Generator.kt b/src/main/kotlin/app/revanced/meta/readme/Generator.kt
new file mode 100644
index 0000000..904b966
--- /dev/null
+++ b/src/main/kotlin/app/revanced/meta/readme/Generator.kt
@@ -0,0 +1,42 @@
+package app.revanced.meta.readme
+
+import app.revanced.meta.Bundle
+import app.revanced.patcher.data.Context
+import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
+import app.revanced.patcher.extensions.PatchExtensions.description
+import app.revanced.patcher.extensions.PatchExtensions.patchName
+import app.revanced.patcher.patch.Patch
+import java.io.File
+
+private const val TABLE_HEADER =
+ "| \uD83D\uDC8A Patch | \uD83D\uDCDC Description | \uD83C\uDFF9 Target Version |\n" + "|:--------:|:--------------:|:-----------------:|"
+
+private val TABLE_REGEX = Regex("\\{\\{\\s?table\\s?}}")
+
+fun generateText(bundle: Bundle) {
+ val output = StringBuilder()
+ val packages = mutableMapOf>>>()
+
+ for (patch in bundle) {
+ patch.compatiblePackages?.forEach { pkg ->
+ if (!packages.contains(pkg.name)) packages[pkg.name] = mutableListOf()
+ packages[pkg.name]!!.add(patch)
+ }
+ }
+
+ for (pkg in packages.entries.sortedByDescending { it.value.size }) {
+ output.appendLine("### [\uD83D\uDCE6 `${pkg.key}`](https://play.google.com/store/apps/details?id=${pkg.key})")
+ output.appendLine("\n")
+
+ output.appendLine(TABLE_HEADER)
+ pkg.value.forEach { output.appendLine("| `${it.patchName}` | ${it.description} | ${it.getLatestVersion() ?: "all"} |") }
+
+ output.appendLine(" \n")
+ }
+
+ val readmeTemplate = Template(File("README-template.md").readText())
+ readmeTemplate.replaceVariable(TABLE_REGEX, output.toString())
+
+ val readme = File("README.md")
+ readme.writeText(readmeTemplate.toString())
+}
diff --git a/src/main/kotlin/app/revanced/meta/readme/SemanticVersion.kt b/src/main/kotlin/app/revanced/meta/readme/SemanticVersion.kt
new file mode 100644
index 0000000..6c461f2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/meta/readme/SemanticVersion.kt
@@ -0,0 +1,22 @@
+package app.revanced.meta.readme
+
+data class SemanticVersion(val major: Int, val minor: Int, val patch: Int) {
+ companion object {
+ fun fromString(version: String): SemanticVersion {
+ val parts = version.split(".")
+ if (parts.count() != 3) throw IllegalArgumentException("Invalid semantic version")
+ val versionNumbers = parts.map { it.toInt() }
+ return SemanticVersion(versionNumbers[0], versionNumbers[1], versionNumbers[2])
+ }
+ }
+
+ override fun toString(): String = "$major.$minor.$patch"
+}
+
+object SemanticVersionComparator : Comparator {
+ override fun compare(a: SemanticVersion, b: SemanticVersion): Int = when {
+ a.major != b.major -> a.major - b.major
+ a.minor != b.minor -> a.minor - b.minor
+ else -> a.patch - b.patch
+ }
+}
diff --git a/src/main/kotlin/app/revanced/meta/readme/Template.kt b/src/main/kotlin/app/revanced/meta/readme/Template.kt
new file mode 100644
index 0000000..3b952c9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/meta/readme/Template.kt
@@ -0,0 +1,12 @@
+package app.revanced.meta.readme
+
+class Template(template: String) {
+ val result = StringBuilder(template)
+
+ fun replaceVariable(regex: Regex, value: String) {
+ val range = regex.find(result)!!.range
+ result.replace(range.first, range.last + 1, value)
+ }
+
+ override fun toString(): String = result.toString()
+}
diff --git a/src/main/kotlin/app/revanced/patches/all/interaction/gestures/patch/PredictiveBackGesturePatch.kt b/src/main/kotlin/app/revanced/patches/all/interaction/gestures/patch/PredictiveBackGesturePatch.kt
new file mode 100644
index 0000000..05beb7b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/all/interaction/gestures/patch/PredictiveBackGesturePatch.kt
@@ -0,0 +1,37 @@
+package app.revanced.patches.all.interaction.gestures.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.Patch
+
+@Patch(false)
+@Name("predictive-back-gesture")
+@Description("Enables the predictive back gesture introduced on Android 13.")
+@Version("0.0.1")
+class PredictiveBackGesturePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ context.xmlEditor["AndroidManifest.xml"].use { editor ->
+ val document = editor.file
+
+ with(document.getElementsByTagName("application").item(0)) {
+ if (attributes.getNamedItem(FLAG) != null) return@with
+
+ document.createAttribute(FLAG)
+ .apply { value = "true" }
+ .let(attributes::setNamedItem)
+
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+
+ private companion object {
+ const val FLAG = "android:enableOnBackInvokedCallback"
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/annotations/ProUnlockCompatibility.kt b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/annotations/ProUnlockCompatibility.kt
new file mode 100644
index 0000000..60b4832
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/annotations/ProUnlockCompatibility.kt
@@ -0,0 +1,7 @@
+package app.revanced.patches.backdrops.misc.pro.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.backdrops.wallpapers")])
+internal annotation class ProUnlockCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/fingerprints/ProUnlockFingerprint.kt b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/fingerprints/ProUnlockFingerprint.kt
new file mode 100644
index 0000000..7deff0a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/fingerprints/ProUnlockFingerprint.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.backdrops.misc.pro.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object ProUnlockFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ
+ ),
+ customFingerprint = { it.definingClass == "Lcom/backdrops/wallpapers/data/local/DatabaseHandlerIAB;" && it.name == "lambda\$existPurchase\$0" }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/patch/ProUnlockPatch.kt b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/patch/ProUnlockPatch.kt
new file mode 100644
index 0000000..0365284
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/patch/ProUnlockPatch.kt
@@ -0,0 +1,42 @@
+package app.revanced.patches.backdrops.misc.pro.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.backdrops.misc.pro.annotations.ProUnlockCompatibility
+import app.revanced.patches.backdrops.misc.pro.fingerprints.ProUnlockFingerprint
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@Name("pro-unlock")
+@Description("Unlocks pro-only functions.")
+@ProUnlockCompatibility
+@Version("0.0.1")
+class ProUnlockPatch : BytecodePatch(
+ listOf(ProUnlockFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = ProUnlockFingerprint.result ?: return PatchResultError("${ProUnlockFingerprint.name} not found")
+
+ val moveRegisterInstruction = result.mutableMethod.instruction(result.scanResult.patternScanResult!!.endIndex - 1)
+ val register = (moveRegisterInstruction as OneRegisterInstruction).registerA
+
+ result.mutableMethod.addInstructions(
+ result.scanResult.patternScanResult!!.endIndex,
+ """
+ const/4 v$register, 0x1
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/citra/misc/premium/annotations/PremiumUnlockCompatbility.kt b/src/main/kotlin/app/revanced/patches/citra/misc/premium/annotations/PremiumUnlockCompatbility.kt
new file mode 100644
index 0000000..a40ee14
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/citra/misc/premium/annotations/PremiumUnlockCompatbility.kt
@@ -0,0 +1,7 @@
+package app.revanced.patches.citra.misc.premium.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("org.citra.citra_emu"), Package("org.citra.citra_emu.canary")])
+internal annotation class PremiumUnlockCompatbility
diff --git a/src/main/kotlin/app/revanced/patches/citra/misc/premium/fingerprints/PremiumUnlockFingerprint.kt b/src/main/kotlin/app/revanced/patches/citra/misc/premium/fingerprints/PremiumUnlockFingerprint.kt
new file mode 100644
index 0000000..347c31a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/citra/misc/premium/fingerprints/PremiumUnlockFingerprint.kt
@@ -0,0 +1,7 @@
+package app.revanced.patches.citra.misc.premium.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PremiumUnlockFingerprint : MethodFingerprint(
+ customFingerprint = { it.definingClass == "Lorg/citra/citra_emu/ui/main/MainActivity;" && it.name == "isPremiumActive" }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/citra/misc/premium/patch/PremiumUnlockPatch.kt b/src/main/kotlin/app/revanced/patches/citra/misc/premium/patch/PremiumUnlockPatch.kt
new file mode 100644
index 0000000..10d4eeb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/citra/misc/premium/patch/PremiumUnlockPatch.kt
@@ -0,0 +1,37 @@
+package app.revanced.patches.citra.misc.premium.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patches.citra.misc.premium.annotations.PremiumUnlockCompatbility
+import app.revanced.patches.citra.misc.premium.fingerprints.PremiumUnlockFingerprint
+
+@Patch
+@Name("premium-unlock")
+@Description("Unlocks premium functions.")
+@PremiumUnlockCompatbility
+@Version("0.0.1")
+class PremiumUnlockPatch : BytecodePatch(
+ listOf(PremiumUnlockFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = PremiumUnlockFingerprint.result ?: return PatchResultError("${PremiumUnlockFingerprint.name} not found")
+
+ result.mutableMethod.addInstructions(
+ 0,
+ """
+ const v0, 0x1
+ return v0
+ """
+ )
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/hexeditor/ad/annotations/HexEditorAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/hexeditor/ad/annotations/HexEditorAdsCompatibility.kt
new file mode 100644
index 0000000..e0ae0e0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/hexeditor/ad/annotations/HexEditorAdsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.hexeditor.ad.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.myprog.hexedit")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HexEditorAdsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/hexeditor/ad/fingerprints/PrimaryAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/hexeditor/ad/fingerprints/PrimaryAdsFingerprint.kt
new file mode 100644
index 0000000..34c3e94
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/hexeditor/ad/fingerprints/PrimaryAdsFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.hexeditor.ad.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PrimaryAdsFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("PreferencesHelper;") && methodDef.name == "isAdsDisabled"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/hexeditor/ad/patch/HexEditorAdsPatch.kt b/src/main/kotlin/app/revanced/patches/hexeditor/ad/patch/HexEditorAdsPatch.kt
new file mode 100644
index 0000000..67bc094
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/hexeditor/ad/patch/HexEditorAdsPatch.kt
@@ -0,0 +1,39 @@
+package app.revanced.patches.hexeditor.ad.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.extensions.replaceInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.hexeditor.ad.annotations.HexEditorAdsCompatibility
+import app.revanced.patches.hexeditor.ad.fingerprints.PrimaryAdsFingerprint
+
+@Patch
+@Name("disable-ads")
+@Description("Disables ads in HexEditor.")
+@HexEditorAdsCompatibility
+@Version("0.0.1")
+class HexEditorAdsPatch : BytecodePatch(
+ listOf(
+ PrimaryAdsFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = PrimaryAdsFingerprint.result!!.mutableMethod
+
+ method.replaceInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+ return v0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/annotations/UnlockProCompatibility.kt
new file mode 100644
index 0000000..649e066
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/annotations/UnlockProCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.iconpackstudio.misc.pro.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("ginlemon.iconpackstudio")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class UnlockProCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/fingerprints/CheckProFingerprint.kt b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/fingerprints/CheckProFingerprint.kt
new file mode 100644
index 0000000..945ef2c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/fingerprints/CheckProFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.iconpackstudio.misc.pro.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CheckProFingerprint : MethodFingerprint(
+ "Z",
+ customFingerprint = { it.definingClass.endsWith("IPSPurchaseRepository;")}
+)
diff --git a/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/patch/UnlockProPatch.kt
new file mode 100644
index 0000000..a4a2d4f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/patch/UnlockProPatch.kt
@@ -0,0 +1,38 @@
+package app.revanced.patches.iconpackstudio.misc.pro.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.iconpackstudio.misc.pro.annotations.UnlockProCompatibility
+import app.revanced.patches.iconpackstudio.misc.pro.fingerprints.CheckProFingerprint
+
+@Patch
+@Name("unlock-pro")
+@Description("Unlocks all pro features.")
+@UnlockProCompatibility
+@Version("0.0.1")
+class UnlockProPatch : BytecodePatch(
+ listOf(
+ CheckProFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = CheckProFingerprint.result!!.mutableMethod
+ method.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+ return v0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/moneymanager/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/moneymanager/annotations/UnlockProCompatibility.kt
new file mode 100644
index 0000000..8a46c0e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/moneymanager/annotations/UnlockProCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.moneymanager.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.ithebk.expensemanager")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class UnlockProCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/moneymanager/fingerprints/UnlockProFingerprint.kt b/src/main/kotlin/app/revanced/patches/moneymanager/fingerprints/UnlockProFingerprint.kt
new file mode 100644
index 0000000..9ea14f8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/moneymanager/fingerprints/UnlockProFingerprint.kt
@@ -0,0 +1,19 @@
+package app.revanced.patches.moneymanager.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object UnlockProFingerprint : MethodFingerprint(
+ "Z",
+ AccessFlags.STATIC or AccessFlags.SYNTHETIC,
+ parameters = listOf("L"),
+ opcodes = listOf(
+ Opcode.IGET_BOOLEAN,
+ Opcode.RETURN
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("MainActivity;")
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/moneymanager/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/moneymanager/patch/UnlockProPatch.kt
new file mode 100644
index 0000000..e857e71
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/moneymanager/patch/UnlockProPatch.kt
@@ -0,0 +1,34 @@
+package app.revanced.patches.moneymanager.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.moneymanager.annotations.UnlockProCompatibility
+import app.revanced.patches.moneymanager.fingerprints.UnlockProFingerprint
+
+@Patch
+@Name("unlock-pro")
+@Description("Unlocks pro features.")
+@UnlockProCompatibility
+@Version("0.0.1")
+class UnlockProPatch : BytecodePatch(
+ listOf(UnlockProFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ UnlockProFingerprint.result!!.mutableMethod.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+
+ return v0
+ """
+ )
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/ad/video/annotations/MusicVideoAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/ad/video/annotations/MusicVideoAdsCompatibility.kt
new file mode 100644
index 0000000..314e159
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/ad/video/annotations/MusicVideoAdsCompatibility.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.music.ad.video.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.17.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class MusicVideoAdsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsConstructorFingerprint.kt
new file mode 100644
index 0000000..dd60e20
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsConstructorFingerprint.kt
@@ -0,0 +1,27 @@
+package app.revanced.patches.music.ad.video.fingerprints
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object ShowMusicVideoAdsConstructorFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L"), listOf(
+ Opcode.INVOKE_DIRECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.CONST_4,
+ Opcode.IPUT_BOOLEAN,
+ Opcode.RETURN_VOID
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsFingerprint.kt
new file mode 100644
index 0000000..d6d8a13
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsFingerprint.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.music.ad.video.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object ShowMusicVideoAdsFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("Z"), listOf(
+ Opcode.IPUT_BOOLEAN,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.RETURN_VOID
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/music/ad/video/patch/MusicVideoAdsPatch.kt b/src/main/kotlin/app/revanced/patches/music/ad/video/patch/MusicVideoAdsPatch.kt
new file mode 100644
index 0000000..bbf4848
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/ad/video/patch/MusicVideoAdsPatch.kt
@@ -0,0 +1,40 @@
+package app.revanced.patches.music.ad.video.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.music.ad.video.annotations.MusicVideoAdsCompatibility
+import app.revanced.patches.music.ad.video.fingerprints.ShowMusicVideoAdsConstructorFingerprint
+import app.revanced.patches.music.ad.video.fingerprints.ShowMusicVideoAdsFingerprint
+
+@Patch
+@Name("music-video-ads")
+@Description("Removes ads in the music player.")
+@MusicVideoAdsCompatibility
+@Version("0.0.1")
+class MusicVideoAdsPatch : BytecodePatch(
+ listOf(
+ ShowMusicVideoAdsConstructorFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ ShowMusicVideoAdsFingerprint.resolve(context, ShowMusicVideoAdsConstructorFingerprint.result!!.classDef)
+
+ val result = ShowMusicVideoAdsFingerprint.result!!
+
+ result.mutableMethod.addInstructions(
+ result.scanResult.patternScanResult!!.startIndex, """
+ const/4 p1, 0x0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/audio/codecs/annotations/CodecsUnlockCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/audio/codecs/annotations/CodecsUnlockCompatibility.kt
new file mode 100644
index 0000000..5fd4bca
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/audio/codecs/annotations/CodecsUnlockCompatibility.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.music.audio.codecs.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.17.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class CodecsUnlockCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/AllCodecsReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/AllCodecsReferenceFingerprint.kt
new file mode 100644
index 0000000..92f047d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/AllCodecsReferenceFingerprint.kt
@@ -0,0 +1,52 @@
+package app.revanced.patches.music.audio.codecs.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object AllCodecsReferenceFingerprint : MethodFingerprint(
+ "J", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf(
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ Opcode.CONST_4,
+ Opcode.IF_EQZ,
+ Opcode.IGET_BOOLEAN,
+ Opcode.IF_NEZ,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.IPUT_BOOLEAN,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.GOTO,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_NEZ,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.IGET_BOOLEAN,
+ Opcode.IF_NEZ,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.IPUT_BOOLEAN,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.GOTO,
+ Opcode.MOVE_EXCEPTION,
+ Opcode.INVOKE_SUPER,
+ Opcode.MOVE_RESULT_WIDE,
+ Opcode.RETURN_WIDE
+ ), listOf("itag")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/CodecsLockFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/CodecsLockFingerprint.kt
new file mode 100644
index 0000000..75189e8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/CodecsLockFingerprint.kt
@@ -0,0 +1,29 @@
+package app.revanced.patches.music.audio.codecs.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object CodecsLockFingerprint : MethodFingerprint(
+ "L", AccessFlags.PUBLIC or AccessFlags.STATIC, opcodes = listOf(
+ Opcode.INVOKE_DIRECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_NEZ,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_NEZ,
+ Opcode.SGET_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.RETURN_OBJECT
+ ),
+ strings = listOf("eac3_supported")
+)
diff --git a/src/main/kotlin/app/revanced/patches/music/audio/codecs/patch/CodecsUnlockPatch.kt b/src/main/kotlin/app/revanced/patches/music/audio/codecs/patch/CodecsUnlockPatch.kt
new file mode 100644
index 0000000..9ff6916
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/audio/codecs/patch/CodecsUnlockPatch.kt
@@ -0,0 +1,56 @@
+package app.revanced.patches.music.audio.codecs.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.data.toMethodWalker
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.toInstruction
+import app.revanced.patches.music.audio.codecs.annotations.CodecsUnlockCompatibility
+import app.revanced.patches.music.audio.codecs.fingerprints.AllCodecsReferenceFingerprint
+import app.revanced.patches.music.audio.codecs.fingerprints.CodecsLockFingerprint
+import org.jf.dexlib2.Opcode
+
+@Patch
+@Name("codecs-unlock")
+@Description("Adds more audio codec options. The new audio codecs usually result in better audio quality.")
+@CodecsUnlockCompatibility
+@Version("0.0.1")
+class CodecsUnlockPatch : BytecodePatch(
+ listOf(
+ CodecsLockFingerprint, AllCodecsReferenceFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val codecsLockResult = CodecsLockFingerprint.result!!
+
+ val implementation = codecsLockResult.mutableMethod.implementation!!
+
+ val scanResultStartIndex = codecsLockResult.scanResult.patternScanResult!!.startIndex
+ val instructionIndex = scanResultStartIndex +
+ if (implementation.instructions[scanResultStartIndex - 1].opcode == Opcode.CHECK_CAST) {
+ // for 5.16.xx and lower
+ -3
+ } else {
+ // since 5.17.xx
+ -2
+ }
+
+ val allCodecsResult = AllCodecsReferenceFingerprint.result!!
+ val allCodecsMethod =
+ context.toMethodWalker(allCodecsResult.method)
+ .nextMethod(allCodecsResult.scanResult.patternScanResult!!.startIndex)
+ .getMethod()
+
+ implementation.replaceInstruction(
+ instructionIndex,
+ "invoke-static {}, ${allCodecsMethod.definingClass}->${allCodecsMethod.name}()Ljava/util/Set;".toInstruction()
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/annotations/ExclusiveAudioCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/annotations/ExclusiveAudioCompatibility.kt
new file mode 100644
index 0000000..a653e57
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/annotations/ExclusiveAudioCompatibility.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.music.audio.exclusiveaudio.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.17.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ExclusiveAudioCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/AudioOnlyEnablerFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/AudioOnlyEnablerFingerprint.kt
new file mode 100644
index 0000000..29c90b6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/AudioOnlyEnablerFingerprint.kt
@@ -0,0 +1,28 @@
+package app.revanced.patches.music.audio.exclusiveaudio.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object AudioOnlyEnablerFingerprint: MethodFingerprint(
+ "Z", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf(
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.IF_NEZ,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.GOTO,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.RETURN
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/ExclusiveAudioFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/ExclusiveAudioFingerprint.kt
new file mode 100644
index 0000000..02eb60a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/ExclusiveAudioFingerprint.kt
@@ -0,0 +1,51 @@
+package app.revanced.patches.music.audio.exclusiveaudio.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object ExclusiveAudioFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("L", "Z"),
+ listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQ,
+ Opcode.CONST_4,
+ Opcode.GOTO,
+ Opcode.NOP,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.IF_EQZ,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.IF_EQZ,
+ Opcode.IF_EQZ,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.GOTO,
+ Opcode.RETURN_VOID
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/patch/ExclusiveAudioPatch.kt b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/patch/ExclusiveAudioPatch.kt
new file mode 100644
index 0000000..d679e87
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/patch/ExclusiveAudioPatch.kt
@@ -0,0 +1,33 @@
+package app.revanced.patches.music.audio.exclusiveaudio.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.music.audio.exclusiveaudio.annotations.ExclusiveAudioCompatibility
+import app.revanced.patches.music.audio.exclusiveaudio.fingerprints.AudioOnlyEnablerFingerprint
+
+@Patch
+@Name("exclusive-audio-playback")
+@Description("Enables the option to play music without video.")
+@ExclusiveAudioCompatibility
+@Version("0.0.1")
+class ExclusiveAudioPatch : BytecodePatch(
+ listOf(
+ AudioOnlyEnablerFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = AudioOnlyEnablerFingerprint.result!!.mutableMethod
+ method.replaceInstruction(method.implementation!!.instructions.count() - 1, "const/4 v0, 0x1")
+ method.addInstruction("return v0")
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/compactheader/annotations/CompactHeaderCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/annotations/CompactHeaderCompatibility.kt
new file mode 100644
index 0000000..a54a371
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/annotations/CompactHeaderCompatibility.kt
@@ -0,0 +1,29 @@
+package app.revanced.patches.music.layout.compactheader.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class CompactHeaderCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/compactheader/fingerprints/CompactHeaderConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/fingerprints/CompactHeaderConstructorFingerprint.kt
new file mode 100644
index 0000000..661c138
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/fingerprints/CompactHeaderConstructorFingerprint.kt
@@ -0,0 +1,23 @@
+package app.revanced.patches.music.layout.compactheader.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object CompactHeaderConstructorFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L", "L", "L"), listOf(
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.CONST,
+ Opcode.CONST_4,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/compactheader/patch/CompactHeaderPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/patch/CompactHeaderPatch.kt
new file mode 100644
index 0000000..879dfa0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/patch/CompactHeaderPatch.kt
@@ -0,0 +1,41 @@
+package app.revanced.patches.music.layout.compactheader.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.music.layout.compactheader.annotations.CompactHeaderCompatibility
+import app.revanced.patches.music.layout.compactheader.fingerprints.CompactHeaderConstructorFingerprint
+import org.jf.dexlib2.builder.instruction.BuilderInstruction11x
+
+@Patch(false)
+@Name("compact-header")
+@Description("Hides the music category bar at the top of the homepage.")
+@CompactHeaderCompatibility
+@Version("0.0.1")
+class CompactHeaderPatch : BytecodePatch(
+ listOf(
+ CompactHeaderConstructorFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = CompactHeaderConstructorFingerprint.result!!
+ val method = result.mutableMethod
+
+ val insertIndex = result.scanResult.patternScanResult!!.endIndex
+ val register = (method.implementation!!.instructions[insertIndex - 1] as BuilderInstruction11x).registerA
+ method.addInstructions(
+ insertIndex, """
+ const/16 v2, 0x8
+ invoke-virtual {v${register}, v2}, Landroid/view/View;->setVisibility(I)V
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt
new file mode 100644
index 0000000..b5ec0f5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.music.layout.minimizedplayback.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.17.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class MinimizedPlaybackCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt
new file mode 100644
index 0000000..eb3d28a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt
@@ -0,0 +1,26 @@
+package app.revanced.patches.music.layout.minimizedplayback.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object MinimizedPlaybackManagerFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("I", "L", "Z"),
+ listOf(
+ Opcode.IGET,
+ Opcode.IF_NE,
+ Opcode.IGET_OBJECT,
+ Opcode.IF_NE,
+ Opcode.IGET_BOOLEAN,
+ Opcode.IF_EQ,
+ Opcode.GOTO,
+ Opcode.RETURN_VOID,
+ Opcode.SGET_OBJECT,
+ Opcode.CONST_4,
+ Opcode.IF_NE,
+ Opcode.IPUT_BOOLEAN,
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/patch/MinimizedPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/patch/MinimizedPlaybackPatch.kt
new file mode 100644
index 0000000..dcfdb9f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/patch/MinimizedPlaybackPatch.kt
@@ -0,0 +1,34 @@
+package app.revanced.patches.music.layout.minimizedplayback.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.music.layout.minimizedplayback.annotations.MinimizedPlaybackCompatibility
+import app.revanced.patches.music.layout.minimizedplayback.fingerprints.MinimizedPlaybackManagerFingerprint
+
+@Patch
+@Name("minimized-playback-music")
+@Description("Enables minimized playback on Kids music.")
+@MinimizedPlaybackCompatibility
+@Version("0.0.1")
+class MinimizedPlaybackPatch : BytecodePatch(
+ listOf(
+ MinimizedPlaybackManagerFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ MinimizedPlaybackManagerFingerprint.result!!.mutableMethod.addInstructions(
+ 0, """
+ return-void
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/premium/annotations/HideGetPremiumCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/premium/annotations/HideGetPremiumCompatibility.kt
new file mode 100644
index 0000000..f2089df
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/premium/annotations/HideGetPremiumCompatibility.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.music.layout.premium.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.17.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideGetPremiumCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumFingerprint.kt
new file mode 100644
index 0000000..5b89674
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumFingerprint.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.music.layout.premium.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object HideGetPremiumFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf(
+ Opcode.IF_NEZ,
+ Opcode.CONST_16,
+ Opcode.GOTO,
+ Opcode.NOP,
+ Opcode.INVOKE_VIRTUAL
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumParentFingerprint.kt
new file mode 100644
index 0000000..d91ff64
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumParentFingerprint.kt
@@ -0,0 +1,19 @@
+package app.revanced.patches.music.layout.premium.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object HideGetPremiumParentFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf(
+ Opcode.IGET_BOOLEAN,
+ Opcode.CONST_4,
+ Opcode.IF_EQZ,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_STATIC
+ ),
+ listOf("FEmusic_history"),
+)
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/premium/patch/HideGetPremiumPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/premium/patch/HideGetPremiumPatch.kt
new file mode 100644
index 0000000..727ff9d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/premium/patch/HideGetPremiumPatch.kt
@@ -0,0 +1,51 @@
+package app.revanced.patches.music.layout.premium.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.music.layout.premium.annotations.HideGetPremiumCompatibility
+import app.revanced.patches.music.layout.premium.fingerprints.HideGetPremiumFingerprint
+import app.revanced.patches.music.layout.premium.fingerprints.HideGetPremiumParentFingerprint
+
+@Patch
+@Name("hide-get-premium")
+@Description("Removes all \"Get Premium\" evidences from the avatar menu.")
+@HideGetPremiumCompatibility
+@Version("0.0.1")
+class HideGetPremiumPatch : BytecodePatch(
+ listOf(
+ HideGetPremiumParentFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val parentResult = HideGetPremiumParentFingerprint.result!!
+ HideGetPremiumFingerprint.resolve(context, parentResult.classDef)
+
+ val startIndex = parentResult.scanResult.patternScanResult!!.startIndex
+
+ val parentMethod = parentResult.mutableMethod
+ parentMethod.replaceInstruction(
+ startIndex, """
+ const/4 v1, 0x0
+ """
+ )
+
+ val result = HideGetPremiumFingerprint.result!!
+ val method = result.mutableMethod
+ method.addInstructions(
+ startIndex, """
+ const/16 v0, 0x8
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/annotations/RemoveTasteBuilderCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/annotations/RemoveTasteBuilderCompatibility.kt
new file mode 100644
index 0000000..c51a2f1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/annotations/RemoveTasteBuilderCompatibility.kt
@@ -0,0 +1,32 @@
+package app.revanced.patches.music.layout.tastebuilder.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+/**
+ * -- Note 2022-08-05 --
+ * Since 5.17.xx the tastebuilder component is dismissible, so this patch is less useful
+ * also it is partly litho now
+ */
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class RemoveTasteBuilderCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/fingerprints/TasteBuilderConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/fingerprints/TasteBuilderConstructorFingerprint.kt
new file mode 100644
index 0000000..e72193c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/fingerprints/TasteBuilderConstructorFingerprint.kt
@@ -0,0 +1,34 @@
+package app.revanced.patches.music.layout.tastebuilder.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object TasteBuilderConstructorFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L"), listOf(
+ Opcode.INVOKE_DIRECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CONST,
+ Opcode.CONST_4,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.CONST
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/patch/RemoveTasteBuilderPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/patch/RemoveTasteBuilderPatch.kt
new file mode 100644
index 0000000..d3b61a3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/patch/RemoveTasteBuilderPatch.kt
@@ -0,0 +1,41 @@
+package app.revanced.patches.music.layout.tastebuilder.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.music.layout.tastebuilder.annotations.RemoveTasteBuilderCompatibility
+import app.revanced.patches.music.layout.tastebuilder.fingerprints.TasteBuilderConstructorFingerprint
+import org.jf.dexlib2.iface.instruction.formats.Instruction22c
+
+@Patch
+@Name("tasteBuilder-remover")
+@Description("Removes the \"Tell us which artists you like\" card from the home screen.")
+@RemoveTasteBuilderCompatibility
+@Version("0.0.1")
+class RemoveTasteBuilderPatch : BytecodePatch(
+ listOf(
+ TasteBuilderConstructorFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = TasteBuilderConstructorFingerprint.result!!
+ val method = result.mutableMethod
+
+ val insertIndex = result.scanResult.patternScanResult!!.endIndex - 8
+ val register = (method.implementation!!.instructions[insertIndex] as Instruction22c).registerA
+ method.addInstructions(
+ insertIndex, """
+ const/16 v1, 0x8
+ invoke-virtual {v${register}, v1}, Landroid/view/View;->setVisibility(I)V
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/annotations/RemoveUpgradeButtonCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/annotations/RemoveUpgradeButtonCompatibility.kt
new file mode 100644
index 0000000..b949a4a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/annotations/RemoveUpgradeButtonCompatibility.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.music.layout.upgradebutton.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.17.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class RemoveUpgradeButtonCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/fingerprints/PivotBarConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/fingerprints/PivotBarConstructorFingerprint.kt
new file mode 100644
index 0000000..e62225b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/fingerprints/PivotBarConstructorFingerprint.kt
@@ -0,0 +1,62 @@
+package app.revanced.patches.music.layout.upgradebutton.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object PivotBarConstructorFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
+ listOf("L", "Z"),
+ listOf(
+ Opcode.INVOKE_DIRECT,
+ Opcode.CONST_4,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_BOOLEAN,
+ Opcode.IGET_OBJECT,
+ Opcode.IF_NEZ,
+ Opcode.GOTO,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.IGET,
+ Opcode.CONST,
+ Opcode.IF_NE,
+ Opcode.IGET_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.GOTO,
+ Opcode.SGET_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.IGET,
+ Opcode.CONST,
+ Opcode.IF_NE,
+ Opcode.IGET_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.GOTO,
+ Opcode.NOP,
+ Opcode.IPUT_OBJECT,
+ Opcode.RETURN_VOID
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/patch/RemoveUpgradeButtonPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/patch/RemoveUpgradeButtonPatch.kt
new file mode 100644
index 0000000..9bf8ad0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/patch/RemoveUpgradeButtonPatch.kt
@@ -0,0 +1,78 @@
+package app.revanced.patches.music.layout.upgradebutton.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.toInstructions
+import app.revanced.patches.music.layout.upgradebutton.annotations.RemoveUpgradeButtonCompatibility
+import app.revanced.patches.music.layout.upgradebutton.fingerprints.PivotBarConstructorFingerprint
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.builder.instruction.BuilderInstruction22t
+import org.jf.dexlib2.iface.instruction.formats.Instruction22c
+import org.jf.dexlib2.iface.instruction.formats.Instruction35c
+
+
+@Patch
+@Name("upgrade-button-remover")
+@Description("Removes the upgrade tab from the pivot bar.")
+@RemoveUpgradeButtonCompatibility
+@Version("0.0.1")
+class RemoveUpgradeButtonPatch : BytecodePatch(
+ listOf(
+ PivotBarConstructorFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = PivotBarConstructorFingerprint.result!!
+ val implementation = result.mutableMethod.implementation!!
+
+ val pivotBarElementFieldRef =
+ (implementation.instructions[result.scanResult.patternScanResult!!.endIndex - 1] as Instruction22c).reference
+
+ val register = (implementation.instructions.first() as Instruction35c).registerC
+ // first compile all the needed instructions
+ val instructionList = """
+ invoke-interface { v0 }, Ljava/util/List;->size()I
+ move-result v1
+ const/4 v2, 0x3
+ invoke-interface {v0, v2}, Ljava/util/List;->remove(I)Ljava/lang/Object;
+ iput-object v0, v$register, $pivotBarElementFieldRef
+ """.toInstructions().toMutableList()
+
+
+ val endIndex = result.scanResult.patternScanResult!!.endIndex
+
+ // replace the instruction to retain the label at given index
+ implementation.replaceInstruction(
+ endIndex - 1, instructionList[0] // invoke-interface
+ )
+ // do not forget to remove this instruction since we added it already
+ instructionList.removeFirst()
+
+ val exitInstruction = instructionList.last() // iput-object
+ implementation.addInstruction(
+ endIndex, exitInstruction
+ )
+ // do not forget to remove this instruction since we added it already
+ instructionList.removeLast()
+
+ // add the necessary if statement to remove the upgrade tab button in case it exists
+ instructionList.add(
+ 2, // if-le
+ BuilderInstruction22t(
+ Opcode.IF_LE, 1, 2, implementation.newLabelForIndex(endIndex)
+ )
+ )
+
+ implementation.addInstructions(
+ endIndex, instructionList
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/annotations/MusicMicroGPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/annotations/MusicMicroGPatchCompatibility.kt
new file mode 100644
index 0000000..3bb82d4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/annotations/MusicMicroGPatchCompatibility.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.music.misc.microg.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.17.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class MusicMicroGPatchCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastContextFetchFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastContextFetchFingerprint.kt
new file mode 100644
index 0000000..04d0430
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastContextFetchFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.music.misc.microg.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CastContextFetchFingerprint : MethodFingerprint(
+ strings = listOf("Error fetching CastContext.")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt
new file mode 100644
index 0000000..a4d39a8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.music.misc.microg.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CastDynamiteModuleFingerprint : MethodFingerprint(
+ strings = listOf("com.google.android.gms.cast.framework.internal.CastDynamiteModuleImpl")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt
new file mode 100644
index 0000000..f4119e5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.music.misc.microg.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CastDynamiteModuleV2Fingerprint : MethodFingerprint(
+ strings = listOf("Failed to load module via V2: ")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt
new file mode 100644
index 0000000..cfc36f0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt
@@ -0,0 +1,18 @@
+package app.revanced.patches.music.misc.microg.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object GooglePlayUtilityFingerprint : MethodFingerprint(
+ "I",
+ AccessFlags.PUBLIC or AccessFlags.STATIC,
+ listOf("L", "I"),
+ strings = listOf(
+ "This should never happen.",
+ "MetadataValueReader",
+ "GooglePlayServicesUtil",
+ "com.android.vending",
+ "android.hardware.type.embedded"
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/PrimeFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/PrimeFingerprint.kt
new file mode 100644
index 0000000..e9931b4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/PrimeFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.music.misc.microg.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PrimeFingerprint : MethodFingerprint(
+ strings = listOf("com.google.android.GoogleCamera", "com.android.vending")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/ServiceCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/ServiceCheckFingerprint.kt
new file mode 100644
index 0000000..d81f77f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/ServiceCheckFingerprint.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.music.misc.microg.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object ServiceCheckFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.STATIC,
+ listOf("L", "I"),
+ strings = listOf("Google Play Services not available")
+)
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt
new file mode 100644
index 0000000..07b99c3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt
@@ -0,0 +1,65 @@
+package app.revanced.patches.music.misc.microg.patch.bytecode
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.music.misc.microg.annotations.MusicMicroGPatchCompatibility
+import app.revanced.patches.music.misc.microg.fingerprints.*
+import app.revanced.patches.music.misc.microg.patch.resource.MusicMicroGResourcePatch
+import app.revanced.patches.music.misc.microg.shared.Constants.MUSIC_PACKAGE_NAME
+import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_PACKAGE_NAME
+import app.revanced.patches.youtube.misc.microg.shared.Constants
+import app.revanced.util.microg.MicroGBytecodeHelper
+
+@Patch
+@DependsOn([MusicMicroGResourcePatch::class])
+@Name("music-microg-support")
+@Description("Allows YouTube Music ReVanced to run without root and under a different package name.")
+@MusicMicroGPatchCompatibility
+@Version("0.0.2")
+class MusicMicroGBytecodePatch : BytecodePatch(
+ listOf(
+ ServiceCheckFingerprint,
+ GooglePlayUtilityFingerprint,
+ CastDynamiteModuleFingerprint,
+ CastDynamiteModuleV2Fingerprint,
+ CastContextFetchFingerprint,
+ PrimeFingerprint,
+ )
+) {
+ // NOTE: the previous patch also replaced the following strings, but it seems like they are not needed:
+ // - "com.google.android.gms.chimera.GmsIntentOperationService",
+ // - "com.google.android.gms.phenotype.internal.IPhenotypeCallbacks",
+ // - "com.google.android.gms.phenotype.internal.IPhenotypeService",
+ // - "com.google.android.gms.phenotype.PACKAGE_NAME",
+ // - "com.google.android.gms.phenotype.UPDATE",
+ // - "com.google.android.gms.phenotype",
+ override fun execute(context: BytecodeContext) =
+ // apply common microG patch
+ MicroGBytecodeHelper.patchBytecode(
+ context,
+ arrayOf(
+ MicroGBytecodeHelper.packageNameTransform(
+ Constants.PACKAGE_NAME,
+ Constants.REVANCED_PACKAGE_NAME
+ )
+ ),
+ MicroGBytecodeHelper.PrimeMethodTransformationData(
+ PrimeFingerprint,
+ MUSIC_PACKAGE_NAME,
+ REVANCED_MUSIC_PACKAGE_NAME
+ ),
+ listOf(
+ ServiceCheckFingerprint,
+ GooglePlayUtilityFingerprint,
+ CastDynamiteModuleFingerprint,
+ CastDynamiteModuleV2Fingerprint,
+ CastContextFetchFingerprint
+ )
+ ).let { PatchResultSuccess() }
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt
new file mode 100644
index 0000000..19daba3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt
@@ -0,0 +1,41 @@
+package app.revanced.patches.music.misc.microg.patch.resource
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patches.music.misc.microg.annotations.MusicMicroGPatchCompatibility
+import app.revanced.patches.music.misc.microg.shared.Constants.MUSIC_PACKAGE_NAME
+import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_APP_NAME
+import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_PACKAGE_NAME
+import app.revanced.patches.music.misc.microg.shared.Constants.SPOOFED_PACKAGE_NAME
+import app.revanced.patches.music.misc.microg.shared.Constants.SPOOFED_PACKAGE_SIGNATURE
+import app.revanced.util.microg.MicroGManifestHelper
+import app.revanced.util.microg.MicroGResourceHelper
+
+@Name("music-microg-resource-patch")
+@Description("Resource patch to allow YouTube Music ReVanced to run without root and under a different package name.")
+@MusicMicroGPatchCompatibility
+@Version("0.0.2")
+class MusicMicroGResourcePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ // update manifest
+ MicroGResourceHelper.patchManifest(
+ context,
+ MUSIC_PACKAGE_NAME,
+ REVANCED_MUSIC_PACKAGE_NAME,
+ REVANCED_MUSIC_APP_NAME
+ )
+
+ // add metadata to the manifest
+ MicroGManifestHelper.addSpoofingMetadata(
+ context,
+ SPOOFED_PACKAGE_NAME,
+ SPOOFED_PACKAGE_SIGNATURE
+ )
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt
new file mode 100644
index 0000000..a7405cc
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.music.misc.microg.shared
+
+object Constants {
+ internal const val REVANCED_MUSIC_APP_NAME = "YT Music ReVanced"
+ internal const val REVANCED_MUSIC_PACKAGE_NAME = "app.revanced.android.apps.youtube.music"
+ internal const val MUSIC_PACKAGE_NAME = "com.google.android.apps.youtube.music"
+ internal const val SPOOFED_PACKAGE_NAME = MUSIC_PACKAGE_NAME
+ internal const val SPOOFED_PACKAGE_SIGNATURE = "afb0fed5eeaebdd86f56a97742f4b6b33ef59875"
+}
diff --git a/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/annotations/BackgroundPlayCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/annotations/BackgroundPlayCompatibility.kt
new file mode 100644
index 0000000..0c91cb5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/annotations/BackgroundPlayCompatibility.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.music.premium.backgroundplay.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.apps.youtube.music",
+ arrayOf(
+ "5.14.53",
+ "5.16.51",
+ "5.17.51",
+ "5.21.52",
+ "5.22.54",
+ "5.23.50",
+ "5.25.51",
+ "5.25.52",
+ "5.26.52",
+ "5.27.51",
+ "5.28.52",
+ "5.29.52",
+ "5.31.50",
+ "5.34.51",
+ "5.36.51"
+ )
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class BackgroundPlayCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/fingerprints/BackgroundPlaybackDisableFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/fingerprints/BackgroundPlaybackDisableFingerprint.kt
new file mode 100644
index 0000000..fdaea77
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/fingerprints/BackgroundPlaybackDisableFingerprint.kt
@@ -0,0 +1,43 @@
+package app.revanced.patches.music.premium.backgroundplay.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object BackgroundPlaybackDisableFingerprint : MethodFingerprint(
+ "Z", AccessFlags.PUBLIC or AccessFlags.STATIC, listOf("L"), listOf(
+ Opcode.CONST_4,
+ Opcode.IF_EQZ,
+ Opcode.IGET,
+ Opcode.AND_INT_LIT16,
+ Opcode.IF_EQZ,
+ Opcode.IGET_OBJECT,
+ Opcode.IF_NEZ,
+ Opcode.SGET_OBJECT,
+ Opcode.IGET,
+ Opcode.CONST,
+ Opcode.IF_NE,
+ Opcode.IGET_OBJECT,
+ Opcode.IF_NEZ,
+ Opcode.SGET_OBJECT,
+ Opcode.IGET,
+ Opcode.IF_NE,
+ Opcode.IGET_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.GOTO,
+ Opcode.SGET_OBJECT,
+ Opcode.GOTO,
+ Opcode.CONST_4,
+ Opcode.IF_EQZ,
+ Opcode.IGET_BOOLEAN,
+ Opcode.IF_EQZ,
+ Opcode.CONST_4,
+ Opcode.RETURN,
+ Opcode.RETURN,
+ Opcode.RETURN
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/patch/BackgroundPlayPatch.kt b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/patch/BackgroundPlayPatch.kt
new file mode 100644
index 0000000..9c860a9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/patch/BackgroundPlayPatch.kt
@@ -0,0 +1,36 @@
+package app.revanced.patches.music.premium.backgroundplay.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.music.premium.backgroundplay.annotations.BackgroundPlayCompatibility
+import app.revanced.patches.music.premium.backgroundplay.fingerprints.BackgroundPlaybackDisableFingerprint
+
+@Patch
+@Name("background-play")
+@Description("Enables playing music in the background.")
+@BackgroundPlayCompatibility
+@Version("0.0.1")
+class BackgroundPlayPatch : BytecodePatch(
+ listOf(
+ BackgroundPlaybackDisableFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ BackgroundPlaybackDisableFingerprint.result!!.mutableMethod.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+ return v0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/annotations/UnlockProCompatibility.kt
new file mode 100644
index 0000000..e4d8a13
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/annotations/UnlockProCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.myexpenses.misc.pro.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("org.totschnig.myexpenses")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class UnlockProCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/fingerprints/IsEnabledFingerprint.kt b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/fingerprints/IsEnabledFingerprint.kt
new file mode 100644
index 0000000..caebe9e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/fingerprints/IsEnabledFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.myexpenses.misc.pro.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object IsEnabledFingerprint : MethodFingerprint(
+ "Z",
+ strings = listOf("feature", "feature.licenceStatus")
+)
diff --git a/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/patch/UnlockProPatch.kt
new file mode 100644
index 0000000..ebf34e6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/patch/UnlockProPatch.kt
@@ -0,0 +1,38 @@
+package app.revanced.patches.myexpenses.misc.pro.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.myexpenses.misc.pro.annotations.UnlockProCompatibility
+import app.revanced.patches.myexpenses.misc.pro.fingerprints.IsEnabledFingerprint
+
+@Patch
+@Name("unlock-pro")
+@Description("Unlocks all professional features.")
+@UnlockProCompatibility
+@Version("0.0.1")
+class UnlockProPatch : BytecodePatch(
+ listOf(
+ IsEnabledFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = IsEnabledFingerprint.result!!.mutableMethod
+ method.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+ return v0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/nyx/misc/pro/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/annotations/UnlockProCompatibility.kt
new file mode 100644
index 0000000..39c9434
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/annotations/UnlockProCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.nyx.misc.pro.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.awedea.nyx")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class UnlockProCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/nyx/misc/pro/fingerprints/CheckProFingerprint.kt b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/fingerprints/CheckProFingerprint.kt
new file mode 100644
index 0000000..90b0af5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/fingerprints/CheckProFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.nyx.misc.pro.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CheckProFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("BillingManager;") && methodDef.name == "isProVersion"
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/nyx/misc/pro/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/patch/UnlockProPatch.kt
new file mode 100644
index 0000000..bb0ee59
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/patch/UnlockProPatch.kt
@@ -0,0 +1,38 @@
+package app.revanced.patches.nyx.misc.pro.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.nyx.misc.pro.annotations.UnlockProCompatibility
+import app.revanced.patches.nyx.misc.pro.fingerprints.CheckProFingerprint
+
+@Patch
+@Name("unlock-pro")
+@Description("Unlocks all pro features.")
+@UnlockProCompatibility
+@Version("0.0.1")
+class UnlockProPatch : BytecodePatch(
+ listOf(
+ CheckProFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = CheckProFingerprint.result!!.mutableMethod
+ method.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+ return v0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/GeneralAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/GeneralAdsCompatibility.kt
new file mode 100644
index 0000000..0666c87
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/GeneralAdsCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.reddit.ad.general.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.reddit.frontpage")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class GeneralAdsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/GeneralAdsPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/GeneralAdsPatch.kt
new file mode 100644
index 0000000..86341a6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/GeneralAdsPatch.kt
@@ -0,0 +1,54 @@
+package app.revanced.patches.reddit.ad.general.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.reddit.ad.general.annotations.GeneralAdsCompatibility
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.reference.StringReference
+import org.jf.dexlib2.immutable.reference.ImmutableStringReference
+
+@Patch
+@Name("general-reddit-ads")
+@Description("Removes general ads from the Reddit frontpage and subreddits.")
+@GeneralAdsCompatibility
+@Version("0.0.1")
+class GeneralAdsPatch : BytecodePatch() {
+ override fun execute(context: BytecodeContext): PatchResult {
+ context.classes.forEach { classDef ->
+ classDef.methods.forEach methodLoop@{ method ->
+ val implementation = method.implementation ?: return@methodLoop
+
+ implementation.instructions.forEachIndexed { i, instruction ->
+ if (instruction.opcode != Opcode.CONST_STRING) return@forEachIndexed
+ if (((instruction as ReferenceInstruction).reference as StringReference).string != "AdPost") return@forEachIndexed
+
+ val proxiedClass = context.proxy(classDef).mutableClass
+
+ val proxiedImplementation = proxiedClass.methods.first {
+ it.name == method.name && it.parameterTypes.containsAll(method.parameterTypes)
+ }.implementation!!
+
+ var newString = "AdPost1"
+ if (proxiedImplementation.instructions[i - 1].opcode == Opcode.CONST_STRING) {
+ newString = "SubredditPost"
+ }
+ proxiedImplementation.replaceInstruction(
+ i, BuilderInstruction21c(
+ Opcode.CONST_STRING, (proxiedImplementation.instructions[i] as BuilderInstruction21c).registerA, ImmutableStringReference(newString)
+ )
+ )
+ }
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/annotations/PremiumIconCompatibility.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/annotations/PremiumIconCompatibility.kt
new file mode 100644
index 0000000..11ea3c8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/annotations/PremiumIconCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.reddit.layout.premiumicon.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.reddit.frontpage")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PremiumIconCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/fingerprints/PremiumIconFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/fingerprints/PremiumIconFingerprint.kt
new file mode 100644
index 0000000..bb8bf0d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/fingerprints/PremiumIconFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.reddit.layout.premiumicon.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PremiumIconFingerprint : MethodFingerprint(
+ "Z",
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("MyAccount;") && methodDef.name == "isPremiumSubscriber"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/patch/PremiumIconPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/patch/PremiumIconPatch.kt
new file mode 100644
index 0000000..156c746
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/patch/PremiumIconPatch.kt
@@ -0,0 +1,36 @@
+package app.revanced.patches.reddit.layout.premiumicon.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.reddit.layout.premiumicon.annotations.PremiumIconCompatibility
+import app.revanced.patches.reddit.layout.premiumicon.fingerprints.PremiumIconFingerprint
+
+@Patch
+@Name("premium-icon-reddit")
+@Description("Unlocks premium Reddit app icons.")
+@PremiumIconCompatibility
+@Version("0.0.1")
+class PremiumIconPatch : BytecodePatch(
+ listOf(
+ PremiumIconFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = PremiumIconFingerprint.result!!.mutableMethod
+ method.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+ return v0
+ """
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarFingerprint.kt
new file mode 100644
index 0000000..572ec21
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.shared.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SeekbarFingerprint : MethodFingerprint(
+ "V",
+ strings = listOf("timed_markers_width")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarOnDrawFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarOnDrawFingerprint.kt
new file mode 100644
index 0000000..7afcdae
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarOnDrawFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.shared.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SeekbarOnDrawFingerprint : MethodFingerprint(
+ customFingerprint = { it.name == "onDraw" }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/fingerprints/WatchWhileActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/WatchWhileActivityFingerprint.kt
new file mode 100644
index 0000000..b10c5e2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/WatchWhileActivityFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.shared.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object WatchWhileActivityFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("WatchWhileActivity;") && methodDef.name == ""
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/shared/integrations/patch/AbstractIntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/shared/integrations/patch/AbstractIntegrationsPatch.kt
new file mode 100644
index 0000000..2ef2e48
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/integrations/patch/AbstractIntegrationsPatch.kt
@@ -0,0 +1,66 @@
+package app.revanced.patches.shared.integrations.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import org.jf.dexlib2.iface.Method
+
+@Description("Applies mandatory patches to implement the ReVanced integrations into the application.")
+@Version("0.0.1")
+abstract class AbstractIntegrationsPatch(
+ private val integrationsDescriptor: String,
+ private val hooks: Iterable
+) : BytecodePatch(hooks) {
+ /**
+ * [MethodFingerprint] for integrations.
+ *
+ * @param contextRegisterResolver A [RegisterResolver] to get the register.
+ * @see MethodFingerprint
+ */
+ abstract class IntegrationsFingerprint(
+ strings: Iterable? = null,
+ customFingerprint: ((methodDef: Method) -> Boolean)? = null,
+ private val contextRegisterResolver: (Method) -> Int = object : RegisterResolver {}
+ ) : MethodFingerprint(strings = strings, customFingerprint = customFingerprint) {
+ fun invoke(integrationsDescriptor: String): PatchResult {
+ result?.mutableMethod?.let { method ->
+ val contextRegister = contextRegisterResolver(method)
+
+ method.addInstruction(
+ 0,
+ "sput-object v$contextRegister, " +
+ "$integrationsDescriptor->context:Landroid/content/Context;"
+ )
+ } ?: return PatchResultError("Could not find hook target fingerprint.")
+
+ return PatchResultSuccess()
+ }
+
+ interface RegisterResolver : (Method) -> Int {
+ override operator fun invoke(method: Method) = method.implementation!!.registerCount - 1
+ }
+ }
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ if (context.findClass(integrationsDescriptor) == null) return MISSING_INTEGRATIONS
+
+ for (hook in hooks) hook.invoke(integrationsDescriptor).let {
+ if (it is PatchResultError) return it
+ }
+
+ return PatchResultSuccess()
+ }
+
+ private companion object {
+ val MISSING_INTEGRATIONS = PatchResultError(
+ "Integrations have not been merged yet. " +
+ "This patch can not succeed without merging the integrations."
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/mapping/misc/patch/ResourceMappingPatch.kt b/src/main/kotlin/app/revanced/patches/shared/mapping/misc/patch/ResourceMappingPatch.kt
new file mode 100644
index 0000000..b607dc2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/mapping/misc/patch/ResourceMappingPatch.kt
@@ -0,0 +1,73 @@
+package app.revanced.patches.shared.mapping.misc.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import org.w3c.dom.Element
+import java.util.*
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+
+@Name("resource-mapping")
+@Description("Creates a map of public resources.")
+@Version("0.0.1")
+class ResourceMappingPatch : ResourcePatch {
+ companion object {
+ internal lateinit var resourceMappings: List
+ private set
+
+ private val THREAD_COUNT = Runtime.getRuntime().availableProcessors()
+ private val threadPoolExecutor = Executors.newFixedThreadPool(THREAD_COUNT)
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ // save the file in memory to concurrently read from
+ val resourceXmlFile = context["res/values/public.xml"].readBytes()
+
+ // create a synchronized list to store the resource mappings
+ val mappings = Collections.synchronizedList(mutableListOf())
+
+ for (threadIndex in 0 until THREAD_COUNT) {
+ threadPoolExecutor.execute thread@{
+ context.xmlEditor[resourceXmlFile.inputStream()].use { editor ->
+ val resources = editor.file.documentElement.childNodes
+ val resourcesLength = resources.length
+ val jobSize = resourcesLength / THREAD_COUNT
+
+ val batchStart = jobSize * threadIndex
+ val batchEnd = jobSize * (threadIndex + 1)
+ element@ for (i in batchStart until batchEnd) {
+ // make sure to not to go out of bounds when rounding errors occur at calculating the jobSize
+ if (i >= resourcesLength) return@thread
+
+ val node = resources.item(i)
+ if (node !is Element) continue
+
+ val nameAttribute = node.getAttribute("name")
+ val typeAttribute = node.getAttribute("type")
+
+ if (node.nodeName != "public" || nameAttribute.startsWith("APKTOOL")) continue
+
+ val id = node.getAttribute("id").substring(2).toLong(16)
+ mappings.add(ResourceElement(typeAttribute, nameAttribute, id))
+ }
+ }
+ }
+ }
+
+ threadPoolExecutor
+ .also { it.shutdown() }
+ .awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS)
+
+ resourceMappings = mappings
+
+ return PatchResultSuccess()
+ }
+}
+
+data class ResourceElement(val type: String, val name: String, val id: Long)
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt
new file mode 100644
index 0000000..1853a97
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt
@@ -0,0 +1,31 @@
+package app.revanced.patches.shared.settings.preference
+
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Base preference class for all preferences.
+ *
+ * @param key The key of the preference.
+ * @param title The title of the preference.
+ */
+internal abstract class BasePreference(
+ override val key: String,
+ override val title: StringResource,
+) : IPreference {
+
+ /**
+ * Serialize preference element to XML.
+ * Overriding methods should invoke super and operate on its return value.
+ * @param ownerDocument Target document to create elements from.
+ * @param resourceCallback Called when a resource has been processed.
+ */
+ open fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)? = null): Element {
+ return ownerDocument.createElement(tag).apply {
+ if(key.isNotEmpty())
+ setAttribute("android:key", key)
+ setAttribute("android:title", "@string/${title.also { resourceCallback?.invoke(it) }.name}")
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/BaseResource.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BaseResource.kt
new file mode 100644
index 0000000..5c5e417
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BaseResource.kt
@@ -0,0 +1,26 @@
+package app.revanced.patches.shared.settings.preference
+
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Base resource class for all resources.
+ *
+ * @param name The name of the resource.
+ */
+internal abstract class BaseResource(
+ override val name: String
+) : IResource {
+
+ /**
+ * Serialize resource element to XML.
+ * Overriding methods should invoke super and operate on its return value.
+ * @param ownerDocument Target document to create elements from.
+ * @param resourceCallback Called when a resource has been processed.
+ */
+ open fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)? = null): Element {
+ return ownerDocument.createElement(tag).apply {
+ setAttribute("name", name)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/ComponentsExtensions.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/ComponentsExtensions.kt
new file mode 100644
index 0000000..b0e8ddb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/ComponentsExtensions.kt
@@ -0,0 +1,42 @@
+package app.revanced.patches.shared.settings.preference
+
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+
+/**
+ * Add a resource node child
+ *
+ * @param resource The resource to add.
+ * @param resourceCallback Called when a resource has been processed.
+ */
+internal fun Node.addResource(resource: BaseResource, resourceCallback: ((IResource) -> Unit)? = null) {
+ appendChild(resource.serialize(ownerDocument, resourceCallback))
+}
+
+/**
+ * Add a preference node child to the settings.
+ *
+ * @param preference The preference to add.
+ * @param resourceCallback Called when a resource has been processed.
+ */
+internal fun Node.addPreference(preference: BasePreference, resourceCallback: ((IResource) -> Unit)? = null) {
+ appendChild(preference.serialize(ownerDocument, resourceCallback))
+}
+
+internal fun Element.addSummary(summaryResource: StringResource?, summaryType: SummaryType = SummaryType.DEFAULT) =
+ summaryResource?.let { summary ->
+ setAttribute("android:${summaryType.type}", "@string/${summary.name}")
+ }
+
+internal fun Element.addDefault(default: T) {
+ default?.let {
+ setAttribute(
+ "android:defaultValue", when (it) {
+ is Boolean -> if (it) "true" else "false"
+ is String -> it
+ else -> throw IllegalArgumentException("Unsupported default value type: ${it::class.java.name}")
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/IPreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/IPreference.kt
new file mode 100644
index 0000000..ec31835
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/IPreference.kt
@@ -0,0 +1,23 @@
+package app.revanced.patches.shared.settings.preference
+
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+
+/**
+ * Preference
+ */
+internal interface IPreference {
+ /**
+ * Key of the preference.
+ */
+ val key: String
+
+ /**
+ * Title of the preference.
+ */
+ val title: StringResource
+
+ /**
+ * Tag name of the preference.
+ */
+ val tag: String
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/IResource.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/IResource.kt
new file mode 100644
index 0000000..dc3fe61
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/IResource.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.shared.settings.preference
+
+/**
+ * Resource
+ */
+internal interface IResource {
+ /**
+ * Name of the resource.
+ */
+ val name: String
+
+ /**
+ * Tag name of the resource.
+ */
+ val tag: String
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/SummaryType.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/SummaryType.kt
new file mode 100644
index 0000000..006f698
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/SummaryType.kt
@@ -0,0 +1,5 @@
+package app.revanced.patches.shared.settings.preference
+
+enum class SummaryType(val type: String) {
+ DEFAULT("summary"), ON("summaryOn"), OFF("summaryOff")
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ArrayResource.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ArrayResource.kt
new file mode 100644
index 0000000..608dccc
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ArrayResource.kt
@@ -0,0 +1,33 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+import app.revanced.patches.shared.settings.preference.BaseResource
+import app.revanced.patches.shared.settings.preference.IResource
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Represents an array resource.
+ *
+ * @param name The name of the array resource.
+ * @param items The items of the array resource.
+ */
+internal data class ArrayResource(
+ override val name: String,
+ val items: List
+) : BaseResource(name) {
+ override val tag = "string-array"
+
+ override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element {
+ return super.serialize(ownerDocument, resourceCallback).apply {
+ setAttribute("name", name)
+
+ items.forEach { item ->
+ resourceCallback?.invoke(item)
+
+ this.appendChild(ownerDocument.createElement("item").also { itemNode ->
+ itemNode.textContent = "@string/${item.name}"
+ })
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/InputType.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/InputType.kt
new file mode 100644
index 0000000..0943214
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/InputType.kt
@@ -0,0 +1,6 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+enum class InputType(val type: String) {
+ STRING("text"),
+ NUMBER("number"),
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ListPreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ListPreference.kt
new file mode 100644
index 0000000..9f2f9f8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ListPreference.kt
@@ -0,0 +1,38 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.IResource
+import app.revanced.patches.shared.settings.preference.addDefault
+import app.revanced.patches.shared.settings.preference.addSummary
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * List preference.
+ *
+ * @param key The key of the list preference.
+ * @param title The title of the list preference.
+ * @param entries The human-readable entries of the list preference.
+ * @param entryValues The entry values of the list preference.
+ * @param default The default entry value of the list preference.
+ * @param summary The summary of the list preference.
+ */
+internal class ListPreference(
+ key: String,
+ title: StringResource,
+ val entries: ArrayResource,
+ val entryValues: ArrayResource,
+ val default: String? = null,
+ val summary: StringResource? = null
+) : BasePreference(key, title) {
+ override val tag: String = "ListPreference"
+
+ override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element {
+ return super.serialize(ownerDocument, resourceCallback).apply {
+ setAttribute("android:entries", "@array/${entries.also { resourceCallback?.invoke(it) }.name}")
+ setAttribute("android:entryValues", "@array/${entryValues.also { resourceCallback?.invoke(it) }.name}")
+ addDefault(default)
+ addSummary(summary)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/Preference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/Preference.kt
new file mode 100644
index 0000000..fcacd61
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/Preference.kt
@@ -0,0 +1,44 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.IResource
+import app.revanced.patches.shared.settings.preference.addSummary
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * A Preference object.
+ *
+ * @param title The title of the preference.
+ * @param intent The intent of the preference.
+ * @param summary The summary of the text preference.
+ */
+internal class Preference(
+ key: String,
+ title: StringResource,
+ val intent: Intent,
+ val summary: StringResource? = null
+) : BasePreference(key, title) {
+ override val tag: String = "Preference"
+
+ /* Key-less constructor */
+ constructor(
+ title: StringResource,
+ intent: Intent,
+ summary: StringResource? = null
+ ) : this("", title, intent, summary)
+
+ override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element {
+ return super.serialize(ownerDocument, resourceCallback).apply {
+ addSummary(summary?.also { resourceCallback?.invoke(it) })
+
+ this.appendChild(ownerDocument.createElement("intent").also { intentNode ->
+ intentNode.setAttribute("android:targetPackage", intent.targetPackage)
+ intentNode.setAttribute("android:data", intent.data)
+ intentNode.setAttribute("android:targetClass", intent.targetClass)
+ })
+ }
+ }
+
+ data class Intent(val targetPackage: String, val data: String, val targetClass: String)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceCategory.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceCategory.kt
new file mode 100644
index 0000000..f575dfe
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceCategory.kt
@@ -0,0 +1,29 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.IResource
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Preference category.
+ *
+ * @param key The key of the preference.
+ * @param title The title of the preference.
+ * @param preferences Child preferences of this category.
+ */
+internal open class PreferenceCategory(
+ key: String,
+ title: StringResource,
+ var preferences: List
+) : BasePreference(key, title) {
+ override val tag: String = "PreferenceCategory"
+
+ override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element {
+ return super.serialize(ownerDocument, resourceCallback).apply {
+ for (childPreference in preferences) {
+ this.appendChild(childPreference.serialize(ownerDocument, resourceCallback))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceScreen.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceScreen.kt
new file mode 100644
index 0000000..b7c82ba
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceScreen.kt
@@ -0,0 +1,34 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.IResource
+import app.revanced.patches.shared.settings.preference.addSummary
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Preference screen.
+ *
+ * @param key The key of the preference.
+ * @param title The title of the preference.
+ * @param preferences Child preferences of this screen.
+ * @param summary The summary of the text preference.
+ */
+internal open class PreferenceScreen(
+ key: String,
+ title: StringResource,
+ var preferences: List,
+ val summary: StringResource? = null
+) : BasePreference(key, title) {
+ override val tag: String = "PreferenceScreen"
+
+ override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element {
+ return super.serialize(ownerDocument, resourceCallback).apply {
+ addSummary(summary?.also { resourceCallback?.invoke(it) })
+
+ for (childPreference in preferences) {
+ this.appendChild(childPreference.serialize(ownerDocument, resourceCallback))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/StringResource.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/StringResource.kt
new file mode 100644
index 0000000..49471fa
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/StringResource.kt
@@ -0,0 +1,31 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+import app.revanced.patches.shared.settings.preference.BaseResource
+import app.revanced.patches.shared.settings.preference.IResource
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Represents a string value in the strings.xml file
+ *
+ * @param name The name of the string
+ * @param value The value of the string
+ * @param formatted If the string is formatted. If false, the attribute will be set
+ */
+internal data class StringResource(
+ override val name: String,
+ val value: String,
+ val formatted: Boolean = true
+) : BaseResource(name) {
+ override val tag = "string"
+
+ override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element {
+ return super.serialize(ownerDocument, resourceCallback).apply {
+ // if the string is un-formatted, explicitly add the formatted attribute
+ if (!formatted)
+ setAttribute("formatted", "false")
+
+ textContent = value
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/SwitchPreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/SwitchPreference.kt
new file mode 100644
index 0000000..78d9f1d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/SwitchPreference.kt
@@ -0,0 +1,35 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.IResource
+import app.revanced.patches.shared.settings.preference.addDefault
+import app.revanced.patches.shared.settings.preference.addSummary
+import app.revanced.patches.shared.settings.preference.SummaryType
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Switch preference.
+ *
+ * @param key The key of the switch.
+ * @param title The title of the switch.
+ * @param default The default value of the switch.
+ * @param summaryOn The summary to show when the preference is enabled.
+ * @param summaryOff The summary to show when the preference is disabled.
+ */
+internal class SwitchPreference(
+ key: String, title: StringResource,
+ val default: Boolean = false,
+ val summaryOn: StringResource? = null,
+ val summaryOff: StringResource? = null
+) : BasePreference(key, title) {
+ override val tag: String = "SwitchPreference"
+
+ override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element {
+ return super.serialize(ownerDocument, resourceCallback).apply {
+ addDefault(default)
+ addSummary(summaryOn?.also { resourceCallback?.invoke(it) }, SummaryType.ON)
+ addSummary(summaryOff?.also { resourceCallback?.invoke(it) }, SummaryType.OFF)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/TextPreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/TextPreference.kt
new file mode 100644
index 0000000..907a4a1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/TextPreference.kt
@@ -0,0 +1,35 @@
+package app.revanced.patches.shared.settings.preference.impl
+
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.IResource
+import app.revanced.patches.shared.settings.preference.addDefault
+import app.revanced.patches.shared.settings.preference.addSummary
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Text preference.
+ *
+ * @param key The key of the text preference.
+ * @param title The title of the text preference.
+ * @param inputType The input type of the text preference.
+ * @param default The default value of the text preference.
+ * @param summary The summary of the text preference.
+ */
+internal class TextPreference(
+ key: String,
+ title: StringResource,
+ var inputType: InputType = InputType.STRING,
+ val default: String? = null,
+ val summary: StringResource? = null
+) : BasePreference(key, title) {
+ override val tag: String = "EditTextPreference"
+
+ override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element {
+ return super.serialize(ownerDocument, resourceCallback).apply {
+ setAttribute("android:inputType", inputType.type)
+ addDefault(default)
+ addSummary(summary?.also { resourceCallback?.invoke(it) })
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/resource/patch/AbstractSettingsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/shared/settings/resource/patch/AbstractSettingsResourcePatch.kt
new file mode 100644
index 0000000..f62c24b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/resource/patch/AbstractSettingsResourcePatch.kt
@@ -0,0 +1,135 @@
+package app.revanced.patches.shared.settings.resource.patch
+
+import app.revanced.patcher.data.DomFileEditor
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.IResource
+import app.revanced.patches.shared.settings.preference.addPreference
+import app.revanced.patches.shared.settings.preference.addResource
+import app.revanced.patches.shared.settings.preference.impl.ArrayResource
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.util.resources.ResourceUtils
+import app.revanced.util.resources.ResourceUtils.copyResources
+import org.w3c.dom.Node
+
+/**
+ * Abstract settings resource patch
+ *
+ * @param preferenceFileName Name of the settings preference xml file
+ * @param sourceDirectory Source directory to copy the preference template from
+ */
+abstract class AbstractSettingsResourcePatch(
+ private val preferenceFileName: String,
+ private val sourceDirectory: String,
+) : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ /*
+ * used for self-restart
+ */
+ context.xmlEditor["AndroidManifest.xml"].use { editor ->
+ editor.file.getElementsByTagName("manifest").item(0).also {
+ it.appendChild(it.ownerDocument.createElement("uses-permission").also { element ->
+ element.setAttribute("android:name", "android.permission.SCHEDULE_EXACT_ALARM")
+ })
+ }
+ }
+
+ /* copy preference template from source dir */
+ context.copyResources(
+ sourceDirectory,
+ ResourceUtils.ResourceGroup(
+ "xml", "$preferenceFileName.xml"
+ )
+ )
+
+ /* prepare xml editors */
+ stringsEditor = context.xmlEditor["res/values/strings.xml"]
+ arraysEditor = context.xmlEditor["res/values/arrays.xml"]
+ revancedPreferencesEditor = context.xmlEditor["res/xml/$preferenceFileName.xml"]
+
+ return PatchResultSuccess()
+ }
+
+ internal companion object {
+ private var revancedPreferenceNode: Node? = null
+ private var stringsNode: Node? = null
+ private var arraysNode: Node? = null
+
+ private var strings = mutableListOf()
+
+ private var revancedPreferencesEditor: DomFileEditor? = null
+ set(value) {
+ field = value
+ revancedPreferenceNode = value.getNode("PreferenceScreen")
+ }
+ private var stringsEditor: DomFileEditor? = null
+ set(value) {
+ field = value
+ stringsNode = value.getNode("resources")
+ }
+ private var arraysEditor: DomFileEditor? = null
+ set(value) {
+ field = value
+ arraysNode = value.getNode("resources")
+ }
+
+ /**
+ * Add a new string to the resources.
+ *
+ * @param identifier The key of the string.
+ * @param value The value of the string.
+ * @throws IllegalArgumentException if the string already exists.
+ */
+ fun addString(identifier: String, value: String, formatted: Boolean) =
+ StringResource(identifier, value, formatted).include()
+
+ /**
+ * Add an array to the resources.
+ *
+ * @param arrayResource The array resource to add.
+ */
+ fun addArray(arrayResource: ArrayResource) =
+ arraysNode!!.addResource(arrayResource) { it.include() }
+
+ /**
+ * Add a preference to the settings.
+ *
+ * @param preference The preference to add.
+ */
+ fun addPreference(preference: BasePreference) =
+ revancedPreferenceNode!!.addPreference(preference) { it.include() }
+
+ /**
+ * Add a new resource to the resources.
+ *
+ * @throws IllegalArgumentException if the resource already exists.
+ */
+ internal fun IResource.include() {
+ when (this) {
+ is StringResource -> {
+ if (strings.any { it.name == name }) return
+ strings.add(this)
+ }
+
+ is ArrayResource -> addArray(this)
+ else -> throw NotImplementedError("Unsupported resource type")
+ }
+ }
+
+ internal fun DomFileEditor?.getNode(tagName: String) = this!!.file.getElementsByTagName(tagName).item(0)
+ }
+
+ override fun close() {
+ // merge all strings, skip duplicates
+ strings.forEach {
+ stringsNode!!.addResource(it)
+ }
+
+ revancedPreferencesEditor?.close()
+ stringsEditor?.close()
+ arraysEditor?.close()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/util/AbstractPreferenceScreen.kt b/src/main/kotlin/app/revanced/patches/shared/settings/util/AbstractPreferenceScreen.kt
new file mode 100644
index 0000000..a257af9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/settings/util/AbstractPreferenceScreen.kt
@@ -0,0 +1,89 @@
+package app.revanced.patches.shared.settings.util
+
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.impl.PreferenceCategory
+import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import java.io.Closeable
+
+internal abstract class AbstractPreferenceScreen(
+ private val root: MutableList = mutableListOf()
+) : Closeable {
+
+ override fun close() {
+ if (root.isEmpty())
+ return
+
+ for (preference in root.sortedBy { it.title }) {
+ commit(preference.transform())
+ }
+ }
+
+ /**
+ * Finalize and insert root preference into resource patch
+ */
+ abstract fun commit(screen: PreferenceScreen)
+
+ open inner class Screen(
+ key: String,
+ title: String,
+ val summary: String? = null,
+ preferences: MutableList = mutableListOf(),
+ val categories: MutableList = mutableListOf()
+ ) : BasePreferenceCollection(key, title, preferences) {
+ override fun transform(): PreferenceScreen {
+ return PreferenceScreen(
+ key,
+ StringResource("${key}_title", title),
+ preferences.sortedBy { it.title.value } +
+ categories.sortedBy { it.title }.map { it.transform() },
+ summary?.let { summary ->
+ StringResource("${key}_summary", summary)
+ }
+ )
+ }
+
+ private fun ensureScreenInserted() {
+ // Add to screens if not yet done
+ if(!this@AbstractPreferenceScreen.root.contains(this))
+ this@AbstractPreferenceScreen.root.add(this)
+ }
+
+ fun addPreferences(vararg preferences: BasePreference) {
+ ensureScreenInserted()
+ this.preferences.addAll(preferences)
+ }
+
+ open inner class Category(
+ key: String,
+ title: String,
+ preferences: MutableList = mutableListOf()
+ ): BasePreferenceCollection(key, title, preferences) {
+ override fun transform(): PreferenceCategory {
+ return PreferenceCategory(
+ key,
+ StringResource("${key}_title", title),
+ preferences.sortedBy { it.title.value }
+ )
+ }
+
+ fun addPreferences(vararg preferences: BasePreference) {
+ ensureScreenInserted()
+
+ // Add to categories if not yet done
+ if(!this@Screen.categories.contains(this))
+ this@Screen.categories.add(this)
+
+ this.preferences.addAll(preferences)
+ }
+ }
+ }
+
+ abstract class BasePreferenceCollection(
+ val key: String,
+ val title: String,
+ val preferences: MutableList = mutableListOf()
+ ) {
+ abstract fun transform(): BasePreference
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/spotify/audio/annotation/DisableCaptureRestrictionCompatibility.kt b/src/main/kotlin/app/revanced/patches/spotify/audio/annotation/DisableCaptureRestrictionCompatibility.kt
new file mode 100644
index 0000000..3aad515
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/audio/annotation/DisableCaptureRestrictionCompatibility.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.spotify.audio.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package("com.spotify.music")]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class DisableCaptureRestrictionCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/spotify/audio/bytecode/patch/DisableCaptureRestrictionBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/spotify/audio/bytecode/patch/DisableCaptureRestrictionBytecodePatch.kt
new file mode 100644
index 0000000..88aae6a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/audio/bytecode/patch/DisableCaptureRestrictionBytecodePatch.kt
@@ -0,0 +1,86 @@
+package app.revanced.patches.spotify.audio.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.spotify.audio.annotation.DisableCaptureRestrictionCompatibility
+import app.revanced.patches.spotify.audio.fingerprints.DisableCaptureRestrictionAudioDriverFingerprint
+import app.revanced.patches.spotify.audio.resource.patch.DisableCaptureRestrictionResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.reference.MethodReference
+
+@Patch
+@Name("disable-capture-restriction")
+@DependsOn([DisableCaptureRestrictionResourcePatch::class])
+@Description("Allows capturing Spotify's audio output while screen sharing or screen recording.")
+@DisableCaptureRestrictionCompatibility
+@Version("0.0.2")
+class DisableCaptureRestrictionBytecodePatch : BytecodePatch(
+ listOf(
+ DisableCaptureRestrictionAudioDriverFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = DisableCaptureRestrictionAudioDriverFingerprint.result!!.mutableMethod
+
+ var invokePosition: Int? = null
+ var invokeParamRegister: Int? = null
+
+ // Find INVOKE_VIRTUAL opcode with call to AudioAttributesBuilder.setAllowedCapturePolicy(I)
+ for ((index, instruction) in method.implementation!!.instructions.withIndex()) {
+ if(instruction.opcode != Opcode.INVOKE_VIRTUAL)
+ continue
+
+ val methodName = ((instruction as ReferenceInstruction).reference as MethodReference).name
+ if (methodName != "setAllowedCapturePolicy")
+ continue
+
+ // Store register of the integer parameter for setAllowedCapturePolicy
+ invokeParamRegister = (instruction as FiveRegisterInstruction).registerD
+ invokePosition = index
+ }
+
+ if(invokePosition == null || invokeParamRegister == null)
+ return PatchResultError("Cannot find setAllowedCapturePolicy method call")
+
+ // Walk back to the const/4 instruction that sets the parameter register
+ var matchFound = false
+ for (index in invokePosition downTo 0) {
+ val instruction = method.instruction(index)
+ if(instruction.opcode != Opcode.CONST_4)
+ continue
+
+ val register = (instruction as OneRegisterInstruction).registerA
+ if(register != invokeParamRegister)
+ continue
+
+ // Replace parameter value
+ method.replaceInstruction(
+ index, "const/4 v$register, $ALLOW_CAPTURE_BY_ALL"
+ )
+ matchFound = true
+ break
+ }
+
+ return if (matchFound)
+ PatchResultSuccess()
+ else
+ PatchResultError("Const instruction not found")
+ }
+
+ private companion object {
+ const val ALLOW_CAPTURE_BY_ALL = 0x01
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/spotify/audio/fingerprints/DisableCaptureRestrictionAudioDriverFingerprint.kt b/src/main/kotlin/app/revanced/patches/spotify/audio/fingerprints/DisableCaptureRestrictionAudioDriverFingerprint.kt
new file mode 100644
index 0000000..1917555
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/audio/fingerprints/DisableCaptureRestrictionAudioDriverFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.spotify.audio.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object DisableCaptureRestrictionAudioDriverFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass == "Lcom/spotify/playback/playbacknative/AudioDriver;" && methodDef.name == "constructAudioAttributes"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/spotify/audio/resource/patch/DisableCaptureRestrictionResourcePatch.kt b/src/main/kotlin/app/revanced/patches/spotify/audio/resource/patch/DisableCaptureRestrictionResourcePatch.kt
new file mode 100644
index 0000000..5088cce
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/audio/resource/patch/DisableCaptureRestrictionResourcePatch.kt
@@ -0,0 +1,33 @@
+package app.revanced.patches.spotify.audio.resource.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patches.spotify.audio.annotation.DisableCaptureRestrictionCompatibility
+import org.w3c.dom.Element
+
+@Name("disable-capture-restriction-resource-patch")
+@Description("Sets allowAudioPlaybackCapture in manifest to true.")
+@DisableCaptureRestrictionCompatibility
+@Version("0.0.2")
+class DisableCaptureRestrictionResourcePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ // create an xml editor instance
+ context.xmlEditor["AndroidManifest.xml"].use { dom ->
+ // get the application node
+ val applicationNode = dom
+ .file
+ .getElementsByTagName("application")
+ .item(0) as Element
+
+ // set allowAudioPlaybackCapture attribute to true
+ applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true")
+ }
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/spotify/layout/theme/annotations/ThemeCompatibility.kt b/src/main/kotlin/app/revanced/patches/spotify/layout/theme/annotations/ThemeCompatibility.kt
new file mode 100644
index 0000000..bc24c39
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/layout/theme/annotations/ThemeCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.spotify.layout.theme.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.spotify.music")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ThemeCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/spotify/layout/theme/patch/ThemePatch.kt b/src/main/kotlin/app/revanced/patches/spotify/layout/theme/patch/ThemePatch.kt
new file mode 100644
index 0000000..dfbc7fd
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/layout/theme/patch/ThemePatch.kt
@@ -0,0 +1,66 @@
+package app.revanced.patches.spotify.layout.theme.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.*
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.spotify.layout.theme.annotations.ThemeCompatibility
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import org.w3c.dom.Element
+
+@Patch
+@DependsOn([FixLocaleConfigErrorPatch::class])
+@Name("spotify-theme")
+@Description("Applies a custom theme.")
+@ThemeCompatibility
+@Version("0.0.1")
+class ThemePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ context.xmlEditor["res/values/colors.xml"].use { editor ->
+ val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element
+
+ for (i in 0 until resourcesNode.childNodes.length) {
+ val node = resourcesNode.childNodes.item(i) as? Element ?: continue
+
+ node.textContent = when (node.getAttribute("name")) {
+ "gray_7" -> backgroundColor!!
+ "dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor!!
+ "dark_brightaccent_background_press" -> accentPressedColor!!
+ else -> continue
+ }
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+
+ companion object : OptionsContainer() {
+ var backgroundColor: String? by option(
+ PatchOption.StringOption(
+ key = "backgroundColor",
+ default = "@android:color/black",
+ title = "Background color",
+ description = "The background color. Can be a hex color or a resource reference.",
+ )
+ )
+ var accentColor: String? by option(
+ PatchOption.StringOption(
+ key = "accentColor",
+ default = "#ff1ed760",
+ title = "Accent color",
+ description = "The accent color ('spotify green' by default). Can be a hex color or a resource reference.",
+ )
+ )
+ var accentPressedColor: String? by option(
+ PatchOption.StringOption(
+ key = "accentPressedColor",
+ default = "#ff169c46",
+ title = "Pressed accent for the dark theme",
+ description = "The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.",
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/annotations/PremiumNavbarTabCompatibility.kt b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/annotations/PremiumNavbarTabCompatibility.kt
new file mode 100644
index 0000000..41f6531
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/annotations/PremiumNavbarTabCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.spotify.premium_navbar_tab.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.spotify.music")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PremiumNavbarTabCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabFingerprint.kt b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabFingerprint.kt
new file mode 100644
index 0000000..296c87f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabFingerprint.kt
@@ -0,0 +1,7 @@
+package app.revanced.patches.spotify.premium_navbar_tab.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object AddPremiumNavbarTabFingerprint : MethodFingerprint(
+ parameters = listOf("L", "L", "L", "L", "L", "L")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabParentFingerprint.kt
new file mode 100644
index 0000000..e67bd8f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabParentFingerprint.kt
@@ -0,0 +1,7 @@
+package app.revanced.patches.spotify.premium_navbar_tab.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object AddPremiumNavbarTabParentFingerprint : MethodFingerprint(
+ strings = listOf("com.samsung.android.samsungaccount.action.REQUEST_AUTHCODE")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/patch/PremiumNavbarTabPatch.kt b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/patch/PremiumNavbarTabPatch.kt
new file mode 100644
index 0000000..e45bb6f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/patch/PremiumNavbarTabPatch.kt
@@ -0,0 +1,63 @@
+package app.revanced.patches.spotify.premium_navbar_tab.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.spotify.premium_navbar_tab.annotations.PremiumNavbarTabCompatibility
+import app.revanced.patches.spotify.premium_navbar_tab.fingerprints.AddPremiumNavbarTabFingerprint
+import app.revanced.patches.spotify.premium_navbar_tab.fingerprints.AddPremiumNavbarTabParentFingerprint
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+@Patch
+@Name("hide-premium-navbar")
+@Description("Removes the premium tab from the navbar.")
+@PremiumNavbarTabCompatibility
+@Version("0.0.1")
+@DependsOn([ResourceMappingPatch::class])
+class PremiumNavbarTabPatch : BytecodePatch(
+ listOf(
+ AddPremiumNavbarTabParentFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val parentResult = AddPremiumNavbarTabParentFingerprint.result!!
+ AddPremiumNavbarTabFingerprint.resolve(context, parentResult.classDef)
+
+ val result = AddPremiumNavbarTabFingerprint.result!!
+
+ val method = result.mutableMethod
+ val methodInstructions = method.implementation!!.instructions
+ val lastInstructionIdx = methodInstructions.size - 1
+
+ val premiumTabId =
+ ResourceMappingPatch.resourceMappings.single { it.type == "id" && it.name == "premium_tab" }.id
+
+ var removeAmount = 2
+ // 2nd const remove method
+ for ((i, instruction) in methodInstructions.asReversed().withIndex()) {
+ if (instruction.opcode.ordinal != Opcode.CONST.ordinal) continue
+ if ((instruction as WideLiteralInstruction).wideLiteral != premiumTabId) continue
+
+ val findThreshold = 10
+ val constIndex = lastInstructionIdx - i
+ val invokeInstruction = methodInstructions.subList(constIndex, constIndex + findThreshold).first {
+ it.opcode.ordinal == Opcode.INVOKE_VIRTUAL_RANGE.ordinal
+ }
+ method.removeInstruction(methodInstructions.indexOf(invokeInstruction))
+
+ if (--removeAmount == 0) break
+ }
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/annotations/UnlockThemesCompatibility.kt b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/annotations/UnlockThemesCompatibility.kt
new file mode 100644
index 0000000..cc09664
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/annotations/UnlockThemesCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.ticktick.misc.themeunlock.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.ticktick.task")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class UnlockThemesCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/CheckLockedThemesFingerprint.kt b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/CheckLockedThemesFingerprint.kt
new file mode 100644
index 0000000..dccab40
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/CheckLockedThemesFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.ticktick.misc.themeunlock.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CheckLockedThemesFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("Theme;") && methodDef.name == "isLockedTheme"
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/SetThemeFingerprint.kt b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/SetThemeFingerprint.kt
new file mode 100644
index 0000000..dc22b02
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/SetThemeFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.ticktick.misc.themeunlock.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SetThemeFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("ThemePreviewActivity;") && methodDef.name == "lambda\$updateUserBtn\$1"
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/patch/UnlockThemePatch.kt b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/patch/UnlockThemePatch.kt
new file mode 100644
index 0000000..e1b6664
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/patch/UnlockThemePatch.kt
@@ -0,0 +1,43 @@
+package app.revanced.patches.ticktick.misc.themeunlock.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.removeInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.ticktick.misc.themeunlock.annotations.UnlockThemesCompatibility
+import app.revanced.patches.ticktick.misc.themeunlock.fingerprints.CheckLockedThemesFingerprint
+import app.revanced.patches.ticktick.misc.themeunlock.fingerprints.SetThemeFingerprint
+
+@Patch
+@Name("unlock-themes")
+@Description("Unlocks all themes.")
+@UnlockThemesCompatibility
+@Version("0.0.1")
+class UnlockProPatch : BytecodePatch(
+ listOf(
+ CheckLockedThemesFingerprint,
+ SetThemeFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val lockedThemesMethod = CheckLockedThemesFingerprint.result!!.mutableMethod
+ lockedThemesMethod.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x0
+ return v0
+ """
+ )
+
+ val setThemeMethod = SetThemeFingerprint.result!!.mutableMethod
+ setThemeMethod.removeInstructions(0, 9)
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/ad/annotations/HideAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/ad/annotations/HideAdsCompatibility.kt
new file mode 100644
index 0000000..529acaf
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/ad/annotations/HideAdsCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.ad.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideAdsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/ConvertHelpFeedItemListFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/ConvertHelpFeedItemListFingerprint.kt
new file mode 100644
index 0000000..fc252a2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/ConvertHelpFeedItemListFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.tiktok.ad.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object ConvertHelpFeedItemListFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/ConvertHelp;") &&
+ methodDef.name.endsWith("${'$'}FeedItemList")
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/FeedItemListCloneFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/FeedItemListCloneFingerprint.kt
new file mode 100644
index 0000000..4f9de5d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/FeedItemListCloneFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.tiktok.ad.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object FeedItemListCloneFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/FeedItemList;") && methodDef.name == "clone"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/ad/patch/HideAdsPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/ad/patch/HideAdsPatch.kt
new file mode 100644
index 0000000..862f805
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/ad/patch/HideAdsPatch.kt
@@ -0,0 +1,57 @@
+package app.revanced.patches.tiktok.ad.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.tiktok.ad.annotations.HideAdsCompatibility
+import app.revanced.patches.tiktok.ad.fingerprints.ConvertHelpFeedItemListFingerprint
+import app.revanced.patches.tiktok.ad.fingerprints.FeedItemListCloneFingerprint
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction
+import org.jf.dexlib2.iface.reference.FieldReference
+
+@Patch
+@Name("hide-ads")
+@Description("Removes ads from TikTok.")
+@HideAdsCompatibility
+@Version("0.0.1")
+class HideAdsPatch : BytecodePatch(
+ listOf(
+ FeedItemListCloneFingerprint,
+ ConvertHelpFeedItemListFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ listOf(
+ FeedItemListCloneFingerprint,
+ ConvertHelpFeedItemListFingerprint
+ ).forEach { fingerprint ->
+ val method = fingerprint.result!!.mutableMethod
+ // iterate all instructions in the clone method
+ for ((index, instruction) in method.implementation!!.instructions.withIndex()) {
+ // conditions for the instruction we need
+ if (instruction.opcode.ordinal != Opcode.IPUT_OBJECT.ordinal) continue
+ val preloadAdsFieldInstruction = (instruction as? ReferenceInstruction)
+ if ((preloadAdsFieldInstruction?.reference as? FieldReference)?.name != "preloadAds") continue
+
+ // set null instead of the field "preloadAds"
+ val overrideRegister = (preloadAdsFieldInstruction as TwoRegisterInstruction).registerA
+ method.addInstruction(
+ index,
+ "const/4 v$overrideRegister, 0x0"
+ )
+ return@forEach
+ }
+ return PatchResultError("Can not find required instruction.")
+ }
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/annotations/FeedFilterCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/annotations/FeedFilterCompatibility.kt
new file mode 100644
index 0000000..d33f698
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/annotations/FeedFilterCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.feedfilter.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class FeedFilterCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/fingerprints/FeedApiServiceLIZFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/fingerprints/FeedApiServiceLIZFingerprint.kt
new file mode 100644
index 0000000..e2f05c0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/fingerprints/FeedApiServiceLIZFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.tiktok.feedfilter.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object FeedApiServiceLIZFingerprint : MethodFingerprint(
+ access = AccessFlags.PUBLIC or AccessFlags.STATIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC,
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/FeedApiService;") && methodDef.name == "LIZ"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/patch/FeedFilterPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/patch/FeedFilterPatch.kt
new file mode 100644
index 0000000..fb6ba0b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/patch/FeedFilterPatch.kt
@@ -0,0 +1,51 @@
+package app.revanced.patches.tiktok.feedfilter.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.tiktok.feedfilter.annotations.FeedFilterCompatibility
+import app.revanced.patches.tiktok.feedfilter.fingerprints.FeedApiServiceLIZFingerprint
+import app.revanced.patches.tiktok.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsStatusLoadFingerprint
+import app.revanced.patches.tiktok.misc.settings.patch.SettingsPatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("feed-filter")
+@Description("Filters tiktok videos: removing ads, removing livestreams.")
+@FeedFilterCompatibility
+@Version("0.0.1")
+class FeedFilterPatch : BytecodePatch(
+ listOf(
+ FeedApiServiceLIZFingerprint,
+ SettingsStatusLoadFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = FeedApiServiceLIZFingerprint.result!!.mutableMethod
+ for ((index, instruction) in method.implementation!!.instructions.withIndex()) {
+ if (instruction.opcode != Opcode.RETURN_OBJECT) continue
+ val feedItemsRegister = (instruction as OneRegisterInstruction).registerA
+ method.addInstruction(
+ index,
+ "invoke-static {v$feedItemsRegister}, Lapp/revanced/tiktok/feedfilter/FeedItemsFilter;->filter(Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;)V"
+ )
+ break
+ }
+ val method2 = SettingsStatusLoadFingerprint.result!!.mutableMethod
+ method2.addInstruction(
+ 0,
+ "invoke-static {}, Lapp/revanced/tiktok/settingsmenu/SettingsStatus;->enableFeedFilter()V"
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/annotations/DownloadsCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/annotations/DownloadsCompatibility.kt
new file mode 100644
index 0000000..2a353e8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/annotations/DownloadsCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.interaction.downloads.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class DownloadsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint.kt
new file mode 100644
index 0000000..f1e8516
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.interaction.downloads.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object ACLCommonShareFingerprint : MethodFingerprint(
+ "I",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/ACLCommonShare;") &&
+ methodDef.name == "getCode"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint2.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint2.kt
new file mode 100644
index 0000000..384530d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint2.kt
@@ -0,0 +1,20 @@
+package app.revanced.patches.tiktok.interaction.downloads.fingerprints
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.tiktok.interaction.downloads.annotations.DownloadsCompatibility
+import org.jf.dexlib2.AccessFlags
+
+@Name("acl-common-share-get-show-type")
+@DownloadsCompatibility
+@Version("0.0.1")
+object ACLCommonShareFingerprint2 : MethodFingerprint(
+ "I",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/ACLCommonShare;") &&
+ methodDef.name == "getShowType"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint3.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint3.kt
new file mode 100644
index 0000000..cf585dd
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint3.kt
@@ -0,0 +1,20 @@
+package app.revanced.patches.tiktok.interaction.downloads.fingerprints
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.tiktok.interaction.downloads.annotations.DownloadsCompatibility
+import org.jf.dexlib2.AccessFlags
+
+@Name("acl-common-share-get-transcode")
+@DownloadsCompatibility
+@Version("0.0.1")
+object ACLCommonShareFingerprint3 : MethodFingerprint(
+ "I",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/ACLCommonShare;") &&
+ methodDef.name == "getTranscode"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/DownloadPathParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/DownloadPathParentFingerprint.kt
new file mode 100644
index 0000000..8daff09
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/DownloadPathParentFingerprint.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.tiktok.interaction.downloads.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object DownloadPathParentFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ strings = listOf(
+ "code",
+ "reason",
+ "params insufficient"
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/patch/DownloadsPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/patch/DownloadsPatch.kt
new file mode 100644
index 0000000..a3ea757
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/patch/DownloadsPatch.kt
@@ -0,0 +1,131 @@
+package app.revanced.patches.tiktok.interaction.downloads.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.data.toMethodWalker
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.extensions.replaceInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.tiktok.interaction.downloads.annotations.DownloadsCompatibility
+import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint
+import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint2
+import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint3
+import app.revanced.patches.tiktok.interaction.downloads.fingerprints.DownloadPathParentFingerprint
+import app.revanced.patches.tiktok.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsStatusLoadFingerprint
+import app.revanced.patches.tiktok.misc.settings.patch.SettingsPatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.reference.StringReference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("downloads")
+@Description("Removes download restrictions and changes the default path to download to.")
+@DownloadsCompatibility
+@Version("0.0.1")
+class DownloadsPatch : BytecodePatch(
+ listOf(
+ ACLCommonShareFingerprint,
+ ACLCommonShareFingerprint2,
+ ACLCommonShareFingerprint3,
+ DownloadPathParentFingerprint,
+ SettingsStatusLoadFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method1 = ACLCommonShareFingerprint.result!!.mutableMethod
+ method1.replaceInstructions(
+ 0,
+ """
+ const/4 v0, 0x0
+ return v0
+ """
+ )
+ val method2 = ACLCommonShareFingerprint2.result!!.mutableMethod
+ method2.replaceInstructions(
+ 0,
+ """
+ const/4 v0, 0x2
+ return v0
+ """
+ )
+ //Download videos without watermark.
+ val method3 = ACLCommonShareFingerprint3.result!!.mutableMethod
+ method3.addInstructions(
+ 0,
+ """
+ invoke-static {}, Lapp/revanced/tiktok/download/DownloadsPatch;->shouldRemoveWatermark()Z
+ move-result v0
+ if-eqz v0, :noremovewatermark
+ const/4 v0, 0x1
+ return v0
+ :noremovewatermark
+ nop
+ """
+ )
+ //Change the download path patch
+ val method4 = DownloadPathParentFingerprint.result!!.mutableMethod
+ val implementation4 = method4.implementation
+ val instructions = implementation4!!.instructions
+ var targetOffset = -1
+ //Search for the target method called instruction offset.
+ for ((index, instruction) in instructions.withIndex()) {
+ if (instruction.opcode != Opcode.CONST_STRING) continue
+ val reference = (instruction as ReferenceInstruction).reference as StringReference
+ if (reference.string != "video/mp4") continue
+ val targetInstruction = instructions[index + 1]
+ if (targetInstruction.opcode != Opcode.INVOKE_STATIC) continue
+ targetOffset = index + 1
+ break
+ }
+ if (targetOffset == -1) return PatchResultError("Can not find download path uri method.")
+ //Change videos' download path.
+ val downloadUriMethod = context
+ .toMethodWalker(DownloadPathParentFingerprint.result!!.method)
+ .nextMethod(targetOffset, true)
+ .getMethod() as MutableMethod
+ downloadUriMethod.implementation!!.instructions.forEachIndexed { index, instruction ->
+ if (instruction.opcode == Opcode.SGET_OBJECT) {
+ val overrideRegister = (instruction as OneRegisterInstruction).registerA
+ downloadUriMethod.addInstructions(
+ index + 1,
+ """
+ invoke-static {}, Lapp/revanced/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
+ move-result-object v$overrideRegister
+ """
+ )
+ }
+ if (instruction.opcode == Opcode.CONST_STRING) {
+ val string = ((instruction as ReferenceInstruction).reference as StringReference).string
+ if (string.contains("/Camera")) {
+ val overrideRegister = (instruction as OneRegisterInstruction).registerA
+ val overrideString = string.replace("/Camera", "")
+ downloadUriMethod.replaceInstruction(
+ index,
+ """
+ const-string v$overrideRegister, "$overrideString"
+ """
+ )
+ }
+ }
+ }
+ val method5 = SettingsStatusLoadFingerprint.result!!.mutableMethod
+ method5.addInstruction(
+ 0,
+ "invoke-static {}, Lapp/revanced/tiktok/settingsmenu/SettingsStatus;->enableDownload()V"
+ )
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/annotations/ShowSeekbarCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/annotations/ShowSeekbarCompatibility.kt
new file mode 100644
index 0000000..713de94
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/annotations/ShowSeekbarCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.interaction.seekbar.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ShowSeekbarCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/fingerprints/AwemeGetVideoControlFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/fingerprints/AwemeGetVideoControlFingerprint.kt
new file mode 100644
index 0000000..fb64631
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/fingerprints/AwemeGetVideoControlFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.tiktok.interaction.seekbar.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object AwemeGetVideoControlFingerprint : MethodFingerprint(
+ "L",
+ AccessFlags.PUBLIC.value,
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/Aweme;") && methodDef.name == "getVideoControl"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/patch/ShowSeekbarPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/patch/ShowSeekbarPatch.kt
new file mode 100644
index 0000000..0c99ab3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/patch/ShowSeekbarPatch.kt
@@ -0,0 +1,47 @@
+package app.revanced.patches.tiktok.interaction.seekbar.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.tiktok.interaction.seekbar.annotations.ShowSeekbarCompatibility
+import app.revanced.patches.tiktok.interaction.seekbar.fingerprints.AwemeGetVideoControlFingerprint
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.builder.instruction.BuilderInstruction11n
+import org.jf.dexlib2.builder.instruction.BuilderInstruction22c
+
+@Patch
+@Name("show-seekbar")
+@Description("Shows progress bar for all video.")
+@ShowSeekbarCompatibility
+@Version("0.0.1")
+class ShowSeekbarPatch : BytecodePatch(
+ listOf(
+ AwemeGetVideoControlFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ //Get VideoControl FieldReference
+ val videoControl = context.findClass { it.type.endsWith("/VideoControl;") }
+ ?: return PatchResultError("Can not find target class")
+ val fieldList = videoControl.immutableClass.fields.associateBy { field -> field.name }
+
+ val method = AwemeGetVideoControlFingerprint.result!!.mutableMethod
+ val implementation = method.implementation!!
+ implementation.addInstructions(
+ 1, listOf(
+ BuilderInstruction11n(Opcode.CONST_4, 1, 1),
+ BuilderInstruction22c(Opcode.IPUT, 1, 0, fieldList["showProgressBar"]!!),
+ BuilderInstruction22c(Opcode.IPUT, 1, 0, fieldList["draftProgressBar"]!!)
+ )
+ )
+ return PatchResultSuccess()
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/annotations/PlaybackSpeedCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/annotations/PlaybackSpeedCompatibility.kt
new file mode 100644
index 0000000..33c3dc4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/annotations/PlaybackSpeedCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.interaction.speed.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PlaybackSpeedCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/fingerprints/SpeedControlParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/fingerprints/SpeedControlParentFingerprint.kt
new file mode 100644
index 0000000..4e70ca8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/fingerprints/SpeedControlParentFingerprint.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.tiktok.interaction.speed.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object SpeedControlParentFingerprint : MethodFingerprint(
+ returnType = "L",
+ access = AccessFlags.PRIVATE or AccessFlags.FINAL,
+ parameters = listOf(
+ "L"
+ ),
+ strings = listOf(
+ "playback_speed"
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/patch/PlaybackSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/patch/PlaybackSpeedPatch.kt
new file mode 100644
index 0000000..c1f15a7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/patch/PlaybackSpeedPatch.kt
@@ -0,0 +1,48 @@
+package app.revanced.patches.tiktok.interaction.speed.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.data.toMethodWalker
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.tiktok.interaction.speed.annotations.PlaybackSpeedCompatibility
+import app.revanced.patches.tiktok.interaction.speed.fingerprints.SpeedControlParentFingerprint
+import org.jf.dexlib2.Opcode
+
+@Patch
+@Name("playback-speed")
+@Description("Enables the playback speed option for all videos.")
+@PlaybackSpeedCompatibility
+@Version("0.0.1")
+class PlaybackSpeedPatch : BytecodePatch(
+ listOf(
+ SpeedControlParentFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val parentMethod = SpeedControlParentFingerprint.result!!.mutableMethod
+ val parentMethodInstructions = parentMethod.implementation!!.instructions
+ for ((index, instruction) in parentMethodInstructions.withIndex()) {
+ if (instruction.opcode != Opcode.INVOKE_VIRTUAL) continue
+ val isSpeedEnableMethod = context
+ .toMethodWalker(parentMethod)
+ .nextMethod(index, true)
+ .getMethod() as MutableMethod
+ isSpeedEnableMethod.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+ return v0
+ """
+ )
+ break
+ }
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/annotations/IntegrationsCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/annotations/IntegrationsCompatibility.kt
new file mode 100644
index 0000000..e397ecf
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/annotations/IntegrationsCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.misc.integrations.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class IntegrationsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/fingerprints/InitFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/fingerprints/InitFingerprint.kt
new file mode 100644
index 0000000..9afc4ec
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/fingerprints/InitFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.tiktok.misc.integrations.fingerprints
+
+import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint
+
+object InitFingerprint : IntegrationsFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/AwemeHostApplication;") &&
+ methodDef.name == "onCreate"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/patch/IntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/patch/IntegrationsPatch.kt
new file mode 100644
index 0000000..6deed29
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/patch/IntegrationsPatch.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.tiktok.misc.integrations.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patches.tiktok.misc.integrations.annotations.IntegrationsCompatibility
+import app.revanced.patches.tiktok.misc.integrations.fingerprints.InitFingerprint
+import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch
+
+@Name("integrations")
+@IntegrationsCompatibility
+class IntegrationsPatch : AbstractIntegrationsPatch(
+ "Lapp/revanced/tiktok/utils/ReVancedUtils;",
+ listOf(InitFingerprint)
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/annotations/DisableLoginRequirementCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/annotations/DisableLoginRequirementCompatibility.kt
new file mode 100644
index 0000000..64acf15
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/annotations/DisableLoginRequirementCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.misc.login.disablerequirement.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class DisableLoginRequirementCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint.kt
new file mode 100644
index 0000000..842dcae
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.tiktok.misc.login.disablerequirement.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+object MandatoryLoginServiceFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/MandatoryLoginService;") &&
+ methodDef.name == "enableForcedLogin"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint2.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint2.kt
new file mode 100644
index 0000000..59015a6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint2.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.tiktok.misc.login.disablerequirement.fingerprints
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.tiktok.misc.login.disablerequirement.annotations.DisableLoginRequirementCompatibility
+
+@Name("mandatory-login-service-fingerprint2")
+@DisableLoginRequirementCompatibility
+@Version("0.0.1")
+object MandatoryLoginServiceFingerprint2 : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/MandatoryLoginService;") &&
+ methodDef.name == "shouldShowForcedLogin"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/patch/DisableLoginRequirementPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/patch/DisableLoginRequirementPatch.kt
new file mode 100644
index 0000000..0967f72
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/patch/DisableLoginRequirementPatch.kt
@@ -0,0 +1,43 @@
+package app.revanced.patches.tiktok.misc.login.disablerequirement.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.tiktok.misc.login.disablerequirement.annotations.DisableLoginRequirementCompatibility
+import app.revanced.patches.tiktok.misc.login.disablerequirement.fingerprints.MandatoryLoginServiceFingerprint
+import app.revanced.patches.tiktok.misc.login.disablerequirement.fingerprints.MandatoryLoginServiceFingerprint2
+
+@Patch
+@Name("disable-login-requirement")
+@Description("Do not force login.")
+@DisableLoginRequirementCompatibility
+@Version("0.0.1")
+class DisableLoginRequirementPatch : BytecodePatch(
+ listOf(
+ MandatoryLoginServiceFingerprint,
+ MandatoryLoginServiceFingerprint2
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ listOf(
+ MandatoryLoginServiceFingerprint,
+ MandatoryLoginServiceFingerprint2
+ ).forEach { fingerprint ->
+ val method = fingerprint.result!!.mutableMethod
+ method.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x0
+ return v0
+ """
+ )
+ }
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/annotations/FixGoogleLoginCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/annotations/FixGoogleLoginCompatibility.kt
new file mode 100644
index 0000000..0f03b09
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/annotations/FixGoogleLoginCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.misc.login.fixgoogle.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class FixGoogleLoginCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleAuthAvailableFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleAuthAvailableFingerprint.kt
new file mode 100644
index 0000000..2e1a1ea
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleAuthAvailableFingerprint.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.misc.login.fixgoogle.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object GoogleAuthAvailableFingerprint : MethodFingerprint(
+ returnType = "Z",
+ access = AccessFlags.PUBLIC or AccessFlags.FINAL,
+ parameters = listOf(),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass == "Lcom/bytedance/lobby/google/GoogleAuth;"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleOneTapAuthAvailableFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleOneTapAuthAvailableFingerprint.kt
new file mode 100644
index 0000000..26e51a5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleOneTapAuthAvailableFingerprint.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.misc.login.fixgoogle.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object GoogleOneTapAuthAvailableFingerprint : MethodFingerprint(
+ returnType = "Z",
+ access = AccessFlags.PUBLIC or AccessFlags.FINAL,
+ parameters = listOf(),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass == "Lcom/bytedance/lobby/google/GoogleOneTapAuth;"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/patch/FixGoogleLoginPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/patch/FixGoogleLoginPatch.kt
new file mode 100644
index 0000000..9e27025
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/patch/FixGoogleLoginPatch.kt
@@ -0,0 +1,44 @@
+package app.revanced.patches.tiktok.misc.login.fixgoogle.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.tiktok.misc.login.fixgoogle.annotations.FixGoogleLoginCompatibility
+import app.revanced.patches.tiktok.misc.login.fixgoogle.fingerprints.GoogleAuthAvailableFingerprint
+import app.revanced.patches.tiktok.misc.login.fixgoogle.fingerprints.GoogleOneTapAuthAvailableFingerprint
+
+@Patch
+@Name("fix-google-login")
+@Description("Allows logging in with a Google account.")
+@FixGoogleLoginCompatibility
+@Version("0.0.1")
+class FixGoogleLoginPatch : BytecodePatch(
+ listOf(
+ GoogleOneTapAuthAvailableFingerprint,
+ GoogleAuthAvailableFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ listOf(
+ GoogleOneTapAuthAvailableFingerprint,
+ GoogleAuthAvailableFingerprint
+ ).forEach {
+ with(it.result!!.mutableMethod) {
+ addInstructions(
+ 0,
+ """
+ const/4 v0, 0x0
+ return v0
+ """
+ )
+ }
+ }
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/annotations/SettingsCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/annotations/SettingsCompatibility.kt
new file mode 100644
index 0000000..8aa98d7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/annotations/SettingsCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.misc.settings.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class SettingsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AboutViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AboutViewFingerprint.kt
new file mode 100644
index 0000000..e734a6f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AboutViewFingerprint.kt
@@ -0,0 +1,39 @@
+package app.revanced.patches.tiktok.misc.settings.fingerprints
+
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+@FuzzyPatternScanMethod(4)
+object AboutViewFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.MOVE,
+ Opcode.INVOKE_DIRECT_RANGE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.NEW_INSTANCE,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.CONST_4,
+ Opcode.CONST_STRING,
+ Opcode.INVOKE_DIRECT_RANGE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.CONST,
+ Opcode.SGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.CONST
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AdPersonalizationActivityOnCreateFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AdPersonalizationActivityOnCreateFingerprint.kt
new file mode 100644
index 0000000..69778a6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AdPersonalizationActivityOnCreateFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.tiktok.misc.settings.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object AdPersonalizationActivityOnCreateFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/AdPersonalizationActivity;") &&
+ methodDef.name == "onCreate"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsOnViewCreatedFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsOnViewCreatedFingerprint.kt
new file mode 100644
index 0000000..46f2ba6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsOnViewCreatedFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.tiktok.misc.settings.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SettingsOnViewCreatedFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/SettingNewVersionFragment;") &&
+ methodDef.name == "onViewCreated"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsStatusLoadFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsStatusLoadFingerprint.kt
new file mode 100644
index 0000000..c2f1d3c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsStatusLoadFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.tiktok.misc.settings.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SettingsStatusLoadFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("Lapp/revanced/tiktok/settingsmenu/SettingsStatus;") &&
+ methodDef.name == "load"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/patch/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/patch/SettingsPatch.kt
new file mode 100644
index 0000000..0fcd094
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/patch/SettingsPatch.kt
@@ -0,0 +1,120 @@
+package app.revanced.patches.tiktok.misc.settings.patch
+
+import app.revanced.extensions.toErrorResult
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.tiktok.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.tiktok.misc.settings.annotations.SettingsCompatibility
+import app.revanced.patches.tiktok.misc.settings.fingerprints.AboutViewFingerprint
+import app.revanced.patches.tiktok.misc.settings.fingerprints.AdPersonalizationActivityOnCreateFingerprint
+import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsOnViewCreatedFingerprint
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.instruction.formats.Instruction35c
+import org.jf.dexlib2.iface.reference.StringReference
+import org.jf.dexlib2.iface.reference.TypeReference
+
+@Patch
+@DependsOn([IntegrationsPatch::class])
+@Name("settings")
+@Description("Adds ReVanced settings to TikTok.")
+@SettingsCompatibility
+@Version("0.0.1")
+class SettingsPatch : BytecodePatch(
+ listOf(
+ AdPersonalizationActivityOnCreateFingerprint,
+ SettingsOnViewCreatedFingerprint,
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsOnViewCreatedFingerprint.result?.let {
+ AboutViewFingerprint.resolve(context, it.method, it.classDef)
+ }
+ // Patch Settings UI to add 'Revanced Settings'.
+ val targetIndexes = findOptionsOnClickIndex()
+ with(SettingsOnViewCreatedFingerprint.result!!.mutableMethod) {
+ for (index in targetIndexes) {
+ if (
+ instruction(index).opcode != Opcode.NEW_INSTANCE ||
+ instruction(index - 4).opcode != Opcode.MOVE_RESULT_OBJECT
+ )
+ return PatchResultError("Hardcode offset changed.")
+ patchOptionNameAndOnClickEvent(index, context)
+ }
+ }
+ // Implement settings screen in `AdPersonalizationActivity`
+ with(AdPersonalizationActivityOnCreateFingerprint.result!!.mutableMethod) {
+ for ((index, instruction) in implementation!!.instructions.withIndex()) {
+ if (instruction.opcode != Opcode.INVOKE_SUPER) continue
+ val thisRegister = (instruction as Instruction35c).registerC
+ addInstructions(
+ index + 1,
+ """
+ invoke-static {v$thisRegister}, Lapp/revanced/tiktok/settingsmenu/SettingsMenu;->initializeSettings(Lcom/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity;)V
+ return-void
+ """
+ )
+ break
+ }
+ }
+ return PatchResultSuccess()
+ }
+
+ private fun findOptionsOnClickIndex(): IntArray {
+ val results = IntArray(2)
+ SettingsOnViewCreatedFingerprint.result?.apply {
+ for ((index, instruction) in mutableMethod.implementation!!.instructions.withIndex()) {
+ // Old UI settings option to replace to 'Revanced Settings'
+ if (instruction.opcode == Opcode.CONST_STRING) {
+ val string = ((instruction as ReferenceInstruction).reference as StringReference).string
+ if (string == "copyright_policy") {
+ results[0] = index - 2
+ break
+ }
+ }
+ }
+
+ // New UI settings option to replace to 'Revanced Settings'
+ results[1] = AboutViewFingerprint.result!!.scanResult.patternScanResult!!.startIndex
+ } ?: throw SettingsOnViewCreatedFingerprint.toErrorResult()
+ return results
+ }
+
+ private fun patchOptionNameAndOnClickEvent(index: Int, context: BytecodeContext) {
+ with(SettingsOnViewCreatedFingerprint.result!!.mutableMethod) {
+ // Patch option name
+ val overrideRegister = (instruction(index - 4) as OneRegisterInstruction).registerA
+ replaceInstruction(
+ index - 4,
+ """
+ const-string v$overrideRegister, "Revanced Settings"
+ """
+ )
+
+ // Patch option OnClick Event
+ with(((instruction(index) as ReferenceInstruction).reference as TypeReference).type) {
+ context.findClass(this)!!.mutableClass.methods.first { it.name == "onClick" }
+ .addInstructions(
+ 0,
+ """
+ invoke-static {}, Lapp/revanced/tiktok/settingsmenu/SettingsMenu;->startSettingsActivity()V
+ return-void
+ """
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/annotations/SpoofSimCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/annotations/SpoofSimCompatibility.kt
new file mode 100644
index 0000000..a775e5c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/annotations/SpoofSimCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.tiktok.misc.spoof.sim.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.ss.android.ugc.trill"),
+ Package("com.zhiliaoapp.musically")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class SpoofSimCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/patch/SpoofSimPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/patch/SpoofSimPatch.kt
new file mode 100644
index 0000000..822a2e2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/patch/SpoofSimPatch.kt
@@ -0,0 +1,110 @@
+package app.revanced.patches.tiktok.misc.spoof.sim.patch
+
+import app.revanced.extensions.findMutableMethodOf
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.tiktok.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsStatusLoadFingerprint
+import app.revanced.patches.tiktok.misc.settings.patch.SettingsPatch
+import app.revanced.patches.tiktok.misc.spoof.sim.annotations.SpoofSimCompatibility
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+import org.jf.dexlib2.iface.instruction.formats.Instruction35c
+import org.jf.dexlib2.iface.reference.MethodReference
+
+@Patch(false)
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("sim-spoof")
+@Description("Spoofs the information which is retrieved from the sim-card.")
+@SpoofSimCompatibility
+@Version("0.0.1")
+class SpoofSimPatch : BytecodePatch() {
+ private companion object {
+ val replacements = hashMapOf(
+ "getSimCountryIso" to "getCountryIso",
+ "getNetworkCountryIso" to "getCountryIso",
+ "getSimOperator" to "getOperator",
+ "getNetworkOperator" to "getOperator",
+ "getSimOperatorName" to "getOperatorName",
+ "getNetworkOperatorName" to "getOperatorName"
+ )
+ }
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ // Find all api call to check sim information
+ buildMap {
+ context.classes.forEach { classDef ->
+ classDef.methods.let { methods ->
+ buildMap methodList@{
+ methods.forEach methods@{ method ->
+ with(method.implementation?.instructions ?: return@methods) {
+ ArrayDeque>().also { patchIndices ->
+ this.forEachIndexed { index, instruction ->
+ if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return@forEachIndexed
+
+ val methodRef =
+ (instruction as Instruction35c).reference as MethodReference
+ if (methodRef.definingClass != "Landroid/telephony/TelephonyManager;") return@forEachIndexed
+
+ replacements[methodRef.name]?.let { replacement ->
+ patchIndices.add(index to replacement)
+ }
+ }
+ }.also { if (it.isEmpty()) return@methods }.let { patches ->
+ put(method, patches)
+ }
+ }
+ }
+ }
+ }.also { if (it.isEmpty()) return@forEach }.let { methodPatches ->
+ put(classDef, methodPatches)
+ }
+ }
+ }.forEach { (classDef, methods) ->
+ with(context.proxy(classDef).mutableClass) {
+ methods.forEach { (method, patches) ->
+ with(findMutableMethodOf(method)) {
+ while (!patches.isEmpty()) {
+ val (index, replacement) = patches.removeLast()
+ replaceReference(index, replacement)
+ }
+ }
+ }
+ }
+ }
+
+ // Enable patch in settings
+ with(SettingsStatusLoadFingerprint.result!!.mutableMethod) {
+ addInstruction(
+ 0,
+ "invoke-static {}, Lapp/revanced/tiktok/settingsmenu/SettingsStatus;->enableSimSpoof()V"
+ )
+ }
+
+ return PatchResultSuccess()
+ }
+
+ // Patch Android API and return fake sim information
+ private fun MutableMethod.replaceReference(index: Int, replacement: String) {
+ val resultReg = (instruction(index + 1) as OneRegisterInstruction).registerA
+
+ addInstructions(
+ index + 2,
+ """
+ invoke-static {v$resultReg}, Lapp/revanced/tiktok/spoof/sim/SpoofSimPatch;->$replacement(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v$resultReg
+ """
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/audio/annotations/AudioAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/annotations/AudioAdsCompatibility.kt
new file mode 100644
index 0000000..0e39cb8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/annotations/AudioAdsCompatibility.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.ad.audio.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("tv.twitch.android.app")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class AudioAdsCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/audio/fingerprints/AudioAdsPresenterPlayFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/fingerprints/AudioAdsPresenterPlayFingerprint.kt
new file mode 100644
index 0000000..56bdf62
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/fingerprints/AudioAdsPresenterPlayFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.ad.audio.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object AudioAdsPresenterPlayFingerprint : MethodFingerprint(
+ customFingerprint = { method ->
+ method.definingClass.endsWith("AudioAdsPlayerPresenter;") && method.name == "playAd"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt
new file mode 100644
index 0000000..a5cccb1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt
@@ -0,0 +1,67 @@
+package app.revanced.patches.twitch.ad.audio.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.twitch.ad.audio.annotations.AudioAdsCompatibility
+import app.revanced.patches.twitch.ad.audio.fingerprints.AudioAdsPresenterPlayFingerprint
+import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("block-audio-ads")
+@Description("Blocks audio ads in streams and VODs.")
+@AudioAdsCompatibility
+@Version("0.0.1")
+class AudioAdsPatch : BytecodePatch(
+ listOf(AudioAdsPresenterPlayFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ // Block playAds call
+ with(AudioAdsPresenterPlayFingerprint.result!!) {
+ mutableMethod.addInstructions(
+ 0,
+ """
+ invoke-static { }, Lapp/revanced/twitch/patches/AudioAdsPatch;->shouldBlockAudioAds()Z
+ move-result v0
+ if-eqz v0, :show_audio_ads
+ return-void
+ """,
+ listOf(ExternalLabel("show_audio_ads", mutableMethod.instruction(0)))
+ )
+ }
+
+ SettingsPatch.PreferenceScreen.ADS.CLIENT_SIDE.addPreferences(
+ SwitchPreference(
+ "revanced_block_audio_ads",
+ StringResource(
+ "revanced_block_audio_ads",
+ "Block audio ads"
+ ),
+ true,
+ StringResource(
+ "revanced_block_audio_ads_on",
+ "Audio ads are blocked"
+ ),
+ StringResource(
+ "revanced_block_audio_ads_off",
+ "Audio ads are unblocked"
+ ),
+ )
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/annotations/EmbeddedAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/annotations/EmbeddedAdsCompatibility.kt
new file mode 100644
index 0000000..4103c94
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/annotations/EmbeddedAdsCompatibility.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.ad.embedded.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("tv.twitch.android.app")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class EmbeddedAdsCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/fingerprints/CreateUsherClientFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/fingerprints/CreateUsherClientFingerprint.kt
new file mode 100644
index 0000000..3480440
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/fingerprints/CreateUsherClientFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitch.ad.embedded.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CreateUsherClientFingerprint : MethodFingerprint(
+ customFingerprint = { method ->
+ method.definingClass.endsWith("Ltv/twitch/android/network/OkHttpClientFactory;") && method.name == "buildOkHttpClient"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/patch/EmbeddedAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/patch/EmbeddedAdsPatch.kt
new file mode 100644
index 0000000..cf8f53e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/patch/EmbeddedAdsPatch.kt
@@ -0,0 +1,78 @@
+package app.revanced.patches.twitch.ad.embedded.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.shared.settings.preference.impl.ArrayResource
+import app.revanced.patches.shared.settings.preference.impl.ListPreference
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.twitch.ad.embedded.annotations.EmbeddedAdsCompatibility
+import app.revanced.patches.twitch.ad.embedded.fingerprints.CreateUsherClientFingerprint
+import app.revanced.patches.twitch.ad.video.patch.VideoAdsPatch
+import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([VideoAdsPatch::class, IntegrationsPatch::class, SettingsPatch::class])
+@Name("block-embedded-ads")
+@Description("Blocks embedded steam ads using services like TTV.lol or PurpleAdBlocker.")
+@EmbeddedAdsCompatibility
+@Version("0.0.1")
+class EmbeddedAdsPatch : BytecodePatch(
+ listOf(CreateUsherClientFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = CreateUsherClientFingerprint.result ?: return PatchResultError("${CreateUsherClientFingerprint.name} not found")
+
+ // Inject OkHttp3 application interceptor
+ result.mutableMethod.addInstructions(
+ 3,
+ """
+ invoke-static {}, Lapp/revanced/twitch/patches/EmbeddedAdsPatch;->createRequestInterceptor()Lapp/revanced/twitch/api/RequestInterceptor;
+ move-result-object v2
+ invoke-virtual {v0, v2}, Lokhttp3/OkHttpClient${"$"}Builder;->addInterceptor(Lokhttp3/Interceptor;)Lokhttp3/OkHttpClient${"$"}Builder;
+ """
+ )
+
+ SettingsPatch.PreferenceScreen.ADS.SURESTREAM.addPreferences(
+ ListPreference(
+ "revanced_block_embedded_ads",
+ StringResource(
+ "revanced_block_embedded_ads",
+ "Block embedded video ads"
+ ),
+ ArrayResource(
+ "revanced_hls_proxies",
+ listOf(
+ StringResource("revanced_proxy_disabled", "Disabled"),
+ StringResource("revanced_proxy_ttv_lol", "TTV LOL proxy"),
+ StringResource("revanced_proxy_purpleadblock", "PurpleAdBlock proxy"),
+ )
+ ),
+ ArrayResource(
+ "revanced_hls_proxies_values",
+ listOf(
+ StringResource("key_revanced_proxy_disabled", "disabled"),
+ StringResource("key_revanced_proxy_ttv_lol", "ttv-lol"),
+ StringResource("key_revanced_proxy_purpleadblock", "purpleadblock")
+ )
+ ),
+ "ttv-lol"
+ )
+ )
+
+ SettingsPatch.addString("revanced_embedded_ads_service_unavailable", "%s is unavailable. Ads may show. Try switching to another ad block service in settings.")
+ SettingsPatch.addString("revanced_embedded_ads_service_failed", "%s server returned an error. Ads may show. Try switching to another ad block service in settings.")
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/shared/util/AbstractAdPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/shared/util/AbstractAdPatch.kt
new file mode 100644
index 0000000..dca265b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/shared/util/AbstractAdPatch.kt
@@ -0,0 +1,51 @@
+package app.revanced.patches.twitch.ad.shared.util
+
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.util.smali.ExternalLabel
+
+abstract class AbstractAdPatch(
+ val conditionCall: String,
+ val skipLabelName: String,
+ internal val fingerprints: Iterable? = null,
+) : BytecodePatch(fingerprints) {
+
+ protected fun createConditionInstructions(register: String = "v0") = """
+ invoke-static { }, $conditionCall
+ move-result $register
+ if-eqz $register, :$skipLabelName
+ """
+
+ protected data class ReturnMethod(val returnType: Char = 'V', val value: String = "")
+
+ protected fun BytecodeContext.blockMethods(clazz: String, vararg methodNames: String, returnMethod: ReturnMethod = ReturnMethod()): Boolean {
+
+ return with(findClass(clazz)?.mutableClass) {
+ this ?: return false
+
+ this.methods.filter { methodNames.contains(it.name) }.forEach {
+ val retIntructions = when(returnMethod.returnType) {
+ 'V' -> "return-void"
+ 'Z' -> """
+ const/4 v0, ${returnMethod.value}
+ return v0
+ """
+ else -> throw NotImplementedError()
+ }
+ it.addInstructions(
+ 0,
+ """
+ ${createConditionInstructions("v0")}
+ $retIntructions
+ """,
+ listOf(ExternalLabel(skipLabelName, it.instruction(0)))
+ )
+ }
+ true
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/annotations/VideoAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/annotations/VideoAdsCompatibility.kt
new file mode 100644
index 0000000..60c4d16
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/annotations/VideoAdsCompatibility.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.ad.video.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("tv.twitch.android.app")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class VideoAdsCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/CheckAdEligibilityLambdaFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/CheckAdEligibilityLambdaFingerprint.kt
new file mode 100644
index 0000000..04c1c09
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/CheckAdEligibilityLambdaFingerprint.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.twitch.ad.video.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object CheckAdEligibilityLambdaFingerprint : MethodFingerprint(
+ "L",
+ AccessFlags.PRIVATE or AccessFlags.FINAL or AccessFlags.STATIC,
+ listOf("L", "L", "L"),
+ customFingerprint = { method ->
+ method.definingClass.endsWith("AdEligibilityFetcher;") &&
+ method.name.contains("shouldRequestAd")
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/ContentConfigShowAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/ContentConfigShowAdsFingerprint.kt
new file mode 100644
index 0000000..ed38035
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/ContentConfigShowAdsFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.ad.video.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object ContentConfigShowAdsFingerprint : MethodFingerprint(
+ customFingerprint = { method ->
+ method.definingClass.endsWith("ContentConfigData;") && method.name == "getShowAds"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/GetReadyToShowAdFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/GetReadyToShowAdFingerprint.kt
new file mode 100644
index 0000000..3256ad0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/GetReadyToShowAdFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitch.ad.video.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object GetReadyToShowAdFingerprint : MethodFingerprint(
+ customFingerprint = { method ->
+ method.definingClass.endsWith("/StreamDisplayAdsPresenter;") && method.name == "getReadyToShowAdOrAbort"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt
new file mode 100644
index 0000000..9b1197e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt
@@ -0,0 +1,149 @@
+package app.revanced.patches.twitch.ad.video.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.twitch.ad.shared.util.AbstractAdPatch
+import app.revanced.patches.twitch.ad.video.annotations.VideoAdsCompatibility
+import app.revanced.patches.twitch.ad.video.fingerprints.CheckAdEligibilityLambdaFingerprint
+import app.revanced.patches.twitch.ad.video.fingerprints.ContentConfigShowAdsFingerprint
+import app.revanced.patches.twitch.ad.video.fingerprints.GetReadyToShowAdFingerprint
+import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("block-video-ads")
+@Description("Blocks video ads in streams and VODs.")
+@VideoAdsCompatibility
+@Version("0.0.1")
+class VideoAdsPatch : AbstractAdPatch(
+ "Lapp/revanced/twitch/patches/VideoAdsPatch;->shouldBlockVideoAds()Z",
+ "show_video_ads",
+ listOf(
+ ContentConfigShowAdsFingerprint,
+ CheckAdEligibilityLambdaFingerprint,
+ GetReadyToShowAdFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ /* Amazon ads SDK */
+ context.blockMethods(
+ "Lcom/amazon/ads/video/player/AdsManagerImpl;",
+ "playAds"
+ )
+
+ /* Twitch ads manager */
+ context.blockMethods(
+ "Ltv/twitch/android/shared/ads/VideoAdManager;",
+ "checkAdEligibilityAndRequestAd", "requestAd", "requestAds"
+ )
+
+ /* Various ad presenters */
+ context.blockMethods(
+ "Ltv/twitch/android/shared/ads/AdsPlayerPresenter;",
+ "requestAd", "requestFirstAd", "requestFirstAdIfEligible", "requestMidroll", "requestAdFromMultiAdFormatEvent"
+ )
+
+ context.blockMethods(
+ "Ltv/twitch/android/shared/ads/AdsVodPlayerPresenter;",
+ "requestAd", "requestFirstAd",
+ )
+
+ context.blockMethods(
+ "Ltv/twitch/android/feature/theatre/ads/AdEdgeAllocationPresenter;",
+ "parseAdAndCheckEligibility", "requestAdsAfterEligibilityCheck", "showAd", "bindMultiAdFormatAllocation"
+ )
+
+ /* A/B ad testing experiments */
+ context.blockMethods(
+ "Ltv/twitch/android/provider/experiments/helpers/DisplayAdsExperimentHelper;",
+ "areDisplayAdsEnabled",
+ returnMethod = ReturnMethod('Z', "0")
+ )
+
+ context.blockMethods(
+ "Ltv/twitch/android/shared/ads/tracking/MultiFormatAdsTrackingExperiment;",
+ "shouldUseMultiAdFormatTracker", "shouldUseVideoAdTracker",
+ returnMethod = ReturnMethod('Z', "0")
+ )
+
+ context.blockMethods(
+ "Ltv/twitch/android/shared/ads/MultiformatAdsExperiment;",
+ "shouldDisableClientSideLivePreroll", "shouldDisableClientSideVodPreroll",
+ returnMethod = ReturnMethod('Z', "1")
+ )
+
+ // Pretend our player is ineligible for all ads
+ with(CheckAdEligibilityLambdaFingerprint.result!!) {
+ mutableMethod.addInstructions(
+ 0,
+ """
+ ${createConditionInstructions()}
+ const/4 v0, 0
+ invoke-static {v0}, Lio/reactivex/Single;->just(Ljava/lang/Object;)Lio/reactivex/Single;
+ move-result-object p0
+ return-object p0
+ """,
+ listOf(ExternalLabel(skipLabelName, mutableMethod.instruction(0)))
+ )
+ }
+
+ with(GetReadyToShowAdFingerprint.result!!) {
+ val adFormatDeclined = "Ltv/twitch/android/shared/display/ads/theatre/StreamDisplayAdsPresenter\$Action\$AdFormatDeclined;"
+ mutableMethod.addInstructions(
+ 0,
+ """
+ ${createConditionInstructions()}
+ sget-object p2, $adFormatDeclined->INSTANCE:$adFormatDeclined
+ invoke-static {p1, p2}, Ltv/twitch/android/core/mvp/presenter/StateMachineKt;->plus(Ltv/twitch/android/core/mvp/presenter/PresenterState;Ltv/twitch/android/core/mvp/presenter/PresenterAction;)Ltv/twitch/android/core/mvp/presenter/StateAndAction;
+ move-result-object p1
+ return-object p1
+ """,
+ listOf(ExternalLabel(skipLabelName, mutableMethod.instruction(0)))
+ )
+ }
+
+ // Spoof showAds JSON field
+ with(ContentConfigShowAdsFingerprint.result!!) {
+ mutableMethod.addInstructions(0, """
+ ${createConditionInstructions()}
+ const/4 v0, 0
+ :$skipLabelName
+ return v0
+ """
+ )
+ }
+
+ SettingsPatch.PreferenceScreen.ADS.CLIENT_SIDE.addPreferences(
+ SwitchPreference(
+ "revanced_block_video_ads",
+ StringResource(
+ "revanced_block_video_ads",
+ "Block video ads"
+ ),
+ true,
+ StringResource(
+ "revanced_block_video_ads_on",
+ "Video ads are blocked"
+ ),
+ StringResource(
+ "revanced_block_video_ads_off",
+ "Video ads are unblocked"
+ ),
+ )
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/annotations/ShowDeletedMessagesCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/annotations/ShowDeletedMessagesCompatibility.kt
new file mode 100644
index 0000000..c0c71be
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/annotations/ShowDeletedMessagesCompatibility.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.chat.antidelete.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("tv.twitch.android.app")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ShowDeletedMessagesCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt
new file mode 100644
index 0000000..733710b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitch.chat.antidelete.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object ChatUtilCreateDeletedSpanFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/ChatUtil\$Companion;") && methodDef.name == "createDeletedSpanFromChatMessageSpan"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/DeletedMessageClickableSpanCtorFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/DeletedMessageClickableSpanCtorFingerprint.kt
new file mode 100644
index 0000000..6ecb63c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/DeletedMessageClickableSpanCtorFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.twitch.chat.antidelete.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object DeletedMessageClickableSpanCtorFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("DeletedMessageClickableSpan;")
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/SetHasModAccessFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/SetHasModAccessFingerprint.kt
new file mode 100644
index 0000000..7e8ee66
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/SetHasModAccessFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.chat.antidelete.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SetHasModAccessFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("DeletedMessageClickableSpan;") && methodDef.name == "setHasModAccess"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt
new file mode 100644
index 0000000..dba6e83
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt
@@ -0,0 +1,103 @@
+package app.revanced.patches.twitch.chat.antidelete.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.*
+import app.revanced.patcher.patch.*
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.shared.settings.preference.impl.ArrayResource
+import app.revanced.patches.shared.settings.preference.impl.ListPreference
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.twitch.chat.antidelete.annotations.ShowDeletedMessagesCompatibility
+import app.revanced.patches.twitch.chat.antidelete.fingerprints.*
+import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("show-deleted-messages")
+@Description("Shows deleted chat messages behind a clickable spoiler.")
+@ShowDeletedMessagesCompatibility
+@Version("0.0.1")
+class ShowDeletedMessagesPatch : BytecodePatch(
+ listOf(
+ SetHasModAccessFingerprint,
+ DeletedMessageClickableSpanCtorFingerprint,
+ ChatUtilCreateDeletedSpanFingerprint
+ )
+) {
+ private fun createSpoilerConditionInstructions(register: String = "v0") = """
+ invoke-static {}, Lapp/revanced/twitch/patches/ShowDeletedMessagesPatch;->shouldUseSpoiler()Z
+ move-result $register
+ if-eqz $register, :no_spoiler
+ """
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ // Spoiler mode: Force set hasModAccess member to true in constructor
+ with(DeletedMessageClickableSpanCtorFingerprint.result!!.mutableMethod) {
+ addInstructions(
+ implementation!!.instructions.lastIndex, /* place in front of return-void */
+ """
+ ${createSpoilerConditionInstructions()}
+ const/4 v0, 1
+ iput-boolean v0, p0, $definingClass->hasModAccess:Z
+ """,
+ listOf(ExternalLabel("no_spoiler", instruction(implementation!!.instructions.lastIndex)))
+ )
+ }
+
+ // Spoiler mode: Disable setHasModAccess setter
+ with(SetHasModAccessFingerprint.result!!) {
+ mutableMethod.addInstruction(0, "return-void")
+ }
+
+ // Cross-out mode: Reformat span of deleted message
+ with(ChatUtilCreateDeletedSpanFingerprint.result!!) {
+ mutableMethod.addInstructions(
+ 0,
+ """
+ invoke-static {p2}, Lapp/revanced/twitch/patches/ShowDeletedMessagesPatch;->reformatDeletedMessage(Landroid/text/Spanned;)Landroid/text/Spanned;
+ move-result-object v0
+ if-eqz v0, :no_reformat
+ return-object v0
+ """,
+ listOf(ExternalLabel("no_reformat", mutableMethod.instruction(0)))
+ )
+ }
+
+ SettingsPatch.PreferenceScreen.CHAT.GENERAL.addPreferences(
+ ListPreference(
+ "revanced_show_deleted_messages",
+ StringResource(
+ "revanced_show_deleted_messages_title",
+ "Show deleted messages"
+ ),
+ ArrayResource(
+ "revanced_deleted_messages",
+ listOf(
+ StringResource("revanced_deleted_messages_hide", "Do not show deleted messages"),
+ StringResource("revanced_deleted_messages_spoiler", "Hide deleted messages behind a spoiler"),
+ StringResource("revanced_deleted_messages_cross_out", "Show deleted messages as crossed-out text")
+ )
+ ),
+ ArrayResource(
+ "revanced_deleted_messages_values",
+ listOf(
+ StringResource("key_revanced_deleted_messages_hide", "hide"),
+ StringResource("key_revanced_deleted_messages_spoiler", "spoiler"),
+ StringResource("key_revanced_deleted_messages_cross_out", "cross-out")
+ )
+ ),
+ "cross-out"
+ )
+ )
+
+ SettingsPatch.addString("revanced_deleted_msg", "message deleted")
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/annotations/DebugModeCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/annotations/DebugModeCompatibility.kt
new file mode 100644
index 0000000..394b9e9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/debug/annotations/DebugModeCompatibility.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.debug.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("tv.twitch.android.app")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class DebugModeCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsDebugConfigEnabledFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsDebugConfigEnabledFingerprint.kt
new file mode 100644
index 0000000..e7a3178
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsDebugConfigEnabledFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.debug.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object IsDebugConfigEnabledFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("BuildConfigUtil;") && methodDef.name == "isDebugConfigEnabled"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsOmVerificationEnabledFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsOmVerificationEnabledFingerprint.kt
new file mode 100644
index 0000000..cf8f1f7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsOmVerificationEnabledFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.debug.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object IsOmVerificationEnabledFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("BuildConfigUtil;") && methodDef.name == "isOmVerificationEnabled"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/ShouldShowDebugOptionsFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/ShouldShowDebugOptionsFingerprint.kt
new file mode 100644
index 0000000..12c5de0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/ShouldShowDebugOptionsFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.debug.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object ShouldShowDebugOptionsFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("BuildConfigUtil;") && methodDef.name == "shouldShowDebugOptions"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt
new file mode 100644
index 0000000..755efe6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt
@@ -0,0 +1,76 @@
+package app.revanced.patches.twitch.debug.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.twitch.debug.annotations.DebugModeCompatibility
+import app.revanced.patches.twitch.debug.fingerprints.IsDebugConfigEnabledFingerprint
+import app.revanced.patches.twitch.debug.fingerprints.IsOmVerificationEnabledFingerprint
+import app.revanced.patches.twitch.debug.fingerprints.ShouldShowDebugOptionsFingerprint
+import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch(false)
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("debug-mode")
+@Description("Enables Twitch's internal debugging mode.")
+@DebugModeCompatibility
+@Version("0.0.1")
+class DebugModePatch : BytecodePatch(
+ listOf(
+ IsDebugConfigEnabledFingerprint,
+ IsOmVerificationEnabledFingerprint,
+ ShouldShowDebugOptionsFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ listOf(
+ IsDebugConfigEnabledFingerprint,
+ IsOmVerificationEnabledFingerprint,
+ ShouldShowDebugOptionsFingerprint
+ ).forEach {
+ with(it.result!!) {
+ with(mutableMethod) {
+ addInstructions(
+ 0,
+ """
+ invoke-static {}, Lapp/revanced/twitch/patches/DebugModePatch;->isDebugModeEnabled()Z
+ move-result v0
+ return v0
+ """
+ )
+ }
+ }
+ }
+
+ SettingsPatch.PreferenceScreen.MISC.OTHER.addPreferences(
+ SwitchPreference(
+ "revanced_debug_mode",
+ StringResource(
+ "revanced_debug_mode_enable",
+ "Enable debug mode"
+ ),
+ false,
+ StringResource(
+ "revanced_debug_mode_on",
+ "Debug mode is enabled (not recommended)"
+ ),
+ StringResource(
+ "revanced_debug_mode_off",
+ "Debug mode is disabled"
+ ),
+ )
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/annotations/IntegrationsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/annotations/IntegrationsCompatibility.kt
new file mode 100644
index 0000000..f0e219f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/annotations/IntegrationsCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitch.misc.integrations.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("tv.twitch.android.app")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class IntegrationsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/fingerprints/InitFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/fingerprints/InitFingerprint.kt
new file mode 100644
index 0000000..7f55dd2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/fingerprints/InitFingerprint.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.twitch.misc.integrations.fingerprints
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patches.twitch.misc.integrations.annotations.IntegrationsCompatibility
+import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint
+
+@Name("init-fingerprint")
+@IntegrationsCompatibility
+@Version("0.0.1")
+object InitFingerprint : IntegrationsFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/TwitchApplication;") &&
+ methodDef.name == "onCreate"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/patch/IntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/patch/IntegrationsPatch.kt
new file mode 100644
index 0000000..027106d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/patch/IntegrationsPatch.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.twitch.misc.integrations.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patches.twitch.misc.integrations.fingerprints.InitFingerprint
+import app.revanced.patches.twitch.misc.integrations.annotations.IntegrationsCompatibility
+import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch
+
+@Name("integrations")
+@IntegrationsCompatibility
+class IntegrationsPatch : AbstractIntegrationsPatch(
+ "Lapp/revanced/twitch/utils/ReVancedUtils;",
+ listOf(InitFingerprint)
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/annotations/SettingsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/annotations/SettingsCompatibility.kt
new file mode 100644
index 0000000..0eadf69
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/annotations/SettingsCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitch.misc.settings.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("tv.twitch.android.app")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class SettingsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/bytecode/patch/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/bytecode/patch/SettingsPatch.kt
new file mode 100644
index 0000000..d0f8b72
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/bytecode/patch/SettingsPatch.kt
@@ -0,0 +1,196 @@
+package app.revanced.patches.twitch.misc.settings.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.*
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.shared.settings.preference.impl.PreferenceCategory
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.util.AbstractPreferenceScreen
+import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.twitch.misc.settings.annotations.SettingsCompatibility
+import app.revanced.patches.twitch.misc.settings.components.CustomPreferenceCategory
+import app.revanced.patches.twitch.misc.settings.fingerprints.*
+import app.revanced.patches.twitch.misc.settings.resource.patch.SettingsResourcePatch
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.immutable.ImmutableField
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsResourcePatch::class])
+@Name("settings")
+@Description("Adds settings menu to Twitch.")
+@SettingsCompatibility
+@Version("0.0.1")
+class SettingsPatch : BytecodePatch(
+ listOf(
+ SettingsActivityOnCreateFingerprint,
+ SettingsMenuItemEnumFingerprint,
+ MenuGroupsUpdatedFingerprint,
+ MenuGroupsOnClickFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ // Hook onCreate to handle fragment creation
+ with(SettingsActivityOnCreateFingerprint.result!!) {
+ val insertIndex = mutableMethod.implementation!!.instructions.size - 2
+ mutableMethod.addInstructions(
+ insertIndex,
+ """
+ invoke-static {p0}, $SETTINGS_HOOKS_CLASS->handleSettingsCreation(Landroidx/appcompat/app/AppCompatActivity;)Z
+ move-result v0
+ if-eqz v0, :no_rv_settings_init
+ return-void
+ """,
+ listOf(ExternalLabel("no_rv_settings_init", mutableMethod.instruction(insertIndex)))
+ )
+ }
+
+ // Create new menu item for settings menu
+ with(SettingsMenuItemEnumFingerprint.result!!) {
+ injectMenuItem(
+ REVANCED_SETTINGS_MENU_ITEM_NAME,
+ REVANCED_SETTINGS_MENU_ITEM_ID,
+ REVANCED_SETTINGS_MENU_ITEM_TITLE_RES,
+ REVANCED_SETTINGS_MENU_ITEM_ICON_RES
+ )
+ }
+
+ // Intercept settings menu creation and add new menu item
+ with(MenuGroupsUpdatedFingerprint.result!!) {
+ mutableMethod.addInstructions(
+ 0,
+ """
+ sget-object v0, $MENU_ITEM_ENUM_CLASS->$REVANCED_SETTINGS_MENU_ITEM_NAME:$MENU_ITEM_ENUM_CLASS
+ invoke-static {p1, v0}, $SETTINGS_HOOKS_CLASS->handleSettingMenuCreation(Ljava/util/List;Ljava/lang/Object;)Ljava/util/List;
+ move-result-object p1
+ """
+ )
+ }
+
+ // Intercept onclick events for the settings menu
+ with(MenuGroupsOnClickFingerprint.result!!) {
+ val insertIndex = 0
+ mutableMethod.addInstructions(
+ insertIndex,
+ """
+ invoke-static {p1}, $SETTINGS_HOOKS_CLASS->handleSettingMenuOnClick(Ljava/lang/Enum;)Z
+ move-result p2
+ if-eqz p2, :no_rv_settings_onclick
+ sget-object p1, $MENU_DISMISS_EVENT_CLASS->INSTANCE:$MENU_DISMISS_EVENT_CLASS
+ invoke-virtual {p0, p1}, Ltv/twitch/android/core/mvp/viewdelegate/RxViewDelegate;->pushEvent(Ltv/twitch/android/core/mvp/viewdelegate/ViewDelegateEvent;)V
+ return-void
+ """,
+ listOf(ExternalLabel("no_rv_settings_onclick", mutableMethod.instruction(insertIndex)))
+ )
+ }
+
+ addString("revanced_settings", "ReVanced Settings", false)
+ addString("revanced_reboot_message", "Twitch needs to restart to apply your changes. Restart now?", false)
+ addString("revanced_reboot", "Restart", false)
+ addString("revanced_cancel", "Cancel", false)
+
+ return PatchResultSuccess()
+ }
+
+ internal companion object {
+ fun addString(identifier: String, value: String, formatted: Boolean = true) =
+ SettingsResourcePatch.addString(identifier, value, formatted)
+
+ fun addPreferenceScreen(preferenceScreen: app.revanced.patches.shared.settings.preference.impl.PreferenceScreen) =
+ SettingsResourcePatch.addPreferenceScreen(preferenceScreen)
+
+ /* Private members */
+ private const val REVANCED_SETTINGS_MENU_ITEM_NAME = "RevancedSettings"
+ private const val REVANCED_SETTINGS_MENU_ITEM_ID = 0x7
+ private const val REVANCED_SETTINGS_MENU_ITEM_TITLE_RES = "revanced_settings"
+ private const val REVANCED_SETTINGS_MENU_ITEM_ICON_RES = "ic_settings"
+
+ private const val MENU_ITEM_ENUM_CLASS = "Ltv/twitch/android/feature/settings/menu/SettingsMenuItem;"
+ private const val MENU_DISMISS_EVENT_CLASS = "Ltv/twitch/android/feature/settings/menu/SettingsMenuViewDelegate\$Event\$OnDismissClicked;"
+
+ private const val INTEGRATIONS_PACKAGE = "app/revanced/twitch"
+ private const val SETTINGS_HOOKS_CLASS = "L$INTEGRATIONS_PACKAGE/settingsmenu/SettingsHooks;"
+ private const val REVANCED_UTILS_CLASS = "L$INTEGRATIONS_PACKAGE/utils/ReVancedUtils;"
+
+ private fun MethodFingerprintResult.injectMenuItem(
+ name: String,
+ value: Int,
+ titleResourceName: String,
+ iconResourceName: String
+ ) {
+ // Add new static enum member field
+ mutableClass.staticFields.add(
+ ImmutableField(
+ mutableMethod.definingClass,
+ name,
+ MENU_ITEM_ENUM_CLASS,
+ AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.ENUM or AccessFlags.STATIC,
+ null,
+ null,
+ null
+ ).toMutable()
+ )
+
+ // Add initializer for the new enum member
+ mutableMethod.addInstructions(
+ mutableMethod.implementation!!.instructions.size - 4,
+ """
+ new-instance v0, $MENU_ITEM_ENUM_CLASS
+ const-string v1, "$titleResourceName"
+ invoke-static {v1}, $REVANCED_UTILS_CLASS->getStringId(Ljava/lang/String;)I
+ move-result v1
+ const-string v3, "$iconResourceName"
+ invoke-static {v3}, $REVANCED_UTILS_CLASS->getDrawableId(Ljava/lang/String;)I
+ move-result v3
+ const-string v4, "$name"
+ const/4 v5, $value
+ invoke-direct {v0, v4, v5, v1, v3}, $MENU_ITEM_ENUM_CLASS->(Ljava/lang/String;III)V
+ sput-object v0, $MENU_ITEM_ENUM_CLASS->$name:$MENU_ITEM_ENUM_CLASS
+ """
+ )
+ }
+ }
+
+ /**
+ * Preference screens patches should add their settings to.
+ */
+ internal object PreferenceScreen : AbstractPreferenceScreen() {
+ val ADS = CustomScreen("ads", "Ads", "Ad blocking settings")
+ val CHAT = CustomScreen("chat", "Chat", "Chat settings")
+ val MISC = CustomScreen("misc", "Misc", "Miscellaneous patches")
+
+ internal class CustomScreen(key: String, title: String, summary: String) : Screen(key, title, summary) {
+ /* Categories */
+ val GENERAL = CustomCategory("general", "General settings")
+ val OTHER = CustomCategory("other", "Other settings")
+ val CLIENT_SIDE = CustomCategory("client_ads", "Client-side ads")
+ val SURESTREAM = CustomCategory("surestream_ads", "Server-side surestream ads")
+
+ internal inner class CustomCategory(key: String, title: String) : Screen.Category(key, title) {
+ /* For Twitch, we need to load our CustomPreferenceCategory class instead of the default one. */
+ override fun transform(): PreferenceCategory {
+ return CustomPreferenceCategory(
+ key,
+ StringResource("${key}_title", title),
+ preferences.sortedBy { it.title.value }
+ )
+ }
+ }
+ }
+
+ override fun commit(screen: app.revanced.patches.shared.settings.preference.impl.PreferenceScreen) {
+ addPreferenceScreen(screen)
+ }
+ }
+
+ override fun close() = PreferenceScreen.close()
+}
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/components/CustomPreferenceCategory.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/components/CustomPreferenceCategory.kt
new file mode 100644
index 0000000..20c7bdb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/components/CustomPreferenceCategory.kt
@@ -0,0 +1,20 @@
+package app.revanced.patches.twitch.misc.settings.components
+
+import app.revanced.patches.shared.settings.preference.BasePreference
+import app.revanced.patches.shared.settings.preference.impl.PreferenceCategory
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+
+/**
+ * Customized preference category for Twitch.
+ *
+ * @param key The key of the preference.
+ * @param title The title of the preference.
+ * @param preferences Child preferences of this category.
+ */
+internal open class CustomPreferenceCategory(
+ key: String,
+ title: StringResource,
+ preferences: List
+) : PreferenceCategory(key, title, preferences) {
+ override val tag: String = "app.revanced.twitch.settingsmenu.preference.CustomPreferenceCategory"
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsOnClickFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsOnClickFingerprint.kt
new file mode 100644
index 0000000..1a63759
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsOnClickFingerprint.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.twitch.misc.settings.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object MenuGroupsOnClickFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PRIVATE or AccessFlags.STATIC or AccessFlags.FINAL,
+ listOf("L", "L", "L"),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/SettingsMenuViewDelegate;")
+ && methodDef.name.contains("render")
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsUpdatedFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsUpdatedFingerprint.kt
new file mode 100644
index 0000000..913f31d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsUpdatedFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.misc.settings.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object MenuGroupsUpdatedFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/SettingsMenuPresenter\$Event\$MenuGroupsUpdated;")
+ && methodDef.name == ""
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsActivityOnCreateFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsActivityOnCreateFingerprint.kt
new file mode 100644
index 0000000..f976f02
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsActivityOnCreateFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.twitch.misc.settings.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SettingsActivityOnCreateFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/SettingsActivity;") &&
+ methodDef.name == "onCreate"
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsMenuItemEnumFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsMenuItemEnumFingerprint.kt
new file mode 100644
index 0000000..cdb2dd1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsMenuItemEnumFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitch.misc.settings.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SettingsMenuItemEnumFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("/SettingsMenuItem;") && methodDef.name == ""
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/resource/patch/SettingsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/resource/patch/SettingsResourcePatch.kt
new file mode 100644
index 0000000..07420ce
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/resource/patch/SettingsResourcePatch.kt
@@ -0,0 +1,44 @@
+package app.revanced.patches.twitch.misc.settings.resource.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patches.shared.settings.preference.impl.ArrayResource
+import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen
+import app.revanced.patches.shared.settings.resource.patch.AbstractSettingsResourcePatch
+import app.revanced.patches.twitch.misc.settings.annotations.SettingsCompatibility
+
+@Name("settings-resource-patch")
+@SettingsCompatibility
+@Version("0.0.1")
+class SettingsResourcePatch : AbstractSettingsResourcePatch(
+"revanced_prefs",
+"twitch/settings"
+) {
+ internal companion object {
+ /* Companion delegates */
+
+ /**
+ * Add a new string to the resources.
+ *
+ * @param identifier The key of the string.
+ * @param value The value of the string.
+ * @throws IllegalArgumentException if the string already exists.
+ */
+ fun addString(identifier: String, value: String, formatted: Boolean) =
+ AbstractSettingsResourcePatch.addString(identifier, value, formatted)
+
+ /**
+ * Add an array to the resources.
+ *
+ * @param arrayResource The array resource to add.
+ */
+ fun addArray(arrayResource: ArrayResource) = AbstractSettingsResourcePatch.addArray(arrayResource)
+
+ /**
+ * Add a preference to the settings.
+ *
+ * @param preferenceScreen The name of the preference screen.
+ */
+ fun addPreferenceScreen(preferenceScreen: PreferenceScreen) = AbstractSettingsResourcePatch.addPreference(preferenceScreen)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt
new file mode 100644
index 0000000..54ce876
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitter.ad.timeline.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.twitter.android")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class TimelineAdsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt
new file mode 100644
index 0000000..5d4b987
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt
@@ -0,0 +1,18 @@
+package app.revanced.patches.twitter.ad.timeline.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object TimelineTweetJsonParserFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.IPUT_OBJECT,
+ Opcode.GOTO,
+ Opcode.SGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.IPUT_OBJECT,
+ Opcode.RETURN_VOID,
+ ), strings = listOf("tweetPromotedMetadata", "promotedMetadata", "hasModeratedReplies", "conversationAnnotation"),
+ customFingerprint = { methodDef -> methodDef.name == "parseField" }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt
new file mode 100644
index 0000000..753a2ea
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt
@@ -0,0 +1,77 @@
+package app.revanced.patches.twitter.ad.timeline.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.twitter.ad.timeline.annotations.TimelineAdsCompatibility
+import app.revanced.patches.twitter.ad.timeline.fingerprints.TimelineTweetJsonParserFingerprint
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.builder.BuilderInstruction
+import org.jf.dexlib2.builder.instruction.BuilderInstruction22c
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.reference.FieldReference
+import org.jf.dexlib2.iface.reference.StringReference
+
+@Patch
+@Name("timeline-ads")
+@Description("Removes ads from the Twitter timeline. Might require clearing app data to remove already cached ads.")
+@TimelineAdsCompatibility
+@Version("0.0.1")
+class TimelineAdsPatch : BytecodePatch(
+ listOf(TimelineTweetJsonParserFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ if (removePromotedAds())
+ return PatchResultError("The instruction for the tweet id field could not be found")
+
+ return PatchResultSuccess()
+ }
+
+ private fun removePromotedAds(): Boolean {
+ val (parserFingerprintResult, parserMethod, instructions) = TimelineTweetJsonParserFingerprint.unwrap()
+
+ // Anchor index
+ val tweetIdFieldInstructionIndex = instructions.indexOfFirst { instruction ->
+ if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@indexOfFirst false
+ if (((instruction as? ReferenceInstruction)?.reference as StringReference).string != "tweetSocialProof") return@indexOfFirst false
+
+ // Use the above conditions as an anchor to find the index for the instruction with the field we need
+ return@indexOfFirst true
+ } - 2 // This is where the instruction with the field is located
+
+ // Reference to the tweetId field for of the timeline tweet
+ val tweetIdFieldReference =
+ (parserMethod.instruction(tweetIdFieldInstructionIndex) as? BuilderInstruction22c)?.reference as? FieldReference
+ ?: return true
+
+ // Set the tweetId field to null
+ // This will cause twitter to not show the promoted ads, because we set it to null, when the tweet is promoted
+ parserFingerprintResult.mutableMethod.addInstructions(
+ parserFingerprintResult.scanResult.patternScanResult!!.startIndex + 1,
+ """
+ const/4 v2, 0x0
+ iput-object v2, p0, Lcom/twitter/model/json/timeline/urt/JsonTimelineTweet;->${tweetIdFieldReference.name}:Ljava/lang/String;
+ """
+ )
+ return false
+ }
+
+ private fun MethodFingerprint.unwrap(): Triple> {
+ val parserFingerprintResult = this.result!!
+ val parserMethod = parserFingerprintResult.mutableMethod
+ val instructions = parserMethod.implementation!!.instructions
+
+ return Triple(parserFingerprintResult, parserMethod, instructions)
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/annotations/DynamicColorCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/annotations/DynamicColorCompatibility.kt
new file mode 100644
index 0000000..135276a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/annotations/DynamicColorCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitter.misc.dynamiccolor.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.twitter.android")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class DynamicColorCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/patch/DynamicColorPatch.kt b/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/patch/DynamicColorPatch.kt
new file mode 100644
index 0000000..3ba3b56
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/patch/DynamicColorPatch.kt
@@ -0,0 +1,87 @@
+package app.revanced.patches.twitter.misc.dynamiccolor.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.twitter.misc.dynamiccolor.annotations.DynamicColorCompatibility
+import java.io.FileWriter
+import java.nio.file.Files
+
+@Patch
+@Name("dynamic-color")
+@Description("Replaces the default Twitter Blue with the users Material You palette.")
+@DynamicColorCompatibility
+@Version("0.0.1")
+class DynamicColorPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ val resDirectory = context["res"]
+ if (!resDirectory.isDirectory) return PatchResultError("The res folder can not be found.")
+
+ val valuesV31Directory = resDirectory.resolve("values-v31")
+ if (!valuesV31Directory.isDirectory) Files.createDirectories(valuesV31Directory.toPath())
+
+ val valuesNightV31Directory = resDirectory.resolve("values-night-v31")
+ if (!valuesNightV31Directory.isDirectory) Files.createDirectories(valuesNightV31Directory.toPath())
+
+ listOf(valuesV31Directory, valuesNightV31Directory).forEach { it ->
+ val colorsXml = it.resolve("colors.xml")
+
+ if(!colorsXml.exists()) {
+ FileWriter(colorsXml).use {
+ it.write("")
+ }
+ }
+ }
+
+ context.xmlEditor["res/values-v31/colors.xml"].use { editor ->
+ val document = editor.file
+
+ mapOf(
+ "ps__twitter_blue" to "@color/twitter_blue",
+ "ps__twitter_blue_pressed" to "@color/twitter_blue_fill_pressed",
+ "twitter_blue" to "@android:color/system_accent1_400",
+ "twitter_blue_fill_pressed" to "@android:color/system_accent1_300",
+ "twitter_blue_opacity_30" to "@android:color/system_accent1_100",
+ "twitter_blue_opacity_50" to "@android:color/system_accent1_200",
+ "twitter_blue_opacity_58" to "@android:color/system_accent1_300",
+ "deep_transparent_twitter_blue" to "@android:color/system_accent1_200",
+ "ic_launcher_background" to "#1DA1F2"
+ ).forEach { (k, v) ->
+ val colorElement = document.createElement("color")
+
+ colorElement.setAttribute("name", k)
+ colorElement.textContent = v
+
+ document.getElementsByTagName("resources").item(0).appendChild(colorElement)
+ }
+ }
+
+ context.xmlEditor["res/values-night-v31/colors.xml"].use { editor ->
+ val document = editor.file
+
+ mapOf(
+ "twitter_blue" to "@android:color/system_accent1_200",
+ "twitter_blue_fill_pressed" to "@android:color/system_accent1_300",
+ "twitter_blue_opacity_30" to "@android:color/system_accent1_50",
+ "twitter_blue_opacity_50" to "@android:color/system_accent1_100",
+ "twitter_blue_opacity_58" to "@android:color/system_accent1_200",
+ "deep_transparent_twitter_blue" to "@android:color/system_accent1_200"
+ ).forEach { (k, v) ->
+ val colorElement = document.createElement("color")
+
+ colorElement.setAttribute("name", k)
+ colorElement.textContent = v
+
+ document.getElementsByTagName("resources").item(0).appendChild(colorElement)
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/annotations/MonochromeIconCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/annotations/MonochromeIconCompatibility.kt
new file mode 100644
index 0000000..646ad2b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/annotations/MonochromeIconCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.twitter.misc.monochrome.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.twitter.android")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class MonochromeIconCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/patch/MonochromeIconPatch.kt b/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/patch/MonochromeIconPatch.kt
new file mode 100644
index 0000000..6e83ca5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/patch/MonochromeIconPatch.kt
@@ -0,0 +1,55 @@
+package app.revanced.patches.twitter.misc.monochrome.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.twitter.misc.monochrome.annotations.MonochromeIconCompatibility
+import java.io.FileWriter
+import java.nio.file.Files
+
+@Patch
+@Name("monochrome-icon")
+@Description("Adds a monochrome icon.")
+@MonochromeIconCompatibility
+@Version("0.0.1")
+class MonochromeIconPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ val resDirectory = context["res"]
+ if (!resDirectory.isDirectory) return PatchResultError("The res folder can not be found.")
+
+ val mipmapV33Directory = resDirectory.resolve("mipmap-anydpi-v33")
+ if (!mipmapV33Directory.isDirectory) Files.createDirectories(mipmapV33Directory.toPath())
+
+ FileWriter(mipmapV33Directory.resolve("ic_launcher_twitter.xml")).use {
+ it.write(
+ "\n" +
+ "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ ""
+ )
+ }
+
+ FileWriter(mipmapV33Directory.resolve("ic_launcher_twitter_round.xml")).use {
+ it.write(
+ "\n" +
+ "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ ""
+ )
+ }
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/annotations/FirebaseGetCertPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/annotations/FirebaseGetCertPatchCompatibility.kt
new file mode 100644
index 0000000..a87fa53
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/annotations/FirebaseGetCertPatchCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.warnwetter.misc.firebasegetcert.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("de.dwd.warnapp")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class FirebaseGetCertPatchCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertMessagingFingerprint.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertMessagingFingerprint.kt
new file mode 100644
index 0000000..196f649
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertMessagingFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.warnwetter.misc.firebasegetcert.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object GetMessagingCertFingerprint : MethodFingerprint(
+ "Ljava/lang/String;",
+ strings = listOf(
+ "ContentValues",
+ "Could not get fingerprint hash for package: ",
+ "No such package: "
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertRegistrationFingerprint.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertRegistrationFingerprint.kt
new file mode 100644
index 0000000..8c1fb8c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertRegistrationFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.warnwetter.misc.firebasegetcert.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object GetReqistrationCertFingerprint : MethodFingerprint(
+ "Ljava/lang/String;",
+ strings = listOf(
+ "FirebaseRemoteConfig",
+ "Could not get fingerprint hash for package: ",
+ "No such package: "
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/patch/FirebaseGetCertPatch.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/patch/FirebaseGetCertPatch.kt
new file mode 100644
index 0000000..c7dd384
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/patch/FirebaseGetCertPatch.kt
@@ -0,0 +1,46 @@
+package app.revanced.patches.warnwetter.misc.firebasegetcert.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patches.warnwetter.misc.firebasegetcert.annotations.FirebaseGetCertPatchCompatibility
+import app.revanced.patches.warnwetter.misc.firebasegetcert.fingerprints.GetMessagingCertFingerprint
+import app.revanced.patches.warnwetter.misc.firebasegetcert.fingerprints.GetReqistrationCertFingerprint
+
+@Name("spoof-cert-patch")
+@Description("Spoofs the X-Android-Cert header.")
+@FirebaseGetCertPatchCompatibility
+@Version("0.0.1")
+class FirebaseGetCertPatch : BytecodePatch(
+ listOf(
+ GetReqistrationCertFingerprint,
+ GetMessagingCertFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val spoofedInstruction =
+ """
+ const-string v0, "0799DDF0414D3B3475E88743C91C0676793ED450"
+ return-object v0
+ """
+
+ val registrationCertMethod = GetReqistrationCertFingerprint.result!!.mutableMethod
+ val messagingCertMethod = GetMessagingCertFingerprint.result!!.mutableMethod
+
+ registrationCertMethod.addInstructions(
+ 0,
+ spoofedInstruction
+ )
+ messagingCertMethod.addInstructions(
+ 0,
+ spoofedInstruction
+ )
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/annotations/PromoCodeUnlockCompatibility.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/annotations/PromoCodeUnlockCompatibility.kt
new file mode 100644
index 0000000..f4c2cc1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/annotations/PromoCodeUnlockCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.warnwetter.misc.promocode.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("de.dwd.warnapp")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PromoCodeUnlockCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/fingerprints/PromoCodeUnlockFingerprint.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/fingerprints/PromoCodeUnlockFingerprint.kt
new file mode 100644
index 0000000..5b0dd14
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/fingerprints/PromoCodeUnlockFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.warnwetter.misc.promocode.fingerprints
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PromoCodeUnlockFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("PromoTokenVerification;") && methodDef.name == "isValid"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/patch/PromoCodeUnlockPatch.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/patch/PromoCodeUnlockPatch.kt
new file mode 100644
index 0000000..68ee1de
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/patch/PromoCodeUnlockPatch.kt
@@ -0,0 +1,47 @@
+package app.revanced.patches.warnwetter.misc.promocode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.warnwetter.misc.firebasegetcert.patch.FirebaseGetCertPatch
+import app.revanced.patches.warnwetter.misc.promocode.annotations.PromoCodeUnlockCompatibility
+import app.revanced.patches.warnwetter.misc.promocode.fingerprints.PromoCodeUnlockFingerprint
+
+@DependsOn(
+ [
+ FirebaseGetCertPatch::class
+ ]
+)
+@Patch
+@Name("promo-code-unlock")
+@Description("Disables the validation of promo code. Any code will work to unlock all features.")
+@PromoCodeUnlockCompatibility
+@Version("0.0.1")
+class PromoCodeUnlockPatch : BytecodePatch(
+ listOf(
+ PromoCodeUnlockFingerprint
+ )
+) {
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = PromoCodeUnlockFingerprint.result!!.mutableMethod
+ method.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x1
+ return v0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/annotations/UnlockProCompatibility.kt
new file mode 100644
index 0000000..b4b99e4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/annotations/UnlockProCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.windyapp.misc.pro.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("co.windyapp.android")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class UnlockProCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/fingerprints/CheckProFingerprint.kt b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/fingerprints/CheckProFingerprint.kt
new file mode 100644
index 0000000..230f83a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/fingerprints/CheckProFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.windyapp.misc.pro.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CheckProFingerprint : MethodFingerprint(
+ "I",
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("RawUserData;") && methodDef.name == "isPro"
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/patch/UnlockProPatch.kt
new file mode 100644
index 0000000..fac915b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/patch/UnlockProPatch.kt
@@ -0,0 +1,38 @@
+package app.revanced.patches.windyapp.misc.pro.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.windyapp.misc.pro.annotations.UnlockProCompatibility
+import app.revanced.patches.windyapp.misc.pro.fingerprints.CheckProFingerprint
+
+@Patch
+@Name("unlock-pro")
+@Description("Unlocks all pro features.")
+@UnlockProCompatibility
+@Version("0.0.1")
+class UnlockProPatch : BytecodePatch(
+ listOf(
+ CheckProFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val method = CheckProFingerprint.result!!.mutableMethod
+ method.addInstructions(
+ 0,
+ """
+ const/16 v0, 0x1
+ return v0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/annotation/GeneralAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/annotation/GeneralAdsCompatibility.kt
new file mode 100644
index 0000000..c5ec9b4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/annotation/GeneralAdsCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.ad.general.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class GeneralAdsCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/fingerprints/ReelConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/fingerprints/ReelConstructorFingerprint.kt
new file mode 100644
index 0000000..b6140dc
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/fingerprints/ReelConstructorFingerprint.kt
@@ -0,0 +1,17 @@
+package app.revanced.patches.youtube.ad.general.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.ad.general.resource.patch.GeneralAdsResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object ReelConstructorFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_VIRTUAL
+ ),
+ customFingerprint = { method ->
+ method.implementation?.instructions?.any {
+ it.opcode == Opcode.CONST && (it as WideLiteralInstruction).wideLiteral == GeneralAdsResourcePatch.reelMultipleItemShelfId
+ } ?: false
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/patch/GeneralAdsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/patch/GeneralAdsPatch.kt
new file mode 100644
index 0000000..219e02a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/patch/GeneralAdsPatch.kt
@@ -0,0 +1,91 @@
+package app.revanced.patches.youtube.ad.general.bytecode.patch
+
+import app.revanced.extensions.findMutableMethodOf
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.youtube.ad.general.annotation.GeneralAdsCompatibility
+import app.revanced.patches.youtube.ad.general.bytecode.fingerprints.ReelConstructorFingerprint
+import app.revanced.patches.youtube.ad.general.resource.patch.GeneralAdsResourcePatch
+import app.revanced.patches.youtube.misc.fix.verticalscroll.patch.VerticalScrollPatch
+import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction
+import org.jf.dexlib2.iface.instruction.formats.Instruction31i
+import org.jf.dexlib2.iface.instruction.formats.Instruction35c
+
+
+@Patch
+@DependsOn([GeneralAdsResourcePatch::class, VerticalScrollPatch::class])
+@Name("general-ads")
+@Description("Removes general ads.")
+@GeneralAdsCompatibility
+@Version("0.0.1")
+class GeneralAdsPatch : BytecodePatch(
+ listOf(ReelConstructorFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ fun String.buildHideCall(viewRegister: Int) = "invoke-static { v$viewRegister }, " +
+ "Lapp/revanced/integrations/patches/GeneralAdsPatch;" +
+ "->" +
+ "$this(Landroid/view/View;)V"
+
+ fun MutableMethod.injectHideCall(insertIndex: Int, viewRegister: Int, method: String) =
+ this.addInstruction(insertIndex, method.buildHideCall(viewRegister))
+
+ context.classes.forEach { classDef ->
+ classDef.methods.forEach { method ->
+ with(method.implementation) {
+ this?.instructions?.forEachIndexed { index, instruction ->
+ if (instruction.opcode != org.jf.dexlib2.Opcode.CONST)
+ return@forEachIndexed
+ // Instruction to store the id adAttribution into a register
+ if ((instruction as Instruction31i).wideLiteral != GeneralAdsResourcePatch.adAttributionId)
+ return@forEachIndexed
+
+ val insertIndex = index + 1
+
+ // Call to get the view with the id adAttribution
+ with(instructions.elementAt(insertIndex)) {
+ if (opcode != org.jf.dexlib2.Opcode.INVOKE_VIRTUAL)
+ return@forEachIndexed
+
+ // Hide the view
+ val viewRegister = (this as Instruction35c).registerC
+ context.proxy(classDef)
+ .mutableClass
+ .findMutableMethodOf(method)
+ .injectHideCall(insertIndex, viewRegister, "hideAdAttributionView")
+ }
+ }
+ }
+ }
+ }
+
+ with(
+ ReelConstructorFingerprint.result
+ ?: return PatchResultError("Could not resolve fingerprint")
+ ) {
+ // iput-object v$viewRegister, ...
+ val insertIndex = this.scanResult.patternScanResult!!.startIndex + 2
+
+ with(this.mutableMethod) {
+ val viewRegister = (instruction(insertIndex) as TwoRegisterInstruction).registerA
+
+ injectHideCall(insertIndex, viewRegister, "hideReelView")
+ }
+
+ }
+
+ return PatchResultSuccess()
+ }
+
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/resource/patch/GeneralAdsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/resource/patch/GeneralAdsResourcePatch.kt
new file mode 100644
index 0000000..af5afea
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/resource/patch/GeneralAdsResourcePatch.kt
@@ -0,0 +1,232 @@
+package app.revanced.patches.youtube.ad.general.resource.patch
+
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.shared.settings.preference.impl.InputType
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.shared.settings.preference.impl.TextPreference
+import app.revanced.patches.youtube.ad.general.annotation.GeneralAdsCompatibility
+import app.revanced.patches.youtube.misc.litho.filter.patch.LithoFilterPatch
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch.PreferenceScreen
+
+@DependsOn(dependencies = [
+ FixLocaleConfigErrorPatch::class,
+ LithoFilterPatch::class,
+ SettingsPatch::class,
+ ResourceMappingPatch::class
+])
+@GeneralAdsCompatibility
+@Version("0.0.1")
+class GeneralAdsResourcePatch : ResourcePatch {
+ internal companion object {
+ var adAttributionId: Long = -1
+ var reelMultipleItemShelfId: Long = -1
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ PreferenceScreen.ADS.addPreferences(
+ SwitchPreference(
+ "revanced_adremover_ad_removal",
+ StringResource("revanced_adremover_ad_removal_enabled_title", "Hide general ads"),
+ true,
+ StringResource("revanced_adremover_ad_removal_enabled_summary_on", "General ads are hidden"),
+ StringResource("revanced_adremover_ad_removal_enabled_summary_off", "General ads are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_buttoned",
+ StringResource("revanced_adremover_buttoned_enabled_title", "Hide buttoned ad"),
+ true,
+ StringResource("revanced_adremover_buttoned_enabled_summary_on", "Buttoned ads are hidden"),
+ StringResource("revanced_adremover_buttoned_enabled_summary_off", "Buttoned ads are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_merchandise",
+ StringResource("revanced_adremover_merchandise_enabled_title", "Hide merchandise banners"),
+ true,
+ StringResource("revanced_adremover_merchandise_enabled_summary_on", "Merchandise banners are hidden"),
+ StringResource("revanced_adremover_merchandise_enabled_summary_off", "Merchandise banners are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_community_posts_removal",
+ StringResource("revanced_adremover_community_posts_enabled_title", "Hide community posts"),
+ false,
+ StringResource("revanced_adremover_community_posts_enabled_summary_on", "Community posts are hidden"),
+ StringResource("revanced_adremover_community_posts_enabled_summary_off", "Community posts are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_compact_banner_removal",
+ StringResource("revanced_adremover_compact_banner_enabled_title", "Hide compact banners"),
+ true,
+ StringResource("revanced_adremover_compact_banner_enabled_summary_on", "Compact banners are hidden"),
+ StringResource("revanced_adremover_compact_banner_enabled_summary_off", "Compact banners are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_movie",
+ StringResource("revanced_adremover_movie_enabled_title", "Hide movies section"),
+ true,
+ StringResource("revanced_adremover_movie_enabled_summary_on", "Movies section is hidden"),
+ StringResource("revanced_adremover_movie_enabled_summary_off", "Movies section is shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_feed_survey",
+ StringResource("revanced_adremover_feed_survey_enabled_title", "Hide feed surveys"),
+ true,
+ StringResource("revanced_adremover_feed_survey_enabled_summary_on", "Feed surveys are hidden"),
+ StringResource("revanced_adremover_feed_survey_enabled_summary_off", "Feed surveys are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_shorts",
+ StringResource("revanced_adremover_shorts_enabled_title", "Hide shorts"),
+ true,
+ StringResource("revanced_adremover_shorts_enabled_summary_on", "Shorts are hidden"),
+ StringResource("revanced_adremover_shorts_enabled_summary_off", "Shorts are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_community_guidelines",
+ StringResource("revanced_adremover_community_guidelines_enabled_title", "Hide community guidelines"),
+ true,
+ StringResource(
+ "revanced_adremover_community_guidelines_enabled_summary_on",
+ "Community guidelines are hidden"
+ ),
+ StringResource(
+ "revanced_adremover_community_guidelines_enabled_summary_off",
+ "Community guidelines are shown"
+ )
+ ),
+ SwitchPreference(
+ "revanced_adremover_emergency_box_removal",
+ StringResource("revanced_adremover_emergency_box_enabled_title", "Hide emergency boxes"),
+ true,
+ StringResource("revanced_adremover_emergency_box_enabled_summary_on", "Emergency boxes are hidden"),
+ StringResource("revanced_adremover_emergency_box_enabled_summary_off", "Emergency boxes are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_info_panel",
+ StringResource("revanced_adremover_info_panel_enabled_title", "Hide info panels"),
+ true,
+ StringResource("revanced_adremover_info_panel_enabled_summary_on", "Info panels are hidden"),
+ StringResource("revanced_adremover_info_panel_enabled_summary_off", "Info panels are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_medical_panel",
+ StringResource("revanced_adremover_medical_panel_enabled_title", "Hide medical panels"),
+ true,
+ StringResource("revanced_adremover_medical_panel_enabled_summary_on", "Medical panels are hidden"),
+ StringResource("revanced_adremover_medical_panel_enabled_summary_off", "Medical panels are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_paid_content",
+ StringResource("revanced_adremover_paid_content_enabled_title", "Hide paid content"),
+ true,
+ StringResource("revanced_adremover_paid_content_enabled_summary_on", "Paid content is hidden"),
+ StringResource("revanced_adremover_paid_content_enabled_summary_off", "Paid content is shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_hide_suggestions",
+ StringResource("revanced_adremover_hide_suggestions_enabled_title", "Hide suggestions"),
+ true,
+ StringResource("revanced_adremover_hide_suggestions_enabled_summary_on", "Suggestions are hidden"),
+ StringResource("revanced_adremover_hide_suggestions_enabled_summary_off", "Suggestions are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_hide_latest_posts",
+ StringResource("revanced_adremover_hide_latest_posts_enabled_title", "Hide latest posts"),
+ true,
+ StringResource("revanced_adremover_hide_latest_posts_enabled_summary_on", "Latest posts are hidden"),
+ StringResource("revanced_adremover_hide_latest_posts_enabled_summary_off", "Latest posts are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_hide_channel_guidelines",
+ StringResource("revanced_adremover_hide_channel_guidelines_enabled_title", "Hide channel guidelines"),
+ true,
+ StringResource(
+ "revanced_adremover_hide_channel_guidelines_enabled_summary_on",
+ "Channel guidelines are hidden"
+ ),
+ StringResource(
+ "revanced_adremover_hide_channel_guidelines_enabled_summary_off",
+ "Channel guidelines are shown"
+ )
+ ),
+ SwitchPreference(
+ "revanced_adremover_self_sponsor",
+ StringResource("revanced_adremover_self_sponsor_enabled_title", "Hide self sponsored cards"),
+ true,
+ StringResource("revanced_adremover_self_sponsor_enabled_summary_on", "Self sponsored cards are hidden"),
+ StringResource("revanced_adremover_self_sponsor_enabled_summary_off", "Self sponsored cards are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_separator",
+ StringResource("revanced_adremover_separator_title", "Hide gray separator"),
+ true,
+ StringResource("revanced_adremover_separator_summary_on", "Gray separators are hidden"),
+ StringResource("revanced_adremover_separator_summary_off", "Gray separators are shown")
+ ),
+ SwitchPreference(
+ "revanced_adremover_chapter_teaser",
+ StringResource(
+ "revanced_adremover_chapter_teaser_enabled_title",
+ "Hide chapter teaser under videos"
+ ),
+ true,
+ StringResource(
+ "revanced_adremover_chapter_teaser_enabled_summary_on",
+ "Chapter teasers are hidden"
+ ),
+ StringResource(
+ "revanced_adremover_chapter_teaser_enabled_summary_off",
+ "Chapter teasers are shown"
+ )
+ ),
+ app.revanced.patches.shared.settings.preference.impl.PreferenceScreen(
+ "revanced_adremover_custom",
+ StringResource("revanced_adremover_custom_title", "Custom filter"),
+ listOf(
+ SwitchPreference(
+ "revanced_adremover_custom_enabled",
+ StringResource(
+ "revanced_adremover_custom_enabled_title",
+ "Enable custom filter"
+ ),
+ false,
+ StringResource(
+ "revanced_adremover_custom_enabled_summary_on",
+ "Custom filter is enabled"
+ ),
+ StringResource(
+ "revanced_adremover_custom_enabled_summary_off",
+ "Custom filter is disabled"
+ )
+ ),
+ // TODO: This should be a ListPreference, which does not exist yet
+ TextPreference(
+ "revanced_adremover_custom_strings",
+ StringResource("revanced_adremover_custom_strings_title", "Custom filter"),
+ InputType.STRING,
+ "",
+ StringResource(
+ "revanced_adremover_custom_strings_summary",
+ "Filter components by their name separated by a comma"
+ )
+ )
+ )
+ )
+ )
+
+ fun String.getId() = ResourceMappingPatch.resourceMappings.single { it.name == this }.id
+
+ adAttributionId = "ad_attribution".getId()
+ reelMultipleItemShelfId = "reel_multiple_items_shelf".getId()
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/video/annotations/VideoAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/video/annotations/VideoAdsCompatibility.kt
new file mode 100644
index 0000000..daf8278
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/ad/video/annotations/VideoAdsCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.ad.video.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class VideoAdsCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/video/fingerprints/LoadVideoAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/video/fingerprints/LoadVideoAdsFingerprint.kt
new file mode 100644
index 0000000..b5a4c70
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/ad/video/fingerprints/LoadVideoAdsFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.ad.video.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object LoadVideoAdsFingerprint : MethodFingerprint(
+ strings = listOf(
+ "TriggerBundle doesn't have the required metadata specified by the trigger ",
+ "Tried to enter slot with no assigned slotAdapter",
+ "Trying to enter a slot when a slot of same type and physical position is already active. Its status: ",
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/video/patch/VideoAdsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/video/patch/VideoAdsPatch.kt
new file mode 100644
index 0000000..5e16901
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/ad/video/patch/VideoAdsPatch.kt
@@ -0,0 +1,58 @@
+package app.revanced.patches.youtube.ad.video.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.ad.video.annotations.VideoAdsCompatibility
+import app.revanced.patches.youtube.ad.video.fingerprints.LoadVideoAdsFingerprint
+import app.revanced.patches.youtube.misc.fix.playback.patch.FixPlaybackPatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class, FixPlaybackPatch::class])
+@Name("video-ads")
+@Description("Removes ads in the video player.")
+@VideoAdsCompatibility
+@Version("0.0.1")
+class VideoAdsPatch : BytecodePatch(
+ listOf(
+ LoadVideoAdsFingerprint,
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.ADS.addPreferences(
+ SwitchPreference(
+ "revanced_video_ads_removal",
+ StringResource("revanced_video_ads_removal_title", "Hide video ads"),
+ true,
+ StringResource("revanced_video_ads_removal_summary_on", "Video ads are hidden"),
+ StringResource("revanced_video_ads_removal_summary_off", "Video ads are shown")
+ )
+ )
+
+ val loadVideoAdsFingerprintMethod = LoadVideoAdsFingerprint.result!!.mutableMethod
+
+ loadVideoAdsFingerprintMethod.addInstructions(
+ 0, """
+ invoke-static { }, Lapp/revanced/integrations/patches/VideoAdsPatch;->shouldShowAds()Z
+ move-result v0
+ if-nez v0, :show_video_ads
+ return-void
+ """, listOf(ExternalLabel("show_video_ads", loadVideoAdsFingerprintMethod.instruction(0)))
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt
new file mode 100644
index 0000000..3f1e339
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.interaction.downloads.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class DownloadsCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt
new file mode 100644
index 0000000..c5e6ba0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt
@@ -0,0 +1,52 @@
+package app.revanced.patches.youtube.interaction.downloads.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.interaction.downloads.annotation.DownloadsCompatibility
+import app.revanced.patches.youtube.interaction.downloads.resource.patch.DownloadsResourcePatch
+import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch
+import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch
+
+@Patch
+@Name("downloads")
+@DependsOn([DownloadsResourcePatch::class, PlayerControlsBytecodePatch::class, VideoIdPatch::class])
+@Description("Enables downloading music and videos from YouTube.")
+@DownloadsCompatibility
+@Version("0.0.1")
+class DownloadsBytecodePatch : BytecodePatch() {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val integrationsPackage = "app/revanced/integrations"
+ val classDescriptor = "L$integrationsPackage/videoplayer/DownloadButton;"
+
+ /*
+ initialize the control
+ */
+
+ val initializeDownloadsDescriptor = "$classDescriptor->initializeDownloadButton(Ljava/lang/Object;)V"
+ PlayerControlsBytecodePatch.initializeControl(initializeDownloadsDescriptor)
+
+ /*
+ add code to change the visibility of the control
+ */
+
+ val changeVisibilityDescriptor = "$classDescriptor->changeVisibility(Z)V"
+ PlayerControlsBytecodePatch.injectVisibilityCheckCall(changeVisibilityDescriptor)
+
+ /*
+ add code to change to update the video id
+ */
+
+ val setVideoIdDescriptor =
+ "L$integrationsPackage/patches/downloads/DownloadsPatch;->setVideoId(Ljava/lang/String;)V"
+ VideoIdPatch.injectCall(setVideoIdDescriptor)
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt
new file mode 100644
index 0000000..0ea6834
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt
@@ -0,0 +1,72 @@
+package app.revanced.patches.youtube.interaction.downloads.resource.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.settings.preference.impl.*
+import app.revanced.patches.youtube.interaction.downloads.annotation.DownloadsCompatibility
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import app.revanced.patches.youtube.misc.playercontrols.resource.patch.BottomControlsResourcePatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.util.resources.ResourceUtils
+import app.revanced.util.resources.ResourceUtils.copyResources
+import app.revanced.util.resources.ResourceUtils.mergeStrings
+
+@Name("downloads-resource-patch")
+@DependsOn([BottomControlsResourcePatch::class, FixLocaleConfigErrorPatch::class, SettingsPatch::class])
+@Description("Makes necessary changes to resources for the download button.")
+@DownloadsCompatibility
+@Version("0.0.1")
+class DownloadsResourcePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.INTERACTIONS.addPreferences(
+ PreferenceScreen(
+ "revanced_downloads",
+ StringResource("revanced_downloads_title", "Download settings"),
+ listOf(
+ SwitchPreference(
+ "revanced_downloads",
+ StringResource("revanced_downloads_enabled_title", "Show download button"),
+ true,
+ StringResource("revanced_downloads_enabled_summary_on", "Download button is shown"),
+ StringResource("revanced_downloads_enabled_summary_off", "Download button is not shown")
+ ),
+ TextPreference(
+ "revanced_downloads_package_name",
+ StringResource("revanced_downloads_package_name_title", "Downloader package name"),
+ InputType.STRING,
+ "org.schabi.newpipe" /* NewPipe */,
+ StringResource("revanced_downloads_package_name_summary", "Package name of the downloader app such as NewPipe\\'s or PowerTube\\'s")
+ )
+ ),
+ StringResource("revanced_downloads_summary", "Settings related to downloads")
+ )
+ )
+
+
+ /*
+ * Copy strings
+ */
+
+ context.mergeStrings("downloads/host/values/strings.xml")
+
+ /*
+ * Copy resources
+ */
+
+ context.copyResources("downloads", ResourceUtils.ResourceGroup("drawable", "revanced_yt_download_button.xml"))
+
+ /*
+ * Add download button node
+ */
+
+ BottomControlsResourcePatch.addControls("downloads/host/layout/${BottomControlsResourcePatch.TARGET_RESOURCE_NAME}")
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/annotation/SeekbarTappingCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/annotation/SeekbarTappingCompatibility.kt
new file mode 100644
index 0000000..5f282ec
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/annotation/SeekbarTappingCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.interaction.seekbar.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class SeekbarTappingCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt
new file mode 100644
index 0000000..94d6b2c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt
@@ -0,0 +1,34 @@
+package app.revanced.patches.youtube.interaction.seekbar.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object SeekbarTappingFingerprint : MethodFingerprint(
+ "Z", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_WIDE,
+ Opcode.IGET,
+ Opcode.IGET_OBJECT,
+ Opcode.IGET,
+ Opcode.DIV_INT_2ADDR,
+ Opcode.ADD_INT,
+ Opcode.SUB_INT_2ADDR,
+ Opcode.INT_TO_FLOAT,
+ Opcode.CMPG_FLOAT,
+ Opcode.IF_GTZ,
+ Opcode.INT_TO_FLOAT,
+ Opcode.CMPG_FLOAT,
+ Opcode.IF_GTZ,
+ Opcode.CONST_4,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.INVOKE_VIRTUAL
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingParentFingerprint.kt
new file mode 100644
index 0000000..20f83ef
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingParentFingerprint.kt
@@ -0,0 +1,44 @@
+package app.revanced.patches.youtube.interaction.seekbar.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object SeekbarTappingParentFingerprint : MethodFingerprint(
+ "L", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CONST_4,
+ Opcode.NEW_ARRAY,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_WIDE,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CONST_4,
+ Opcode.APUT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_WIDE,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CONST_4,
+ Opcode.APUT_OBJECT,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.RETURN_OBJECT
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/patch/EnableSeekbarTappingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/patch/EnableSeekbarTappingPatch.kt
new file mode 100644
index 0000000..fed7924
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/patch/EnableSeekbarTappingPatch.kt
@@ -0,0 +1,110 @@
+package app.revanced.patches.youtube.interaction.seekbar.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.interaction.seekbar.annotation.SeekbarTappingCompatibility
+import app.revanced.patches.youtube.interaction.seekbar.fingerprints.SeekbarTappingFingerprint
+import app.revanced.patches.youtube.interaction.seekbar.fingerprints.SeekbarTappingParentFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.builder.instruction.BuilderInstruction21t
+import org.jf.dexlib2.iface.Method
+import org.jf.dexlib2.iface.instruction.formats.Instruction11n
+import org.jf.dexlib2.iface.instruction.formats.Instruction35c
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("seekbar-tapping")
+@Description("Enables tap-to-seek on the seekbar of the video player.")
+@SeekbarTappingCompatibility
+@Version("0.0.1")
+class EnableSeekbarTappingPatch : BytecodePatch(
+ listOf(
+ SeekbarTappingParentFingerprint, SeekbarTappingFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.INTERACTIONS.addPreferences(
+ SwitchPreference(
+ "revanced_enable_tap_seeking",
+ StringResource("revanced_seekbar_tapping_enabled_title", "Enable seekbar tapping"),
+ true,
+ StringResource("revanced_seekbar_tapping_summary_on", "Seekbar tapping is enabled"),
+ StringResource("revanced_seekbar_tapping_summary_off", "Seekbar tapping is disabled")
+ )
+ )
+
+ var result = SeekbarTappingParentFingerprint.result!!
+
+ val tapSeekMethods = mutableMapOf()
+
+ // find the methods which tap the seekbar
+ for (it in result.classDef.methods) {
+ if (it.implementation == null) continue
+
+ val instructions = it.implementation!!.instructions
+ // here we make sure we actually find the method because it has more than 7 instructions
+ if (instructions.count() < 7) continue
+
+ // we know that the 7th instruction has the opcode CONST_4
+ val instruction = instructions.elementAt(6)
+ if (instruction.opcode != Opcode.CONST_4) continue
+
+ // the literal for this instruction has to be either 1 or 2
+ val literal = (instruction as Instruction11n).narrowLiteral
+
+ // method founds
+ if (literal == 1) tapSeekMethods["P"] = it
+ if (literal == 2) tapSeekMethods["O"] = it
+ }
+
+ // replace map because we don't need the upper one anymore
+ result = SeekbarTappingFingerprint.result!!
+
+ val implementation = result.mutableMethod.implementation!!
+
+ // if tap-seeking is enabled, do not invoke the two methods below
+ val pMethod = tapSeekMethods["P"]!!
+ val oMethod = tapSeekMethods["O"]!!
+
+ val insertIndex = result.scanResult.patternScanResult!!.endIndex + 1
+
+ // get the required register
+ val instruction = implementation.instructions[insertIndex - 1]
+ if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return PatchResultError("Could not find the correct register")
+ val register = (instruction as Instruction35c).registerC
+
+ val elseLabel = implementation.newLabelForIndex(insertIndex)
+ // the instructions are written in reverse order.
+ result.mutableMethod.addInstructions(
+ insertIndex, """
+ invoke-virtual { v$register, v2 }, ${oMethod.definingClass}->${oMethod.name}(I)V
+ invoke-virtual { v$register, v2 }, ${pMethod.definingClass}->${pMethod.name}(I)V
+ """
+ )
+
+ // if tap-seeking is disabled, do not invoke the two methods above by jumping to the else label
+ implementation.addInstruction(
+ insertIndex, BuilderInstruction21t(Opcode.IF_EQZ, 0, elseLabel)
+ )
+ result.mutableMethod.addInstructions(
+ insertIndex, """
+ invoke-static { }, Lapp/revanced/integrations/patches/SeekbarTappingPatch;->isTapSeekingEnabled()Z
+ move-result v0
+ """
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/annotation/SwipeControlsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/annotation/SwipeControlsCompatibility.kt
new file mode 100644
index 0000000..6691d5d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/annotation/SwipeControlsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.interaction.swipecontrols.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class SwipeControlsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/fingerprints/SwipeControlsHostActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/fingerprints/SwipeControlsHostActivityFingerprint.kt
new file mode 100644
index 0000000..25e3ada
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/fingerprints/SwipeControlsHostActivityFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.interaction.swipecontrols.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SwipeControlsHostActivityFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass == "Lapp/revanced/integrations/swipecontrols/SwipeControlsHostActivity;" && methodDef.name == ""
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/bytecode/SwipeControlsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/bytecode/SwipeControlsBytecodePatch.kt
new file mode 100644
index 0000000..22f94b8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/bytecode/SwipeControlsBytecodePatch.kt
@@ -0,0 +1,68 @@
+package app.revanced.patches.youtube.interaction.swipecontrols.patch.bytecode
+
+import app.revanced.extensions.transformMethods
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
+import app.revanced.patches.shared.fingerprints.WatchWhileActivityFingerprint
+import app.revanced.patches.youtube.interaction.swipecontrols.annotation.SwipeControlsCompatibility
+import app.revanced.patches.youtube.interaction.swipecontrols.fingerprints.SwipeControlsHostActivityFingerprint
+import app.revanced.patches.youtube.interaction.swipecontrols.patch.resource.SwipeControlsResourcePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.playertype.patch.PlayerTypeHookPatch
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.immutable.ImmutableMethod
+
+@Patch
+@Name("swipe-controls")
+@Description("Adds volume and brightness swipe controls.")
+@SwipeControlsCompatibility
+@Version("0.0.3")
+@DependsOn(
+ [
+ IntegrationsPatch::class,
+ PlayerTypeHookPatch::class,
+ SwipeControlsResourcePatch::class
+ ]
+)
+class SwipeControlsBytecodePatch : BytecodePatch(
+ listOf(
+ WatchWhileActivityFingerprint,
+ SwipeControlsHostActivityFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val wrapperClass = SwipeControlsHostActivityFingerprint.result!!.mutableClass
+ val targetClass = WatchWhileActivityFingerprint.result!!.mutableClass
+
+ // inject the wrapper class from integrations into the class hierarchy of WatchWhileActivity
+ wrapperClass.setSuperClass(targetClass.superclass)
+ targetClass.setSuperClass(wrapperClass.type)
+
+ // ensure all classes and methods in the hierarchy are non-final, so we can override them in integrations
+ context.traverseClassHierarchy(targetClass) {
+ accessFlags = accessFlags and AccessFlags.FINAL.value.inv()
+ transformMethods {
+ ImmutableMethod(
+ definingClass,
+ name,
+ parameters,
+ returnType,
+ accessFlags and AccessFlags.FINAL.value.inv(),
+ annotations,
+ hiddenApiRestrictions,
+ implementation
+ ).toMutable()
+ }
+ }
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt
new file mode 100644
index 0000000..01cfffd
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt
@@ -0,0 +1,111 @@
+package app.revanced.patches.youtube.interaction.swipecontrols.patch.resource
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.shared.settings.preference.impl.TextPreference
+import app.revanced.patches.shared.settings.preference.impl.InputType
+import app.revanced.patches.youtube.interaction.swipecontrols.annotation.SwipeControlsCompatibility
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.util.resources.ResourceUtils
+import app.revanced.util.resources.ResourceUtils.copyResources
+
+@Name("swipe-controls-resource-patch")
+@DependsOn([SettingsPatch::class])
+@SwipeControlsCompatibility
+@Version("0.0.1")
+class SwipeControlsResourcePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.INTERACTIONS.addPreferences(
+ PreferenceScreen(
+ "revanced_swipe_controls", StringResource("revanced_swipe_controls_title", "Swipe controls"), listOf(
+ SwitchPreference(
+ "revanced_enable_swipe_brightness",
+ StringResource("revanced_swipe_brightness_enabled_title", "Enable brightness gesture"),
+ true,
+ StringResource("revanced_swipe_brightness_summary_on", "Brightness swipe is enabled"),
+ StringResource("revanced_swipe_brightness_summary_off", "Brightness swipe is disabled")
+ ),
+ SwitchPreference(
+ "revanced_enable_swipe_volume",
+ StringResource("revanced_swipe_volume_enabled_title", "Enable volume gesture"),
+ true,
+ StringResource("revanced_swipe_volume_summary_on", "Volume swipe is enabled"),
+ StringResource("revanced_swipe_volume_summary_off", "Volume swipe is disabled")
+ ),
+ SwitchPreference(
+ "revanced_enable_press_to_swipe",
+ StringResource("revanced_press_to_swipe_enabled_title", "Enable press-to-swipe gesture"),
+ false,
+ StringResource("revanced_press_to_swipe_summary_on", "Press-to-swipe is enabled"),
+ StringResource("revanced_press_to_swipe_summary_off", "Press-to-swipe is disabled")
+ ),
+ SwitchPreference(
+ "revanced_enable_swipe_haptic_feedback",
+ StringResource("revanced_swipe_haptic_feedback_enabled_title", "Enable haptic feedback"),
+ true,
+ StringResource("revanced_swipe_haptic_feedback_summary_on", "Haptic feedback is enabled"),
+ StringResource("revanced_swipe_haptic_feedback_summary_off", "Haptic feedback is disabled")
+ ),
+ TextPreference(
+ "revanced_swipe_overlay_timeout",
+ StringResource("revanced_swipe_overlay_timeout_title", "Swipe overlay timeout"),
+ InputType.NUMBER,
+ "500",
+ StringResource(
+ "revanced_swipe_overlay_timeout_summary",
+ "The amount of milliseconds the overlay is visible"
+ )
+ ),
+ TextPreference(
+ "revanced_swipe_overlay_text_size",
+ StringResource("revanced_swipe_overlay_text_size_title", "Swipe overlay text size"),
+ InputType.NUMBER,
+ "22",
+ StringResource("revanced_swipe_overlay_text_size_summary", "The text size for swipe overlay")
+ ),
+ TextPreference(
+ "revanced_swipe_overlay_background_alpha",
+ StringResource("revanced_swipe_overlay_background_alpha_title", "Swipe background visibility"),
+ InputType.NUMBER,
+ "127",
+ StringResource(
+ "revanced_swipe_overlay_background_alpha_summary",
+ "The visibility of swipe overlay background"
+ )
+ ),
+ TextPreference(
+ "revanced_swipe_magnitude_threshold",
+ StringResource("revanced_swipe_magnitude_threshold_title", "Swipe magnitude threshold"),
+ InputType.NUMBER,
+ "30",
+ StringResource(
+ "revanced_swipe_magnitude_threshold_summary",
+ "The amount of threshold for swipe to occur"
+ )
+ )
+ ),
+ StringResource("revanced_swipe_controls_summary","Control volume and brightness")
+ )
+ )
+
+ context.copyResources(
+ "swipecontrols",
+ ResourceUtils.ResourceGroup(
+ "drawable",
+ "ic_sc_brightness_auto.xml",
+ "ic_sc_brightness_manual.xml",
+ "ic_sc_volume_mute.xml",
+ "ic_sc_volume_normal.xml"
+ )
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/annotations/AutoCaptionsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/annotations/AutoCaptionsCompatibility.kt
new file mode 100644
index 0000000..fc1f4c9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/annotations/AutoCaptionsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.autocaptions.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class AutoCaptionsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/StartVideoInformerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/StartVideoInformerFingerprint.kt
new file mode 100644
index 0000000..e65c2b7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/StartVideoInformerFingerprint.kt
@@ -0,0 +1,33 @@
+package app.revanced.patches.youtube.layout.autocaptions.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(3)
+object StartVideoInformerFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L", "L", "L", "L"), listOf(
+ Opcode.INVOKE_STATIC,
+ Opcode.IGET_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.IF_EQZ,
+ Opcode.CONST_STRING,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.CONST_4,
+ Opcode.IF_EQ,
+ Opcode.GOTO,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleButtonControllerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleButtonControllerFingerprint.kt
new file mode 100644
index 0000000..d42965f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleButtonControllerFingerprint.kt
@@ -0,0 +1,20 @@
+package app.revanced.patches.youtube.layout.autocaptions.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object SubtitleButtonControllerFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf(
+ Opcode.IGET_OBJECT,
+ Opcode.IF_NEZ,
+ Opcode.RETURN_VOID,
+ Opcode.IGET_BOOLEAN,
+ Opcode.CONST_4,
+ Opcode.IF_NEZ,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IGET_OBJECT,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleTrackFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleTrackFingerprint.kt
new file mode 100644
index 0000000..a011e60
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleTrackFingerprint.kt
@@ -0,0 +1,18 @@
+package app.revanced.patches.youtube.layout.autocaptions.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object SubtitleTrackFingerprint : MethodFingerprint(
+ "Z", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf(
+ Opcode.CONST_STRING,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.RETURN,
+ ),
+ strings = listOf("DISABLE_CAPTIONS_OPTION")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/patch/AutoCaptionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/patch/AutoCaptionsPatch.kt
new file mode 100644
index 0000000..38c12c9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/patch/AutoCaptionsPatch.kt
@@ -0,0 +1,80 @@
+package app.revanced.patches.youtube.layout.autocaptions.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.autocaptions.annotations.AutoCaptionsCompatibility
+import app.revanced.patches.youtube.layout.autocaptions.fingerprints.StartVideoInformerFingerprint
+import app.revanced.patches.youtube.layout.autocaptions.fingerprints.SubtitleButtonControllerFingerprint
+import app.revanced.patches.youtube.layout.autocaptions.fingerprints.SubtitleTrackFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("disable-auto-captions")
+@Description("Disable forced captions from being automatically enabled.")
+@AutoCaptionsCompatibility
+@Version("0.0.1")
+class AutoCaptionsPatch : BytecodePatch(
+ listOf(
+ StartVideoInformerFingerprint, SubtitleButtonControllerFingerprint, SubtitleTrackFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_autocaptions_enabled",
+ StringResource("revanced_autocaptions_enabled_title", "Disable auto-captions"),
+ false,
+ StringResource("revanced_autocaptions_summary_on", "Auto-captions are disabled"),
+ StringResource("revanced_autocaptions_summary_off", "Auto-captions are enabled")
+ )
+ )
+
+ val startVideoInformerMethod = StartVideoInformerFingerprint.result!!.mutableMethod
+
+ startVideoInformerMethod.addInstructions(
+ 0, """
+ const/4 v0, 0x0
+ sput-boolean v0, Lapp/revanced/integrations/patches/DisableAutoCaptionsPatch;->captionsButtonDisabled:Z
+ """
+ )
+
+ val subtitleButtonControllerMethod = SubtitleButtonControllerFingerprint.result!!.mutableMethod
+
+ subtitleButtonControllerMethod.addInstructions(
+ 0, """
+ const/4 v0, 0x1
+ sput-boolean v0, Lapp/revanced/integrations/patches/DisableAutoCaptionsPatch;->captionsButtonDisabled:Z
+ """
+ )
+
+ val subtitleTrackMethod = SubtitleTrackFingerprint.result!!.mutableMethod
+
+ subtitleTrackMethod.addInstructions(
+ 0, """
+ invoke-static {}, Lapp/revanced/integrations/patches/DisableAutoCaptionsPatch;->autoCaptionsEnabled()Z
+ move-result v0
+ if-eqz v0, :auto_captions_enabled
+ sget-boolean v0, Lapp/revanced/integrations/patches/DisableAutoCaptionsPatch;->captionsButtonDisabled:Z
+ if-nez v0, :auto_captions_enabled
+ const/4 v0, 0x1
+ return v0
+ :auto_captions_enabled
+ nop
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/annotations/AutoplayButtonCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/annotations/AutoplayButtonCompatibility.kt
new file mode 100644
index 0000000..0a32de4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/annotations/AutoplayButtonCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.autoplaybutton.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class AutoplayButtonCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/AutoNavInformerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/AutoNavInformerFingerprint.kt
new file mode 100644
index 0000000..469d564
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/AutoNavInformerFingerprint.kt
@@ -0,0 +1,20 @@
+package app.revanced.patches.youtube.layout.autoplaybutton.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object AutoNavInformerFingerprint : MethodFingerprint(
+ "Z",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ opcodes = listOf(
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ ),
+ customFingerprint = { it.definingClass.endsWith("WillAutonavInformer;") }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/LayoutConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/LayoutConstructorFingerprint.kt
new file mode 100644
index 0000000..fcc47c1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/LayoutConstructorFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.layout.autoplaybutton.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object LayoutConstructorFingerprint : MethodFingerprint(
+ strings = listOf("1.0x"),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("YouTubeControlsOverlay;")
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/patch/HideAutoplayButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/patch/HideAutoplayButtonPatch.kt
new file mode 100644
index 0000000..0791144
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/patch/HideAutoplayButtonPatch.kt
@@ -0,0 +1,92 @@
+package app.revanced.patches.youtube.layout.autoplaybutton.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.youtube.layout.autoplaybutton.annotations.AutoplayButtonCompatibility
+import app.revanced.patches.youtube.layout.autoplaybutton.fingerprints.AutoNavInformerFingerprint
+import app.revanced.patches.youtube.layout.autoplaybutton.fingerprints.LayoutConstructorFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.iface.instruction.Instruction
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+import org.jf.dexlib2.iface.reference.MethodReference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class, ResourceMappingPatch::class])
+@Name("hide-autoplay-button")
+@Description("Hides the autoplay button in the video player.")
+@AutoplayButtonCompatibility
+@Version("0.0.1")
+class HideAutoplayButtonPatch : BytecodePatch(
+ listOf(
+ LayoutConstructorFingerprint, AutoNavInformerFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_autoplay_button",
+ StringResource("revanced_hide_autoplay_button_title", "Hide autoplay button"),
+ true,
+ StringResource("revanced_hide_autoplay_button_summary_on", "Autoplay button is hidden"),
+ StringResource("revanced_hide_autoplay_button_summary_off", "Autoplay button is shown")
+ )
+ )
+
+ val autoNavInformerMethod = AutoNavInformerFingerprint.result!!.mutableMethod
+
+ val layoutGenMethodResult = LayoutConstructorFingerprint.result!!
+ val layoutGenMethod = layoutGenMethodResult.mutableMethod
+ val layoutGenMethodInstructions = layoutGenMethod.implementation!!.instructions
+
+ // resolve the offsets such as ...
+ val autoNavPreviewStubId = ResourceMappingPatch.resourceMappings.single {
+ it.name == "autonav_preview_stub"
+ }.id
+ // where to insert the branch instructions and ...
+ val insertIndex = layoutGenMethodInstructions.indexOfFirst {
+ (it as? WideLiteralInstruction)?.wideLiteral == autoNavPreviewStubId
+ }
+ // where to branch away
+ val branchIndex = layoutGenMethodInstructions.subList(insertIndex + 1, layoutGenMethodInstructions.size - 1).indexOfFirst {
+ ((it as? ReferenceInstruction)?.reference as? MethodReference)?.name == "addOnLayoutChangeListener"
+ } + 2
+
+ val jumpInstruction = layoutGenMethodInstructions[insertIndex + branchIndex] as Instruction
+ layoutGenMethod.addInstructions(
+ insertIndex, """
+ invoke-static {}, Lapp/revanced/integrations/patches/HideAutoplayButtonPatch;->isButtonShown()Z
+ move-result v11
+ if-eqz v11, :hidden
+ """, listOf(ExternalLabel("hidden", jumpInstruction))
+ )
+
+ //force disable autoplay since it's hard to do without the button
+ autoNavInformerMethod.addInstructions(
+ 0, """
+ invoke-static {}, Lapp/revanced/integrations/patches/HideAutoplayButtonPatch;->isButtonShown()Z
+ move-result v0
+ if-nez v0, :hidden
+ const/4 v0, 0x0
+ return v0
+ :hidden
+ nop
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/annotations/PremiumHeadingCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/annotations/PremiumHeadingCompatibility.kt
new file mode 100644
index 0000000..96b1d50
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/annotations/PremiumHeadingCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.layout.branding.header.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.google.android.youtube")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PremiumHeadingCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/patch/PremiumHeadingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/patch/PremiumHeadingPatch.kt
new file mode 100644
index 0000000..83eca46
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/patch/PremiumHeadingPatch.kt
@@ -0,0 +1,51 @@
+package app.revanced.patches.youtube.layout.branding.header.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.branding.header.annotations.PremiumHeadingCompatibility
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import java.nio.file.Files
+import java.nio.file.StandardCopyOption
+import kotlin.io.path.exists
+
+@Patch
+@DependsOn([FixLocaleConfigErrorPatch::class])
+@Name("premium-heading")
+@Description("Shows premium branding on the home screen.")
+@PremiumHeadingCompatibility
+@Version("0.0.1")
+class PremiumHeadingPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ val resDirectory = context["res"]
+ if (!resDirectory.isDirectory) return PatchResultError("The res folder can not be found.")
+
+ val (original, replacement) = "yt_premium_wordmark_header" to "yt_wordmark_header"
+ val modes = arrayOf("light", "dark")
+
+ arrayOf("xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi").forEach { size ->
+ val headingDirectory = resDirectory.resolve("drawable-$size")
+ modes.forEach { mode ->
+ val fromPath = headingDirectory.resolve("${original}_$mode.png").toPath()
+ val toPath = headingDirectory.resolve("${replacement}_$mode.png").toPath()
+
+ if (!fromPath.exists())
+ return PatchResultError("The file $fromPath does not exist in the resources. Therefore, this patch can not succeed.")
+ Files.copy(
+ fromPath,
+ toPath,
+ StandardCopyOption.REPLACE_EXISTING
+ )
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/annotations/CustomBrandingCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/annotations/CustomBrandingCompatibility.kt
new file mode 100644
index 0000000..a0ffc71
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/annotations/CustomBrandingCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.layout.branding.icon.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.google.android.youtube")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class CustomBrandingCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/patch/CustomBrandingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/patch/CustomBrandingPatch.kt
new file mode 100644
index 0000000..f373fd6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/patch/CustomBrandingPatch.kt
@@ -0,0 +1,94 @@
+package app.revanced.patches.youtube.layout.branding.icon.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.*
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.branding.icon.annotations.CustomBrandingCompatibility
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import app.revanced.util.resources.ResourceUtils
+import app.revanced.util.resources.ResourceUtils.copyResources
+import java.io.File
+import java.nio.file.Files
+
+@Patch
+@DependsOn([FixLocaleConfigErrorPatch::class])
+@Name("custom-branding")
+@Description("Changes the YouTube launcher icon and name to your choice (defaults to ReVanced).")
+@CustomBrandingCompatibility
+@Version("0.0.1")
+class CustomBrandingPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ fun copyResources(resourceGroups: List) {
+ iconPath?.let { iconPathString ->
+ val iconPath = File(iconPathString)
+ val resourceDirectory = context["res"]
+
+ resourceGroups.forEach { group ->
+ val fromDirectory = iconPath.resolve(group.resourceDirectoryName)
+ val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
+
+ group.resources.forEach { iconFileName ->
+ Files.write(
+ toDirectory.resolve(iconFileName).toPath(),
+ fromDirectory.resolve(iconFileName).readBytes()
+ )
+ }
+ }
+ } ?: resourceGroups.forEach { context.copyResources("branding", it) }
+ }
+
+ val iconResourceFileNames = arrayOf(
+ "adaptiveproduct_youtube_background_color_108",
+ "adaptiveproduct_youtube_foreground_color_108",
+ "ic_launcher",
+ "ic_launcher_round"
+ ).map { "$it.png" }.toTypedArray()
+
+ fun createGroup(directory: String) = ResourceUtils.ResourceGroup(
+ directory, *iconResourceFileNames
+ )
+
+ // change the app icon
+ arrayOf("xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi")
+ .map { "mipmap-$it" }
+ .map(::createGroup)
+ .let(::copyResources)
+
+ // change the name of the app
+ val manifest = context["AndroidManifest.xml"]
+ manifest.writeText(
+ manifest.readText()
+ .replace(
+ "android:label=\"@string/application_name",
+ "android:label=\"$appName"
+ )
+ )
+
+ return PatchResultSuccess()
+ }
+
+ companion object : OptionsContainer() {
+ private var appName: String? by option(
+ PatchOption.StringOption(
+ key = "appName",
+ default = "YouTube ReVanced",
+ title = "Application Name",
+ description = "The name of the application it will show on your home screen.",
+ required = true
+ )
+ )
+
+ private var iconPath: String? by option(
+ PatchOption.StringOption(
+ key = "iconPath",
+ default = null,
+ title = "App Icon Path",
+ description = "A path containing mipmap resource folders with icons."
+ )
+ )
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/annotations/HideButtonsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/annotations/HideButtonsCompatibility.kt
new file mode 100644
index 0000000..c2847ff
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/annotations/HideButtonsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.buttons.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideButtonsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/patch/HideButtonsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/patch/HideButtonsPatch.kt
new file mode 100644
index 0000000..bd82e1d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/patch/HideButtonsPatch.kt
@@ -0,0 +1,81 @@
+package app.revanced.patches.youtube.layout.buttons.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.misc.litho.filter.patch.LithoFilterPatch
+import app.revanced.patches.youtube.layout.buttons.annotations.HideButtonsCompatibility
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([ResourceMappingPatch::class, LithoFilterPatch::class])
+@Name("hide-video-buttons")
+@Description("Adds options to hide action buttons under a video.")
+@HideButtonsCompatibility
+@Version("0.0.1")
+class HideButtonsPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ PreferenceScreen(
+ "revanced_hide_buttons",
+ StringResource("revanced_hide_buttons_title", "Hide action buttons"),
+ listOf(
+ SwitchPreference(
+ "revanced_hide_like_button",
+ StringResource("revanced_hide_like_button_title", "Hide like button"),
+ false,
+ StringResource("revanced_hide_like_button_summary_on", "Like button is hidden"),
+ StringResource("revanced_hide_like_button_summary_off", "Like button is shown")
+ ),
+ SwitchPreference(
+ "revanced_hide_dislike_button",
+ StringResource("revanced_hide_dislike_button_title", "Hide dislike button"),
+ false,
+ StringResource("revanced_hide_dislike_button_summary_on", "Dislike button is hidden"),
+ StringResource("revanced_hide_dislike_button_summary_off", "Dislike button is shown")
+ ),
+ SwitchPreference(
+ "revanced_hide_download_button",
+ StringResource("revanced_hide_download_button_title", "Hide download button"),
+ false,
+ StringResource("revanced_hide_download_button_summary_on", "Download button is hidden"),
+ StringResource("revanced_hide_download_button_summary_off", "Download button is shown")
+ ),
+ SwitchPreference(
+ "revanced_hide_playlist_button",
+ StringResource("revanced_hide_playlist_button_title", "Hide playlist button"),
+ false,
+ StringResource("revanced_hide_playlist_button_summary_on", "Playlist button is hidden"),
+ StringResource("revanced_hide_playlist_button_summary_off", "Playlist button is shown")
+ ),
+ SwitchPreference(
+ "revanced_hide_action_button",
+ StringResource("revanced_hide_action_button_title", "Hide create, clip and thanks buttons"),
+ true,
+ StringResource("revanced_hide_action_button_summary_on", "Buttons are hidden"),
+ StringResource("revanced_hide_action_button_summary_off", "Buttons are shown")
+ ),
+ SwitchPreference(
+ "revanced_hide_share_button",
+ StringResource("revanced_hide_share_button_title", "Hide share button"),
+ false,
+ StringResource("revanced_hide_share_button_summary_on", "Share button is hidden"),
+ StringResource("revanced_hide_share_button_summaryoff", "Share button is shown")
+ ),
+ ),
+ StringResource("revanced_hide_buttons_summary", "Hide or show buttons under videos")
+ )
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/annotations/CastPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/annotations/CastPatchCompatibility.kt
new file mode 100644
index 0000000..86adb91
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/annotations/CastPatchCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.layout.castbutton.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.google.android.youtube")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class CastButtonCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/patch/HideCastButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/patch/HideCastButtonPatch.kt
new file mode 100644
index 0000000..f1925c0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/patch/HideCastButtonPatch.kt
@@ -0,0 +1,57 @@
+package app.revanced.patches.youtube.layout.castbutton.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.castbutton.annotations.CastButtonCompatibility
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("hide-cast-button")
+@Description("Hides the cast button in the video player.")
+@CastButtonCompatibility
+@Version("0.0.1")
+class HideCastButtonPatch : BytecodePatch() {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_cast_button",
+ StringResource("revanced_hide_cast_button_title", "Hide cast button"),
+ true,
+ StringResource("revanced_hide_cast_button_summary_on", "Cast button is hidden"),
+ StringResource("revanced_hide_cast_button_summary_off", "Cast button is shown")
+ )
+ )
+
+ with(
+ context.findClass("MediaRouteButton")
+ ?: return PatchResultError("MediaRouteButton class not found.")
+ ) {
+ with(
+ mutableClass.methods.find { it.name == "setVisibility" }
+ ?: return PatchResultError("setVisibility method not found.")
+ ) {
+ addInstructions(
+ 0, """
+ invoke-static {p1}, Lapp/revanced/integrations/patches/HideCastButtonPatch;->getCastButtonOverrideV2(I)I
+ move-result p1
+ """
+ )
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/comments/annotations/CommentsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/annotations/CommentsCompatibility.kt
new file mode 100644
index 0000000..fb13661
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/annotations/CommentsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.comments.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class CommentsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/fingerprints/ShortsCommentsButtonFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/fingerprints/ShortsCommentsButtonFingerprint.kt
new file mode 100644
index 0000000..878f112
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/fingerprints/ShortsCommentsButtonFingerprint.kt
@@ -0,0 +1,17 @@
+package app.revanced.patches.youtube.layout.comments.bytecode.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.comments.resource.patch.CommentsResourcePatch
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object ShortsCommentsButtonFingerprint : MethodFingerprint(
+ "V", AccessFlags.PRIVATE or AccessFlags.FINAL, listOf("Z", "Z", "L"),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any {
+ it.opcode.ordinal == Opcode.CONST.ordinal && (it as WideLiteralInstruction).wideLiteral == CommentsResourcePatch.shortsCommentsButtonId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/patch/CommentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/patch/CommentsPatch.kt
new file mode 100644
index 0000000..ddbcc78
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/patch/CommentsPatch.kt
@@ -0,0 +1,62 @@
+package app.revanced.patches.youtube.layout.comments.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.comments.annotations.CommentsCompatibility
+import app.revanced.patches.youtube.layout.comments.bytecode.fingerprints.ShortsCommentsButtonFingerprint
+import app.revanced.patches.youtube.layout.comments.resource.patch.CommentsResourcePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@DependsOn([IntegrationsPatch::class, CommentsResourcePatch::class])
+@Name("comments")
+@Description("Hides components related to comments.")
+@CommentsCompatibility
+@Version("0.0.1")
+class CommentsPatch : BytecodePatch(
+ listOf(
+ ShortsCommentsButtonFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val shortsCommentsButtonResult = ShortsCommentsButtonFingerprint.result!!
+ val shortsCommentsButtonMethod = shortsCommentsButtonResult.mutableMethod
+
+ val checkCastAnchorFingerprint = object : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.CONST,
+ Opcode.CONST_HIGH16,
+ Opcode.IF_EQZ,
+ Opcode.CONST,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ )
+ ) {}
+
+ val checkCastAnchorIndex = checkCastAnchorFingerprint.also {
+ it.resolve(context, shortsCommentsButtonMethod, shortsCommentsButtonResult.classDef)
+ }.result!!.scanResult.patternScanResult!!.endIndex
+
+ shortsCommentsButtonMethod.addInstructions(
+ checkCastAnchorIndex + 1, """
+ invoke-static {v${(shortsCommentsButtonMethod.instruction(checkCastAnchorIndex) as OneRegisterInstruction).registerA}}, Lapp/revanced/integrations/patches/HideShortsCommentsButtonPatch;->hideShortsCommentsButton(Landroid/view/View;)V
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/comments/resource/patch/CommentsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/resource/patch/CommentsResourcePatch.kt
new file mode 100644
index 0000000..052b130
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/resource/patch/CommentsResourcePatch.kt
@@ -0,0 +1,64 @@
+package app.revanced.patches.youtube.layout.comments.resource.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.layout.comments.annotations.CommentsCompatibility
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Name("comments-resource-patch")
+@CommentsCompatibility
+@DependsOn([SettingsPatch::class, ResourceMappingPatch::class])
+@Version("0.0.1")
+class CommentsResourcePatch : ResourcePatch {
+ companion object {
+ internal var shortsCommentsButtonId: Long = -1
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ PreferenceScreen(
+ "revanced_comments",
+ StringResource("revanced_comments_title", "Comments"),
+ listOf(
+ SwitchPreference(
+ "revanced_hide_comments_section",
+ StringResource("revanced_hide_comments_section_title", "Hide comments section"),
+ false,
+ StringResource("revanced_hide_comments_section_summary_on", "Comment section is hidden"),
+ StringResource("revanced_hide_comments_section_summary_off", "Comment section is shown")
+ ),
+ SwitchPreference(
+ "revanced_hide_preview_comment",
+ StringResource("revanced_hide_preview_comment_title", "Hide preview comment"),
+ false,
+ StringResource("revanced_hide_preview_comment_on", "Preview comment is hidden"),
+ StringResource("revanced_hide_preview_comment_off", "Preview comment is shown")
+ ),
+ SwitchPreference(
+ "revanced_hide_shorts_comments_button",
+ StringResource("revanced_hide_shorts_comments_button_title", "Hide shorts comments button"),
+ false,
+ StringResource("revanced_hide_shorts_comments_button_on", "Shorts comments button is hidden"),
+ StringResource("revanced_hide_shorts_comments_button_off", "Shorts comments button is shown")
+ ),
+ ),
+ StringResource("revanced_comments_summary", "Manage the visibility of comments section components")
+ )
+ )
+
+ shortsCommentsButtonId = ResourceMappingPatch.resourceMappings.single {
+ it.type == "drawable" && it.name == "ic_right_comment_32c"
+ }.id
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/annotations/FullscreenPanelsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/annotations/FullscreenPanelsCompatibility.kt
new file mode 100644
index 0000000..cd799d1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/annotations/FullscreenPanelsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.fullscreenpanels.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class FullscreenPanelsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderFingerprint.kt
new file mode 100644
index 0000000..ef0769d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object FullscreenViewAdderFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.IGET_BOOLEAN
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderParentFingerprint.kt
new file mode 100644
index 0000000..ad59ea9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderParentFingerprint.kt
@@ -0,0 +1,18 @@
+package app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object FullscreenViewAdderParentFingerprint : MethodFingerprint(
+ parameters = listOf("L", "L"),
+ opcodes = listOf(
+ Opcode.GOTO,
+ Opcode.IGET_BOOLEAN,
+ Opcode.IF_EQ,
+ Opcode.GOTO,
+ Opcode.CONST_4,
+ Opcode.INVOKE_VIRTUAL,
+ ),
+ customFingerprint = { it.definingClass.endsWith("FullscreenEngagementPanelOverlay;") }
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/patch/FullscreenPanelsRemoverPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/patch/FullscreenPanelsRemoverPatch.kt
new file mode 100644
index 0000000..e61de0f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/patch/FullscreenPanelsRemoverPatch.kt
@@ -0,0 +1,65 @@
+package app.revanced.patches.youtube.layout.fullscreenpanels.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.fullscreenpanels.annotations.FullscreenPanelsCompatibility
+import app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints.FullscreenViewAdderFingerprint
+import app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints.FullscreenViewAdderParentFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@Name("disable-fullscreen-panels")
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Description("Disables video description and comments panel in fullscreen view.")
+@FullscreenPanelsCompatibility
+@Version("0.0.1")
+class FullscreenPanelsRemoverPatch : BytecodePatch(
+ listOf(
+ FullscreenViewAdderParentFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_fullscreen_panels",
+ StringResource("revanced_hide_fullscreen_panels_title", "Hide fullscreen panels"),
+ true,
+ StringResource("revanced_hide_fullscreen_panels_summary_on", "Fullscreen panels are hidden"),
+ StringResource("revanced_hide_fullscreen_panels_summary_off", "Fullscreen panels are shown")
+ )
+ )
+
+ val parentResult = FullscreenViewAdderParentFingerprint.result!!
+ FullscreenViewAdderFingerprint.resolve(context, parentResult.method, parentResult.classDef)
+ val result = FullscreenViewAdderParentFingerprint.result
+ ?: return PatchResultError("Fingerprint not resolved!")
+
+ val method = result.mutableMethod
+
+ val ifIndex = result.scanResult.patternScanResult!!.startIndex + 2
+
+ method.removeInstruction(ifIndex)
+ method.addInstructions(
+ ifIndex, """
+ invoke-static {}, Lapp/revanced/integrations/patches/FullscreenPanelsRemoverPatch;->getFullscreenPanelsVisibility()I
+ move-result p1
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/annotations/AlbumCardsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/annotations/AlbumCardsCompatibility.kt
new file mode 100644
index 0000000..d82f26e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/annotations/AlbumCardsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.hidealbumcards.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class AlbumCardsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/fingerprints/AlbumCardsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/fingerprints/AlbumCardsFingerprint.kt
new file mode 100644
index 0000000..505345f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/fingerprints/AlbumCardsFingerprint.kt
@@ -0,0 +1,23 @@
+package app.revanced.patches.youtube.layout.hidealbumcards.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.hidealbumcards.resource.patch.AlbumCardsResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object AlbumCardsFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CONST,
+ Opcode.CONST_4,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ instruction.opcode.ordinal == Opcode.CONST.ordinal &&
+ (instruction as? WideLiteralInstruction)?.wideLiteral == AlbumCardsResourcePatch.albumCardId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/patch/AlbumCardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/patch/AlbumCardsPatch.kt
new file mode 100644
index 0000000..9d7593d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/patch/AlbumCardsPatch.kt
@@ -0,0 +1,45 @@
+package app.revanced.patches.youtube.layout.hidealbumcards.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.hidealbumcards.annotations.AlbumCardsCompatibility
+import app.revanced.patches.youtube.layout.hidealbumcards.bytecode.fingerprints.AlbumCardsFingerprint
+import app.revanced.patches.youtube.layout.hidealbumcards.resource.patch.AlbumCardsResourcePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@DependsOn([IntegrationsPatch::class, AlbumCardsResourcePatch::class])
+@Name("hide-album-cards")
+@Description("Hides the album cards below the artist description.")
+@AlbumCardsCompatibility
+@Version("0.0.1")
+class AlbumCardsPatch : BytecodePatch(
+ listOf(
+ AlbumCardsFingerprint,
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val albumCardsResult = AlbumCardsFingerprint.result!!
+ val albumCardsMethod = albumCardsResult.mutableMethod
+
+ val checkCastAnchorIndex = albumCardsResult.scanResult.patternScanResult!!.endIndex
+
+ albumCardsMethod.addInstruction(
+ checkCastAnchorIndex + 1, """
+ invoke-static {v${(albumCardsMethod.instruction(checkCastAnchorIndex) as OneRegisterInstruction).registerA}}, Lapp/revanced/integrations/patches/HideAlbumCardsPatch;->hideAlbumCards(Landroid/view/View;)V
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/resource/patch/AlbumCardsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/resource/patch/AlbumCardsResourcePatch.kt
new file mode 100644
index 0000000..faf7575
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/resource/patch/AlbumCardsResourcePatch.kt
@@ -0,0 +1,42 @@
+package app.revanced.patches.youtube.layout.hidealbumcards.resource.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.layout.hidealbumcards.annotations.AlbumCardsCompatibility
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Name("hide-album-cards-resource-patch")
+@AlbumCardsCompatibility
+@DependsOn([SettingsPatch::class, ResourceMappingPatch::class])
+@Version("0.0.1")
+class AlbumCardsResourcePatch : ResourcePatch {
+ companion object {
+ internal var albumCardId: Long = -1
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_album_cards",
+ StringResource("revanced_hide_album_cards_title", "Hide album cards"),
+ false,
+ StringResource("revanced_hide_album_cards_summary_on", "Music album cards are hidden"),
+ StringResource("revanced_hide_album_cards_summary_off", "Music album cards are shown")
+ )
+ )
+
+ albumCardId = ResourceMappingPatch.resourceMappings.single {
+ it.type == "layout" && it.name == "album_card"
+ }.id
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/annotations/HideArtistCardCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/annotations/HideArtistCardCompatibility.kt
new file mode 100644
index 0000000..6bbe049
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/annotations/HideArtistCardCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.buttons.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideArtistCardCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/patch/HideArtistCardPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/patch/HideArtistCardPatch.kt
new file mode 100644
index 0000000..9b7ff77
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/patch/HideArtistCardPatch.kt
@@ -0,0 +1,38 @@
+package app.revanced.patches.youtube.layout.hideartistcard.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.misc.litho.filter.patch.LithoFilterPatch
+import app.revanced.patches.youtube.layout.buttons.annotations.HideArtistCardCompatibility
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([ResourceMappingPatch::class, LithoFilterPatch::class])
+@Name("hide-artist-card")
+@Description("Hides the artist card below the searchbar.")
+@HideArtistCardCompatibility
+@Version("0.0.1")
+class HideArtistCardPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_artist_card",
+ StringResource("revanced_hide_hide_artist_card_title", "Hide artist card"),
+ false,
+ StringResource("revanced_hide_hide_artist_card_on", "Artist card is hidden"),
+ StringResource("revanced_hide_hide_artist_card_off", "Artist card is shown")
+ ),
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecaptionsbutton/patch/HideCaptionsButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecaptionsbutton/patch/HideCaptionsButtonPatch.kt
new file mode 100644
index 0000000..a250036
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecaptionsbutton/patch/HideCaptionsButtonPatch.kt
@@ -0,0 +1,56 @@
+package app.revanced.patches.youtube.layout.hidecaptionsbutton.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.autocaptions.annotations.AutoCaptionsCompatibility
+import app.revanced.patches.youtube.layout.autocaptions.fingerprints.SubtitleButtonControllerFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.Opcode
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("hide-captions-button")
+@Description("Hides the captions button on video player.")
+@AutoCaptionsCompatibility
+@Version("0.0.1")
+class HideCaptionsButtonPatch : BytecodePatch(listOf(
+ SubtitleButtonControllerFingerprint,
+)) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_captions_button",
+ StringResource("revanced_hide_captions_button_title", "Hide captions button"),
+ false,
+ StringResource("revanced_hide_captions_button_summary_on", "Captions button is hidden"),
+ StringResource("revanced_hide_captions_button_summary_off", "Captions button is shown")
+ )
+ )
+
+ val subtitleButtonControllerMethod = SubtitleButtonControllerFingerprint.result!!.mutableMethod
+
+ // Due to previously applied patches, scanResult index cannot be used in this context
+ val igetBooleanIndex = subtitleButtonControllerMethod.implementation!!.instructions.indexOfFirst {
+ it.opcode == Opcode.IGET_BOOLEAN
+ }
+
+ subtitleButtonControllerMethod.addInstructions(
+ igetBooleanIndex + 1, """
+ invoke-static {v0}, Lapp/revanced/integrations/patches/HideCaptionsButtonPatch;->hideCaptionsButton(Landroid/widget/ImageView;)V
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/annotations/CrowdfundingBoxCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/annotations/CrowdfundingBoxCompatibility.kt
new file mode 100644
index 0000000..455ef57
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/annotations/CrowdfundingBoxCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.hidecrowdfundingbox.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class CrowdfundingBoxCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/fingerprints/CrowdfundingBoxFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/fingerprints/CrowdfundingBoxFingerprint.kt
new file mode 100644
index 0000000..ff76e7b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/fingerprints/CrowdfundingBoxFingerprint.kt
@@ -0,0 +1,23 @@
+package app.revanced.patches.youtube.layout.hidecrowdfundingbox.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.hidecrowdfundingbox.resource.patch.CrowdfundingBoxResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object CrowdfundingBoxFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.CONST_4,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.CONST,
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ instruction.opcode.ordinal == Opcode.CONST.ordinal &&
+ (instruction as? WideLiteralInstruction)?.wideLiteral == CrowdfundingBoxResourcePatch.crowdfundingBoxId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/patch/CrowdfundingBoxPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/patch/CrowdfundingBoxPatch.kt
new file mode 100644
index 0000000..0058318
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/patch/CrowdfundingBoxPatch.kt
@@ -0,0 +1,46 @@
+package app.revanced.patches.youtube.layout.hidecrowdfundingbox.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.hidecrowdfundingbox.resource.patch.CrowdfundingBoxResourcePatch
+import app.revanced.patches.youtube.layout.hidecrowdfundingbox.annotations.CrowdfundingBoxCompatibility
+import app.revanced.patches.youtube.layout.hidecrowdfundingbox.bytecode.fingerprints.CrowdfundingBoxFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@DependsOn([IntegrationsPatch::class, CrowdfundingBoxResourcePatch::class])
+@Name("hide-crowdfunding-box")
+@Description("Hides the crowdfunding box between the player and video description.")
+@CrowdfundingBoxCompatibility
+@Version("0.0.1")
+class CrowdfundingBoxPatch : BytecodePatch(
+ listOf(
+ CrowdfundingBoxFingerprint,
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val crowdfundingBoxResult = CrowdfundingBoxFingerprint.result!!
+ val crowdfundingBoxMethod = crowdfundingBoxResult.mutableMethod
+
+ val moveResultObjectIndex =
+ crowdfundingBoxResult.scanResult.patternScanResult!!.endIndex - 2
+
+ crowdfundingBoxMethod.addInstruction(
+ moveResultObjectIndex + 1, """
+ invoke-static {v${(crowdfundingBoxMethod.instruction(moveResultObjectIndex) as OneRegisterInstruction).registerA}}, Lapp/revanced/integrations/patches/HideCrowdfundingBoxPatch;->hideCrowdfundingBox(Landroid/view/View;)V
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/resource/patch/CrowdfundingBoxResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/resource/patch/CrowdfundingBoxResourcePatch.kt
new file mode 100644
index 0000000..2529b26
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/resource/patch/CrowdfundingBoxResourcePatch.kt
@@ -0,0 +1,42 @@
+package app.revanced.patches.youtube.layout.hidecrowdfundingbox.resource.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.layout.hidecrowdfundingbox.annotations.CrowdfundingBoxCompatibility
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Name("crowdfunding-box-resource-patch")
+@CrowdfundingBoxCompatibility
+@DependsOn([SettingsPatch::class, ResourceMappingPatch::class])
+@Version("0.0.1")
+class CrowdfundingBoxResourcePatch : ResourcePatch {
+ companion object {
+ internal var crowdfundingBoxId: Long = -1
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_crowdfunding_box",
+ StringResource("revanced_hide_crowdfunding_box_title", "Hide crowdfunding box"),
+ false,
+ StringResource("revanced_hide_crowdfunding_box_summary_on", "Crowdfunding box is hidden"),
+ StringResource("revanced_hide_crowdfunding_box_summary_off", "Crowdfunding box is shown")
+ )
+ )
+
+ crowdfundingBoxId = ResourceMappingPatch.resourceMappings.single {
+ it.type == "layout" && it.name == "donation_companion"
+ }.id
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/annotations/HideEndScreenCardsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/annotations/HideEndScreenCardsCompatibility.kt
new file mode 100644
index 0000000..03101c7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/annotations/HideEndScreenCardsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.hideendscreencards.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideEndscreenCardsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutCircleFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutCircleFingerprint.kt
new file mode 100644
index 0000000..5f75ca7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutCircleFingerprint.kt
@@ -0,0 +1,22 @@
+package app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.hideendscreencards.resource.patch.HideEndscreenCardsResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object LayoutCircleFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.CONST,
+ Opcode.CONST_4,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ instruction.opcode.ordinal == Opcode.CONST.ordinal &&
+ (instruction as? WideLiteralInstruction)?.wideLiteral == HideEndscreenCardsResourcePatch.layoutCircle
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutIconFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutIconFingerprint.kt
new file mode 100644
index 0000000..8e5730b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutIconFingerprint.kt
@@ -0,0 +1,22 @@
+package app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.hideendscreencards.resource.patch.HideEndscreenCardsResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object LayoutIconFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.CONST_4,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ instruction.opcode.ordinal == Opcode.CONST.ordinal &&
+ (instruction as? WideLiteralInstruction)?.wideLiteral == HideEndscreenCardsResourcePatch.layoutIcon
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutVideoFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutVideoFingerprint.kt
new file mode 100644
index 0000000..4d91d29
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutVideoFingerprint.kt
@@ -0,0 +1,22 @@
+package app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.hideendscreencards.resource.patch.HideEndscreenCardsResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object LayoutVideoFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.CONST,
+ Opcode.CONST_4,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ instruction.opcode.ordinal == Opcode.CONST.ordinal &&
+ (instruction as? WideLiteralInstruction)?.wideLiteral == HideEndscreenCardsResourcePatch.layoutVideo
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/patch/HideEndscreenCardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/patch/HideEndscreenCardsPatch.kt
new file mode 100644
index 0000000..8bef7a9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/patch/HideEndscreenCardsPatch.kt
@@ -0,0 +1,57 @@
+package app.revanced.patches.youtube.layout.hideendscreencards.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.fingerprint.Fingerprint
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.youtube.layout.hideendscreencards.annotations.HideEndscreenCardsCompatibility
+import app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints.LayoutCircleFingerprint
+import app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints.LayoutIconFingerprint
+import app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints.LayoutVideoFingerprint
+import app.revanced.patches.youtube.layout.hideendscreencards.resource.patch.HideEndscreenCardsResourcePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import org.jf.dexlib2.iface.instruction.formats.Instruction21c
+
+@Patch
+@DependsOn([IntegrationsPatch::class, HideEndscreenCardsResourcePatch::class])
+@Name("hide-endscreen-cards")
+@Description("Hides the suggested video cards at the end of a video in fullscreen.")
+@HideEndscreenCardsCompatibility
+@Version("0.0.1")
+class HideEndscreenCardsPatch : BytecodePatch(
+ listOf(
+ LayoutCircleFingerprint,
+ LayoutIconFingerprint,
+ LayoutVideoFingerprint,
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ fun MethodFingerprint.injectHideCall() {
+ val layoutResult = result!!
+ val layoutMethod = layoutResult.mutableMethod
+
+ val checkCastIndex = layoutResult.scanResult.patternScanResult!!.endIndex
+ val viewRegister = (layoutMethod.instruction(checkCastIndex) as Instruction21c).registerA
+
+ layoutMethod.addInstruction(
+ checkCastIndex + 1,
+ "invoke-static { v$viewRegister }, Lapp/revanced/integrations/patches/HideEndscreenCardsPatch;->hideEndscreen(Landroid/view/View;)V"
+ )
+ }
+
+ listOf(LayoutCircleFingerprint, LayoutIconFingerprint, LayoutVideoFingerprint).forEach(MethodFingerprint::injectHideCall)
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/resource/patch/HideEndscreenCardsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/resource/patch/HideEndscreenCardsResourcePatch.kt
new file mode 100644
index 0000000..949e584
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/resource/patch/HideEndscreenCardsResourcePatch.kt
@@ -0,0 +1,48 @@
+package app.revanced.patches.youtube.layout.hideendscreencards.resource.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.layout.hideendscreencards.annotations.HideEndscreenCardsCompatibility
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Name("hide-endscreen-cards-resource-patch")
+@HideEndscreenCardsCompatibility
+@DependsOn([SettingsPatch::class, ResourceMappingPatch::class])
+@Version("0.0.1")
+class HideEndscreenCardsResourcePatch : ResourcePatch {
+ internal companion object {
+ var layoutCircle: Long = -1
+ var layoutIcon: Long = -1
+ var layoutVideo: Long = -1
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_endscreen_cards",
+ StringResource("revanced_hide_endscreen_cards_title", "Hide end-screen cards"),
+ true,
+ StringResource("revanced_hide_endscreen_cards_summary_on", "End-screen cards are hidden"),
+ StringResource("revanced_hide_endscreen_cards_summary_off", "End-screen cards are shown")
+ ),
+ )
+
+ fun findEndscreenResourceId(name: String) = ResourceMappingPatch.resourceMappings.single {
+ it.type == "layout" && it.name == "endscreen_element_layout_$name"
+ }.id
+
+ layoutCircle = findEndscreenResourceId("circle")
+ layoutIcon = findEndscreenResourceId("icon")
+ layoutVideo = findEndscreenResourceId("video")
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/annotations/HideInfocardsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/annotations/HideInfocardsCompatibility.kt
new file mode 100644
index 0000000..03b2030
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/annotations/HideInfocardsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.hideinfocards.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideInfocardsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoFingerprint.kt
new file mode 100644
index 0000000..03df9af
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.layout.hideinfocards.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object InfocardsIncognitoFingerprint : MethodFingerprint(
+ "Ljava/lang/Boolean;",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ strings = listOf("vibrator")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoParentFingerprint.kt
new file mode 100644
index 0000000..53a6be4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoParentFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.layout.hideinfocards.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object InfocardsIncognitoParentFingerprint : MethodFingerprint(
+ "Ljava/lang/String;",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ strings = listOf("player_overlay_info_card_teaser"),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsMethodCallFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsMethodCallFingerprint.kt
new file mode 100644
index 0000000..222a18e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsMethodCallFingerprint.kt
@@ -0,0 +1,19 @@
+package app.revanced.patches.youtube.layout.hideinfocards.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.hideinfocards.resource.patch.HideInfocardsResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object InfocardsMethodCallFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ (instruction as? WideLiteralInstruction)?.wideLiteral == HideInfocardsResourcePatch.drawerResourceId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/patch/HideInfocardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/patch/HideInfocardsPatch.kt
new file mode 100644
index 0000000..9d5f040
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/patch/HideInfocardsPatch.kt
@@ -0,0 +1,77 @@
+package app.revanced.patches.youtube.layout.hideinfocards.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.youtube.layout.hideinfocards.annotations.HideInfocardsCompatibility
+import app.revanced.patches.youtube.layout.hideinfocards.fingerprints.InfocardsIncognitoFingerprint
+import app.revanced.patches.youtube.layout.hideinfocards.fingerprints.InfocardsIncognitoParentFingerprint
+import app.revanced.patches.youtube.layout.hideinfocards.fingerprints.InfocardsMethodCallFingerprint
+import app.revanced.patches.youtube.layout.hideinfocards.resource.patch.HideInfocardsResourcePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.builder.instruction.BuilderInstruction35c
+
+@Patch
+@DependsOn([IntegrationsPatch::class, HideInfocardsResourcePatch::class])
+@Name("hide-info-cards")
+@Description("Hides info-cards in videos.")
+@HideInfocardsCompatibility
+@Version("0.0.1")
+class HideInfocardsPatch : BytecodePatch(
+ listOf(
+ InfocardsIncognitoParentFingerprint,
+ InfocardsMethodCallFingerprint,
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ with(InfocardsIncognitoFingerprint.also {
+ it.resolve(context, InfocardsIncognitoParentFingerprint.result!!.classDef)
+ }.result!!.mutableMethod) {
+ val invokeInstructionIndex = implementation!!.instructions.indexOfFirst {
+ it.opcode.ordinal == Opcode.INVOKE_VIRTUAL.ordinal &&
+ ((it as? BuilderInstruction35c)?.reference.toString() ==
+ "Landroid/view/View;->setVisibility(I)V")
+ }
+
+ replaceInstruction(
+ invokeInstructionIndex,
+ "invoke-static {v${(instruction(invokeInstructionIndex) as? BuilderInstruction35c)?.registerC}}," +
+ " Lapp/revanced/integrations/patches/HideInfocardsPatch;->hideInfocardsIncognito(Landroid/view/View;)V"
+ )
+ }
+
+ with(InfocardsMethodCallFingerprint.result!!) {
+ val hideInfocardsCallMethod = mutableMethod
+
+ val invokeInterfaceIndex = scanResult.patternScanResult!!.endIndex
+ val toggleRegister = hideInfocardsCallMethod.implementation!!.registerCount - 1
+
+ hideInfocardsCallMethod.addInstructions(
+ invokeInterfaceIndex, """
+ invoke-static {}, Lapp/revanced/integrations/patches/HideInfocardsPatch;->hideInfocardsMethodCall()Z
+ move-result v$toggleRegister
+ if-nez v$toggleRegister, :hide_info_cards
+ """,
+ listOf(
+ ExternalLabel(
+ "hide_info_cards", hideInfocardsCallMethod.instruction(invokeInterfaceIndex + 1)
+ )
+ )
+ )
+ }
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/resource/patch/HideInfocardsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/resource/patch/HideInfocardsResourcePatch.kt
new file mode 100644
index 0000000..ff58fb5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/resource/patch/HideInfocardsResourcePatch.kt
@@ -0,0 +1,40 @@
+package app.revanced.patches.youtube.layout.hideinfocards.resource.patch
+
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.layout.hideinfocards.annotations.HideInfocardsCompatibility
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@HideInfocardsCompatibility
+@DependsOn([SettingsPatch::class, ResourceMappingPatch::class])
+@Version("0.0.1")
+class HideInfocardsResourcePatch : ResourcePatch {
+ internal companion object {
+ var drawerResourceId: Long = -1
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_infocards",
+ StringResource("revanced_hide_infocards_title", "Hide info-cards"),
+ true,
+ StringResource("revanced_hide_infocards_summary_on", "Info-cards are hidden"),
+ StringResource("revanced_hide_infocards_summary_off", "Info-cards are shown")
+ )
+ )
+
+ drawerResourceId = ResourceMappingPatch.resourceMappings.single {
+ it.type == "id" && it.name == "info_cards_drawer_header"
+ }.id
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/annotations/MixPlaylistsPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/annotations/MixPlaylistsPatchCompatibility.kt
new file mode 100644
index 0000000..030bc1e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/annotations/MixPlaylistsPatchCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.hidemixplaylists.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class MixPlaylistsPatchCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/CreateMixPlaylistFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/CreateMixPlaylistFingerprint.kt
new file mode 100644
index 0000000..0105797
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/CreateMixPlaylistFingerprint.kt
@@ -0,0 +1,24 @@
+package app.revanced.patches.youtube.layout.hidemixplaylists.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object CreateMixPlaylistFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L", "L", "L", "L", "L"), listOf(
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/SecondCreateMixPlaylistFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/SecondCreateMixPlaylistFingerprint.kt
new file mode 100644
index 0000000..dcbb906
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/SecondCreateMixPlaylistFingerprint.kt
@@ -0,0 +1,24 @@
+package app.revanced.patches.youtube.layout.hidemixplaylists.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object SecondCreateMixPlaylistFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L", "L", "L", "L"), listOf(
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.RETURN_VOID
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/patch/MixPlaylistsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/patch/MixPlaylistsPatch.kt
new file mode 100644
index 0000000..e241848
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/patch/MixPlaylistsPatch.kt
@@ -0,0 +1,64 @@
+package app.revanced.patches.youtube.layout.hidemixplaylists.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patches.youtube.layout.hidemixplaylists.annotations.MixPlaylistsPatchCompatibility
+import app.revanced.patches.youtube.layout.hidemixplaylists.fingerprints.CreateMixPlaylistFingerprint
+import app.revanced.patches.youtube.layout.hidemixplaylists.fingerprints.SecondCreateMixPlaylistFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@DependsOn([IntegrationsPatch::class])
+@Name("hide-my-mix")
+@Description("Hides mix playlists.")
+@MixPlaylistsPatchCompatibility
+@Version("0.0.1")
+class MixPlaylistsPatch : BytecodePatch(
+ listOf(
+ CreateMixPlaylistFingerprint, SecondCreateMixPlaylistFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_mix_playlists",
+ StringResource("revanced_hide_mix_playlists_title", "Hide mix playlists"),
+ false,
+ StringResource("revanced_hide_mix_playlists_summary_on", "Mix playlists are hidden"),
+ StringResource("revanced_hide_mix_playlists_summary_off", "Mix playlists are shown")
+ )
+ )
+
+ arrayOf(CreateMixPlaylistFingerprint, SecondCreateMixPlaylistFingerprint).forEach(::addHook)
+
+ return PatchResultSuccess()
+ }
+
+ private fun addHook(fingerprint: MethodFingerprint) {
+ with (fingerprint.result!!) {
+ val insertIndex = scanResult.patternScanResult!!.endIndex - 3
+
+ val register = (mutableMethod.instruction(insertIndex - 2) as OneRegisterInstruction).registerA
+
+ mutableMethod.addInstruction(
+ insertIndex,
+ "invoke-static {v$register}, Lapp/revanced/integrations/patches/HideMixPlaylistsPatch;->hideMixPlaylists(Landroid/view/View;)V"
+ )
+ }
+
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/annotations/HideTimeAndSeekbarCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/annotations/HideTimeAndSeekbarCompatibility.kt
new file mode 100644
index 0000000..c6b4b2a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/annotations/HideTimeAndSeekbarCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.hidetimeandseekbar.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideTimeAndSeekbarCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/fingerprints/TimeCounterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/fingerprints/TimeCounterFingerprint.kt
new file mode 100644
index 0000000..5bd00c5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/fingerprints/TimeCounterFingerprint.kt
@@ -0,0 +1,25 @@
+package app.revanced.patches.youtube.layout.hidetimeandseekbar.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(3)
+object TimeCounterFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf(
+ Opcode.IGET_OBJECT,
+ Opcode.IGET_WIDE,
+ Opcode.CONST_WIDE_16,
+ Opcode.CMP_LONG,
+ Opcode.IF_LEZ,
+ Opcode.IGET_OBJECT,
+ Opcode.IF_EQZ,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.GOTO,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/patch/HideTimeAndSeekbarPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/patch/HideTimeAndSeekbarPatch.kt
new file mode 100644
index 0000000..8a7821a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/patch/HideTimeAndSeekbarPatch.kt
@@ -0,0 +1,76 @@
+package app.revanced.patches.youtube.layout.hidetimeandseekbar.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.shared.fingerprints.SeekbarFingerprint
+import app.revanced.patches.shared.fingerprints.SeekbarOnDrawFingerprint
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.layout.hidetimeandseekbar.annotations.HideTimeAndSeekbarCompatibility
+import app.revanced.patches.youtube.layout.hidetimeandseekbar.fingerprints.TimeCounterFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("hide-time-and-seekbar")
+@Description("Hides progress bar and time counter on videos.")
+@HideTimeAndSeekbarCompatibility
+@Version("0.0.1")
+class HideTimeAndSeekbarPatch : BytecodePatch(
+ listOf(
+ SeekbarFingerprint, TimeCounterFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_time_and_seekbar",
+ StringResource("revanced_hide_time_and_seekbar_title", "Hide time and seekbar"),
+ false,
+ StringResource("revanced_hide_time_and_seekbar_summary_on", "Time and seekbar are hidden"),
+ StringResource("revanced_hide_time_and_seekbar_summary_off", "Time and seekbar are shown")
+ )
+ )
+
+ val createVideoPlayerSeekbarMethod = SeekbarFingerprint.result!!.let {
+ SeekbarOnDrawFingerprint.apply { resolve(context, it.mutableClass) }
+ }.result!!.mutableMethod
+
+ createVideoPlayerSeekbarMethod.addInstructions(
+ 0, """
+ const/4 v0, 0x0
+ invoke-static { }, Lapp/revanced/integrations/patches/HideTimeAndSeekbarPatch;->hideTimeAndSeekbar()Z
+ move-result v0
+ if-eqz v0, :hide_time_and_seekbar
+ return-void
+ :hide_time_and_seekbar
+ nop
+ """
+ )
+
+ val timeCounterMethod = TimeCounterFingerprint.result!!.mutableMethod
+
+ timeCounterMethod.addInstructions(
+ 0, """
+ invoke-static { }, Lapp/revanced/integrations/patches/HideTimeAndSeekbarPatch;->hideTimeAndSeekbar()Z
+ move-result v0
+ if-eqz v0, :hide_time_and_seekbar
+ return-void
+ :hide_time_and_seekbar
+ nop
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/annotations/OldQualityLayoutCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/annotations/OldQualityLayoutCompatibility.kt
new file mode 100644
index 0000000..e6e5058
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/annotations/OldQualityLayoutCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.oldqualitylayout.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class OldQualityLayoutCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/fingerprints/QualityMenuViewInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/fingerprints/QualityMenuViewInflateFingerprint.kt
new file mode 100644
index 0000000..4abb688
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/fingerprints/QualityMenuViewInflateFingerprint.kt
@@ -0,0 +1,31 @@
+package app.revanced.patches.youtube.layout.oldqualitylayout.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object QualityMenuViewInflateFingerprint : MethodFingerprint(
+ "L", AccessFlags.FINAL or AccessFlags.PUBLIC, listOf("L", "L", "L"), listOf(
+ Opcode.INVOKE_SUPER,
+ Opcode.CONST,
+ Opcode.CONST_4,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CONST_16,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.CONST_STRING,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/patch/OldQualityLayoutPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/patch/OldQualityLayoutPatch.kt
new file mode 100644
index 0000000..dd4597b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/patch/OldQualityLayoutPatch.kt
@@ -0,0 +1,61 @@
+package app.revanced.patches.youtube.layout.oldqualitylayout.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.oldqualitylayout.annotations.OldQualityLayoutCompatibility
+import app.revanced.patches.youtube.layout.oldqualitylayout.fingerprints.QualityMenuViewInflateFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("old-quality-layout")
+@Description("Enables the original video quality flyout in the video player settings")
+@OldQualityLayoutCompatibility
+@Version("0.0.1")
+// new ReVanced users have no idea what it means to use the "old quality layout menu"
+// maybe rename this patch to better describe what it provides (ie: user-selectable-video-resolution )
+class OldQualityLayoutPatch : BytecodePatch(
+ listOf(QualityMenuViewInflateFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_use_old_style_quality_settings",
+ StringResource("revanced_old_style_quality_settings_enabled_title", "Use old video quality player menu"),
+ true,
+ StringResource("revanced_old_style_quality_settings_summary_on", "Old video quality menu is used"),
+ StringResource("revanced_old_style_quality_settings_summary_off", "Old video quality menu is not used")
+ )
+ )
+
+ val inflateFingerprintResult = QualityMenuViewInflateFingerprint.result!!
+ val method = inflateFingerprintResult.mutableMethod
+ val instructions = method.implementation!!.instructions
+
+ // at this index the listener is added to the list view
+ val listenerInvokeRegister = instructions.size - 1 - 1
+
+ // get the register which stores the quality menu list view
+ val onItemClickViewRegister = (instructions[listenerInvokeRegister] as FiveRegisterInstruction).registerC
+
+ // insert the integrations method
+ method.addInstruction(
+ listenerInvokeRegister, // insert the integrations instructions right before the listener
+ "invoke-static { v$onItemClickViewRegister }, Lapp/revanced/integrations/patches/playback/quality/OldQualityLayoutPatch;->showOldQualityMenu(Landroid/widget/ListView;)V"
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/annotations/HideEmailAddressCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/annotations/HideEmailAddressCompatibility.kt
new file mode 100644
index 0000000..527d1b8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/annotations/HideEmailAddressCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.personalinformation.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideEmailAddressCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/fingerprints/AccountSwitcherAccessibilityLabelFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/fingerprints/AccountSwitcherAccessibilityLabelFingerprint.kt
new file mode 100644
index 0000000..0f9de46
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/fingerprints/AccountSwitcherAccessibilityLabelFingerprint.kt
@@ -0,0 +1,24 @@
+package app.revanced.patches.youtube.layout.personalinformation.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.personalinformation.resource.patch.HideEmailAddressResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object AccountSwitcherAccessibilityLabelFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IGET_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.NEW_ARRAY,
+ Opcode.CONST_4,
+ Opcode.APUT_OBJECT,
+ Opcode.CONST,
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ instruction.opcode.ordinal == Opcode.CONST.ordinal &&
+ (instruction as? WideLiteralInstruction)?.wideLiteral == HideEmailAddressResourcePatch.accountSwitcherAccessibilityLabelId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/patch/HideEmailAddressPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/patch/HideEmailAddressPatch.kt
new file mode 100644
index 0000000..46234db
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/patch/HideEmailAddressPatch.kt
@@ -0,0 +1,53 @@
+package app.revanced.patches.youtube.layout.personalinformation.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.youtube.layout.personalinformation.annotations.HideEmailAddressCompatibility
+import app.revanced.patches.youtube.layout.personalinformation.bytecode.fingerprints.AccountSwitcherAccessibilityLabelFingerprint
+import app.revanced.patches.youtube.layout.personalinformation.resource.patch.HideEmailAddressResourcePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@DependsOn([IntegrationsPatch::class, HideEmailAddressResourcePatch::class])
+@Name("hide-email-address")
+@Description("Hides the email address in the account switcher.")
+@HideEmailAddressCompatibility
+@Version("0.0.1")
+class HideEmailAddressPatch : BytecodePatch(
+ listOf(
+ AccountSwitcherAccessibilityLabelFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val accountSwitcherAccessibilityLabelResult = AccountSwitcherAccessibilityLabelFingerprint.result!!
+ val accountSwitcherAccessibilityLabelMethod = accountSwitcherAccessibilityLabelResult.mutableMethod
+
+ val setVisibilityConstIndex =
+ accountSwitcherAccessibilityLabelResult.scanResult.patternScanResult!!.endIndex
+
+ val setVisibilityConstRegister = (
+ accountSwitcherAccessibilityLabelMethod.instruction
+ (setVisibilityConstIndex - 2) as OneRegisterInstruction
+ ).registerA
+
+ accountSwitcherAccessibilityLabelMethod.addInstructions(
+ setVisibilityConstIndex, """
+ invoke-static {v$setVisibilityConstRegister}, Lapp/revanced/integrations/patches/HideEmailAddressPatch;->hideEmailAddress(I)I
+ move-result v$setVisibilityConstRegister
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/resource/patch/HideEmailAddressResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/resource/patch/HideEmailAddressResourcePatch.kt
new file mode 100644
index 0000000..4029b83
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/resource/patch/HideEmailAddressResourcePatch.kt
@@ -0,0 +1,42 @@
+package app.revanced.patches.youtube.layout.personalinformation.resource.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.layout.personalinformation.annotations.HideEmailAddressCompatibility
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Name("hide-email-address-resource-patch")
+@HideEmailAddressCompatibility
+@DependsOn([SettingsPatch::class, ResourceMappingPatch::class])
+@Version("0.0.1")
+class HideEmailAddressResourcePatch : ResourcePatch {
+ companion object {
+ internal var accountSwitcherAccessibilityLabelId: Long = -1
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_email_address",
+ StringResource("revanced_hide_email_address_title", "Hide email in account switcher"),
+ false,
+ StringResource("revanced_hide_email_address_summary_on", "Email address is hidden"),
+ StringResource("revanced_hide_email_address_summary_off", "Email address is shown")
+ )
+ )
+
+ accountSwitcherAccessibilityLabelId = ResourceMappingPatch.resourceMappings.single {
+ it.type == "string" && it.name == "account_switcher_accessibility_label"
+ }.id
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/annotations/PivotBarCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/annotations/PivotBarCompatibility.kt
new file mode 100644
index 0000000..1457062
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/annotations/PivotBarCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.pivotbar.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PivotBarCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/fingerprints/PivotBarCreateButtonViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/fingerprints/PivotBarCreateButtonViewFingerprint.kt
new file mode 100644
index 0000000..8be27c3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/fingerprints/PivotBarCreateButtonViewFingerprint.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.layout.pivotbar.createbutton.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object PivotBarCreateButtonViewFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_DIRECT_RANGE, // unique instruction anchor
+ Opcode.CONST_4,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_STATIC
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/patch/CreateButtonRemoverPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/patch/CreateButtonRemoverPatch.kt
new file mode 100644
index 0000000..f3b70d5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/patch/CreateButtonRemoverPatch.kt
@@ -0,0 +1,71 @@
+package app.revanced.patches.youtube.layout.pivotbar.createbutton.patch
+
+import app.revanced.extensions.toErrorResult
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.layout.pivotbar.annotations.PivotBarCompatibility
+import app.revanced.patches.youtube.layout.pivotbar.createbutton.fingerprints.PivotBarCreateButtonViewFingerprint
+import app.revanced.patches.youtube.layout.pivotbar.fingerprints.InitializeButtonsFingerprint
+import app.revanced.patches.youtube.layout.pivotbar.resource.patch.ResolvePivotBarFingerprintsPatch
+import app.revanced.patches.youtube.layout.pivotbar.utils.InjectionUtils.REGISTER_TEMPLATE_REPLACEMENT
+import app.revanced.patches.youtube.layout.pivotbar.utils.InjectionUtils.injectHook
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, ResourceMappingPatch::class, SettingsPatch::class, ResolvePivotBarFingerprintsPatch::class])
+@Name("hide-create-button")
+@Description("Hides the create button in the navigation bar.")
+@PivotBarCompatibility
+@Version("0.0.1")
+class CreateButtonRemoverPatch : BytecodePatch() {
+ private companion object {
+ const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/HideCreateButtonPatch;"
+ }
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_create_button",
+ StringResource("revanced_hide_create_button_title", "Hide create button"),
+ true,
+ StringResource("revanced_hide_create_button_summary_on", "Create button is hidden"),
+ StringResource("revanced_hide_create_button_summary_off", "Create button is shown")
+ )
+ )
+
+ /*
+ * Resolve fingerprints
+ */
+
+ InitializeButtonsFingerprint.result!!.let {
+ if (!PivotBarCreateButtonViewFingerprint.resolve(context, it.mutableMethod, it.mutableClass))
+ return PivotBarCreateButtonViewFingerprint.toErrorResult()
+ }
+
+ val createButtonResult = PivotBarCreateButtonViewFingerprint.result!!
+ val insertIndex = createButtonResult.scanResult.patternScanResult!!.endIndex
+
+ /*
+ * Inject hooks
+ */
+
+ val hook = "invoke-static { v$REGISTER_TEMPLATE_REPLACEMENT }, " +
+ "$INTEGRATIONS_CLASS_DESCRIPTOR->hideCreateButton(Landroid/view/View;)V"
+
+ createButtonResult.mutableMethod.injectHook(hook, insertIndex)
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/InitializeButtonsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/InitializeButtonsFingerprint.kt
new file mode 100644
index 0000000..32a77f7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/InitializeButtonsFingerprint.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.youtube.layout.pivotbar.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.pivotbar.resource.patch.ResolvePivotBarFingerprintsPatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object InitializeButtonsFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any {
+ it.opcode == Opcode.CONST && (it as WideLiteralInstruction).wideLiteral ==
+ ResolvePivotBarFingerprintsPatch.imageOnlyTabResourceId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/PivotBarConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/PivotBarConstructorFingerprint.kt
new file mode 100644
index 0000000..0609dd1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/PivotBarConstructorFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.layout.pivotbar.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object PivotBarConstructorFingerprint : MethodFingerprint(
+ access = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
+ strings = listOf("com.google.android.apps.youtube.app.endpoint.flags")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/resource/patch/ResolvePivotBarFingerprintsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/resource/patch/ResolvePivotBarFingerprintsPatch.kt
new file mode 100644
index 0000000..785d61c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/resource/patch/ResolvePivotBarFingerprintsPatch.kt
@@ -0,0 +1,45 @@
+package app.revanced.patches.youtube.layout.pivotbar.resource.patch
+
+import app.revanced.extensions.toErrorResult
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.layout.pivotbar.annotations.PivotBarCompatibility
+import app.revanced.patches.youtube.layout.pivotbar.fingerprints.InitializeButtonsFingerprint
+import app.revanced.patches.youtube.layout.pivotbar.fingerprints.PivotBarConstructorFingerprint
+
+@DependsOn([ResourceMappingPatch::class])
+@PivotBarCompatibility
+@Description("Resolves necessary fingerprints.")
+@Version("0.0.1")
+class ResolvePivotBarFingerprintsPatch : BytecodePatch(
+ listOf(PivotBarConstructorFingerprint)
+) {
+ internal companion object {
+ var imageOnlyTabResourceId: Long = -1
+ }
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ // imageOnlyTabResourceId is used in InitializeButtonsFingerprint fingerprint
+ ResourceMappingPatch.resourceMappings.find { it.type == "layout" && it.name == "image_only_tab" }
+ ?.let { imageOnlyTabResourceId = it.id } ?: return PatchResultError("Failed to find resource")
+
+ PivotBarConstructorFingerprint.result?.let {
+ // Resolve InitializeButtonsFingerprint on the class of the method
+ // which PivotBarConstructorFingerprint resolved to
+ if (!InitializeButtonsFingerprint.resolve(
+ context,
+ it.classDef
+ )
+ ) return InitializeButtonsFingerprint.toErrorResult()
+ } ?: return PivotBarConstructorFingerprint.toErrorResult()
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarEnumFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarEnumFingerprint.kt
new file mode 100644
index 0000000..770d63c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarEnumFingerprint.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.youtube.layout.pivotbar.shortsbutton.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object PivotBarEnumFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.IF_NEZ, // target reference
+ Opcode.SGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarShortsButtonViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarShortsButtonViewFingerprint.kt
new file mode 100644
index 0000000..cfa5e93
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarShortsButtonViewFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.layout.pivotbar.shortsbutton.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object PivotBarShortsButtonViewFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_VIRTUAL_RANGE,
+ Opcode.MOVE_RESULT_OBJECT, // target reference
+ Opcode.GOTO,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/patch/ShortsButtonRemoverPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/patch/ShortsButtonRemoverPatch.kt
new file mode 100644
index 0000000..259f1bc
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/patch/ShortsButtonRemoverPatch.kt
@@ -0,0 +1,93 @@
+package app.revanced.patches.youtube.layout.pivotbar.shortsbutton.patch
+
+import app.revanced.extensions.toErrorResult
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.layout.pivotbar.annotations.PivotBarCompatibility
+import app.revanced.patches.youtube.layout.pivotbar.fingerprints.InitializeButtonsFingerprint
+import app.revanced.patches.youtube.layout.pivotbar.resource.patch.ResolvePivotBarFingerprintsPatch
+import app.revanced.patches.youtube.layout.pivotbar.shortsbutton.fingerprints.PivotBarEnumFingerprint
+import app.revanced.patches.youtube.layout.pivotbar.shortsbutton.fingerprints.PivotBarShortsButtonViewFingerprint
+import app.revanced.patches.youtube.layout.pivotbar.utils.InjectionUtils.REGISTER_TEMPLATE_REPLACEMENT
+import app.revanced.patches.youtube.layout.pivotbar.utils.InjectionUtils.injectHook
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class, ResolvePivotBarFingerprintsPatch::class])
+@Name("hide-shorts-button")
+@Description("Hides the shorts button on the navigation bar.")
+@PivotBarCompatibility
+@Version("0.0.1")
+class ShortsButtonRemoverPatch : BytecodePatch() {
+ private companion object {
+ const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/HideShortsButtonPatch;"
+ }
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_shorts_button",
+ StringResource("revanced_hide_shorts_button_title", "Hide shorts button"),
+ true,
+ StringResource("revanced_hide_shorts_button_summary_on", "Shorts button is hidden"),
+ StringResource("revanced_hide_shorts_button_summary_off", "Shorts button is shown")
+ )
+ )
+
+ /*
+ * Resolve fingerprints
+ */
+
+ val initializeButtonsResult = InitializeButtonsFingerprint.result!!
+
+ val fingerprintResults =
+ arrayOf(PivotBarEnumFingerprint, PivotBarShortsButtonViewFingerprint)
+ .onEach {
+ if (!it.resolve(
+ context,
+ initializeButtonsResult.mutableMethod,
+ initializeButtonsResult.mutableClass
+ )
+ )
+ return it.toErrorResult()
+ }
+ .map { it.result!!.scanResult.patternScanResult!! }
+
+
+ val enumScanResult = fingerprintResults[0]
+ val buttonViewResult = fingerprintResults[1]
+
+ val enumHookInsertIndex = enumScanResult.startIndex + 2
+ val buttonHookInsertIndex = buttonViewResult.endIndex
+
+ /*
+ * Inject hooks
+ */
+
+ val enumHook = "sput-object v$REGISTER_TEMPLATE_REPLACEMENT, " +
+ "$INTEGRATIONS_CLASS_DESCRIPTOR->lastPivotTab:Ljava/lang/Enum;"
+ val buttonHook = "invoke-static { v$REGISTER_TEMPLATE_REPLACEMENT }, " +
+ "$INTEGRATIONS_CLASS_DESCRIPTOR->hideShortsButton(Landroid/view/View;)V"
+
+ // Inject bottom to top to not mess up the indices
+ mapOf(
+ buttonHook to buttonHookInsertIndex,
+ enumHook to enumHookInsertIndex
+ ).forEach { (hook, insertIndex) ->
+ initializeButtonsResult.mutableMethod.injectHook(hook, insertIndex)
+ }
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/utils/InjectionUtils.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/utils/InjectionUtils.kt
new file mode 100644
index 0000000..8667b10
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/utils/InjectionUtils.kt
@@ -0,0 +1,30 @@
+package app.revanced.patches.youtube.layout.pivotbar.utils
+
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import org.jf.dexlib2.Opcode.MOVE_RESULT_OBJECT
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+internal object InjectionUtils {
+ const val REGISTER_TEMPLATE_REPLACEMENT: String = "REGISTER_INDEX"
+
+ /**
+ * Injects an instruction into insertIndex of the hook.
+ * @param hook The hook to insert.
+ * @param insertIndex The index to insert the instruction at.
+ * [MOVE_RESULT_OBJECT] has to be the previous instruction before [insertIndex].
+ */
+ fun MutableMethod.injectHook(hook: String, insertIndex: Int) {
+ val injectTarget = this
+
+ // Register to pass to the hook
+ val registerIndex = insertIndex - 1 // MOVE_RESULT_OBJECT is always the previous instruction
+ val register = (injectTarget.instruction(registerIndex) as OneRegisterInstruction).registerA
+
+ injectTarget.addInstruction(
+ insertIndex,
+ hook.replace("REGISTER_INDEX", register.toString()),
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/annotations/PlayerButtonBackgroundCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/annotations/PlayerButtonBackgroundCompatibility.kt
new file mode 100644
index 0000000..016ff8f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/annotations/PlayerButtonBackgroundCompatibility.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.layout.playerbuttonbackground.annotations
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.google.android.youtube", arrayOf("17.49.37"))
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PlayerButtonBackgroundCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/patch/PlayerButtonBackgroundPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/patch/PlayerButtonBackgroundPatch.kt
new file mode 100644
index 0000000..c99615d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/patch/PlayerButtonBackgroundPatch.kt
@@ -0,0 +1,41 @@
+package app.revanced.patches.youtube.layout.playerbuttonbackground.patch
+
+import app.revanced.extensions.doRecursively
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patches.youtube.layout.playerbuttonbackground.annotations.PlayerButtonBackgroundCompatibility
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import org.w3c.dom.Element
+
+@Patch
+@DependsOn([FixLocaleConfigErrorPatch::class])
+@Name("remove-player-button-background")
+@Description("Removes the background from the video player buttons.")
+@PlayerButtonBackgroundCompatibility
+@Version("0.0.1")
+class PlayerButtonBackgroundPatch : ResourcePatch {
+ private companion object {
+ const val RESOURCE_FILE_PATH = "res/drawable/player_button_circle_background.xml"
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ context.xmlEditor[RESOURCE_FILE_PATH].use { editor ->
+ editor.file.doRecursively node@{ node ->
+ if (node !is Element) return@node
+
+ node.getAttributeNode("android:color")?.let { attribute ->
+ attribute.textContent = "@android:color/transparent"
+ }
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/annotations/PlayerPopupPanelsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/annotations/PlayerPopupPanelsCompatibility.kt
new file mode 100644
index 0000000..aad154c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/annotations/PlayerPopupPanelsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.playerpopuppanels.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PlayerPopupPanelsCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/fingerprints/EngagementPanelControllerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/fingerprints/EngagementPanelControllerFingerprint.kt
new file mode 100644
index 0000000..4b9636f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/fingerprints/EngagementPanelControllerFingerprint.kt
@@ -0,0 +1,36 @@
+package app.revanced.patches.youtube.layout.playerpopuppanels.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(3)
+object EngagementPanelControllerFingerprint : MethodFingerprint(
+ "L", AccessFlags.PRIVATE or AccessFlags.FINAL, listOf("L", "L", "Z", "Z", "Z"), listOf(
+ Opcode.MOVE_OBJECT_FROM16,
+ Opcode.MOVE_OBJECT_FROM16,
+ Opcode.MOVE_FROM16,
+ Opcode.IGET_BOOLEAN,
+ Opcode.CONST_4,
+ Opcode.IF_NEZ,
+ Opcode.CONST_STRING,
+ Opcode.INVOKE_STATIC,
+ Opcode.SGET_OBJECT,
+ Opcode.SGET_OBJECT,
+ Opcode.CONST_STRING,
+ Opcode.INVOKE_STATIC,
+ Opcode.RETURN_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.CONST_4,
+ Opcode.CONST_4,
+ Opcode.CONST_4,
+ Opcode.CONST_4,
+ Opcode.CONST_4,
+ Opcode.CONST_4,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/patch/PlayerPopupPanelsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/patch/PlayerPopupPanelsPatch.kt
new file mode 100644
index 0000000..9aca33f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/patch/PlayerPopupPanelsPatch.kt
@@ -0,0 +1,59 @@
+package app.revanced.patches.youtube.layout.playerpopuppanels.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.playerpopuppanels.annotations.PlayerPopupPanelsCompatibility
+import app.revanced.patches.youtube.layout.playerpopuppanels.fingerprints.EngagementPanelControllerFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("disable-auto-player-popup-panels")
+@Description("Disable automatic popup panels (playlist or live chat) on video player.")
+@PlayerPopupPanelsCompatibility
+@Version("0.0.1")
+class PlayerPopupPanelsPatch : BytecodePatch(
+ listOf(
+ EngagementPanelControllerFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_player_popup_panels_enabled",
+ StringResource("revanced_player_popup_panels_title", "Disable player popup panels"),
+ false,
+ StringResource("revanced_player_popup_panels_summary_on", "Player popup panels are disabled"),
+ StringResource("revanced_player_popup_panels_summary_off", "Player popup panels are enabled")
+ )
+ )
+
+ val engagementPanelControllerMethod = EngagementPanelControllerFingerprint.result!!.mutableMethod
+
+ engagementPanelControllerMethod.addInstructions(
+ 0, """
+ invoke-static { }, Lapp/revanced/integrations/patches/DisablePlayerPopupPanelsPatch;->disablePlayerPopupPanels()Z
+ move-result v0
+ if-eqz v0, :player_popup_panels
+ if-eqz p4, :player_popup_panels
+ const/4 v0, 0x0
+ return-object v0
+ :player_popup_panels
+ nop
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/reels/annotations/HideReelsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/annotations/HideReelsCompatibility.kt
new file mode 100644
index 0000000..280d5c1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/annotations/HideReelsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.reels.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideReelsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/reels/fingerprints/HideReelsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/fingerprints/HideReelsFingerprint.kt
new file mode 100644
index 0000000..af24744
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/fingerprints/HideReelsFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.layout.reels.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object HideReelsFingerprint : MethodFingerprint(
+ access = AccessFlags.PROTECTED or AccessFlags.FINAL, parameters = listOf("L", "L"),
+ strings = listOf("multiReelDismissalCallback", "reelItemRenderers", "reelDismissalInfo")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/reels/patch/HideReelsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/patch/HideReelsPatch.kt
new file mode 100644
index 0000000..65ed14c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/patch/HideReelsPatch.kt
@@ -0,0 +1,51 @@
+package app.revanced.patches.youtube.layout.reels.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.layout.reels.annotations.HideReelsCompatibility
+import app.revanced.patches.youtube.layout.reels.fingerprints.HideReelsFingerprint
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+//@Patch TODO: this is currently in the general-bytecode-ads patch due to the integrations having a preference for including reels or not. Move it here.
+@Name("hide-reels")
+@Description("Hides reels on the home page.")
+@DependsOn([SettingsPatch::class])
+@HideReelsCompatibility
+@Version("0.0.1")
+class HideReelsPatch : BytecodePatch(
+ listOf(
+ HideReelsFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_reel_button",
+ StringResource("revanced_hide_reel_button_title", "Hide reels button"),
+ true,
+ StringResource("revanced_hide_reel_button_summary_on", "Reels button is hidden"),
+ StringResource("revanced_hide_reel_button_summary_off", "Reels button is shown")
+ )
+ )
+
+ val result = HideReelsFingerprint.result!!
+
+ // HideReel will hide the reel view before it is being used,
+ // so we pass the view to the HideReel method
+ result.mutableMethod.addInstruction(
+ result.scanResult.patternScanResult!!.endIndex,
+ "invoke-static { v2 }, Lapp/revanced/integrations/patches/HideReelsPatch;->HideReel(Landroid/view/View;)V"
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/annotations/ReturnYouTubeDislikeCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/annotations/ReturnYouTubeDislikeCompatibility.kt
new file mode 100644
index 0000000..393f8ed
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/annotations/ReturnYouTubeDislikeCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.returnyoutubedislike.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ReturnYouTubeDislikeCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/DislikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/DislikeFingerprint.kt
new file mode 100644
index 0000000..81c5699
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/DislikeFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object DislikeFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PROTECTED or AccessFlags.CONSTRUCTOR,
+ strings = listOf("like/dislike")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/LikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/LikeFingerprint.kt
new file mode 100644
index 0000000..f3d9a8f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/LikeFingerprint.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+
+@FuzzyPatternScanMethod(2)
+object LikeFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PROTECTED or AccessFlags.CONSTRUCTOR,
+ strings = listOf("like/like")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RemoveLikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RemoveLikeFingerprint.kt
new file mode 100644
index 0000000..17b43b2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RemoveLikeFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object RemoveLikeFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PROTECTED or AccessFlags.CONSTRUCTOR,
+ strings = listOf("like/removelike")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentFingerprint.kt
new file mode 100644
index 0000000..dadd8e6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object TextComponentFingerprint : MethodFingerprint(
+ strings = listOf("com.google.android.apps.youtube.music")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentSpecParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentSpecParentFingerprint.kt
new file mode 100644
index 0000000..631ca5f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentSpecParentFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object TextComponentSpecParentFingerprint : MethodFingerprint(
+ strings = listOf("TextComponentSpec: No converter for extension: ")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt
new file mode 100644
index 0000000..de92cf1
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt
@@ -0,0 +1,85 @@
+package app.revanced.patches.youtube.layout.returnyoutubedislike.patch
+
+import app.revanced.extensions.toErrorResult
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility
+import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.*
+import app.revanced.patches.youtube.layout.returnyoutubedislike.resource.patch.ReturnYouTubeDislikeResourcePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, VideoIdPatch::class, ReturnYouTubeDislikeResourcePatch::class])
+@Name("return-youtube-dislike")
+@Description("Shows the dislike count of videos using the Return YouTube Dislike API.")
+@ReturnYouTubeDislikeCompatibility
+@Version("0.0.1")
+class ReturnYouTubeDislikePatch : BytecodePatch(
+ listOf(
+ TextComponentSpecParentFingerprint, LikeFingerprint, DislikeFingerprint, RemoveLikeFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ listOf(
+ LikeFingerprint.toPatch(Vote.LIKE),
+ DislikeFingerprint.toPatch(Vote.DISLIKE),
+ RemoveLikeFingerprint.toPatch(Vote.REMOVE_LIKE)
+ ).forEach { (fingerprint, vote) ->
+ with(fingerprint.result ?: return PatchResultError("Failed to find ${fingerprint.name} method.")) {
+ mutableMethod.addInstructions(
+ 0,
+ """
+ const/4 v0, ${vote.value}
+ invoke-static {v0}, Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->sendVote(I)V
+ """
+ )
+ }
+ }
+
+ VideoIdPatch.injectCall("Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->newVideoLoaded(Ljava/lang/String;)V")
+
+ with(TextComponentFingerprint
+ .apply { resolve(context, TextComponentSpecParentFingerprint.result!!.classDef) }
+ .result ?: return TextComponentFingerprint.toErrorResult()
+ ) {
+ val createComponentMethod = mutableMethod
+
+ val conversionContextParam = 5
+ val textRefParam = createComponentMethod.parameters.size - 2
+ val insertIndex = scanResult.stringsScanResult!!.matches.first().index - 2
+
+ createComponentMethod.addInstructions(
+ insertIndex,
+ """
+ move-object/from16 v7, p$conversionContextParam
+ move-object/from16 v8, p$textRefParam
+ invoke-static {v7, v8}, Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->onComponentCreated(Ljava/lang/Object;Ljava/util/concurrent/atomic/AtomicReference;)V
+ """
+ )
+ }
+ return PatchResultSuccess()
+ }
+
+ private fun MethodFingerprint.toPatch(voteKind: Vote) = VotePatch(this, voteKind)
+
+ private data class VotePatch(val fingerprint: MethodFingerprint, val voteKind: Vote)
+
+ private enum class Vote(val value: Int) {
+ LIKE(1),
+ DISLIKE(-1),
+ REMOVE_LIKE(0)
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt
new file mode 100644
index 0000000..35dd0af
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt
@@ -0,0 +1,42 @@
+package app.revanced.patches.youtube.layout.returnyoutubedislike.resource.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.settings.preference.impl.Preference
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.util.resources.ResourceUtils.mergeStrings
+
+@DependsOn([FixLocaleConfigErrorPatch::class, SettingsPatch::class])
+@Name("return-youtube-dislike-resource-patch")
+@Description("Adds the preferences for Return YouTube Dislike.")
+@ReturnYouTubeDislikeCompatibility
+@Version("0.0.1")
+class ReturnYouTubeDislikeResourcePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ val youtubePackage = "com.google.android.youtube"
+ SettingsPatch.addPreference(
+ Preference(
+ StringResource("revanced_ryd_settings_title", "Return YouTube Dislike"),
+ Preference.Intent(
+ youtubePackage,
+ "ryd_settings",
+ "com.google.android.libraries.social.licenses.LicenseActivity"
+ ),
+ StringResource("revanced_ryd_settings_summary", "Settings for Return YouTube Dislike"),
+ )
+ )
+ // merge strings
+ context.mergeStrings("returnyoutubedislike/host/values/strings.xml")
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/annotations/SponsorBlockCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/annotations/SponsorBlockCompatibility.kt
new file mode 100644
index 0000000..710fb22
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/annotations/SponsorBlockCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.sponsorblock.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class SponsorBlockCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/AppendTimeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/AppendTimeFingerprint.kt
new file mode 100644
index 0000000..2c6d472
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/AppendTimeFingerprint.kt
@@ -0,0 +1,29 @@
+package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object AppendTimeFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("L", "L", "L"),
+ listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_NEZ,
+ Opcode.IGET_OBJECT,
+ Opcode.IGET_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.RETURN_VOID
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/NextGenWatchLayoutFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/NextGenWatchLayoutFingerprint.kt
new file mode 100644
index 0000000..d173337
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/NextGenWatchLayoutFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.util.MethodUtil
+
+object NextGenWatchLayoutFingerprint : MethodFingerprint(
+ "V", // constructors return void, in favour of speed of matching, this fingerprint has been added
+ customFingerprint = { methodDef -> MethodUtil.isConstructor(methodDef) && methodDef.parameterTypes.size == 3 && methodDef.definingClass.endsWith("NextGenWatchLayout;") }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/PlayerOverlaysLayoutInitFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/PlayerOverlaysLayoutInitFingerprint.kt
new file mode 100644
index 0000000..975bdfe
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/PlayerOverlaysLayoutInitFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PlayerOverlaysLayoutInitFingerprint : MethodFingerprint(
+
+ customFingerprint = { methodDef -> methodDef.returnType.endsWith("YouTubePlayerOverlaysLayout;") }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/RectangleFieldInvalidatorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/RectangleFieldInvalidatorFingerprint.kt
new file mode 100644
index 0000000..822484a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/RectangleFieldInvalidatorFingerprint.kt
@@ -0,0 +1,22 @@
+package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.reference.MethodReference
+
+object RectangleFieldInvalidatorFingerprint : MethodFingerprint(
+ "V",
+ customFingerprint = custom@{ methodDef ->
+ val instructions = methodDef.implementation?.instructions!!
+ val instructionCount = instructions.count()
+
+ // the method has definitely more than 5 instructions
+ if (instructionCount < 5) return@custom false
+
+ val referenceInstruction = instructions.elementAt(instructionCount - 2) // the second to last instruction
+ val reference = ((referenceInstruction as? ReferenceInstruction)?.reference as? MethodReference)
+
+ reference?.parameterTypes?.size == 1 && reference.name == "invalidate" // the reference is the invalidate(..) method
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShortsPlayerConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShortsPlayerConstructorFingerprint.kt
new file mode 100644
index 0000000..8620559
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShortsPlayerConstructorFingerprint.kt
@@ -0,0 +1,41 @@
+package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.layout.sponsorblock.resource.patch.SponsorBlockResourcePatch
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+
+@FuzzyPatternScanMethod(3)
+object ShortsPlayerConstructorFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
+ opcodes = listOf(
+ Opcode.MOVE_OBJECT_FROM16,
+ Opcode.MOVE_OBJECT_FROM16,
+ Opcode.MOVE_OBJECT_FROM16,
+ Opcode.INVOKE_DIRECT_RANGE,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.IPUT_OBJECT,
+ Opcode.CONST_4
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ instruction.opcode.ordinal == Opcode.CONST.ordinal &&
+ (instruction as? WideLiteralInstruction)?.wideLiteral == SponsorBlockResourcePatch.reelButtonGroupResourceId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt
new file mode 100644
index 0000000..15e40ff
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt
@@ -0,0 +1,293 @@
+package app.revanced.patches.youtube.layout.sponsorblock.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.data.toMethodWalker
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.shared.fingerprints.SeekbarFingerprint
+import app.revanced.patches.shared.fingerprints.SeekbarOnDrawFingerprint
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.layout.autocaptions.fingerprints.StartVideoInformerFingerprint
+import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
+import app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints.*
+import app.revanced.patches.youtube.layout.sponsorblock.resource.patch.SponsorBlockResourcePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch
+import app.revanced.patches.youtube.misc.video.information.patch.VideoInformationPatch
+import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.*
+import org.jf.dexlib2.iface.instruction.formats.Instruction35c
+import org.jf.dexlib2.iface.reference.FieldReference
+import org.jf.dexlib2.iface.reference.MethodReference
+import org.jf.dexlib2.iface.reference.StringReference
+
+@Patch
+@DependsOn(
+ dependencies = [
+ VideoInformationPatch::class, // updates video information and adds method to seek in video
+ PlayerControlsBytecodePatch::class,
+ IntegrationsPatch::class,
+ SponsorBlockResourcePatch::class,
+ VideoIdPatch::class
+ ]
+)
+@Name("sponsorblock")
+@Description("Integrate SponsorBlock.")
+@SponsorBlockCompatibility
+@Version("0.0.1")
+class SponsorBlockBytecodePatch : BytecodePatch(
+ listOf(
+ SeekbarFingerprint,
+ NextGenWatchLayoutFingerprint,
+ AppendTimeFingerprint,
+ PlayerOverlaysLayoutInitFingerprint,
+ ShortsPlayerConstructorFingerprint,
+ StartVideoInformerFingerprint
+ )
+) {
+
+ private companion object {
+ const val INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR =
+ "Lapp/revanced/integrations/sponsorblock/PlayerController;"
+ }
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ /*
+ Hook the video time methods
+ */
+ with(VideoInformationPatch) {
+ videoTimeHook(
+ INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR,
+ "setVideoTime"
+ )
+ highPrecisionTimeHook(
+ INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR,
+ "setHighPrecisionVideoTime"
+ )
+ }
+
+ /*
+ Set current video id
+ */
+ VideoIdPatch.injectCall("$INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V")
+
+ /*
+ Seekbar drawing
+ */
+ val seekbarSignatureResult = SeekbarFingerprint.result!!.let {
+ SeekbarOnDrawFingerprint.apply { resolve(context, it.mutableClass) }
+ }.result!!
+ val seekbarMethod = seekbarSignatureResult.mutableMethod
+ val seekbarMethodInstructions = seekbarMethod.implementation!!.instructions
+
+ /*
+ Get the instance of the seekbar rectangle
+ */
+ for ((index, instruction) in seekbarMethodInstructions.withIndex()) {
+ if (instruction.opcode != Opcode.MOVE_OBJECT_FROM16) continue
+ seekbarMethod.addInstruction(
+ index + 1,
+ "invoke-static/range {p0 .. p0}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;)V"
+ )
+ break
+ }
+
+ for ((index, instruction) in seekbarMethodInstructions.withIndex()) {
+ if (instruction.opcode != Opcode.INVOKE_STATIC) continue
+
+ val invokeInstruction = instruction as Instruction35c
+ if ((invokeInstruction.reference as MethodReference).name != "round") continue
+
+ val insertIndex = index + 2
+
+ // set the thickness of the segment
+ seekbarMethod.addInstruction(
+ insertIndex,
+ "invoke-static {v${invokeInstruction.registerC}}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarThickness(I)V"
+ )
+ break
+ }
+
+ /*
+ Set rectangle absolute left and right positions
+ */
+ val drawRectangleInstructions = seekbarMethodInstructions.withIndex().filter { (_, instruction) ->
+ instruction is ReferenceInstruction && (instruction.reference as? MethodReference)?.name == "drawRect"
+ }.map { (index, instruction) -> // TODO: improve code
+ index to (instruction as FiveRegisterInstruction).registerD
+ }
+
+ val (indexRight, rectangleRightRegister) = drawRectangleInstructions[0]
+ val (indexLeft, rectangleLeftRegister) = drawRectangleInstructions[3]
+
+ // order of operation is important here due to the code above which has to be improved
+ // the reason for that is that we get the index, add instructions and then the offset would be wrong
+ seekbarMethod.addInstruction(
+ indexLeft + 1,
+ "invoke-static {v$rectangleLeftRegister}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarAbsoluteLeft(Landroid/graphics/Rect;)V"
+ )
+ seekbarMethod.addInstruction(
+ indexRight + 1,
+ "invoke-static {v$rectangleRightRegister}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarAbsoluteRight(Landroid/graphics/Rect;)V"
+ )
+
+ /*
+ Draw segment
+ */
+ val drawSegmentInstructionInsertIndex = (seekbarMethodInstructions.size - 1 - 2)
+ val (canvasInstance, centerY) = (seekbarMethodInstructions[drawSegmentInstructionInsertIndex] as FiveRegisterInstruction).let {
+ it.registerC to it.registerE
+ }
+ seekbarMethod.addInstruction(
+ drawSegmentInstructionInsertIndex,
+ "invoke-static {v$canvasInstance, v$centerY}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V"
+ )
+
+ /*
+ Voting & Shield button
+ */
+ val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult
+
+ val controlsLayoutStubResourceId =
+ ResourceMappingPatch.resourceMappings.single { it.type == "id" && it.name == "controls_layout_stub" }.id
+ val zoomOverlayResourceId =
+ ResourceMappingPatch.resourceMappings.single { it.type == "id" && it.name == "video_zoom_overlay_stub" }.id
+
+ methods@ for (method in controlsMethodResult.mutableClass.methods) {
+ val instructions = method.implementation?.instructions!!
+ instructions@ for ((index, instruction) in instructions.withIndex()) {
+ // search for method which inflates the controls layout view
+ if (instruction.opcode != Opcode.CONST) continue@instructions
+
+ when ((instruction as NarrowLiteralInstruction).wideLiteral) {
+ controlsLayoutStubResourceId -> {
+ // replace the view with the YouTubeControlsOverlay
+ val moveResultInstructionIndex = index + 5
+ val inflatedViewRegister =
+ (instructions[moveResultInstructionIndex] as OneRegisterInstruction).registerA
+ // initialize with the player overlay object
+ method.addInstructions(
+ moveResultInstructionIndex + 1, // insert right after moving the view to the register and use that register
+ """
+ invoke-static {v$inflatedViewRegister}, Lapp/revanced/integrations/sponsorblock/ShieldButton;->initialize(Ljava/lang/Object;)V
+ invoke-static {v$inflatedViewRegister}, Lapp/revanced/integrations/sponsorblock/VotingButton;->initialize(Ljava/lang/Object;)V
+ """
+ )
+ }
+
+ zoomOverlayResourceId -> {
+ val invertVisibilityMethod =
+ context.toMethodWalker(method).nextMethod(index - 6, true).getMethod() as MutableMethod
+ // change visibility of the buttons
+ invertVisibilityMethod.addInstructions(
+ 0, """
+ invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibilityNegatedImmediate(Z)V
+ invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibilityNegatedImmediate(Z)V
+ """.trimIndent()
+ )
+ }
+ }
+ }
+ }
+
+ // change visibility of the buttons
+ PlayerControlsBytecodePatch.injectVisibilityCheckCall("Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibility(Z)V")
+ PlayerControlsBytecodePatch.injectVisibilityCheckCall("Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibility(Z)V")
+
+ // set SegmentHelperLayout.context to the player layout instance
+ val instanceRegister = 0
+ NextGenWatchLayoutFingerprint.result!!.mutableMethod.addInstruction(
+ 3, // after super call
+ "invoke-static/range {p$instanceRegister}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->addSkipSponsorView15(Landroid/view/View;)V"
+ )
+
+ // append the new time to the player layout
+ val appendTimeFingerprintResult = AppendTimeFingerprint.result!!
+ val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex
+ val targetRegister =
+ (appendTimeFingerprintResult.method.implementation!!.instructions.elementAt(appendTimePatternScanStartIndex + 1) as OneRegisterInstruction).registerA
+
+ appendTimeFingerprintResult.mutableMethod.addInstructions(
+ appendTimePatternScanStartIndex + 2, """
+ invoke-static {v$targetRegister}, Lapp/revanced/integrations/sponsorblock/SponsorBlockUtils;->appendTimeWithoutSegments(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v$targetRegister
+ """
+ )
+
+ // initialize the player controller
+ VideoInformationPatch.onCreateHook(INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR, "initialize")
+
+ // initialize the sponsorblock view
+ PlayerOverlaysLayoutInitFingerprint.result!!.mutableMethod.addInstruction(
+ 6, // after inflating the view
+ "invoke-static {p0}, Lapp/revanced/integrations/sponsorblock/player/ui/SponsorBlockView;->initialize(Ljava/lang/Object;)V"
+ )
+
+ // get rectangle field name
+ RectangleFieldInvalidatorFingerprint.resolve(context, seekbarSignatureResult.classDef)
+ val rectangleFieldInvalidatorInstructions =
+ RectangleFieldInvalidatorFingerprint.result!!.method.implementation!!.instructions
+ val rectangleFieldName =
+ ((rectangleFieldInvalidatorInstructions.elementAt(rectangleFieldInvalidatorInstructions.count() - 3) as ReferenceInstruction).reference as FieldReference).name
+
+ // replace the "replaceMeWith*" strings
+ context
+ .proxy(context.classes.first { it.type.endsWith("PlayerController;") })
+ .mutableClass
+ .methods
+ .find { it.name == "setSponsorBarRect" }
+ ?.let { method ->
+ fun MutableMethod.replaceStringInstruction(index: Int, instruction: Instruction, with: String) {
+ val register = (instruction as OneRegisterInstruction).registerA
+ this.replaceInstruction(
+ index, "const-string v$register, \"$with\""
+ )
+ }
+ for ((index, it) in method.implementation!!.instructions.withIndex()) {
+ if (it.opcode.ordinal != Opcode.CONST_STRING.ordinal) continue
+
+ when (((it as ReferenceInstruction).reference as StringReference).string) {
+ "replaceMeWithsetSponsorBarRect" ->
+ method.replaceStringInstruction(index, it, rectangleFieldName)
+
+ "replaceMeWithsetMillisecondMethod" ->
+ method.replaceStringInstruction(index, it, "seekHelper")
+ }
+ }
+ } ?: return PatchResultError("Could not find the method which contains the replaceMeWith* strings")
+
+ val startVideoInformerMethod = StartVideoInformerFingerprint.result!!.mutableMethod
+ startVideoInformerMethod.addInstructions(
+ 0, """
+ const/4 v0, 0x0
+ sput-boolean v0, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->shorts_playing:Z
+ """
+ )
+
+ val shortsPlayerConstructorMethod = ShortsPlayerConstructorFingerprint.result!!.mutableMethod
+
+ shortsPlayerConstructorMethod.addInstructions(
+ 0, """
+ const/4 v0, 0x1
+ sput-boolean v0, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->shorts_playing:Z
+ """
+ )
+
+ // TODO: isSBChannelWhitelisting implementation
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt
new file mode 100644
index 0000000..7b0d58f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt
@@ -0,0 +1,116 @@
+package app.revanced.patches.youtube.layout.sponsorblock.resource.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.shared.settings.preference.impl.Preference
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.util.resources.ResourceUtils
+import app.revanced.util.resources.ResourceUtils.copyResources
+import app.revanced.util.resources.ResourceUtils.copyXmlNode
+import app.revanced.util.resources.ResourceUtils.mergeStrings
+
+@Name("sponsorblock-resource-patch")
+@SponsorBlockCompatibility
+@DependsOn([FixLocaleConfigErrorPatch::class, SettingsPatch::class, ResourceMappingPatch::class])
+@Version("0.0.1")
+class SponsorBlockResourcePatch : ResourcePatch {
+ companion object {
+ internal var reelButtonGroupResourceId: Long = 0
+ }
+
+ override fun execute(context: ResourceContext): PatchResult {
+ val youtubePackage = "com.google.android.youtube"
+ SettingsPatch.addPreference(
+ Preference(
+ StringResource("sb_settings", "SponsorBlock"),
+ Preference.Intent(
+ youtubePackage,
+ "sponsorblock_settings",
+ "com.google.android.libraries.social.licenses.LicenseActivity"
+ ),
+ StringResource("revanced_sponsorblock_settings_summary", "SponsorBlock related settings"),
+ )
+ )
+ val classLoader = this.javaClass.classLoader
+
+ /*
+ merge SponsorBlock strings to main strings
+ */
+ context.mergeStrings("sponsorblock/host/values/strings.xml")
+
+ /*
+ merge SponsorBlock drawables to main drawables
+ */
+
+ arrayOf(
+ ResourceUtils.ResourceGroup(
+ "layout",
+ "inline_sponsor_overlay.xml",
+ "new_segment.xml",
+ "skip_sponsor_button.xml"
+ ),
+ ResourceUtils.ResourceGroup(
+ // required resource for back button, because when the base APK is used, this resource will not exist
+ "drawable",
+ "ic_sb_adjust.xml",
+ "ic_sb_compare.xml",
+ "ic_sb_edit.xml",
+ "ic_sb_logo.xml",
+ "ic_sb_publish.xml",
+ "ic_sb_voting.xml"
+ ),
+ ResourceUtils.ResourceGroup(
+ // required resource for back button, because when the base APK is used, this resource will not exist
+ "drawable-xxxhdpi", "quantum_ic_skip_next_white_24.png"
+ )
+ ).forEach { resourceGroup ->
+ context.copyResources("sponsorblock", resourceGroup)
+ }
+
+ /*
+ merge xml nodes from the host to their real xml files
+ */
+
+ // copy nodes from host resources to their real xml files
+ val hostingResourceStream =
+ classLoader.getResourceAsStream("sponsorblock/host/layout/youtube_controls_layout.xml")!!
+
+ val targetXmlEditor = context.xmlEditor["res/layout/youtube_controls_layout.xml"]
+ "RelativeLayout".copyXmlNode(
+ context.xmlEditor[hostingResourceStream],
+ targetXmlEditor
+ ).also {
+ val children = targetXmlEditor.file.getElementsByTagName("RelativeLayout").item(0).childNodes
+
+ // Replace the startOf with the voting button view so that the button does not overlap
+ for (i in 1 until children.length) {
+ val view = children.item(i)
+
+ // Replace the attribute for a specific node only
+ if (!(view.hasAttributes() && view.attributes.getNamedItem("android:id").nodeValue.endsWith("live_chat_overlay_button"))) continue
+
+ // voting button id from the voting button view from the youtube_controls_layout.xml host file
+ val votingButtonId = "@+id/voting_button"
+
+ view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId
+
+ break
+ }
+ }.close() // close afterwards
+
+ reelButtonGroupResourceId = ResourceMappingPatch.resourceMappings.single {
+ it.type == "id" && it.name == "reel_persistent_edu_button_group"
+ }.id
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/annotations/StartupShortsResetCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/annotations/StartupShortsResetCompatibility.kt
new file mode 100644
index 0000000..c974f49
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/annotations/StartupShortsResetCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.startupshortsreset.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class StartupShortsResetCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsFingerprint.kt
new file mode 100644
index 0000000..a994a26
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsFingerprint.kt
@@ -0,0 +1,24 @@
+package app.revanced.patches.youtube.layout.startupshortsreset.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(3)
+object UserWasInShortsFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"),
+ opcodes = listOf(
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT,
+ ),
+ strings = listOf("Failed to read user_was_in_shorts proto after successful warmup"),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/patch/DisableShortsOnStartupPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/patch/DisableShortsOnStartupPatch.kt
new file mode 100644
index 0000000..924c078
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/patch/DisableShortsOnStartupPatch.kt
@@ -0,0 +1,59 @@
+package app.revanced.patches.youtube.layout.startupshortsreset.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.startupshortsreset.annotations.StartupShortsResetCompatibility
+import app.revanced.patches.youtube.layout.startupshortsreset.fingerprints.UserWasInShortsFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("disable-startup-shorts-player")
+@Description("Disables playing YouTube Shorts when launching YouTube.")
+@StartupShortsResetCompatibility
+@Version("0.0.1")
+class DisableShortsOnStartupPatch : BytecodePatch(
+ listOf(
+ UserWasInShortsFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_startup_shorts_player_enabled",
+ StringResource("revanced_startup_shorts_player_title", "Disable shorts player at app startup"),
+ false,
+ StringResource("revanced_startup_shorts_player_summary_on", "Shorts player is disabled at app startup"),
+ StringResource("revanced_startup_shorts_player_summary_off", "Shorts player is enabled at app startup")
+ )
+ )
+
+ val userWasInShortsResult = UserWasInShortsFingerprint.result!!
+ val userWasInShortsMethod = userWasInShortsResult.mutableMethod
+ val moveResultIndex = userWasInShortsResult.scanResult.patternScanResult!!.endIndex
+
+ userWasInShortsMethod.addInstructions(
+ moveResultIndex + 1, """
+ invoke-static { }, Lapp/revanced/integrations/patches/DisableStartupShortsPlayerPatch;->disableStartupShortsPlayer()Z
+ move-result v5
+ if-eqz v5, :disable_shorts_player
+ return-void
+ :disable_shorts_player
+ nop
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/annotations/TabletMiniPlayerCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/annotations/TabletMiniPlayerCompatibility.kt
new file mode 100644
index 0000000..8d5a0fb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/annotations/TabletMiniPlayerCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.tabletminiplayer.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class TabletMiniPlayerCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorFingerprint.kt
new file mode 100644
index 0000000..a378eaf
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorFingerprint.kt
@@ -0,0 +1,23 @@
+package app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // TODO: Find a good threshold value
+object MiniPlayerDimensionsCalculatorFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("L"),
+ listOf(
+ Opcode.INVOKE_DIRECT,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_NEZ,
+ Opcode.FLOAT_TO_DOUBLE,
+ Opcode.CONST_WIDE_HIGH16,
+ Opcode.CMPL_DOUBLE,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt
new file mode 100644
index 0000000..59350da
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object MiniPlayerOverrideFingerprint : MethodFingerprint(
+ "Z", AccessFlags.STATIC or AccessFlags.PUBLIC,
+ opcodes = listOf(Opcode.RETURN), // anchor to insert the instruction
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt
new file mode 100644
index 0000000..aba50b3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object MiniPlayerOverrideNoContextFingerprint : MethodFingerprint(
+ "Z", AccessFlags.FINAL or AccessFlags.PRIVATE,
+ opcodes = listOf(Opcode.RETURN), // anchor to insert the instruction
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt
new file mode 100644
index 0000000..e4777c4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt
@@ -0,0 +1,20 @@
+package app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object MiniPlayerResponseModelSizeCheckFingerprint : MethodFingerprint(
+ "L",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("L", "L"),
+ listOf(
+ Opcode.RETURN_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_NEZ,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/patch/TabletMiniPlayerPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/patch/TabletMiniPlayerPatch.kt
new file mode 100644
index 0000000..6f0b2a5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/patch/TabletMiniPlayerPatch.kt
@@ -0,0 +1,105 @@
+package app.revanced.patches.youtube.layout.tabletminiplayer.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.youtube.layout.tabletminiplayer.annotations.TabletMiniPlayerCompatibility
+import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerDimensionsCalculatorFingerprint
+import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerOverrideFingerprint
+import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerOverrideNoContextFingerprint
+import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerResponseModelSizeCheckFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("tablet-mini-player")
+@Description("Enables the tablet mini player layout.")
+@TabletMiniPlayerCompatibility
+@Version("0.0.1")
+class TabletMiniPlayerPatch : BytecodePatch(
+ listOf(
+ MiniPlayerDimensionsCalculatorFingerprint,
+ MiniPlayerResponseModelSizeCheckFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_tablet_miniplayer",
+ StringResource("revanced_tablet_miniplayer_title", "Enable the tablet Mini-player"),
+ false,
+ StringResource("revanced_tablet_miniplayer_summary_on", "Tablet Mini-player is enabled"),
+ StringResource("revanced_tablet_miniplayer_summary_off", "Tablet Mini-player is disabled")
+ )
+ )
+
+ // first resolve the fingerprints via the parent fingerprint
+ val miniPlayerClass = MiniPlayerDimensionsCalculatorFingerprint.result!!.classDef
+
+ /*
+ * no context parameter method
+ */
+ MiniPlayerOverrideNoContextFingerprint.resolve(context, miniPlayerClass)
+ val (method, _, parameterRegister) = MiniPlayerOverrideNoContextFingerprint.addProxyCall()
+ // - 1 means to insert before the return instruction
+ val secondInsertIndex = method.implementation!!.instructions.size - 1
+ method.insertOverride(secondInsertIndex, parameterRegister /** same register used to return **/)
+
+ /*
+ * method with context parameter
+ */
+ MiniPlayerOverrideFingerprint.resolve(context, miniPlayerClass)
+ val (_, _, _) = MiniPlayerOverrideFingerprint.addProxyCall()
+
+ /*
+ * size check return value override
+ */
+ val (_, _, _) = MiniPlayerResponseModelSizeCheckFingerprint.addProxyCall()
+
+ return PatchResultSuccess()
+ }
+
+ // helper methods
+ private companion object {
+ fun MethodFingerprint.addProxyCall(): Triple {
+ val (method, scanIndex, parameterRegister) = this.unwrap()
+ method.insertOverride(scanIndex, parameterRegister)
+
+ return Triple(method, scanIndex, parameterRegister)
+ }
+
+ fun MutableMethod.insertOverride(index: Int, overrideRegister: Int) {
+ this.addInstructions(
+ index,
+ """
+ invoke-static {v$overrideRegister}, Lapp/revanced/integrations/patches/TabletMiniPlayerOverridePatch;->getTabletMiniPlayerOverride(Z)Z
+ move-result v$overrideRegister
+ """
+ )
+ }
+
+ fun MethodFingerprint.unwrap(): Triple {
+ val result = this.result!!
+ val scanIndex = result.scanResult.patternScanResult!!.endIndex
+ val method = result.mutableMethod
+ val instructions = method.implementation!!.instructions
+ val parameterRegister = (instructions[scanIndex] as OneRegisterInstruction).registerA
+
+ return Triple(method, scanIndex, parameterRegister)
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/annotations/ThemeCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/annotations/ThemeCompatibility.kt
new file mode 100644
index 0000000..5bf3350
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/annotations/ThemeCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.layout.theme.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.google.android.youtube")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ThemeCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/LithoThemeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/LithoThemeFingerprint.kt
new file mode 100644
index 0000000..ba6e29c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/LithoThemeFingerprint.kt
@@ -0,0 +1,26 @@
+package app.revanced.patches.youtube.layout.theme.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object LithoThemeFingerprint : MethodFingerprint(
+ "V", AccessFlags.PROTECTED or AccessFlags.FINAL, listOf("L"), listOf(
+ Opcode.APUT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_DIRECT,
+ Opcode.IGET_OBJECT,
+ Opcode.SGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IPUT_OBJECT,
+ Opcode.IGET,
+ Opcode.IF_EQZ,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_NEZ,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.RETURN_VOID
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt
new file mode 100644
index 0000000..6575ff4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt
@@ -0,0 +1,36 @@
+package app.revanced.patches.youtube.layout.theme.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patches.youtube.layout.theme.annotations.ThemeCompatibility
+import app.revanced.patches.youtube.layout.theme.fingerprints.LithoThemeFingerprint
+
+@Name("litho-components-theme")
+@Description("Applies a custom theme to litho components.")
+@ThemeCompatibility
+@Version("0.0.1")
+class LithoThemePatch : BytecodePatch(
+ listOf(
+ LithoThemeFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = LithoThemeFingerprint.result!!
+ val method = result.mutableMethod
+ val patchIndex = result.scanResult.patternScanResult!!.endIndex - 1
+
+ method.addInstructions(
+ patchIndex, """
+ invoke-static {p1}, Lapp/revanced/integrations/patches/LithoThemePatch;->applyLithoTheme(I)I
+ move-result p1
+ """
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt
new file mode 100644
index 0000000..919342c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt
@@ -0,0 +1,74 @@
+package app.revanced.patches.youtube.layout.theme.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.*
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.theme.annotations.ThemeCompatibility
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import app.revanced.util.resources.ResourceUtils
+import app.revanced.util.resources.ResourceUtils.copyResources
+import org.w3c.dom.Element
+
+@Patch(include = false)
+@DependsOn([LithoThemePatch::class, FixLocaleConfigErrorPatch::class])
+@Name("theme")
+@Description("Applies a custom theme.")
+@ThemeCompatibility
+@Version("0.0.1")
+class ThemePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ val darkThemeBackgroundColor = darkThemeBackgroundColor!!
+ val lightThemeBackgroundColor = lightThemeBackgroundColor!!
+
+ context.xmlEditor["res/values/colors.xml"].use { editor ->
+ val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element
+
+ for (i in 0 until resourcesNode.childNodes.length) {
+ val node = resourcesNode.childNodes.item(i) as? Element ?: continue
+
+ node.textContent = when (node.getAttribute("name")) {
+ "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3",
+ "yt_black4", "yt_status_bar_background_dark", "material_grey_100", "material_grey_50",
+ "material_grey_600", "material_grey_800", "material_grey_850", "material_grey_900",
+ "material_grey_white_1000", "sud_glif_v3_dialog_background_color_dark" -> darkThemeBackgroundColor
+
+ "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", "yt_white2", "yt_white3", "yt_white4",
+ "sud_glif_v3_dialog_background_color_light" -> lightThemeBackgroundColor
+
+ else -> continue
+ }
+ }
+ }
+
+ // copies the resource file to change the splash screen color
+ context.copyResources("theme",
+ ResourceUtils.ResourceGroup("values-night-v31", "styles.xml")
+ )
+
+ return PatchResultSuccess()
+ }
+
+ companion object : OptionsContainer() {
+ var darkThemeBackgroundColor: String? by option(
+ PatchOption.StringOption(
+ key = "darkThemeBackgroundColor",
+ default = "@android:color/black",
+ title = "Background color for the dark theme",
+ description = "The background color of the dark theme. Can be a hex color or a resource reference.",
+ )
+ )
+
+ var lightThemeBackgroundColor: String? by option(
+ PatchOption.StringOption(
+ key = "lightThemeBackgroundColor",
+ default = "@android:color/white",
+ title = "Background color for the light theme",
+ description = "The background color of the light theme. Can be a hex color or a resource reference.",
+ )
+ )
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/annotations/WatchinVRCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/annotations/WatchinVRCompatibility.kt
new file mode 100644
index 0000000..313a0df
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/annotations/WatchinVRCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.watchinvr.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class WatchinVRCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/fingerprints/WatchinVRFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/fingerprints/WatchinVRFingerprint.kt
new file mode 100644
index 0000000..febf7b3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/fingerprints/WatchinVRFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.layout.watchinvr.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object WatchinVRFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("Z"),
+ strings = listOf("menu_item_cardboard_vr")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/patch/WatchinVRPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/patch/WatchinVRPatch.kt
new file mode 100644
index 0000000..d2915f3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/patch/WatchinVRPatch.kt
@@ -0,0 +1,55 @@
+package app.revanced.patches.youtube.layout.watchinvr.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.watchinvr.annotations.WatchinVRCompatibility
+import app.revanced.patches.youtube.layout.watchinvr.fingerprints.WatchinVRFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("hide-watch-in-vr")
+@Description("Hides the Watch in VR option in the player settings flyout panel.")
+@WatchinVRCompatibility
+@Version("0.0.1")
+class WatchinVRPatch : BytecodePatch(
+ listOf(
+ WatchinVRFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_watch_in_vr",
+ StringResource("revanced_hide_watch_in_vr_title", "Hide watch in VR"),
+ false,
+ StringResource("revanced_hide_watch_in_vr_summary_on", "Watch in VR player setting is hidden"),
+ StringResource("revanced_hide_watch_in_vr_summary_off", "Watch in VR player setting is shown")
+ )
+ )
+
+ WatchinVRFingerprint.result!!.mutableMethod.addInstructions(
+ 0, """
+ invoke-static {}, Lapp/revanced/integrations/patches/HideWatchinVRPatch;->hideWatchinVR()Z
+ move-result v0
+ if-eqz v0, :shown
+ return-void
+ :shown
+ nop
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/annotations/HideWaterwarkCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/annotations/HideWaterwarkCompatibility.kt
new file mode 100644
index 0000000..509e146
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/annotations/HideWaterwarkCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.watermark.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HideWatermarkCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkFingerprint.kt
new file mode 100644
index 0000000..088edb7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.layout.watermark.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object HideWatermarkFingerprint : MethodFingerprint (
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L", "L")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkParentFingerprint.kt
new file mode 100644
index 0000000..9d03758
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkParentFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.layout.watermark.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object HideWatermarkParentFingerprint : MethodFingerprint (
+ "L", AccessFlags.PUBLIC or AccessFlags.FINAL, strings = listOf("player_overlay_in_video_programming")
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/patch/HideWatermarkPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/patch/HideWatermarkPatch.kt
new file mode 100644
index 0000000..ec5c1b3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/patch/HideWatermarkPatch.kt
@@ -0,0 +1,63 @@
+package app.revanced.patches.youtube.layout.watermark.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.layout.watermark.annotations.HideWatermarkCompatibility
+import app.revanced.patches.youtube.layout.watermark.fingerprints.HideWatermarkFingerprint
+import app.revanced.patches.youtube.layout.watermark.fingerprints.HideWatermarkParentFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("hide-watermark")
+@Description("Hides creator's watermarks on videos.")
+@HideWatermarkCompatibility
+@Version("0.0.1")
+class HideWatermarkPatch : BytecodePatch(
+ listOf(
+ HideWatermarkParentFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_hide_video_watermark",
+ StringResource("revanced_hide_video_watermark_title", "Hide creator watermark on videos"),
+ true,
+ StringResource("revanced_hide_video_watermark_summary_on", "Watermark is hidden"),
+ StringResource("revanced_hide_video_watermark_summary_off", "Watermark is shown")
+ )
+ )
+
+ HideWatermarkFingerprint.resolve(context, HideWatermarkParentFingerprint.result!!.classDef)
+ val result = HideWatermarkFingerprint.result
+ ?: return PatchResultError("Required parent method could not be found.")
+
+ val method = result.mutableMethod
+ val line = method.implementation!!.instructions.size - 5
+
+ method.removeInstruction(line)
+ method.addInstructions(
+ line, """
+ invoke-static {}, Lapp/revanced/integrations/patches/BrandingWaterMarkPatch;->isBrandingWatermarkShown()Z
+ move-result p2
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/annotations/WideSearchbarCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/annotations/WideSearchbarCompatibility.kt
new file mode 100644
index 0000000..2a070b2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/annotations/WideSearchbarCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.layout.widesearchbar.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class WideSearchbarCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/DrawActionBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/DrawActionBarFingerprint.kt
new file mode 100644
index 0000000..5ea1ffa
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/DrawActionBarFingerprint.kt
@@ -0,0 +1,27 @@
+package app.revanced.patches.youtube.layout.widesearchbar.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object DrawActionBarFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PRIVATE or AccessFlags.FINAL,
+ listOf("I", "I"),
+ listOf(
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.SGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IGET_OBJECT,
+ Opcode.CONST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_STATIC,
+ ),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/SetWordmarkHeaderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/SetWordmarkHeaderFingerprint.kt
new file mode 100644
index 0000000..84ee999
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/SetWordmarkHeaderFingerprint.kt
@@ -0,0 +1,22 @@
+package app.revanced.patches.youtube.layout.widesearchbar.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object SetWordmarkHeaderFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"),
+ opcodes = listOf(
+ Opcode.IGET_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_NEZ,
+ Opcode.IGET_BOOLEAN,
+ Opcode.IF_EQZ,
+ Opcode.IGET_OBJECT,
+ Opcode.CONST,
+ Opcode.INVOKE_STATIC,
+ ),
+ customFingerprint = { methodDef -> methodDef.parameterTypes.first() == "Landroid/widget/ImageView;" }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/patch/WideSearchbarPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/patch/WideSearchbarPatch.kt
new file mode 100644
index 0000000..50263ba
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/patch/WideSearchbarPatch.kt
@@ -0,0 +1,88 @@
+package app.revanced.patches.youtube.layout.widesearchbar.patch
+
+import app.revanced.extensions.toErrorResult
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.data.toMethodWalker
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.layout.widesearchbar.annotations.WideSearchbarCompatibility
+import app.revanced.patches.youtube.layout.widesearchbar.fingerprints.DrawActionBarFingerprint
+import app.revanced.patches.youtube.layout.widesearchbar.fingerprints.SetWordmarkHeaderFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("enable-wide-searchbar")
+@Description("Replaces the search icon with a wide search bar. This will hide the YouTube logo when active.")
+@WideSearchbarCompatibility
+@Version("0.0.1")
+class WideSearchbarPatch : BytecodePatch(
+ listOf(
+ SetWordmarkHeaderFingerprint, DrawActionBarFingerprint
+ )
+) {
+ private companion object {
+ /**
+ * Walk a fingerprints method at a given index mutably.
+ *
+ * @param index The index to walk at.
+ * @param fromFingerprint The fingerprint to walk the method on.
+ * @return The [MutableMethod] which was walked on.
+ */
+ fun BytecodeContext.walkMutable(index: Int, fromFingerprint: MethodFingerprint) =
+ fromFingerprint.result?.let {
+ toMethodWalker(it.method).nextMethod(index, true).getMethod() as MutableMethod
+ } ?: throw SetWordmarkHeaderFingerprint.toErrorResult()
+
+
+ /**
+ * Injects instructions required for certain methods.
+ *
+ */
+ fun MutableMethod.injectSearchBarHook() {
+ addInstructions(
+ implementation!!.instructions.size - 1,
+ """
+ invoke-static {}, Lapp/revanced/integrations/patches/NewActionbarPatch;->getNewActionBar()Z
+ move-result p0
+ """
+ )
+ }
+ }
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
+ SwitchPreference(
+ "revanced_wide_searchbar",
+ StringResource("revanced_wide_searchbar_enabled_title", "Enable wide search bar"),
+ false,
+ StringResource("revanced_wide_searchbar_summary_on", "Wide search bar is enabled"),
+ StringResource("revanced_wide_searchbar_summary_off", "Wide search bar is disabled")
+ )
+ )
+
+ val result = DrawActionBarFingerprint.result ?: return DrawActionBarFingerprint.toErrorResult()
+
+ // patch methods
+ mapOf(
+ SetWordmarkHeaderFingerprint to 1,
+ DrawActionBarFingerprint to result.scanResult.patternScanResult!!.endIndex
+ ).forEach { (fingerprint, callIndex) ->
+ context.walkMutable(callIndex, fingerprint).injectSearchBarHook()
+ }
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/annotations/AutoRepeatCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/annotations/AutoRepeatCompatibility.kt
new file mode 100644
index 0000000..18de2e6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/annotations/AutoRepeatCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.autorepeat.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class AutoRepeatCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatFingerprint.kt
new file mode 100644
index 0000000..a16683b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.misc.autorepeat.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object AutoRepeatFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf(),
+ customFingerprint = { methodDef -> methodDef.implementation!!.instructions.count() == 3 && methodDef.annotations.isEmpty()}
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatParentFingerprint.kt
new file mode 100644
index 0000000..81d6b00
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatParentFingerprint.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.misc.autorepeat.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object AutoRepeatParentFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ strings = listOf(
+ "play() called when the player wasn't loaded.",
+ "play() blocked because Background Playability failed"
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/patch/AutoRepeatPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/patch/AutoRepeatPatch.kt
new file mode 100644
index 0000000..3861796
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/patch/AutoRepeatPatch.kt
@@ -0,0 +1,88 @@
+package app.revanced.patches.youtube.misc.autorepeat.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.removeInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.misc.autorepeat.annotations.AutoRepeatCompatibility
+import app.revanced.patches.youtube.misc.autorepeat.fingerprints.AutoRepeatFingerprint
+import app.revanced.patches.youtube.misc.autorepeat.fingerprints.AutoRepeatParentFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+
+@Patch
+@DependsOn([IntegrationsPatch::class])
+@Name("always-autorepeat")
+@Description("Always repeats the playing video again.")
+@AutoRepeatCompatibility
+@Version("0.0.1")
+class AutoRepeatPatch : BytecodePatch(
+ listOf(
+ AutoRepeatParentFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ SwitchPreference(
+ "revanced_pref_auto_repeat",
+ StringResource("revanced_auto_repeat_enabled_title", "Enable auto-repeat"),
+ false,
+ StringResource("revanced_auto_repeat_summary_on", "Auto-repeat is enabled"),
+ StringResource("revanced_auto_repeat_summary_off", "Auto-repeat is disabled")
+ )
+ )
+
+ //Get Result from the ParentFingerprint which is the playMethod we need to get.
+ val parentResult = AutoRepeatParentFingerprint.result
+ ?: return PatchResultError("ParentFingerprint did not resolve.")
+
+ //this one needs to be called when app/revanced/integrations/patches/AutoRepeatPatch;->shouldAutoRepeat() returns true
+ val playMethod = parentResult.mutableMethod
+ AutoRepeatFingerprint.resolve(context, parentResult.classDef)
+ //String is: Laamp;->E()V
+ val methodToCall = playMethod.definingClass + "->" + playMethod.name + "()V";
+
+ //This is the method we search for
+ val result = AutoRepeatFingerprint.result
+ ?: return PatchResultError("FingerPrint did not resolve.")
+ val method = result.mutableMethod
+
+ //Instructions to add to the smali code
+ val instructions = """
+ invoke-static {}, Lapp/revanced/integrations/patches/AutoRepeatPatch;->shouldAutoRepeat()Z
+ move-result v0
+ if-eqz v0, :noautorepeat
+ invoke-virtual {p0}, $methodToCall
+ :noautorepeat
+ return-void
+ """
+
+ //Get the implementation so we can do a check for null and get instructions size.
+ val implementation = method.implementation
+ ?: return PatchResultError("No Method Implementation found!")
+
+ //Since addInstructions needs an index which starts counting at 0 and size starts counting at 1,
+ //we have to remove 1 to get the latest instruction
+ val index = implementation.instructions.size - 1
+
+
+ //remove last instruction which is return-void
+ method.removeInstruction(index)
+ // Add our own instructions there
+ method.addInstructions(index, instructions)
+
+ //Everything worked as expected, return Success
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/annotations/DebuggingCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/annotations/DebuggingCompatibility.kt
new file mode 100644
index 0000000..cac120b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/annotations/DebuggingCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.misc.debugging.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.google.android.youtube")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class DebuggingCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/patch/DebuggingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/patch/DebuggingPatch.kt
new file mode 100644
index 0000000..34cdde2
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/patch/DebuggingPatch.kt
@@ -0,0 +1,77 @@
+package app.revanced.patches.youtube.misc.debugging.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.*
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.misc.debugging.annotations.DebuggingCompatibility
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import org.w3c.dom.Element
+
+@Patch
+@Name("debugging")
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Description("Adds debugging options.")
+@DebuggingCompatibility
+@Version("0.0.1")
+class DebuggingPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ app.revanced.patches.shared.settings.preference.impl.PreferenceScreen(
+ "revanced_debug",
+ StringResource("revanced_debug_title", "Debugging"),
+ listOf(
+ SwitchPreference(
+ "revanced_debug_enabled",
+ StringResource("revanced_debug_enabled_title", "Enable debug logs"),
+ false,
+ StringResource("revanced_debug_summary_on", "Debug logs are enabled"),
+ StringResource("revanced_debug_summary_off", "Debug logs are disabled")
+ ),
+ SwitchPreference(
+ "revanced_debug_stacktrace_enabled",
+ StringResource(
+ "revanced_debug_stacktrace_enabled_title",
+ "Print stack traces"
+ ),
+ false,
+ StringResource("revanced_debug_stacktrace_summary_on", "Enabled printing stack traces"),
+ StringResource("revanced_debug_stacktrace_summary_off", "Disabled printing stack traces")
+ ),
+ ),
+ StringResource("revanced_debug_summary", "Enable or disable debugging options")
+ )
+ )
+
+ if (debuggable == true) {
+ context.xmlEditor["AndroidManifest.xml"].use { dom ->
+ val applicationNode = dom
+ .file
+ .getElementsByTagName("application")
+ .item(0) as Element
+
+ // set application as debuggable
+ applicationNode.setAttribute("android:debuggable", "true")
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+
+ companion object : OptionsContainer() {
+ var debuggable: Boolean? by option(
+ PatchOption.BooleanOption(
+ key = "debuggable",
+ default = false,
+ title = "App debugging",
+ description = "Whether to make the app debuggable on Android.",
+ )
+ )
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/annotations/FixPlaybackCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/annotations/FixPlaybackCompatibility.kt
new file mode 100644
index 0000000..8a2bb46
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/annotations/FixPlaybackCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.fix.playback.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class FixPlaybackCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/patch/FixPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/patch/FixPlaybackPatch.kt
new file mode 100644
index 0000000..1483956
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/patch/FixPlaybackPatch.kt
@@ -0,0 +1,52 @@
+package app.revanced.patches.youtube.misc.fix.playback.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.misc.fix.playback.annotations.FixPlaybackCompatibility
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.youtube.misc.video.information.patch.VideoInformationPatch
+import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch
+
+@DependsOn([
+ IntegrationsPatch::class,
+ VideoInformationPatch::class, // updates video length and adds method to seek in video, necessary for this patch
+ SettingsPatch::class,
+ VideoIdPatch::class
+])
+@Name("fix-playback")
+@Description("Fixes the issue with videos not playing when video ads are removed.")
+@FixPlaybackCompatibility
+@Version("0.0.1")
+class FixPlaybackPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ SwitchPreference(
+ "revanced_fix_playback",
+ StringResource("revanced_fix_playback_title", "Fix video playback issues"),
+ false,
+ StringResource(
+ "revanced_fix_playback_summary_on",
+ "The fix is enabled"
+ ),
+ StringResource(
+ "revanced_fix_playback_summary_off",
+ "The fix is disabled"
+ )
+ )
+ )
+
+ // If a new video loads, fix the playback issue
+ VideoIdPatch.injectCall("Lapp/revanced/integrations/patches/FixPlaybackPatch;->newVideoLoaded(Ljava/lang/String;)V")
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/annotations/ClientSpoofCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/annotations/ClientSpoofCompatibility.kt
new file mode 100644
index 0000000..9d2760b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/annotations/ClientSpoofCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.misc.fix.spoof.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.google.android.youtube"),
+ Package("com.vanced.android.youtube")
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ClientSpoofCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/fingerprints/UserAgentHeaderBuilderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/fingerprints/UserAgentHeaderBuilderFingerprint.kt
new file mode 100644
index 0000000..0c1dcdb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/fingerprints/UserAgentHeaderBuilderFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.misc.fix.spoof.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object UserAgentHeaderBuilderFingerprint : MethodFingerprint(
+ parameters = listOf("L", "L", "L"),
+ opcodes = listOf(Opcode.MOVE_RESULT_OBJECT, Opcode.INVOKE_VIRTUAL),
+ strings = listOf("(Linux; U; Android "),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/patch/ClientSpoofPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/patch/ClientSpoofPatch.kt
new file mode 100644
index 0000000..0b6a1eb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/patch/ClientSpoofPatch.kt
@@ -0,0 +1,37 @@
+package app.revanced.patches.youtube.misc.fix.spoof.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.misc.fix.spoof.annotations.ClientSpoofCompatibility
+import app.revanced.patches.youtube.misc.fix.spoof.fingerprints.UserAgentHeaderBuilderFingerprint
+import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction
+
+@Patch
+@Name("client-spoof")
+@Description("Spoofs the YouTube or Vanced client to prevent playback issues.")
+@ClientSpoofCompatibility
+@Version("0.0.1")
+class ClientSpoofPatch : BytecodePatch(
+ listOf(UserAgentHeaderBuilderFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = UserAgentHeaderBuilderFingerprint.result!!
+ val method = result.mutableMethod
+
+ val insertIndex = result.scanResult.patternScanResult!!.endIndex
+ val packageNameRegister = (method.instruction(insertIndex) as FiveRegisterInstruction).registerD
+
+ val originalPackageName = "com.google.android.youtube"
+ method.addInstruction(insertIndex, "const-string v$packageNameRegister, \"$originalPackageName\"")
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/annotations/VerticalScrollCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/annotations/VerticalScrollCompatibility.kt
new file mode 100644
index 0000000..12a4b74
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/annotations/VerticalScrollCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.fix.verticalscroll.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [
+ Package("com.google.android.youtube"),
+ ]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class VerticalScrollCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/fingerprints/CanScrollVerticallyFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/fingerprints/CanScrollVerticallyFingerprint.kt
new file mode 100644
index 0000000..1f87924
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/fingerprints/CanScrollVerticallyFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.misc.fix.verticalscroll.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object CanScrollVerticallyFingerprint : MethodFingerprint(
+ "Z",
+ parameters = emptyList(),
+ opcodes = listOf(Opcode.INSTANCE_OF),
+ customFingerprint = { methodDef -> methodDef.definingClass.endsWith("SwipeRefreshLayout;") }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/patch/VerticalScrollPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/patch/VerticalScrollPatch.kt
new file mode 100644
index 0000000..3619ea6
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/patch/VerticalScrollPatch.kt
@@ -0,0 +1,33 @@
+package app.revanced.patches.youtube.misc.fix.verticalscroll.patch
+
+import app.revanced.extensions.toErrorResult
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patches.youtube.misc.fix.verticalscroll.annotations.VerticalScrollCompatibility
+import app.revanced.patches.youtube.misc.fix.verticalscroll.fingerprints.CanScrollVerticallyFingerprint
+
+@Description("Fixes issues with scrolling on the home screen when the first component is of type EmptyComponent.")
+@VerticalScrollCompatibility
+@Version("0.0.1")
+class VerticalScrollPatch : BytecodePatch(
+ listOf(CanScrollVerticallyFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ val result = CanScrollVerticallyFingerprint.result ?: return CanScrollVerticallyFingerprint.toErrorResult()
+
+ result.mutableMethod.addInstructions(
+ 0,
+ """
+ const/4 v0, 0x0
+ return v0
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/annotations/HDRBrightnessCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/annotations/HDRBrightnessCompatibility.kt
new file mode 100644
index 0000000..3687269
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/annotations/HDRBrightnessCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.hdrbrightness.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class HDRBrightnessCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/fingerprints/HDRBrightnessFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/fingerprints/HDRBrightnessFingerprint.kt
new file mode 100644
index 0000000..979f4e8
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/fingerprints/HDRBrightnessFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.misc.hdrbrightness.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object HDRBrightnessFingerprint : MethodFingerprint(
+ "V",
+ opcodes = listOf(Opcode.CMPL_FLOAT),
+ strings = listOf("c.SettingNotFound;", "screen_brightness", "android.mediaview"),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/patch/HDRBrightnessPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/patch/HDRBrightnessPatch.kt
new file mode 100644
index 0000000..f84efbe
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/patch/HDRBrightnessPatch.kt
@@ -0,0 +1,64 @@
+package app.revanced.patches.youtube.misc.hdrbrightness.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.misc.hdrbrightness.annotations.HDRBrightnessCompatibility
+import app.revanced.patches.youtube.misc.hdrbrightness.fingerprints.HDRBrightnessFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction
+import org.jf.dexlib2.iface.reference.FieldReference
+
+@Patch
+@Name("hdr-auto-brightness")
+@Description("Makes the brightness of HDR videos follow the system default.")
+@HDRBrightnessCompatibility
+@Version("0.0.2")
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+class HDRBrightnessPatch : BytecodePatch(
+ listOf(HDRBrightnessFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ SwitchPreference(
+ "revanced_pref_hdr_autobrightness",
+ StringResource("revanced_hdr_autobrightness_enabled_title", "Enable auto HDR brightness"),
+ true,
+ StringResource("revanced_hdr_autobrightness_summary_on", "Auto HDR brightness is enabled"),
+ StringResource("revanced_hdr_autobrightness_summary_off", "Auto HDR brightness is disabled")
+ )
+ )
+
+ val method = HDRBrightnessFingerprint.result!!.mutableMethod
+
+ method.implementation!!.instructions.filter { instruction ->
+ val fieldReference = (instruction as? ReferenceInstruction)?.reference as? FieldReference
+ fieldReference?.let { it.name == "screenBrightness" } == true
+ }.forEach { instruction ->
+ val brightnessRegisterIndex = method.implementation!!.instructions.indexOf(instruction)
+ val register = (instruction as TwoRegisterInstruction).registerA
+
+ val insertIndex = brightnessRegisterIndex + 1
+ method.addInstructions(
+ insertIndex,
+ """
+ invoke-static {v$register}, Lapp/revanced/integrations/patches/HDRAutoBrightnessPatch;->getHDRBrightness(F)F
+ move-result v$register
+ """
+ )
+ }
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/annotations/IntegrationsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/annotations/IntegrationsCompatibility.kt
new file mode 100644
index 0000000..d8e9cd7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/annotations/IntegrationsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.integrations.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class IntegrationsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/InitFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/InitFingerprint.kt
new file mode 100644
index 0000000..730e856
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/InitFingerprint.kt
@@ -0,0 +1,7 @@
+package app.revanced.patches.youtube.misc.integrations.fingerprints
+
+import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint
+
+object InitFingerprint : IntegrationsFingerprint(
+ strings = listOf("Application creation"),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/ServiceFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/ServiceFingerprint.kt
new file mode 100644
index 0000000..813aaf5
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/ServiceFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.misc.integrations.fingerprints
+
+import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint
+
+object ServiceFingerprint : IntegrationsFingerprint(
+ customFingerprint = { methodDef -> methodDef.definingClass.endsWith("ApiPlayerService;") && methodDef.name == "" },
+ contextRegisterResolver = { it.implementation!!.registerCount - it.parameters.size }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerFingerprint.kt
new file mode 100644
index 0000000..ff71061
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.misc.integrations.fingerprints
+
+import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint
+
+object StandalonePlayerFingerprint : IntegrationsFingerprint(
+ strings = listOf(
+ "Invalid PlaybackStartDescriptor. Returning the instance itself.",
+ "com.google.android.music",
+ ),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/patch/IntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/patch/IntegrationsPatch.kt
new file mode 100644
index 0000000..a5d219f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/patch/IntegrationsPatch.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.youtube.misc.integrations.patch
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patches.youtube.misc.integrations.annotations.IntegrationsCompatibility
+import app.revanced.patches.youtube.misc.integrations.fingerprints.InitFingerprint
+import app.revanced.patches.youtube.misc.integrations.fingerprints.ServiceFingerprint
+import app.revanced.patches.youtube.misc.integrations.fingerprints.StandalonePlayerFingerprint
+import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch
+
+@Name("integrations")
+@IntegrationsCompatibility
+class IntegrationsPatch : AbstractIntegrationsPatch(
+ "Lapp/revanced/integrations/utils/ReVancedUtils;",
+ listOf(InitFingerprint, StandalonePlayerFingerprint, ServiceFingerprint),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/annotation/LithoFilterCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/annotation/LithoFilterCompatibility.kt
new file mode 100644
index 0000000..ea1772e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/annotation/LithoFilterCompatibility.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.youtube.misc.litho.filter.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class LithoFilterCompatibility
+
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt
new file mode 100644
index 0000000..7f661ac
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt
@@ -0,0 +1,20 @@
+package app.revanced.patches.youtube.misc.litho.filter.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object ComponentContextParserFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.GOTO,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.CONST_16,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IPUT_OBJECT,
+ Opcode.NEW_INSTANCE
+ ),
+ strings = listOf("LoggingProperties are not in proto format")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt
new file mode 100644
index 0000000..1842275
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.misc.litho.filter.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object EmptyComponentBuilderFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.INVOKE_STATIC_RANGE
+ ),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/patch/LithoFilterPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/patch/LithoFilterPatch.kt
new file mode 100644
index 0000000..9ea24e4
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/patch/LithoFilterPatch.kt
@@ -0,0 +1,81 @@
+package app.revanced.patches.youtube.misc.litho.filter.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultError
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.litho.filter.annotation.LithoFilterCompatibility
+import app.revanced.patches.youtube.misc.litho.filter.fingerprints.ComponentContextParserFingerprint
+import app.revanced.patches.youtube.misc.litho.filter.fingerprints.EmptyComponentBuilderFingerprint
+import org.jf.dexlib2.iface.instruction.Instruction
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction
+import org.jf.dexlib2.iface.reference.FieldReference
+import org.jf.dexlib2.iface.reference.MethodReference
+
+@DependsOn([IntegrationsPatch::class])
+@Description("Hooks the method which parses the bytes into a ComponentContext to filter components.")
+@LithoFilterCompatibility
+@Version("0.0.1")
+class LithoFilterPatch : BytecodePatch(
+ listOf(ComponentContextParserFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ ComponentContextParserFingerprint.result?.let { result ->
+ val builderMethodIndex = EmptyComponentBuilderFingerprint
+ .also { it.resolve(context, result.mutableMethod, result.mutableClass) }
+ .let { it.result!!.scanResult.patternScanResult!!.startIndex }
+
+ val emptyComponentFieldIndex = builderMethodIndex + 2
+
+ with(result.mutableMethod) {
+ val insertHookIndex = result.scanResult.patternScanResult!!.endIndex
+ val clobberedRegister = (instruction(insertHookIndex - 3) as OneRegisterInstruction).registerA
+
+ val builderMethodDescriptor = instruction(builderMethodIndex).toDescriptor()
+ val emptyComponentFieldDescriptor = instruction(emptyComponentFieldIndex).toDescriptor()
+
+ val stringBuilderRegister = (instruction(insertHookIndex - 1) as TwoRegisterInstruction).registerA
+
+ addInstructions(
+ insertHookIndex, // right after setting the component.pathBuilder field,
+ """
+ invoke-static {v$stringBuilderRegister, v0}, Lapp/revanced/integrations/patches/LithoFilterPatch;->filter(Ljava/lang/StringBuilder;Ljava/lang/String;)Z
+ move-result v$clobberedRegister
+ if-eqz v$clobberedRegister, :not_an_ad
+ move-object/from16 v0, p1
+ invoke-static {v0}, $builderMethodDescriptor
+ move-result-object v0
+ iget-object v0, v0, $emptyComponentFieldDescriptor
+ return-object v0
+ """,
+ listOf(ExternalLabel("not_an_ad", instruction(insertHookIndex)))
+ )
+ }
+ } ?: return PatchResultError("Could not find the method to hook.")
+
+ return PatchResultSuccess()
+ }
+
+ private companion object {
+ fun Instruction.toDescriptor() = when (val reference = (this as? ReferenceInstruction)?.reference) {
+ is MethodReference -> "${reference.definingClass}->${reference.name}(${
+ reference.parameterTypes.joinToString(
+ ""
+ ) { it }
+ })${reference.returnType}"
+ is FieldReference -> "${reference.definingClass}->${reference.name}:${reference.type}"
+ else -> throw PatchResultError("Unsupported reference type")
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/annotations/FixLocaleConfigErrorCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/annotations/FixLocaleConfigErrorCompatibility.kt
new file mode 100644
index 0000000..a304b71
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/annotations/FixLocaleConfigErrorCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.misc.manifest.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.google.android.youtube")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class FixLocaleConfigErrorCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/patch/FixLocaleConfigErrorPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/patch/FixLocaleConfigErrorPatch.kt
new file mode 100644
index 0000000..801e444
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/patch/FixLocaleConfigErrorPatch.kt
@@ -0,0 +1,36 @@
+package app.revanced.patches.youtube.misc.manifest.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patches.youtube.misc.manifest.annotations.FixLocaleConfigErrorCompatibility
+import org.w3c.dom.Element
+
+@Name("locale-config-fix")
+@Description("Fixes an error when building the resources by patching the manifest file.")
+@FixLocaleConfigErrorCompatibility
+@Version("0.0.1")
+class FixLocaleConfigErrorPatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ // create an xml editor instance
+ context.xmlEditor["AndroidManifest.xml"].use {
+ // edit the application nodes attribute...
+ val applicationNode = it
+ .file
+ .getElementsByTagName("application")
+ .item(0) as Element
+
+
+ // by replacing the attributes name
+ val attribute = "android:localeConfig"
+ applicationNode.setAttribute("localeConfig", applicationNode.getAttribute(attribute))
+ applicationNode.removeAttribute(attribute)
+ }
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/annotations/MicroGPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/annotations/MicroGPatchCompatibility.kt
new file mode 100644
index 0000000..4f4fa0d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/annotations/MicroGPatchCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.microg.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class MicroGPatchCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastContextFetchFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastContextFetchFingerprint.kt
new file mode 100644
index 0000000..577f101
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastContextFetchFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.misc.microg.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CastContextFetchFingerprint : MethodFingerprint(
+ strings = listOf("Error fetching CastContext.")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt
new file mode 100644
index 0000000..bf1f58c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.misc.microg.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CastDynamiteModuleFingerprint : MethodFingerprint(
+ strings = listOf("com.google.android.gms.cast.framework.internal.CastDynamiteModuleImpl")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt
new file mode 100644
index 0000000..8939593
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.misc.microg.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CastDynamiteModuleV2Fingerprint : MethodFingerprint(
+ strings = listOf("Failed to load module via V2: ")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt
new file mode 100644
index 0000000..e50f656
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.misc.microg.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object GooglePlayUtilityFingerprint : MethodFingerprint(
+ "I",
+ AccessFlags.PUBLIC or AccessFlags.STATIC,
+ listOf("L", "L"),
+ strings = listOf("This should never happen.", "MetadataValueReader", "com.google.android.gms")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/IntegrityCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/IntegrityCheckFingerprint.kt
new file mode 100644
index 0000000..f3d4119
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/IntegrityCheckFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.misc.microg.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object IntegrityCheckFingerprint : MethodFingerprint(
+ "L", AccessFlags.PUBLIC or AccessFlags.STATIC, listOf("L", "L"),
+ strings = listOf("This should never happen.", "GooglePlayServicesUtil", "Google Play Store signature invalid.")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/PrimeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/PrimeFingerprint.kt
new file mode 100644
index 0000000..04fcf86
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/PrimeFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.misc.microg.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PrimeFingerprint : MethodFingerprint(
+ strings = listOf("com.google.android.GoogleCamera", "com.android.vending")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/ServiceCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/ServiceCheckFingerprint.kt
new file mode 100644
index 0000000..9a07e25
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/ServiceCheckFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.misc.microg.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object ServiceCheckFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.STATIC, listOf("L", "I"),
+ strings = listOf("Google Play Services not available", "GooglePlayServices not available due to error ")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt
new file mode 100644
index 0000000..f4869c7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt
@@ -0,0 +1,75 @@
+package app.revanced.patches.youtube.misc.microg.patch.bytecode
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.shared.fingerprints.WatchWhileActivityFingerprint
+import app.revanced.patches.youtube.layout.castbutton.patch.HideCastButtonPatch
+import app.revanced.patches.youtube.misc.fix.spoof.patch.ClientSpoofPatch
+import app.revanced.patches.youtube.misc.microg.annotations.MicroGPatchCompatibility
+import app.revanced.patches.youtube.misc.microg.fingerprints.*
+import app.revanced.patches.youtube.misc.microg.patch.resource.MicroGResourcePatch
+import app.revanced.patches.youtube.misc.microg.shared.Constants.PACKAGE_NAME
+import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_PACKAGE_NAME
+import app.revanced.util.microg.MicroGBytecodeHelper
+
+@Patch
+@DependsOn(
+ [
+ MicroGResourcePatch::class,
+ HideCastButtonPatch::class,
+ ClientSpoofPatch::class
+ ]
+)
+@Name("microg-support")
+@Description("Allows YouTube ReVanced to run without root and under a different package name with Vanced MicroG.")
+@MicroGPatchCompatibility
+@Version("0.0.1")
+class MicroGBytecodePatch : BytecodePatch(
+ listOf(
+ IntegrityCheckFingerprint,
+ ServiceCheckFingerprint,
+ GooglePlayUtilityFingerprint,
+ CastDynamiteModuleFingerprint,
+ CastDynamiteModuleV2Fingerprint,
+ CastContextFetchFingerprint,
+ PrimeFingerprint,
+ WatchWhileActivityFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ // apply common microG patch
+ MicroGBytecodeHelper.patchBytecode(
+ context, arrayOf(
+ MicroGBytecodeHelper.packageNameTransform(
+ PACKAGE_NAME,
+ REVANCED_PACKAGE_NAME
+ )
+ ),
+ MicroGBytecodeHelper.PrimeMethodTransformationData(
+ PrimeFingerprint,
+ PACKAGE_NAME,
+ REVANCED_PACKAGE_NAME
+ ),
+ listOf(
+ IntegrityCheckFingerprint,
+ ServiceCheckFingerprint,
+ GooglePlayUtilityFingerprint,
+ CastDynamiteModuleFingerprint,
+ CastDynamiteModuleV2Fingerprint,
+ CastContextFetchFingerprint
+ )
+ )
+
+ // inject the notice for MicroG
+ MicroGBytecodeHelper.injectNotice(WatchWhileActivityFingerprint)
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt
new file mode 100644
index 0000000..1cdc47f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt
@@ -0,0 +1,62 @@
+package app.revanced.patches.youtube.misc.microg.patch.resource
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import app.revanced.patches.youtube.misc.microg.annotations.MicroGPatchCompatibility
+import app.revanced.patches.youtube.misc.microg.shared.Constants.PACKAGE_NAME
+import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_APP_NAME
+import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_PACKAGE_NAME
+import app.revanced.patches.youtube.misc.microg.shared.Constants.SPOOFED_PACKAGE_NAME
+import app.revanced.patches.youtube.misc.microg.shared.Constants.SPOOFED_PACKAGE_SIGNATURE
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.Preference
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch
+import app.revanced.util.microg.Constants.MICROG_VENDOR
+import app.revanced.util.microg.MicroGManifestHelper
+import app.revanced.util.microg.MicroGResourceHelper
+
+@Name("microg-resource-patch")
+@DependsOn([FixLocaleConfigErrorPatch::class, SettingsResourcePatch::class])
+@Description("Resource patch to allow YouTube ReVanced to run without root and under a different package name.")
+@MicroGPatchCompatibility
+@Version("0.0.1")
+class MicroGResourcePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ SettingsPatch.addPreference(
+ Preference(
+ StringResource("microg_settings", "MicroG Settings"),
+ Preference.Intent("$MICROG_VENDOR.android.gms", "", "org.microg.gms.ui.SettingsActivity"),
+ StringResource("microg_settings_summary", "Settings for MicroG"),
+ )
+ )
+ SettingsPatch.renameIntentsTargetPackage(REVANCED_PACKAGE_NAME)
+
+ // update manifest
+ MicroGResourceHelper.patchManifest(
+ context,
+ PACKAGE_NAME,
+ REVANCED_PACKAGE_NAME,
+ REVANCED_APP_NAME
+ )
+
+ // add metadata to manifest
+ MicroGManifestHelper.addSpoofingMetadata(
+ context,
+ SPOOFED_PACKAGE_NAME,
+ SPOOFED_PACKAGE_SIGNATURE
+ )
+
+ // add strings
+ MicroGResourceHelper.addStrings(context)
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt
new file mode 100644
index 0000000..84b48de
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.misc.microg.shared
+
+object Constants {
+ internal const val REVANCED_APP_NAME = "YouTube ReVanced"
+ internal const val REVANCED_PACKAGE_NAME = "app.revanced.android.youtube"
+ internal const val PACKAGE_NAME = "com.google.android.youtube"
+ internal const val SPOOFED_PACKAGE_NAME = PACKAGE_NAME
+ internal const val SPOOFED_PACKAGE_SIGNATURE = "24bb24c05e47e0aefa68a58a766179d9b613a600"
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt
new file mode 100644
index 0000000..c64a1be
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.minimizedplayback.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class MinimizedPlaybackCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackKidsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackKidsFingerprint.kt
new file mode 100644
index 0000000..860925e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackKidsFingerprint.kt
@@ -0,0 +1,23 @@
+package app.revanced.patches.youtube.misc.minimizedplayback.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object MinimizedPlaybackKidsFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("I", "L", "L"),
+ listOf(
+ Opcode.IF_EQZ,
+ Opcode.SGET_OBJECT,
+ Opcode.IF_NE,
+ Opcode.CONST_4,
+ Opcode.IPUT_BOOLEAN,
+ Opcode.IF_EQZ,
+ Opcode.IGET,
+ Opcode.INVOKE_STATIC
+ ),
+ customFingerprint = { it.definingClass.endsWith("MinimizedPlaybackPolicyController;") }
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt
new file mode 100644
index 0000000..015736b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt
@@ -0,0 +1,42 @@
+package app.revanced.patches.youtube.misc.minimizedplayback.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
+object MinimizedPlaybackManagerFingerprint : MethodFingerprint(
+ "Z",
+ AccessFlags.PUBLIC or AccessFlags.STATIC,
+ listOf("L"),
+ listOf(
+ Opcode.CONST_4,
+ Opcode.IF_EQZ,
+ Opcode.IGET,
+ Opcode.AND_INT_LIT16,
+ Opcode.IF_EQZ,
+ Opcode.IGET_OBJECT,
+ Opcode.IF_NEZ,
+ Opcode.SGET_OBJECT,
+ Opcode.IGET,
+ Opcode.CONST,
+ Opcode.IF_NE,
+ Opcode.IGET_OBJECT,
+ Opcode.IF_NEZ,
+ Opcode.SGET_OBJECT,
+ Opcode.IGET,
+ Opcode.IF_NE,
+ Opcode.IGET_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.GOTO,
+ Opcode.SGET_OBJECT,
+ Opcode.GOTO,
+ Opcode.CONST_4,
+ Opcode.IF_EQZ,
+ Opcode.IGET_BOOLEAN,
+ Opcode.IF_EQZ
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackSettingsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackSettingsFingerprint.kt
new file mode 100644
index 0000000..af3840d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackSettingsFingerprint.kt
@@ -0,0 +1,25 @@
+package app.revanced.patches.youtube.misc.minimizedplayback.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+
+@FuzzyPatternScanMethod(2)
+object MinimizedPlaybackSettingsFingerprint : MethodFingerprint(
+ "L",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ opcodes = listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.IF_NEZ,
+ Opcode.GOTO,
+ Opcode.IGET_OBJECT,
+ Opcode.CHECK_CAST
+ ),
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/patch/MinimizedPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/patch/MinimizedPlaybackPatch.kt
new file mode 100644
index 0000000..94e9924
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/patch/MinimizedPlaybackPatch.kt
@@ -0,0 +1,88 @@
+package app.revanced.patches.youtube.misc.minimizedplayback.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.data.toMethodWalker
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.minimizedplayback.annotations.MinimizedPlaybackCompatibility
+import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackKidsFingerprint
+import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackManagerFingerprint
+import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackSettingsFingerprint
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.reference.MethodReference
+
+
+@Patch
+@Name("minimized-playback")
+@Description("Enables minimized and background playback.")
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@MinimizedPlaybackCompatibility
+@Version("0.0.1")
+class MinimizedPlaybackPatch : BytecodePatch(
+ listOf(
+ MinimizedPlaybackKidsFingerprint, MinimizedPlaybackManagerFingerprint, MinimizedPlaybackSettingsFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ SwitchPreference(
+ "revanced_enable_minimized_playback",
+ StringResource("revanced_minimized_playback_enabled_title", "Enable minimized playback"),
+ true,
+ StringResource("revanced_minimized_playback_summary_on", "Minimized playback is enabled"),
+ StringResource("revanced_minimized_playback_summary_off", "Minimized playback is disabled")
+ )
+ )
+
+ // Instead of removing all instructions like Vanced,
+ // we return the method at the beginning instead
+ MinimizedPlaybackManagerFingerprint.result!!.mutableMethod.addInstructions(
+ 0, """
+ invoke-static {}, Lapp/revanced/integrations/patches/MinimizedPlaybackPatch;->isMinimizedPlaybackEnabled()Z
+ move-result v0
+ return v0
+ """
+ )
+
+ val method = MinimizedPlaybackSettingsFingerprint.result!!.mutableMethod
+ val booleanCalls = method.implementation!!.instructions.withIndex()
+ .filter { ((it.value as? ReferenceInstruction)?.reference as? MethodReference)?.returnType == "Z" }
+
+ val settingsBooleanIndex = booleanCalls.elementAt(1).index
+ val settingsBooleanMethod =
+ context.toMethodWalker(method).nextMethod(settingsBooleanIndex, true).getMethod() as MutableMethod
+
+ settingsBooleanMethod.addInstructions(
+ 0, """
+ invoke-static {}, Lapp/revanced/integrations/patches/MinimizedPlaybackPatch;->isMinimizedPlaybackEnabled()Z
+ move-result v0
+ return v0
+ """
+ )
+
+ MinimizedPlaybackKidsFingerprint.result!!.mutableMethod.addInstructions(
+ 0, """
+ invoke-static {}, Lapp/revanced/integrations/patches/MinimizedPlaybackPatch;->isMinimizedPlaybackEnabled()Z
+ move-result v0
+ if-eqz v0, :enable
+ return-void
+ :enable
+ nop
+ """
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/annotations/OpenLinksDirectlyCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/annotations/OpenLinksDirectlyCompatibility.kt
new file mode 100644
index 0000000..20241f3
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/annotations/OpenLinksDirectlyCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.openlinksdirectly.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class OpenLinksDirectlyCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary.kt
new file mode 100644
index 0000000..e5c497f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary.kt
@@ -0,0 +1,23 @@
+package app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.misc.openlinksdirectly.annotations.OpenLinksDirectlyCompatibility
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+@Name("open-links-directly-primary-fingerprint")
+@OpenLinksDirectlyCompatibility
+@Version("0.0.1")
+object OpenLinksDirectlyFingerprintPrimary : MethodFingerprint(
+ "L", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf(
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.RETURN_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.SGET
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary.kt
new file mode 100644
index 0000000..2dda99b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary.kt
@@ -0,0 +1,28 @@
+package app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints
+
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.misc.openlinksdirectly.annotations.OpenLinksDirectlyCompatibility
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+@Name("open-links-directly-secondary-fingerprint")
+@OpenLinksDirectlyCompatibility
+@Version("0.0.1")
+object OpenLinksDirectlyFingerprintSecondary : MethodFingerprint(
+ "L", AccessFlags.PUBLIC or AccessFlags.STATIC, listOf("L"), listOf(
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.RETURN_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.CONST_STRING
+ ),
+ strings = listOf("Uri must have an absolute scheme")
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/patch/OpenLinksDirectlyPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/patch/OpenLinksDirectlyPatch.kt
new file mode 100644
index 0000000..fc4fb51
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/patch/OpenLinksDirectlyPatch.kt
@@ -0,0 +1,72 @@
+package app.revanced.patches.youtube.misc.openlinksdirectly.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.openlinksdirectly.annotations.OpenLinksDirectlyCompatibility
+import app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints.OpenLinksDirectlyFingerprintPrimary
+import app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints.OpenLinksDirectlyFingerprintSecondary
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import org.jf.dexlib2.iface.instruction.Instruction
+import org.jf.dexlib2.iface.instruction.formats.Instruction11x
+import org.jf.dexlib2.iface.instruction.formats.Instruction35c
+
+@Patch
+@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
+@Name("open-links-directly")
+@Description("Bypasses URL redirects and opens links directly inside YouTube app.")
+@OpenLinksDirectlyCompatibility
+@Version("0.0.1")
+class OpenLinksDirectlyPatch : BytecodePatch(
+ listOf(
+ OpenLinksDirectlyFingerprintPrimary, OpenLinksDirectlyFingerprintSecondary
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ SwitchPreference(
+ "revanced_uri_redirect",
+ StringResource("revanced_uri_redirect_title", "Open YouTube links inside app"),
+ true,
+ StringResource("revanced_uri_redirect_summary_on", "Links opened inside YouTube ReVanced"),
+ StringResource("revanced_uri_redirect_summary_off", "Links opened in web browser")
+ )
+ )
+
+ OpenLinksDirectlyFingerprintPrimary.hookUriParser(true)
+ OpenLinksDirectlyFingerprintSecondary.hookUriParser(false)
+
+ return PatchResultSuccess()
+ }
+}
+
+fun MethodFingerprint.hookUriParser(isPrimaryFingerprint: Boolean) {
+ fun getTargetRegister(instruction: Instruction): Int {
+ if (isPrimaryFingerprint) return (instruction as Instruction35c).registerC
+ return (instruction as Instruction11x).registerA
+ }
+ with(this.result!!) {
+ val startIndex = scanResult.patternScanResult!!.startIndex
+ val instruction = method.implementation!!.instructions.elementAt(startIndex + 1)
+ val insertIndex = if (isPrimaryFingerprint) 1 else 2
+ val targetRegister = getTargetRegister(instruction)
+
+ mutableMethod.addInstructions(
+ startIndex + insertIndex, """
+ invoke-static {v$targetRegister}, Lapp/revanced/integrations/patches/OpenLinksDirectlyPatch;->parseRedirectUri(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v$targetRegister
+ """
+ )
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt
new file mode 100644
index 0000000..9149a05
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.playercontrols.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PlayerControlsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt
new file mode 100644
index 0000000..50b3d5c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt
@@ -0,0 +1,83 @@
+package app.revanced.patches.youtube.misc.playercontrols.bytecode.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.youtube.misc.playercontrols.annotation.PlayerControlsCompatibility
+import app.revanced.patches.youtube.misc.playercontrols.fingerprints.BottomControlsInflateFingerprint
+import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Name("player-controls-bytecode-patch")
+@DependsOn([ResourceMappingPatch::class])
+@Description("Manages the code for the player controls of the YouTube player.")
+@PlayerControlsCompatibility
+@Version("0.0.1")
+class PlayerControlsBytecodePatch : BytecodePatch(
+ listOf(PlayerControlsVisibilityFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!!
+
+ bottomUiContainerResourceId = ResourceMappingPatch
+ .resourceMappings
+ .single { it.type == "id" && it.name == "bottom_ui_container_stub" }.id
+
+ // TODO: another solution is required, this is hacky
+ listOf(BottomControlsInflateFingerprint).resolve(context, context.classes)
+ inflateFingerprintResult = BottomControlsInflateFingerprint.result!!
+
+ return PatchResultSuccess()
+ }
+
+ internal companion object {
+ var bottomUiContainerResourceId: Long = 0
+
+ lateinit var showPlayerControlsFingerprintResult: MethodFingerprintResult
+
+ private var inflateFingerprintResult: MethodFingerprintResult? = null
+ set(fingerprint) {
+ field = fingerprint!!.also {
+ moveToRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex
+ viewRegister =
+ (it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA
+ }
+ }
+
+ private var moveToRegisterInstructionIndex: Int = 0
+ private var viewRegister: Int = 0
+
+ /**
+ * Injects the code to change the visibility of controls.
+ * @param descriptor The descriptor of the method which should be called.
+ */
+ fun injectVisibilityCheckCall(descriptor: String) {
+ showPlayerControlsFingerprintResult.mutableMethod.addInstruction(
+ 0,
+ """
+ invoke-static {p1}, $descriptor
+ """
+ )
+ }
+
+ /**
+ * Injects the code to initialize the controls.
+ * @param descriptor The descriptor of the method which should be calleed.
+ */
+ fun initializeControl(descriptor: String) {
+ inflateFingerprintResult!!.mutableMethod.addInstruction(
+ moveToRegisterInstructionIndex + 1,
+ "invoke-static {v$viewRegister}, $descriptor"
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt
new file mode 100644
index 0000000..9df6bcf
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt
@@ -0,0 +1,21 @@
+package app.revanced.patches.youtube.misc.playercontrols.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object BottomControlsInflateFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT
+ ),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any { instruction ->
+ instruction.opcode.ordinal == Opcode.CONST.ordinal &&
+ (instruction as? WideLiteralInstruction)?.wideLiteral == PlayerControlsBytecodePatch.bottomUiContainerResourceId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt
new file mode 100644
index 0000000..91e0acb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.misc.playercontrols.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PlayerControlsVisibilityFingerprint : MethodFingerprint(
+ "V",
+ parameters = listOf("Z", "Z"),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("YouTubeControlsOverlay;")
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt
new file mode 100644
index 0000000..fbba6ef
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt
@@ -0,0 +1,85 @@
+package app.revanced.patches.youtube.misc.playercontrols.resource.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.DomFileEditor
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.ResourcePatch
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
+import app.revanced.patches.youtube.misc.playercontrols.annotation.PlayerControlsCompatibility
+
+@Name("bottom-controls-resource-patch")
+@DependsOn([FixLocaleConfigErrorPatch::class])
+@Description("Manages the resources for the bottom controls of the YouTube player.")
+@PlayerControlsCompatibility
+@Version("0.0.1")
+class BottomControlsResourcePatch : ResourcePatch {
+ override fun execute(context: ResourceContext): PatchResult {
+ resourceContext = context
+ targetXmlEditor = context.xmlEditor[TARGET_RESOURCE]
+
+ return PatchResultSuccess()
+ }
+
+ companion object {
+ internal const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml"
+ private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME"
+
+ private lateinit var resourceContext: ResourceContext
+ private lateinit var targetXmlEditor: DomFileEditor
+
+ // The element to which to add the new elements to
+ private var lastLeftOf = "fullscreen_button"
+
+
+
+ /**
+ * Add new controls to the bottom of the YouTube player.
+ * @param hostYouTubeControlsBottomUiResourceName The hosting resource name containing the elements.
+ */
+ internal fun addControls(hostYouTubeControlsBottomUiResourceName: String) {
+ val sourceXmlEditor =
+ resourceContext.xmlEditor[this::class.java.classLoader.getResourceAsStream(
+ hostYouTubeControlsBottomUiResourceName
+ )!!]
+
+ val targetElement =
+ "android.support.constraint.ConstraintLayout"
+
+ val hostElements = sourceXmlEditor.file.getElementsByTagName(targetElement).item(0).childNodes
+
+ val destinationResourceFile = this.targetXmlEditor.file
+ val destinationElement =
+ destinationResourceFile.getElementsByTagName(targetElement).item(0)
+
+ for (index in 1 until hostElements.length) {
+ val element = hostElements.item(index).cloneNode(true)
+
+ // if the element has no attributes theres no point to adding it to the destination
+ if (!element.hasAttributes()) continue
+
+ // set the elements lastLeftOf attribute to the lastLeftOf value
+ val namespace = "@+id"
+ element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue =
+ "$namespace/$lastLeftOf"
+
+ // set lastLeftOf attribute to the the current element
+ val nameSpaceLength = 4
+ lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength)
+
+ // copy the element
+ destinationResourceFile.adoptNode(element)
+ destinationElement.appendChild(element)
+ }
+ sourceXmlEditor.close()
+ }
+ }
+
+ override fun close() {
+ targetXmlEditor.close()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/annotation/PlayerOverlaysHookCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/annotation/PlayerOverlaysHookCompatibility.kt
new file mode 100644
index 0000000..d74d8bb
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/annotation/PlayerOverlaysHookCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.playeroverlay.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PlayerOverlaysHookCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/fingerprint/PlayerOverlaysOnFinishInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/fingerprint/PlayerOverlaysOnFinishInflateFingerprint.kt
new file mode 100644
index 0000000..1defdfe
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/fingerprint/PlayerOverlaysOnFinishInflateFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.misc.playeroverlay.fingerprint
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PlayerOverlaysOnFinishInflateFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("YouTubePlayerOverlaysLayout;") && methodDef.name == "onFinishInflate"
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/patch/PlayerOverlaysHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/patch/PlayerOverlaysHookPatch.kt
new file mode 100644
index 0000000..9b1c95b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/patch/PlayerOverlaysHookPatch.kt
@@ -0,0 +1,35 @@
+package app.revanced.patches.youtube.misc.playeroverlay.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.playeroverlay.annotation.PlayerOverlaysHookCompatibility
+import app.revanced.patches.youtube.misc.playeroverlay.fingerprint.PlayerOverlaysOnFinishInflateFingerprint
+
+@Name("player-overlays-hook")
+@Description("Hook for adding custom overlays to the video player.")
+@PlayerOverlaysHookCompatibility
+@Version("0.0.1")
+@DependsOn([IntegrationsPatch::class])
+class PlayerOverlaysHookPatch : BytecodePatch(
+ listOf(
+ PlayerOverlaysOnFinishInflateFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ // hook YouTubePlayerOverlaysLayout.onFinishInflate()
+ val method = PlayerOverlaysOnFinishInflateFingerprint.result!!.mutableMethod
+ method.addInstruction(
+ method.implementation!!.instructions.size - 2,
+ "invoke-static { p0 }, Lapp/revanced/integrations/patches/PlayerOverlaysHookPatch;->YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava/lang/Object;)V"
+ )
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/annotation/PlayerTypeHookCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/annotation/PlayerTypeHookCompatibility.kt
new file mode 100644
index 0000000..d9ce962
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/annotation/PlayerTypeHookCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.playertype.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class PlayerTypeHookCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/UpdatePlayerTypeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/UpdatePlayerTypeFingerprint.kt
new file mode 100644
index 0000000..0eb8e9a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/UpdatePlayerTypeFingerprint.kt
@@ -0,0 +1,33 @@
+package app.revanced.patches.youtube.misc.playertype.fingerprint
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+@FuzzyPatternScanMethod(2)
+object UpdatePlayerTypeFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ opcodes = listOf(
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IGET_OBJECT,
+ Opcode.IF_NE,
+ Opcode.RETURN_VOID,
+ Opcode.IPUT_OBJECT,
+ Opcode.INVOKE_DIRECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.CONST_4,
+ Opcode.INVOKE_STATIC,
+ Opcode.RETURN_VOID,
+ Opcode.CONST_4,
+ Opcode.INVOKE_STATIC,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.RETURN_VOID
+ )
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/patch/PlayerTypeHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/patch/PlayerTypeHookPatch.kt
new file mode 100644
index 0000000..75e7220
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/patch/PlayerTypeHookPatch.kt
@@ -0,0 +1,34 @@
+package app.revanced.patches.youtube.misc.playertype.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.playertype.annotation.PlayerTypeHookCompatibility
+import app.revanced.patches.youtube.misc.playertype.fingerprint.UpdatePlayerTypeFingerprint
+
+@Name("player-type-hook")
+@Description("Hook to get the current player type of WatchWhileActivity")
+@PlayerTypeHookCompatibility
+@Version("0.0.1")
+@DependsOn([IntegrationsPatch::class])
+class PlayerTypeHookPatch : BytecodePatch(
+ listOf(
+ UpdatePlayerTypeFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ // hook YouTubePlayerOverlaysLayout.updatePlayerLayout()
+ UpdatePlayerTypeFingerprint.result!!.mutableMethod.addInstruction(
+ 0,
+ "invoke-static { p1 }, Lapp/revanced/integrations/patches/PlayerTypeHookPatch;->YouTubePlayerOverlaysLayout_updatePlayerTypeHookEX(Ljava/lang/Object;)V"
+ )
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/annotations/SettingsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/annotations/SettingsCompatibility.kt
new file mode 100644
index 0000000..5fa1408
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/annotations/SettingsCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.misc.settings.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.google.android.youtube")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class SettingsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/LicenseActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/LicenseActivityFingerprint.kt
new file mode 100644
index 0000000..1139510
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/LicenseActivityFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object LicenseActivityFingerprint : MethodFingerprint(
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("LicenseActivity;") && methodDef.name == "onCreate"
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeConstructorFingerprint.kt
new file mode 100644
index 0000000..599314a
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeConstructorFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+
+object ThemeConstructorFingerprint : MethodFingerprint(
+ "L",
+ AccessFlags.PUBLIC or AccessFlags.STATIC,
+ listOf("L"),
+ strings = listOf("settings.SettingsActivity", ":android:show_fragment", "settings.GeneralPrefsFragment")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterAppFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterAppFingerprint.kt
new file mode 100644
index 0000000..9fc0065
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterAppFingerprint.kt
@@ -0,0 +1,22 @@
+package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object ThemeSetterAppFingerprint : MethodFingerprint(
+ "L", AccessFlags.PUBLIC or AccessFlags.STATIC, opcodes = listOf(
+ Opcode.CONST, //target reference
+ Opcode.GOTO,
+ Opcode.CONST, //target reference
+ Opcode.INVOKE_DIRECT,
+ Opcode.RETURN_OBJECT,
+ Opcode.NEW_INSTANCE,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.SGET_OBJECT,
+ Opcode.IF_NE,
+ Opcode.CONST, //target reference
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterSystemFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterSystemFingerprint.kt
new file mode 100644
index 0000000..d578927
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterSystemFingerprint.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
+
+object ThemeSetterSystemFingerprint : MethodFingerprint(
+ "L",
+ opcodes = listOf(Opcode.RETURN_OBJECT),
+ customFingerprint = { methodDef ->
+ methodDef.implementation?.instructions?.any {
+ it.opcode.ordinal == Opcode.CONST.ordinal && (it as WideLiteralInstruction).wideLiteral == SettingsResourcePatch.appearanceStringId
+ } == true
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/patch/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/patch/SettingsPatch.kt
new file mode 100644
index 0000000..2c1ee88
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/patch/SettingsPatch.kt
@@ -0,0 +1,160 @@
+package app.revanced.patches.youtube.misc.settings.bytecode.patch
+
+import app.revanced.extensions.toErrorResult
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.shared.settings.preference.impl.Preference
+import app.revanced.patches.shared.settings.util.AbstractPreferenceScreen
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibility
+import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.LicenseActivityFingerprint
+import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeConstructorFingerprint
+import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeSetterAppFingerprint
+import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeSetterSystemFingerprint
+import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch
+import org.jf.dexlib2.util.MethodUtil
+
+@Patch
+@DependsOn(
+ [
+ IntegrationsPatch::class,
+ SettingsResourcePatch::class,
+ ]
+)
+@Name("settings")
+@Description("Adds settings for ReVanced to YouTube.")
+@SettingsCompatibility
+@Version("0.0.1")
+class SettingsPatch : BytecodePatch(
+ listOf(LicenseActivityFingerprint, ThemeSetterSystemFingerprint, ThemeConstructorFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ fun buildInvokeInstructionsString(
+ registers: String = "v0",
+ classDescriptor: String = THEME_HELPER_DESCRIPTOR,
+ methodName: String = SET_THEME_METHOD_NAME,
+ parameters: String = "Ljava/lang/Object;"
+ ) = "invoke-static {$registers}, $classDescriptor->$methodName($parameters)V"
+
+ // apply the current theme of the settings page
+ ThemeSetterSystemFingerprint.result!!.let { result ->
+ val call = buildInvokeInstructionsString()
+ result.mutableMethod.apply {
+ addInstruction(
+ result.scanResult.patternScanResult!!.startIndex, call
+ )
+ addInstructions(
+ implementation!!.instructions.size - 1, call
+ )
+ }
+ }
+
+ // set the theme based on the preference of the app
+ with((ThemeConstructorFingerprint.result?.let {
+ ThemeSetterAppFingerprint.apply {
+ if (!resolve(context, it.classDef)) return ThemeSetterAppFingerprint.toErrorResult()
+ }
+ } ?: return ThemeConstructorFingerprint.toErrorResult()).result!!) {
+ fun buildInstructionsString(theme: Int) = """
+ const/4 v0, 0x$theme
+ ${buildInvokeInstructionsString(parameters = "I")}
+ """
+
+ val patternScanResult = scanResult.patternScanResult!!
+
+ mutableMethod.apply {
+ addInstructions(
+ patternScanResult.endIndex + 1, buildInstructionsString(1)
+ )
+ addInstructions(
+ patternScanResult.endIndex - 7, buildInstructionsString(0)
+ )
+
+ addInstructions(
+ patternScanResult.endIndex - 9, buildInstructionsString(1)
+ )
+ addInstructions(
+ implementation!!.instructions.size - 2, buildInstructionsString(0)
+ )
+ }
+
+ }
+
+ // set the theme based on the preference of the device
+ with(LicenseActivityFingerprint.result!!) licenseActivity@{
+ with(mutableMethod) {
+ fun buildSettingsActivityInvokeString(
+ registers: String = "p0",
+ classDescriptor: String = SETTINGS_ACTIVITY_DESCRIPTOR,
+ methodName: String = "initializeSettings",
+ parameters: String = this@licenseActivity.mutableClass.type
+ ) = buildInvokeInstructionsString(registers, classDescriptor, methodName, parameters)
+
+ // initialize the settings
+ addInstructions(
+ 1, """
+ ${buildSettingsActivityInvokeString()}
+ return-void
+ """
+ )
+
+ // set the current theme
+ addInstruction(0, buildSettingsActivityInvokeString(methodName = "setTheme"))
+ }
+
+ // remove method overrides
+ with(mutableClass) {
+ methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
+ }
+ }
+
+ return PatchResultSuccess()
+ }
+
+ internal companion object {
+ private const val INTEGRATIONS_PACKAGE = "app/revanced/integrations"
+
+ private const val SETTINGS_ACTIVITY_DESCRIPTOR = "L$INTEGRATIONS_PACKAGE/settingsmenu/ReVancedSettingActivity;"
+
+ private const val THEME_HELPER_DESCRIPTOR = "L$INTEGRATIONS_PACKAGE/utils/ThemeHelper;"
+ private const val SET_THEME_METHOD_NAME = "setTheme"
+
+ fun addString(identifier: String, value: String, formatted: Boolean = true) =
+ SettingsResourcePatch.addString(identifier, value, formatted)
+
+ fun addPreferenceScreen(preferenceScreen: app.revanced.patches.shared.settings.preference.impl.PreferenceScreen) =
+ SettingsResourcePatch.addPreferenceScreen(preferenceScreen)
+
+ fun addPreference(preference: Preference) = SettingsResourcePatch.addPreference(preference)
+
+ fun renameIntentsTargetPackage(newPackage: String) {
+ SettingsResourcePatch.overrideIntentsTargetPackage = newPackage
+ }
+ }
+
+ /**
+ * Preference screens patches should add their settings to.
+ */
+ internal object PreferenceScreen : AbstractPreferenceScreen() {
+ val ADS = Screen("ads", "Ads", "Ad related settings")
+ val INTERACTIONS = Screen("interactions", "Interaction", "Settings related to interactions")
+ val LAYOUT = Screen("layout", "Layout", "Settings related to the layout")
+ val MISC = Screen("misc", "Misc", "Miscellaneous patches")
+
+ override fun commit(screen: app.revanced.patches.shared.settings.preference.impl.PreferenceScreen) {
+ addPreferenceScreen(screen)
+ }
+ }
+
+ override fun close() = PreferenceScreen.close()
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/resource/patch/SettingsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/resource/patch/SettingsResourcePatch.kt
new file mode 100644
index 0000000..2bd580d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/resource/patch/SettingsResourcePatch.kt
@@ -0,0 +1,169 @@
+package app.revanced.patches.youtube.misc.settings.resource.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.DomFileEditor
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch
+import app.revanced.patches.shared.settings.preference.addPreference
+import app.revanced.patches.shared.settings.preference.impl.ArrayResource
+import app.revanced.patches.shared.settings.preference.impl.Preference
+import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.resource.patch.AbstractSettingsResourcePatch
+import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibility
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.util.resources.ResourceUtils
+import app.revanced.util.resources.ResourceUtils.copyResources
+import org.w3c.dom.Node
+
+@Name("settings-resource-patch")
+@DependsOn([ResourceMappingPatch::class])
+@SettingsCompatibility
+@Description("Applies mandatory patches to implement ReVanced settings into the application.")
+@Version("0.0.1")
+class SettingsResourcePatch : AbstractSettingsResourcePatch(
+ "revanced_prefs",
+ "settings"
+) {
+ override fun execute(context: ResourceContext): PatchResult {
+ super.execute(context)
+
+ /*
+ * used by a fingerprint of SettingsPatch
+ */
+ appearanceStringId = ResourceMappingPatch.resourceMappings.find {
+ it.type == "string" && it.name == "app_theme_appearance_dark"
+ }!!.id
+
+ /*
+ * create missing directory for the resources
+ */
+ context["res/drawable-ldrtl-xxxhdpi"].mkdirs()
+
+ /*
+ * copy layout resources
+ */
+ arrayOf(
+ ResourceUtils.ResourceGroup(
+ "layout",
+ "revanced_settings_toolbar.xml",
+ "revanced_settings_with_toolbar.xml",
+ "revanced_settings_with_toolbar_layout.xml"
+ ), ResourceUtils.ResourceGroup(
+ // required resource for back button, because when the base APK is used, this resource will not exist
+ "drawable-xxxhdpi", "quantum_ic_arrow_back_white_24.png"
+ ), ResourceUtils.ResourceGroup(
+ // required resource for back button, because when the base APK is used, this resource will not exist
+ "drawable-ldrtl-xxxhdpi", "quantum_ic_arrow_back_white_24.png"
+ )
+ ).forEach { resourceGroup ->
+ context.copyResources("settings", resourceGroup)
+ }
+
+ preferencesEditor = context.xmlEditor["res/xml/settings_fragment.xml"]
+
+ // Add the ReVanced settings to the YouTube settings
+ val youtubePackage = "com.google.android.youtube"
+ SettingsPatch.addPreference(
+ Preference(
+ StringResource("revanced_settings", "ReVanced"),
+ Preference.Intent(
+ youtubePackage, "revanced_settings", "com.google.android.libraries.social.licenses.LicenseActivity"
+ ),
+ StringResource("revanced_settings_summary", "ReVanced specific settings"),
+ )
+ )
+
+ return PatchResultSuccess()
+ }
+
+
+ internal companion object {
+ // Used by a fingerprint of SettingsPatch
+ // this field is located in the SettingsResourcePatch
+ // because if it were to be defined in the SettingsPatch companion object,
+ // the companion object could be initialized before ResourceMappingResourcePatch has executed.
+ internal var appearanceStringId: Long = -1
+
+ // if this is not null, all intents will be renamed to this
+ var overrideIntentsTargetPackage: String? = null
+
+ private var preferencesNode: Node? = null
+
+ private var preferencesEditor: DomFileEditor? = null
+ set(value) {
+ field = value
+ preferencesNode = value.getNode("PreferenceScreen")
+ }
+
+ /* Companion delegates */
+
+ /**
+ * Add a preference fragment to the main preferences.
+ *
+ * @param preference The preference to add.
+ */
+ fun addPreference(preference: Preference) =
+ preferencesNode!!.addPreference(preference) { it.include() }
+
+ /**
+ * Add a new string to the resources.
+ *
+ * @param identifier The key of the string.
+ * @param value The value of the string.
+ * @throws IllegalArgumentException if the string already exists.
+ */
+ fun addString(identifier: String, value: String, formatted: Boolean) =
+ AbstractSettingsResourcePatch.addString(identifier, value, formatted)
+
+ /**
+ * Add an array to the resources.
+ *
+ * @param arrayResource The array resource to add.
+ */
+ fun addArray(arrayResource: ArrayResource) = AbstractSettingsResourcePatch.addArray(arrayResource)
+
+ /**
+ * Add a preference to the settings.
+ *
+ * @param preferenceScreen The name of the preference screen.
+ */
+ fun addPreferenceScreen(preferenceScreen: PreferenceScreen) = addPreference(preferenceScreen)
+ }
+
+ override fun close() {
+ super.close()
+
+ // rename the intent package names if it was set
+ overrideIntentsTargetPackage?.let { packageName ->
+ val preferences = preferencesEditor!!.getNode("PreferenceScreen").childNodes
+ for (i in 1 until preferences.length) {
+ val preferenceNode = preferences.item(i)
+ // preferences have a child node with the intent tag, skip over every other node
+ if (preferenceNode.childNodes.length == 0) continue
+
+ val intentNode = preferenceNode.firstChild
+
+ // if the node doesn't have a target package attribute, skip it
+ val targetPackageAttribute = intentNode.attributes.getNamedItem("android:targetPackage") ?: continue
+
+ // do not replace intent target package if the package name is not from YouTube
+ val youtubePackage = "com.google.android.youtube"
+ if (targetPackageAttribute.nodeValue != youtubePackage) continue
+
+ // replace the target package name
+ intentNode.attributes.setNamedItem(preferenceNode.ownerDocument.createAttribute("android:targetPackage")
+ .also { attribute ->
+ attribute.value = packageName
+ })
+ }
+ }
+
+ preferencesEditor?.close()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/annotation/VideoInformationCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/annotation/VideoInformationCompatibility.kt
new file mode 100644
index 0000000..e642800
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/annotation/VideoInformationCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.video.information.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class VideoInformationCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/CreateVideoPlayerSeekbarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/CreateVideoPlayerSeekbarFingerprint.kt
new file mode 100644
index 0000000..13f82a9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/CreateVideoPlayerSeekbarFingerprint.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.misc.video.information.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object CreateVideoPlayerSeekbarFingerprint : MethodFingerprint(
+ "V",
+ strings = listOf("timed_markers_width")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt
new file mode 100644
index 0000000..fe0355d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.misc.video.information.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+import org.jf.dexlib2.Opcode
+
+object PlayerControllerSetTimeReferenceFingerprint : MethodFingerprint(
+ opcodes = listOf(Opcode.INVOKE_DIRECT_RANGE, Opcode.IGET_OBJECT),
+ strings = listOf("Media progress reported outside media playback: ")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerInitFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerInitFingerprint.kt
new file mode 100644
index 0000000..7e64855
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerInitFingerprint.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.youtube.misc.video.information.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object PlayerInitFingerprint : MethodFingerprint(
+ strings = listOf(
+ "playVideo called on player response with no videoStreamingData."
+ ),
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/SeekFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/SeekFingerprint.kt
new file mode 100644
index 0000000..8f2ba96
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/SeekFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.misc.video.information.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object SeekFingerprint : MethodFingerprint(
+ strings = listOf("Attempting to seek during an ad")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoLengthFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoLengthFingerprint.kt
new file mode 100644
index 0000000..2d9e986
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoLengthFingerprint.kt
@@ -0,0 +1,23 @@
+package app.revanced.patches.youtube.misc.video.information.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+import org.jf.dexlib2.Opcode
+
+object VideoLengthFingerprint : MethodFingerprint(
+ opcodes = listOf(
+ Opcode.MOVE_RESULT_WIDE,
+ Opcode.CMP_LONG,
+ Opcode.IF_LEZ,
+ Opcode.IGET_OBJECT,
+ Opcode.CHECK_CAST,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_WIDE,
+ Opcode.GOTO,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_WIDE,
+ Opcode.CONST_4,
+ Opcode.INVOKE_VIRTUAL
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoTimeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoTimeFingerprint.kt
new file mode 100644
index 0000000..d04b753
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoTimeFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.misc.video.information.fingerprints
+
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object VideoTimeFingerprint : MethodFingerprint(
+ strings = listOf("MedialibPlayerTimeInfo{currentPositionMillis=")
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/patch/VideoInformationPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/patch/VideoInformationPatch.kt
new file mode 100644
index 0000000..4df446b
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/patch/VideoInformationPatch.kt
@@ -0,0 +1,178 @@
+package app.revanced.patches.youtube.misc.video.information.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.data.toMethodWalker
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.video.information.annotation.VideoInformationCompatibility
+import app.revanced.patches.youtube.misc.video.information.fingerprints.*
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.builder.MutableMethodImplementation
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+import org.jf.dexlib2.immutable.ImmutableMethod
+import org.jf.dexlib2.immutable.ImmutableMethodParameter
+import org.jf.dexlib2.util.MethodUtil
+
+@Name("video-information")
+@Description("Hooks YouTube to get information about the current playing video.")
+@VideoInformationCompatibility
+@Version("0.0.1")
+@DependsOn([IntegrationsPatch::class])
+class VideoInformationPatch : BytecodePatch(
+ listOf(
+ PlayerInitFingerprint,
+ CreateVideoPlayerSeekbarFingerprint,
+ PlayerControllerSetTimeReferenceFingerprint,
+ VideoTimeFingerprint,
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ with(PlayerInitFingerprint.result!!) {
+ playerInitMethod = mutableClass.methods.first { MethodUtil.isConstructor(it) }
+
+ // hook the player controller for use through integrations
+ onCreateHook(INTEGRATIONS_CLASS_DESCRIPTOR, "playerController_onCreateHook")
+
+ // seek method
+ val seekFingerprintResultMethod = SeekFingerprint.also { it.resolve(context, classDef) }.result!!.method
+
+ // create helper method
+ val seekHelperMethod = ImmutableMethod(
+ seekFingerprintResultMethod.definingClass,
+ "seekTo",
+ listOf(ImmutableMethodParameter("J", null, "time")),
+ "Z",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ null, null,
+ MutableMethodImplementation(4)
+ ).toMutable()
+
+ // get enum type for the seek helper method
+ val seekSourceEnumType = seekFingerprintResultMethod.parameterTypes[1].toString()
+
+ // insert helper method instructions
+ seekHelperMethod.addInstructions(
+ 0,
+ """
+ sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
+ invoke-virtual {p0, p1, p2, v0}, ${seekFingerprintResultMethod.definingClass}->${seekFingerprintResultMethod.name}(J$seekSourceEnumType)Z
+ move-result p1
+ return p1
+ """
+ )
+
+ // add the seekTo method to the class for the integrations to call
+ mutableClass.methods.add(seekHelperMethod)
+ }
+
+ with(CreateVideoPlayerSeekbarFingerprint.result!!) {
+ val videoLengthMethodResult = VideoLengthFingerprint.also { it.resolve(context, classDef) }.result!!
+
+ with(videoLengthMethodResult.mutableMethod) {
+ val videoLengthRegisterIndex = videoLengthMethodResult.scanResult.patternScanResult!!.endIndex - 2
+ val videoLengthRegister = (instruction(videoLengthRegisterIndex) as OneRegisterInstruction).registerA
+ val dummyRegisterForLong = videoLengthRegister + 1 // required for long values since they are wide
+
+ addInstruction(
+ videoLengthMethodResult.scanResult.patternScanResult!!.endIndex,
+ "invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, $INTEGRATIONS_CLASS_DESCRIPTOR->setVideoLength(J)V"
+ )
+ }
+ }
+
+ /*
+ Set the video time method
+ */
+ with(PlayerControllerSetTimeReferenceFingerprint.result!!) {
+ timeMethod = context.toMethodWalker(method)
+ .nextMethod(scanResult.patternScanResult!!.startIndex, true)
+ .getMethod() as MutableMethod
+ }
+
+ /*
+ Set the high precision video time method
+ */
+ highPrecisionTimeMethod =
+ (object : MethodFingerprint("V", null, listOf("J", "J", "J", "J", "I", "L"), null) {}).also {
+ it.resolve(context, VideoTimeFingerprint.result!!.classDef)
+ }.result!!.mutableMethod
+
+ /*
+ Hook the methods which set the time
+ */
+ highPrecisionTimeHook(INTEGRATIONS_CLASS_DESCRIPTOR, "setVideoTime")
+
+ return PatchResultSuccess()
+ }
+
+ companion object {
+ private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/VideoInformation;"
+
+ private lateinit var playerInitMethod: MutableMethod
+ private lateinit var timeMethod: MutableMethod
+ private lateinit var highPrecisionTimeMethod: MutableMethod
+
+ private fun MutableMethod.insert(insert: InsertIndex, register: String, descriptor: String) =
+ addInstruction(insert.index, "invoke-static { $register }, $descriptor")
+
+ private fun MutableMethod.insertTimeHook(insert: InsertIndex, descriptor: String) =
+ insert(insert, "p1, p2", descriptor)
+
+ /**
+ * Hook the player controller.
+ *
+ * @param targetMethodClass The descriptor for the class to invoke when the player controller is created.
+ * @param targetMethodName The name of the static method to invoke when the player controller is created.
+ */
+ internal fun onCreateHook(targetMethodClass: String, targetMethodName: String) =
+ playerInitMethod.insert(
+ InsertIndex.CREATE,
+ "v0",
+ "$targetMethodClass->$targetMethodName(Ljava/lang/Object;)V"
+ )
+
+ /**
+ * Hook the video time.
+ *
+ * @param targetMethodClass The descriptor for the static method to invoke when the player controller is created.
+ * @param targetMethodName The name of the static method to invoke when the player controller is created.
+ */
+ internal fun videoTimeHook(targetMethodClass: String, targetMethodName: String) =
+ timeMethod.insertTimeHook(
+ InsertIndex.TIME,
+ "$targetMethodClass->$targetMethodName(J)V"
+ )
+
+ /**
+ * Hook the high precision video time.
+ *
+ * @param targetMethodClass The descriptor for the static method to invoke when the player controller is created.
+ * @param targetMethodName The name of the static method to invoke when the player controller is created.
+ */
+ internal fun highPrecisionTimeHook(targetMethodClass: String, targetMethodName: String) =
+ highPrecisionTimeMethod.insertTimeHook(
+ InsertIndex.HIGH_PRECISION_TIME,
+ "$targetMethodClass->$targetMethodName(J)V"
+ )
+
+ enum class InsertIndex(internal val index: Int) {
+ CREATE(4),
+ TIME(2),
+ HIGH_PRECISION_TIME(0),
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/annotations/RememberVideoQualityCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/annotations/RememberVideoQualityCompatibility.kt
new file mode 100644
index 0000000..147801e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/annotations/RememberVideoQualityCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.video.quality.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class RememberVideoQualityCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualityReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualityReferenceFingerprint.kt
new file mode 100644
index 0000000..3ea366f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualityReferenceFingerprint.kt
@@ -0,0 +1,12 @@
+package app.revanced.patches.youtube.misc.video.quality.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object VideoQualityReferenceFingerprint : MethodFingerprint(
+ "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf(
+ Opcode.IPUT_OBJECT, Opcode.RETURN_VOID
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualitySetterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualitySetterFingerprint.kt
new file mode 100644
index 0000000..db1853c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualitySetterFingerprint.kt
@@ -0,0 +1,20 @@
+
+package app.revanced.patches.youtube.misc.video.quality.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object VideoQualitySetterFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("[L", "I", "I", "Z", "I"),
+ listOf(
+ Opcode.IF_EQZ,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.INVOKE_VIRTUAL,
+ Opcode.IPUT_BOOLEAN,
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoUserQualityChangeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoUserQualityChangeFingerprint.kt
new file mode 100644
index 0000000..2575d5d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoUserQualityChangeFingerprint.kt
@@ -0,0 +1,20 @@
+
+package app.revanced.patches.youtube.misc.video.quality.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object VideoUserQualityChangeFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("L","L","I","J"),
+ listOf(
+ Opcode.MOVE,
+ Opcode.MOVE_WIDE,
+ Opcode.INVOKE_INTERFACE_RANGE,
+ Opcode.RETURN_VOID
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/patch/RememberVideoQualityPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/patch/RememberVideoQualityPatch.kt
new file mode 100644
index 0000000..08dd672
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/patch/RememberVideoQualityPatch.kt
@@ -0,0 +1,88 @@
+package app.revanced.patches.youtube.misc.video.quality.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.video.quality.annotations.RememberVideoQualityCompatibility
+import app.revanced.patches.youtube.misc.video.quality.fingerprints.VideoQualityReferenceFingerprint
+import app.revanced.patches.youtube.misc.video.quality.fingerprints.VideoQualitySetterFingerprint
+import app.revanced.patches.youtube.misc.video.quality.fingerprints.VideoUserQualityChangeFingerprint
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.reference.FieldReference
+
+@Patch
+@DependsOn([IntegrationsPatch::class, VideoIdPatch::class, SettingsPatch::class])
+@Name("remember-video-quality")
+@Description("Adds the ability to remember the video quality you chose in the video quality flyout.")
+@RememberVideoQualityCompatibility
+@Version("0.0.1")
+class RememberVideoQualityPatch : BytecodePatch(
+ listOf(
+ VideoQualitySetterFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ SwitchPreference(
+ "revanced_remember_video_quality_last_selected",
+ StringResource("revanced_remember_video_quality_last_selected_title", "Remember video quality changes"),
+ true,
+ StringResource(
+ "revanced_remember_video_quality_last_selected_summary_on",
+ "Quality changes apply to all videos"
+ ),
+ StringResource(
+ "revanced_remember_video_quality_last_selected_summary_off",
+ "Quality changes only apply to the current video and are reverted back to the last remembered quality for future playbacks"
+ )
+ )
+ )
+
+ val setterMethod = VideoQualitySetterFingerprint.result!!
+
+ VideoUserQualityChangeFingerprint.resolve(context, setterMethod.classDef)
+ val userQualityMethod = VideoUserQualityChangeFingerprint.result!!
+
+ VideoQualityReferenceFingerprint.resolve(context, setterMethod.classDef)
+ val qualityFieldReference =
+ VideoQualityReferenceFingerprint.result!!.method.let { method ->
+ (method.implementation!!.instructions.elementAt(0) as ReferenceInstruction).reference as FieldReference
+ }
+
+ VideoIdPatch.injectCall("Lapp/revanced/integrations/patches/playback/quality/RememberVideoQualityPatch;->newVideoStarted(Ljava/lang/String;)V")
+
+ val qIndexMethodName =
+ context.classes.single { it.type == qualityFieldReference.type }.methods.single { it.parameterTypes.first() == "I" }.name
+
+ setterMethod.mutableMethod.addInstructions(
+ 0,
+ """
+ iget-object v0, p0, ${setterMethod.classDef.type}->${qualityFieldReference.name}:${qualityFieldReference.type}
+ const-string v1, "$qIndexMethodName"
+ invoke-static {p1, p2, v0, v1}, Lapp/revanced/integrations/patches/playback/quality/RememberVideoQualityPatch;->setVideoQuality([Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/String;)I
+ move-result p2
+ """,
+ )
+
+ userQualityMethod.mutableMethod.addInstruction(
+ 0,
+ "invoke-static {p3}, Lapp/revanced/integrations/patches/playback/quality/RememberVideoQualityPatch;->userChangedQuality(I)V"
+ )
+
+ return PatchResultSuccess()
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/annotations/CustomPlaybackSpeedCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/annotations/CustomPlaybackSpeedCompatibility.kt
new file mode 100644
index 0000000..bcecd7d
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/annotations/CustomPlaybackSpeedCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.video.speed.custom.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class CustomPlaybackSpeedCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedArrayGeneratorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedArrayGeneratorFingerprint.kt
new file mode 100644
index 0000000..e4e3368
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedArrayGeneratorFingerprint.kt
@@ -0,0 +1,22 @@
+package app.revanced.patches.youtube.misc.video.speed.custom.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+@FuzzyPatternScanMethod(2)
+object SpeedArrayGeneratorFingerprint : MethodFingerprint(
+ "[L",
+ AccessFlags.PUBLIC or AccessFlags.STATIC,
+ opcodes = listOf(
+ Opcode.IF_NEZ,
+ Opcode.SGET_OBJECT,
+ Opcode.GOTO,
+ Opcode.INVOKE_INTERFACE,
+ Opcode.MOVE_RESULT_OBJECT,
+ Opcode.IGET_OBJECT,
+ ),
+ strings = listOf("0.0#")
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedLimiterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedLimiterFingerprint.kt
new file mode 100644
index 0000000..4cb98cd
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedLimiterFingerprint.kt
@@ -0,0 +1,22 @@
+package app.revanced.patches.youtube.misc.video.speed.custom.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object SpeedLimiterFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.PUBLIC or AccessFlags.FINAL,
+ listOf("F"),
+ listOf(
+ Opcode.INVOKE_STATIC,
+ Opcode.MOVE_RESULT,
+ Opcode.IF_EQZ,
+ Opcode.CONST_HIGH16,
+ Opcode.GOTO,
+ Opcode.CONST_HIGH16,
+ Opcode.CONST_HIGH16,
+ Opcode.INVOKE_STATIC,
+ ),
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/VideoSpeedPatchFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/VideoSpeedPatchFingerprint.kt
new file mode 100644
index 0000000..541601f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/VideoSpeedPatchFingerprint.kt
@@ -0,0 +1,11 @@
+package app.revanced.patches.youtube.misc.video.speed.custom.fingerprints
+
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.Opcode
+
+object VideoSpeedPatchFingerprint : MethodFingerprint(
+ opcodes = listOf(Opcode.FILL_ARRAY_DATA),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass.endsWith("CustomVideoSpeedPatch;") && methodDef.name == ""
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/patch/CustomVideoSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/patch/CustomVideoSpeedPatch.kt
new file mode 100644
index 0000000..1b76883
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/patch/CustomVideoSpeedPatch.kt
@@ -0,0 +1,195 @@
+package app.revanced.patches.youtube.misc.video.speed.custom.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.patch.*
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patches.youtube.misc.video.speed.custom.annotations.CustomPlaybackSpeedCompatibility
+import app.revanced.patches.youtube.misc.video.speed.custom.fingerprints.SpeedArrayGeneratorFingerprint
+import app.revanced.patches.youtube.misc.video.speed.custom.fingerprints.SpeedLimiterFingerprint
+import app.revanced.patches.youtube.misc.video.speed.custom.fingerprints.VideoSpeedPatchFingerprint
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import org.jf.dexlib2.builder.instruction.BuilderArrayPayload
+import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+import org.jf.dexlib2.iface.instruction.ReferenceInstruction
+import org.jf.dexlib2.iface.reference.FieldReference
+import org.jf.dexlib2.iface.reference.MethodReference
+import java.util.stream.DoubleStream
+import kotlin.math.roundToInt
+
+@Patch
+@Name("custom-video-speed")
+@Description("Adds more video speed options.")
+@DependsOn([IntegrationsPatch::class])
+@CustomPlaybackSpeedCompatibility
+@Version("0.0.1")
+class CustomVideoSpeedPatch : BytecodePatch(
+ listOf(
+ SpeedArrayGeneratorFingerprint, SpeedLimiterFingerprint, VideoSpeedPatchFingerprint
+ )
+) {
+
+ override fun execute(context: BytecodeContext): PatchResult {
+ val speedLimitMin = minVideoSpeed!!.toFloat()
+ val speedLimitMax = maxVideoSpeed!!.toFloat().coerceAtLeast(speedLimitMin)
+ val speedsGranularity = videoSpeedsGranularity!!.toFloat()
+
+ val arrayGenMethod = SpeedArrayGeneratorFingerprint.result?.mutableMethod!!
+ val arrayGenMethodImpl = arrayGenMethod.implementation!!
+
+ val sizeCallIndex = arrayGenMethodImpl.instructions
+ .indexOfFirst { ((it as? ReferenceInstruction)?.reference as? MethodReference)?.name == "size" }
+
+ if (sizeCallIndex == -1) return PatchResultError("Couldn't find call to size()")
+
+ val sizeCallResultRegister =
+ (arrayGenMethodImpl.instructions.elementAt(sizeCallIndex + 1) as OneRegisterInstruction).registerA
+
+ arrayGenMethod.replaceInstruction(
+ sizeCallIndex + 1,
+ "const/4 v$sizeCallResultRegister, 0x0"
+ )
+
+ val (arrayLengthConstIndex, arrayLengthConst) = arrayGenMethodImpl.instructions.withIndex()
+ .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 7 }
+
+ val arrayLengthConstDestination = (arrayLengthConst as OneRegisterInstruction).registerA
+
+ val videoSpeedsArrayType = "Lapp/revanced/integrations/patches/playback/speed/CustomVideoSpeedPatch;->videoSpeeds:[F"
+
+ arrayGenMethod.addInstructions(
+ arrayLengthConstIndex + 1,
+ """
+ sget-object v$arrayLengthConstDestination, $videoSpeedsArrayType
+ array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
+ """
+ )
+
+ val (originalArrayFetchIndex, originalArrayFetch) = arrayGenMethodImpl.instructions.withIndex()
+ .first {
+ val reference = ((it.value as? ReferenceInstruction)?.reference as? FieldReference)
+ reference?.definingClass?.contains("PlayerConfigModel") ?: false &&
+ reference?.type == "[F"
+ }
+
+ val originalArrayFetchDestination = (originalArrayFetch as OneRegisterInstruction).registerA
+
+ arrayGenMethod.replaceInstruction(
+ originalArrayFetchIndex,
+ "sget-object v$originalArrayFetchDestination, $videoSpeedsArrayType"
+ )
+
+ val limiterMethod = SpeedLimiterFingerprint.result?.mutableMethod!!
+ val limiterMethodImpl = limiterMethod.implementation!!
+
+ val (limiterMinConstIndex, limiterMinConst) = limiterMethodImpl.instructions.withIndex()
+ .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 0.25f.toRawBits() }
+ val (limiterMaxConstIndex, limiterMaxConst) = limiterMethodImpl.instructions.withIndex()
+ .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 2.0f.toRawBits() }
+
+ val limiterMinConstDestination = (limiterMinConst as OneRegisterInstruction).registerA
+ val limiterMaxConstDestination = (limiterMaxConst as OneRegisterInstruction).registerA
+
+ fun hexFloat(float: Float): String = "0x%08x".format(float.toRawBits())
+
+ limiterMethod.replaceInstruction(
+ limiterMinConstIndex,
+ "const/high16 v$limiterMinConstDestination, ${hexFloat(speedLimitMin)}"
+ )
+ limiterMethod.replaceInstruction(
+ limiterMaxConstIndex,
+ "const/high16 v$limiterMaxConstDestination, ${hexFloat(speedLimitMax)}"
+ )
+
+ val constructorResult = VideoSpeedPatchFingerprint.result!!
+ val constructor = constructorResult.mutableMethod
+ val implementation = constructor.implementation!!
+
+ val stepsGranularity = 8F
+ val step = speedLimitMax
+ .minus(speedLimitMin) // calculate the range of the speeds
+ .div(speedsGranularity)
+ .times(stepsGranularity)
+ .roundToInt()
+ .div(stepsGranularity)// round to nearest multiple of stepsGranularity
+ .coerceAtLeast(1 / stepsGranularity) // ensure steps are at least 1/8th of the step granularity
+
+ val videoSpeedsArray = buildList {
+ DoubleStream
+ .iterate(speedLimitMin.toDouble()) { it + step } // create a stream of speeds
+ .let { speedStream ->
+ for (speed in speedStream) {
+ if (speed > speedLimitMax) break
+ add(speed.toFloat().toRawBits())
+ }
+ }
+ }
+
+ // adjust the new array of speeds size
+ constructor.replaceInstruction(
+ 0,
+ "const/16 v0, ${videoSpeedsArray.size}"
+ )
+
+ // create the payload with the new speeds
+ val arrayPayloadIndex = implementation.instructions.size - 1
+ implementation.replaceInstruction(
+ arrayPayloadIndex,
+ BuilderArrayPayload(
+ 4,
+ videoSpeedsArray
+ )
+ )
+
+ return PatchResultSuccess()
+ }
+
+ companion object : OptionsContainer() {
+ private fun String?.validate(max: Int? = null) = this?.toFloatOrNull() != null &&
+ toFloat().let { float ->
+ float > 0 && max?.let { max -> float <= max } ?: true
+ }
+
+ val videoSpeedsGranularity by option(
+ PatchOption.StringOption(
+ "granularity",
+ "16",
+ "Video speed granularity",
+ "The granularity of the video speeds. The higher the value, the more speeds will be available.",
+ true
+ ) {
+ it.validate()
+ }
+ )
+
+ val minVideoSpeed by option(
+ PatchOption.StringOption(
+ "min",
+ "0.25",
+ "Minimum video speed",
+ "The minimum video speed.",
+ true
+ ) {
+ it.validate()
+ }
+ )
+
+ val maxVideoSpeed by option(
+ PatchOption.StringOption(
+ "max",
+ "5.0",
+ "Maximum video speed",
+ "The maximum video speed. Must be greater than the minimum video speed and smaller than 5.",
+ true
+ ) {
+ it.validate(5)
+ }
+ )
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/annotation/VideoIdCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/annotation/VideoIdCompatibility.kt
new file mode 100644
index 0000000..8f7401f
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/annotation/VideoIdCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.video.videoid.annotation
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class VideoIdCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt
new file mode 100644
index 0000000..b688fb7
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.youtube.misc.video.videoid.fingerprint
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+
+object VideoIdFingerprint : MethodFingerprint(
+ "V",
+ AccessFlags.DECLARED_SYNCHRONIZED or AccessFlags.FINAL or AccessFlags.PUBLIC,
+ listOf("L"),
+ listOf(Opcode.INVOKE_INTERFACE),
+ customFingerprint = {
+ it.definingClass.endsWith("PlaybackLifecycleMonitor;")
+ }
+)
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt
new file mode 100644
index 0000000..da21078
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt
@@ -0,0 +1,58 @@
+package app.revanced.patches.youtube.misc.video.videoid.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
+import app.revanced.patches.youtube.misc.video.videoid.annotation.VideoIdCompatibility
+import app.revanced.patches.youtube.misc.video.videoid.fingerprint.VideoIdFingerprint
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Name("video-id-hook")
+@Description("Hooks to detect when the video id changes")
+@VideoIdCompatibility
+@Version("0.0.1")
+@DependsOn([IntegrationsPatch::class])
+class VideoIdPatch : BytecodePatch(
+ listOf(
+ VideoIdFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ with(VideoIdFingerprint.result!!) {
+ insertMethod = mutableMethod
+ insertIndex = scanResult.patternScanResult!!.endIndex + 2
+
+ videoIdRegister = (insertMethod.instruction(insertIndex - 1) as OneRegisterInstruction).registerA
+ }
+
+ return PatchResultSuccess()
+ }
+
+ companion object {
+ private var videoIdRegister = 0
+ private var insertIndex = 0
+
+ private lateinit var insertMethod: MutableMethod
+
+ /**
+ * Adds an invoke-static instruction, called with the new id when the video changes
+ * @param methodDescriptor which method to call. Params have to be `Ljava/lang/String;`
+ */
+ fun injectCall(
+ methodDescriptor: String
+ ) = insertMethod.addInstructions(
+ insertIndex, // move-result-object offset
+ "invoke-static {v$videoIdRegister}, $methodDescriptor"
+ )
+ }
+}
+
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/annotations/CustomVideoBufferCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/annotations/CustomVideoBufferCompatibility.kt
new file mode 100644
index 0000000..92f3ae0
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/annotations/CustomVideoBufferCompatibility.kt
@@ -0,0 +1,13 @@
+package app.revanced.patches.youtube.misc.videobuffer.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility(
+ [Package(
+ "com.google.android.youtube", arrayOf("17.49.37")
+ )]
+)
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class CustomVideoBufferCompatibility
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/MaxBufferFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/MaxBufferFingerprint.kt
new file mode 100644
index 0000000..9f194e9
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/MaxBufferFingerprint.kt
@@ -0,0 +1,19 @@
+package app.revanced.patches.youtube.misc.videobuffer.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction
+
+object MaxBufferFingerprint : MethodFingerprint(
+ "I", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(),
+ listOf(Opcode.SGET_OBJECT, Opcode.IGET, Opcode.IF_EQZ, Opcode.RETURN),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
+ && methodDef.implementation!!.instructions.any {
+ ((it as? NarrowLiteralInstruction)?.narrowLiteral == 120000)
+ && methodDef.name == "r"
+ }
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/PlaybackBufferFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/PlaybackBufferFingerprint.kt
new file mode 100644
index 0000000..b4de743
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/PlaybackBufferFingerprint.kt
@@ -0,0 +1,18 @@
+package app.revanced.patches.youtube.misc.videobuffer.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction
+
+object PlaybackBufferFingerprint : MethodFingerprint(
+ "I", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(),
+ listOf(Opcode.IF_LEZ, Opcode.RETURN),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
+ && methodDef.implementation!!.instructions.any {
+ ((it as? NarrowLiteralInstruction)?.narrowLiteral == 1600)
+ }
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/ReBufferFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/ReBufferFingerprint.kt
new file mode 100644
index 0000000..d9754dd
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/ReBufferFingerprint.kt
@@ -0,0 +1,18 @@
+package app.revanced.patches.youtube.misc.videobuffer.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import org.jf.dexlib2.AccessFlags
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction
+
+object ReBufferFingerprint : MethodFingerprint(
+ "I", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(),
+ listOf(Opcode.IF_LEZ, Opcode.RETURN),
+ customFingerprint = { methodDef ->
+ methodDef.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
+ && methodDef.implementation!!.instructions.any {
+ ((it as? NarrowLiteralInstruction)?.narrowLiteral == 5000)
+ }
+ }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/patch/CustomVideoBufferPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/patch/CustomVideoBufferPatch.kt
new file mode 100644
index 0000000..4562f93
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/patch/CustomVideoBufferPatch.kt
@@ -0,0 +1,137 @@
+package app.revanced.patches.youtube.misc.videobuffer.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.InputType
+import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.TextPreference
+import app.revanced.patches.youtube.misc.videobuffer.annotations.CustomVideoBufferCompatibility
+import app.revanced.patches.youtube.misc.videobuffer.fingerprints.MaxBufferFingerprint
+import app.revanced.patches.youtube.misc.videobuffer.fingerprints.PlaybackBufferFingerprint
+import app.revanced.patches.youtube.misc.videobuffer.fingerprints.ReBufferFingerprint
+import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
+
+@Patch
+@Name("custom-video-buffer")
+@Description("Lets you change the buffers of videos.")
+@DependsOn([SettingsPatch::class])
+@CustomVideoBufferCompatibility
+@Version("0.0.1")
+class CustomVideoBufferPatch : BytecodePatch(
+ listOf(
+ MaxBufferFingerprint, PlaybackBufferFingerprint, ReBufferFingerprint
+ )
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ PreferenceScreen(
+ "revanced_custom_video_buffer",
+ StringResource("revanced_custom_video_buffer_title", "Video buffer settings"),
+ listOf(
+ TextPreference(
+ "revanced_pref_max_buffer_ms",
+ StringResource("revanced_pref_max_buffer_ms_title", "Maximum buffer size"),
+ InputType.NUMBER,
+ "120000",
+ StringResource(
+ "revanced_pref_max_buffer_ms_summary",
+ "The maximum size of a buffer for playback"
+ )
+ ),
+ TextPreference(
+ "revanced_pref_buffer_for_playback_ms",
+ StringResource("revanced_pref_buffer_for_playback_ms_title", "Maximum buffer for playback"),
+ InputType.NUMBER,
+ "2500",
+ StringResource(
+ "revanced_pref_buffer_for_playback_ms_summary",
+ "Maximum size of a buffer for playback"
+ )
+ ),
+ TextPreference(
+ "revanced_pref_buffer_for_playback_after_rebuffer_ms",
+ StringResource(
+ "revanced_pref_buffer_for_playback_after_rebuffer_ms_title",
+ "Maximum buffer for playback after rebuffer"
+ ),
+ InputType.NUMBER,
+ "5000",
+ StringResource(
+ "revanced_pref_buffer_for_playback_after_rebuffer_ms_summary",
+ "Maximum size of a buffer for playback after rebuffering"
+ )
+ )
+ ),
+ StringResource("revanced_custom_video_buffer_summary", "Custom settings for video buffer")
+ )
+ )
+
+ execMaxBuffer()
+ execPlaybackBuffer()
+ execReBuffer()
+ return PatchResultSuccess()
+ }
+
+ private fun execMaxBuffer() {
+ val (method, result) = MaxBufferFingerprint.unwrap(true, -1)
+ val (index, register) = result
+
+ method.addInstructions(
+ index + 1, """
+ invoke-static {}, Lapp/revanced/integrations/patches/VideoBufferPatch;->getMaxBuffer()I
+ move-result v$register
+ """
+ )
+ }
+
+ private fun execPlaybackBuffer() {
+ val (method, result) = PlaybackBufferFingerprint.unwrap()
+ val (index, register) = result
+
+ method.addInstructions(
+ index + 1, """
+ invoke-static {}, Lapp/revanced/integrations/patches/VideoBufferPatch;->getPlaybackBuffer()I
+ move-result v$register
+ """
+ )
+ }
+
+ private fun execReBuffer() {
+ val (method, result) = ReBufferFingerprint.unwrap()
+ val (index, register) = result
+
+ method.addInstructions(
+ index + 1, """
+ invoke-static {}, Lapp/revanced/integrations/patches/VideoBufferPatch;->getReBuffer()I
+ move-result v$register
+ """
+ )
+ }
+
+ private fun MethodFingerprint.unwrap(
+ forEndIndex: Boolean = false,
+ offset: Int = 0
+ ): Pair> {
+ val result = this.result!!
+ val method = result.mutableMethod
+ val scanResult = result.scanResult.patternScanResult!!
+ val index = (if (forEndIndex) scanResult.endIndex else scanResult.startIndex) + offset
+
+ val register = (method.instruction(index) as OneRegisterInstruction).registerA
+
+ return method to (index to register)
+ }
+}
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/annotations/ZoomHapticsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/annotations/ZoomHapticsCompatibility.kt
new file mode 100644
index 0000000..86819cd
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/annotations/ZoomHapticsCompatibility.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.youtube.misc.zoomhaptics.annotations
+
+import app.revanced.patcher.annotation.Compatibility
+import app.revanced.patcher.annotation.Package
+
+@Compatibility([Package("com.google.android.youtube")])
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ZoomHapticsCompatibility
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/fingerprints/ZoomHapticsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/fingerprints/ZoomHapticsFingerprint.kt
new file mode 100644
index 0000000..493db9c
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/fingerprints/ZoomHapticsFingerprint.kt
@@ -0,0 +1,8 @@
+package app.revanced.patches.youtube.misc.zoomhaptics.fingerprints
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+
+object ZoomHapticsFingerprint : MethodFingerprint(
+ strings = listOf(
+ "Failed to haptics vibrate for video zoom"
+ )
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/patch/ZoomHapticsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/patch/ZoomHapticsPatch.kt
new file mode 100644
index 0000000..89cccda
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/patch/ZoomHapticsPatch.kt
@@ -0,0 +1,54 @@
+package app.revanced.patches.youtube.misc.zoomhaptics.patch
+
+import app.revanced.patcher.annotation.Description
+import app.revanced.patcher.annotation.Name
+import app.revanced.patcher.annotation.Version
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.instruction
+import app.revanced.patcher.patch.BytecodePatch
+import app.revanced.patcher.patch.PatchResult
+import app.revanced.patcher.patch.PatchResultSuccess
+import app.revanced.patcher.patch.annotations.DependsOn
+import app.revanced.patcher.patch.annotations.Patch
+import app.revanced.patcher.util.smali.ExternalLabel
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
+import app.revanced.patches.youtube.misc.zoomhaptics.annotations.ZoomHapticsCompatibility
+import app.revanced.patches.youtube.misc.zoomhaptics.fingerprints.ZoomHapticsFingerprint
+
+@Patch
+@Name("disable-zoom-haptics")
+@Description("Disables haptics when zooming.")
+@DependsOn([SettingsPatch::class])
+@ZoomHapticsCompatibility
+@Version("0.0.1")
+class ZoomHapticsPatch : BytecodePatch(
+ listOf(ZoomHapticsFingerprint)
+) {
+ override fun execute(context: BytecodeContext): PatchResult {
+ SettingsPatch.PreferenceScreen.MISC.addPreferences(
+ SwitchPreference(
+ "revanced_disable_zoom_haptics",
+ StringResource("revanced_disable_zoom_haptics_title", "Disable zoom haptics"),
+ true,
+ StringResource("revanced_disable_zoom_haptics_summary_on", "Haptics are disabled"),
+ StringResource("revanced_disable_zoom_haptics_summary_off", "Haptics are enabled")
+ )
+ )
+
+ val zoomHapticsFingerprintMethod = ZoomHapticsFingerprint.result!!.mutableMethod
+
+ zoomHapticsFingerprintMethod.addInstructions(
+ 0, """
+ invoke-static { }, Lapp/revanced/integrations/patches/ZoomHapticsPatch;->shouldVibrate()Z
+ move-result v0
+ if-nez v0, :vibrate
+ return-void
+ """, listOf(ExternalLabel("vibrate", zoomHapticsFingerprintMethod.instruction(0)))
+ )
+
+ return PatchResultSuccess()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/util/microg/Constants.kt b/src/main/kotlin/app/revanced/util/microg/Constants.kt
new file mode 100644
index 0000000..912b975
--- /dev/null
+++ b/src/main/kotlin/app/revanced/util/microg/Constants.kt
@@ -0,0 +1,130 @@
+package app.revanced.util.microg
+
+/**
+ * constants for microG builds with signature spoofing
+ */
+internal object Constants {
+ /**
+ * microG vendor name
+ * aka. package prefix / package base
+ */
+ const val MICROG_VENDOR = "com.mgoogle"
+
+ /**
+ * meta-data for microG package name spoofing on patched builds
+ */
+ const val META_SPOOFED_PACKAGE_NAME = "$MICROG_VENDOR.android.gms.SPOOFED_PACKAGE_NAME"
+
+ /**
+ * meta-data for microG package signature spoofing on patched builds
+ */
+ const val META_SPOOFED_PACKAGE_SIGNATURE = "$MICROG_VENDOR.android.gms.SPOOFED_PACKAGE_SIGNATURE"
+
+ /**
+ * meta-data for microG package detection
+ */
+ const val META_GMS_PACKAGE_NAME = "app.revanced.MICROG_PACKAGE_NAME"
+
+ /**
+ * a list of all permissions in microG
+ */
+ val PERMISSIONS = listOf(
+ // C2DM / GCM
+ "com.google.android.c2dm.permission.RECEIVE",
+ "com.google.android.c2dm.permission.SEND",
+ "com.google.android.gtalkservice.permission.GTALK_SERVICE",
+
+ // GAuth
+ "com.google.android.googleapps.permission.GOOGLE_AUTH",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH.local",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
+ "com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
+ )
+
+ /**
+ * a list of all (intent) actions in microG
+ */
+ val ACTIONS = listOf(
+ // location
+ "com.google.android.gms.location.places.ui.PICK_PLACE",
+ "com.google.android.gms.location.places.GeoDataApi",
+ "com.google.android.gms.location.places.PlacesApi",
+ "com.google.android.gms.location.places.PlaceDetectionApi",
+ "com.google.android.gms.wearable.MESSAGE_RECEIVED",
+
+ // C2DM / GCM
+ "com.google.android.c2dm.intent.REGISTER",
+ "com.google.android.c2dm.intent.REGISTRATION",
+ "com.google.android.c2dm.intent.UNREGISTER",
+ "com.google.android.c2dm.intent.RECEIVE",
+ "com.google.iid.TOKEN_REQUEST",
+ "com.google.android.gcm.intent.SEND",
+
+ // car
+ "com.google.android.gms.car.service.START",
+
+ // people
+ "com.google.android.gms.people.service.START",
+
+ // wearable
+ "com.google.android.gms.wearable.BIND",
+
+ // auth
+ "com.google.android.gsf.login",
+ "com.google.android.gsf.action.GET_GLS",
+ "com.google.android.gms.common.account.CHOOSE_ACCOUNT",
+ "com.google.android.gms.auth.login.LOGIN",
+ "com.google.android.gms.auth.api.credentials.PICKER",
+ "com.google.android.gms.auth.api.credentials.service.START",
+ "com.google.android.gms.auth.service.START",
+ "com.google.firebase.auth.api.gms.service.START",
+ "com.google.android.gms.auth.be.appcert.AppCertService",
+
+ // fido
+ "com.google.android.gms.fido.fido2.privileged.START",
+
+ // games
+ "com.google.android.gms.games.service.START",
+ "com.google.android.gms.games.PLAY_GAMES_UPGRADE",
+
+ // chimera
+ "com.google.android.gms.chimera",
+
+ // fonts
+ "com.google.android.gms.fonts",
+
+ // phenotype
+ "com.google.android.gms.phenotype.service.START",
+
+ // location
+ "com.google.android.gms.location.reporting.service.START",
+
+ // misc
+ "com.google.android.gms.gmscompliance.service.START",
+ "com.google.android.gms.oss.licenses.service.START",
+ "com.google.android.gms.safetynet.service.START",
+ "com.google.android.gms.tapandpay.service.BIND"
+ )
+
+ /**
+ * a list of all content provider authorities in microG
+ */
+ val AUTHORITIES = listOf(
+ // gsf
+ "com.google.android.gsf.gservices",
+ "com.google.settings",
+
+ // auth
+ "com.google.android.gms.auth.accounts",
+
+ // chimera
+ "com.google.android.gms.chimera",
+
+ // fonts
+ "com.google.android.gms.fonts",
+
+ // phenotype
+ "com.google.android.gms.phenotype"
+ )
+}
diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt
new file mode 100644
index 0000000..b4b4038
--- /dev/null
+++ b/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt
@@ -0,0 +1,241 @@
+package app.revanced.util.microg
+
+import app.revanced.patcher.data.BytecodeContext
+import app.revanced.patcher.extensions.addInstruction
+import app.revanced.patcher.extensions.addInstructions
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
+import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
+import app.revanced.util.microg.Constants.ACTIONS
+import app.revanced.util.microg.Constants.AUTHORITIES
+import app.revanced.util.microg.Constants.MICROG_VENDOR
+import app.revanced.util.microg.Constants.PERMISSIONS
+import org.jf.dexlib2.Opcode
+import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
+import org.jf.dexlib2.iface.instruction.formats.Instruction21c
+import org.jf.dexlib2.iface.reference.StringReference
+import org.jf.dexlib2.immutable.reference.ImmutableStringReference
+
+/**
+ * Helper class for applying bytecode patches needed for the microg-support patches.
+ */
+internal object MicroGBytecodeHelper {
+ /**
+ * Hook a method to check the availability of MicroG.
+ *
+ * @param fingerprint The fingerprint of the method to add the call for the notice in.
+ */
+ fun injectNotice(fingerprint: MethodFingerprint) {
+ fingerprint.result!!.mutableMethod.addInstruction(
+ 0,
+ "invoke-static {}, Lapp/revanced/integrations/patches/MicroGSupport;->checkAvailability()V"
+ )
+ }
+
+ /**
+ * Transform strings with package name out of [fromPackageName] and [toPackageName].
+ *
+ * @param fromPackageName Original package name.
+ * @param toPackageName The package name to accept.
+ **/
+ fun packageNameTransform(fromPackageName: String, toPackageName: String): (String) -> String? {
+ return { referencedString ->
+ when (referencedString) {
+ "$fromPackageName.SuggestionsProvider",
+ "$fromPackageName.fileprovider" -> referencedString.replace(fromPackageName, toPackageName)
+ else -> null
+ }
+ }
+ }
+
+ /**
+ * Prime method data class for the [MicroGBytecodeHelper] class.
+ *
+ * @param primeMethodFingerprint The prime methods [MethodFingerprint].
+ * @param fromPackageName Original package name.
+ * @param toPackageName The package name to accept.
+ **/
+ data class PrimeMethodTransformationData(
+ val primeMethodFingerprint: MethodFingerprint,
+ val fromPackageName: String,
+ val toPackageName: String
+ ) {
+ /**
+ * Patch the prime method to accept the new package name.
+ */
+ fun transformPrimeMethodPackageName() {
+ val primeMethod = primeMethodFingerprint.result!!.mutableMethod
+ val implementation = primeMethod.implementation!!
+
+ var register = 2
+ val index = implementation.instructions.indexOfFirst {
+ if (it.opcode != Opcode.CONST_STRING) return@indexOfFirst false
+
+ val instructionString = ((it as Instruction21c).reference as StringReference).string
+ if (instructionString != fromPackageName) return@indexOfFirst false
+
+ register = it.registerA
+ return@indexOfFirst true
+ }
+
+ primeMethod.replaceInstruction(
+ index, "const-string v$register, \"$toPackageName\""
+ )
+ }
+ }
+
+ /**
+ * Patch the bytecode to work with MicroG.
+ * Note: this only handles string constants to gms (intent actions, authorities, ...).
+ * If the app employs additional checks to validate the installed gms package, you'll have to handle those in the app- specific patch
+ *
+ * @param context The context.
+ * @param additionalStringTransforms Additional transformations applied to all const-string references.
+ * @param primeMethodTransformationData Data to patch the prime method.
+ * @param earlyReturns List of [MethodFingerprint] to return the resolved methods early.
+ */
+ fun patchBytecode(
+ context: BytecodeContext,
+ additionalStringTransforms: Array<(str: String) -> String?>,
+ primeMethodTransformationData: PrimeMethodTransformationData,
+ earlyReturns: List
+ ) {
+ earlyReturns.returnEarly()
+ primeMethodTransformationData.transformPrimeMethodPackageName()
+
+ val allTransforms = arrayOf(
+ MicroGBytecodeHelper::commonTransform,
+ MicroGBytecodeHelper::contentUrisTransform,
+ *additionalStringTransforms
+ )
+
+ // transform all strings using all provided transforms, first match wins
+ context.transformStringReferences transform@{
+ for (transformFn in allTransforms) {
+ val s = transformFn(it)
+ if (s != null) return@transform s
+ }
+
+ return@transform null
+ }
+ }
+
+ /**
+ * const-string transform function for common gms string references.
+ *
+ * @param referencedString The string to transform.
+ */
+ private fun commonTransform(referencedString: String): String? =
+ when (referencedString) {
+ "com.google",
+ "com.google.android.gms",
+ in PERMISSIONS,
+ in ACTIONS,
+ in AUTHORITIES -> referencedString.replace("com.google", MICROG_VENDOR)
+
+ // subscribedfeeds has no vendor prefix for whatever reason...
+ "subscribedfeeds" -> "${MICROG_VENDOR}.subscribedfeeds"
+ else -> null
+ }
+
+
+ /**
+ * const-string transform function for strings containing gms content uris / authorities.
+ */
+ private fun contentUrisTransform(str: String): String? {
+ // only when content:// uri
+ if (str.startsWith("content://")) {
+ // check if matches any authority
+ for (authority in AUTHORITIES) {
+ val uriPrefix = "content://$authority"
+ if (str.startsWith(uriPrefix)) {
+ return str.replace(
+ uriPrefix,
+ "content://${authority.replace("com.google", MICROG_VENDOR)}"
+ )
+ }
+ }
+
+ // gms also has a 'subscribedfeeds' authority, check for that one too
+ val subFeedsUriPrefix = "content://subscribedfeeds"
+ if (str.startsWith(subFeedsUriPrefix)) {
+ return str.replace(subFeedsUriPrefix, "content://${MICROG_VENDOR}.subscribedfeeds")
+ }
+ }
+
+ return null
+ }
+
+ /**
+ * Transform all constant string references using a transformation function.
+ *
+ * @param transformFn string transformation function. if null, string is not changed.
+ */
+ private fun BytecodeContext.transformStringReferences(transformFn: (str: String) -> String?) {
+ classes.forEach { classDef ->
+ var mutableClass: MutableClass? = null
+
+ // enumerate all methods
+ classDef.methods.forEach classLoop@{ methodDef ->
+ var mutableMethod: MutableMethod? = null
+ val implementation = methodDef.implementation ?: return@classLoop
+
+ // enumerate all instructions and find const-string
+ implementation.instructions.forEachIndexed implLoop@{ index, instruction ->
+ // skip all that are not const-string
+ if (instruction.opcode != Opcode.CONST_STRING) return@implLoop
+ val str = ((instruction as Instruction21c).reference as StringReference).string
+
+ // call transform function
+ val transformedStr = transformFn(str)
+ if (transformedStr != null) {
+ // make class and method mutable, if not already
+ mutableClass = mutableClass ?: proxy(classDef).mutableClass
+ mutableMethod = mutableMethod ?: mutableClass!!.methods.first {
+ it.name == methodDef.name && it.parameterTypes.containsAll(methodDef.parameterTypes)
+ }
+
+ // replace instruction with updated string
+ mutableMethod!!.implementation!!.replaceInstruction(
+ index,
+ BuilderInstruction21c(
+ Opcode.CONST_STRING,
+ instruction.registerA,
+ ImmutableStringReference(
+ transformedStr
+ )
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the resolved methods of a list of [MethodFingerprint] early.
+ */
+ private fun List.returnEarly() {
+ this.forEach { fingerprint ->
+ val result = fingerprint.result!!
+ val stringInstructions = when (result.method.returnType.first()) {
+ 'L' -> """
+ const/4 v0, 0x0
+ return-object v0
+ """
+
+ 'V' -> "return-void"
+ 'I' -> """
+ const/4 v0, 0x0
+ return v0
+ """
+
+ else -> throw Exception("This case should never happen.")
+ }
+ result.mutableMethod.addInstructions(
+ 0, stringInstructions
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt
new file mode 100644
index 0000000..003fa00
--- /dev/null
+++ b/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt
@@ -0,0 +1,58 @@
+package app.revanced.util.microg
+
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.util.microg.Constants.META_GMS_PACKAGE_NAME
+import app.revanced.util.microg.Constants.META_SPOOFED_PACKAGE_NAME
+import app.revanced.util.microg.Constants.META_SPOOFED_PACKAGE_SIGNATURE
+import app.revanced.util.microg.Constants.MICROG_VENDOR
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+
+/**
+ * helper class for adding manifest metadata needed for microG builds with signature spoofing
+ */
+internal object MicroGManifestHelper {
+
+ /**
+ * Add manifest entries needed for package and signature spoofing when using MicroG.
+ * Note: this only adds metadata entries for signature spoofing, other changes may still be required to make a microG patch work.
+ *
+ * @param context Resource context.
+ * @param spoofedPackage The package to spoof.
+ * @param spoofedSignature The signature to spoof.
+ */
+ fun addSpoofingMetadata(
+ context: ResourceContext,
+ spoofedPackage: String,
+ spoofedSignature: String
+ ) {
+ context.xmlEditor["AndroidManifest.xml"].use {
+ val applicationNode = it
+ .file
+ .getElementsByTagName("application")
+ .item(0)
+
+ // package spoofing
+ applicationNode.adoptChild("meta-data") {
+ setAttribute("android:name", META_SPOOFED_PACKAGE_NAME)
+ setAttribute("android:value", spoofedPackage)
+ }
+ applicationNode.adoptChild("meta-data") {
+ setAttribute("android:name", META_SPOOFED_PACKAGE_SIGNATURE)
+ setAttribute("android:value", spoofedSignature)
+ }
+
+ // microG presence detection in integrations
+ applicationNode.adoptChild("meta-data") {
+ setAttribute("android:name", META_GMS_PACKAGE_NAME)
+ setAttribute("android:value", "${MICROG_VENDOR}.android.gms")
+ }
+ }
+ }
+
+ private fun Node.adoptChild(tagName: String, block: Element.() -> Unit) {
+ val child = ownerDocument.createElement(tagName)
+ child.block()
+ appendChild(child)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt
new file mode 100644
index 0000000..348e145
--- /dev/null
+++ b/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt
@@ -0,0 +1,59 @@
+package app.revanced.util.microg
+
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.util.resources.ResourceUtils.mergeStrings
+
+/**
+ * Helper class for applying resource patches needed for the microg-support patches.
+ */
+internal object MicroGResourceHelper {
+
+ /**
+ * Add necessary strings to the strings.xml file.
+ *
+ * @param context The resource context.
+ * @param stringsHost The file which hosts the strings.
+ */
+ fun addStrings(context: ResourceContext, stringsHost: String = "microg/host/values/strings.xml") = context.mergeStrings(stringsHost)
+
+ /**
+ * Patch the manifest to work with MicroG.
+ *
+ * @param context The resource context.
+ * @param fromPackageName The original package name.
+ * @param toPackageName The package name to accept.
+ * @param toName The new name of the app.
+ */
+ fun patchManifest(
+ context: ResourceContext,
+ fromPackageName: String,
+ toPackageName: String,
+ toName: String
+ ) {
+ val manifest = context["AndroidManifest.xml"].readText()
+ context["AndroidManifest.xml"].writeText(
+ manifest.replace(
+ "package=\"$fromPackageName",
+ "package=\"$toPackageName"
+ ).replace(
+ "android:label=\"@string/app_name",
+ "android:label=\"$toName"
+ ).replace(
+ "android:label=\"@string/app_launcher_name",
+ "android:label=\"$toName"
+ ).replace(
+ "android:authorities=\"$fromPackageName",
+ "android:authorities=\"$toPackageName"
+ ).replace(
+ "$fromPackageName.permission.C2D_MESSAGE",
+ "$toPackageName.permission.C2D_MESSAGE"
+ ).replace(
+ "com.google.android.c2dm",
+ "${Constants.MICROG_VENDOR}.android.c2dm"
+ ).replace(
+ "",
+ ""
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt b/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt
new file mode 100644
index 0000000..be6a774
--- /dev/null
+++ b/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt
@@ -0,0 +1,99 @@
+package app.revanced.util.resources
+
+import app.revanced.patcher.data.DomFileEditor
+import app.revanced.patcher.data.ResourceContext
+import app.revanced.patches.shared.settings.preference.impl.StringResource
+import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
+import org.w3c.dom.Node
+import java.nio.file.Files
+import java.nio.file.StandardCopyOption
+
+internal object ResourceUtils {
+
+ /**
+ * Merge strings. This manages [StringResource]s automatically.
+ * @param host The hosting xml resource. Needs to be a valid strings.xml resource.
+ */
+ internal fun ResourceContext.mergeStrings(host: String) {
+ this.iterateXmlNodeChildren(host, "resources") {
+ // TODO: figure out why this is needed
+ if (!it.hasAttributes()) return@iterateXmlNodeChildren
+
+ val attributes = it.attributes
+ val key = attributes.getNamedItem("name")!!.nodeValue!!
+ val value = it.textContent!!
+
+ val formatted = attributes.getNamedItem("formatted") == null
+
+ SettingsPatch.addString(key, value, formatted)
+ }
+ }
+
+ /**
+ * Copy resources from the current class loader to the resource directory.
+ * @param sourceResourceDirectory The source resource directory name.
+ * @param resources The resources to copy.
+ */
+ internal fun ResourceContext.copyResources(sourceResourceDirectory: String, vararg resources: ResourceGroup) {
+ val classLoader = ResourceUtils.javaClass.classLoader
+ val targetResourceDirectory = this["res"]
+
+ for (resourceGroup in resources) {
+ resourceGroup.resources.forEach { resource ->
+ val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource"
+ Files.copy(
+ classLoader.getResourceAsStream("$sourceResourceDirectory/$resourceFile")!!,
+ targetResourceDirectory.resolve(resourceFile).toPath(), StandardCopyOption.REPLACE_EXISTING
+ )
+ }
+ }
+ }
+
+ /**
+ * Resource names mapped to their corresponding resource data.
+ * @param resourceDirectoryName The name of the directory of the resource.
+ * @param resources A list of resource names.
+ */
+ internal class ResourceGroup(val resourceDirectoryName: String, vararg val resources: String)
+
+ /**
+ * Iterate through the children of a node by its tag.
+ * @param resource The xml resource.
+ * @param targetTag The target xml node.
+ * @param callback The callback to call when iterating over the nodes.
+ */
+ internal fun ResourceContext.iterateXmlNodeChildren(
+ resource: String,
+ targetTag: String,
+ callback: (node: Node) -> Unit
+ ) =
+ xmlEditor[ResourceUtils.javaClass.classLoader.getResourceAsStream(resource)!!].use {
+ val stringsNode = it.file.getElementsByTagName(targetTag).item(0).childNodes
+ for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i))
+ }
+
+
+ /**
+ * Copies the specified node of the source [DomFileEditor] to the target [DomFileEditor].
+ * @param source the source [DomFileEditor].
+ * @param target the target [DomFileEditor]-
+ * @return AutoCloseable that closes the target [DomFileEditor]s.
+ */
+ fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoCloseable {
+ val hostNodes = source.file.getElementsByTagName(this).item(0).childNodes
+
+ val destinationResourceFile = target.file
+ val destinationNode = destinationResourceFile.getElementsByTagName(this).item(0)
+
+ for (index in 0 until hostNodes.length) {
+ val node = hostNodes.item(index).cloneNode(true)
+ destinationResourceFile.adoptNode(node)
+ destinationNode.appendChild(node)
+ }
+
+ return AutoCloseable {
+ source.close()
+ target.close()
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_background_color_108.png b/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_background_color_108.png
new file mode 100644
index 0000000..e2dc8a5
Binary files /dev/null and b/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_background_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_foreground_color_108.png
new file mode 100644
index 0000000..e5a73b5
Binary files /dev/null and b/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_foreground_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-hdpi/ic_launcher.png b/src/main/resources/branding/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..dc967b7
Binary files /dev/null and b/src/main/resources/branding/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/main/resources/branding/mipmap-hdpi/ic_launcher_round.png b/src/main/resources/branding/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dc967b7
Binary files /dev/null and b/src/main/resources/branding/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_background_color_108.png b/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_background_color_108.png
new file mode 100644
index 0000000..4dee175
Binary files /dev/null and b/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_background_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_foreground_color_108.png
new file mode 100644
index 0000000..3d26f5b
Binary files /dev/null and b/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_foreground_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-mdpi/ic_launcher.png b/src/main/resources/branding/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..4e3735d
Binary files /dev/null and b/src/main/resources/branding/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/main/resources/branding/mipmap-mdpi/ic_launcher_round.png b/src/main/resources/branding/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4e3735d
Binary files /dev/null and b/src/main/resources/branding/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_background_color_108.png b/src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_background_color_108.png
new file mode 100644
index 0000000..49d5f45
Binary files /dev/null and b/src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_background_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_foreground_color_108.png
new file mode 100644
index 0000000..4f66f60
Binary files /dev/null and b/src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_foreground_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-xhdpi/ic_launcher.png b/src/main/resources/branding/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..471c977
Binary files /dev/null and b/src/main/resources/branding/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/main/resources/branding/mipmap-xhdpi/ic_launcher_round.png b/src/main/resources/branding/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..471c977
Binary files /dev/null and b/src/main/resources/branding/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_background_color_108.png b/src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_background_color_108.png
new file mode 100644
index 0000000..1a92bea
Binary files /dev/null and b/src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_background_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_foreground_color_108.png
new file mode 100644
index 0000000..777daea
Binary files /dev/null and b/src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_foreground_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-xxhdpi/ic_launcher.png b/src/main/resources/branding/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6e21785
Binary files /dev/null and b/src/main/resources/branding/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/main/resources/branding/mipmap-xxhdpi/ic_launcher_round.png b/src/main/resources/branding/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..6e21785
Binary files /dev/null and b/src/main/resources/branding/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_background_color_108.png b/src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_background_color_108.png
new file mode 100644
index 0000000..9c4462f
Binary files /dev/null and b/src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_background_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_foreground_color_108.png
new file mode 100644
index 0000000..0abe542
Binary files /dev/null and b/src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_foreground_color_108.png differ
diff --git a/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher.png b/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..c0505bf
Binary files /dev/null and b/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher_round.png b/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..c0505bf
Binary files /dev/null and b/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/src/main/resources/downloads/drawable/revanced_yt_download_button.xml b/src/main/resources/downloads/drawable/revanced_yt_download_button.xml
new file mode 100644
index 0000000..69ab699
--- /dev/null
+++ b/src/main/resources/downloads/drawable/revanced_yt_download_button.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml b/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml
new file mode 100644
index 0000000..2325478
--- /dev/null
+++ b/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/main/resources/downloads/host/values/strings.xml b/src/main/resources/downloads/host/values/strings.xml
new file mode 100644
index 0000000..957d490
--- /dev/null
+++ b/src/main/resources/downloads/host/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ is not installed. Please install it.
+
diff --git a/src/main/resources/microg/host/values/strings.xml b/src/main/resources/microg/host/values/strings.xml
new file mode 100644
index 0000000..c5c1f69
--- /dev/null
+++ b/src/main/resources/microg/host/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Vanced MicroG is not installed. Please install it.
+
diff --git a/src/main/resources/returnyoutubedislike/host/values/strings.xml b/src/main/resources/returnyoutubedislike/host/values/strings.xml
new file mode 100644
index 0000000..314f5dc
--- /dev/null
+++ b/src/main/resources/returnyoutubedislike/host/values/strings.xml
@@ -0,0 +1,12 @@
+
+ Return YouTube Dislike
+ Dislikes are shown
+ Dislikes are not shown
+
+ Dislikes as percentage
+ Dislikes shown as percentage
+ Dislikes shown as number
+
+ ReturnYouTubeDislike.com
+ Dislike data is provided by the Return YouTube Dislike API. Tap here to learn more.
+
\ No newline at end of file
diff --git a/src/main/resources/settings/drawable-ldrtl-xxxhdpi/quantum_ic_arrow_back_white_24.png b/src/main/resources/settings/drawable-ldrtl-xxxhdpi/quantum_ic_arrow_back_white_24.png
new file mode 100644
index 0000000..d409b54
Binary files /dev/null and b/src/main/resources/settings/drawable-ldrtl-xxxhdpi/quantum_ic_arrow_back_white_24.png differ
diff --git a/src/main/resources/settings/drawable-xxxhdpi/quantum_ic_arrow_back_white_24.png b/src/main/resources/settings/drawable-xxxhdpi/quantum_ic_arrow_back_white_24.png
new file mode 100644
index 0000000..e27034d
Binary files /dev/null and b/src/main/resources/settings/drawable-xxxhdpi/quantum_ic_arrow_back_white_24.png differ
diff --git a/src/main/resources/settings/layout/revanced_settings_toolbar.xml b/src/main/resources/settings/layout/revanced_settings_toolbar.xml
new file mode 100644
index 0000000..86c7177
--- /dev/null
+++ b/src/main/resources/settings/layout/revanced_settings_toolbar.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/settings/layout/revanced_settings_with_toolbar.xml b/src/main/resources/settings/layout/revanced_settings_with_toolbar.xml
new file mode 100644
index 0000000..21ff597
--- /dev/null
+++ b/src/main/resources/settings/layout/revanced_settings_with_toolbar.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/settings/layout/revanced_settings_with_toolbar_layout.xml b/src/main/resources/settings/layout/revanced_settings_with_toolbar_layout.xml
new file mode 100644
index 0000000..31b20d9
--- /dev/null
+++ b/src/main/resources/settings/layout/revanced_settings_with_toolbar_layout.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/settings/xml/revanced_prefs.xml b/src/main/resources/settings/xml/revanced_prefs.xml
new file mode 100644
index 0000000..9562c00
--- /dev/null
+++ b/src/main/resources/settings/xml/revanced_prefs.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/sponsorblock/drawable-xxxhdpi/quantum_ic_skip_next_white_24.png b/src/main/resources/sponsorblock/drawable-xxxhdpi/quantum_ic_skip_next_white_24.png
new file mode 100644
index 0000000..19c4929
Binary files /dev/null and b/src/main/resources/sponsorblock/drawable-xxxhdpi/quantum_ic_skip_next_white_24.png differ
diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_adjust.xml b/src/main/resources/sponsorblock/drawable/ic_sb_adjust.xml
new file mode 100644
index 0000000..76a4b8b
--- /dev/null
+++ b/src/main/resources/sponsorblock/drawable/ic_sb_adjust.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_compare.xml b/src/main/resources/sponsorblock/drawable/ic_sb_compare.xml
new file mode 100644
index 0000000..04cc65e
--- /dev/null
+++ b/src/main/resources/sponsorblock/drawable/ic_sb_compare.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_edit.xml b/src/main/resources/sponsorblock/drawable/ic_sb_edit.xml
new file mode 100644
index 0000000..e93574b
--- /dev/null
+++ b/src/main/resources/sponsorblock/drawable/ic_sb_edit.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_logo.xml b/src/main/resources/sponsorblock/drawable/ic_sb_logo.xml
new file mode 100644
index 0000000..c39b9e0
--- /dev/null
+++ b/src/main/resources/sponsorblock/drawable/ic_sb_logo.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_publish.xml b/src/main/resources/sponsorblock/drawable/ic_sb_publish.xml
new file mode 100644
index 0000000..de4e58d
--- /dev/null
+++ b/src/main/resources/sponsorblock/drawable/ic_sb_publish.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_voting.xml b/src/main/resources/sponsorblock/drawable/ic_sb_voting.xml
new file mode 100644
index 0000000..97db9c9
--- /dev/null
+++ b/src/main/resources/sponsorblock/drawable/ic_sb_voting.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/main/resources/sponsorblock/host/layout/youtube_controls_layout.xml b/src/main/resources/sponsorblock/host/layout/youtube_controls_layout.xml
new file mode 100644
index 0000000..9779626
--- /dev/null
+++ b/src/main/resources/sponsorblock/host/layout/youtube_controls_layout.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/main/resources/sponsorblock/host/values/strings.xml b/src/main/resources/sponsorblock/host/values/strings.xml
new file mode 100644
index 0000000..2f48383
--- /dev/null
+++ b/src/main/resources/sponsorblock/host/values/strings.xml
@@ -0,0 +1,142 @@
+
+
+ Enable SponsorBlock
+ SponsorBlock is a crowd-sourced system for skipping annoying parts in YouTube videos
+ Enable new segment adding
+ Switch this on to enable experimental segment adding (has button visibility issues)
+ What to do with different segments
+ General
+ Show a toast when skipping segment automatically
+ Click to see an example toast
+ Skip count tracking
+ This lets SponsorBlock leaderboard system know how much time people have saved. The extension sends a message to the server each time you skip a segment
+ Adjusting new segment step
+ This is the number of milliseconds you can move when you use the time adjustment buttons while adding new segment
+ Minimum segment duration
+ Segments shorter than the set value (in seconds) will not be skipped or shown in the player
+ Your private user id
+ This should be kept private. This is like a password and should not be shared with anyone. If someone has this, they can impersonate you
+ Import/Export settings
+ This is your entire configuration that is applicable in the desktop extension in JSON. This includes your Private userID, so be sure to share this wisely.
+ Change API URL
+ The address SponsorBlock uses to make calls to the server. <b>Don\'t change this unless you know what you\'re doing.</b>
+ Settings were successfully imported
+ Failed to import settings
+ Failed to export settings
+ Cache segments locally
+ Frequently watched videos (eg. music videos) may store segments in app cache to make skipping segments faster
+ Clear SponsorBlock segments cache
+ Sponsor
+ Paid promotion, paid referrals and direct advertisements
+ Intermission/Intro Animation
+ An interval without actual content. Could be a pause, static frame, repeating animation
+ Endcards/Credits
+ Credits or when the YouTube endcards appear. Not for spoken conclusions
+ Interaction Reminder (Subscribe)
+ When there is a short reminder to like, subscribe, follow or interact with them on any free or paid platform
+ Unpaid/Self Promotion
+ When there is unpaid or self promotion. This includes specific sections about merchandise, donations, or information about who they collaborated with
+ Music: Non-Music Section
+ Only for use in music videos. Skips parts of the video not in official mixes
+ Filler Tangent/Jokes
+ Tangential scenes added only for filler or humor that are not required to understand the main content of the video. This should not include context or background details
+ Skipped a sponsor segment
+ Skipped sponsor
+ Skipped intro
+ Skipped outro
+ Skipped annoying reminder
+ Skipped self promotion
+ Skipped a non-music section
+ Skipped preview
+ Skipped filler
+ Skipped unsubmitted segment
+ Skip automatically
+ Skip automatically once
+ Show a skip button
+ Don\'t do anything
+ Skip segment
+ About
+ This app uses the API from SponsorBlock
+ Tap to learn more, and see downloads for other platforms at: sponsor.ajay.app
+ Integration made by JakubWeg, recoded by oSumAtrIX
+ Tap to skip
+
+ Can\'t submit the segment: %s
+ Unable to submit segments: Status: %d %s
+ Can\'t submit the segment.\nRate Limited (Too many from the same user or IP)
+ Can\'t submit the segment.\n\n%s
+ Can\'t submit the segment.\nAlready exists
+ Segment submitted successfully
+ Submitting segment…
+
+ Unable to vote for segment: Status: %d %s
+ Can\'t vote for segment.\nRate Limited (Too many from the same user or IP)
+ Can\'t vote for segment.\n\n%s
+ Voted successfully
+ Voting for segment…
+ Upvote
+ Downvote
+ Change category
+ There are no segments to vote for
+ Enable voting
+ Switch this on to enable voting.
+
+ Choose the segment category
+ You\'ve disabled this category in the settings, enable it to be able to submit
+ New SponsorBlock segment
+ Set %02d:%02d:%04d as the start or end of a new segment?
+ start
+ end
+ now
+ Time the segment begins at
+ Time the segment ends at
+ Beginning of segment set
+ End of segment set
+ Are the times correct?
+ The segment lasts from %02d:%02d to %02d:%02d (%d minutes %02d seconds)\nIs it ready to submit?
+ Mark two locations on the time bar first
+ Edit timing of segment manually
+ Do you want to edit the timing for the start or end of the segment?
+ Done
+ Invalid time given
+
+ View guidelines
+ Guidelines contain tips and rules about submitting segments
+ There are guidelines
+ It\'s recommended to read the SponsorBlock guidelines before submitting any segment
+ Already read
+ Show me
+
+ Show time without segments
+ This time appears in brackets next to the current time. This shows the total video duration minus any segments.
+ Preview/Recap
+ Recap of previous episodes, or a preview of what\'s coming up later in the current video or future videos in the same series. Clips should not provide additional information.
+ Stats
+ Loading..
+ SponsorBlock is disabled
+ Your username: <b>%s</b>
+ Click to change your username
+ Unable to change username: Status: %d %s
+ Username successfully changed
+ Submissions: <b>%s</b>
+ You\'ve saved people from <b>%s</b> segments.
+ That\'s <b>%s</b> of their lives. Click to see the leaderboard
+ You\'ve skipped <b>%s</b> segments.
+ That\'s <b>%s</b>.
+ minutes
+ Are you looking for changing colors?
+ You can now change a category\'s color by clicking on it above.
+ Choose the category
+ Color changed
+ Color reset
+ Invalid hex code
+ Change
+ Reset
+
+ Segments
+ SB Browser
+
+ API URL changed
+ API URL reset
+ Provided API URL is invalid
+
diff --git a/src/main/resources/sponsorblock/layout/inline_sponsor_overlay.xml b/src/main/resources/sponsorblock/layout/inline_sponsor_overlay.xml
new file mode 100644
index 0000000..6bc670f
--- /dev/null
+++ b/src/main/resources/sponsorblock/layout/inline_sponsor_overlay.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/sponsorblock/layout/new_segment.xml b/src/main/resources/sponsorblock/layout/new_segment.xml
new file mode 100644
index 0000000..155907c
--- /dev/null
+++ b/src/main/resources/sponsorblock/layout/new_segment.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/sponsorblock/layout/skip_sponsor_button.xml b/src/main/resources/sponsorblock/layout/skip_sponsor_button.xml
new file mode 100644
index 0000000..3ceb8c3
--- /dev/null
+++ b/src/main/resources/sponsorblock/layout/skip_sponsor_button.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/swipecontrols/drawable/ic_sc_brightness_auto.xml b/src/main/resources/swipecontrols/drawable/ic_sc_brightness_auto.xml
new file mode 100644
index 0000000..469b335
--- /dev/null
+++ b/src/main/resources/swipecontrols/drawable/ic_sc_brightness_auto.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/main/resources/swipecontrols/drawable/ic_sc_brightness_manual.xml b/src/main/resources/swipecontrols/drawable/ic_sc_brightness_manual.xml
new file mode 100644
index 0000000..2f6c707
--- /dev/null
+++ b/src/main/resources/swipecontrols/drawable/ic_sc_brightness_manual.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/main/resources/swipecontrols/drawable/ic_sc_volume_mute.xml b/src/main/resources/swipecontrols/drawable/ic_sc_volume_mute.xml
new file mode 100644
index 0000000..73dc595
--- /dev/null
+++ b/src/main/resources/swipecontrols/drawable/ic_sc_volume_mute.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/main/resources/swipecontrols/drawable/ic_sc_volume_normal.xml b/src/main/resources/swipecontrols/drawable/ic_sc_volume_normal.xml
new file mode 100644
index 0000000..30dff4b
--- /dev/null
+++ b/src/main/resources/swipecontrols/drawable/ic_sc_volume_normal.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/main/resources/theme/values-night-v31/styles.xml b/src/main/resources/theme/values-night-v31/styles.xml
new file mode 100644
index 0000000..53da895
--- /dev/null
+++ b/src/main/resources/theme/values-night-v31/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/src/main/resources/twitch/settings/xml/revanced_prefs.xml b/src/main/resources/twitch/settings/xml/revanced_prefs.xml
new file mode 100644
index 0000000..14e1c85
--- /dev/null
+++ b/src/main/resources/twitch/settings/xml/revanced_prefs.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file