├── .gitignore ├── LICENSE ├── README.md ├── configs ├── .dialogrcDark └── .dialogrcLight ├── fetch_link.sh ├── fetch_patches.sh ├── fetch_versions.sh ├── install.sh ├── main.sh ├── revancify ├── revancify.keystore ├── root_util.sh └── sources.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | !sources.json 3 | *.jar 4 | *.apk 5 | *.sh 6 | !main.sh 7 | !root_util.sh 8 | !fetch*.sh 9 | !install.sh 10 | *.txt 11 | pup -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Revancify 🛠️ 2 | ### A TUI wrapper for Revanced CLI with amazing features. 3 | 4 | [![TelegramChannel](https://img.shields.io/badge/Telegram_Support_Chat-2CA5E0?style=for-the-badge&logo=Telegram&logoColor=FFFFFF)](https://t.me/revancifychat) 5 | 6 | ## Termux 7 | | Android Version | Download Link| 8 | | ---- | ----- | 9 | | Android 8+ | [Termux Monet](https://github.com/HardcodedCat/termux-monet/releases/latest) (Strictly Recommended) 10 | | Android 4+ | [Termux](https://github.com/termux/termux-app/releases/latest) 11 | 12 | # Features 13 | 1. Auto updates Patches and CLI 14 | 2. Interactive and Easy to use 15 | 3. Inbuilt scrapper for [ApkMirror](https://apkmirror.com) 16 | > Only support apps available on apkmirror. However, you can still download app manually and use the apk file to patch 17 | 4. Contains User-friendly Patch-options Editor 18 | 5. Conserve selected patches 19 | 6. Supports App Version downgrade for devices with Signature Spoof enabled 20 | 7. Convenient Installation and usage 21 | 6. Lightweight and faster than any other tool 22 | 23 | # Guide 24 | 25 | ## Installation 26 | 1. Open Termux. 27 | 2. Copy and paste this command. 28 | ``` 29 | curl -sL "https://raw.githubusercontent.com/decipher3114/Revancify/main/install.sh" | bash 30 | ``` 31 | 32 |
33 | If the above one doesn't work, use this. 34 | 35 | ``` 36 | pkg update -y -o Dpkg::Options::="--force-confnew" && pkg install git -y && git clone --depth=1 https://github.com/decipher3114/Revancify.git && ./Revancify/revancify 37 | ``` 38 |
39 | 40 | ## Usage 41 | After installation, type `revancify` in termux and press enter. 42 | 43 | Or use with arguments. Check them with `revancify -h` or `revancify --help` 44 | 45 | # Thanks & Credits 46 | [Revanced](https://github.com/revanced) 47 | [Revanced Extended](https://github.com/inotia00) 48 | -------------------------------------------------------------------------------- /configs/.dialogrcDark: -------------------------------------------------------------------------------- 1 | # 2 | # Run-time configuration file for dialog 3 | # 4 | # Custom Dark Config by "decipher3114" 5 | # 6 | # 7 | # Types of values: 8 | # 9 | # Number - 10 | # String - "string" 11 | # Boolean - 12 | # Attribute - (foreground,background,highlight?,underline?,reverse?) 13 | 14 | # Set aspect-ration. 15 | aspect = 0 16 | 17 | # Set separator (for multiple widgets output). 18 | separate_widget = "" 19 | 20 | # Set tab-length (for textbox tab-conversion). 21 | tab_len = 0 22 | 23 | # Make tab-traversal for checklist, etc., include the list. 24 | visit_items = OFF 25 | 26 | # Shadow dialog boxes? This also turns on color. 27 | use_shadow = OFF 28 | 29 | # Turn color support ON or OFF 30 | use_colors = ON 31 | 32 | # Screen color 33 | screen_color = (WHITE,BLACK,OFF) 34 | 35 | # Shadow color 36 | shadow_color = (BLACK,BLACK,OFF) 37 | 38 | # Dialog box color 39 | dialog_color = screen_color 40 | 41 | # Dialog box title color 42 | title_color = screen_color 43 | 44 | # Dialog box border color 45 | border_color = screen_color 46 | 47 | # Active button color 48 | button_active_color = (BLACK,WHITE,OFF) 49 | 50 | # Inactive button color 51 | button_inactive_color = screen_color 52 | 53 | # Active button key color 54 | button_key_active_color = button_active_color 55 | 56 | # Inactive button key color 57 | button_key_inactive_color = screen_color 58 | 59 | # Active button label color 60 | button_label_active_color = button_active_color 61 | 62 | # Inactive button label color 63 | button_label_inactive_color = screen_color 64 | 65 | # Input box color 66 | inputbox_color = screen_color 67 | 68 | # Input box border color 69 | inputbox_border_color = screen_color 70 | 71 | # Search box color 72 | searchbox_color = screen_color 73 | 74 | # Search box title color 75 | searchbox_title_color = title_color 76 | 77 | # Search box border color 78 | searchbox_border_color = border_color 79 | 80 | # File position indicator color 81 | position_indicator_color = title_color 82 | 83 | # Menu box color 84 | menubox_color = screen_color 85 | 86 | # Menu box border color 87 | menubox_border_color = border_color 88 | 89 | # Item color 90 | item_color = screen_color 91 | 92 | # Selected item color 93 | item_selected_color = button_active_color 94 | 95 | # Tag color 96 | tag_color = title_color 97 | 98 | # Selected tag color 99 | tag_selected_color = button_label_active_color 100 | 101 | # Tag key color 102 | tag_key_color = button_key_inactive_color 103 | 104 | # Selected tag key color 105 | tag_key_selected_color = button_active_color 106 | 107 | # Check box color 108 | check_color = screen_color 109 | 110 | # Selected check box color 111 | check_selected_color = button_active_color 112 | 113 | # Up arrow color 114 | uarrow_color = screen_color 115 | 116 | # Down arrow color 117 | darrow_color = uarrow_color 118 | 119 | # Item help-text color 120 | itemhelp_color = screen_color 121 | 122 | # Active form text color 123 | form_active_text_color = (WHITE,BLACK,OFF,ON) 124 | 125 | # Form text color 126 | form_text_color = screen_color 127 | 128 | # Readonly form item color 129 | form_item_readonly_color = screen_color 130 | 131 | # Dialog box gauge color 132 | gauge_color = title_color 133 | 134 | # Dialog box border2 color 135 | border2_color = border_color 136 | 137 | # Input box border2 color 138 | inputbox_border2_color = screen_color 139 | 140 | # Search box border2 color 141 | searchbox_border2_color = screen_color 142 | 143 | # Menu box border2 color 144 | menubox_border2_color = screen_color 145 | -------------------------------------------------------------------------------- /configs/.dialogrcLight: -------------------------------------------------------------------------------- 1 | # 2 | # Run-time configuration file for dialog 3 | # 4 | # Custom Light Config by "decipher3114" 5 | # 6 | # 7 | # Types of values: 8 | # 9 | # Number - 10 | # String - "string" 11 | # Boolean - 12 | # Attribute - (foreground,background,highlight?,underline?,reverse?) 13 | 14 | # Set aspect-ration. 15 | aspect = 0 16 | 17 | # Set separator (for multiple widgets output). 18 | separate_widget = "" 19 | 20 | # Set tab-length (for textbox tab-conversion). 21 | tab_len = 0 22 | 23 | # Make tab-traversal for checklist, etc., include the list. 24 | visit_items = OFF 25 | 26 | # Shadow dialog boxes? This also turns on color. 27 | use_shadow = OFF 28 | 29 | # Turn color support ON or OFF 30 | use_colors = ON 31 | 32 | # Screen color 33 | screen_color = (BLACK,WHITE,OFF) 34 | 35 | # Shadow color 36 | shadow_color = (WHITE,WHITE,OFF) 37 | 38 | # Dialog box color 39 | dialog_color = screen_color 40 | 41 | # Dialog box title color 42 | title_color = screen_color 43 | 44 | # Dialog box border color 45 | border_color = screen_color 46 | 47 | # Active button color 48 | button_active_color = (WHITE,BLACK,OFF) 49 | 50 | # Inactive button color 51 | button_inactive_color = screen_color 52 | 53 | # Active button key color 54 | button_key_active_color = button_active_color 55 | 56 | # Inactive button key color 57 | button_key_inactive_color = screen_color 58 | 59 | # Active button label color 60 | button_label_active_color = button_active_color 61 | 62 | # Inactive button label color 63 | button_label_inactive_color = screen_color 64 | 65 | # Input box color 66 | inputbox_color = screen_color 67 | 68 | # Input box border color 69 | inputbox_border_color = screen_color 70 | 71 | # Search box color 72 | searchbox_color = screen_color 73 | 74 | # Search box title color 75 | searchbox_title_color = title_color 76 | 77 | # Search box border color 78 | searchbox_border_color = border_color 79 | 80 | # File position indicator color 81 | position_indicator_color = title_color 82 | 83 | # Menu box color 84 | menubox_color = screen_color 85 | 86 | # Menu box border color 87 | menubox_border_color = border_color 88 | 89 | # Item color 90 | item_color = screen_color 91 | 92 | # Selected item color 93 | item_selected_color = button_active_color 94 | 95 | # Tag color 96 | tag_color = title_color 97 | 98 | # Selected tag color 99 | tag_selected_color = button_label_active_color 100 | 101 | # Tag key color 102 | tag_key_color = button_key_inactive_color 103 | 104 | # Selected tag key color 105 | tag_key_selected_color = button_active_color 106 | 107 | # Check box color 108 | check_color = screen_color 109 | 110 | # Selected check box color 111 | check_selected_color = button_active_color 112 | 113 | # Up arrow color 114 | uarrow_color = screen_color 115 | 116 | # Down arrow color 117 | darrow_color = uarrow_color 118 | 119 | # Item help-text color 120 | itemhelp_color = screen_color 121 | 122 | # Active form text color 123 | form_active_text_color = (BLACK,WHITE,OFF,ON) 124 | 125 | # Form text color 126 | form_text_color = screen_color 127 | 128 | # Readonly form item color 129 | form_item_readonly_color = screen_color 130 | 131 | # Dialog box gauge color 132 | gauge_color = title_color 133 | 134 | # Dialog box border2 color 135 | border2_color = border_color 136 | 137 | # Input box border2 color 138 | inputbox_border2_color = screen_color 139 | 140 | # Search box border2 color 141 | searchbox_border2_color = screen_color 142 | 143 | # Menu box border2 color 144 | menubox_border2_color = screen_color 145 | -------------------------------------------------------------------------------- /fetch_link.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | UserAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36" 4 | 5 | arch=$(getprop ro.product.cpu.abi) 6 | developer="$1" 7 | appName="$2" 8 | appVer="$3" 9 | 10 | page1=$(curl -vsL -A "$UserAgent" "https://www.apkmirror.com/apk/$developer/$appName/$appName-$appVer-release" 2>&1) 11 | 12 | canonicalUrl=$(pup -p --charset utf-8 'link[rel="canonical"] attr{href}' <<<"$page1") 13 | if [[ "$canonicalUrl" == *"apk-download"* ]]; then 14 | url1=("${canonicalUrl/"https://www.apkmirror.com/"//}") 15 | else 16 | grep -q 'class="error404"' <<<"$page1" && echo noversion >&2 && exit 1 17 | 18 | page2=$(pup -p --charset utf-8 ':parent-of(:parent-of(span:contains("APK")))' <<<"$page1") 19 | 20 | [[ "$(pup -p --charset utf-8 ':parent-of(div:contains("noarch"))' <<<"$page2")" == "" ]] || arch=noarch 21 | [[ "$(pup -p --charset utf-8 ':parent-of(div:contains("universal"))' <<<"$page2")" == "" ]] || arch=universal 22 | 23 | readarray -t url1 < <(pup -p --charset utf-8 ":parent-of(div:contains(\"$arch\")) a.accent_color attr{href}" <<<"$page2") 24 | 25 | [ "${#url1[@]}" -eq 0 ] && echo noapk >&2 && exit 1 26 | fi 27 | echo 33 28 | 29 | url2=$(curl -sL -A "$UserAgent" "https://www.apkmirror.com${url1[-1]}" | pup -p --charset utf-8 'a:contains("Download APK") attr{href}') 30 | 31 | [ "$url2" == "" ] && echo error >&2 && exit 1 32 | echo 66 33 | 34 | url3=$(curl -sL -A "$UserAgent" "https://www.apkmirror.com$url2" | pup -p --charset UTF-8 'a[data-google-vignette="false"][rel="nofollow"] attr{href}') 35 | 36 | [ "$url3" == "" ] && echo error >&2 && exit 1 37 | echo 100 38 | 39 | echo "https://www.apkmirror.com$url3" >&2 -------------------------------------------------------------------------------- /fetch_patches.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source="$1" 4 | storagePath="$3" 5 | 6 | patchesJson=$(jq '.' "$source"-patches-*.json) 7 | 8 | savedJson=$(jq '.' "$storagePath/$source-patches.json" 2>/dev/null || jq -n '[]') 9 | 10 | if ! jq -n --argjson savedJson "$savedJson" '$savedJson' > /dev/null 2>&1; then 11 | savedJson=$(jq -n '[]') 12 | fi 13 | 14 | allPackages=$(echo "$patchesJson" | jq '[.[].compatiblePackages | if . != null then .[].name else empty end]') 15 | 16 | pkgs=$(jq -n --argjson allPackages "$allPackages" '[$allPackages | to_entries[] | .value as $pkg | map($allPackages | to_entries[] | select(.value == $pkg).key)[0] as $index | if $index == .key then $pkg else empty end]') 17 | 18 | generatedJson=$(jq --null-input --argjson pkgs "$pkgs" --argjson savedJson "$savedJson" --slurpfile patchesFile "$source"-patches-*.json ' 19 | [ 20 | $patchesFile[] | . as $patchesJson | 21 | $pkgs[] | . as $pkgName | 22 | { 23 | "pkgName": ., 24 | "appName": ( 25 | if (($savedJson | length) != 0) then 26 | ($savedJson[] | select(.pkgName == $pkgName) | .appName) // null 27 | else 28 | null 29 | end 30 | ), 31 | "apkmirrorAppName": ( 32 | if (($savedJson | length) != 0) then 33 | ($savedJson[] | select(.pkgName == $pkgName) | .apkmirrorAppName) // null 34 | else 35 | null 36 | end 37 | ), 38 | "developerName": ( 39 | if (($savedJson | length) != 0) then 40 | ($savedJson[] | select(.pkgName == $pkgName) | .developerName) // null 41 | else 42 | null 43 | end 44 | ), 45 | "versions": ( 46 | [ 47 | $patchesJson[] | 48 | .compatiblePackages | 49 | if . != null then 50 | if ((map(.name) | index($pkgName)) != null) then 51 | .[(map(.name) | index($pkgName))].versions | if . != null then .[] else empty end 52 | else 53 | empty 54 | end 55 | else 56 | empty 57 | end] | 58 | unique 59 | ), 60 | "includedPatches": ( 61 | if (($savedJson | length) != 0) then 62 | [ 63 | ( 64 | ($savedJson[] | select(.pkgName == $pkgName)) | 65 | if ((.includedPatches | length) == 0) then 66 | ( 67 | $patchesJson[] | 68 | .name as $patchName | 69 | .use as $use | 70 | .excluded as $excluded | 71 | .compatiblePackages | 72 | if . != null then 73 | if ((((map(.name) | index($pkgName)) != null) or (length == 0)) and (($use == true) or ($excluded == false))) then 74 | $patchName 75 | else 76 | empty 77 | end 78 | else 79 | empty 80 | end 81 | ) 82 | else 83 | .includedPatches[] 84 | end) // 85 | ( 86 | $patchesJson[] | 87 | .name as $patchName | 88 | .use as $use | 89 | .excluded as $excluded | 90 | .compatiblePackages | 91 | if . != null then 92 | if ((((map(.name) | index($pkgName)) != null) or (length == 0)) and (($use == true) or ($excluded == false))) then 93 | $patchName 94 | else 95 | empty 96 | end 97 | else 98 | "hi" 99 | end 100 | ) 101 | ] 102 | else 103 | [( 104 | $patchesJson[] | 105 | .name as $patchName | 106 | .use as $use | 107 | .excluded as $excluded | 108 | .compatiblePackages | 109 | if . != null then 110 | if ((((map(.name) | index($pkgName)) != null) or (length == 0)) and (($use == true) or ($excluded == false))) then 111 | $patchName 112 | else 113 | empty 114 | end 115 | else 116 | empty 117 | end 118 | )] 119 | end 120 | ) 121 | } 122 | ]') 123 | 124 | if [ "$2" == "online" ]; then 125 | response=$(curl --fail-early --connect-timeout 2 --max-time 5 -s 'https://www.apkmirror.com/wp-json/apkm/v1/app_exists/' \ 126 | -H 'Accept: application/json' \ 127 | -H 'Content-Type: application/json' \ 128 | -H 'Authorization: Basic YXBpLXRvb2xib3gtZm9yLWdvb2dsZS1wbGF5OkNiVVcgQVVMZyBNRVJXIHU4M3IgS0s0SCBEbmJL' \ 129 | -H 'User-Agent: Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.86 Mobile Safari/537.36' \ 130 | -d "$(jq -n --argjson pkgs "$pkgs" '{"pnames": $pkgs}')") 131 | 132 | if ! responseJson=$( 133 | echo "$response" | jq '[.data[] | 134 | if .exists then 135 | ({ 136 | "key": (.pname), 137 | "value": { 138 | "appName": (.app.name | sub("( -)|( &)|:"; ""; "g") | sub("[()\\|]"; ""; "g") | sub(" *[-, ] *"; "-"; "g") | sub("-Wear-OS"; ""; "g")), 139 | "apkmirrorAppName": (.app.link | sub("-wear-os"; "") | match("(?<=\\/)(((?!\\/).)*)(?=\\/$)").string), 140 | "developerName": (.app.link | match("(?<=apk\\/).*?(?=\\/)").string) 141 | } 142 | }) 143 | else 144 | empty 145 | end] | from_entries' 2>/dev/null 146 | ); then 147 | echo error 148 | exit 1 149 | fi 150 | generatedJson=$( 151 | jq -n --argjson generatedJson "$generatedJson" --argjson responseJson "$responseJson" '[ 152 | $generatedJson[] | .pkgName as $pkgName | (.appName = ($responseJson[$pkgName].appName)) | (.apkmirrorAppName = ($responseJson[$pkgName].apkmirrorAppName)) | (.developerName = ($responseJson[$pkgName].developerName))]' 153 | 2>/dev/null || { echo error && exit 1 ;}) 154 | fi 155 | 156 | echo "$generatedJson" | jq '.' >"$storagePath/$source-patches.json" 157 | -------------------------------------------------------------------------------- /fetch_versions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | UserAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36" 4 | 5 | appName="$1" 6 | apkmirrorAppName="$2" 7 | patchesSource="$3" 8 | currentVersion="$4" 9 | storagePath="$5" 10 | 11 | page1=$(curl --fail-early --connect-timeout 2 --max-time 5 -sL -A "$UserAgent" "https://www.apkmirror.com/uploads/?appcategory=$apkmirrorAppName" 2>&1) 12 | 13 | [ "$page1" == "" ] && echo error && exit 1 14 | 15 | readarray -t versions < <(pup -p 'div.widget_appmanager_recentpostswidget h5 a.fontBlack text{}' <<<"$page1") 16 | 17 | supportedVers=$(jq -r --arg apkmirrorAppName "$apkmirrorAppName" '[.[] | select(.apkmirrorAppName == $apkmirrorAppName).versions[] | sub(" *[-, ] *"; "-"; "g") | sub(":"; ""; "g")]' "$storagePath/$patchesSource-patches.json") 18 | 19 | jq -r -n --arg appName "$appName-"\ 20 | --arg currentVersion "$currentVersion"\ 21 | --argjson supportedVers "$supportedVers"\ 22 | '($currentVersion | sub(" *[-, ] *"; "-"; "g") | sub(":"; ""; "g")) as $installedVer | 23 | [ 24 | [ 25 | $ARGS.positional[] | 26 | sub("( -)|( &)|:"; ""; "g") | 27 | sub("[()\\|]"; ""; "g") | 28 | sub(" *[-, ] *"; "-"; "g") | 29 | sub($appName; "") 30 | ] | 31 | . |= . + $supportedVers | 32 | unique | 33 | reverse | 34 | index($installedVer) as $index | 35 | if $index == null then 36 | .[] 37 | else 38 | .[0:($index + 1)][] 39 | end | . as $version | 40 | if (($supportedVers | index($version)) != null) then 41 | $version, "[RECOMMENDED]" 42 | elif ($version | test("beta|Beta|BETA")) then 43 | $version | sub("(?<=[0-9])-[a-zA-Z]*$"; ""), "[BETA]" 44 | elif ($version | test("alpha|Alpha|ALPHA")) then 45 | $version | sub("(?<=[0-9])-[a-zA-Z]$"; ""), "[ALPHA]" 46 | else 47 | $version, "[STABLE]" 48 | end 49 | ] | 50 | if (. | index($installedVer)) != null then 51 | .[-1] |= "[INSTALLED]" 52 | else 53 | . 54 | end | 55 | if ((. | index("[RECOMMENDED]")) != null) then 56 | . |= ["Auto Select", "[RECOMMENDED]"] + . 57 | else 58 | . 59 | end | 60 | .[]' --args "${versions[@]}" 61 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | servers=("google.com" "raw.githubusercontent.com") 4 | 5 | for server in "${servers[@]}"; do 6 | if ! ping -c 1 -W 3 "$server"&> /dev/null; then 7 | echo -e "\e[1;31m$server is not reachable with your current network.\nChange your network configuration.\e[0m" 8 | fi 9 | done 10 | 11 | if [ -z "$TERMUX_VERSION" ]; then 12 | echo -e "\e[1;31mTermux not detected !!\e[0m\n\e[1;31mInstall aborted !!\e[0m" 13 | exit 1 14 | fi 15 | 16 | if [ -d "$HOME/Revancify" ]; then 17 | ./Revancify/revancify 18 | exit 0 19 | fi 20 | 21 | if ! command -v git &> /dev/null; then 22 | if ! pkg update -y -o Dpkg::Options::="--force-confnew"; then 23 | echo -e "\e[1;31mOops !! 24 | Possible causes of error: 25 | 1. Termux from Playstore is not maintained. Download Termux from github. 26 | 2. Unstable internet Connection. 27 | 3. Repository issues. Clear Termux Data and retry." 28 | exit 1 29 | fi 30 | pkg install git -y -o Dpkg::Options::="--force-confnew" 31 | fi 32 | 33 | if git clone --depth=1 https://github.com/decipher3114/Revancify.git; then 34 | $HOME/Revancify/revancify 35 | else 36 | echo -e "\e[1;31mInstall Failed !!\e[0m" 37 | echo "Please Try again" 38 | exit 1 39 | fi -------------------------------------------------------------------------------- /main.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | terminate() { 4 | killall -9 java &> /dev/null 5 | killall -9 dialog &> /dev/null 6 | killall -9 wget &> /dev/null 7 | clear 8 | exit "${1:-1}" 9 | } 10 | trap terminate SIGTERM SIGINT SIGABRT 11 | 12 | setEnv() { 13 | if [ ! -f "$4" ]; then 14 | : > "$4" 15 | fi 16 | if ! grep -q "${1}=" "$4"; then 17 | echo "$1=$2" >> "$4" 18 | elif [ "$3" == "update" ]; then 19 | sed -i "s/$1=.*/$1=$2/" "$4" 20 | fi 21 | } 22 | 23 | initialize() { 24 | internalStorage="/storage/emulated/0" 25 | storagePath="$internalStorage/Revancify" 26 | [ ! -d "$storagePath" ] && mkdir -p "$storagePath" 27 | [ ! -d apps ] && mkdir -p apps 28 | arch=$(getprop ro.product.cpu.abi) 29 | repoDir="$HOME/Revancify" 30 | header=(dialog --backtitle "Revancify | [Arch: $arch, Root: $root]" --no-shadow) 31 | envFile=config.cfg 32 | [ ! -f "apps/.appSize" ] && : > "apps/.appSize" 33 | userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36" 34 | 35 | AutocheckToolsUpdate="" Riplibs="" LightTheme="" ShowConfirmPatchesMenu="" LaunchAppAfterMount="" AllowVersionDowngrade="" FetchPreReleasedTools="" 36 | setEnv AutocheckToolsUpdate false init "$envFile" 37 | setEnv Riplibs true init "$envFile" 38 | setEnv LightTheme false init "$envFile" 39 | setEnv ShowConfirmPatchesMenu false init "$envFile" 40 | setEnv LaunchAppAfterMount true init "$envFile" 41 | setEnv AllowVersionDowngrade false init "$envFile" 42 | setEnv FetchPreReleasedTools false init "$envFile" 43 | # shellcheck source=/dev/null 44 | source "$envFile" 45 | if [ -z "$source" ]; then 46 | readarray -t allSources < <(jq -r --arg source "$source" 'to_entries | .[] | .key,"["+.value.projectName+"]","on"' "$repoDir"/sources.json) 47 | source=$("${header[@]}" --begin 2 0 --title '| Source Selection Menu |' --no-cancel --ok-label "Done" --radiolist "Use arrow keys to navigate; Press Spacebar to select option" -1 -1 0 "${allSources[@]}" 2>&1 >/dev/tty) 48 | setEnv source "$source" update "$envFile" 49 | fi 50 | [ "$root" == true ] && menuEntry="Uninstall Patched app" || menuEntry="Download Microg" 51 | 52 | [ "$LightTheme" == true ] && theme=Light || theme=Dark 53 | export DIALOGRC="$repoDir/configs/.dialogrc$theme" 54 | 55 | # shellcheck source=/dev/null 56 | source <(jq -r --arg source "$source" '.[$source].sources | to_entries[] | .key+"Source="+.value.org' "$repoDir"/sources.json) 57 | sourceName=$(jq -r --arg source "$source" '.[$source].projectName' "$repoDir"/sources.json) 58 | 59 | if [ "$online" != false ]; then 60 | if [ "$AutocheckToolsUpdate" == true ]; then 61 | getTools || terminate 1 62 | else 63 | checkTools || terminate 1 64 | fi 65 | fi 66 | 67 | if [ -f "$storagePath/$source-patches.json" ]; then 68 | bash "$repoDir/fetch_patches.sh" "$source" offline "$storagePath" &> /dev/null 69 | refreshJson || return 1 70 | fi 71 | } 72 | 73 | internet() { 74 | if ! ping -c 1 google.com &> /dev/null; then 75 | "${header[@]}" --msgbox "Oops! No Internet Connection available.\n\nConnect to Internet and try again later." 12 45 76 | return 1 77 | fi 78 | } 79 | 80 | fetchToolsAPI() { 81 | internet || return 1 82 | 83 | "${header[@]}" --infobox "Please Wait !!\nFetching tools data from github API..." 12 45 84 | readarray -t tools < <(jq -r --arg source "$source" '.[$source].sources | keys_unsorted[]' "$repoDir"/sources.json) 85 | readarray -t links < <(jq -r --arg source "$source" '.[$source].sources[] | .org+"/"+.repo' "$repoDir"/sources.json) 86 | : >".${source}-data" 87 | [ "$FetchPreReleasedTools" == false ] && stableRelease="/latest" || stableRelease="" 88 | i=0 && for tool in "${tools[@]}"; do 89 | curl -s --fail-early --connect-timeout 2 --max-time 5 "https://api.github.com/repos/${links[$i]}/releases$stableRelease" | jq -r --arg tool "$tool" 'if type == "array" then .[0] else . end | $tool+"Latest="+.tag_name, (.assets[] | if .content_type == "application/json" then "jsonUrl="+.browser_download_url, "jsonSize="+(.size|tostring) else $tool+"Url="+.browser_download_url, $tool+"Size="+(.size|tostring) end)' >>".${source}-data" 90 | i=$(("$i" + 1)) 91 | done 92 | 93 | if [ "$(wc -l <".${source}-data")" -lt "11" ]; then 94 | "${header[@]}" --msgbox "Oops! Unable to connect to Github.\n\nRetry or change your Network." 12 45 95 | return 1 96 | fi 97 | # shellcheck source=/dev/null 98 | source ./".${source}-data" 99 | 100 | cliAvailableSize=$(ls "$cliSource"-cli-*.jar &> /dev/null && du -b "$cliSource"-cli-*.jar | cut -d $'\t' -f 1 || echo 0) 101 | patchesAvailableSize=$(ls "$patchesSource"-patches-*.jar &> /dev/null && du -b "$patchesSource"-patches-*.jar | cut -d $'\t' -f 1 || echo 0) 102 | integrationsAvailableSize=$(ls "$integrationsSource"-integrations-*.apk &> /dev/null && du -b "$integrationsSource"-integrations-*.apk | cut -d $'\t' -f 1 || echo 0) 103 | } 104 | 105 | getTools() { 106 | fetchToolsAPI || return 1 107 | if [ -f "$patchesSource-patches-$patchesLatest.jar" ] && [ -f "$patchesSource-patches-$patchesLatest.json" ] && [ -f "$cliSource-cli-$cliLatest.jar" ] && [ -f "$integrationsSource-integrations-$integrationsLatest.apk" ] && [ "$cliSize" == "$cliAvailableSize" ] && [ "$patchesSize" == "$patchesAvailableSize" ] && [ "$integrationsSize" == "$integrationsAvailableSize" ]; then 108 | if [ "$(bash "$repoDir/fetch_patches.sh" "$source" online "$storagePath")" == "error" ]; then 109 | "${header[@]}" --msgbox "Tools are successfully downloaded but Apkmirror API is not accessible. So, patches are not successfully synced.\nRevancify may crash.\n\nChange your network." 12 45 110 | return 1 111 | fi 112 | "${header[@]}" --msgbox "Tools are already downloaded !!\n\nPatches are successfully synced." 12 45 113 | return 0 114 | fi 115 | [ -f "$patchesSource-patches-$patchesLatest.jar" ] || { rm "$patchesSource"-patches-*.jar &> /dev/null && rm "$patchesSource"-patches-*.json &> /dev/null && patchesAvailableSize=0 ;} 116 | [ -f "$cliSource-cli-$cliLatest.jar" ] || { rm "$cliSource"-cli-*.jar &> /dev/null && cliAvailableSize=0 ;} 117 | [ -f "$integrationsSource-integrations-$integrationsLatest.apk" ] || { rm "$integrationsSource"-integrations-*.apk &> /dev/null && integrationsAvailableSize=0 ;} 118 | [ "$cliSize" != "$cliAvailableSize" ] && 119 | wget -q -c "$cliUrl" -O "$cliSource"-cli-"$cliLatest".jar --show-progress --user-agent="$userAgent" 2>&1 | stdbuf -o0 cut -b 63-65 | stdbuf -o0 grep '[0-9]' | "${header[@]}" --begin 2 0 --gauge "Source : $cliSource\nTool : CLI\nVersion : $cliLatest\nSize : $(numfmt --to=iec --format="%0.1f" "$cliSize")\n\nDownloading..." -1 -1 "$(($((cliAvailableSize * 100)) / cliSize))" && tput civis 120 | 121 | [ "$cliSize" != "$(ls "$cliSource"-cli-*.jar &> /dev/null && du -b "$cliSource"-cli-*.jar | cut -d $'\t' -f 1 || echo 0)" ] && "${header[@]}" --msgbox "Oops! Unable to download completely.\n\nRetry or change your Network." 12 45 && return 1 122 | 123 | [ "$patchesSize" != "$patchesAvailableSize" ] && 124 | wget -q -c "$patchesUrl" -O "$patchesSource"-patches-"$patchesLatest".jar --show-progress --user-agent="$userAgent" 2>&1 | stdbuf -o0 cut -b 63-65 | stdbuf -o0 grep '[0-9]' | "${header[@]}" --begin 2 0 --gauge "Source : $patchesSource\nTool : Patches\nVersion : $patchesLatest\nSize : $(numfmt --to=iec --format="%0.1f" "$patchesSize")\n\nDownloading..." -1 -1 "$(($((patchesAvailableSize * 100 / patchesSize))))" && tput civis && patchesUpdated=true 125 | 126 | wget -q -c "$jsonUrl" -O "$patchesSource"-patches-"$patchesLatest".json --user-agent="$userAgent" 127 | 128 | [ "$patchesSize" != "$(ls "$patchesSource"-patches-*.jar &> /dev/null && du -b "$patchesSource"-patches-*.jar | cut -d $'\t' -f 1 || echo 0)" ] && "${header[@]}" --msgbox "Oops! Unable to download completely.\n\nRetry or change your Network." 12 45 && return 1 129 | 130 | [ "$integrationsSize" != "$integrationsAvailableSize" ] && 131 | wget -q -c "$integrationsUrl" -O "$integrationsSource"-integrations-"$integrationsLatest".apk --show-progress --user-agent="$userAgent" 2>&1 | stdbuf -o0 cut -b 63-65 | stdbuf -o0 grep '[0-9]' | "${header[@]}" --begin 2 0 --gauge "Source : $integrationsSource\nTool : Integrations\nVersion : $integrationsLatest\nSize : $(numfmt --to=iec --format="%0.1f" "$integrationsSize")\n\nDownloading..." -1 -1 "$(($((integrationsAvailableSize * 100 / integrationsSize))))" && tput civis 132 | 133 | [ "$integrationsSize" != "$(ls "$integrationsSource"-integrations-*.apk &> /dev/null && du -b "$integrationsSource"-integrations-*.apk | cut -d $'\t' -f 1 || echo 0)" ] && "${header[@]}" --msgbox "Oops! File not downloaded.\n\nRetry or change your Network." 12 45 && return 1 134 | 135 | if [ "$patchesUpdated" == true ]; then 136 | "${header[@]}" --infobox "Updating patches and options file..." 12 45 137 | if [ "$(bash "$repoDir/fetch_patches.sh" "$source" online "$storagePath")" == "error" ]; then 138 | "${header[@]}" --msgbox "Tools are successfully downloaded but Apkmirror API is not accessible. So, patches are not successfully synced.\nRevancify may crash.\n\nChange your network." 12 45 139 | return 1 140 | fi 141 | java -jar "$cliSource"-cli-*.jar options -ou -p "$storagePath/$source-options.json" "$patchesSource"-patches-*.jar &> /dev/null 142 | fi 143 | 144 | refreshJson || return 1 145 | } 146 | 147 | changeSource() { 148 | internet || return 1 149 | readarray -t allSources < <(jq -r --arg source "$source" 'to_entries | .[] | if .key == $source then .key,"["+.value.projectName+"]","on" else .key,"["+.value.projectName+"]","off" end' "$repoDir"/sources.json) 150 | selectedSource=$("${header[@]}" --begin 2 0 --title '| Source Selection Menu |' --no-cancel --ok-label "Done" --radiolist "Use arrow keys to navigate; Press Spacebar to select option" -1 -1 0 "${allSources[@]}" 2>&1 >/dev/tty) 151 | if [ "$source" != "$selectedSource" ]; then 152 | source="$selectedSource" 153 | # shellcheck source=/dev/null 154 | source <(jq -r --arg source "$source" '.[$source].sources | to_entries[] | .key+"Source="+.value.org' "$repoDir"/sources.json) 155 | setEnv source "$selectedSource" update "$envFile" 156 | sourceName=$(jq -r --arg source "$source" '.[$source].projectName' "$repoDir"/sources.json) 157 | checkTools || return 1 158 | fi 159 | } 160 | 161 | selectApp() { 162 | [ "$1" == "storage" ] && helpTag=(--help-button --help-label "From Storage") || helpTag=() 163 | previousAppName="$appName" 164 | readarray -t availableApps < <(jq -n -r --argjson appsArray "$appsArray" '$appsArray[] | .index, .appName, .pkgName') 165 | appIndex=$("${header[@]}" --begin 2 0 --title '| App Selection Menu |' --item-help --default-item "$appIndex" "${helpTag[@]}" --ok-label "Select" --cancel-label "Back" --menu "Use arrow keys to navigate\nSource: $sourceName" $(($(tput lines) - 3)) -1 15 "${availableApps[@]}" 2>&1 >/dev/tty) 166 | appSelectResult=$? 167 | case $appSelectResult in 168 | 0) 169 | readarray -t appSelectedResult < <(jq -n -r --arg appIndex "$appIndex" --argjson appsArray "$appsArray" '$appsArray[] | select(.index == ($appIndex | tonumber)) | .appName, .pkgName, .developerName, .apkmirrorAppName') 170 | appName="${appSelectedResult[0]}" 171 | pkgName="${appSelectedResult[1]}" 172 | developerName="${appSelectedResult[2]}" 173 | apkmirrorAppName="${appSelectedResult[3]}" 174 | appType=downloaded 175 | ;; 176 | 1) 177 | return 1 178 | ;; 179 | 2) 180 | appType=local 181 | unset appName appVer 182 | ;; 183 | esac 184 | if [ "$previousAppName" != "$appName" ]; then 185 | unset appVerList 186 | fi 187 | } 188 | 189 | selectPatches() { 190 | while true; do 191 | readarray -t patchesInfo < <( 192 | jq -n -r --arg pkgName "$pkgName" \ 193 | --slurpfile patchesFile "$patchesSource"-patches-*.json \ 194 | --argjson includedPatches "$includedPatches" \ 195 | '$patchesFile[][] | 196 | .name as $patchName | 197 | .description as $desc | 198 | .compatiblePackages | 199 | if . != null then 200 | if (((map(.name) | index($pkgName)) != null) or (length == 0)) then 201 | ( 202 | if ((($includedPatches | length) != 0) and (($includedPatches[] | select(.pkgName == $pkgName).includedPatches | index($patchName)) != null)) then 203 | $patchName, "on", $desc 204 | else 205 | $patchName, "off", $desc 206 | end 207 | ) 208 | else 209 | empty 210 | end 211 | else 212 | if ((($includedPatches | length) != 0) and (($includedPatches[] | select(.pkgName == $pkgName).includedPatches | index($patchName)) != null)) then 213 | $patchName, "on", $desc 214 | else 215 | $patchName, "off", $desc 216 | end 217 | end' 218 | ) 219 | grep -qoP "(?<=\s)off(?=\s)" <<< "${patchesInfo[@]}" && toogleName="Include All" || toogleName="Exclude All" 220 | choices=$("${header[@]}" --begin 2 0 --title '| Patch Selection Menu |' --item-help --no-items --separate-output --ok-label "$1" --cancel-label "$toogleName" --help-button --help-label "Recommended" --checklist "Use arrow keys to navigate; Press Spacebar to toogle patch\nSource: $sourceName; AppName: $appName" $(($(tput lines) - 3)) -1 15 "${patchesInfo[@]}" 2>&1 >/dev/tty) 221 | selectPatchStatus=$? 222 | readarray -t choices <<< "$choices" 223 | patchSaver || break 224 | done 225 | } 226 | 227 | patchSaver() { 228 | case "$selectPatchStatus" in 229 | 0 ) 230 | includedPatches=$(jq -n --arg pkgName "$pkgName" --argjson includedPatches "$includedPatches" '[$includedPatches[] | select(.pkgName == $pkgName).includedPatches = $ARGS.positional]' --args "${choices[@]}") 231 | echo "$includedPatches" >"$storagePath/$source-patches.json" && return 1 232 | ;; 233 | 1 ) 234 | if [ "$toogleName" == "Include All" ]; then 235 | includedPatches=$(jq -n --arg pkgName "$pkgName" --slurpfile patchesFile "$patchesSource"-patches-*.json --argjson includedPatches "$includedPatches" ' 236 | [ 237 | $includedPatches[] | 238 | select(.pkgName == $pkgName).includedPatches = [ 239 | $patchesFile[][] | 240 | .name as $patchName | 241 | .compatiblePackages | 242 | if . != null then 243 | if (((map(.name) | index($pkgName)) != null) or (length == 0)) then 244 | $patchName 245 | else 246 | empty 247 | end 248 | else 249 | $patchName 250 | end 251 | ] 252 | ]' 253 | ) 254 | elif [ "$toogleName" == "Exclude All" ]; then 255 | includedPatches=$(jq -n --arg pkgName "$pkgName" --argjson includedPatches "$includedPatches" '[$includedPatches[] | select(.pkgName == $pkgName).includedPatches = []]') 256 | fi 257 | ;; 258 | 2 ) 259 | includedPatches=$(jq -n --arg pkgName "$pkgName" --slurpfile patchesFile "$patchesSource"-patches-*.json --argjson includedPatches "$includedPatches" ' 260 | [ 261 | $includedPatches[] | 262 | select(.pkgName == $pkgName).includedPatches = [ 263 | $patchesFile[][] | 264 | .name as $patchName | 265 | .use as $use | 266 | .excluded as $excluded | 267 | .compatiblePackages | 268 | if . != null then 269 | if ((((map(.name) | index($pkgName)) != null) or (length == 0)) and (($use == true) or ($excluded == false))) then 270 | $patchName 271 | else 272 | empty 273 | end 274 | else 275 | $patchName 276 | end 277 | ] 278 | ]') 279 | ;; 280 | esac 281 | } 282 | 283 | editPatchOptions() { 284 | if [ ! -f "$storagePath/$source-options.json" ]; then 285 | java -jar "$cliSource"-cli-*.jar options -ou -p "$storagePath/$source-options.json" "$patchesSource"-patches-*.jar &> /dev/null 286 | fi 287 | currentPatch="none" 288 | optionsJson=$(jq '.' "$storagePath/$source-options.json") 289 | readarray -t patchNames < <(jq -n -r --argjson optionsJson "$optionsJson" '$optionsJson[].patchName') 290 | while true; do 291 | if [ "$currentPatch" == "none" ]; then 292 | if ! currentPatch=$("${header[@]}" --begin 2 0 --title '| Patch Options Menu |' --no-items --ok-label "Select" --cancel-label "Back" --menu "Select Patch to edit options" -1 -1 0 "${patchNames[@]}" 2>&1 >/dev/tty); then 293 | jq -n --argjson optionsJson "$optionsJson" '$optionsJson' > "$storagePath/$source-options.json" 294 | break 295 | fi 296 | else 297 | tput cnorm 298 | readarray -t patchOptionEntries < <(jq -n -r --arg currentPatch "$currentPatch" --argjson optionsJson "$optionsJson" '$optionsJson[] | select(.patchName == $currentPatch) | .options | to_entries[] | .key as $key | (.value | (.key | length) as $wordLength | ((($key+1) | tostring) + ". " + .key + ":"), ($key*2)+1, 0, .value, ($key*2)+1, ($wordLength + 6), 100, 100)') 299 | readarray -t newValues < <("${header[@]}" --begin 2 0 --title '| Patch Options Form |' --ok-label "Save" --cancel-label "Back" --form "Edit patch options for \"$currentPatch\" patch" -1 -1 0 "${patchOptionEntries[@]}" 2>&1 >/dev/tty) 300 | if [ "${newValues[*]}" != "" ]; then 301 | optionsJson=$(jq -n -r --arg currentPatch "$currentPatch" --argjson optionsJson "$optionsJson" '$optionsJson | map((select(.patchName == $currentPatch) | .options) |= [(to_entries[] | .key as $key | .value.value = (if $ARGS.positional[$key] == "" then null elif $ARGS.positional[$key] == "null" then null elif $ARGS.positional[$key] == "true" then true elif $ARGS.positional[$key] == "false" then false else $ARGS.positional[$key] end)) | .value])' --args "${newValues[@]}") 302 | fi 303 | currentPatch="none" 304 | tput civis 305 | fi 306 | done 307 | } 308 | 309 | initInstall() { 310 | if [ "$root" == true ]; 311 | then 312 | "${header[@]}" --infobox "Please Wait !!\nInstalling Patched $appName..." 12 45 313 | if ! su -mm -c "/system/bin/sh $repoDir/root_util.sh mount $pkgName $appName $appVer $sourceName" > /dev/null 2>&1; then 314 | "${header[@]}" --msgbox "Installation Failed !!\nLogs saved to \"Internal-Storage/Revancify/install_log.txt\". Share the Install logs to developer." 12 45 315 | return 1 316 | else 317 | "${header[@]}" --msgbox "$appName installed Successfully !!" 12 45 318 | fi 319 | if [ "$LaunchAppAfterMount" == true ]; then 320 | su -c "settings list secure | sed -n -e 's/\/.*//' -e 's/default_input_method=//p' | xargs pidof | xargs kill -9 && pm resolve-activity --brief $pkgName | tail -n 1 | xargs am start -n && pidof com.termux | xargs kill -9" &> /dev/null 321 | fi 322 | else 323 | "${header[@]}" --infobox "Copying $appName $sourceName $selectedVer to Internal Storage..." 12 45 324 | [ -d "$storagePath/$appName-$appVer" ] || mkdir -p "$storagePath/$appName-$appVer" 325 | cp "apps/$appName-$appVer/base-$sourceName.apk" "$storagePath/$appName-$appVer" &> /dev/null 326 | termux-open "$storagePath/$appName-$appVer/base-$sourceName.apk" 327 | return 1 328 | fi 329 | } 330 | 331 | rootUninstall() { 332 | selectApp normal || return 1 333 | su -mm -c "/system/bin/sh $repoDir/root_util.sh unmount $pkgName" &> /dev/null 334 | unmountStatus=$? 335 | if [ "$unmountStatus" -eq "2" ]; then 336 | "${header[@]}" --msgbox "Patched $appName is not installed(mounted) in your device." 12 45 337 | return 1 338 | else 339 | "${header[@]}" --infobox "Uninstalling Patched $appName by Unmounting..." 12 45 340 | sleep 2 341 | [ "$unmountStatus" -ne "0" ] && "${header[@]}" --msgbox "Unmount failed !! Something went wrong." 12 45 && sleep 1 && return 1 342 | fi 343 | "${header[@]}" --msgbox "Unmount Successful !!" 12 45 344 | sleep 1 345 | } 346 | 347 | refreshJson() { 348 | if ! ls "$patchesSource"-patches-*.json &> /dev/null; then 349 | getTools || return 1 350 | return 0 351 | fi 352 | if [ ! -f "$storagePath/$source-patches.json" ]; then 353 | internet || return 1 354 | "${header[@]}" --infobox "Please Wait !!" 12 45 355 | if [ "$(bash "$repoDir/fetch_patches.sh" "$source" online "$storagePath")" == "error" ]; then 356 | "${header[@]}" --msgbox "Oops !! Apkmirror API is not accessible. Patches are not successfully synced.\nRevancify may crash.\n\nChange your network." 12 45 357 | return 1 358 | fi 359 | fi 360 | includedPatches=$(jq '.' "$storagePath/$source-patches.json" 2>/dev/null || jq -n '[]') 361 | appsArray=$(jq -n --argjson includedPatches "$includedPatches" --arg pkgName "$pkgName" '$includedPatches | map(select(.appName != null)) | to_entries | map({"index": (.key + 1), "appName": (.value.appName), "pkgName" :(.value.pkgName), "developerName" :(.value.developerName), "apkmirrorAppName" :(.value.apkmirrorAppName)})') 362 | } 363 | 364 | checkTools() { 365 | if [ -f ".${source}-data" ]; then 366 | # shellcheck source=/dev/null 367 | source ./".${source}-data" 368 | else 369 | getTools || return 1 370 | fi 371 | if [ "$cliSize" == "$(ls "$cliSource"-cli-*.jar &> /dev/null && du -b "$cliSource"-cli-*.jar | cut -d $'\t' -f 1 || echo 0)" ] && [ "$patchesSize" == "$(ls "$patchesSource"-patches-*.jar &> /dev/null && du -b "$patchesSource"-patches-*.jar | cut -d $'\t' -f 1 || echo 0)" ] && [ "$integrationsSize" == "$(ls "$integrationsSource"-integrations-*.apk &> /dev/null && du -b "$integrationsSource"-integrations-*.apk | cut -d $'\t' -f 1 || echo 0)" ] && ls "$storagePath/$source-patches.json" &> /dev/null; then 372 | refreshJson || return 1 373 | else 374 | getTools || return 1 375 | fi 376 | } 377 | 378 | getAppVer() { 379 | if [ "$root" == true ] && su -c "pm list packages | grep -q $pkgName" && [ "$AllowVersionDowngrade" == false ]; then 380 | selectedVer=$(su -c dumpsys package "$pkgName" | sed -n '/versionName/s/.*=//p' | sed -n '1p') 381 | appVer="${selectedVer// /-}" 382 | fi 383 | if [ "${#appVerList[@]}" -lt 2 ]; then 384 | internet || return 1 385 | "${header[@]}" --infobox "Please Wait !!\nScraping versions list for $appName from apkmirror.com..." 12 45 386 | readarray -t appVerList < <(bash "$repoDir/fetch_versions.sh" "$appName" "$apkmirrorAppName" "$source" "$selectedVer" "$storagePath") 387 | fi 388 | versionSelector || return 1 389 | } 390 | 391 | versionSelector() { 392 | if [ "${appVerList[0]}" == "error" ]; then 393 | "${header[@]}" --msgbox "Unable to fetch link !!\nThere is some problem with your internet connection. Disable VPN or Change your network." 12 45 394 | return 1 395 | fi 396 | selectedVer=$("${header[@]}" --begin 2 0 --title '| Version Selection Menu |' --default-item "$selectedVer" --ok-label "Select" --cancel-label "Back" --menu "Use arrow keys to navigate\nSource: $sourceName; AppName: $appName" -1 -1 0 "${appVerList[@]}" 2>&1 >/dev/tty) || return 1 397 | if [ "$selectedVer" == "Auto Select" ]; then 398 | selectedVer=$(jq -n -r --argjson includedPatches "$includedPatches" --arg pkgName "$pkgName" '$includedPatches[] | select(.pkgName == $pkgName) | .versions[-1]') 399 | fi 400 | appVer="${selectedVer// /-}" 401 | } 402 | 403 | checkPatched() { 404 | if [ -f "apps/$appName-$appVer/base-$sourceName.apk" ]; then 405 | "${header[@]}" --begin 2 0 --title '| Patched apk found |' --no-items --defaultno --yes-label 'Patch' --no-label 'Install' --help-button --help-label 'Back' --yesno "Current directory already contains Patched $appName version $selectedVer.\n\n\nDo you want to patch $appName again?" -1 -1 406 | apkFoundPrompt=$? 407 | case "$apkFoundPrompt" in 408 | 0 ) 409 | rm "apps/$appName-$appVer/base-$sourceName.apk" 410 | ;; 411 | 1 ) 412 | initInstall 413 | return 1 414 | ;; 415 | 2 ) 416 | return 1 417 | ;; 418 | esac 419 | else 420 | rm "apps/$appName-$appVer/base-$sourceName.apk" &> /dev/null 421 | return 0 422 | fi 423 | } 424 | 425 | selectFile() { 426 | newPath="" 427 | while [ ! -f "$newPath" ]; do 428 | currentPath=${currentPath:-$internalStorage} 429 | dirList=() 430 | files=() 431 | if [ "$currentPath" != "$internalStorage" ]; then 432 | dirUp=(1 ".." "GO BACK TO PREVIOUS DIRECTORY") 433 | num=1 434 | else 435 | unset dirUp 436 | num=0 437 | fi 438 | while read -r itemName; do 439 | if [ -d "$currentPath/$itemName" ]; then 440 | files+=("$itemName") 441 | [ "${#itemName}" -gt $(("$(tput cols)" - 24)) ] && itemNameDisplay=${itemName:0:$(("$(tput cols)" - 34))}...${itemName: -10} || itemNameDisplay="$itemName" 442 | dirList+=("$((++num))" "$itemNameDisplay/" "DIR: $itemName/") 443 | elif [ "${itemName##*.}" == "apk" ]; then 444 | files+=("$itemName") 445 | [ "${#itemName}" -gt $(("$(tput cols)" - 24)) ] && itemNameDisplay=${itemName:0:$(("$(tput cols)" - 34))}...${itemName: -10} || itemNameDisplay=$itemName 446 | dirList+=("$((++num))" "$itemNameDisplay" "APK: $itemName") 447 | fi 448 | done < <(ls -1 --group-directories-first "$currentPath") 449 | pathIndex=$("${header[@]}" --begin 2 0 --title '| Apk File Selection Menu |' --item-help --ok-label "Select" --menu "Use arrow keys to navigate\nCurrent Path: $currentPath/" $(($(tput lines) - 3)) -1 15 "${dirUp[@]}" "${dirList[@]}" 2>&1 >/dev/tty) 450 | exitstatus=$? 451 | [ "$exitstatus" -eq 1 ] && break 452 | if [ "$currentPath" != "$internalStorage" ] && [ "$pathIndex" -eq 1 ]; then 453 | newPath=".." 454 | elif [ "$currentPath" != "$internalStorage" ] && [ "$pathIndex" -ne 1 ]; then 455 | newPath=${files[$pathIndex - 2]} 456 | else 457 | newPath=${files[$pathIndex - 1]} 458 | fi 459 | if [ "$newPath" == ".." ]; then 460 | newPath=${currentPath%/*} 461 | else 462 | newPath=$currentPath/$newPath 463 | fi 464 | if [ -d "$newPath" ]; then 465 | currentPath=$newPath 466 | fi 467 | done 468 | [ "$exitstatus" -eq 1 ] && return 1 469 | return 0 470 | } 471 | 472 | fetchCustomApk() { 473 | selectedVer="" installedVer="" 474 | selectFile || return 1 475 | "${header[@]}" --infobox "Please Wait !!\nExtracting data from \"$(basename "$newPath")\"" 12 45 476 | if ! aaptData=$(./aapt2 dump badging "$newPath"); then 477 | "${header[@]}" --msgbox "The apkfile you selected is not an valid app. Download the apk again and retry." 12 45 478 | return 1 479 | fi 480 | pkgName=$(grep "package:" <<<"$aaptData" | sed -e 's/package: name='\''//' -e 's/'\'' versionCode.*//') 481 | if [ "$(jq -n --arg pkgName "$pkgName" --argjson includedPatches "$includedPatches" '$includedPatches[] | select(.pkgName == $pkgName) | .patches')" == "" ]; then 482 | "${header[@]}" --msgbox "The app you selected is not supported for patching by $sourceName patches !!" 12 45 483 | return 1 484 | fi 485 | fileAppName=$(grep "application-label:" <<<"$aaptData" | sed -e 's/application-label://' -e 's/'\''//g') 486 | appName="$(sed 's/\./-/g;s/ /-/g' <<<"$fileAppName")" 487 | selectedVer=$(grep "package:" <<<"$aaptData" | sed -e 's/.*versionName='\''//' -e 's/'\'' platformBuildVersionName.*//') 488 | appVer="${selectedVer// /-}" 489 | if [ "$root" == true ] && su -c "pm list packages | grep -q $pkgName" && [ "$AllowVersionDowngrade" == false ]; then 490 | installedVer=$(su -c dumpsys package "$pkgName" | sed -n '/versionName/s/.*=//p' | sed -n '1p') 491 | if [ "$installedVer" != "$selectedVer" ]; then 492 | sorted=$(jq -nr --arg installedVer "$installedVer" --arg selectedVer "$selectedVer" '[$installedVer, $selectedVer] | sort | .[0]') 493 | if [ "${sorted[0]}" != "$installedVer" ];then 494 | "${header[@]}" --msgbox "The selected version $selectedVer is lower then version $installedVer installed on your device.\nPlease Select a higher version !!" 12 45 495 | return 1 496 | fi 497 | fi 498 | fi 499 | [ -d "apps/$appName-$appVer" ] || mkdir -p "apps/$appName-$appVer" 500 | cp "$newPath" "apps/$appName-$appVer/base.apk" 501 | if [ "$(jq -n -r --argjson includedPatches "$includedPatches" --arg pkgName "$pkgName" '$includedPatches[] | select(.pkgName == $pkgName) | .versions | length')" -eq 0 ]; then 502 | if ! "${header[@]}" --begin 2 0 --title '| Proceed |' --no-items --yesno "The following data is extracted from the apk file you provided.\nApp Name : $fileAppName\nPackage Name: $pkgName\nVersion : $selectedVer\nDo you want to proceed with this app?" -1 -1; then 503 | return 1 504 | fi 505 | else 506 | if [ "$(jq -n -r --arg selectedVer "$selectedVer" --arg pkgName "$pkgName" --argjson includedPatches "$includedPatches" '$includedPatches[] | select(.pkgName == $pkgName) | .versions | index($selectedVer)')" != "null" ]; then 507 | if ! "${header[@]}" --begin 2 0 --title '| Proceed |' --no-items --yesno "The following data is extracted from the apk file you provided.\nApp Name : $fileAppName\nPackage Name: $pkgName\nVersion : $selectedVer\nDo you want to proceed with this app?" -1 -1; then 508 | return 1 509 | fi 510 | else 511 | if ! "${header[@]}" --begin 2 0 --title '| Proceed |' --no-items --yesno "The following data is extracted from the apk file you provided.\nApp Name : $fileAppName\nPackage Name: $pkgName\nVersion : $selectedVer\n\nThe version $selectedVer is not supported. Supported versions are: \n$(jq -n -r --arg pkgName "$pkgName" --argjson includedPatches "$includedPatches" '$includedPatches[] | select(.pkgName == $pkgName).versions | length as $array_length | to_entries[] | if .key != ($array_length - 1) then .value + "," else .value end')\n\nDo you still want to proceed with version $selectedVer for $appName?" -1 -1; then 512 | return 1 513 | fi 514 | fi 515 | fi 516 | checkPatched || return 1 517 | } 518 | 519 | fetchApk() { 520 | selectedVer="" appVer="" 521 | getAppVer || return 1 522 | if [ "$(jq -n -r --argjson includedPatches "$includedPatches" --arg selectedVer "$selectedVer" --arg pkgName "$pkgName" '$includedPatches[] | select(.pkgName == $pkgName) | .versions | if ((. | length) == 0) then 0 elif ((. | index($selectedVer)) != null) then 0 else 1 end')" -eq 0 ]; then 523 | if ! "${header[@]}" --begin 2 0 --title '| Proceed |' --no-items --yesno "Do you want to proceed with version $selectedVer for $appName?" -1 -1; then 524 | return 1 525 | fi 526 | else 527 | if ! "${header[@]}" --begin 2 0 --title '| Proceed |' --no-items --yesno "The version $selectedVer is not supported. Supported versions are: \n$(jq -n -r --arg pkgName "$pkgName" --argjson includedPatches "$includedPatches" '$includedPatches[] | select(.pkgName == $pkgName).versions | length as $array_length | to_entries[] | if .key != ($array_length - 1) then .value + "," else .value end')\n\nDo you still want to proceed with version $selectedVer for $appName?" -1 -1; then 528 | return 1 529 | fi 530 | fi 531 | checkPatched || return 1 532 | if [ -f "apps/$appName-$appVer/base.apk" ]; then 533 | # shellcheck source=/dev/null 534 | if [ "$(source "apps/.appSize"; eval echo \$"${appName//-/_}"Size)" == "$([ -f "apps/$appName-$appVer/base.apk" ] && du -b "apps/$appName-$appVer/base.apk" | cut -d $'\t' -f 1 || echo 0)" ]; then 535 | return 0 536 | fi 537 | else 538 | rm -rf "apps/$appName"* &> /dev/null 539 | fi 540 | downloadApp || return 1 541 | } 542 | 543 | downloadApp() { 544 | internet || return 1 545 | appUrl=$( (bash "$repoDir/fetch_link.sh" "$developerName" "$apkmirrorAppName" "$appVer" 2>&3 | "${header[@]}" --begin 2 0 --gauge "App : $appName\nVersion: $selectedVer\n\nScraping Download Link..." -1 -1 0 >&2) 3>&1) 546 | tput civis 547 | case $appUrl in 548 | "error" ) 549 | "${header[@]}" --msgbox "Unable to fetch link !!\nThere is some problem with your internet connection. Disable VPN or Change your network." 12 45 550 | return 1 551 | ;; 552 | "noapk" ) 553 | if [ "$root" == false ]; then 554 | "${header[@]}" --msgbox "No apk found on apkmirror.com for version $selectedVer !!\nTry selecting other version." 12 45 555 | return 1 556 | else 557 | "${header[@]}" --msgbox "No apk found on apkmirror.com for version $selectedVer !!\nPlease upgrade or degrade the version to patch it.\n\nSuggestion: Download apk manually and use that file to patch." 12 45 558 | return 1 559 | fi 560 | ;; 561 | "noversion" ) 562 | "${header[@]}" --msgbox "This version is not uploaded on apkmirror.com!!\nPlease upgrade or degrade the version to patch it.\n\nSuggestion: Download apk manually and use that file to patch." 12 45 563 | return 1 564 | ;; 565 | esac 566 | appSize="$(curl -sLI "$appUrl" -A "$userAgent" | sed -n '/Content-Length/s/[^0-9]*//p' | tr -d '\r')" 567 | [ "$appSize" == "" ] && return 1 568 | setEnv "${appName//-/_}Size" "$appSize" update "apps/.appSize" 569 | [ -d "apps/$appName-$appVer" ] || mkdir -p "apps/$appName-$appVer" 570 | wget -q -c "$appUrl" -O "apps/$appName-$appVer/base.apk" --show-progress --user-agent="$userAgent" 2>&1 | stdbuf -o0 cut -b 63-65 | stdbuf -o0 grep '[0-9]' | "${header[@]}" --begin 2 0 --gauge "App : $appName\nVersion: $selectedVer\nSize : $(numfmt --to=iec --format="%0.1f" "$appSize")\n\nDownloading..." -1 -1 "$(($(( "$([ -f "apps/$appName-$appVer/base.apk" ] && du -b "apps/$appName-$appVer/base.apk" | cut -d $'\t' -f 1 || echo 0)" * 100)) / appSize))" 571 | tput civis 572 | sleep 0.5s 573 | if [ "$appSize" != "$(du -b "apps/$appName-$appVer/base.apk" | cut -d $'\t' -f 1)" ]; then 574 | "${header[@]}" --msgbox "Oh No !!\nUnable to complete download. Please Check your internet connection and Retry." 12 45 575 | return 1 576 | fi 577 | } 578 | 579 | downloadMicrog() { 580 | microgName=mMicroG microgRepo=inotia00 581 | if "${header[@]}" --begin 2 0 --title '| MicroG Prompt |' --no-items --defaultno --yesno "$microgName is used to run MicroG services without root.\nYouTube and YouTube Music won't work without it.\nIf you already have $microgName, You don't need to download it.\n\n\n\n\n\nDo you want to download $microgName app?" -1 -1; then 582 | internet || return 1 583 | readarray -t microgheaders < <(curl -s "https://api.github.com/repos/$microgRepo/$microgName/releases/latest" | jq -r --arg regex ".*$arch.*" '(.assets[] | if .name | test($regex) then .browser_download_url, .size else empty end), .tag_name') 584 | wget -q -c "${microgheaders[0]}" -O "$microgName-${microgheaders[2]}.apk" --show-progress --user-agent="$userAgent" 2>&1 | stdbuf -o0 cut -b 63-65 | stdbuf -o0 grep '[0-9]' | "${header[@]}" --begin 2 0 --gauge "App : $microgName \nVersion : ${microgheaders[2]}\nSize : $(numfmt --to=iec --format="%0.1f" "${microgheaders[1]}")\n\nDownloading..." -1 -1 && tput civis 585 | ls $microgName* &> /dev/null && mv $microgName* "$storagePath/" && termux-open "$storagePath/$microgName-${microgheaders[2]}.apk" 586 | fi 587 | } 588 | 589 | patchApp() { 590 | if [ "$cliSource" == "inotia00" ] && [ "$Riplibs" == true ]; then 591 | riplibArgs="--rip-lib=x86_64 --rip-lib=x86 --rip-lib=armeabi-v7a --rip-lib=arm64-v8a " 592 | riplibArgs="${riplibArgs//--rip-lib=$arch /}" 593 | else 594 | riplibArgs="" 595 | fi 596 | includedPatches=$(jq '.' "$storagePath/$source-patches.json" 2>/dev/null || jq -n '[]') 597 | readarray -t patchesArg < <(jq -n -r --argjson includedPatches "$includedPatches" --arg pkgName "$pkgName" '$includedPatches[] | select(.pkgName == $pkgName).includedPatches | if ((. | length) != 0) then (.[] | "-i", .) else empty end') 598 | java -jar "$cliSource"-cli-*.jar patch -fpw -b "$patchesSource"-patches-*.jar -m "$integrationsSource"-integrations-*.apk -o "apps/$appName-$appVer/base-$sourceName.apk" $riplibArgs "${patchesArg[@]}" --keystore "$repoDir"/revancify.keystore --alias "decipher" --signer "decipher" --keystore-entry-password "revancify" --keystore-password "revancify" --custom-aapt2-binary ./aapt2 --options "$storagePath/$source-options.json" --exclusive "apps/$appName-$appVer/base.apk" 2>&1 | tee "$storagePath/patch_log.txt" | "${header[@]}" --begin 2 0 --ok-label "Continue" --cursor-off-label --programbox "Patching $appName $selectedVer.apk" -1 -1 599 | echo -e "\n\n\nRooted: $root\nArch: $arch\nApp: $appName v$appVer\nCLI: $(ls "$cliSource"-cli-*.jar)\nPatches: $(ls "$patchesSource"-patches-*.jar)\nIntegrations: $(ls "$integrationsSource"-integrations-*.apk)\nPatches argument: ${patchesArg[*]}" >>"$storagePath/patch_log.txt" 600 | tput civis 601 | sleep 1 602 | if [ ! -f "apps/$appName-$appVer/base-$sourceName.apk" ]; then 603 | "${header[@]}" --msgbox "Oops, Patching failed !!\nLogs saved to \"Internal Storage/Revancify/patch_log.txt\". Share the Patchlog to developer." 12 45 604 | return 1 605 | fi 606 | } 607 | 608 | deleteComponents() { 609 | while true; do 610 | delComponentPrompt=$("${header[@]}" --begin 2 0 --title '| Delete Components Menu |' --ok-label "Select" --cancel-label "Back" --menu "Use arrow keys to navigate\nSource: $sourceName" -1 -1 0 1 "Tools" 2 "Apps" 3 "Patch Options" 2>&1 >/dev/tty) || break 611 | case "$delComponentPrompt" in 612 | 1 ) 613 | if "${header[@]}" --begin 2 0 --title '| Delete Tools |' --no-items --defaultno --yesno "Please confirm to delete the tools.\nIt will delete the $sourceName CLI, patches and integrations." -1 -1; then 614 | rm "$cliSource"-cli-*.jar &> /dev/null 615 | rm "$patchesSource"-patches-*.jar &> /dev/null 616 | rm "$patchesSource"-patches-*.json &> /dev/null 617 | rm "$integrationsSource"-integrations-*.apk &> /dev/null 618 | "${header[@]}" --msgbox "All $sourceName Tools successfully deleted !!" 12 45 619 | fi 620 | ;; 621 | 2 ) 622 | if "${header[@]}" --begin 2 0 --title '| Delete Apps |' --no-items --defaultno --yesno "Please confirm to delete all the downloaded and patched apps." -1 -1; then 623 | rm -rf "apps"/* 624 | "${header[@]}" --msgbox "All Apps are successfully deleted !!" 12 45 625 | fi 626 | ;; 627 | 3 ) 628 | if "${header[@]}" --begin 2 0 --title '| Delete Patch Options |' --no-items --defaultno --yesno "Please confirm to delete the patch options file for $sourceName patches." -1 -1; then 629 | rm "$storagePath/$source-options.json" &> /dev/null 630 | "${header[@]}" --msgbox "Options file successfully deleted for current source !!" 12 45 631 | fi 632 | ;; 633 | esac 634 | done 635 | } 636 | 637 | preferences() { 638 | [ "$cliSource" == "inotia00" ] && RiplibsPref=("Riplibs" "$Riplibs" "Removes extra libs from app") || RiplibsPref=() 639 | prefsArray=("LightTheme" "$LightTheme" "Use Light theme for Revancify" "${RiplibsPref[@]}" "AutocheckToolsUpdate" "$AutocheckToolsUpdate" "Check for tools update at startup" "ShowConfirmPatchesMenu" "$ShowConfirmPatchesMenu" "Shows Patches Menu before Patching starts" "LaunchAppAfterMount" "$LaunchAppAfterMount" "[Root] Launches app automatically after mount" AllowVersionDowngrade "$AllowVersionDowngrade" "[Root] Allows downgrading version if any such module is present" "FetchPreReleasedTools" "$FetchPreReleasedTools" "Fetches the pre-release version of tools") 640 | readarray -t prefsArray < <(for pref in "${prefsArray[@]}"; do sed 's/false/off/;s/true/on/' <<< "$pref"; done) 641 | read -ra newPrefs < <("${header[@]}" --begin 2 0 --title '| Preferences Menu |' --item-help --no-items --no-cancel --ok-label "Save" --checklist "Use arrow keys to navigate; Press Spacebar to toogle patch" $(($(tput lines) - 3)) -1 15 "${prefsArray[@]}" 2>&1 >/dev/tty) 642 | sed -i 's/true/false/' "$envFile" 643 | for newPref in "${newPrefs[@]}"; do 644 | setEnv "$newPref" true update "$envFile" 645 | done 646 | # shellcheck source=/dev/null 647 | source "$envFile" 648 | [ "$LightTheme" == true ] && theme=Light || theme=Dark 649 | export DIALOGRC="$repoDir/configs/.dialogrc$theme" 650 | } 651 | 652 | buildApk() { 653 | if [ "$appType" == "downloaded" ]; then 654 | fetchApk || return 1 655 | else 656 | fetchCustomApk || return 1 657 | selectPatches Proceed 658 | fi 659 | if [ "$appType" == "downloaded" ] && [ "$ShowConfirmPatchesMenu" == true ]; then 660 | selectPatches Proceed 661 | fi 662 | patchApp || return 1 663 | initInstall 664 | } 665 | 666 | mainMenu() { 667 | mainMenu=$("${header[@]}" --begin 2 0 --title '| Main Menu |' --default-item "$mainMenu" --ok-label "Select" --cancel-label "Exit" --menu "Use arrow keys to navigate\nSource: $sourceName" -1 -1 0 1 "Patch App" 2 "Select Patches" 3 "Change Source" 4 "Fetch Tools" 5 "Edit Patch Options" 6 "$menuEntry" 7 "Delete Components" 8 "Preferences" 2>&1 >/dev/tty) || terminate 0 668 | case "$mainMenu" in 669 | 1 ) 670 | while true; do 671 | selectApp storage || break 672 | buildApk 673 | done 674 | ;; 675 | 2 ) 676 | while true; do 677 | selectApp normal || break 678 | selectPatches Save || break 679 | done 680 | ;; 681 | 3 ) 682 | changeSource 683 | ;; 684 | 4 ) 685 | getTools 686 | ;; 687 | 5 ) 688 | editPatchOptions 689 | ;; 690 | 6 ) 691 | if [ "$root" == true ]; then 692 | rootUninstall 693 | else 694 | downloadMicrog 695 | fi 696 | ;; 697 | 7 ) 698 | deleteComponents 699 | ;; 700 | 8 ) 701 | preferences 702 | ;; 703 | esac 704 | } 705 | 706 | if su -c exit &> /dev/null; then 707 | [ "$1" == false ] && root=false || root=true 708 | else 709 | root=false 710 | fi 711 | 712 | online="$2" 713 | 714 | initialize 715 | 716 | while true; do 717 | unset appVerList appVer appName pkgName 718 | mainMenu 719 | done -------------------------------------------------------------------------------- /revancify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | HOME=/data/data/com.termux/files/home 4 | BIN=/data/data/com.termux/files/usr/bin 5 | 6 | repo="$HOME/Revancify" 7 | cp "$repo/revancify" "$BIN/revancify" >/dev/null 2>&1 8 | root=default 9 | online="Checking..." 10 | DATA="$HOME/revancify-data" 11 | [ ! -d $DATA ] && mkdir -p "$DATA" 12 | # shellcheck source=/dev/null 13 | source "$DATA/config.cfg" > /dev/null 2>&1 14 | [ "${LightTheme:-false}" == "true" ] && theme=Light || theme=Dark 15 | export DIALOGRC="$repo/configs/.dialogrc$theme" 16 | 17 | help() { 18 | if [ "$1" == "1" ]; then 19 | readarray -t invalidArgs < <(for i in "${FLAG_ERR[@]}"; do 20 | if grep -q "unrecognized" <<< "$i"; then 21 | grep -oP "(?<=\`).*(?=')" <<< "$i" 22 | else 23 | cut -b 27 <<< "$i" | xargs -0 -I {} echo -n "-{}" 24 | fi 25 | done) 26 | IFS="," 27 | echo -e "\e[1;31mInvalid Argument(s) Passed: ${invalidArgs[*]}\n" 28 | fi 29 | echo -e "revancify\n 30 | Usage: revancify [OPTION]\n 31 | Options: 32 | -n, --no-root Run without SU permissions 33 | -o, --offline Run without updating revancify 34 | -r, --reinstall Reinstall revancify 35 | -h, --help Prints help statement" 36 | exit "$1" 37 | } 38 | 39 | if ! ARGS=$(getopt -o nohr -l "no-root,offline,help,reinstall" -- "$@" 2>/dev/null); then 40 | readarray -t FLAG_ERR < <(getopt -o nohr -l no-root,offline,help,reinstall -- "$@" 2>&1 1>&-) 41 | help 1 42 | fi 43 | eval set -- "$ARGS" 44 | while [ $# -gt 0 ] 45 | do 46 | case "$1" in 47 | "-h" | "--help") 48 | help 0 49 | ;; 50 | "-n" | "--no-root") 51 | root=false 52 | shift 53 | ;; 54 | "-r" | "--reinstall") 55 | if ping -c 1 google.com >/dev/null 2>&1; then 56 | rm -rf $DATA 57 | cd "$repo"/.. && rm -rf "$repo" && git clone --depth=1 https://github.com/decipher3114/Revancify.git && "$repo/revancify" && exit 58 | else 59 | echo "No internet Connection !!" 60 | exit 1 61 | fi 62 | ;; 63 | "-o" | "--offline") 64 | online=false 65 | shift 66 | ;; 67 | "--") 68 | shift 69 | break 70 | ;; 71 | esac 72 | shift 73 | done 74 | 75 | if [ "$(getprop ro.product.cpu.abi)" = "armeabi-v7a" ]; then 76 | echo "CPU architecture \"armeabi-v7a\" of your device is not supported for patching." 77 | echo "You may get build errors." 78 | read -N 1 -s -r -p $'Press ENTER to CONTINUE or SPACE to EXIT...\n' key 79 | if [ "$key" = " " ]; then 80 | echo "Script terminated" 81 | exit 82 | fi 83 | fi 84 | 85 | terminate() { 86 | clear 87 | echo "Script terminated !!" 88 | tput cnorm 89 | exit 1 90 | } 91 | trap terminate SIGTERM SIGINT SIGABRT 92 | 93 | checkdependencies() { 94 | if [ -f "$DATA/aapt2" ] && bins=$(ls "$BIN") && grep -q java <<<"$bins" && grep -q wget <<<"$bins" && grep -q tput <<<"$bins" && grep -q dialog <<<"$bins" && grep -q pup <<<"$bins" && grep -q jq <<<"$bins" && ls "$BIN/revancify" >/dev/null 2>&1 && ls "$HOME/storage" >/dev/null 2>&1; then 95 | return 0 96 | else 97 | if ping -c 1 google.com >/dev/null 2>&1; then 98 | installdependencies || (echo "Dependencies not installed !!" && exit 1) 99 | else 100 | cp "$repo/revancify" "$BIN/revancify" 101 | echo -e "Dependencies not installed !!\nRun again with internet connection." 102 | exit 1 103 | fi 104 | fi 105 | } 106 | 107 | installdependencies() { 108 | clear 109 | echo "Installing dependencies..." 110 | arch=$(getprop ro.product.cpu.abi) 111 | cp "$repo/revancify" "$BIN/revancify" 112 | sleep 1 113 | [ ! -d "$HOME/storage" ] && termux-setup-storage 114 | pkg update -y -o Dpkg::Options::="--force-confnew" || return 1 115 | pkg install openjdk-17 wget ncurses-utils dialog pup jq -y -o Dpkg::Options::="--force-confnew" || return 1 116 | git config --global pull.rebase true 117 | sed -i '/allow-external-apps/s/# //' "$HOME/.termux/termux.properties" 118 | [ ! -f "$DATA/aapt2" ] && wget "https://github.com/decipher3114/binaries/releases/download/v0.1/aapt2_$arch" -O "$DATA/aapt2" && chmod +x "$DATA/aapt2" 119 | echo "Dependencies installed successfully." 120 | return 0 121 | } 122 | 123 | checkrevancify() { 124 | if [ -d "$repo" ]; then 125 | cd "$repo" >/dev/null 2>&1 || true 126 | rm -rf ./*cache* 127 | return 0 128 | else 129 | echo -e "\e[1;31mRevancify dir is not found !!" 130 | echo -e "\e[1;31mDo you want to reinstall Revancify?" 131 | read -r -p "[Y/N]: " choice 132 | case "$choice" in 133 | y | Y) 134 | rm -rf "$repo" 135 | git clone --depth=1 https://github.com/decipher3114/Revancify.git && $repo/revancify && exit 136 | ;; 137 | n | N) 138 | echo "Removing revancify completely !!" 139 | sleep 0.5s 140 | rm "$BIN/revancify" 141 | echo "Successfully Uninstalled revancify." 142 | exit 0 143 | ;; 144 | ?) 145 | echo "Invalid option !!" 146 | exit 1 147 | ;; 148 | esac 149 | fi 150 | } 151 | 152 | checkdependencies 153 | checkrevancify 154 | tput civis 155 | dialog --no-shadow --infobox "\n █▀█ █▀▀ █░█ ▄▀█ █▄░█ █▀▀ █ █▀▀ █▄█\n █▀▄ ██▄ ▀▄▀ █▀█ █░▀█ █▄▄ █ █▀░ ░█░ \n\nDeveloper : decipher\nLast Updated : Checking...\nOnline : $online" 10 42 156 | 157 | if [ "$online" != false ]; then 158 | ping -c 1 google.com >/dev/null 2>&1 && online=true || online=false 159 | fi 160 | 161 | if [ "$online" == true ]; then 162 | if ! git pull >/dev/null 2>&1;then 163 | git fetch --all >/dev/null 2>&1 &&\ 164 | git reset --hard "@{u}" >/dev/null 2>&1 165 | fi 166 | fi 167 | dialog --no-shadow --infobox "\n █▀█ █▀▀ █░█ ▄▀█ █▄░█ █▀▀ █ █▀▀ █▄█\n █▀▄ ██▄ ▀▄▀ █▀█ █░▀█ █▄▄ █ █▀░ ░█░ \n\nDeveloper : decipher\nLast Updated : $(git log -1 --pretty='format:%cd' --date=format:'%b %d, %Y | %H:%M')\nOnline : $online" 10 42 168 | cd "$DATA" >/dev/null 2>&1 || true 169 | bash "$repo/main.sh" "$root" "$online" 170 | exitstatus=$? 171 | clear 172 | cd "$HOME" || : 173 | if [ $exitstatus -eq 0 ]; then 174 | echo "Script exited !!" 175 | else 176 | echo "Script terminated !!" 177 | rm -rf -- *cache >/dev/null 2>&1 178 | fi 179 | tput cnorm 180 | -------------------------------------------------------------------------------- /revancify.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YT-Advanced/Revancify/f661a28617efd854f8f62503d670dd9d2d9d927c/revancify.keystore -------------------------------------------------------------------------------- /root_util.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | pkgName="$2" 4 | appName="$3" 5 | appVer="$4" 6 | sourceName="$5" 7 | 8 | 9 | if [ "$1" = "unmount" ]; then 10 | grep -q "$pkgName" /proc/mounts || exit 2 11 | am force-stop "$pkgName" 12 | grep "$pkgName" /proc/mounts | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l 13 | stockApp=$(pm path "$pkgName" | sed -n "/base/s/package://p") 14 | am force-stop "$pkgName" 15 | rm "/data/adb/service.d/mount_revanced_$pkgName.sh" 16 | rm "/data/adb/post-fs-data.d/umount_revanced_$pkgName.sh" 17 | rm -rf "/data/local/tmp/revancify/$pkgName.apk" 18 | grep -q "$pkgName" /proc/mounts && exit 1 19 | exit 0 20 | fi 21 | 22 | [ -d /data/local/tmp/revancify/ ] || mkdir -p /data/local/tmp/revancify/ 23 | [ -d /data/adb/post-fs-data.d/ ] || mkdir -p /data/adb/post-fs-data.d/ 24 | [ -d /data/adb/service.d/ ] || mkdir -p /data/adb/service.d/ 25 | 26 | rm "/data/adb/post-fs-data.d/umount_revanced_$pkgName.sh" 27 | rm "/data/adb/service.d/mount_revanced_$pkgName.sh" 28 | rm "/data/local/tmp/revancify/$pkgName.apk" 29 | 30 | 31 | if pm list packages | grep -q "$pkgName" && [ "$(dumpsys package "$pkgName" | sed -n '/versionName/s/.*=//p' | sed 's/ /./1p')" = "$appVer" ]; then 32 | : 33 | else 34 | pm install --user 0 -r "apps/$appName-$appVer/base.apk" 35 | fi 36 | 37 | pm list packages | grep -q "$pkgName" || exit 1 38 | 39 | stockApp=$(pm path "$pkgName" | sed -n "/base/s/package://p") 40 | revancedApp="/data/local/tmp/revancify/$pkgName.apk" 41 | 42 | am force-stop "$pkgName" 43 | 44 | { 45 | grep "$pkgName" /proc/mounts | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -vl 46 | cp "apps/$appName-$appVer/base-$sourceName.apk" "/data/local/tmp/revancify/$pkgName.apk" 47 | chmod -v 644 "$revancedApp" && chown -v system:system "$revancedApp" 48 | chcon -v u:object_r:apk_data_file:s0 "$revancedApp" 49 | mount -vo bind "$revancedApp" "$stockApp" 50 | } > /storage/emulated/0/Revancify/install_log.txt 2>&1 51 | 52 | am force-stop "$pkgName" 53 | 54 | grep -q "$pkgName" /proc/mounts || exit 1 55 | 56 | cat <"/data/adb/service.d/mount_revanced_$pkgName.sh" 57 | #!/system/bin/sh 58 | while [ "\$(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 5; done 59 | 60 | base_path="$revancedApp" 61 | stock_path="\$(pm path $pkgName | sed -n '/base/s/package://p')" 62 | am force-stop "$pkgName" 63 | chcon u:object_r:apk_data_file:s0 "\$base_path" 64 | [ ! -z "\$stock_path" ] && mount -o bind "\$base_path" "\$stock_path" 65 | am force-stop $pkgName 66 | EOF 67 | cat <"/data/adb/post-fs-data.d/umount_revanced_$pkgName.sh" 68 | #!/system/bin/sh 69 | stock_path="\$(pm path $pkgName | sed -n '/base/s/package://p')" 70 | [ ! -z "\$stock_path" ] && umount -l "\$stock_path" 71 | grep $pkgName /proc/mounts | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l 72 | EOF 73 | chmod 0744 "/data/adb/service.d/mount_revanced_$pkgName.sh" 74 | chmod 0744 "/data/adb/post-fs-data.d/umount_revanced_$pkgName.sh" 75 | -------------------------------------------------------------------------------- /sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "revanced": 3 | { 4 | "projectName": "Revanced", 5 | "sources": 6 | { 7 | "cli": 8 | { 9 | "org": "revanced", 10 | "repo": "revanced-cli" 11 | }, 12 | "patches": 13 | { 14 | "org": "revanced", 15 | "repo": "revanced-patches" 16 | }, 17 | "integrations": 18 | { 19 | "org": "revanced", 20 | "repo": "revanced-integrations" 21 | } 22 | } 23 | }, 24 | "YT-Advanced": 25 | { 26 | "projectName": "ReX", 27 | "sources": 28 | { 29 | "cli": 30 | { 31 | "org": "inotia00", 32 | "repo": "revanced-cli" 33 | }, 34 | "patches": 35 | { 36 | "org": "YT-Advanced", 37 | "repo": "ReX-patches" 38 | }, 39 | "integrations": 40 | { 41 | "org": "YT-Advanced", 42 | "repo": "ReX-integrations" 43 | } 44 | } 45 | }, 46 | "inotia00": 47 | { 48 | "projectName": "Revanced-Extended", 49 | "sources": 50 | { 51 | "cli": 52 | { 53 | "org": "inotia00", 54 | "repo": "revanced-cli" 55 | }, 56 | "patches": 57 | { 58 | "org": "inotia00", 59 | "repo": "revanced-patches" 60 | }, 61 | "integrations": 62 | { 63 | "org": "inotia00", 64 | "repo": "revanced-integrations" 65 | } 66 | } 67 | }, 68 | "kitadai31": 69 | { 70 | "projectName": "RVX-Android_6-7", 71 | "sources": 72 | { 73 | "cli": 74 | { 75 | "org": "kitadai31", 76 | "repo": "rvcli" 77 | }, 78 | "patches": 79 | { 80 | "org": "kitadai31", 81 | "repo": "revanced-patches-android6-7" 82 | }, 83 | "integrations": 84 | { 85 | "org": "kitadai31", 86 | "repo": "revanced-integrations" 87 | } 88 | } 89 | }, 90 | "d4n3436": 91 | { 92 | "projectName": "RVX-Android-5", 93 | "sources": 94 | { 95 | "cli": 96 | { 97 | "org": "kitadai31", 98 | "repo": "rvcli" 99 | }, 100 | "patches": 101 | { 102 | "org": "d4n3436", 103 | "repo": "revanced-patches-android5" 104 | }, 105 | "integrations": 106 | { 107 | "org": "d4n3436", 108 | "repo": "revanced-integrations" 109 | } 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------