mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-26 10:54:07 +01:00
Compare commits
54 Commits
v4.14.0-de
...
v4.16.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9131e387b5 | ||
|
|
1928e89085 | ||
|
|
35973c721a | ||
|
|
31b18fec39 | ||
|
|
db15b68dc8 | ||
|
|
82d44f691d | ||
|
|
fee2218303 | ||
|
|
1f0b4cdcb4 | ||
|
|
1fd30c1b44 | ||
|
|
fa94ddd510 | ||
|
|
94cf815e4a | ||
|
|
8a3b0610b4 | ||
|
|
b920355d9c | ||
|
|
8b49012130 | ||
|
|
4c7b018878 | ||
|
|
5ddd957313 | ||
|
|
bb0dcbe83d | ||
|
|
163736fb26 | ||
|
|
0c6db43bde | ||
|
|
317e9a80eb | ||
|
|
464e6a3673 | ||
|
|
2e9142eda4 | ||
|
|
b4c6d0a7d2 | ||
|
|
c0ee85e12a | ||
|
|
2d326072e2 | ||
|
|
586770aa3a | ||
|
|
9f314c2425 | ||
|
|
c8b3456738 | ||
|
|
e8cb6ee028 | ||
|
|
3e796eb7c2 | ||
|
|
303d2de81d | ||
|
|
0ab7344295 | ||
|
|
ff8fe46685 | ||
|
|
a104eeaf68 | ||
|
|
18b09168cc | ||
|
|
f7209f0a53 | ||
|
|
1fb3fc4857 | ||
|
|
e03c14cc01 | ||
|
|
bed29d00dc | ||
|
|
d36982e245 | ||
|
|
13031f0534 | ||
|
|
b5b6ef5d6f | ||
|
|
5b1e07d861 | ||
|
|
e3220cc10a | ||
|
|
02db9378ea | ||
|
|
f83e314dff | ||
|
|
d5e383b78a | ||
|
|
395e18d830 | ||
|
|
887684e7c7 | ||
|
|
2f7d751f9f | ||
|
|
4886a95713 | ||
|
|
58719239cf | ||
|
|
fcb68cc65e | ||
|
|
16217f012e |
186
CHANGELOG.md
186
CHANGELOG.md
@@ -1,3 +1,189 @@
|
||||
# [4.16.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v4.16.0-dev.6...v4.16.0-dev.7) (2024-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - SponsorBlock:** Fade out SB buttons without overlapping other buttons ([#3719](https://github.com/ReVanced/revanced-patches/issues/3719)) ([bf96108](https://github.com/ReVanced/revanced-patches/commit/bf9610894f0a9f9e751e2eed5b825c5d327a722c))
|
||||
|
||||
# [4.16.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v4.16.0-dev.5...v4.16.0-dev.6) (2024-09-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Add `Hide save music`, `Hide stickers` ([#3710](https://github.com/ReVanced/revanced-patches/issues/3710)) ([8c99321](https://github.com/ReVanced/revanced-patches/commit/8c99321df4db696156330fc90dd547c1345d880e))
|
||||
|
||||
# [4.16.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v4.16.0-dev.4...v4.16.0-dev.5) (2024-09-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Disable precise seeking gesture:** Hide "pull up" label that shows up when swiping ([#3668](https://github.com/ReVanced/revanced-patches/issues/3668)) ([3fa8af9](https://github.com/ReVanced/revanced-patches/commit/3fa8af9fe534b59ad093c36f1927f56f549a330d))
|
||||
|
||||
# [4.16.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v4.16.0-dev.3...v4.16.0-dev.4) (2024-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Soundcloud:** Support latest versions ([#3702](https://github.com/ReVanced/revanced-patches/issues/3702)) ([099ac5e](https://github.com/ReVanced/revanced-patches/commit/099ac5ea2cf55633a7c6a7e6f8e963599bcd5784))
|
||||
|
||||
# [4.16.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v4.16.0-dev.2...v4.16.0-dev.3) (2024-09-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Google Photos:** Restore hidden 'Back up while charging' toggle ([#3678](https://github.com/ReVanced/revanced-patches/issues/3678)) ([f9e19ce](https://github.com/ReVanced/revanced-patches/commit/f9e19ce6e9185fdf31b2b0d5f2934f6e8a544b8e))
|
||||
|
||||
# [4.16.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.16.0-dev.1...v4.16.0-dev.2) (2024-09-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Add patch option to hide Shorts from app launcher widget Beta ([#3707](https://github.com/ReVanced/revanced-patches/issues/3707)) ([838f183](https://github.com/ReVanced/revanced-patches/commit/838f1834a5df547ce2c3217b874c0594b6878a67))
|
||||
|
||||
# [4.16.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.15.1-dev.2...v4.16.0-dev.1) (2024-09-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Add patch option to hide Shorts app shortcut (long press app icon) ([#3699](https://github.com/ReVanced/revanced-patches/issues/3699)) ([0d4e1f5](https://github.com/ReVanced/revanced-patches/commit/0d4e1f5d03cf3dcc06fd41165e26a1ce901b976b))
|
||||
|
||||
## [4.15.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.15.1-dev.1...v4.15.1-dev.2) (2024-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Show video chapter titles without clipping when overlay buttons are enabled ([#3674](https://github.com/ReVanced/revanced-patches/issues/3674)) ([4b88c31](https://github.com/ReVanced/revanced-patches/commit/4b88c316ed90c56e83e2aee266561833b36fc37d))
|
||||
|
||||
## [4.15.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.15.0...v4.15.1-dev.1) (2024-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Twitter - Open links with app chooser:** Fix incorrect version in compatibility list ([#3683](https://github.com/ReVanced/revanced-patches/issues/3683)) ([adafe85](https://github.com/ReVanced/revanced-patches/commit/adafe85d77f6a0031a5523b9b7da69475959d78d))
|
||||
|
||||
# [4.15.0](https://github.com/ReVanced/revanced-patches/compare/v4.14.1...v4.15.0) (2024-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **TikTok - Playback speed:** Prevent crash by fixing invalid patch ([82d53cb](https://github.com/ReVanced/revanced-patches/commit/82d53cbc3bbfa585ba4337fdfaec9f0f19c802e6))
|
||||
* **TikTok - Settings:** Prevent crash by fixing invalid patch ([8074032](https://github.com/ReVanced/revanced-patches/commit/8074032fad3eff1c03296a882d2e2820da99b592))
|
||||
* **Twitter - Open links with app chooser:** Constrain patch to last working version `10.48.0-release` ([b9955d5](https://github.com/ReVanced/revanced-patches/commit/b9955d5ff6e456593b01f0f25d80ff660d02082a))
|
||||
* **YouTube - Spoof video streams:** Change default client type to Android VR ([74c8637](https://github.com/ReVanced/revanced-patches/commit/74c8637943347078955f51325bc6af92a35d4463))
|
||||
* **YouTube - Spoof video streams:** Change default client type to Android VR ([#3672](https://github.com/ReVanced/revanced-patches/issues/3672)) ([a3306f6](https://github.com/ReVanced/revanced-patches/commit/a3306f6717a09b734354f00363a96abad0ae14e7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **TikTok:** Bump patches to support the latest version 36.5.4 ([e5dcb72](https://github.com/ReVanced/revanced-patches/commit/e5dcb72597092fb32003f11fdf6f861ede4e7ff3))
|
||||
|
||||
# [4.15.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.2-dev.2...v4.15.0-dev.1) (2024-09-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **TikTok:** Bump patches to support the latest version 36.5.4 ([e5dcb72](https://github.com/ReVanced/revanced-patches/commit/e5dcb72597092fb32003f11fdf6f861ede4e7ff3))
|
||||
|
||||
## [4.14.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.14.2-dev.1...v4.14.2-dev.2) (2024-09-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof video streams:** Change default client type to Android VR ([74c8637](https://github.com/ReVanced/revanced-patches/commit/74c8637943347078955f51325bc6af92a35d4463))
|
||||
* **YouTube - Spoof video streams:** Change default client type to Android VR ([#3672](https://github.com/ReVanced/revanced-patches/issues/3672)) ([a3306f6](https://github.com/ReVanced/revanced-patches/commit/a3306f6717a09b734354f00363a96abad0ae14e7))
|
||||
|
||||
## [4.14.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.1...v4.14.2-dev.1) (2024-09-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **TikTok - Playback speed:** Prevent crash by fixing invalid patch ([82d53cb](https://github.com/ReVanced/revanced-patches/commit/82d53cbc3bbfa585ba4337fdfaec9f0f19c802e6))
|
||||
* **TikTok - Settings:** Prevent crash by fixing invalid patch ([8074032](https://github.com/ReVanced/revanced-patches/commit/8074032fad3eff1c03296a882d2e2820da99b592))
|
||||
* **Twitter - Open links with app chooser:** Constrain patch to last working version `10.48.0-release` ([b9955d5](https://github.com/ReVanced/revanced-patches/commit/b9955d5ff6e456593b01f0f25d80ff660d02082a))
|
||||
|
||||
## [4.14.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.0...v4.14.1) (2024-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Check environment:** Only use fields available since Android 8 ([#3655](https://github.com/ReVanced/revanced-patches/issues/3655)) ([4413533](https://github.com/ReVanced/revanced-patches/commit/441353306572340131030e1c4fee1ab6acb63cd9))
|
||||
|
||||
## [4.14.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.14.0...v4.14.1-dev.1) (2024-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Check environment:** Only use fields available since Android 8 ([#3655](https://github.com/ReVanced/revanced-patches/issues/3655)) ([4413533](https://github.com/ReVanced/revanced-patches/commit/441353306572340131030e1c4fee1ab6acb63cd9))
|
||||
|
||||
# [4.14.0](https://github.com/ReVanced/revanced-patches/compare/v4.13.3...v4.14.0) (2024-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Pixiv - Hide ads:** Fix for latest version ([#3616](https://github.com/ReVanced/revanced-patches/issues/3616)) ([98956e8](https://github.com/ReVanced/revanced-patches/commit/98956e8f1a41347bb435720bbf984969469a7110))
|
||||
* **Soundcloud - Hide ads:** Support latest version ([#3628](https://github.com/ReVanced/revanced-patches/issues/3628)) ([66e7e33](https://github.com/ReVanced/revanced-patches/commit/66e7e33efce9b702fdfcc2b9803e9da8491c1f08))
|
||||
* **SwissID:** Rename `Remove Google Play Integrity Integrity check` to `Remove Google Play Integrity check` ([#3558](https://github.com/ReVanced/revanced-patches/issues/3558)) ([0f5a771](https://github.com/ReVanced/revanced-patches/commit/0f5a771a5cff5684b4a8fd317f4938fe2cf3cbbe))
|
||||
* **YouTube - ReturnYouTubeDislike:** Show estimated like count for videos with hidden likes ([#3601](https://github.com/ReVanced/revanced-patches/issues/3601)) ([005be82](https://github.com/ReVanced/revanced-patches/commit/005be82d71b2a42387b1b57035930b20f4663794))
|
||||
* **YouTube - SponsorBlock:** Add summary text to 'view my segments' button ([df80b9f](https://github.com/ReVanced/revanced-patches/commit/df80b9f92f0d981b9a40b7756d74f8ccc3dcb1e9))
|
||||
* **YouTube - SponsorBlock:** Handle if the user enters an invalid number into any SB settings ([37b3dd1](https://github.com/ReVanced/revanced-patches/commit/37b3dd1e789f8bb16fa1b9dd582e39c89dbe730c))
|
||||
* **YouTube:** Fix issues related to playback by replace streaming data ([#3582](https://github.com/ReVanced/revanced-patches/issues/3582)) ([dfa94d7](https://github.com/ReVanced/revanced-patches/commit/dfa94d70f65150d6ef24ea6378b8e6a317055186))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `Change data directory location` patch ([#3602](https://github.com/ReVanced/revanced-patches/issues/3602)) ([5998029](https://github.com/ReVanced/revanced-patches/commit/59980292809cc0626bf49a160eeb05a1523c4eda))
|
||||
* Add `Check environment` patch ([#3610](https://github.com/ReVanced/revanced-patches/issues/3610)) ([fbcbdaf](https://github.com/ReVanced/revanced-patches/commit/fbcbdafa4938a35b5fdec46aae7b250a84b9c139))
|
||||
* **Duolingo:** Add `Disable ads` and `Enable debug menu` patch ([#3422](https://github.com/ReVanced/revanced-patches/issues/3422)) ([d0a8599](https://github.com/ReVanced/revanced-patches/commit/d0a8599f76ce653e5d7c98069ad3c58b9ab9c5eb))
|
||||
* **Sync for Reddit:** Add `Fix /user/ endpoint` patch ([46d11f3](https://github.com/ReVanced/revanced-patches/commit/46d11f3530fcdae9ed08b7e93aac235638a92dff))
|
||||
* **Sync for Reddit:** Rename patch to `Use /user/ endpoint` ([98ead49](https://github.com/ReVanced/revanced-patches/commit/98ead493380932cb105530f4ba992673fd364d82))
|
||||
* **YouTube - Hide Shorts components:** Hide 'Use this sound' button ([#3647](https://github.com/ReVanced/revanced-patches/issues/3647)) ([33fc090](https://github.com/ReVanced/revanced-patches/commit/33fc09061431d4aa457d743c09a0de31ec566df1))
|
||||
* **YouTube - Keyword filter:** Add syntax to match whole keywords and not substrings ([#3592](https://github.com/ReVanced/revanced-patches/issues/3592)) ([f5fb351](https://github.com/ReVanced/revanced-patches/commit/f5fb3512cfafe214ba6a6d25ba0825ae1884a0ff))
|
||||
* **YouTube - Spoof client:** Allow forcing AVC codec with iOS ([#3570](https://github.com/ReVanced/revanced-patches/issues/3570)) ([1a49d1f](https://github.com/ReVanced/revanced-patches/commit/1a49d1f3c2a343d05d0abc07c143add486246fd0))
|
||||
* **YouTube Music:** Make working patches compatible with latest versions ([#3556](https://github.com/ReVanced/revanced-patches/issues/3556)) ([12f6f19](https://github.com/ReVanced/revanced-patches/commit/12f6f1966ad04631451940f7b64d785c3ef481a0))
|
||||
* **YouTube:** Add donation link to settings about screen ([#3626](https://github.com/ReVanced/revanced-patches/issues/3626)) ([0684ab5](https://github.com/ReVanced/revanced-patches/commit/0684ab5f183631de5720352049cfd293daa58eb0))
|
||||
|
||||
# [4.14.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.14...v4.14.0-dev.15) (2024-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Fix issues related to playback by replace streaming data ([#3582](https://github.com/ReVanced/revanced-patches/issues/3582)) ([dfa94d7](https://github.com/ReVanced/revanced-patches/commit/dfa94d70f65150d6ef24ea6378b8e6a317055186))
|
||||
|
||||
# [4.14.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.13...v4.14.0-dev.14) (2024-09-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube Music:** Make working patches compatible with latest versions ([#3556](https://github.com/ReVanced/revanced-patches/issues/3556)) ([12f6f19](https://github.com/ReVanced/revanced-patches/commit/12f6f1966ad04631451940f7b64d785c3ef481a0))
|
||||
|
||||
# [4.14.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.12...v4.14.0-dev.13) (2024-09-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide 'Use this sound' button ([#3647](https://github.com/ReVanced/revanced-patches/issues/3647)) ([33fc090](https://github.com/ReVanced/revanced-patches/commit/33fc09061431d4aa457d743c09a0de31ec566df1))
|
||||
|
||||
# [4.14.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.11...v4.14.0-dev.12) (2024-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Soundcloud - Hide ads:** Support latest version ([#3628](https://github.com/ReVanced/revanced-patches/issues/3628)) ([66e7e33](https://github.com/ReVanced/revanced-patches/commit/66e7e33efce9b702fdfcc2b9803e9da8491c1f08))
|
||||
|
||||
# [4.14.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.10...v4.14.0-dev.11) (2024-09-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Sync for Reddit:** Rename patch to `Use /user/ endpoint` ([98ead49](https://github.com/ReVanced/revanced-patches/commit/98ead493380932cb105530f4ba992673fd364d82))
|
||||
|
||||
# [4.14.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.9...v4.14.0-dev.10) (2024-09-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Sync for Reddit:** Add `Fix /user/ endpoint` patch ([46d11f3](https://github.com/ReVanced/revanced-patches/commit/46d11f3530fcdae9ed08b7e93aac235638a92dff))
|
||||
|
||||
# [4.14.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.8...v4.14.0-dev.9) (2024-09-09)
|
||||
|
||||
|
||||
|
||||
@@ -321,6 +321,12 @@ public final class app/revanced/patches/googlephotos/misc/integrations/Integrati
|
||||
public static final field INSTANCE Lapp/revanced/patches/googlephotos/misc/integrations/IntegrationsPatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/googlephotos/preferences/RestoreHiddenBackUpWhileChargingTogglePatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/googlephotos/preferences/RestoreHiddenBackUpWhileChargingTogglePatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/googlerecorder/restrictions/RemoveDeviceRestrictions : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/googlerecorder/restrictions/RemoveDeviceRestrictions;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
@@ -828,6 +834,12 @@ public final class app/revanced/patches/reddit/customclients/syncforreddit/fix/s
|
||||
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/fix/slink/FixSLinksPatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/reddit/customclients/syncforreddit/fix/user/UseUserEndpointPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/fix/user/UseUserEndpointPatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/reddit/customclients/syncforreddit/misc/integrations/IntegrationsPatch : app/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/misc/integrations/IntegrationsPatch;
|
||||
}
|
||||
@@ -1910,6 +1922,12 @@ public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignature
|
||||
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch;
|
||||
public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object;
|
||||
@@ -1988,13 +2006,19 @@ public final class app/revanced/patches/youtube/misc/playercontrols/BottomContro
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch;
|
||||
public static field showPlayerControlsFingerprintResult Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
public final fun getShowPlayerControlsFingerprintResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
|
||||
public final fun initializeBottomControl (Ljava/lang/String;)V
|
||||
public final fun initializeControl (Ljava/lang/String;)V
|
||||
public final fun injectVisibilityCheckCall (Ljava/lang/String;)V
|
||||
public final fun setShowPlayerControlsFingerprintResult (Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch : app/revanced/patcher/patch/ResourcePatch, java/io/Closeable {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch;
|
||||
public final fun addBottomControls (Ljava/lang/String;)V
|
||||
public fun close ()V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/playeroverlay/PlayerOverlaysHookPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
@@ -2162,6 +2186,8 @@ public final class app/revanced/util/BytecodeUtilsKt {
|
||||
public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
|
||||
public static final fun indexOfFirstWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
||||
public static final fun indexOfFirstWideLiteralInstructionValueOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
||||
public static final fun indexOfFirstWideLiteralInstructionValueReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
||||
public static final fun indexOfFirstWideLiteralInstructionValueReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
||||
public static final fun indexOfIdResource (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
|
||||
public static final fun indexOfIdResourceOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
|
||||
public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V
|
||||
@@ -2189,6 +2215,7 @@ public final class app/revanced/util/ResourceUtilsKt {
|
||||
public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/DomFileEditor;Lapp/revanced/patcher/util/DomFileEditor;)Ljava/lang/AutoCloseable;
|
||||
public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
|
||||
public static final fun forEachChildElement (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
|
||||
public static final fun insertFirst (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)V
|
||||
public static final fun iterateXmlNodeChildren (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 4.14.0-dev.9
|
||||
version = 4.16.0-dev.7
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package app.revanced.patches.googlephotos.preferences
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.googlephotos.preferences.fingerprints.BackupPreferencesFingerprint
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
name = "Restore hidden 'Back up while charging' toggle",
|
||||
description = "Restores a hidden toggle to only run backups when the device is charging.",
|
||||
compatiblePackages = [CompatiblePackage("com.google.android.apps.photos")],
|
||||
)
|
||||
@Suppress("unused")
|
||||
object RestoreHiddenBackUpWhileChargingTogglePatch : BytecodePatch(
|
||||
setOf(BackupPreferencesFingerprint),
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
// Patches 'backup_prefs_had_backup_only_when_charging_enabled' to always be true.
|
||||
BackupPreferencesFingerprint.result?.let {
|
||||
val chargingPrefStringIndex = it.scanResult.stringsScanResult!!.matches.first().index
|
||||
it.mutableMethod.apply {
|
||||
// Get the register of move-result.
|
||||
val resultRegister = getInstruction<OneRegisterInstruction>(chargingPrefStringIndex + 2).registerA
|
||||
// Insert const after move-result to override register as true.
|
||||
addInstruction(chargingPrefStringIndex + 3, "const/4 v$resultRegister, 0x1")
|
||||
}
|
||||
} ?: throw Exception("BackupPreferencesFingerprint result not found")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.revanced.patches.googlephotos.preferences.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object BackupPreferencesFingerprint : MethodFingerprint(
|
||||
returnType = "Lcom/google/android/apps/photos/backup/data/BackupPreferences;",
|
||||
strings = listOf(
|
||||
"backup_prefs_had_backup_only_when_charging_enabled",
|
||||
),
|
||||
)
|
||||
@@ -13,16 +13,7 @@ import app.revanced.util.exception
|
||||
name = "Hide video ads",
|
||||
description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
],
|
||||
)
|
||||
@Suppress("unused")
|
||||
|
||||
@@ -12,16 +12,7 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||
@Patch(
|
||||
description = "Adds more audio codec options. The new audio codecs usually result in better audio quality.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
]
|
||||
)
|
||||
@Deprecated("This patch is no longer needed as the feature is now enabled by default.")
|
||||
|
||||
@@ -12,16 +12,7 @@ import app.revanced.util.exception
|
||||
name = "Enable exclusive audio playback",
|
||||
description = "Enables the option to play audio without video.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
]
|
||||
)
|
||||
@Suppress("unused")
|
||||
|
||||
@@ -14,16 +14,7 @@ import app.revanced.patches.music.interaction.permanentrepeat.fingerprints.Repea
|
||||
name = "Permanent repeat",
|
||||
description = "Permanently remember your repeating preference even if the playlist ends or another track is played.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
],
|
||||
use = false
|
||||
)
|
||||
|
||||
@@ -14,16 +14,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
name = "Hide category bar",
|
||||
description = "Hides the category bar at the top of the homepage.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
],
|
||||
use = false,
|
||||
)
|
||||
|
||||
@@ -17,16 +17,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
name = "Hide 'Get Music Premium' label",
|
||||
description = "Hides the \"Get Music Premium\" label from the account menu and settings.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
]
|
||||
)
|
||||
@Suppress("unused")
|
||||
|
||||
@@ -23,16 +23,7 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
name = "Remove upgrade button",
|
||||
description = "Removes the upgrade tab from the pivot bar.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
]
|
||||
)
|
||||
@Suppress("unused")
|
||||
|
||||
@@ -13,16 +13,7 @@ import app.revanced.patches.music.misc.androidauto.fingerprints.CheckCertificate
|
||||
name = "Bypass certificate checks",
|
||||
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
]
|
||||
)
|
||||
@Suppress("unused")
|
||||
|
||||
@@ -14,16 +14,7 @@ import app.revanced.util.resultOrThrow
|
||||
name = "Remove background playback restrictions",
|
||||
description = "Removes restrictions on background playback, including playing kids videos in the background.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
]
|
||||
)
|
||||
CompatiblePackage("com.google.android.apps.youtube.music")
|
||||
]
|
||||
)
|
||||
@Suppress("unused")
|
||||
|
||||
@@ -23,16 +23,7 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
|
||||
integrationsPatchDependency = IntegrationsPatch::class,
|
||||
gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
|
||||
compatiblePackages = setOf(
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
setOf(
|
||||
"6.45.54",
|
||||
"6.51.53",
|
||||
"7.01.53",
|
||||
"7.02.52",
|
||||
"7.03.52",
|
||||
),
|
||||
),
|
||||
CompatiblePackage("com.google.android.apps.youtube.music"),
|
||||
),
|
||||
fingerprints = setOf(
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints.*
|
||||
import app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints.OAuthFriendRequestFingerprint
|
||||
import app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints.OAuthSubredditInfoRequestHelperFingerprint
|
||||
import app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints.OAuthUnfriendRequestFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
|
||||
@Patch(
|
||||
name = "Use /user/ endpoint",
|
||||
description = "Replaces the deprecated endpoint for viewing user profiles /u with /user, that used to fix a bug.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage("com.laurencedawson.reddit_sync"),
|
||||
CompatiblePackage("com.laurencedawson.reddit_sync.pro"),
|
||||
CompatiblePackage("com.laurencedawson.reddit_sync.dev"),
|
||||
],
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
object UseUserEndpointPatch : BytecodePatch(
|
||||
fingerprints = setOf(
|
||||
OAuthFriendRequestFingerprint,
|
||||
OAuthSubredditInfoRequestConstructorFingerprint,
|
||||
OAuthSubredditInfoRequestHelperFingerprint,
|
||||
OAuthUnfriendRequestFingerprint,
|
||||
OAuthUserIdRequestFingerprint,
|
||||
OAuthUserInfoRequestFingerprint,
|
||||
),
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
arrayOf(
|
||||
OAuthFriendRequestFingerprint,
|
||||
OAuthSubredditInfoRequestConstructorFingerprint,
|
||||
OAuthSubredditInfoRequestHelperFingerprint,
|
||||
OAuthUnfriendRequestFingerprint,
|
||||
OAuthUserIdRequestFingerprint,
|
||||
OAuthUserInfoRequestFingerprint,
|
||||
).map(MethodFingerprint::resultOrThrow).map {
|
||||
it.scanResult.stringsScanResult!!.matches.first().index to it.mutableMethod
|
||||
}.forEach { (userPathStringIndex, method) ->
|
||||
val userPathStringInstruction = method.getInstruction<OneRegisterInstruction>(userPathStringIndex)
|
||||
val userPathStringRegister = userPathStringInstruction.registerA
|
||||
val fixedUserPathString = userPathStringInstruction.getReference<StringReference>()!!.string.replace("u/", "user/")
|
||||
|
||||
method.replaceInstruction(
|
||||
userPathStringIndex,
|
||||
"const-string v$userPathStringRegister, \"${fixedUserPathString}\"",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal abstract class BaseUserEndpointFingerprint(source: String, accessFlags: Int? = null) :
|
||||
MethodFingerprint(
|
||||
accessFlags = accessFlags,
|
||||
strings = listOf("u/"),
|
||||
customFingerprint = { _, classDef -> classDef.sourceFile == source },
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
internal object OAuthFriendRequestFingerprint : BaseUserEndpointFingerprint("OAuthFriendRequest.java")
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object OAuthSubredditInfoRequestConstructorFingerprint :
|
||||
BaseUserEndpointFingerprint(
|
||||
"OAuthSubredditInfoRequest.java",
|
||||
AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object OAuthSubredditInfoRequestHelperFingerprint :
|
||||
BaseUserEndpointFingerprint(
|
||||
"OAuthSubredditInfoRequest.java",
|
||||
AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
internal object OAuthUnfriendRequestFingerprint : BaseUserEndpointFingerprint("OAuthUnfriendRequest.java")
|
||||
@@ -0,0 +1,3 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
internal object OAuthUserIdRequestFingerprint : BaseUserEndpointFingerprint("OAuthUserIdRequest.java")
|
||||
@@ -0,0 +1,3 @@
|
||||
package app.revanced.patches.reddit.customclients.syncforreddit.fix.user.fingerprints
|
||||
|
||||
internal object OAuthUserInfoRequestFingerprint : BaseUserEndpointFingerprint("OAuthUserInfoRequest.java")
|
||||
@@ -65,12 +65,8 @@ abstract class BaseCheckEnvironmentPatch(
|
||||
"PATCH_ID" to ID.encodedAndHashed,
|
||||
"PATCH_MANUFACTURER" to MANUFACTURER.encodedAndHashed,
|
||||
"PATCH_MODEL" to MODEL.encodedAndHashed,
|
||||
"PATCH_ODM_SKU" to ODM_SKU.encodedAndHashed,
|
||||
"PATCH_PRODUCT" to PRODUCT.encodedAndHashed,
|
||||
"PATCH_RADIO" to RADIO.encodedAndHashed,
|
||||
"PATCH_SKU" to SKU.encodedAndHashed,
|
||||
"PATCH_SOC_MANUFACTURER" to SOC_MANUFACTURER.encodedAndHashed,
|
||||
"PATCH_SOC_MODEL" to SOC_MODEL.encodedAndHashed,
|
||||
"PATCH_TAGS" to TAGS.encodedAndHashed,
|
||||
"PATCH_TYPE" to TYPE.encodedAndHashed,
|
||||
"PATCH_USER" to USER.encodedAndHashed,
|
||||
@@ -98,8 +94,10 @@ abstract class BaseCheckEnvironmentPatch(
|
||||
private val String.encodedAndHashed
|
||||
get() = MutableStringEncodedValue(
|
||||
ImmutableStringEncodedValue(
|
||||
Base64.encode(MessageDigest.getInstance("SHA-1")
|
||||
.digest(this.toByteArray(StandardCharsets.UTF_8))),
|
||||
Base64.encode(
|
||||
MessageDigest.getInstance("SHA-1")
|
||||
.digest(this.toByteArray(StandardCharsets.UTF_8)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.util.getNode
|
||||
import app.revanced.util.insertFirst
|
||||
import org.w3c.dom.Node
|
||||
import java.io.Closeable
|
||||
|
||||
@@ -47,11 +48,7 @@ abstract class BaseSettingsResourcePatch(
|
||||
// It may be necessary to ask for the desired resourceValue in the future.
|
||||
AddResourcesPatch("values", resource)
|
||||
}.let { preferenceNode ->
|
||||
if (prepend && firstChild != null) {
|
||||
insertBefore(preferenceNode, firstChild)
|
||||
} else {
|
||||
appendChild(preferenceNode)
|
||||
}
|
||||
insertFirst(preferenceNode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ object HideAdsPatch : BytecodePatch(
|
||||
|
||||
// Prevent verification of an HTTP header containing the user's current plan, which would contradict the previous patch.
|
||||
InterceptFingerprint.resultOrThrow().let { result ->
|
||||
val conditionIndex = result.scanResult.patternScanResult!!.endIndex
|
||||
val conditionIndex = result.scanResult.patternScanResult!!.endIndex + 1
|
||||
result.mutableMethod.addInstruction(
|
||||
conditionIndex,
|
||||
"return-object p1",
|
||||
|
||||
@@ -9,14 +9,9 @@ internal object InterceptFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC.value,
|
||||
parameters = listOf("L"),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT
|
||||
),
|
||||
strings = listOf("SC-Mob-UserPlan", "Configuration"),
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.sourceFile == "ApiUserPlanInterceptor.java"
|
||||
},
|
||||
)
|
||||
|
||||
@@ -8,7 +8,4 @@ internal object UserConsumerPlanConstructorFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
parameters = listOf("Ljava/lang/String;", "Z", "Ljava/lang/String;", "Ljava/util/List;", "Ljava/lang/String;", "Ljava/lang/String;"),
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.sourceFile == "UserConsumerPlan.kt"
|
||||
},
|
||||
)
|
||||
@@ -7,6 +7,7 @@ internal object CreateTrackingApiFingerprint : MethodFingerprint(
|
||||
returnType = "L",
|
||||
accessFlags = AccessFlags.PUBLIC.value,
|
||||
customFingerprint = { methodDef, classDef ->
|
||||
classDef.sourceFile == "DefaultTrackingApiFactory.kt" && methodDef.name == "create"
|
||||
methodDef.name == "create"
|
||||
},
|
||||
strings = listOf("backend", "boogaloo")
|
||||
)
|
||||
|
||||
@@ -15,7 +15,5 @@ internal object DownloadOperationsHeaderVerificationFingerprint : MethodFingerpr
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_STRING
|
||||
),
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.sourceFile == "DownloadOperations.kt"
|
||||
}
|
||||
strings = listOf("X-SC-Mime-Type", "X-SC-Preset", "X-SC-Quality")
|
||||
)
|
||||
@@ -14,7 +14,4 @@ internal object DownloadOperationsURLBuilderFingerprint : MethodFingerprint(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.FILLED_NEW_ARRAY
|
||||
),
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.sourceFile == "DownloadOperations.kt"
|
||||
}
|
||||
)
|
||||
@@ -3,12 +3,16 @@ package app.revanced.patches.soundcloud.shared.fingerprints
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
|
||||
internal object FeatureConstructorFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
parameters = listOf("Ljava/lang/String;", "Z", "Ljava/util/List;"),
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.sourceFile == "Feature.kt"
|
||||
},
|
||||
opcodes = listOf(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.INVOKE_VIRTUAL
|
||||
)
|
||||
)
|
||||
@@ -17,16 +17,16 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
@Patch(
|
||||
name = "Feed filter",
|
||||
description = "Removes ads, livestreams, stories, image videos " +
|
||||
"and videos with a specific amount of views or likes from the feed.",
|
||||
"and videos with a specific amount of views or likes from the feed.",
|
||||
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
|
||||
]
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
|
||||
],
|
||||
)
|
||||
@Suppress("unused")
|
||||
object FeedFilterPatch : BytecodePatch(
|
||||
setOf(FeedApiServiceLIZFingerprint, SettingsStatusLoadFingerprint)
|
||||
setOf(FeedApiServiceLIZFingerprint, SettingsStatusLoadFingerprint),
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
FeedApiServiceLIZFingerprint.result?.mutableMethod?.apply {
|
||||
@@ -36,13 +36,13 @@ object FeedFilterPatch : BytecodePatch(
|
||||
addInstruction(
|
||||
returnFeedItemInstruction.location.index,
|
||||
"invoke-static { v$feedItemsRegister }, " +
|
||||
"Lapp/revanced/integrations/tiktok/feedfilter/FeedItemsFilter;->filter(Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;)V"
|
||||
"Lapp/revanced/integrations/tiktok/feedfilter/FeedItemsFilter;->filter(Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;)V",
|
||||
)
|
||||
} ?: throw FeedApiServiceLIZFingerprint.exception
|
||||
|
||||
SettingsStatusLoadFingerprint.result?.mutableMethod?.addInstruction(
|
||||
0,
|
||||
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableFeedFilter()V"
|
||||
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableFeedFilter()V",
|
||||
) ?: throw SettingsStatusLoadFingerprint.exception
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.tiktok.interaction.cleardisplay.fingerprints.OnClearDisplayEventFingerprint
|
||||
import app.revanced.patches.tiktok.interaction.cleardisplay.fingerprints.OnRenderFirstFrameFingerprint
|
||||
import app.revanced.patches.tiktok.shared.fingerprints.OnRenderFirstFrameFingerprint
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
@@ -19,16 +19,16 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c
|
||||
name = "Remember clear display",
|
||||
description = "Remembers the clear display configurations in between videos.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
|
||||
]
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
|
||||
],
|
||||
)
|
||||
@Suppress("unused")
|
||||
object RememberClearDisplayPatch : BytecodePatch(
|
||||
setOf(
|
||||
OnClearDisplayEventFingerprint,
|
||||
OnRenderFirstFrameFingerprint
|
||||
)
|
||||
OnRenderFirstFrameFingerprint,
|
||||
),
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
OnClearDisplayEventFingerprint.result?.mutableMethod?.let {
|
||||
@@ -40,7 +40,7 @@ object RememberClearDisplayPatch : BytecodePatch(
|
||||
it.addInstructions(
|
||||
isEnabledIndex,
|
||||
"invoke-static { v$isEnabledRegister }, " +
|
||||
"Lapp/revanced/integrations/tiktok/cleardisplay/RememberClearDisplayPatch;->rememberClearDisplayState(Z)V"
|
||||
"Lapp/revanced/integrations/tiktok/cleardisplay/RememberClearDisplayPatch;->rememberClearDisplayState(Z)V",
|
||||
)
|
||||
|
||||
// endregion
|
||||
@@ -54,22 +54,25 @@ object RememberClearDisplayPatch : BytecodePatch(
|
||||
"""
|
||||
# Create a new clearDisplayEvent and post it to the EventBus (https://github.com/greenrobot/EventBus)
|
||||
|
||||
# The state of clear display.
|
||||
invoke-static { }, Lapp/revanced/integrations/tiktok/cleardisplay/RememberClearDisplayPatch;->getClearDisplayState()Z
|
||||
move-result v3
|
||||
if-eqz v3, :clear_display_disabled
|
||||
|
||||
# Clear display type such as 0 = LONG_PRESS, 1 = SCREEN_RECORD etc.
|
||||
const/4 v1, 0x0
|
||||
|
||||
# Enter method (Such as "pinch", "swipe_exit", or an empty string (unknown, what it means)).
|
||||
const-string v2, ""
|
||||
|
||||
# Name of the clear display type which is equivalent to the clear display type.
|
||||
const-string v2, "long_press"
|
||||
const-string v3, "long_press"
|
||||
|
||||
# The state of clear display.
|
||||
invoke-static { }, Lapp/revanced/integrations/tiktok/cleardisplay/RememberClearDisplayPatch;->getClearDisplayState()Z
|
||||
move-result v4
|
||||
if-eqz v4, :clear_display_disabled
|
||||
|
||||
new-instance v0, $clearDisplayEventClass
|
||||
invoke-direct { v0, v1, v2, v3 }, $clearDisplayEventClass-><init>(ILjava/lang/String;Z)V
|
||||
invoke-direct { v0, v1, v2, v3, v4 }, $clearDisplayEventClass-><init>(ILjava/lang/String;Ljava/lang/String;Z)V
|
||||
invoke-virtual { v0 }, $clearDisplayEventClass->post()Lcom/ss/android/ugc/governance/eventbus/IEvent;
|
||||
""",
|
||||
ExternalLabel("clear_display_disabled", getInstruction(0))
|
||||
ExternalLabel("clear_display_disabled", getInstruction(0)),
|
||||
)
|
||||
} ?: throw OnRenderFirstFrameFingerprint.exception
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patches.tiktok.interaction.cleardisplay.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object OnRenderFirstFrameFingerprint : MethodFingerprint(
|
||||
customFingerprint = { methodDef, _ ->
|
||||
methodDef.definingClass.endsWith("/BaseListFragmentPanel;") && methodDef.name == "onRenderFirstFrame"
|
||||
}
|
||||
)
|
||||
@@ -13,14 +13,13 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
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.interaction.downloads.fingerprints.DownloadUriFingerprint
|
||||
import app.revanced.patches.tiktok.misc.integrations.IntegrationsPatch
|
||||
import app.revanced.patches.tiktok.misc.settings.SettingsPatch
|
||||
import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsStatusLoadFingerprint
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@Patch(
|
||||
@@ -28,9 +27,9 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
description = "Removes download restrictions and changes the default path to download to.",
|
||||
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
|
||||
]
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
|
||||
],
|
||||
)
|
||||
@Suppress("unused")
|
||||
object DownloadsPatch : BytecodePatch(
|
||||
@@ -38,9 +37,9 @@ object DownloadsPatch : BytecodePatch(
|
||||
ACLCommonShareFingerprint,
|
||||
ACLCommonShareFingerprint2,
|
||||
ACLCommonShareFingerprint3,
|
||||
DownloadPathParentFingerprint,
|
||||
SettingsStatusLoadFingerprint
|
||||
)
|
||||
DownloadUriFingerprint,
|
||||
SettingsStatusLoadFingerprint,
|
||||
),
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
fun MethodFingerprint.getMethod() = result?.mutableMethod ?: throw exception
|
||||
@@ -52,7 +51,7 @@ object DownloadsPatch : BytecodePatch(
|
||||
"""
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
"""
|
||||
""",
|
||||
)
|
||||
},
|
||||
ACLCommonShareFingerprint2 to {
|
||||
@@ -61,7 +60,7 @@ object DownloadsPatch : BytecodePatch(
|
||||
"""
|
||||
const/4 v0, 0x2
|
||||
return v0
|
||||
"""
|
||||
""",
|
||||
)
|
||||
},
|
||||
// Download videos without watermark.
|
||||
@@ -76,48 +75,40 @@ object DownloadsPatch : BytecodePatch(
|
||||
return v0
|
||||
:noremovewatermark
|
||||
nop
|
||||
"""
|
||||
""",
|
||||
)
|
||||
},
|
||||
// Change the download path patch.
|
||||
DownloadPathParentFingerprint to {
|
||||
val targetIndex = indexOfFirstInstructionOrThrow { opcode == Opcode.INVOKE_STATIC }
|
||||
val downloadUriMethod = context
|
||||
.toMethodWalker(this)
|
||||
.nextMethod(targetIndex, true)
|
||||
.getMethod() as MutableMethod
|
||||
|
||||
val firstIndex = downloadUriMethod.indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_DIRECT && ((this as Instruction35c).reference as MethodReference).name == "<init>"
|
||||
DownloadUriFingerprint to {
|
||||
val firstIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.name == "<init>"
|
||||
}
|
||||
val secondIndex = downloadUriMethod.indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_STATIC && ((this as Instruction35c).reference as MethodReference).returnType.contains(
|
||||
"Uri"
|
||||
)
|
||||
val secondIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.returnType?.contains("Uri") == true
|
||||
}
|
||||
|
||||
downloadUriMethod.addInstructions(
|
||||
addInstructions(
|
||||
secondIndex,
|
||||
"""
|
||||
invoke-static {}, Lapp/revanced/integrations/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
|
||||
move-result-object v0
|
||||
"""
|
||||
invoke-static {}, Lapp/revanced/integrations/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
|
||||
move-result-object v0
|
||||
""",
|
||||
)
|
||||
|
||||
downloadUriMethod.addInstructions(
|
||||
addInstructions(
|
||||
firstIndex,
|
||||
"""
|
||||
invoke-static {}, Lapp/revanced/integrations/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
|
||||
move-result-object v0
|
||||
"""
|
||||
invoke-static {}, Lapp/revanced/integrations/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String;
|
||||
move-result-object v0
|
||||
""",
|
||||
)
|
||||
},
|
||||
SettingsStatusLoadFingerprint to {
|
||||
addInstruction(
|
||||
0,
|
||||
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableDownload()V"
|
||||
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableDownload()V",
|
||||
)
|
||||
}
|
||||
},
|
||||
).forEach { (fingerprint, patch) ->
|
||||
fingerprint.getMethod().patch()
|
||||
}
|
||||
|
||||
@@ -3,22 +3,18 @@ package app.revanced.patches.tiktok.interaction.downloads.fingerprints
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object DownloadPathParentFingerprint : MethodFingerprint(
|
||||
"L",
|
||||
internal object DownloadUriFingerprint : MethodFingerprint(
|
||||
"Landroid/net/Uri;",
|
||||
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
strings = listOf(
|
||||
"video/mp4"
|
||||
"/",
|
||||
"/Camera",
|
||||
"/Camera/",
|
||||
"video/mp4",
|
||||
),
|
||||
parameters = listOf(
|
||||
"L",
|
||||
"L"
|
||||
"Landroid/content/Context;",
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
opcodes = listOf(
|
||||
Opcode.CONST_STRING,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.RETURN_OBJECT
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -9,30 +9,33 @@ import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.tiktok.interaction.speed.fingerprints.GetSpeedFingerprint
|
||||
import app.revanced.patches.tiktok.interaction.speed.fingerprints.OnRenderFirstFrameFingerprint
|
||||
import app.revanced.patches.tiktok.interaction.speed.fingerprints.SetSpeedFingerprint
|
||||
import app.revanced.patches.tiktok.shared.fingerprints.GetEnterFromFingerprint
|
||||
import app.revanced.patches.tiktok.shared.fingerprints.OnRenderFirstFrameFingerprint
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction11x
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@Patch(
|
||||
name = "Playback speed",
|
||||
description = "Enables the playback speed option for all videos and " +
|
||||
"retains the speed configurations in between videos.",
|
||||
"retains the speed configurations in between videos.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
|
||||
]
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
|
||||
],
|
||||
)
|
||||
@Suppress("unused")
|
||||
object PlaybackSpeedPatch : BytecodePatch(
|
||||
setOf(
|
||||
GetSpeedFingerprint,
|
||||
OnRenderFirstFrameFingerprint,
|
||||
SetSpeedFingerprint
|
||||
)
|
||||
SetSpeedFingerprint,
|
||||
GetEnterFromFingerprint,
|
||||
),
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
SetSpeedFingerprint.result?.let { onVideoSwiped ->
|
||||
@@ -44,7 +47,7 @@ object PlaybackSpeedPatch : BytecodePatch(
|
||||
addInstruction(
|
||||
injectIndex,
|
||||
"invoke-static { v$register }," +
|
||||
" Lapp/revanced/integrations/tiktok/speed/PlaybackSpeedPatch;->rememberPlaybackSpeed(F)V"
|
||||
" Lapp/revanced/integrations/tiktok/speed/PlaybackSpeedPatch;->rememberPlaybackSpeed(F)V",
|
||||
)
|
||||
} ?: throw GetSpeedFingerprint.exception
|
||||
|
||||
@@ -53,29 +56,29 @@ object PlaybackSpeedPatch : BytecodePatch(
|
||||
OnRenderFirstFrameFingerprint.result?.mutableMethod?.addInstructions(
|
||||
0,
|
||||
"""
|
||||
# Video playback location (e.g. home page, following page or search result page) retrieved using getEnterFrom method.
|
||||
const/4 v0, 0x1
|
||||
invoke-virtual {p0, v0}, Lcom/ss/android/ugc/aweme/feed/panel/BaseListFragmentPanel;->getEnterFrom(Z)Ljava/lang/String;
|
||||
move-result-object v0
|
||||
|
||||
# Model of current video retrieved using getCurrentAweme method.
|
||||
invoke-virtual {p0}, Lcom/ss/android/ugc/aweme/feed/panel/BaseListFragmentPanel;->getCurrentAweme()Lcom/ss/android/ugc/aweme/feed/model/Aweme;
|
||||
move-result-object v1
|
||||
|
||||
# Desired playback speed retrieved using getPlaybackSpeed method.
|
||||
invoke-static {}, Lapp/revanced/integrations/tiktok/speed/PlaybackSpeedPatch;->getPlaybackSpeed()F
|
||||
move-result-object v2
|
||||
invoke-static { v0, v1, v2 }, ${onVideoSwiped.method}
|
||||
"""
|
||||
# Video playback location (e.g. home page, following page or search result page) retrieved using getEnterFrom method.
|
||||
const/4 v0, 0x1
|
||||
invoke-virtual {p0, v0}, ${GetEnterFromFingerprint.resultOrThrow().method}
|
||||
move-result-object v0
|
||||
|
||||
# Model of current video retrieved using getCurrentAweme method.
|
||||
invoke-virtual {p0}, Lcom/ss/android/ugc/aweme/feed/panel/BaseListFragmentPanel;->getCurrentAweme()Lcom/ss/android/ugc/aweme/feed/model/Aweme;
|
||||
move-result-object v1
|
||||
|
||||
# Desired playback speed retrieved using getPlaybackSpeed method.
|
||||
invoke-static {}, Lapp/revanced/integrations/tiktok/speed/PlaybackSpeedPatch;->getPlaybackSpeed()F
|
||||
move-result v2
|
||||
invoke-static { v0, v1, v2 }, ${onVideoSwiped.method}
|
||||
""",
|
||||
) ?: throw OnRenderFirstFrameFingerprint.exception
|
||||
|
||||
// Force enable the playback speed option for all videos.
|
||||
onVideoSwiped.mutableClass.methods.find { method -> method.returnType == "Z" }?.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
) ?: throw PatchException("Failed to force enable the playback speed option.")
|
||||
} ?: throw SetSpeedFingerprint.exception
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
description = "Adds ReVanced settings to TikTok.",
|
||||
dependencies = [IntegrationsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["32.5.3"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["32.5.3"])
|
||||
]
|
||||
CompatiblePackage("com.ss.android.ugc.trill", ["36.5.4"]),
|
||||
CompatiblePackage("com.zhiliaoapp.musically", ["36.5.4"]),
|
||||
],
|
||||
)
|
||||
object SettingsPatch : BytecodePatch(
|
||||
setOf(
|
||||
@@ -34,21 +34,21 @@ object SettingsPatch : BytecodePatch(
|
||||
AddSettingsEntryFingerprint,
|
||||
SettingsEntryFingerprint,
|
||||
SettingsEntryInfoFingerprint,
|
||||
)
|
||||
),
|
||||
) {
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/integrations/tiktok/settings/AdPersonalizationActivityHook;"
|
||||
|
||||
private const val INITIALIZE_SETTINGS_METHOD_DESCRIPTOR =
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->initialize(" +
|
||||
"Lcom/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity;" +
|
||||
")Z"
|
||||
"Lcom/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity;" +
|
||||
")Z"
|
||||
|
||||
private const val CREATE_SETTINGS_ENTRY_METHOD_DESCRIPTOR =
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->createSettingsEntry(" +
|
||||
"Ljava/lang/String;" +
|
||||
"Ljava/lang/String;" +
|
||||
")Ljava/lang/Object;"
|
||||
"Ljava/lang/String;" +
|
||||
"Ljava/lang/String;" +
|
||||
")Ljava/lang/Object;"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
// Find the class name of classes which construct a settings entry
|
||||
@@ -70,8 +70,8 @@ object SettingsPatch : BytecodePatch(
|
||||
markIndex + 2,
|
||||
listOf(
|
||||
getUnitManager,
|
||||
addEntry
|
||||
)
|
||||
addEntry,
|
||||
),
|
||||
)
|
||||
|
||||
addInstructions(
|
||||
@@ -81,7 +81,8 @@ object SettingsPatch : BytecodePatch(
|
||||
const-string v1, "$settingsButtonInfoClass"
|
||||
invoke-static {v0, v1}, $CREATE_SETTINGS_ENTRY_METHOD_DESCRIPTOR
|
||||
move-result-object v0
|
||||
"""
|
||||
check-cast v0, ${SettingsEntryFingerprint.result!!.classDef}
|
||||
""",
|
||||
)
|
||||
} ?: throw AddSettingsEntryFingerprint.exception
|
||||
|
||||
@@ -102,12 +103,10 @@ object SettingsPatch : BytecodePatch(
|
||||
if-eqz v$usableRegister, :do_not_open
|
||||
return-void
|
||||
""",
|
||||
ExternalLabel("do_not_open", getInstruction(initializeSettingsIndex))
|
||||
ExternalLabel("do_not_open", getInstruction(initializeSettingsIndex)),
|
||||
)
|
||||
} ?: throw AdPersonalizationActivityOnCreateFingerprint.exception
|
||||
}
|
||||
|
||||
private fun String.toClassName(): String {
|
||||
return substring(1, this.length - 1).replace("/", ".")
|
||||
}
|
||||
}
|
||||
private fun String.toClassName(): String = substring(1, this.length - 1).replace("/", ".")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.tiktok.shared.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object GetEnterFromFingerprint : MethodFingerprint(
|
||||
returnType = "Ljava/lang/String;",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("Z"),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
),
|
||||
customFingerprint = { methodDef, _ ->
|
||||
methodDef.definingClass.endsWith("/BaseListFragmentPanel;")
|
||||
},
|
||||
)
|
||||
@@ -1,9 +1,10 @@
|
||||
package app.revanced.patches.tiktok.interaction.speed.fingerprints
|
||||
package app.revanced.patches.tiktok.shared.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object OnRenderFirstFrameFingerprint : MethodFingerprint(
|
||||
strings = listOf("method_enable_viewpager_preload_duration"),
|
||||
customFingerprint = { methodDef, _ ->
|
||||
methodDef.definingClass.endsWith("/BaseListFragmentPanel;") && methodDef.name == "onRenderFirstFrame"
|
||||
}
|
||||
methodDef.definingClass.endsWith("/BaseListFragmentPanel;")
|
||||
},
|
||||
)
|
||||
@@ -12,7 +12,7 @@ import app.revanced.util.exception
|
||||
name = "Open links with app chooser",
|
||||
description = "Instead of opening links directly, open them with an app chooser. " +
|
||||
"As a result you can select a browser to open the link with.",
|
||||
compatiblePackages = [CompatiblePackage("com.twitter.android")],
|
||||
compatiblePackages = [CompatiblePackage("com.twitter.android", ["10.48.0-release.0"])],
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
|
||||
@@ -51,8 +51,8 @@ object CopyVideoUrlBytecodePatch : BytecodePatch(emptySet()) {
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
BUTTONS_DESCRIPTORS.forEach { descriptor ->
|
||||
PlayerControlsBytecodePatch.initializeControl("$descriptor->initializeButton(Landroid/view/View;)V")
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V")
|
||||
PlayerControlsBytecodePatch.initializeBottomControl(descriptor)
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall(descriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
|
||||
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.copyResources
|
||||
@@ -13,7 +13,7 @@ import app.revanced.util.copyResources
|
||||
@Patch(
|
||||
dependencies = [
|
||||
SettingsPatch::class,
|
||||
BottomControlsResourcePatch::class,
|
||||
PlayerControlsResourcePatch::class,
|
||||
AddResourcesPatch::class
|
||||
]
|
||||
)
|
||||
@@ -34,6 +34,6 @@ internal object CopyVideoUrlResourcePatch : ResourcePatch() {
|
||||
)
|
||||
)
|
||||
|
||||
BottomControlsResourcePatch.addControls("copyvideourl")
|
||||
PlayerControlsResourcePatch.addBottomControls("copyvideourl")
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,8 @@ object DownloadsPatch : BytecodePatch(
|
||||
private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
PlayerControlsBytecodePatch.initializeControl("$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V")
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$BUTTON_DESCRIPTOR->changeVisibility(Z)V")
|
||||
PlayerControlsBytecodePatch.initializeBottomControl(BUTTON_DESCRIPTOR)
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall(BUTTON_DESCRIPTOR)
|
||||
|
||||
// Main activity is used to launch downloader intent.
|
||||
MainActivityFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
|
||||
@@ -9,14 +9,14 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
|
||||
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.copyResources
|
||||
|
||||
@Patch(
|
||||
dependencies = [
|
||||
BottomControlsResourcePatch::class,
|
||||
PlayerControlsResourcePatch::class,
|
||||
SettingsPatch::class,
|
||||
AddResourcesPatch::class,
|
||||
],
|
||||
@@ -42,6 +42,6 @@ internal object DownloadsResourcePatch : ResourcePatch() {
|
||||
ResourceGroup("drawable", "revanced_yt_download_button.xml"),
|
||||
)
|
||||
|
||||
BottomControlsResourcePatch.addControls("downloads")
|
||||
PlayerControlsResourcePatch.addBottomControls("downloads")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package app.revanced.patches.youtube.interaction.seekbar
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.interaction.seekbar.fingerprints.IsSwipingUpFingerprint
|
||||
import app.revanced.patches.youtube.interaction.seekbar.fingerprints.AllowSwipingUpGestureFingerprint
|
||||
import app.revanced.patches.youtube.interaction.seekbar.fingerprints.ShowSwipingUpGuideFingerprint
|
||||
import app.revanced.patches.youtube.interaction.seekbar.fingerprints.SwipingUpGestureParentFingerprint
|
||||
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.util.exception
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import app.revanced.util.alsoResolve
|
||||
|
||||
@Patch(
|
||||
name = "Disable precise seeking gesture",
|
||||
@@ -52,11 +54,10 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
)
|
||||
@Suppress("unused")
|
||||
object DisablePreciseSeekingGesturePatch : BytecodePatch(
|
||||
setOf(IsSwipingUpFingerprint)
|
||||
setOf(SwipingUpGestureParentFingerprint)
|
||||
) {
|
||||
private const val INTEGRATIONS_METHOD_DESCRIPTOR =
|
||||
"Lapp/revanced/integrations/youtube/patches/DisablePreciseSeekingGesturePatch;->" +
|
||||
"disableGesture(Landroid/view/VelocityTracker;Landroid/view/MotionEvent;)V"
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/integrations/youtube/patches/DisablePreciseSeekingGesturePatch;"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
AddResourcesPatch(this::class)
|
||||
@@ -65,19 +66,37 @@ object DisablePreciseSeekingGesturePatch : BytecodePatch(
|
||||
SwitchPreference("revanced_disable_precise_seeking_gesture")
|
||||
)
|
||||
|
||||
IsSwipingUpFingerprint.result?.let {
|
||||
val addMovementIndex = it.scanResult.patternScanResult!!.startIndex - 1
|
||||
AllowSwipingUpGestureFingerprint.alsoResolve(
|
||||
context,
|
||||
SwipingUpGestureParentFingerprint
|
||||
).mutableMethod.apply {
|
||||
addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isGestureDisabled()Z
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
return-void
|
||||
""",
|
||||
ExternalLabel("disabled", getInstruction(0))
|
||||
)
|
||||
}
|
||||
|
||||
it.mutableMethod.apply {
|
||||
val addMovementInstruction = getInstruction<FiveRegisterInstruction>(addMovementIndex)
|
||||
val trackerRegister = addMovementInstruction.registerC
|
||||
val eventRegister = addMovementInstruction.registerD
|
||||
|
||||
replaceInstruction(
|
||||
addMovementIndex,
|
||||
"invoke-static {v$trackerRegister, v$eventRegister}, $INTEGRATIONS_METHOD_DESCRIPTOR"
|
||||
)
|
||||
}
|
||||
} ?: throw IsSwipingUpFingerprint.exception
|
||||
ShowSwipingUpGuideFingerprint.alsoResolve(
|
||||
context,
|
||||
SwipingUpGestureParentFingerprint
|
||||
).mutableMethod.apply {
|
||||
addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isGestureDisabled()Z
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
""",
|
||||
ExternalLabel("disabled", getInstruction(0))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.revanced.patches.youtube.interaction.seekbar.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
/**
|
||||
* Resolves using the class found in [SwipingUpGestureParentFingerprint].
|
||||
*/
|
||||
internal object AllowSwipingUpGestureFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
parameters = listOf("L"),
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.patches.youtube.interaction.seekbar.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object IsSwipingUpFingerprint : MethodFingerprint(
|
||||
returnType = "Z",
|
||||
parameters = listOf("Landroid/view/MotionEvent;", "J"),
|
||||
opcodes = listOf(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.IGET_OBJECT
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.revanced.patches.youtube.interaction.seekbar.fingerprints
|
||||
|
||||
import app.revanced.util.patch.LiteralValueFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
/**
|
||||
* Resolves using the class found in [SwipingUpGestureParentFingerprint].
|
||||
*/
|
||||
internal object ShowSwipingUpGuideFingerprint : LiteralValueFingerprint(
|
||||
accessFlags = AccessFlags.FINAL.value,
|
||||
returnType = "Z",
|
||||
parameters = emptyList(),
|
||||
literalSupplier = { 1L }
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package app.revanced.patches.youtube.interaction.seekbar.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.util.patch.LiteralValueFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object SwipingUpGestureParentFingerprint : LiteralValueFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "Z",
|
||||
parameters = listOf(),
|
||||
literalSupplier = { 45379021 }
|
||||
)
|
||||
@@ -89,7 +89,6 @@ object HideLayoutComponentsPatch : BytecodePatch(
|
||||
SwitchPreference("revanced_hide_channel_guidelines"),
|
||||
SwitchPreference("revanced_hide_channel_member_shelf"),
|
||||
SwitchPreference("revanced_hide_channel_watermark"),
|
||||
SwitchPreference("revanced_hide_chips_shelf"),
|
||||
SwitchPreference("revanced_hide_community_guidelines"),
|
||||
PreferenceScreen(
|
||||
key = "revanced_hide_description_components_screen",
|
||||
@@ -145,6 +144,7 @@ object HideLayoutComponentsPatch : BytecodePatch(
|
||||
)
|
||||
|
||||
SettingsPatch.PreferenceScreen.GENERAL_LAYOUT.addPreferences(
|
||||
SwitchPreference("revanced_hide_chips_shelf"),
|
||||
SwitchPreference("revanced_hide_expandable_chip"),
|
||||
SwitchPreference("revanced_hide_gray_separator"),
|
||||
PreferenceScreen(
|
||||
|
||||
@@ -6,6 +6,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import app.revanced.patches.youtube.layout.hide.shorts.fingerprints.*
|
||||
@@ -76,6 +77,20 @@ object HideShortsComponentsPatch : BytecodePatch(
|
||||
) {
|
||||
private const val FILTER_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/components/ShortsFilter;"
|
||||
|
||||
internal val hideShortsAppShortcut by booleanPatchOption(
|
||||
key = "hideShortsAppShortcut",
|
||||
default = false,
|
||||
title = "Hide Shorts app shortcut",
|
||||
description = "Permanently hides the shortcut to open Shorts when long pressing the app icon in your launcher."
|
||||
)
|
||||
|
||||
internal val hideShortsWidget by booleanPatchOption(
|
||||
key = "hideShortsWidget",
|
||||
default = false,
|
||||
title = "Hide Shorts widget",
|
||||
description = "Permanently hides the launcher widget Shorts button."
|
||||
)
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
// region Hide the Shorts shelf.
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsAppShortcut
|
||||
import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsWidget
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
|
||||
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class, AddResourcesPatch::class])
|
||||
object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
@@ -40,6 +43,7 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
SwitchPreference("revanced_hide_shorts_save_sound_button"),
|
||||
SwitchPreference("revanced_hide_shorts_shop_button"),
|
||||
SwitchPreference("revanced_hide_shorts_tagged_products"),
|
||||
SwitchPreference("revanced_hide_shorts_stickers"),
|
||||
SwitchPreference("revanced_hide_shorts_search_suggestions"),
|
||||
SwitchPreference("revanced_hide_shorts_super_thanks_button"),
|
||||
SwitchPreference("revanced_hide_shorts_location_label"),
|
||||
@@ -51,6 +55,28 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
SwitchPreference("revanced_hide_shorts_navigation_bar"),
|
||||
)
|
||||
|
||||
if (hideShortsAppShortcut == true) {
|
||||
context.xmlEditor["res/xml/main_shortcuts.xml"].use { editor ->
|
||||
val shortsItem = editor.file.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:shortcutId",
|
||||
"shorts-shortcut"
|
||||
)
|
||||
|
||||
shortsItem.parentNode.removeChild(shortsItem)
|
||||
}
|
||||
}
|
||||
|
||||
if (hideShortsWidget == true) {
|
||||
context.xmlEditor["res/layout/appwidget_two_rows.xml"].use { editor ->
|
||||
val shortsItem = editor.file.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:id",
|
||||
"@id/button_shorts_container"
|
||||
)
|
||||
|
||||
shortsItem.parentNode.removeChild(shortsItem)
|
||||
}
|
||||
}
|
||||
|
||||
reelPlayerRightCellButtonHeight = ResourceMappingPatch[
|
||||
"dimen",
|
||||
"reel_player_right_cell_button_height",
|
||||
|
||||
@@ -10,7 +10,6 @@ import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.AppendTimeFingerprint
|
||||
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.ControlsOverlayFingerprint
|
||||
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint
|
||||
@@ -26,7 +25,10 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch
|
||||
import app.revanced.patches.youtube.video.videoid.VideoIdPatch
|
||||
import app.revanced.util.exception
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.*
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
@@ -169,59 +171,14 @@ object SponsorBlockBytecodePatch : BytecodePatch(
|
||||
break
|
||||
}
|
||||
|
||||
/*
|
||||
* Voting & Shield button
|
||||
*/
|
||||
val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult
|
||||
// Change visibility of the buttons.
|
||||
PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
|
||||
|
||||
val controlsLayoutStubResourceId =
|
||||
ResourceMappingPatch["id", "controls_layout_stub"]
|
||||
val zoomOverlayResourceId =
|
||||
ResourceMappingPatch["id", "video_zoom_overlay_stub"]
|
||||
PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
|
||||
|
||||
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}, $INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/View;)V
|
||||
invoke-static {v$inflatedViewRegister}, $INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/View;)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}, $INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediate(Z)V
|
||||
invoke-static {p1}, $INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediate(Z)V
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// change visibility of the buttons
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V")
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V")
|
||||
|
||||
// append the new time to the player layout
|
||||
// Append the new time to the player layout.
|
||||
val appendTimeFingerprintResult = AppendTimeFingerprint.result!!
|
||||
val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex
|
||||
val targetRegister =
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package app.revanced.patches.youtube.layout.sponsorblock
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
||||
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsResourcePatch
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.util.copyXmlNode
|
||||
import app.revanced.util.inputStreamFromBundledResource
|
||||
|
||||
@Patch(
|
||||
dependencies = [
|
||||
@@ -60,49 +58,6 @@ internal object SponsorBlockResourcePatch : ResourcePatch() {
|
||||
context.copyResources("sponsorblock", resourceGroup)
|
||||
}
|
||||
|
||||
// copy nodes from host resources to their real xml files
|
||||
|
||||
val hostingResourceStream =
|
||||
inputStreamFromBundledResource(
|
||||
"sponsorblock",
|
||||
"host/layout/youtube_controls_layout.xml",
|
||||
)!!
|
||||
|
||||
var modifiedControlsLayout = false
|
||||
val editor = context.xmlEditor["res/layout/youtube_controls_layout.xml"]
|
||||
"RelativeLayout".copyXmlNode(
|
||||
context.xmlEditor[hostingResourceStream],
|
||||
editor,
|
||||
).also {
|
||||
val document = editor.file
|
||||
|
||||
val children = document.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/revanced_sb_voting_button"
|
||||
|
||||
view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId
|
||||
|
||||
modifiedControlsLayout = true
|
||||
break
|
||||
}
|
||||
}.close()
|
||||
|
||||
if (!modifiedControlsLayout) throw PatchException("Could not modify controls layout")
|
||||
PlayerControlsResourcePatch.addTopControls("sponsorblock")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,399 +1,11 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.backgroundplayback.BackgroundPlaybackPatch
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
|
||||
import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
@Patch(
|
||||
name = "Spoof client",
|
||||
description = "Spoofs the client to allow video playback.",
|
||||
dependencies = [
|
||||
SettingsPatch::class,
|
||||
AddResourcesPatch::class,
|
||||
UserAgentClientSpoofPatch::class,
|
||||
// Required since iOS livestream fix partially enables background playback.
|
||||
BackgroundPlaybackPatch::class,
|
||||
PlayerTypeHookPatch::class,
|
||||
],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.youtube",
|
||||
[
|
||||
// This patch works with these versions,
|
||||
// but the dependent background playback patch does not.
|
||||
// "18.37.36",
|
||||
// "18.38.44",
|
||||
// "18.43.45",
|
||||
// "18.44.41",
|
||||
// "18.45.43",
|
||||
"18.48.39",
|
||||
"18.49.37",
|
||||
"19.01.34",
|
||||
"19.02.39",
|
||||
"19.03.36",
|
||||
"19.04.38",
|
||||
"19.05.36",
|
||||
"19.06.39",
|
||||
"19.07.40",
|
||||
"19.08.36",
|
||||
"19.09.38",
|
||||
"19.10.39",
|
||||
"19.11.43",
|
||||
"19.12.41",
|
||||
"19.13.37",
|
||||
"19.14.43",
|
||||
"19.15.36",
|
||||
"19.16.39",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@Deprecated("This patch is obsolete.", replaceWith = ReplaceWith("SpoofVideoStreamsPatch"))
|
||||
object SpoofClientPatch : BytecodePatch(
|
||||
setOf(
|
||||
// Client type spoof.
|
||||
BuildInitPlaybackRequestFingerprint,
|
||||
BuildPlayerRequestURIFingerprint,
|
||||
SetPlayerRequestClientTypeFingerprint,
|
||||
CreatePlayerRequestBodyFingerprint,
|
||||
CreatePlayerRequestBodyWithModelFingerprint,
|
||||
CreatePlayerRequestBodyWithVersionReleaseFingerprint,
|
||||
|
||||
// Player gesture config.
|
||||
PlayerGestureConfigSyntheticFingerprint,
|
||||
|
||||
// Player speed menu item.
|
||||
CreatePlaybackSpeedMenuItemFingerprint,
|
||||
|
||||
// Video qualities missing.
|
||||
BuildRequestFingerprint,
|
||||
|
||||
// Livestream audio only background playback.
|
||||
PlayerResponseModelBackgroundAudioPlaybackFingerprint,
|
||||
)
|
||||
dependencies = setOf(SpoofVideoStreamsPatch::class),
|
||||
) {
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofClientPatch;"
|
||||
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
|
||||
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
|
||||
private const val REQUEST_CLASS_DESCRIPTOR =
|
||||
"Lorg/chromium/net/ExperimentalUrlRequest;"
|
||||
private const val REQUEST_BUILDER_CLASS_DESCRIPTOR =
|
||||
"Lorg/chromium/net/ExperimentalUrlRequest\$Builder;"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
AddResourcesPatch(this::class)
|
||||
|
||||
SettingsPatch.PreferenceScreen.MISC.addPreferences(
|
||||
PreferenceScreen(
|
||||
key = "revanced_spoof_client_screen",
|
||||
sorting = PreferenceScreen.Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_spoof_client"),
|
||||
ListPreference("revanced_spoof_client_type",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_spoof_client_type_entries",
|
||||
entryValuesKey = "revanced_spoof_client_type_entry_values"
|
||||
),
|
||||
SwitchPreference("revanced_spoof_client_ios_force_avc"),
|
||||
NonInteractivePreference("revanced_spoof_client_about_android_ios"),
|
||||
NonInteractivePreference("revanced_spoof_client_about_android_vr")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// region Block /initplayback requests to fall back to /get_watch requests.
|
||||
|
||||
BuildInitPlaybackRequestFingerprint.resultOrThrow().let {
|
||||
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
|
||||
it.mutableMethod.apply {
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveUriStringIndex + 1,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$targetRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Block /get_watch requests to fall back to /player requests.
|
||||
|
||||
BuildPlayerRequestURIFingerprint.resultOrThrow().let {
|
||||
val invokeToStringIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
|
||||
it.mutableMethod.apply {
|
||||
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
invokeToStringIndex,
|
||||
"""
|
||||
invoke-static { v$uriRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$uriRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Get field references to be used below.
|
||||
|
||||
val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
|
||||
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
|
||||
// Field in the player request object that holds the client info object.
|
||||
val clientInfoField = result.mutableMethod
|
||||
.getInstructions().find { instruction ->
|
||||
// requestMessage.clientInfo = clientInfoBuilder.build();
|
||||
instruction.opcode == Opcode.IPUT_OBJECT &&
|
||||
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
|
||||
}?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
|
||||
|
||||
// Client info object's client type field.
|
||||
val clientInfoClientTypeField = result.mutableMethod
|
||||
.getInstruction(result.scanResult.patternScanResult!!.endIndex)
|
||||
.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientTypeField")
|
||||
|
||||
// Client info object's client version field.
|
||||
val clientInfoClientVersionField = result.mutableMethod
|
||||
.getInstruction(result.scanResult.stringsScanResult!!.matches.first().index + 1)
|
||||
.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoClientVersionField")
|
||||
|
||||
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
|
||||
}
|
||||
|
||||
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().let {
|
||||
val getClientModelIndex =
|
||||
CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction(it.method)
|
||||
|
||||
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
|
||||
val index = it.mutableMethod.indexOfFirstInstructionOrThrow(getClientModelIndex) {
|
||||
opcode == Opcode.IPUT_OBJECT
|
||||
}
|
||||
|
||||
it.mutableMethod.getInstruction(index).getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoClientModelField")
|
||||
}
|
||||
|
||||
val clientInfoOsVersionField = CreatePlayerRequestBodyWithVersionReleaseFingerprint.resultOrThrow().let {
|
||||
val getOsVersionIndex =
|
||||
CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildVersionReleaseInstruction(it.method)
|
||||
|
||||
// The next IPUT_OBJECT instruction after getting the client os version is setting the client os version field.
|
||||
val index = it.mutableMethod.indexOfFirstInstructionOrThrow(getOsVersionIndex) {
|
||||
opcode == Opcode.IPUT_OBJECT
|
||||
}
|
||||
|
||||
it.mutableMethod.getInstruction(index).getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoOsVersionField")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Spoof client type for /player requests.
|
||||
|
||||
CreatePlayerRequestBodyFingerprint.resultOrThrow().let { result ->
|
||||
val setClientInfoMethodName = "patch_setClientInfo"
|
||||
val checkCastIndex = result.scanResult.patternScanResult!!.startIndex
|
||||
var clientInfoContainerClassName: String
|
||||
|
||||
result.mutableMethod.apply {
|
||||
val checkCastInstruction = getInstruction<OneRegisterInstruction>(checkCastIndex)
|
||||
val requestMessageInstanceRegister = checkCastInstruction.registerA
|
||||
clientInfoContainerClassName = checkCastInstruction.getReference<TypeReference>()!!.type
|
||||
|
||||
addInstruction(
|
||||
checkCastIndex + 1,
|
||||
"invoke-static { v$requestMessageInstanceRegister }," +
|
||||
" ${result.classDef.type}->$setClientInfoMethodName($clientInfoContainerClassName)V",
|
||||
)
|
||||
}
|
||||
|
||||
// Change client info to use the spoofed values.
|
||||
// Do this in a helper method, to remove the need of picking out multiple free registers from the hooked code.
|
||||
result.mutableClass.methods.add(
|
||||
ImmutableMethod(
|
||||
result.mutableClass.type,
|
||||
setClientInfoMethodName,
|
||||
listOf(ImmutableMethodParameter(clientInfoContainerClassName, null, "clientInfoContainer")),
|
||||
"V",
|
||||
AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isClientSpoofingEnabled()Z
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
|
||||
iget-object v0, p0, $clientInfoField
|
||||
|
||||
# Set client type to the spoofed value.
|
||||
iget v1, v0, $clientInfoClientTypeField
|
||||
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientTypeId(I)I
|
||||
move-result v1
|
||||
iput v1, v0, $clientInfoClientTypeField
|
||||
|
||||
# Set client model to the spoofed value.
|
||||
iget-object v1, v0, $clientInfoClientModelField
|
||||
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientModel(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $clientInfoClientModelField
|
||||
|
||||
# Set client version to the spoofed value.
|
||||
iget-object v1, v0, $clientInfoClientVersionField
|
||||
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $clientInfoClientVersionField
|
||||
|
||||
# Set client os version to the spoofed value.
|
||||
iget-object v1, v0, $clientInfoOsVersionField
|
||||
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getOsVersion(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $clientInfoOsVersionField
|
||||
|
||||
:disabled
|
||||
return-void
|
||||
""",
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Fix player gesture if spoofing to iOS.
|
||||
|
||||
PlayerGestureConfigSyntheticFingerprint.resultOrThrow().let {
|
||||
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val downAndOutLandscapeAllowedIndex = endIndex - 3
|
||||
val downAndOutPortraitAllowedIndex = endIndex - 9
|
||||
|
||||
arrayOf(
|
||||
downAndOutLandscapeAllowedIndex,
|
||||
downAndOutPortraitAllowedIndex,
|
||||
).forEach { index ->
|
||||
val gestureAllowedMethod = context.toMethodWalker(it.mutableMethod)
|
||||
.nextMethod(index, true)
|
||||
.getMethod() as MutableMethod
|
||||
|
||||
gestureAllowedMethod.apply {
|
||||
val isAllowedIndex = getInstructions().lastIndex
|
||||
val isAllowed = getInstruction<OneRegisterInstruction>(isAllowedIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
isAllowedIndex,
|
||||
"""
|
||||
invoke-static { v$isAllowed }, $INTEGRATIONS_CLASS_DESCRIPTOR->enablePlayerGesture(Z)Z
|
||||
move-result v$isAllowed
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Fix livestream audio only background play if spoofing to iOS.
|
||||
// This force enables audio background playback.
|
||||
|
||||
PlayerResponseModelBackgroundAudioPlaybackFingerprint.resultOrThrow().mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideBackgroundAudioPlayback()Z
|
||||
move-result v0
|
||||
if-eqz v0, :do_not_override
|
||||
return v0
|
||||
:do_not_override
|
||||
nop
|
||||
"""
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// Fix playback speed menu item if spoofing to iOS.
|
||||
|
||||
CreatePlaybackSpeedMenuItemFingerprint.resultOrThrow().let {
|
||||
val scanResult = it.scanResult.patternScanResult!!
|
||||
if (scanResult.startIndex != 0) throw PatchException("Unexpected start index: ${scanResult.startIndex}")
|
||||
|
||||
it.mutableMethod.apply {
|
||||
// Find the conditional check if the playback speed menu item is not created.
|
||||
val shouldCreateMenuIndex =
|
||||
indexOfFirstInstructionOrThrow(scanResult.endIndex) { opcode == Opcode.IF_EQZ }
|
||||
val shouldCreateMenuRegister = getInstruction<OneRegisterInstruction>(shouldCreateMenuIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
shouldCreateMenuIndex,
|
||||
"""
|
||||
invoke-static { v$shouldCreateMenuRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->forceCreatePlaybackSpeedMenu(Z)Z
|
||||
move-result v$shouldCreateMenuRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Fix video qualities missing, if spoofing to iOS by overriding the user agent.
|
||||
|
||||
BuildRequestFingerprint.resultOrThrow().let { result ->
|
||||
result.mutableMethod.apply {
|
||||
val buildRequestIndex = getInstructions().lastIndex - 2
|
||||
val requestBuilderRegister = getInstruction<FiveRegisterInstruction>(buildRequestIndex).registerC
|
||||
|
||||
val newRequestBuilderIndex = result.scanResult.patternScanResult!!.endIndex
|
||||
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
|
||||
// Replace "requestBuilder.build(): Request" with "overrideUserAgent(requestBuilder, url): Request".
|
||||
replaceInstruction(
|
||||
buildRequestIndex,
|
||||
"invoke-static { v$requestBuilderRegister, v$urlRegister }, " +
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->" +
|
||||
"overrideUserAgent(${REQUEST_BUILDER_CLASS_DESCRIPTOR}Ljava/lang/String;)" +
|
||||
REQUEST_CLASS_DESCRIPTOR
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
override fun execute(context: BytecodeContext) {}
|
||||
}
|
||||
@@ -1,239 +1,12 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
|
||||
import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.patches.youtube.video.information.VideoInformationPatch
|
||||
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
|
||||
import app.revanced.util.exception
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
|
||||
@Patch(
|
||||
description = "Spoofs the signature to prevent playback issues.",
|
||||
dependencies = [
|
||||
SettingsPatch::class,
|
||||
PlayerTypeHookPatch::class,
|
||||
PlayerResponseMethodHookPatch::class,
|
||||
VideoInformationPatch::class,
|
||||
SpoofSignatureResourcePatch::class,
|
||||
AddResourcesPatch::class,
|
||||
],
|
||||
)
|
||||
@Deprecated("This patch will be removed in the future.")
|
||||
@Deprecated("This patch is obsolete.", replaceWith = ReplaceWith("SpoofVideoStreamsPatch"))
|
||||
object SpoofSignaturePatch : BytecodePatch(
|
||||
setOf(
|
||||
PlayerResponseModelImplGeneralFingerprint,
|
||||
PlayerResponseModelImplLiveStreamFingerprint,
|
||||
PlayerResponseModelImplRecommendedLevelFingerprint,
|
||||
StoryboardRendererSpecFingerprint,
|
||||
StoryboardRendererDecoderSpecFingerprint,
|
||||
StoryboardRendererDecoderRecommendedLevelFingerprint,
|
||||
StoryboardThumbnailParentFingerprint,
|
||||
SpoofSignaturePatchScrubbedPreviewLayoutFingerprint,
|
||||
StatsQueryParameterFingerprint,
|
||||
ParamsMapPutFingerprint,
|
||||
),
|
||||
dependencies = setOf(SpoofVideoStreamsPatch::class),
|
||||
) {
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch;"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
AddResourcesPatch(this::class)
|
||||
|
||||
SettingsPatch.PreferenceScreen.MISC.addPreferences(
|
||||
PreferenceScreen(
|
||||
key = "revanced_spoof_signature_verification_screen",
|
||||
sorting = Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_spoof_signature_verification_enabled"),
|
||||
SwitchPreference("revanced_spoof_signature_in_feed_enabled"),
|
||||
SwitchPreference("revanced_spoof_storyboard"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Hook the player parameters.
|
||||
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
|
||||
)
|
||||
|
||||
// Force the seekbar time and chapters to always show up.
|
||||
// This is used if the storyboard spec fetch fails, for viewing paid videos,
|
||||
// or if storyboard spoofing is turned off.
|
||||
StoryboardThumbnailParentFingerprint.result?.classDef?.let { classDef ->
|
||||
StoryboardThumbnailFingerprint.also {
|
||||
it.resolve(
|
||||
context,
|
||||
classDef,
|
||||
)
|
||||
}.result?.let {
|
||||
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
// Replace existing instruction to preserve control flow label.
|
||||
// The replaced return instruction always returns false
|
||||
// (it is the 'no thumbnails found' control path),
|
||||
// so there is no need to pass the existing return value to integrations.
|
||||
it.mutableMethod.replaceInstruction(
|
||||
endIndex,
|
||||
"""
|
||||
invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->getSeekbarThumbnailOverrideValue()Z
|
||||
""",
|
||||
)
|
||||
// Since this is end of the method must replace one line then add the rest.
|
||||
it.mutableMethod.addInstructions(
|
||||
endIndex + 1,
|
||||
"""
|
||||
move-result v0
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
} ?: throw StoryboardThumbnailFingerprint.exception
|
||||
}
|
||||
|
||||
// If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view.
|
||||
SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.result?.apply {
|
||||
val endIndex = scanResult.patternScanResult!!.endIndex
|
||||
mutableMethod.apply {
|
||||
val imageViewFieldName = getInstruction<ReferenceInstruction>(endIndex).reference
|
||||
addInstructions(
|
||||
implementation!!.instructions.lastIndex,
|
||||
"""
|
||||
iget-object v0, p0, $imageViewFieldName # copy imageview field to a register
|
||||
invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->seekbarImageViewCreated(Landroid/widget/ImageView;)V
|
||||
""",
|
||||
)
|
||||
}
|
||||
} ?: throw SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.exception
|
||||
|
||||
/**
|
||||
* Hook StoryBoard renderer url
|
||||
*/
|
||||
arrayOf(
|
||||
PlayerResponseModelImplGeneralFingerprint,
|
||||
PlayerResponseModelImplLiveStreamFingerprint,
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.result?.let {
|
||||
it.mutableMethod.apply {
|
||||
val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val getStoryBoardRegister =
|
||||
getInstruction<OneRegisterInstruction>(getStoryBoardIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
getStoryBoardIndex,
|
||||
"""
|
||||
invoke-static { v$getStoryBoardRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$getStoryBoardRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
} ?: throw fingerprint.exception
|
||||
}
|
||||
|
||||
// Hook recommended seekbar thumbnails quality level.
|
||||
StoryboardRendererDecoderRecommendedLevelFingerprint.result?.let {
|
||||
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val originalValueRegister = it.mutableMethod
|
||||
.getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA
|
||||
|
||||
it.mutableMethod.addInstructions(
|
||||
moveOriginalRecommendedValueIndex + 1,
|
||||
"""
|
||||
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
|
||||
move-result v$originalValueRegister
|
||||
""",
|
||||
)
|
||||
} ?: throw StoryboardRendererDecoderRecommendedLevelFingerprint.exception
|
||||
|
||||
// Hook the recommended precise seeking thumbnails quality level.
|
||||
PlayerResponseModelImplRecommendedLevelFingerprint.result?.let {
|
||||
it.mutableMethod.apply {
|
||||
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val originalValueRegister =
|
||||
getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveOriginalRecommendedValueIndex,
|
||||
"""
|
||||
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
|
||||
move-result v$originalValueRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
} ?: throw PlayerResponseModelImplRecommendedLevelFingerprint.exception
|
||||
|
||||
StoryboardRendererSpecFingerprint.result?.let {
|
||||
it.mutableMethod.apply {
|
||||
val storyBoardUrlParams = 0
|
||||
|
||||
addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
if-nez p$storyBoardUrlParams, :ignore
|
||||
invoke-static { p$storyBoardUrlParams }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object p$storyBoardUrlParams
|
||||
""",
|
||||
ExternalLabel("ignore", getInstruction(0)),
|
||||
)
|
||||
}
|
||||
} ?: throw StoryboardRendererSpecFingerprint.exception
|
||||
|
||||
// Hook the seekbar thumbnail decoder and use a NULL spec for live streams.
|
||||
StoryboardRendererDecoderSpecFingerprint.result?.let {
|
||||
val storyBoardUrlIndex = it.scanResult.patternScanResult!!.startIndex + 1
|
||||
val storyboardUrlRegister =
|
||||
it.mutableMethod.getInstruction<OneRegisterInstruction>(storyBoardUrlIndex).registerA
|
||||
|
||||
it.mutableMethod.addInstructions(
|
||||
storyBoardUrlIndex + 1,
|
||||
"""
|
||||
invoke-static { v$storyboardUrlRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardDecoderRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$storyboardUrlRegister
|
||||
""",
|
||||
)
|
||||
} ?: throw StoryboardRendererDecoderSpecFingerprint.exception
|
||||
|
||||
// Fix stats not being tracked.
|
||||
// Due to signature spoofing "adformat" is present in query parameters made for /stats requests,
|
||||
// even though, for regular videos, it should not be.
|
||||
// This breaks stats tracking.
|
||||
// Replace the ad parameter with the video parameter in the query parameters.
|
||||
StatsQueryParameterFingerprint.result?.let {
|
||||
val putMethod = ParamsMapPutFingerprint.result?.method?.toString()
|
||||
?: throw ParamsMapPutFingerprint.exception
|
||||
|
||||
it.mutableMethod.apply {
|
||||
val adParamIndex = it.scanResult.stringsScanResult!!.matches.first().index
|
||||
val videoParamIndex = adParamIndex + 3
|
||||
|
||||
// Replace the ad parameter with the video parameter.
|
||||
replaceInstruction(adParamIndex, getInstruction(videoParamIndex))
|
||||
|
||||
// Call paramsMap.put instead of paramsMap.putIfNotExist
|
||||
// because the key is already present in the map.
|
||||
val putAdParamIndex = adParamIndex + 1
|
||||
val putIfKeyNotExistsInstruction = getInstruction<FiveRegisterInstruction>(putAdParamIndex)
|
||||
replaceInstruction(
|
||||
putAdParamIndex,
|
||||
"invoke-virtual { " +
|
||||
"v${putIfKeyNotExistsInstruction.registerC}, " +
|
||||
"v${putIfKeyNotExistsInstruction.registerD}, " +
|
||||
"v${putIfKeyNotExistsInstruction.registerE} }, " +
|
||||
putMethod,
|
||||
)
|
||||
}
|
||||
} ?: throw StatsQueryParameterFingerprint.exception
|
||||
}
|
||||
override fun execute(context: BytecodeContext) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,8 @@ package app.revanced.patches.youtube.misc.fix.playback
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
|
||||
@Patch(dependencies = [ResourceMappingPatch::class])
|
||||
@Deprecated("This patch will be removed in the future.")
|
||||
object SpoofSignatureResourcePatch : ResourcePatch() {
|
||||
internal var scrubbedPreviewThumbnailResourceId: Long = -1
|
||||
|
||||
override fun execute(context: ResourceContext) {
|
||||
scrubbedPreviewThumbnailResourceId = ResourceMappingPatch[
|
||||
"id",
|
||||
"thumbnail",
|
||||
]
|
||||
}
|
||||
override fun execute(context: ResourceContext) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildInitPlaybackRequestFingerprint
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildMediaDataSourceFingerprint
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildPlayerRequestURIFingerprint
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildRequestFingerprint
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreateStreamingDataFingerprint
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.ProtobufClassParseByteBufferFingerprint
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
@Patch(
|
||||
name = "Spoof video streams",
|
||||
description = "Spoofs the client video streams to allow video playback.",
|
||||
dependencies = [
|
||||
SettingsPatch::class,
|
||||
AddResourcesPatch::class,
|
||||
UserAgentClientSpoofPatch::class,
|
||||
],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.youtube",
|
||||
[
|
||||
"18.37.36",
|
||||
"18.38.44",
|
||||
"18.43.45",
|
||||
"18.44.41",
|
||||
"18.45.43",
|
||||
"18.48.39",
|
||||
"18.49.37",
|
||||
"19.01.34",
|
||||
"19.02.39",
|
||||
"19.03.36",
|
||||
"19.04.38",
|
||||
"19.05.36",
|
||||
"19.06.39",
|
||||
"19.07.40",
|
||||
"19.08.36",
|
||||
"19.09.38",
|
||||
"19.10.39",
|
||||
"19.11.43",
|
||||
"19.12.41",
|
||||
"19.13.37",
|
||||
"19.14.43",
|
||||
"19.15.36",
|
||||
"19.16.39",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
object SpoofVideoStreamsPatch : BytecodePatch(
|
||||
setOf(
|
||||
BuildInitPlaybackRequestFingerprint,
|
||||
BuildPlayerRequestURIFingerprint,
|
||||
CreateStreamingDataFingerprint,
|
||||
BuildMediaDataSourceFingerprint,
|
||||
BuildRequestFingerprint,
|
||||
ProtobufClassParseByteBufferFingerprint,
|
||||
),
|
||||
) {
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofVideoStreamsPatch;"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
AddResourcesPatch(this::class)
|
||||
|
||||
SettingsPatch.PreferenceScreen.MISC.addPreferences(
|
||||
PreferenceScreen(
|
||||
key = "revanced_spoof_video_streams_screen",
|
||||
sorting = PreferenceScreen.Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_spoof_video_streams"),
|
||||
ListPreference(
|
||||
"revanced_spoof_video_streams_client",
|
||||
summaryKey = null
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_spoof_video_streams_ios_force_avc",
|
||||
tag = "app.revanced.integrations.youtube.settings.preference.ForceAVCSpoofingPreference",
|
||||
),
|
||||
NonInteractivePreference("revanced_spoof_video_streams_about_android_vr"),
|
||||
NonInteractivePreference("revanced_spoof_video_streams_about_ios"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// region Block /initplayback requests to fall back to /get_watch requests.
|
||||
|
||||
BuildInitPlaybackRequestFingerprint.resultOrThrow().let {
|
||||
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
|
||||
it.mutableMethod.apply {
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveUriStringIndex + 1,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$targetRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Block /get_watch requests to fall back to /player requests.
|
||||
|
||||
BuildPlayerRequestURIFingerprint.resultOrThrow().let {
|
||||
val invokeToStringIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
|
||||
it.mutableMethod.apply {
|
||||
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
invokeToStringIndex,
|
||||
"""
|
||||
invoke-static { v$uriRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$uriRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Get replacement streams at player requests.
|
||||
|
||||
BuildRequestFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
val newRequestBuilderIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "newUrlRequestBuilder"
|
||||
}
|
||||
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
val freeRegister = getInstruction<OneRegisterInstruction>(newRequestBuilderIndex + 1).registerA
|
||||
|
||||
addInstructions(
|
||||
newRequestBuilderIndex,
|
||||
"""
|
||||
move-object v$freeRegister, p1
|
||||
invoke-static { v$urlRegister, v$freeRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Replace the streaming data with the replacement streams.
|
||||
|
||||
CreateStreamingDataFingerprint.resultOrThrow().let { result ->
|
||||
result.mutableMethod.apply {
|
||||
val setStreamDataMethodName = "patch_setStreamingData"
|
||||
val resultMethodType = result.mutableClass.type
|
||||
val videoDetailsIndex = result.scanResult.patternScanResult!!.endIndex
|
||||
val videoDetailsRegister = getInstruction<TwoRegisterInstruction>(videoDetailsIndex).registerA
|
||||
val videoDetailsClass = getInstruction(videoDetailsIndex).getReference<FieldReference>()!!.type
|
||||
|
||||
addInstruction(
|
||||
videoDetailsIndex + 1,
|
||||
"invoke-direct { p0, v$videoDetailsRegister }, " +
|
||||
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
|
||||
)
|
||||
|
||||
val protobufClass = ProtobufClassParseByteBufferFingerprint.resultOrThrow().mutableMethod.definingClass
|
||||
val setStreamingDataIndex = result.scanResult.patternScanResult!!.startIndex
|
||||
|
||||
val playerProtoClass = getInstruction(setStreamingDataIndex + 1)
|
||||
.getReference<FieldReference>()!!.definingClass
|
||||
|
||||
val setStreamingDataField = getInstruction(setStreamingDataIndex).getReference<FieldReference>()
|
||||
|
||||
val getStreamingDataField = getInstruction(
|
||||
indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.definingClass == playerProtoClass
|
||||
}
|
||||
).getReference<FieldReference>()
|
||||
|
||||
// Use a helper method to avoid the need of picking out multiple free registers from the hooked code.
|
||||
result.mutableClass.methods.add(
|
||||
ImmutableMethod(
|
||||
resultMethodType,
|
||||
setStreamDataMethodName,
|
||||
listOf(ImmutableMethodParameter(videoDetailsClass, null, "videoDetails")),
|
||||
"V",
|
||||
AccessFlags.PRIVATE or AccessFlags.FINAL,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(9),
|
||||
).toMutable().apply {
|
||||
addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
|
||||
# Get video id.
|
||||
iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
|
||||
if-eqz v2, :disabled
|
||||
|
||||
# Get streaming data.
|
||||
invoke-static { v2 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
|
||||
move-result-object v3
|
||||
if-eqz v3, :disabled
|
||||
|
||||
# Parse streaming data.
|
||||
sget-object v4, $playerProtoClass->a:$playerProtoClass
|
||||
invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
|
||||
move-result-object v5
|
||||
check-cast v5, $playerProtoClass
|
||||
|
||||
# Set streaming data.
|
||||
iget-object v6, v5, $getStreamingDataField
|
||||
if-eqz v6, :disabled
|
||||
iput-object v6, p0, $setStreamingDataField
|
||||
|
||||
:disabled
|
||||
return-void
|
||||
""",
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Remove /videoplayback request body to fix playback.
|
||||
// This is needed when using iOS client as streaming data source.
|
||||
|
||||
BuildMediaDataSourceFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val targetIndex = getInstructions().lastIndex
|
||||
|
||||
// Instructions are added just before the method returns,
|
||||
// so there's no concern of clobbering in-use registers.
|
||||
addInstructions(
|
||||
targetIndex,
|
||||
"""
|
||||
# Field a: Stream uri.
|
||||
# Field c: Http method.
|
||||
# Field d: Post data.
|
||||
move-object v0, p0 # method has over 15 registers and must copy p0 to a lower register.
|
||||
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
|
||||
iget v2, v0, $definingClass->c:I
|
||||
iget-object v3, v0, $definingClass->d:[B
|
||||
invoke-static { v1, v2, v3 }, $INTEGRATIONS_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $definingClass->d:[B
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object BuildMediaDataSourceFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
returnType = "V",
|
||||
parameters = listOf(
|
||||
"Landroid/net/Uri;",
|
||||
"J",
|
||||
"I",
|
||||
"[B",
|
||||
"Ljava/util/Map;",
|
||||
"J",
|
||||
"J",
|
||||
"Ljava/lang/String;",
|
||||
"I",
|
||||
"Ljava/lang/Object;"
|
||||
)
|
||||
)
|
||||
@@ -3,13 +3,34 @@ package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object BuildRequestFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
returnType = "Lorg/chromium/net/UrlRequest;",
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_DIRECT,
|
||||
Opcode.INVOKE_VIRTUAL
|
||||
)
|
||||
customFingerprint = { methodDef, _ ->
|
||||
// Different targets have slightly different parameters
|
||||
|
||||
// Earlier targets have parameters:
|
||||
//L
|
||||
//Ljava/util/Map;
|
||||
//[B
|
||||
//L
|
||||
//L
|
||||
//L
|
||||
//Lorg/chromium/net/UrlRequest$Callback;
|
||||
|
||||
// Later targets have parameters:
|
||||
//L
|
||||
//Ljava/util/Map;
|
||||
//[B
|
||||
//L
|
||||
//L
|
||||
//L
|
||||
//Lorg/chromium/net/UrlRequest\$Callback;
|
||||
//L
|
||||
|
||||
val parameterTypes = methodDef.parameterTypes
|
||||
(parameterTypes.size == 7 || parameterTypes.size == 8)
|
||||
&& parameterTypes[1] == "Ljava/util/Map;" // URL headers.
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object CreatePlaybackSpeedMenuItemFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
opcodes = listOf(
|
||||
Opcode.IGET_OBJECT, // First instruction of the method
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
null // MOVE_RESULT or MOVE_RESULT_OBJECT, Return value controls the creation of the playback speed menu item.
|
||||
),
|
||||
// 19.01 and earlier is missing the second parameter.
|
||||
// Since this fingerprint is somewhat weak, work around by checking for both method parameter signatures.
|
||||
customFingerprint = custom@{ methodDef, _ ->
|
||||
// 19.01 and earlier parameters are: "[L"
|
||||
// 19.02+ parameters are "[L", "F"
|
||||
val parameterTypes = methodDef.parameterTypes
|
||||
val firstParameter = parameterTypes.firstOrNull()
|
||||
|
||||
if (firstParameter == null || !firstParameter.startsWith("[L")) {
|
||||
return@custom false
|
||||
}
|
||||
|
||||
parameterTypes.size == 1 || (parameterTypes.size == 2 && parameterTypes[1] == "F")
|
||||
}
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object CreatePlayerRequestBodyFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
parameters = listOf("L"),
|
||||
opcodes = listOf(
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.IGET,
|
||||
Opcode.AND_INT_LIT16,
|
||||
),
|
||||
strings = listOf("ms"),
|
||||
)
|
||||
@@ -1,31 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction
|
||||
import app.revanced.util.containsWideLiteralInstructionValue
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
|
||||
returnType = "L",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf(),
|
||||
customFingerprint = { methodDef, _ ->
|
||||
methodDef.containsWideLiteralInstructionValue(1073741824) &&
|
||||
indexOfBuildModelInstruction(methodDef) >= 0
|
||||
},
|
||||
) {
|
||||
fun indexOfBuildModelInstruction(methodDef: Method) =
|
||||
methodDef.indexOfFirstInstruction {
|
||||
val reference = getReference<FieldReference>()
|
||||
reference?.definingClass == "Landroid/os/Build;" &&
|
||||
reference.name == "MODEL" &&
|
||||
reference.type == "Ljava/lang/String;"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildVersionReleaseInstruction
|
||||
import app.revanced.util.containsWideLiteralInstructionValue
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
internal object CreatePlayerRequestBodyWithVersionReleaseFingerprint : MethodFingerprint(
|
||||
returnType = "L",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf(),
|
||||
customFingerprint = { methodDef, _ ->
|
||||
methodDef.containsWideLiteralInstructionValue(1073741824) &&
|
||||
indexOfBuildVersionReleaseInstruction(methodDef) >= 0
|
||||
},
|
||||
) {
|
||||
fun indexOfBuildVersionReleaseInstruction(methodDef: Method) =
|
||||
methodDef.indexOfFirstInstruction {
|
||||
val reference = getReference<FieldReference>()
|
||||
reference?.definingClass == "Landroid/os/Build\$VERSION;" &&
|
||||
reference.name == "RELEASE" &&
|
||||
reference.type == "Ljava/lang/String;"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object CreateStreamingDataFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
returnType = "V",
|
||||
parameters = listOf("L"),
|
||||
opcodes = listOf(
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.IPUT_OBJECT
|
||||
),
|
||||
customFingerprint = { methodDef, classDef ->
|
||||
classDef.fields.any { field ->
|
||||
field.name == "a" && field.type.endsWith("/StreamingDataOuterClass\$StreamingData;")
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -1,25 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object ParamsMapPutFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf(
|
||||
"Ljava/lang/String;",
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
opcodes = listOf(
|
||||
Opcode.CONST_4,
|
||||
Opcode.CONST_4,
|
||||
Opcode.CONST_4,
|
||||
Opcode.MOVE_OBJECT,
|
||||
Opcode.MOVE_OBJECT,
|
||||
Opcode.MOVE_OBJECT,
|
||||
Opcode.INVOKE_DIRECT_RANGE,
|
||||
),
|
||||
)
|
||||
@@ -1,49 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal object PlayerGestureConfigSyntheticFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("Ljava/lang/Object;"),
|
||||
opcodes = listOf(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutLandscapeAllowed.
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.IPUT_BOOLEAN,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutPortraitAllowed.
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IPUT_BOOLEAN,
|
||||
Opcode.RETURN_VOID,
|
||||
),
|
||||
customFingerprint = { methodDef, classDef ->
|
||||
fun indexOfDownAndOutAllowedInstruction(methodDef: Method) =
|
||||
methodDef.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
|
||||
reference.parameterTypes.isEmpty() &&
|
||||
reference.returnType == "Z"
|
||||
}
|
||||
|
||||
// This method is always called "a" because this kind of class always has a single method.
|
||||
methodDef.name == "a" && classDef.methods.count() == 2 &&
|
||||
indexOfDownAndOutAllowedInstruction(methodDef) >= 0
|
||||
},
|
||||
)
|
||||
@@ -1,25 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object PlayerResponseModelBackgroundAudioPlaybackFingerprint : MethodFingerprint(
|
||||
returnType = "Z",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
|
||||
opcodes = listOf(
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.GOTO,
|
||||
Opcode.RETURN,
|
||||
null, // Opcode.CONST_4 or Opcode.MOVE
|
||||
Opcode.RETURN,
|
||||
)
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.util.containsWideLiteralInstructionValue
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
|
||||
returnType = "Ljava/lang/String;",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
opcodes = listOf(
|
||||
Opcode.RETURN_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN_OBJECT,
|
||||
),
|
||||
customFingerprint = handler@{ methodDef, _ ->
|
||||
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
|
||||
|
||||
methodDef.containsWideLiteralInstructionValue(55735497)
|
||||
},
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.util.containsWideLiteralInstructionValue
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object PlayerResponseModelImplLiveStreamFingerprint : MethodFingerprint(
|
||||
returnType = "Ljava/lang/String;",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
opcodes = listOf(
|
||||
Opcode.RETURN_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN_OBJECT,
|
||||
),
|
||||
customFingerprint = handler@{ methodDef, _ ->
|
||||
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
|
||||
|
||||
methodDef.containsWideLiteralInstructionValue(70276274)
|
||||
},
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.util.containsWideLiteralInstructionValue
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint(
|
||||
returnType = "I",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
opcodes = listOf(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.IGET,
|
||||
Opcode.RETURN,
|
||||
),
|
||||
customFingerprint = handler@{ methodDef, _ ->
|
||||
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
|
||||
|
||||
methodDef.containsWideLiteralInstructionValue(55735497)
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object ProtobufClassParseByteBufferFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PROTECTED or AccessFlags.STATIC,
|
||||
parameters = listOf("L", "Ljava/nio/ByteBuffer;"),
|
||||
returnType = "L",
|
||||
opcodes = listOf(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
),
|
||||
customFingerprint = { methodDef, _ -> methodDef.name == "parseFrom" },
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.util.patch.LiteralValueFingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object SetPlayerRequestClientTypeFingerprint : LiteralValueFingerprint(
|
||||
opcodes = listOf(
|
||||
Opcode.IGET,
|
||||
Opcode.IPUT, // Sets ClientInfo.clientId.
|
||||
),
|
||||
strings = listOf("10.29"),
|
||||
literalSupplier = { 134217728 }
|
||||
)
|
||||
@@ -1,28 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patches.youtube.misc.fix.playback.SpoofSignatureResourcePatch
|
||||
import app.revanced.util.patch.LiteralValueFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object SpoofSignaturePatchScrubbedPreviewLayoutFingerprint : LiteralValueFingerprint(
|
||||
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
parameters = listOf("Landroid/content/Context;", "Landroid/util/AttributeSet;", "I", "I"),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.IPUT_OBJECT, // preview imageview
|
||||
),
|
||||
// This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to.
|
||||
literalSupplier = { SpoofSignatureResourcePatch.scrubbedPreviewThumbnailResourceId },
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object StatsQueryParameterFingerprint : MethodFingerprint(
|
||||
strings = listOf("adunit"),
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint].
|
||||
*/
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT,
|
||||
),
|
||||
strings = listOf("#-1#"),
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
|
||||
*/
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_INTERFACE, // First instruction of the method.
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_NEZ,
|
||||
),
|
||||
strings = listOf("#-1#"),
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
returnType = "L",
|
||||
parameters = listOf("Ljava/lang/String;", "J"),
|
||||
strings = listOf("\\|"),
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* Resolves using the class found in [StoryboardThumbnailParentFingerprint].
|
||||
*/
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object StoryboardThumbnailFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "Z",
|
||||
parameters = listOf(),
|
||||
opcodes = listOf(
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_GTZ,
|
||||
Opcode.GOTO,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN,
|
||||
Opcode.RETURN, // Last instruction of method.
|
||||
),
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
/**
|
||||
* Here lies code that creates the seekbar thumbnails.
|
||||
*
|
||||
* An additional change here might force the thumbnails to be created,
|
||||
* or possibly a change somewhere else (maybe involving YouTube 18.23.35 class `hte`)
|
||||
*/
|
||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||
internal object StoryboardThumbnailParentFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "Landroid/graphics/Bitmap;",
|
||||
strings = listOf("Storyboard regionDecoder.decodeRegion exception - "),
|
||||
)
|
||||
@@ -32,12 +32,11 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
|
||||
CompatiblePackage(
|
||||
"com.google.android.youtube",
|
||||
setOf(
|
||||
// Patch supports these versions but ClientSpoof does not.
|
||||
// "18.37.36",
|
||||
// "18.38.44",
|
||||
// "18.43.45",
|
||||
// "18.44.41",
|
||||
// "18.45.43",
|
||||
"18.37.36",
|
||||
"18.38.44",
|
||||
"18.43.45",
|
||||
"18.44.41",
|
||||
"18.45.43",
|
||||
"18.48.39",
|
||||
"18.49.37",
|
||||
"19.01.34",
|
||||
|
||||
@@ -3,70 +3,18 @@ package app.revanced.patches.youtube.misc.playercontrols
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.DomFileEditor
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import java.io.Closeable
|
||||
|
||||
@Patch(dependencies = [ResourceMappingPatch::class])
|
||||
@Patch(
|
||||
dependencies = [PlayerControlsBytecodePatch::class],
|
||||
)
|
||||
@Deprecated("Patch renamed to PlayerControlsResourcePatch", replaceWith = ReplaceWith("PlayerControlsBytecodePatch"))
|
||||
object BottomControlsResourcePatch : ResourcePatch(), Closeable {
|
||||
internal var bottomUiContainerResourceId: Long = -1
|
||||
override fun execute(context: ResourceContext) {}
|
||||
|
||||
private const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml"
|
||||
private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME"
|
||||
|
||||
// The element to the left of the element being added.
|
||||
private var lastLeftOf = "fullscreen_button"
|
||||
|
||||
private lateinit var resourceContext: ResourceContext
|
||||
private lateinit var targetDocumentEditor: DomFileEditor
|
||||
|
||||
override fun execute(context: ResourceContext) {
|
||||
resourceContext = context
|
||||
targetDocumentEditor = context.xmlEditor[TARGET_RESOURCE]
|
||||
|
||||
bottomUiContainerResourceId = ResourceMappingPatch["id", "bottom_ui_container_stub"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new controls to the bottom of the YouTube player.
|
||||
*
|
||||
* @param resourceDirectoryName The name of the directory containing the hosting resource.
|
||||
*/
|
||||
fun addControls(resourceDirectoryName: String) {
|
||||
val sourceDocumentEditor = resourceContext.xmlEditor[
|
||||
this::class.java.classLoader.getResourceAsStream(
|
||||
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
|
||||
)!!,
|
||||
]
|
||||
val sourceDocument = sourceDocumentEditor.file
|
||||
val targetDocument = targetDocumentEditor.file
|
||||
|
||||
val targetElementTag = "android.support.constraint.ConstraintLayout"
|
||||
|
||||
val sourceElements = sourceDocument.getElementsByTagName(targetElementTag).item(0).childNodes
|
||||
val targetElement = targetDocument.getElementsByTagName(targetElementTag).item(0)
|
||||
|
||||
for (index in 1 until sourceElements.length) {
|
||||
val element = sourceElements.item(index).cloneNode(true)
|
||||
|
||||
// If the element has no attributes there's 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 current element.
|
||||
val nameSpaceLength = 5
|
||||
lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength)
|
||||
|
||||
// Add the element.
|
||||
targetDocument.adoptNode(element)
|
||||
targetElement.appendChild(element)
|
||||
}
|
||||
sourceDocumentEditor.close()
|
||||
PlayerControlsResourcePatch.addBottomControls(resourceDirectoryName)
|
||||
}
|
||||
|
||||
override fun close() = targetDocumentEditor.close()
|
||||
}
|
||||
override fun close() {}
|
||||
}
|
||||
@@ -1,65 +1,144 @@
|
||||
package app.revanced.patches.youtube.misc.playercontrols
|
||||
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprintResult
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.youtube.shared.fingerprints.LayoutConstructorFingerprint
|
||||
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.BottomControlsInflateFingerprint
|
||||
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.ControlsOverlayVisibility
|
||||
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.OverlayViewInflateFingerprint
|
||||
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerBottomControlsInflateFingerprint
|
||||
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsIntegrationHookFingerprint
|
||||
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerTopControlsInflateFingerprint
|
||||
import app.revanced.util.alsoResolve
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstWideLiteralInstructionValueReversedOrThrow
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
|
||||
@Patch(
|
||||
description = "Manages the code for the player controls of the YouTube player.",
|
||||
dependencies = [BottomControlsResourcePatch::class],
|
||||
dependencies = [PlayerControlsResourcePatch::class],
|
||||
)
|
||||
object PlayerControlsBytecodePatch : BytecodePatch(
|
||||
setOf(LayoutConstructorFingerprint, BottomControlsInflateFingerprint)
|
||||
setOf(
|
||||
PlayerTopControlsInflateFingerprint,
|
||||
PlayerBottomControlsInflateFingerprint,
|
||||
OverlayViewInflateFingerprint,
|
||||
PlayerControlsIntegrationHookFingerprint
|
||||
)
|
||||
) {
|
||||
lateinit var showPlayerControlsFingerprintResult: MethodFingerprintResult
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/integrations/youtube/patches/PlayerControlsPatch;"
|
||||
|
||||
private var moveToRegisterInstructionIndex: Int = 0
|
||||
private var viewRegister: Int = 0
|
||||
private lateinit var inflateFingerprintResult: MethodFingerprintResult
|
||||
private lateinit var inflateTopControlMethod: MutableMethod
|
||||
private var inflateTopControlInsertIndex: Int = -1
|
||||
private var inflateTopControlRegister: Int = -1
|
||||
|
||||
private lateinit var inflateBottomControlMethod: MutableMethod
|
||||
private var inflateBottomControlInsertIndex: Int = -1
|
||||
private var inflateBottomControlRegister: Int = -1
|
||||
|
||||
private lateinit var visibilityMethod: MutableMethod
|
||||
private var visibilityInsertIndex: Int = 0
|
||||
|
||||
private lateinit var visibilityImmediateMethod: MutableMethod
|
||||
private var visibilityImmediateInsertIndex: Int = 0
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
LayoutConstructorFingerprint.result?.let {
|
||||
if (!PlayerControlsVisibilityFingerprint.resolve(context, it.classDef))
|
||||
throw LayoutConstructorFingerprint.exception
|
||||
} ?: throw LayoutConstructorFingerprint.exception
|
||||
fun MutableMethod.indexOfFirstViewInflateOrThrow() =
|
||||
indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Landroid/view/ViewStub;" &&
|
||||
reference.name == "inflate"
|
||||
}
|
||||
|
||||
showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!!
|
||||
PlayerBottomControlsInflateFingerprint.resultOrThrow().mutableMethod.apply{
|
||||
inflateBottomControlMethod = this
|
||||
|
||||
inflateFingerprintResult = BottomControlsInflateFingerprint.result!!.also {
|
||||
moveToRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
viewRegister =
|
||||
(it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA
|
||||
val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
|
||||
inflateBottomControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
|
||||
inflateBottomControlInsertIndex = inflateReturnObjectIndex + 1
|
||||
}
|
||||
|
||||
PlayerTopControlsInflateFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
inflateTopControlMethod = this
|
||||
|
||||
val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
|
||||
inflateTopControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
|
||||
inflateTopControlInsertIndex = inflateReturnObjectIndex + 1
|
||||
}
|
||||
|
||||
ControlsOverlayVisibility.alsoResolve(
|
||||
context, PlayerTopControlsInflateFingerprint
|
||||
).mutableMethod.apply {
|
||||
visibilityMethod = this
|
||||
}
|
||||
|
||||
// Hook the fullscreen close button. Used to fix visibility
|
||||
// when seeking and other situations.
|
||||
OverlayViewInflateFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
val resourceIndex = indexOfFirstWideLiteralInstructionValueReversedOrThrow(
|
||||
PlayerControlsResourcePatch.fullscreenButton
|
||||
)
|
||||
|
||||
val index = indexOfFirstInstructionOrThrow(resourceIndex) {
|
||||
opcode == Opcode.CHECK_CAST && getReference<TypeReference>()?.type ==
|
||||
"Landroid/widget/ImageView;"
|
||||
}
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstruction(index + 1, "invoke-static { v$register }, " +
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->setFullscreenCloseButton(Landroid/widget/ImageView;)V")
|
||||
}
|
||||
|
||||
visibilityImmediateMethod = PlayerControlsIntegrationHookFingerprint.resultOrThrow().mutableMethod
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the code to change the visibility of controls.
|
||||
* Injects the code to initialize the controls.
|
||||
* @param descriptor The descriptor of the method which should be called.
|
||||
*/
|
||||
fun injectVisibilityCheckCall(descriptor: String) {
|
||||
showPlayerControlsFingerprintResult.mutableMethod.addInstruction(
|
||||
0,
|
||||
"""
|
||||
invoke-static {p1}, $descriptor
|
||||
"""
|
||||
internal fun initializeTopControl(descriptor: String) {
|
||||
inflateTopControlMethod.addInstruction(
|
||||
inflateTopControlInsertIndex++,
|
||||
"invoke-static { v$inflateTopControlRegister }, $descriptor->initialize(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the code to initialize the controls.
|
||||
* @param descriptor The descriptor of the method which should be calleed.
|
||||
* @param descriptor The descriptor of the method which should be called.
|
||||
*/
|
||||
fun initializeControl(descriptor: String) {
|
||||
inflateFingerprintResult.mutableMethod.addInstruction(
|
||||
moveToRegisterInstructionIndex + 1,
|
||||
"invoke-static {v$viewRegister}, $descriptor"
|
||||
fun initializeBottomControl(descriptor: String) {
|
||||
inflateBottomControlMethod.addInstruction(
|
||||
inflateBottomControlInsertIndex++,
|
||||
"invoke-static { v$inflateBottomControlRegister }, $descriptor->initializeButton(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Injects the code to change the visibility of controls.
|
||||
* @param descriptor The descriptor of the method which should be called.
|
||||
*/
|
||||
fun injectVisibilityCheckCall(descriptor: String) {
|
||||
visibilityMethod.addInstruction(
|
||||
visibilityInsertIndex++,
|
||||
"invoke-static { p1 , p2 }, $descriptor->changeVisibility(ZZ)V"
|
||||
)
|
||||
|
||||
visibilityImmediateMethod.addInstruction(
|
||||
visibilityImmediateInsertIndex++,
|
||||
"invoke-static { p0 }, $descriptor->changeVisibilityImmediate(Z)V"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("Obsolete", replaceWith = ReplaceWith("initializeBottomControl"))
|
||||
fun initializeControl(descriptor: String)= initializeBottomControl(descriptor)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package app.revanced.patches.youtube.misc.playercontrols
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.DomFileEditor
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import app.revanced.util.copyXmlNode
|
||||
import app.revanced.util.findElementByAttributeValue
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
import app.revanced.util.inputStreamFromBundledResource
|
||||
import org.w3c.dom.Node
|
||||
import java.io.Closeable
|
||||
|
||||
@Patch(dependencies = [ResourceMappingPatch::class])
|
||||
object PlayerControlsResourcePatch : ResourcePatch(), Closeable {
|
||||
private const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml"
|
||||
private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME"
|
||||
|
||||
internal var bottomUiContainerResourceId: Long = -1L
|
||||
internal var controlsLayoutStub: Long = -1L
|
||||
internal var heatseekerViewstub = -1L
|
||||
internal var fullscreenButton = -1L
|
||||
|
||||
private lateinit var resourceContext: ResourceContext
|
||||
|
||||
/**
|
||||
* The element to the left of the element being added.
|
||||
*/
|
||||
private var bottomLastLeftOf = "@id/fullscreen_button"
|
||||
private lateinit var bottomInsertBeforeNode: Node
|
||||
private lateinit var bottomTargetDocumentEditor: DomFileEditor
|
||||
private lateinit var bottomTargetElement : Node
|
||||
|
||||
override fun execute(context: ResourceContext) {
|
||||
bottomUiContainerResourceId = ResourceMappingPatch["id", "bottom_ui_container_stub"]
|
||||
controlsLayoutStub = ResourceMappingPatch["id", "controls_layout_stub"]
|
||||
heatseekerViewstub = ResourceMappingPatch["id", "heatseeker_viewstub"]
|
||||
fullscreenButton = ResourceMappingPatch["id", "fullscreen_button"]
|
||||
|
||||
resourceContext = context
|
||||
bottomTargetDocumentEditor = context.xmlEditor[TARGET_RESOURCE]
|
||||
val document = bottomTargetDocumentEditor.file
|
||||
|
||||
bottomTargetElement = document.getElementsByTagName(
|
||||
"android.support.constraint.ConstraintLayout"
|
||||
).item(0)
|
||||
|
||||
bottomInsertBeforeNode = document.childNodes.findElementByAttributeValue(
|
||||
"android:inflatedId",
|
||||
bottomLastLeftOf
|
||||
) ?: document.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:id", // Older targets use non inflated id.
|
||||
bottomLastLeftOf
|
||||
)
|
||||
}
|
||||
|
||||
// Internal until this is modified to work with any patch (and not just SponsorBlock).
|
||||
internal fun addTopControls(resourceDirectoryName: String) {
|
||||
val hostingResourceStream = inputStreamFromBundledResource(
|
||||
resourceDirectoryName,
|
||||
"host/layout/youtube_controls_layout.xml",
|
||||
)!!
|
||||
|
||||
val editor = resourceContext.xmlEditor["res/layout/youtube_controls_layout.xml"]
|
||||
|
||||
"RelativeLayout".copyXmlNode(
|
||||
resourceContext.xmlEditor[hostingResourceStream],
|
||||
editor,
|
||||
).use {
|
||||
val element = editor.file.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:id",
|
||||
"@id/player_video_heading"
|
||||
)
|
||||
|
||||
// FIXME: This uses hard coded values that only works with SponsorBlock.
|
||||
// If other top buttons are added by other patches, this code must be changed.
|
||||
// voting button id from the voting button view from the youtube_controls_layout.xml host file
|
||||
val votingButtonId = "@+id/revanced_sb_voting_button"
|
||||
element.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new controls to the bottom of the YouTube player.
|
||||
*
|
||||
* @param resourceDirectoryName The name of the directory containing the hosting resource.
|
||||
*/
|
||||
fun addBottomControls(resourceDirectoryName: String) {
|
||||
val sourceDocumentEditor = resourceContext.xmlEditor[
|
||||
this::class.java.classLoader.getResourceAsStream(
|
||||
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
|
||||
)!!,
|
||||
]
|
||||
|
||||
val sourceElements = sourceDocumentEditor.file.getElementsByTagName(
|
||||
"android.support.constraint.ConstraintLayout"
|
||||
).item(0).childNodes
|
||||
|
||||
// Copy the patch layout xml into the target layout file.
|
||||
for (index in 1 until sourceElements.length) {
|
||||
val element = sourceElements.item(index).cloneNode(true)
|
||||
|
||||
// If the element has no attributes there's no point to adding it to the destination.
|
||||
if (!element.hasAttributes()) continue
|
||||
|
||||
element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue = bottomLastLeftOf
|
||||
bottomLastLeftOf = element.attributes.getNamedItem("android:id").nodeValue
|
||||
|
||||
bottomTargetDocumentEditor.file.adoptNode(element)
|
||||
// Elements do not need to be added in the layout order since a layout constraint is used,
|
||||
// but in order is easier to make sense of while debugging.
|
||||
bottomTargetElement.insertBefore(element, bottomInsertBeforeNode)
|
||||
bottomInsertBeforeNode = element
|
||||
}
|
||||
|
||||
sourceDocumentEditor.close()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
arrayOf(
|
||||
"@id/bottom_end_container",
|
||||
"@id/multiview_button",
|
||||
).forEach {
|
||||
bottomTargetDocumentEditor.file.childNodes.findElementByAttributeValue(
|
||||
"android:id",
|
||||
it
|
||||
)?.setAttribute("yt:layout_constraintRight_toLeftOf", bottomLastLeftOf)
|
||||
}
|
||||
|
||||
bottomTargetDocumentEditor.close()
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
|
||||
import app.revanced.util.patch.LiteralValueFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object BottomControlsInflateFingerprint : LiteralValueFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC,
|
||||
returnType = "L",
|
||||
parameters = listOf(),
|
||||
opcodes = listOf(
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT
|
||||
),
|
||||
literalSupplier = { BottomControlsResourcePatch.bottomUiContainerResourceId }
|
||||
)
|
||||
@@ -4,7 +4,10 @@ import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object PlayerControlsVisibilityFingerprint : MethodFingerprint(
|
||||
/**
|
||||
* Resolves to the class found in [PlayerTopControlsInflateFingerprint].
|
||||
*/
|
||||
internal object ControlsOverlayVisibility : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
parameters = listOf("Z", "Z")
|
||||
@@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
|
||||
import app.revanced.util.containsWideLiteralInstructionValue
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object OverlayViewInflateFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
parameters = listOf("Landroid/view/View;"),
|
||||
customFingerprint = { methodDef, _ ->
|
||||
methodDef.containsWideLiteralInstructionValue(PlayerControlsResourcePatch.fullscreenButton) &&
|
||||
methodDef.containsWideLiteralInstructionValue(PlayerControlsResourcePatch.heatseekerViewstub)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
|
||||
|
||||
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
|
||||
import app.revanced.util.patch.LiteralValueFingerprint
|
||||
|
||||
internal object PlayerBottomControlsInflateFingerprint : LiteralValueFingerprint(
|
||||
returnType = "Ljava/lang/Object;",
|
||||
parameters = listOf(),
|
||||
literalSupplier = { PlayerControlsResourcePatch.bottomUiContainerResourceId }
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object PlayerControlsIntegrationHookFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
returnType = "V",
|
||||
parameters = listOf("Z"),
|
||||
customFingerprint = { methodDef, classDef ->
|
||||
methodDef.name == "fullscreenButtonVisibilityChanged" &&
|
||||
classDef.type == "Lapp/revanced/integrations/youtube/patches/PlayerControlsPatch;"
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
|
||||
import app.revanced.util.patch.LiteralValueFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object PlayerTopControlsInflateFingerprint : LiteralValueFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
parameters = listOf(),
|
||||
literalSupplier = { PlayerControlsResourcePatch.controlsLayoutStub }
|
||||
)
|
||||
@@ -32,7 +32,7 @@ object PlaybackSpeedButtonPatch : BytecodePatch(emptySet()) {
|
||||
SwitchPreference("revanced_playback_speed_dialog_button"),
|
||||
)
|
||||
|
||||
PlayerControlsBytecodePatch.initializeControl("$SPEED_BUTTON_CLASS_DESCRIPTOR->initializeButton(Landroid/view/View;)V")
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$SPEED_BUTTON_CLASS_DESCRIPTOR->changeVisibility(Z)V")
|
||||
PlayerControlsBytecodePatch.initializeBottomControl(SPEED_BUTTON_CLASS_DESCRIPTOR)
|
||||
PlayerControlsBytecodePatch.injectVisibilityCheckCall(SPEED_BUTTON_CLASS_DESCRIPTOR)
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,12 @@ package app.revanced.patches.youtube.video.speed.button
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
|
||||
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.copyResources
|
||||
|
||||
@Patch(
|
||||
dependencies = [BottomControlsResourcePatch::class],
|
||||
dependencies = [PlayerControlsResourcePatch::class],
|
||||
)
|
||||
internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() {
|
||||
override fun execute(context: ResourceContext) {
|
||||
@@ -20,6 +20,6 @@ internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() {
|
||||
),
|
||||
)
|
||||
|
||||
BottomControlsResourcePatch.addControls("speedbutton")
|
||||
PlayerControlsResourcePatch.addBottomControls("speedbutton")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import org.stringtemplate.v4.compiler.Bytecode.instructions
|
||||
|
||||
fun MethodFingerprint.resultOrThrow() = result ?: throw exception
|
||||
|
||||
@@ -73,7 +74,7 @@ fun MutableMethod.injectHideViewCall(
|
||||
* @param resourceName the name of the resource to find the id for.
|
||||
* @return the index of the first instruction with the id of the given resource name, or -1 if not found.
|
||||
* @throws PatchException if the resource cannot be found.
|
||||
* @see [indexOfIdResourceOrThrow]
|
||||
* @see [indexOfIdResourceOrThrow], [indexOfFirstWideLiteralInstructionValueReversed]
|
||||
*/
|
||||
fun Method.indexOfIdResource(resourceName: String): Int {
|
||||
val resourceId = ResourceMappingPatch["id", resourceName]
|
||||
@@ -86,6 +87,7 @@ fun Method.indexOfIdResource(resourceName: String): Int {
|
||||
* Requires [ResourceMappingPatch] as a dependency.
|
||||
*
|
||||
* @throws [PatchException] if the resource is not found, or the method does not contain the resource id literal value.
|
||||
* @see [indexOfIdResource], [indexOfFirstWideLiteralInstructionValueReversedOrThrow]
|
||||
*/
|
||||
fun Method.indexOfIdResourceOrThrow(resourceName: String): Int {
|
||||
val index = indexOfIdResource(resourceName)
|
||||
@@ -120,6 +122,30 @@ fun Method.indexOfFirstWideLiteralInstructionValueOrThrow(literal: Long): Int {
|
||||
return index
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the last wide literal instruction with the given value.
|
||||
*
|
||||
* @return the last literal instruction with the value, or -1 if not found.
|
||||
* @see indexOfFirstWideLiteralInstructionValueOrThrow
|
||||
*/
|
||||
fun Method.indexOfFirstWideLiteralInstructionValueReversed(literal: Long) = implementation?.let {
|
||||
it.instructions.indexOfLast { instruction ->
|
||||
(instruction as? WideLiteralInstruction)?.wideLiteral == literal
|
||||
}
|
||||
} ?: -1
|
||||
|
||||
/**
|
||||
* Find the index of the last wide literal instruction with the given value,
|
||||
* or throw an exception if not found.
|
||||
*
|
||||
* @return the last literal instruction with the value, or throws [PatchException] if not found.
|
||||
*/
|
||||
fun Method.indexOfFirstWideLiteralInstructionValueReversedOrThrow(literal: Long): Int {
|
||||
val index = indexOfFirstWideLiteralInstructionValueReversed(literal)
|
||||
if (index < 0) throw PatchException("Could not find literal value: $literal")
|
||||
return index
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the method contains a literal with the given value.
|
||||
*
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package app.revanced.util
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.util.DomFileEditor
|
||||
import app.revanced.util.resource.BaseResource
|
||||
import org.w3c.dom.Attr
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
import java.io.InputStream
|
||||
@@ -39,6 +42,14 @@ fun Node.doRecursively(action: (Node) -> Unit) {
|
||||
for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action)
|
||||
}
|
||||
|
||||
fun Node.insertFirst(node: Node) {
|
||||
if (hasChildNodes()) {
|
||||
insertBefore(node, firstChild)
|
||||
} else {
|
||||
appendChild(node)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy resources from the current class loader to the resource directory.
|
||||
*
|
||||
@@ -49,7 +60,7 @@ fun ResourceContext.copyResources(
|
||||
sourceResourceDirectory: String,
|
||||
vararg resources: ResourceGroup,
|
||||
) {
|
||||
val targetResourceDirectory = this.get("res")
|
||||
val targetResourceDirectory = this["res", false]
|
||||
|
||||
for (resourceGroup in resources) {
|
||||
resourceGroup.resources.forEach { resource ->
|
||||
@@ -164,3 +175,37 @@ internal fun Node.addResource(
|
||||
}
|
||||
|
||||
internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0)
|
||||
|
||||
internal fun NodeList.findElementByAttributeValue(attributeName: String, value: String): Element? {
|
||||
for (i in 0 until length) {
|
||||
val node = item(i)
|
||||
if (node.nodeType == Node.ELEMENT_NODE) {
|
||||
val element = node as Element
|
||||
|
||||
if (element.getAttribute(attributeName) == value) {
|
||||
return element
|
||||
}
|
||||
|
||||
// Recursively search.
|
||||
val found = element.childNodes.findElementByAttributeValue(attributeName, value)
|
||||
if (found != null) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String): Element {
|
||||
return findElementByAttributeValue(attributeName, value) ?: throw PatchException("Could not find: $attributeName $value")
|
||||
}
|
||||
|
||||
internal fun Element.copyAttributesFrom(oldContainer: Element) {
|
||||
// Copy attributes from the old element to the new element
|
||||
val attributes = oldContainer.attributes
|
||||
for (i in 0 until attributes.length) {
|
||||
val attr = attributes.item(i) as Attr
|
||||
setAttribute(attr.name, attr.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,15 +32,16 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
-->
|
||||
<resources>
|
||||
<app id="shared">
|
||||
<patch id="misc.checks.BaseCheckEnvironmentPatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.BaseSettingsResourcePatch">
|
||||
<!-- Settings about dialog. -->
|
||||
</patch>
|
||||
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
</app>
|
||||
<app id="youtube">
|
||||
<patch id="misc.settings.SettingsResourcePatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.SettingsPatch">
|
||||
</patch>
|
||||
<patch id="misc.debugging.DebuggingPatch">
|
||||
@@ -57,7 +58,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
|
||||
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
|
||||
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
<patch id="ad.general.HideAdsResourcePatch">
|
||||
@@ -232,10 +233,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.EnableSlideToSeekPatch">
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
||||
</patch>
|
||||
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
|
||||
<patch id="misc.fix.playback.SpoofSignaturePatch">
|
||||
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
|
||||
</patch>
|
||||
<!-- This patch is no longer used and these strings will soon be deleted. -->
|
||||
<patch id="video.hdrbrightness.HDRBrightnessPatch">
|
||||
|
||||
@@ -32,15 +32,16 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
-->
|
||||
<resources>
|
||||
<app id="shared">
|
||||
<patch id="misc.checks.BaseCheckEnvironmentPatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.BaseSettingsResourcePatch">
|
||||
<!-- Settings about dialog. -->
|
||||
</patch>
|
||||
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
</app>
|
||||
<app id="youtube">
|
||||
<patch id="misc.settings.SettingsResourcePatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.SettingsPatch">
|
||||
</patch>
|
||||
<patch id="misc.debugging.DebuggingPatch">
|
||||
@@ -57,7 +58,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
|
||||
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
|
||||
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
<patch id="ad.general.HideAdsResourcePatch">
|
||||
@@ -232,10 +233,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.EnableSlideToSeekPatch">
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
||||
</patch>
|
||||
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
|
||||
<patch id="misc.fix.playback.SpoofSignaturePatch">
|
||||
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
|
||||
</patch>
|
||||
<!-- This patch is no longer used and these strings will soon be deleted. -->
|
||||
<patch id="video.hdrbrightness.HDRBrightnessPatch">
|
||||
|
||||
@@ -32,6 +32,17 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
-->
|
||||
<resources>
|
||||
<app id="shared">
|
||||
<patch id="misc.checks.BaseCheckEnvironmentPatch">
|
||||
<string name="revanced_check_environment_failed_title">فشلت عمليات التحقق</string>
|
||||
<string name="revanced_check_environment_dialog_open_official_source_button">فتح الموقع الرسمي</string>
|
||||
<string name="revanced_check_environment_dialog_ignore_button">تجاهل</string>
|
||||
<string name="revanced_check_environment_failed_message"><h5>لا يبدو أن هذا التطبيق قد تم تعديله من قبلك.</h5><br>قد لا يعمل هذا التطبيق بشكل صحيح، <b>قد يكون ضارًا أو حتى خطيرًا للاستخدام</b>.<br><br>تشير هذه الفحوصات إلى أن هذا التطبيق تم تعديله مسبقًا أو تم الحصول عليه من شخص آخر:<br><br><small>%1$s</small><br>يوصى بشدة بـ <b>إلغاء تثبيت هذا التطبيق وتعديله بنفسك</b> للتأكد من أنك تستخدم تطبيقًا معتمدًا وآمنًا.<p><br>في حالة تجاهل هذا التحذير، سيتم عرضه مرتين فقط.</string>
|
||||
<string name="revanced_check_environment_not_same_patching_device">تم تعديله على جهاز مختلف</string>
|
||||
<string name="revanced_check_environment_manager_not_expected_installer">لم يتم تثبيته بواسطة ReVanced Manager</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time">تم تعديله قبل أكثر من 10 دقائق</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_days">تم التعديل منذ %s يوم</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_invalid">تاريخ إنشاء APK تالف</string>
|
||||
</patch>
|
||||
<patch id="misc.settings.BaseSettingsResourcePatch">
|
||||
<string name="revanced_settings_confirm_user_dialog_title">هل ترغب في المتابعة؟</string>
|
||||
<string name="revanced_settings_reset">إعادة التعيين</string>
|
||||
@@ -42,6 +53,14 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_settings_import_reset">إعادة تعيين إعدادات ReVanced إلى الوضع الافتراضي</string>
|
||||
<string name="revanced_settings_import_success">تم استيراد %d إعدادات</string>
|
||||
<string name="revanced_settings_import_failure_parse">فشل الاستيراد: %s</string>
|
||||
<string name="revanced_pref_import_export_title">استيراد / تصدير</string>
|
||||
<string name="revanced_pref_import_export_summary">استيراد / تصدير إعدادات ReVanced</string>
|
||||
<!-- Settings about dialog. -->
|
||||
<string name="revanced_settings_about_links_body">أنت تستخدم إصدار ReVanced Patches <i>%s</i></string>
|
||||
<string name="revanced_settings_about_links_dev_header">ملاحظة</string>
|
||||
<string name="revanced_settings_about_links_dev_body">هذا الإصدار هو إصدار مسبق، وقد تواجه مشاكل غير متوقعة</string>
|
||||
<string name="revanced_settings_about_links_header">الروابط الرسمية</string>
|
||||
<string name="revanced_settings_about_links_donate">تبرع</string>
|
||||
</patch>
|
||||
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
@@ -54,14 +73,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
</patch>
|
||||
</app>
|
||||
<app id="youtube">
|
||||
<patch id="misc.settings.SettingsResourcePatch">
|
||||
<string name="revanced_settings_about_links_body">أنت تستخدم إصدار ReVanced Patches <i>%s</i></string>
|
||||
<string name="revanced_settings_about_links_dev_header">ملاحظة</string>
|
||||
<string name="revanced_settings_about_links_dev_body">هذا الإصدار هو إصدار مسبق، وقد تواجه مشاكل غير متوقعة</string>
|
||||
<string name="revanced_settings_about_links_header">الروابط الرسمية</string>
|
||||
<string name="revanced_pref_import_export_title">استيراد / تصدير</string>
|
||||
<string name="revanced_pref_import_export_summary">استيراد / تصدير إعدادات ReVanced</string>
|
||||
</patch>
|
||||
<patch id="misc.settings.SettingsPatch">
|
||||
<string name="revanced_settings_screen_00_about_title">لمحة</string>
|
||||
<string name="revanced_settings_screen_01_ads_title">الإعلانات</string>
|
||||
@@ -136,6 +147,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_hide_channel_guidelines_title">إخفاء إرشادات القناة</string>
|
||||
<string name="revanced_hide_channel_guidelines_summary_on">تم إخفاء إرشادات القناة</string>
|
||||
<string name="revanced_hide_channel_guidelines_summary_off">يتم عرض إرشادات القناة</string>
|
||||
<string name="revanced_hide_chips_shelf_title">إخفاء رف الشرائح</string>
|
||||
<string name="revanced_hide_chips_shelf_summary_on">تم إخفاء رف الشرائح</string>
|
||||
<string name="revanced_hide_chips_shelf_summary_off">يتم عرض رف الشرائح</string>
|
||||
<string name="revanced_hide_expandable_chip_title">إخفاء الشريحة القابلة للتوسيع تحت مقاطع الفيديو</string>
|
||||
<string name="revanced_hide_expandable_chip_summary_on">تم إخفاء الرقائق القابلة للتوسيع</string>
|
||||
<string name="revanced_hide_expandable_chip_summary_off">يتم عرض الرقائق القابلة للتوسيع</string>
|
||||
@@ -196,9 +210,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_hide_artist_cards_title">إخفاء بطاقات الفنان</string>
|
||||
<string name="revanced_hide_artist_cards_summary_on">تم إخفاء بطاقات الفنان</string>
|
||||
<string name="revanced_hide_artist_cards_summary_off">يتم عرض بطاقات الفنان</string>
|
||||
<string name="revanced_hide_chips_shelf_title">إخفاء رف الشرائح</string>
|
||||
<string name="revanced_hide_chips_shelf_summary_on">تم إخفاء رف الشرائح</string>
|
||||
<string name="revanced_hide_chips_shelf_summary_off">يتم عرض رف الشرائح</string>
|
||||
<string name="revanced_hide_attributes_section_title">إخفاء قسم الصفات</string>
|
||||
<string name="revanced_hide_attributes_section_summary_on">تم إخفاء \'الأماكن المميزة\'، أقسام الألعاب والموسيقى</string>
|
||||
<string name="revanced_hide_attributes_section_summary_off">يتم عرض \'الأماكن المميزة\'، أقسام الألعاب والموسيقى</string>
|
||||
@@ -242,14 +253,18 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_hide_keyword_content_phrases_title">الكلمات المفتاحية المراد إخفاؤها</string>
|
||||
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
|
||||
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
|
||||
<string name="revanced_hide_keyword_content_phrases_summary">الكلمات المفتاحية والعبارات المراد إخفاؤها، مفصولة بأسطر جديد\n\nالكلمات التي تحتوي على أحرف كبيرة في الوسط يجب إدخالها مع التغليف (مثل: iPhone, TikTok, Leblanc)</string>
|
||||
<string name="revanced_hide_keyword_content_phrases_summary">الكلمات والعبارات الرئيسية التي يجب إخفاؤها، مفصولة بسطر جديد\n\nالكلمات الرئيسية يمكن أن تكون أسماء قنوات أو أي نص يظهر في عناوين الفيديو\n\nيجب إدخال الكلمات التي تحتوي على أحرف كبيرة في الوسط بإستخدام الأحرف الكبيرة (مثال: iPhone، TikTok، LeBlanc)</string>
|
||||
<string name="revanced_hide_keyword_content_about_title">حول تصفية الكلمات المفتاحية</string>
|
||||
<string name="revanced_hide_keyword_content_about_summary">الصفحة الرئيسية/الاشتراك/نتائج البحث يتم تصفيتها لإخفاء المحتوى الذي يطابق عبارات الكلمات المفتاحية\n\nالتقييد\n• بعض المقاطع القصيرة قد لا تكون مخفية\n• بعض مكونات واجهة المستخدم قد لا تكون مخفية\n• البحث عن كلمة رئيسية قد لا يظهر أي نتائج</string>
|
||||
<string name="revanced_hide_keyword_content_about_summary">الصفحة الرئيسية/الاشتراكات/نتائج الإشتراك/يتم تصفية نتائج البحث لإخفاء المحتوى الذي يتطابق مع عبارات الكلمات الرئيسية\n\nالقيود\n• لا يمكن إخفاء فيديوهات Shorts بواسطة اسم القناة\n• قد لا تكون بعض مكونات واجهة المستخدم مخفية\n• قد لا تظهر نتائج بحث عن كلمة رئيسية</string>
|
||||
<string name="revanced_hide_keyword_content_about_whole_words_title">مطابقة الكلمات بأكملها</string>
|
||||
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
|
||||
<string name="revanced_hide_keyword_content_about_whole_words_summary">سيؤدي وضع علامة اقتباس مزدوجة حول كلمة رئيسية/عبارة إلى منع التطابقات الجزئية لعناوين الفيديو وأسماء القنوات.<br><br>على سبيل المثال،<br><b>\"ai\"</b> سيخفي الفيديو: <b>How does AI work?</b><br>ولكن لن يخفي: <b>What does fair use mean?</b></string>
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<string name="revanced_hide_keyword_toast_invalid_common">الكلمة المفتاحية غير صالحة. لا يمكن استخدام: \'%s\' كعامل تصفية</string>
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<string name="revanced_hide_keyword_toast_invalid_length">الكلمة المفتاحية غير صالحة. \'%1$s\' أقل من %2$d حرفًا</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_broad">الكلمة الرئيسية \'%s\' سوف تخفي جميع الفيديوهات</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_common">لا يمكن استخدام الكلمة الرئيسية: %s</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_common_whole_word_required">إضافة اقتباسات لاستخدام الكلمة الرئيسية: %s</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_conflicting">الكلمة الرئيسية لها بيانات متضاربة: %s</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_length">الكلمة الرئيسية قصيرة جدًا وتتطلب اقتباسات: %s</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_broad">الكلمة الرئيسية سوف تخفي جميع الفيديوهات: %s</string>
|
||||
</patch>
|
||||
<patch id="ad.general.HideAdsResourcePatch">
|
||||
<string name="revanced_hide_general_ads_title">إخفاء الإعلانات العامة</string>
|
||||
@@ -612,12 +627,15 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_hide_shorts_location_label_title">إخفاء تسمية الموقع</string>
|
||||
<string name="revanced_hide_shorts_location_label_summary_on">تم إخفاء تسمية الموقع</string>
|
||||
<string name="revanced_hide_shorts_location_label_summary_off">يتم عرض تسمية الموقع</string>
|
||||
<string name="revanced_hide_shorts_save_sound_button_title">إخفاء حفظ الصوت إلى زر قائمة التشغيل</string>
|
||||
<string name="revanced_hide_shorts_save_sound_button_summary_on">تم إخفاء حفظ الصوت في قائمة التشغيل</string>
|
||||
<string name="revanced_hide_shorts_save_sound_button_summary_off">يتم عرض حفظ الصوت في قائمة التشغيل</string>
|
||||
<string name="revanced_hide_shorts_save_sound_button_title">إخفاء زر حفظ الموسيقى</string>
|
||||
<string name="revanced_hide_shorts_save_sound_button_summary_on">حفظ الموسيقى مخفية</string>
|
||||
<string name="revanced_hide_shorts_save_sound_button_summary_off">حفظ الموسيقى يظهر</string>
|
||||
<string name="revanced_hide_shorts_search_suggestions_title">إخفاء اقتراحات البحث</string>
|
||||
<string name="revanced_hide_shorts_search_suggestions_summary_on">تم إخفاء اقتراحات البحث</string>
|
||||
<string name="revanced_hide_shorts_search_suggestions_summary_off">يتم عرض اقتراحات البحث</string>
|
||||
<string name="revanced_hide_shorts_stickers_title">إخفاء الملصقات</string>
|
||||
<string name="revanced_hide_shorts_stickers_summary_on">الملصقات مخفية</string>
|
||||
<string name="revanced_hide_shorts_stickers_summary_off">يتم عرض الملصقات</string>
|
||||
<string name="revanced_hide_shorts_like_button_title">إخفاء زر أعجبني</string>
|
||||
<string name="revanced_hide_shorts_like_button_summary_on">تم إخفاء زر أعجبني</string>
|
||||
<string name="revanced_hide_shorts_like_button_summary_off">يتم عرض زر أعجبني</string>
|
||||
@@ -678,7 +696,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_player_overlay_opacity_invalid_toast">شفافية واجهة المشغل يجب أن تكون بين 0-100</string>
|
||||
</patch>
|
||||
<patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch">
|
||||
<string name="revanced_ryd_video_likes_hidden_by_video_owner">مخفي</string>
|
||||
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
|
||||
<string name="revanced_ryd_failure_connection_timeout">لم يعجبني غير متاح مؤقتًا (انتهت مهلة API)</string>
|
||||
<string name="revanced_ryd_failure_connection_status_code">لم يعجبني غير متاح (الحالة %d)</string>
|
||||
@@ -893,6 +910,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_sb_stats_username_changed">تم تغيير اسم المستخدم بنجاح</string>
|
||||
<string name="revanced_sb_stats_reputation">سمعتك هي <b>%.2f</b></string>
|
||||
<string name="revanced_sb_stats_submissions">لقد أنشأت <b>%s</b> مقطع</string>
|
||||
<string name="revanced_sb_stats_submissions_sum">اضغط هنا لعرض المقاطع الخاصة بك</string>
|
||||
<string name="revanced_sb_stats_saved_zero">متصدرين SponsorBlock</string>
|
||||
<string name="revanced_sb_stats_saved">لقد قمت بحفظ الناس من <b>%s</b> مقطع</string>
|
||||
<string name="revanced_sb_stats_saved_sum_zero">اضغط هنا لرؤية الإحصائيات العالمية وأبرز المساهمين</string>
|
||||
@@ -1114,27 +1132,23 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_slide_to_seek_summary_on">تم تمكين Slide to Seek</string>
|
||||
<string name="revanced_slide_to_seek_summary_off">تم تعطيل Slide to Seek</string>
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
||||
<string name="revanced_spoof_client_screen_title">Spoof Client</string>
|
||||
<string name="revanced_spoof_client_screen_summary">محاكاة العميل لمنع مشكلات التشغيل</string>
|
||||
<string name="revanced_spoof_client_title">Spoof Client</string>
|
||||
<string name="revanced_spoof_client_summary_on">يتم محاكاة العميل</string>
|
||||
<string name="revanced_spoof_client_summary_off">لا يتم محاكاة العميل\n\nقد لا يعمل تشغيل الفيديو</string>
|
||||
<string name="revanced_spoof_client_user_dialog_message">إيقاف تشغيل هذا الإعداد قد يسبب مشاكل في تشغيل الفيديو.</string>
|
||||
<string name="revanced_spoof_client_type_title">نوع Spoof Client</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_title">فرض AVC iOS (H.264)</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_summary_on">ترميز فيديو iOS هو AVC</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_summary_off">ترميز فيديو iOS هو AVC أو VP9 أو AV1</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_user_dialog_message">قد يؤدي تمكين هذا إلى تحسين عمر البطارية وإصلاح مشكلة تقطيع التشغيل.\n\nيتمتع تنسيق AVC بدقة قصوى تبلغ 1080P، وسيستخدم تشغيل الفيديو المزيد من بيانات الإنترنت مقارنةً بتنسيق VP9 أو AV1.</string>
|
||||
<string name="revanced_spoof_client_about_android_ios_title">التأثيرات الجانبية لمحاكاة iOS</string>
|
||||
<string name="revanced_spoof_client_about_android_ios_summary">• HDR مدعوم فقط مع ترميز AV1\n• سجل المشاهدة لا يعمل مع حساب العلامة التجارية</string>
|
||||
<string name="revanced_spoof_client_about_android_vr_title">التأثيرات الجانبية لمحاكاة Android VR</string>
|
||||
<string name="revanced_spoof_client_about_android_vr_summary">• لا يوجد فيديو HDR\n• لا يتم تشغيل مقاطع فيديو الأطفال\n• يمكن استئناف مقاطع الفيديو المتوقفة مؤقتًا بشكل عشوائي\n• مصغرات شريط تقدم فيديوهات Shorts منخفضة الجودة\n• زر إجراء التنزيل مخفي\n• بطاقات شاشة النهاية مخفية</string>
|
||||
<string name="revanced_spoof_client_storyboard_timeout">محاكاة مصغرات العميل غير متوفرة (انتهت مهلة API)</string>
|
||||
<string name="revanced_spoof_client_storyboard_io_exception">محاكاة مصغرات العميل غير متوفرة مؤقتًا: %s</string>
|
||||
</patch>
|
||||
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
|
||||
<patch id="misc.fix.playback.SpoofSignaturePatch">
|
||||
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
|
||||
<string name="revanced_spoof_video_streams_screen_title">Spoof Video Streams</string>
|
||||
<string name="revanced_spoof_video_streams_screen_summary">تزييف تدفقات الفيديو الخاصة بالعميل لمنع حدوث مشكلات أثناء التشغيل</string>
|
||||
<string name="revanced_spoof_video_streams_title">Spoof Video Streams</string>
|
||||
<string name="revanced_spoof_video_streams_summary_on">يتم تزييف تدفقات الفيديو</string>
|
||||
<string name="revanced_spoof_video_streams_summary_off">لا يتم تزييف تدفقات الفيديو\n\nقد لا يعمل تشغيل الفيديو</string>
|
||||
<string name="revanced_spoof_video_streams_user_dialog_message">إيقاف تشغيل هذا الإعداد قد يسبب مشاكل في تشغيل الفيديو.</string>
|
||||
<string name="revanced_spoof_video_streams_client_title">العميل الافتراضي</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_title">فرض AVC (H.264)</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">ترميز الفيديو هو AVC (H.264)</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">ترميز الفيديو هو VP9 أو AV1</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">لا يحتوي جهازك على فك تشفير الأجهزة VP9، وهذا الإعداد يعمل دائما عند تمكين تزييف العميل</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">قد يؤدي تمكين هذا إلى تحسين عمر البطارية وإصلاح مشكلة تقطيع التشغيل.\n\nيتمتع تنسيق AVC بدقة قصوى تبلغ 1080P، وسيستخدم تشغيل الفيديو المزيد من بيانات الإنترنت مقارنةً بتنسيق VP9 أو AV1.</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_title">التأثيرات الجانبية لمحاكاة iOS</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_summary">• قد لا يتم تشغيل الأفلام أو الفيديوهات المدفوعة\n• تبدأ البثوث المباشرة من البداية\n• قد تنتهي الفيديوهات قبل النهاية بثانية واحدة\n• لا يوجد ترميز الصوت Opus</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_vr_title">التأثيرات الجانبية لمحاكاة Android VR</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_vr_summary">• قائمة المقطع الصوتي مفقودة\n• مستوى الصوت الثابت غير متوفر</string>
|
||||
</patch>
|
||||
<!-- This patch is no longer used and these strings will soon be deleted. -->
|
||||
<patch id="video.hdrbrightness.HDRBrightnessPatch">
|
||||
|
||||
@@ -32,15 +32,16 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
-->
|
||||
<resources>
|
||||
<app id="shared">
|
||||
<patch id="misc.checks.BaseCheckEnvironmentPatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.BaseSettingsResourcePatch">
|
||||
<!-- Settings about dialog. -->
|
||||
</patch>
|
||||
<patch id="misc.gms.BaseGmsCoreSupportResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
</app>
|
||||
<app id="youtube">
|
||||
<patch id="misc.settings.SettingsResourcePatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.SettingsPatch">
|
||||
</patch>
|
||||
<patch id="misc.debugging.DebuggingPatch">
|
||||
@@ -57,7 +58,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
|
||||
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
|
||||
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
<patch id="ad.general.HideAdsResourcePatch">
|
||||
@@ -232,10 +233,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.EnableSlideToSeekPatch">
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
||||
</patch>
|
||||
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
|
||||
<patch id="misc.fix.playback.SpoofSignaturePatch">
|
||||
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
|
||||
</patch>
|
||||
<!-- This patch is no longer used and these strings will soon be deleted. -->
|
||||
<patch id="video.hdrbrightness.HDRBrightnessPatch">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user