mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-26 02:44:08 +01:00
Compare commits
47 Commits
v4.14.0-de
...
v4.16.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
d6f20ee67d | ||
|
|
bccd62e593 | ||
|
|
1322403698 | ||
|
|
a64270514f | ||
|
|
f5de555adf | ||
|
|
4c2ec2870c | ||
|
|
a73e2458e9 | ||
|
|
96e6f43ca0 |
172
CHANGELOG.md
172
CHANGELOG.md
@@ -1,3 +1,175 @@
|
||||
# [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **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.8](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.7...v4.14.0-dev.8) (2024-09-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - SponsorBlock:** Add summary text to 'view my segments' button ([df80b9f](https://github.com/ReVanced/revanced-patches/commit/df80b9f92f0d981b9a40b7756d74f8ccc3dcb1e9))
|
||||
|
||||
# [4.14.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.6...v4.14.0-dev.7) (2024-09-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `Check environment` patch ([#3610](https://github.com/ReVanced/revanced-patches/issues/3610)) ([fbcbdaf](https://github.com/ReVanced/revanced-patches/commit/fbcbdafa4938a35b5fdec46aae7b250a84b9c139))
|
||||
|
||||
# [4.14.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.5...v4.14.0-dev.6) (2024-09-06)
|
||||
|
||||
|
||||
### 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))
|
||||
|
||||
# [4.14.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v4.14.0-dev.4...v4.14.0-dev.5) (2024-09-06)
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@ public final class app/revanced/patches/all/connectivity/wifi/spoof/SpoofWifiPat
|
||||
public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lkotlin/Triple;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/directory/ChangeDataDirectoryLocationPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/all/directory/ChangeDataDirectoryLocationPatch;
|
||||
public 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/Integer;
|
||||
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;
|
||||
public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
|
||||
public synthetic fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/interaction/gestures/PredictiveBackGesturePatch : app/revanced/patcher/patch/ResourcePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/all/interaction/gestures/PredictiveBackGesturePatch;
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
@@ -820,6 +828,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;
|
||||
}
|
||||
@@ -854,6 +868,12 @@ public final class app/revanced/patches/serviceportalbund/detection/root/RootDet
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public fun <init> (Lapp/revanced/patcher/fingerprint/MethodFingerprint;Ljava/util/Set;Lapp/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch;)V
|
||||
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/shared/misc/fix/verticalscroll/VerticalScrollPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/shared/misc/fix/verticalscroll/VerticalScrollPatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
@@ -1862,6 +1882,10 @@ public final class app/revanced/patches/youtube/misc/backgroundplayback/Backgrou
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch : app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/check/CheckEnvironmentPatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/debugging/DebuggingPatch : app/revanced/patcher/patch/ResourcePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/debugging/DebuggingPatch;
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
@@ -1892,6 +1916,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;
|
||||
@@ -1970,13 +2000,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 {
|
||||
@@ -2144,6 +2180,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
|
||||
@@ -2171,6 +2209,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
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ dependencies {
|
||||
implementation(libs.guava)
|
||||
// Used in JsonGenerator.
|
||||
implementation(libs.gson)
|
||||
// Android API stubs defined here.
|
||||
compileOnly(project(":stub"))
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 4.14.0-dev.5
|
||||
version = 4.16.0-dev.1
|
||||
|
||||
@@ -5,3 +5,5 @@ buildCache {
|
||||
isEnabled = "CI" !in System.getenv()
|
||||
}
|
||||
}
|
||||
|
||||
include(":stub")
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package app.revanced.patches.all.directory
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@Patch(
|
||||
name = "Change data directory location",
|
||||
description = "Changes the data directory in the application from " +
|
||||
"the app internal storage directory to /sdcard/android/data accessible by root-less devices." +
|
||||
"Using this patch can cause unexpected issues with some apps.",
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
object ChangeDataDirectoryLocationPatch : BaseTransformInstructionsPatch<Int>() {
|
||||
override fun filterMap(
|
||||
classDef: ClassDef,
|
||||
method: Method,
|
||||
instruction: Instruction,
|
||||
instructionIndex: Int,
|
||||
): Int? {
|
||||
val reference = instruction.getReference<MethodReference>() ?: return null
|
||||
|
||||
if (!MethodUtil.methodSignaturesMatch(reference, MethodCall.GetDir.reference)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return instructionIndex
|
||||
}
|
||||
|
||||
override fun transform(
|
||||
mutableMethod: MutableMethod,
|
||||
entry: Int,
|
||||
) = transformMethodCall(entry, mutableMethod)
|
||||
|
||||
private fun transformMethodCall(
|
||||
instructionIndex: Int,
|
||||
mutableMethod: MutableMethod,
|
||||
) {
|
||||
val getDirInstruction = mutableMethod.getInstruction<Instruction35c>(instructionIndex)
|
||||
val contextRegister = getDirInstruction.registerC
|
||||
val dataRegister = getDirInstruction.registerD
|
||||
|
||||
mutableMethod.replaceInstruction(
|
||||
instructionIndex,
|
||||
"invoke-virtual { v$contextRegister, v$dataRegister }, " +
|
||||
"Landroid/content/Context;->getExternalFilesDir(Ljava/lang/String;)Ljava/io/File;",
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
val reference: MethodReference,
|
||||
) {
|
||||
GetDir(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/content/Context;",
|
||||
"getDir",
|
||||
listOf("Ljava/lang/String;", "I"),
|
||||
"Ljava/io/File;",
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
@@ -0,0 +1,114 @@
|
||||
package app.revanced.patches.shared.misc.checks
|
||||
|
||||
import android.os.Build.*
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableLongEncodedValue
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableStringEncodedValue
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.patches.shared.misc.checks.fingerprints.PatchInfoBuildFingerprint
|
||||
import app.revanced.patches.shared.misc.checks.fingerprints.PatchInfoFingerprint
|
||||
import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.immutable.value.ImmutableLongEncodedValue
|
||||
import com.android.tools.smali.dexlib2.immutable.value.ImmutableStringEncodedValue
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
abstract class BaseCheckEnvironmentPatch(
|
||||
private val mainActivityOnCreateFingerprint: MethodFingerprint,
|
||||
compatiblePackages: Set<CompatiblePackage>,
|
||||
integrationsPatch: BaseIntegrationsPatch,
|
||||
) : BytecodePatch(
|
||||
description = "Checks, if the application was patched by, otherwise warns the user.",
|
||||
compatiblePackages = compatiblePackages,
|
||||
dependencies = setOf(
|
||||
AddResourcesPatch::class,
|
||||
integrationsPatch::class,
|
||||
),
|
||||
fingerprints = setOf(
|
||||
PatchInfoFingerprint,
|
||||
PatchInfoBuildFingerprint,
|
||||
mainActivityOnCreateFingerprint,
|
||||
),
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
AddResourcesPatch(BaseCheckEnvironmentPatch::class)
|
||||
|
||||
setPatchInfo()
|
||||
invokeCheck()
|
||||
}
|
||||
|
||||
private fun setPatchInfo() {
|
||||
PatchInfoFingerprint.setClassFields(
|
||||
"PATCH_TIME" to System.currentTimeMillis().encoded,
|
||||
)
|
||||
|
||||
fun setBuildInfo() {
|
||||
PatchInfoBuildFingerprint.setClassFields(
|
||||
"PATCH_BOARD" to BOARD.encodedAndHashed,
|
||||
"PATCH_BOOTLOADER" to BOOTLOADER.encodedAndHashed,
|
||||
"PATCH_BRAND" to BRAND.encodedAndHashed,
|
||||
"PATCH_CPU_ABI" to CPU_ABI.encodedAndHashed,
|
||||
"PATCH_CPU_ABI2" to CPU_ABI2.encodedAndHashed,
|
||||
"PATCH_DEVICE" to DEVICE.encodedAndHashed,
|
||||
"PATCH_DISPLAY" to DISPLAY.encodedAndHashed,
|
||||
"PATCH_FINGERPRINT" to FINGERPRINT.encodedAndHashed,
|
||||
"PATCH_HARDWARE" to HARDWARE.encodedAndHashed,
|
||||
"PATCH_HOST" to HOST.encodedAndHashed,
|
||||
"PATCH_ID" to ID.encodedAndHashed,
|
||||
"PATCH_MANUFACTURER" to MANUFACTURER.encodedAndHashed,
|
||||
"PATCH_MODEL" to MODEL.encodedAndHashed,
|
||||
"PATCH_PRODUCT" to PRODUCT.encodedAndHashed,
|
||||
"PATCH_RADIO" to RADIO.encodedAndHashed,
|
||||
"PATCH_TAGS" to TAGS.encodedAndHashed,
|
||||
"PATCH_TYPE" to TYPE.encodedAndHashed,
|
||||
"PATCH_USER" to USER.encodedAndHashed,
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
Class.forName("android.os.Build")
|
||||
// This only works on Android,
|
||||
// because it uses Android APIs.
|
||||
setBuildInfo()
|
||||
} catch (_: ClassNotFoundException) { }
|
||||
}
|
||||
|
||||
private fun invokeCheck() = mainActivityOnCreateFingerprint.result?.mutableMethod?.addInstructions(
|
||||
0,
|
||||
"invoke-static/range { p0 .. p0 },$INTEGRATIONS_CLASS_DESCRIPTOR->check(Landroid/app/Activity;)V",
|
||||
) ?: throw mainActivityOnCreateFingerprint.exception
|
||||
|
||||
private companion object {
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/integrations/shared/checks/CheckEnvironmentPatch;"
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
private val String.encodedAndHashed
|
||||
get() = MutableStringEncodedValue(
|
||||
ImmutableStringEncodedValue(
|
||||
Base64.encode(
|
||||
MessageDigest.getInstance("SHA-1")
|
||||
.digest(this.toByteArray(StandardCharsets.UTF_8)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private val Long.encoded get() = MutableLongEncodedValue(ImmutableLongEncodedValue(this))
|
||||
|
||||
private fun <T : MutableEncodedValue> MethodFingerprint.setClassFields(vararg fieldNameValues: Pair<String, T>) {
|
||||
val fieldNameValueMap = mapOf(*fieldNameValues)
|
||||
|
||||
resultOrThrow().mutableClass.fields.forEach { field ->
|
||||
field.initialValue = fieldNameValueMap[field.name] ?: return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package app.revanced.patches.shared.misc.checks.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object PatchInfoBuildFingerprint : MethodFingerprint(
|
||||
customFingerprint = { _, classDef -> classDef.type == "Lapp/revanced/integrations/shared/checks/PatchInfo\$Build;" },
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.shared.misc.checks.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object PatchInfoFingerprint : MethodFingerprint(
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.type == "Lapp/revanced/integrations/shared/checks/PatchInfo;"
|
||||
},
|
||||
)
|
||||
@@ -115,8 +115,8 @@ abstract class BaseGmsCoreSupportPatch(
|
||||
|
||||
// Verify GmsCore is installed and whitelisted for power optimizations and background usage.
|
||||
mainActivityOnCreateFingerprint.result?.mutableMethod?.apply {
|
||||
// Temporary fix for Google photos integration.
|
||||
var setContextIndex = indexOfFirstInstruction {
|
||||
// Temporary fix for patches with an integrations patch that hook the onCreate method as well.
|
||||
val setContextIndex = indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>() ?: return@indexOfFirstInstruction false
|
||||
|
||||
reference.toString() == "Lapp/revanced/integrations/shared/Utils;->setContext(Landroid/content/Context;)V"
|
||||
|
||||
@@ -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,13 @@ 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"
|
||||
classDef.sourceFile == "ApiUserPlanInterceptor.java" ||
|
||||
classDef.sourceFile == "ApiUserPlanInterceptor.kt"
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,13 @@ 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 from long pressing the app icon in your launcher."
|
||||
)
|
||||
|
||||
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.misc.settings.SettingsPatch
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
import org.w3c.dom.Element
|
||||
|
||||
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class, AddResourcesPatch::class])
|
||||
object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
@@ -38,6 +41,7 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
SwitchPreference("revanced_hide_shorts_subscribe_button"),
|
||||
SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"),
|
||||
SwitchPreference("revanced_hide_shorts_save_sound_button"),
|
||||
SwitchPreference("revanced_hide_shorts_use_this_sound_button"),
|
||||
SwitchPreference("revanced_hide_shorts_shop_button"),
|
||||
SwitchPreference("revanced_hide_shorts_tagged_products"),
|
||||
SwitchPreference("revanced_hide_shorts_search_suggestions"),
|
||||
@@ -51,6 +55,19 @@ object HideShortsComponentsResourcePatch : ResourcePatch() {
|
||||
SwitchPreference("revanced_hide_shorts_navigation_bar"),
|
||||
)
|
||||
|
||||
if (hideShortsAppShortcut == true) {
|
||||
context.xmlEditor["res/xml/main_shortcuts.xml"].use { editor ->
|
||||
val shortcuts = editor.file.getElementsByTagName("shortcuts").item(0) as Element
|
||||
val shortsItem =
|
||||
shortcuts.getElementsByTagName("shortcut").findElementByAttributeValueOrThrow(
|
||||
"android:shortcutId",
|
||||
"shorts-shortcut"
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package app.revanced.patches.youtube.misc.check
|
||||
|
||||
import app.revanced.patches.shared.misc.checks.BaseCheckEnvironmentPatch
|
||||
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
|
||||
import app.revanced.patches.youtube.shared.fingerprints.MainActivityOnCreateFingerprint
|
||||
|
||||
@Suppress("unused")
|
||||
object CheckEnvironmentPatch :
|
||||
BaseCheckEnvironmentPatch(
|
||||
mainActivityOnCreateFingerprint = MainActivityOnCreateFingerprint,
|
||||
integrationsPatch = IntegrationsPatch,
|
||||
compatiblePackages = setOf(CompatiblePackage("com.google.android.youtube")),
|
||||
)
|
||||
@@ -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,146 @@
|
||||
package app.revanced.patches.youtube.misc.playercontrols
|
||||
|
||||
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.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 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 (index in 1 until children.length) {
|
||||
val view = children.item(index)
|
||||
|
||||
// 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.
|
||||
if (view.hasAttributes() && view.attributes.getNamedItem("android:id")
|
||||
.nodeValue.endsWith("live_chat_overlay_button")
|
||||
) {
|
||||
// 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
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw PatchException("Could not find expected xml to modify")
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 }
|
||||
)
|
||||
@@ -15,6 +15,7 @@ import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
import app.revanced.patches.youtube.misc.check.CheckEnvironmentPatch
|
||||
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
|
||||
import app.revanced.patches.youtube.misc.settings.fingerprints.LicenseActivityOnCreateFingerprint
|
||||
import app.revanced.patches.youtube.misc.settings.fingerprints.SetThemeFingerprint
|
||||
@@ -30,6 +31,9 @@ import java.io.Closeable
|
||||
IntegrationsPatch::class,
|
||||
SettingsResourcePatch::class,
|
||||
AddResourcesPatch::class,
|
||||
// Currently there is no easy way to make a mandatory patch,
|
||||
// so for now this is a dependent of this patch.
|
||||
CheckEnvironmentPatch::class,
|
||||
],
|
||||
)
|
||||
object SettingsPatch :
|
||||
|
||||
@@ -28,8 +28,6 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
|
||||
override fun execute(context: ResourceContext) {
|
||||
super.execute(context)
|
||||
|
||||
AddResourcesPatch(this::class)
|
||||
|
||||
// Used for a fingerprint from SettingsPatch.
|
||||
appearanceStringId = ResourceMappingPatch["string", "app_theme_appearance_dark"]
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -615,6 +630,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<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_use_this_sound_button_title">ØĨØŽŲØ§ØĄ Ø˛Øą Ø§ØŗØĒ؎داŲ
ŲØ°Ø§ Ø§ŲØĩŲØĒ</string>
|
||||
<string name="revanced_hide_shorts_use_this_sound_button_summary_on">ØĒŲ
ØĨØŽŲØ§ØĄ Ø˛Øą Ø§ØŗØĒ؎داŲ
ŲØ°Ø§ Ø§ŲØĩŲØĒ</string>
|
||||
<string name="revanced_hide_shorts_use_this_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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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">Yoxlamalar uÄursuz oldu</string>
|
||||
<string name="revanced_check_environment_dialog_open_official_source_button">XidmÉti veb saytÄą aç</string>
|
||||
<string name="revanced_check_environment_dialog_ignore_button">Yan keç</string>
|
||||
<string name="revanced_check_environment_failed_message"><h5>Bu tÉtbiq sizin tÉrÉfinizdÉn yamaqlanmayÄąb.</h5><br>Bu tÉtbiq dÃŧzgÃŧn iÅlÉmÉyÉ bilÉr, <b>istifadÉ etmÉk zÉrÉrli vÉ ya hÉtta tÉhlÃŧkÉli ola bilÉr</b>.<br><br><br>Bu yoxlamalar bu tÉtbiqin ÉvvÉldÉn yamaqlandÄąÄÄąnÄą vÉ ya baÅqasÄąndan ÉldÉ edildiyini gÃļstÉrir:<br><br><small>%1$s</small><br> <br>onu silmÉyiniz vÉ ÃļzÃŧnÃŧz yamaqlamaÄÄąnÄąz tÃļvsiyÉ olunur. </b>tÉsdiqlÉnmiÅ vÉ tÉhlÃŧkÉsiz tÉtbiq istifadÉ etdiyinizÉ Émin olmaq ÃŧçÃŧn. <p><br> İnkar edilmÉzsÉ, bu xÉbÉrdarlÄąq yalnÄąz iki dÉfÉ gÃļstÉrilÉcÉk.</string>
|
||||
<string name="revanced_check_environment_not_same_patching_device">FÉrqli cihazda yamaqlanÄąb</string>
|
||||
<string name="revanced_check_environment_manager_not_expected_installer">ReVanced Manager tÉrÉfindÉn quraÅdÄąrÄąlmayÄąb</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time">10 dÉqiqÉdÉn çox ÉvvÉl yamaqlanÄąb</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_days">%s gÃŧn ÉvvÉl yamaqlanÄąb</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_invalid">APK quruluÅ tarixi pozulub</string>
|
||||
</patch>
|
||||
<patch id="misc.settings.BaseSettingsResourcePatch">
|
||||
<string name="revanced_settings_confirm_user_dialog_title">Davam etmÉk istÉyirsiniz?</string>
|
||||
<string name="revanced_settings_reset">SÄąfÄąrla</string>
|
||||
@@ -42,6 +53,14 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_settings_import_reset">ReVanced tÉnzimlÉmÉlÉr standarta tÉyin edildi</string>
|
||||
<string name="revanced_settings_import_success">%d tÉnzimlÉmÉ idxal edildi</string>
|
||||
<string name="revanced_settings_import_failure_parse">UÄursuz idxal prosesi: %s</string>
|
||||
<string name="revanced_pref_import_export_title">İdxal/İxrac et</string>
|
||||
<string name="revanced_pref_import_export_summary">ReVanced tÉnzimlÉmÉlÉrin idxal/ixrac et</string>
|
||||
<!-- Settings about dialog. -->
|
||||
<string name="revanced_settings_about_links_body">ReVanced Patches <i>%s</i> versiyasÄąnÄą istifadÉ edirsiniz</string>
|
||||
<string name="revanced_settings_about_links_dev_header">Qeyd</string>
|
||||
<string name="revanced_settings_about_links_dev_body">Bu versiya ilkin buraxÄąlÄąÅdÄąr vÉ gÃļzlÉnilmÉz problemlÉrlÉ ÃŧzlÉÅÉ bilÉrsiniz</string>
|
||||
<string name="revanced_settings_about_links_header">RÉsmi baÄlantÄąlar</string>
|
||||
<string name="revanced_settings_about_links_donate">İanÉ ver</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> versiyasÄąnÄą istifadÉ edirsiniz</string>
|
||||
<string name="revanced_settings_about_links_dev_header">Qeyd</string>
|
||||
<string name="revanced_settings_about_links_dev_body">Bu versiya ilkin buraxÄąlÄąÅdÄąr vÉ gÃļzlÉnilmÉz problemlÉrlÉ ÃŧzlÉÅÉ bilÉrsiniz</string>
|
||||
<string name="revanced_settings_about_links_header">RÉsmi baÄlantÄąlar</string>
|
||||
<string name="revanced_pref_import_export_title">İdxal/İxrac et</string>
|
||||
<string name="revanced_pref_import_export_summary">ReVanced tÉnzimlÉmÉlÉrin idxal/ixrac et</string>
|
||||
</patch>
|
||||
<patch id="misc.settings.SettingsPatch">
|
||||
<string name="revanced_settings_screen_00_about_title">HaqqÄąnda</string>
|
||||
<string name="revanced_settings_screen_01_ads_title">Reklamlar</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">GizlÉdilÉcÉk açar sÃļzlÉr</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">Yeni sÉtirlÉrlÉ ayrÄąlmÄąÅ gizlÉdilÉcÉk açar sÃļzlÉr vÉ ifadÉlÉr\n\nOrtada bÃļyÃŧk hÉrf olan sÃļzlÉr korpusla birlikdÉ daxil edilmÉlidir (yÉni: iPhone, TikTok, LeBlanc)</string>
|
||||
<string name="revanced_hide_keyword_content_phrases_summary">Yeni sÉtirlÉrlÉ ayrÄąlmÄąÅ gizlÉdilÉcÉk açar sÃļzlÉr vÉ frazalar\n\nAçar sÃļzlÉr kanal adlarÄą vÉ ya video adlarÄąnda gÃļstÉrilÉn istÉnilÉn mÉtn ola bilÉr\n\nOrtada bÃļyÃŧk hÉrf olan sÃļzlÉr korpusla birlikdÉ qeyd edilmÉlidir (yÉni: iPhone, TikTok, LeBlanc)</string>
|
||||
<string name="revanced_hide_keyword_content_about_title">Açar sÃļz filtrlÉmÉsi haqqÄąnda</string>
|
||||
<string name="revanced_hide_keyword_content_about_summary">Æsas sÉhifÉ/AbunÉlik/AxtarÄąÅ nÉticÉlÉri açar sÃļz ifadÉlÉrinÉ uyÄunlaÅan mÉzmunu gizlÉtmÉk ÃŧçÃŧn filtrlÉnir\n\nMÉhdudiyyÉtlÉr\nâĸ BÉzi Shorts gizlÉnÉ bilmÉz\nâĸ BÉzi UI elementlÉri gizlÉnÉ bilmÉz\nâĸ Açar sÃļz axtarÄąÅÄą heç bir nÉticÉ gÃļstÉrmÉyÉ bilÉr</string>
|
||||
<string name="revanced_hide_keyword_content_about_summary">Æsas sÉhifÉ/AbunÉlik/AxtarÄąÅ nÉticÉlÉri açar sÃļz ifadÉlÉrinÉ uyÄunlaÅan mÉzmunu gizlÉtmÉk ÃŧçÃŧn filtrlÉnir\n\nMÉhdudiyyÉtlÉr\nâĸ Shorts-lar kanal adÄąna gÃļrÉ gizlÉnÉ bilmÉz\nâĸ BÉzi UI hissÉciklÉri gizlÉdilÉ bilmÉz\nâĸ Açar sÃļz axtarÄąÅÄąnda nÉticÉ olmaya bilÉr</string>
|
||||
<string name="revanced_hide_keyword_content_about_whole_words_title">BÃŧtÃŧn sÃļzlÉri uyÄunlaÅdÄąr</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">Açar sÃļz/frazanÄąn qoÅa dÄąrnaqlarla ÉhatÉ olunmasÄą video adlarÄą vÉ kanal adlarÄąnÄąn qismÉn uyÄunlaÅmasÄąna mane olacaq <br><br>MÉsÉlÉn,<br><b>\"ai\"</b> videonu gizlÉdÉcÉk:<b>How does AI work?</b><br> lakin gizlÉtmÉyÉcÉk: DÃŧzgÃŧn;<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">EtibarsÄąz açar sÃļzÃŧ. \'%s\' istifadÉ edilÉ bilmÉz</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">EtibarsÄąz açar sÃļzÃŧ. \'%1$s\', %2$d simvoldan azdÄąr</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_broad">\"%s\" açar sÃļzÃŧ, bÃŧtÃŧn videolarda gizlÉdilÉcÉk</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_common">Açar sÃļz istifadÉ edilÉ bilmir: %s</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_common_whole_word_required">Açar sÃļz istifadÉsi ÃŧçÃŧn istinad ÉlavÉ et: %s</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_conflicting">Açar sÃļzÃŧn ziddiyyÉtli hissÉciklÉri var: %s</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_length">Açar sÃļz çox qÄąsadÄąr vÉ istinad tÉlÉb edir: %s</string>
|
||||
<string name="revanced_hide_keyword_toast_invalid_broad">Açar sÃļz, bÃŧtÃŧn videolarÄą gizlÉdÉcÉk: %s</string>
|
||||
</patch>
|
||||
<patch id="ad.general.HideAdsResourcePatch">
|
||||
<string name="revanced_hide_general_ads_title">Ãmumi reklamlarÄą gizlÉt</string>
|
||||
@@ -307,7 +322,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_copy_video_url_timestamp_summary_off">DÃŧymÉ gÃļstÉrilmir</string>
|
||||
</patch>
|
||||
<patch id="interaction.dialog.RemoveViewerDiscretionDialogPatch">
|
||||
<string name="revanced_remove_viewer_discretion_dialog_title">İzlÉyici mÃŧlahizÉ dialoqunu sil</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_title">İzlÉyici mÃŧlahizÉ dialoqun sil</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_on">Dialoq silindi</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Dialoq gÃļstÉrilir</string>
|
||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Bu, yaÅ mÉhdudiyyÉtini ÃļtÃŧrmÃŧr. SadÉcÉ avtomatik qÉbul edir.</string>
|
||||
@@ -615,6 +630,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_hide_shorts_save_sound_button_title">SÉsi pleylistdÉ saxlama dÃŧymÉsini gizlÉt</string>
|
||||
<string name="revanced_hide_shorts_save_sound_button_summary_on">SÉsi pleylistdÉ saxlama gizlidir</string>
|
||||
<string name="revanced_hide_shorts_save_sound_button_summary_off">SÉsi pleylistdÉ saxlama gÃļstÉrilir</string>
|
||||
<string name="revanced_hide_shorts_use_this_sound_button_title">\"Bu sÉsi istifadÉ et\" dÃŧymÉsini gizlÉt</string>
|
||||
<string name="revanced_hide_shorts_use_this_sound_button_summary_on">\"Bu sÉsi istifadÉ et\" dÃŧymÉsi gizlidir</string>
|
||||
<string name="revanced_hide_shorts_use_this_sound_button_summary_off">\"Bu sÉsi istifadÉ et\" dÃŧymÉsi gÃļstÉrilir</string>
|
||||
<string name="revanced_hide_shorts_search_suggestions_title">AxtarÄąÅ tÉkliflÉrini gizlÉt</string>
|
||||
<string name="revanced_hide_shorts_search_suggestions_summary_on">AxtarÄąÅ tÉkliflÉri gizlÉdilib</string>
|
||||
<string name="revanced_hide_shorts_search_suggestions_summary_off">AxtarÄąÅ tÉkliflÉri gÃļstÉrilir</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">OynadÄącÄą ÃļrtÃŧyÃŧnÃŧn qeyri-ÅÉffaflÄąÄÄą 0-100 arasÄą olmalÄądÄąr</string>
|
||||
</patch>
|
||||
<patch id="layout.returnyoutubedislike.ReturnYouTubeDislikeResourcePatch">
|
||||
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Gizli</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">\"BÉyÉnmÉmÉ\" mÃŧvÉqqÉti ÉlçatmazdÄąr(API vaxtÄą bitdi)</string>
|
||||
<string name="revanced_ryd_failure_connection_status_code">BÉyÉnmÉmÉ ÉlçatmazdÄąr (status %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">İstifadÉçi adÄą uÄurla dÉyiÅdirildi</string>
|
||||
<string name="revanced_sb_stats_reputation">NÃŧfuzunuz <b>%.2f</b></string>
|
||||
<string name="revanced_sb_stats_submissions"><b>%s</b> bÃļlÃŧm yaratdÄąnÄąz</string>
|
||||
<string name="revanced_sb_stats_submissions_sum">BÃļlÃŧmlÉrinizÉ baxmaq ÃŧçÃŧn bura toxunun</string>
|
||||
<string name="revanced_sb_stats_saved_zero">SponsorBlock liderlik lÃļvhÉsi</string>
|
||||
<string name="revanced_sb_stats_saved">İnsanlarÄą <b>%s</b> bÃļlÃŧmdÉn xilas etdiniz</string>
|
||||
<string name="revanced_sb_stats_saved_sum_zero">Qlobal statistikalarÄą vÉ baÅlÄąca tÃļhfÉçilÉri gÃļrmÉk ÃŧçÃŧn bura toxunun</string>
|
||||
@@ -1006,7 +1024,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_alt_thumbnail_search_title">AxtarÄąÅ nÉticÉlÉri</string>
|
||||
<string name="revanced_alt_thumbnail_options_entry_1">Orijinal miniatÃŧrlÉr</string>
|
||||
<string name="revanced_alt_thumbnail_options_entry_2">DeArrow & Orijinal miniatÃŧrlÉr</string>
|
||||
<string name="revanced_alt_thumbnail_options_entry_3">DeArrow & Kadr çÉkiliÅlÉri</string>
|
||||
<string name="revanced_alt_thumbnail_options_entry_3">DeArrow & Kadr çÉkiliÅlÉr</string>
|
||||
<string name="revanced_alt_thumbnail_options_entry_4">Kadr çÉkiliÅlÉri</string>
|
||||
<string name="revanced_alt_thumbnail_dearrow_about_summary">DeArrow YouTube videolarÄą ÃŧçÃŧn bÃļlÃŧk mÉnbÉli miniatÃŧrlÉr tÉchiz edir. Bu miniatÃŧrlÉr hÉr zaman YouTube tÉrÉfindÉn tÉmin edilÉnlÉrdÉn daha uyÄun olur\n\nÆgÉr aktivlÉÅdirilÉrsÉ, video URL-lÉr API serverinÉ gÃļndÉrilÉcÉk vÉ baÅqa heç bir mÉlumat gÃļndÉrilmÉyÉcÉk. Videoda DeArrow miniatÃŧrlÉri yoxdursa, orijinal vÉ ya hÉlÉ dÉ kadr çÉkiliÅlÉri gÃļstÉrilir\n\nDeArrow haqqÄąnda ÉtraflÄą ÃļyrÉnmÉk ÃŧçÃŧn bura toxun</string>
|
||||
<string name="revanced_alt_thumbnail_dearrow_connection_toast_title">API Élçatan deyilsÉ ani bildiriÅ gÃļstÉr</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">Axtarmaq ÃŧçÃŧn sÃŧrÃŧÅdÃŧrmÉ aktivdir</string>
|
||||
<string name="revanced_slide_to_seek_summary_off">Axtarmaq ÃŧçÃŧn sÃŧrÃŧÅdÃŧrmÉ aktiv deyil</string>
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
||||
<string name="revanced_spoof_client_screen_title">QÉbuledicini saxtalaÅdÄąr</string>
|
||||
<string name="revanced_spoof_client_screen_summary">Oynatma problemlÉrinin olmamasÄą ÃŧçÃŧn client-i saxtalaÅdÄąr</string>
|
||||
<string name="revanced_spoof_client_title">QÉbuledicini saxtalaÅdÄąr</string>
|
||||
<string name="revanced_spoof_client_summary_on">QÉbuledici saxtalaÅdÄąrÄąldÄą</string>
|
||||
<string name="revanced_spoof_client_summary_off">QÉbuledici dÉyiÅmÉyib\n\nVideo oynatma iÅlÉmÉyÉ bilÉr</string>
|
||||
<string name="revanced_spoof_client_user_dialog_message">Bu seçimin baÄlanmasÄą, video oynatma problemlÉrinÉ sÉbÉb ola bilÉr.</string>
|
||||
<string name="revanced_spoof_client_type_title">QÉbuledici saxtalaÅdÄąrma nÃļvÃŧ</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_title">iOS AVC-yÉ Zorla (H.264)</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_summary_on">iOS video kodlayÄącÄą AVC-dir</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_summary_off">iOS video kodlayÄącÄą AVC, VP9 vÉ ya AV1-dir</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_user_dialog_message">Bunu aktivlÉÅdirmÉ batareya ÃļmrÃŧnÃŧ yaxÅÄąlaÅdÄąra vÉ oynatma donmasÄąnÄą dÃŧzÉldÉ bilÉr.\n\nAVC maksimum 1080p gÃļrÃŧntÃŧ imkanÄąna malikdir vÉ video oynadÄąlmasÄą VP9 vÉ ya AV1-dÉn daha çox internet mÉlumatÄąndan istifadÉ edÉcÉk.</string>
|
||||
<string name="revanced_spoof_client_about_android_ios_title">iOS saxtakarlÄąÄÄąnÄąn yan tÉsirlÉri</string>
|
||||
<string name="revanced_spoof_client_about_android_ios_summary">âĸ HDR yalnÄąz AV1 kodlayÄącÄą ilÉ dÉstÉklÉnir\nâĸ BaxÄąÅ tarixçÉsi ÃļdÉyici hesab ilÉ iÅlÉmir</string>
|
||||
<string name="revanced_spoof_client_about_android_vr_title">Android VR saxtakarlÄąÄÄą yan tÉsirlÉri</string>
|
||||
<string name="revanced_spoof_client_about_android_vr_summary">âĸ HDR video yoxdurâĸ UÅaq videolarÄą oynadÄąlmÄąr\nâĸ FasilÉ verilmiÅ videolar gÃļzlÉnilmÉdÉn davam edÉ bilÉr\nâĸ AÅaÄÄą keyfiyyÉtli Shorts axtarma çubuÄu miniatÃŧrlÉri\nâĸ \"YÃŧklÉ\" fÉaliyyÉt dÃŧymÉsi gizlidir\nâĸ BitiÅ ekran kartlarÄą gizlidir</string>
|
||||
<string name="revanced_spoof_client_storyboard_timeout">Client kiçik ÅÉkillÉrini tÉqlid etmÉ ÉlçatmazdÄąr (API vaxtÄą bitdi)</string>
|
||||
<string name="revanced_spoof_client_storyboard_io_exception">Client kiçik ÅÉkillÉrini tÉqlid etmÉ mÃŧvÉqqÉti ÉlçatmazdÄąr: %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">Video yayÄąmlarÄą saxtalaÅdÄąr</string>
|
||||
<string name="revanced_spoof_video_streams_screen_summary">Oynatma problemlÉrin ÃļnlÉmÉk ÃŧçÃŧn qÉbuledici video yayÄąmlarÄąn saxtalaÅdÄąr</string>
|
||||
<string name="revanced_spoof_video_streams_title">Video yayÄąmlarÄą saxtalaÅdÄąr</string>
|
||||
<string name="revanced_spoof_video_streams_summary_on">Video yayÄąmlarÄą saxtalaÅdÄąrÄąlÄąr</string>
|
||||
<string name="revanced_spoof_video_streams_summary_off">Video yayÄąmlarÄą saxtalaÅmÄąr\n\nVideo oynatma iÅlÉmÉyÉ bilÉr</string>
|
||||
<string name="revanced_spoof_video_streams_user_dialog_message">Bu seçimi baÄlamaq, video oynatma problemlÉrinÉ sÉbÉb olar.</string>
|
||||
<string name="revanced_spoof_video_streams_client_title">İlkin qÉbuledici</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_title">MÉcburi AVC (H.264)</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">Video kodlaÅdÄąrma: AVC (H.264)</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">Video kodlaÅdÄąrma / VP9 vÉ ya AV1</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">CihazÄąnÄązÄąn VP9 hardware decoding\'i yoxdur vÉ bu seçim, \"QÉbuledicini saxtalaÅdÄąrma\" aktivlÉÅdikdÉ hÉmiÅÉlikdir</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Bunu aktivlÉÅdirmÉ batareya ÃļmrÃŧnÃŧ yaxÅÄąlaÅdÄąra vÉ oynatma donmasÄąnÄą dÃŧzÉldÉ bilÉr.\n\nAVC maksimum 1080p gÃļrÃŧntÃŧ imkanÄąna malikdir vÉ video oynadÄąlmasÄą VP9 vÉ ya AV1-dÉn daha çox internet mÉlumatÄą istifadÉ edÉcÉk.</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_title">iOS saxtakarlÄąÄÄą yan tÉsirlÉri</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_summary">âĸ FilmlÉr vÉ ya ÃļdÉniÅli videolar oynadÄąlmaya bilÉr\nâĸ CanlÄą yayÄąmlar ÉvvÉldÉn baÅlayÄąr\nâĸ Videolar 1 saniyÉ tez bitÉ bilÉr\nâĸ Opus sÉs kodlama yoxdur</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR saxtakarlÄąÄÄą yan tÉsirlÉri</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_vr_summary">âĸ SÉs axÄąnÄą menyusu Éskikdir\nâĸ Stabil sÉs sÉviyyÉsi Élçatan deyil</string>
|
||||
</patch>
|
||||
<!-- This patch is no longer used and these strings will soon be deleted. -->
|
||||
<patch id="video.hdrbrightness.HDRBrightnessPatch">
|
||||
|
||||
@@ -32,6 +32,8 @@ 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">
|
||||
<string name="revanced_settings_confirm_user_dialog_title">ĐŅ Ņ
ĐžŅаŅĐĩ ĐŋŅаŅŅĐŗĐŊŅŅŅ?</string>
|
||||
<string name="revanced_settings_reset">ĐĄĐēŅĐŊŅŅŅ</string>
|
||||
@@ -42,6 +44,13 @@ 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>
|
||||
</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 +63,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>
|
||||
@@ -242,14 +243,9 @@ 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_about_title">ĐĐą ŅŅĐģŅŅŅаŅŅŅ ĐēĐģŅŅавŅŅ
ŅĐģĐžŅ</string>
|
||||
<string name="revanced_hide_keyword_content_about_summary">ĐаĐģĐžŅĐŊаŅ/ĐадĐŋŅŅĐēа/ĐŅĐŊŅĐēŅ ĐŋĐžŅŅĐēŅ ŅŅĐģŅŅŅŅŅŅŅа, Đēай ŅŅ
аваŅŅ ĐˇĐŧĐĩŅŅŅва, ŅĐēĐžĐĩ ŅŅĐŋадаĐĩ С ĐēĐģŅŅавŅĐŧŅ ŅŅаСаĐŧŅ\n\nĐĐąĐŧĐĩĐļаваĐŊĐŊŅ\nâĸ ĐĐĩĐēаŅĐžŅŅŅ ŅĐžŅŅŅ ĐŧĐžĐŗŅŅŅ ĐąŅŅŅ ĐŊĐĩ ŅŅ
аваĐŊŅ\nâĸ ĐĐĩĐēаŅĐžŅŅŅ ĐēаĐŧĐŋаĐŊĐĩĐŊŅŅ ĐēаŅŅŅŅаĐģŅĐŊŅŅĐēĐ°ĐŗĐ° ŅĐŊŅŅŅŅĐĩĐšŅŅ ĐŧĐžĐŗŅŅŅ ĐąŅŅŅ ĐŊĐĩ ŅŅ
аваĐŊŅ\nâĸ ĐĐžŅŅĐē Đŋа ĐēĐģŅŅавŅĐŧ ŅĐģОвĐĩ ĐŧĐžĐļа ĐŊĐĩ даŅŅ Đ˛ŅĐŊŅĐēаŅ</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. -->
|
||||
<!-- 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>
|
||||
</patch>
|
||||
<patch id="ad.general.HideAdsResourcePatch">
|
||||
<string name="revanced_hide_general_ads_title">ĐĄŅ
аваŅŅ Đ°ĐŗŅĐģŅĐŊŅŅ ŅŅĐēĐģаĐŧŅ</string>
|
||||
@@ -678,7 +674,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>
|
||||
@@ -1112,19 +1107,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_slide_to_seek_summary_on">ĐĄĐģаКд Đ´ĐģŅ ĐŋĐžŅŅĐēŅ ŅĐēĐģŅŅаĐŊŅ</string>
|
||||
<string name="revanced_slide_to_seek_summary_off">ĐĄĐģаКд Đ´ĐģŅ ĐŋĐžŅŅĐēŅ ĐŊĐĩ ŅĐēĐģŅŅаĐŊŅ</string>
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
||||
<string name="revanced_spoof_client_screen_title">ĐадĐŧаĐŊ ĐēĐģŅĐĩĐŊŅа</string>
|
||||
<string name="revanced_spoof_client_screen_summary">ĐадŅОйĐēа ĐēĐģŅĐĩĐŊŅа, Đēай ĐŋŅадŅŅ
ŅĐģŅŅŅ ĐŋŅайĐģĐĩĐŧŅ Đˇ ĐŋŅĐ°ĐšĐŗŅаваĐŊĐŊĐĩĐŧ</string>
|
||||
<string name="revanced_spoof_client_title">ĐадĐŧаĐŊ ĐēĐģŅĐĩĐŊŅа</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_about_android_vr_summary">âĸ ĐŅĐŧа HDR-вŅĐ´Ņа\nâĸ ĐСŅŅŅŅŅŅ Đ˛ŅĐ´Ņа ĐŊĐĩ ĐŋŅĐ°ĐšĐŗŅаваŅŅŅа\nâĸ ĐŅŅĐŋŅĐŊĐĩĐŊŅŅ Đ˛ŅĐ´Ņа ĐŧĐžĐŗŅŅŅ Đ°Đ´ĐŊаŅĐģŅŅŅа вŅĐŋадĐēОвŅĐŧ ŅŅĐŊаĐŧ\nâĸ ĐŅСĐēĐ°Ņ ŅĐēаŅŅŅ ĐŧŅĐŊŅŅŅŅŅ ĐŊа ĐŋаĐŊŅĐģŅ ĐŋĐžŅŅĐēŅ Shorts\nâĸ ĐĐŊĐžĐŋĐēа дСĐĩŅĐŊĐŊŅ ĐĄĐŋаĐŧĐŋаваŅŅ ŅŅ
аваĐŊа\nâĸ ĐаŅŅĐēŅ ĐēаĐŊŅĐ°Đ˛ĐžĐŗĐ° ŅĐēŅаĐŊа ŅŅ
аваĐŊŅ</string>
|
||||
<string name="revanced_spoof_client_storyboard_timeout">ĐŅĐŊŅŅŅŅŅŅ ĐēĐģŅĐĩĐŊŅа Spoof ĐŊĐĩдаŅŅŅĐŋĐŊŅŅ (ŅĐ°Ņ ŅаĐēаĐŊĐŊŅ API ŅĐēĐžĐŊŅŅŅŅŅ)</string>
|
||||
<string name="revanced_spoof_client_storyboard_io_exception">ĐŅĐŊŅŅŅŅŅŅ ĐēĐģŅĐĩĐŊŅа Spoof ŅаŅОва ĐŊĐĩдаŅŅŅĐŋĐŊŅŅ: %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_user_dialog_message">ĐĐ´ĐēĐģŅŅŅĐŊĐŊĐĩ ĐŗŅŅаК ĐŊаĐģĐ°Đ´Ņ ĐŧĐžĐļа вŅĐēĐģŅĐēаŅŅ ĐŋŅайĐģĐĩĐŧŅ Đˇ ĐŋŅĐ°ĐšĐŗŅаваĐŊĐŊĐĩĐŧ вŅĐ´Ņа.</string>
|
||||
</patch>
|
||||
<!-- This patch is no longer used and these strings will soon be deleted. -->
|
||||
<patch id="video.hdrbrightness.HDRBrightnessPatch">
|
||||
|
||||
@@ -32,16 +32,35 @@ 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>
|
||||
<string name="revanced_settings_restart_title">ĐĐŋŅĐĩŅĐŊĐĩŅĐĩ и ŅĐĩŅŅаŅŅиŅаКŅĐĩ</string>
|
||||
<string name="revanced_settings_restart_title">Đ ĐĩŅŅаŅŅиŅаК и ĐžĐŋŅĐĩŅĐŊи</string>
|
||||
<string name="revanced_settings_restart">Đ ĐĩŅŅаŅŅиŅаĐŊĐĩ</string>
|
||||
<string name="revanced_settings_import">ĐĐŧĐŋĐžŅŅиŅаĐŊĐĩ</string>
|
||||
<string name="revanced_settings_import_copy">ĐĐžĐŋиŅаĐŊĐĩ</string>
|
||||
<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>
|
||||
@@ -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âĸ ĐŅĐēОи Shorts ĐŧĐžĐļĐĩ да ĐŊĐĩ Ņа ŅĐēŅиŅи\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>
|
||||
@@ -615,6 +630,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<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_use_this_sound_button_title">ĐŅŅĐžĐŊ âĐСĐŋĐžĐģСваĐŊĐĩ ĐŊа ŅОСи СвŅĐēâ</string>
|
||||
<string name="revanced_hide_shorts_use_this_sound_button_summary_on">ĐŅŅĐžĐŊ âĐСĐŋĐžĐģСваĐŊĐĩ ĐŊа ŅОСи СвŅĐēâ Đĩ ŅĐēŅиŅ</string>
|
||||
<string name="revanced_hide_shorts_use_this_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>
|
||||
@@ -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 timed out)</string>
|
||||
<string name="revanced_ryd_failure_connection_status_code">ĐĐĩŅ
аŅĐĩŅваĐŊиŅŅа ĐŊĐĩ Ņа ĐŊаĐģиŅĐŊи (status %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>
|
||||
@@ -1115,25 +1133,23 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_slide_to_seek_summary_on">ĐĄĐģаКд Са ĐŋŅĐĩвŅŅŅаĐŊĐĩ Đĩ аĐēŅивиŅаĐŊ</string>
|
||||
<string name="revanced_slide_to_seek_summary_off">ĐĄĐģаКд Са ĐŋŅĐĩвŅŅŅаĐŊĐĩ Đĩ Đ´ĐĩаĐēŅивиŅаĐŊ</string>
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
||||
<string name="revanced_spoof_client_screen_title">ĐОдĐŧŅĐŊа ĐŊа вĐĩŅŅиŅŅа (ĐŊа ĐēĐģиĐĩĐŊŅа)</string>
|
||||
<string name="revanced_spoof_client_screen_summary">ĐОдĐŧŅĐŊа ĐŊа вĐĩŅŅиŅŅа, Са да ĐŋŅĐĩĐ´ĐžŅвŅаŅиŅĐĩ ĐŋŅОйĐģĐĩĐŧи Ņ Đ˛ŅСĐŋŅОиСвĐĩĐļдаĐŊĐĩŅĐž ĐŊа видĐĩĐž</string>
|
||||
<string name="revanced_spoof_client_title">ĐОдĐŧŅĐŊа ĐŊа вĐĩŅŅиŅŅа (ĐŊа ĐēĐģиĐĩĐŊŅа)</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">ĐаŅиĐŊ Са ĐŋОдĐŧŅĐŊа ĐŊа вĐĩŅŅиŅŅа</string>
|
||||
<string name="revanced_spoof_client_ios_force_avc_title">ĐŅиĐŊŅдиŅĐĩĐģĐŊĐž AVC (H.264) Са iOS</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\nAVC иĐŧа ĐŧаĐēŅиĐŧаĐģĐŊа ŅаСдĐĩĐģиŅĐĩĐģĐŊа ŅĐŋĐžŅОйĐŊĐžŅŅ ĐžŅ 1080p и вŅСĐŋŅОиСвĐĩĐļдаĐŊĐĩŅĐž ĐŊа видĐĩĐž ŅĐĩ иСĐŋĐžĐģСва ĐŋОвĐĩŅĐĩ иĐŊŅĐĩŅĐŊĐĩŅ Đ´Đ°ĐŊĐŊи ĐžŅ VP9 иĐģи AV1.</string>
|
||||
<string name="revanced_spoof_client_about_android_ios_title">CŅŅаĐŊиŅĐŊи ĐĩŅĐĩĐēŅи ĐžŅ ĐŋОдĐŧŅĐŊаŅа ĐŊа 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>
|
||||
</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">ĐОдĐŋŅавŅĐŊĐĩ ĐŊа видĐĩĐž ĐŋĐžŅĐžŅи</string>
|
||||
<string name="revanced_spoof_video_streams_screen_summary">ĐОдĐŋŅавĐĩŅĐĩ ĐēĐģиĐĩĐŊŅŅĐēиŅĐĩ видĐĩĐž ĐŋĐžŅĐžŅи, Са да ĐŋŅĐĩĐ´ĐžŅвŅаŅиŅĐĩ ĐŋŅОйĐģĐĩĐŧи Ņ Đ˛ŅСĐŋŅОиСвĐĩĐļдаĐŊĐĩŅĐž</string>
|
||||
<string name="revanced_spoof_video_streams_title">ĐОдĐŋŅавŅĐŊĐĩ ĐŊа видĐĩĐž ĐŋĐžŅĐžŅи</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\nAVC иĐŧа ĐŧаĐēŅиĐŧаĐģĐŊа ŅаСдĐĩĐģиŅĐĩĐģĐŊа ŅĐŋĐžŅОйĐŊĐžŅŅ ĐžŅ 1080p и вŅСĐŋŅОиСвĐĩĐļдаĐŊĐĩŅĐž ĐŊа видĐĩĐž ŅĐĩ иСĐŋĐžĐģСва ĐŋОвĐĩŅĐĩ иĐŊŅĐĩŅĐŊĐĩŅ Đ´Đ°ĐŊĐŊи ĐžŅ VP9 иĐģи AV1.</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_title">CŅŅаĐŊиŅĐŊи ĐĩŅĐĩĐēŅи ĐžŅ ĐŋОдĐŧŅĐŊаŅа ĐŊа iOS</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_summary">âĸ ФиĐģĐŧи иĐģи ĐŋĐģаŅĐĩĐŊи видĐĩĐžĐēĐģиĐŋОвĐĩ ĐŧĐžĐļĐĩ да ĐŊĐĩ ŅĐĩ вŅСĐŋŅОиСвĐĩĐļдаŅ\nâĸ ĐĐžŅĐžŅиŅĐĩ ĐŊа ĐļивО СаĐŋĐžŅĐ˛Đ°Ņ ĐžŅĐŊаŅаĐģĐž\nâĸ ĐидĐĩĐžĐēĐģиĐŋОвĐĩŅĐĩ ĐŧĐžĐļĐĩ да СавŅŅŅĐ˛Đ°Ņ 1 ŅĐĩĐēŅĐŊда ĐŋĐž-ŅаĐŊĐž\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,6 +32,8 @@ 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">
|
||||
<string name="revanced_settings_confirm_user_dialog_title">āĻāĻĒāύāĻŋ āĻāĻŋ āĻāĻāĻŋā§ā§ āϝā§āϤ⧠āĻāĻā§āĻā§āĻ?</string>
|
||||
<string name="revanced_settings_reset">āĻāĻŦāĻžāϰ āϏā§āĻ āĻāϰā§āύ</string>
|
||||
@@ -42,6 +44,13 @@ 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 āĻĒā§āϝāĻžāĻ āϏāĻāϏā§āĻāϰāĻŖ <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>
|
||||
</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 +63,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 āĻĒā§āϝāĻžāĻ āϏāĻāϏā§āĻāϰāĻŖ <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>
|
||||
@@ -232,13 +233,9 @@ 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_about_title">āĻā§āĻā§āĻžāϰā§āĻĄ āĻĢāĻŋāϞā§āĻāĻžāϰāĻŋāĻ āϏāĻŽā§āĻĒāϰā§āĻā§</string>
|
||||
<string name="revanced_hide_keyword_content_about_summary">āĻĒā§āϰāϧāĻžāύ āĻĒāĻžāϤāĻž/āϏāĻžāĻŦāϏā§āĻā§āϰāĻŋāĻĒāĻļāύ/āĻ
āύā§āϏāύā§āϧāĻžāύ āĻĢāϞāĻžāĻĢāϞ āĻā§āϞ⧠āĻā§āĻā§āĻžāϰā§āĻĄ āĻŦāĻžāĻā§āϝāĻžāĻāĻļā§āϰ āϏāĻžāĻĨā§ āĻŽāĻŋāϞāĻŋā§ā§ āϞā§āĻāĻžāύā§āϰ āĻāύā§āϝ āĻĢāĻŋāϞā§āĻāĻžāϰ āĻāϰāĻž āĻšā§ā§āĻā§\n\nāϏā§āĻŽāĻžāĻŦāĻĻā§āϧāϤāĻž\nâĸ āĻāĻŋāĻā§ Shorts āύāĻžāĻ āϞā§āĻāĻžāύ⧠āĻšāϤ⧠āĻĒāĻžāϰā§\nâĸ āĻāĻŋāĻā§ āĻāĻāĻāĻ āĻāĻĒāĻžāĻĻāĻžāύ āύāĻžāĻ āϞā§āĻāĻžāύ⧠āĻšāϤ⧠āĻĒāĻžāϰā§\nâĸ āĻā§āύ āĻā§āĻā§āĻžāϰā§āĻĄ āϏāĻžāϰā§āĻ āĻāϰāϞ⧠āĻā§āύ āĻĢāϞāĻžāĻĢāϞ āύāĻžāĻ āĻĻā§āĻāĻžāϤ⧠āĻĒāĻžāϰā§</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. -->
|
||||
<!-- 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>
|
||||
</patch>
|
||||
<patch id="ad.general.HideAdsResourcePatch">
|
||||
<string name="revanced_hide_general_ads_title">āϏāĻžāϧāĻžāϰāĻŖ āĻŦāĻŋāĻā§āĻāĻžāĻĒāύ āϞā§āĻāĻžāύ</string>
|
||||
@@ -604,7 +601,6 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_player_overlay_opacity_invalid_toast">āĻĒā§āϞā§ā§āĻžāϰ āĻāĻāĻžāϰāϞ⧠āĻ
āϏā§āĻŦāĻā§āĻāϤāĻž āĻ
āĻŦāĻļā§āϝāĻ ā§Ļ-ā§§ā§Ļā§Ļ āĻāϰ āĻŽāϧā§āϝ⧠āĻšāϤ⧠āĻšāĻŦā§</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>
|
||||
@@ -1035,19 +1031,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||
<string name="revanced_slide_to_seek_summary_on">āĻāĻŋāĻĄāĻŋāĻāϰ āύāĻŋāϰā§āĻĻāĻŋāώā§āĻ āĻ
āĻāĻļā§ āϝā§āϤ⧠āĻāĻžāύā§āύ āϏāĻā§āϰāĻŋā§ āĻāϰāĻž āĻšā§ā§āĻā§</string>
|
||||
<string name="revanced_slide_to_seek_summary_off">āĻāĻŋāĻĄāĻŋāĻāϰ āύāĻŋāϰā§āĻĻāĻŋāώā§āĻ āĻ
āĻāĻļā§ āϝā§āϤ⧠āĻāĻžāύā§āύ āϏāĻā§āϰāĻŋā§ āĻāϰāĻž āĻšā§āύāĻŋ</string>
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
||||
<string name="revanced_spoof_client_screen_title">āĻā§āϞāĻžā§ā§āύā§āĻ āϏā§āĻĒā§āĻĢ āĻāϰā§āύ</string>
|
||||
<string name="revanced_spoof_client_screen_summary">āĻĒā§āϞā§āĻŦā§āϝāĻžāĻ āϏāĻŽāϏā§āϝāĻž āĻĒā§āϰāϤāĻŋāϰā§āϧ āĻāϰāϤ⧠āĻā§āϞāĻžā§ā§āύā§āĻ āϏā§āĻĒā§āĻĢ āĻāϰā§āύ</string>
|
||||
<string name="revanced_spoof_client_title">āĻā§āϞāĻžā§ā§āύā§āĻ āϏā§āĻĒā§āĻĢ āĻāϰā§āύ</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_about_android_vr_summary">âĸ āĻā§āύāĻ HDR āĻāĻŋāĻĄāĻŋāĻ āύā§āĻ\nâĸ āĻŦāĻžāĻā§āĻāĻžāĻĻā§āϰ āĻāĻŋāĻĄāĻŋāĻ āĻĒā§āϞā§āĻŦā§āϝāĻžāĻ āĻšāϝāĻŧ āύāĻž\nâĸ āĻŦāĻŋāϰāϤāĻŋ āĻĻā§āĻāϝāĻŧāĻž āĻāĻŋāĻĄāĻŋāĻāĻā§āϞāĻŋ āĻāϞā§āĻŽā§āϞā§āĻāĻžāĻŦā§ āĻāĻŦāĻžāϰ āĻļā§āϰ⧠āĻšāϤ⧠āĻĒāĻžāϰā§\nâĸ āύāĻŋāĻŽā§āύāĻŽāĻžāύā§āϰ āĻļāϰā§āĻāϏ āϏāĻŋāĻāĻŦāĻžāϰ āĻĨāĻžāĻŽā§āĻŦāύā§āϞ\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_user_dialog_message">āĻāĻ āϏā§āĻāĻŋāĻāĻāĻŋ āĻŦāύā§āϧ āĻāϰāĻžāϰ āĻĢāϞ⧠āĻāĻŋāĻĄāĻŋāĻ āĻĒā§āϞā§āĻŦā§āϝāĻžāĻ āϤā§āϰā§āĻāĻŋ āĻšāϤ⧠āĻĒāĻžāϰā§āĨ¤</string>
|
||||
</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