Compare commits

...

1839 Commits

Author SHA1 Message Date
LoveSy c6f0762510 Use pidfd_open for setns
which is more efficient on newer kernel
2024-05-20 03:26:55 +08:00
LoveSy 941a363c5a
Support waiting on non-exist prop 2024-05-18 13:55:33 +08:00
Arbri çoçka 2afcdc64a0 Update strings.xml sq 2024-05-18 13:52:31 +08:00
VD $ VD171 @ Priv8 3c66c4bbc5 Update PORTUGUESE translation 2024-05-18 13:52:14 +08:00
VD $ VD171 @ Priv8 9f5cd5e1cc Update PORTUGUESE translation 2024-05-18 13:52:14 +08:00
kubalav a35f2bb73b Update Slovak translation 2024-05-18 13:51:59 +08:00
topjohnwu 6cf00130f4 Check Magisk version instead of app version 2024-05-15 12:42:55 +08:00
topjohnwu 6c27ba6b88 Rename db entry name 2024-05-15 12:42:55 +08:00
vvb2060 dd3b9980e7 app: add safe mode config to menu 2024-05-15 12:42:55 +08:00
vvb2060 02e189a029 core: add safe mode config to db 2024-05-15 12:42:55 +08:00
topjohnwu 72b8d12ee4 Update development guide 2024-05-11 20:40:33 -07:00
topjohnwu eed03080c1 Update to ONDK r27.1 2024-05-09 09:42:40 -07:00
LoveSy 090cb4b0f9 Upgrade AGP to 8.4.0 2024-05-09 09:42:27 -07:00
topjohnwu 6f2c76b898 Fix build script 2024-05-09 02:19:24 -07:00
topjohnwu f61827cbec Switch rustup_wrapper to Rust implementation
For better Windows portability
2024-05-09 02:19:07 -07:00
topjohnwu 3f2264f2c7 Support rustup wrapper 2024-05-09 00:34:12 -07:00
topjohnwu c1cadf4bdc Update build.py to use pathlib 2024-05-09 00:31:41 -07:00
Rodrigo Martínez 0e56991369 Improve and add missing strings for Spanish 2024-04-29 22:23:02 -07:00
LoveSy 4dc1c59040 add missing `xz_dec_end` 2024-04-29 22:22:47 -07:00
topjohnwu 33b7b8b297 Update resetprop 2024-04-26 16:42:24 -07:00
topjohnwu e6af5ed460 Address Rust warnings 2024-04-26 16:28:46 -07:00
topjohnwu b678afa4b6 Update to ONDK r27.0
Co-authored-by: LoveSy <shana@zju.edu.cn>
2024-04-26 03:09:44 -07:00
WINZORT 4bac2df4e7
Improve turkish locales and add the missing strings 2024-04-18 02:14:21 -07:00
igor 50416eee09 Improve Portuguese translation 2024-04-18 02:13:50 -07:00
igor 73cf501d33 Improve Brazilian Portuguese translation 2024-04-18 02:13:50 -07:00
Hen_Ry d2b7907bed
Update german strings.xml 2024-04-18 02:13:28 -07:00
topjohnwu 99d5dd5ea8 Update crt0 2024-04-17 10:12:27 -07:00
cloudchamb3r 5fdb841fa8 Fix typo
Fix typo in values-ko/strings.xml
2024-04-17 09:18:14 -07:00
topjohnwu 7c88484d64 Fix #7988 2024-04-16 19:45:01 -07:00
topjohnwu b22b6a4204 Refactor cpio 2024-04-10 22:46:21 -07:00
topjohnwu 2a3d34c812 Fix mkdirs 2024-04-10 22:36:47 -07:00
topjohnwu c50ee722a1 Use memmem for finding needle in haystack 2024-04-10 14:57:44 -07:00
topjohnwu ffc1e38e48 Add 32 bit CI 2024-04-10 02:56:23 -07:00
topjohnwu 6219d5fcbf Update crt0 for 32 bit 2024-04-10 02:43:37 -07:00
topjohnwu 2e4440b702 Support 32-bit magiskboot 2024-04-09 19:34:14 -07:00
topjohnwu 0d9ec0931b Code cleanup 2024-04-08 23:00:59 -07:00
vvb2060 60e8415369 Make denylist work when zygisk is disabled
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2024-04-08 23:00:50 -07:00
LoveSy 652a26d5d9 Fix comment of sepolicy 2024-04-03 21:03:41 -07:00
topjohnwu f57839379a Update RustCrypto dependencies 2024-04-03 21:03:05 -07:00
LoveSy 36bd00a046 Add p521 to magiskboot 2024-04-03 21:03:05 -07:00
topjohnwu fb5ee86615 Install one single ABI in ramdisk 2024-03-31 22:01:22 -07:00
topjohnwu 30bf5c8448 Fix typos 2024-03-31 14:17:25 -07:00
topjohnwu 2051836a73 Remove unused code 2024-03-30 21:03:51 -07:00
topjohnwu 2cb0af1ff3 Move revert_unmount into Rust 2024-03-30 02:51:39 -07:00
topjohnwu a1b6568226 Implement preinit related features in Rust 2024-03-28 14:11:03 -07:00
topjohnwu 1eddbfd72c Use const_format for const strings 2024-03-26 18:03:40 -07:00
topjohnwu 21ed095601 Update crt0 2024-03-25 16:11:02 -07:00
Js0n 000a2e4d59 Upgrade AGP to 8.3.1 2024-03-22 17:02:18 -07:00
Js0n 7abe635de9 fix: AGP 8.3.X 2024-03-22 17:02:18 -07:00
topjohnwu 9a008c17ba Optimize for binary size 2024-03-22 16:53:44 -07:00
topjohnwu 08dbf728a4 Allow platform_app to access MagiskSU 2024-03-22 16:53:44 -07:00
topjohnwu 4670f762d3 Disable debug only features 2024-03-22 16:53:44 -07:00
topjohnwu efa49567fa Fix parsing logic for term and sterm 2024-03-21 18:17:28 -07:00
topjohnwu 0ffc4527a7 Better error reporting 2024-03-21 15:10:34 -07:00
topjohnwu dd9d43be96 Move sepolicy parsing error message into Rust 2024-03-21 14:07:28 -07:00
topjohnwu 865fca71a5 Optimize sepolicy rules
Close #7916

Co-authored-by: vvb2060 <vvb2060@gmail.com>
2024-03-21 01:51:35 -07:00
topjohnwu 6b4baa3bcd Change a little parsing handling 2024-03-21 00:04:09 -07:00
topjohnwu a9ee2d7d18 Fix xperm parsing logic 2024-03-20 23:13:54 -07:00
topjohnwu d654b9cb97 Several code cleanups in sepolicy 2024-03-20 23:09:22 -07:00
LoveSy 4d2921e742 Rewrite sepolicy statement parsing in Rust 2024-03-20 10:12:23 -07:00
vvb2060 ecc74d45d1 Let magic mount optional 2024-03-19 23:21:41 -07:00
vvb2060 5de597f079 No need to unshare 2024-03-19 23:21:41 -07:00
LoveSy 156b0e67ca No need extra tmpfs for worker 2024-03-19 23:21:41 -07:00
vvb2060 10069215f4 Rename dir name 2024-03-19 23:21:41 -07:00
LoveSy 92b305a389 Remove unnecessary mirror for magic mount
Mirror was previously used for accessing the original file during
magic mount when we are using a tmpfs to cover the target. However,
since we introduce atomic mount, we switch all tmpfs mount in
worker and then move to the target at once. It means that we can
still access the original file when we are constructing the tmpfs
mount point. Thus we no longer need mirror.
2024-03-19 23:21:41 -07:00
topjohnwu d20b30c771 Update libsepol
Close #7915
2024-03-19 02:54:01 -07:00
topjohnwu 83209b21ff Release new canary build 2024-03-19 00:51:07 -07:00
topjohnwu 81658d45f7 Support tar with files larger than 8GiB
Fix #7838
2024-03-14 16:54:46 -07:00
topjohnwu c951b208a1 Always update stub APK when upgrade 2024-03-14 14:31:02 -07:00
topjohnwu 050a073771 Make all I/O suspendable 2024-03-12 03:24:42 -07:00
topjohnwu 21d374214f Minor cleanup of DownloadEngine 2024-03-12 03:14:06 -07:00
LoveSy 19ea25a9d0
Upgrade AGP 2024-03-08 17:12:46 -08:00
topjohnwu dbf6e40dfe Ensure stub APK is expected
Fix #7884
2024-03-08 17:09:54 -08:00
topjohnwu d56f4fbc90 Fix stub on API 34 2024-03-08 15:57:49 -08:00
topjohnwu 73c3d741a7 Reorganize some code 2024-03-06 18:07:01 -08:00
pndwal 2b5fc75127
Update faq.md
Since system Safe Mode may activate without Magisk 'Safe Mode', this clarification will prevent users from erroneously concluding either that Safe Mode doesn't work or that modules are not the issue since 'Safe Mode' was apparently triggered...

Fixes this: #4624
2024-03-06 17:48:54 -08:00
osm0sis 991802ab82 Add no decompression flag to magiskboot split 2024-03-06 16:15:30 -08:00
WindowsFan9600 7f6b5305ba Improve Turkish language translation
Updated "reboot_download" string
2024-03-06 02:37:58 -08:00
canyie 825c6c4316 Reverse format template argument order 2024-03-06 01:39:16 -08:00
canyie f00408c793 Fix zygote restart monitor 2024-03-06 01:39:16 -08:00
topjohnwu a6ff3672af Update crt0 2024-03-04 16:42:25 -08:00
LoveSy 2290ddeb89 Fix segfault when sepolicy.rule has empty line 2024-03-02 06:15:45 -08:00
topjohnwu 74af79ad03 Update crt0 2024-03-02 05:57:48 -08:00
LoveSy b6c24a3a8a No more sony `init.real` tricks
Co-authored-by: canyie <a1364259@163.com>
Co-authored-by: vvb2060 <vvb2060@gmail.com>
2024-02-29 23:40:00 -08:00
LoveSy a8c2ae223a Avoid hexpatch /init for 2SI when possible
Previous we hexpatch /init from /system/bin/init to /data/magiskinit
to redirect the second stage init. However, some devices like sony
has /init that does not directly invoke /system/bin/init, and thus
the hexpatch fails.

In this patch, we further make use of AOSP `SwitchRoot` to help us
bind mount /data/magisk to /system/bin/init after `SwitchRoot`.

Two important assumption about 2SI are i) that the second stage init
is always /system/bin/init and ii) that the /sdcard (path after
`SwitchRoot`) is always a symlink to `/storage/self/primary`. When
these assumptions hold, during first stage init (before `SwitchRoot`)
we can bind mount magiskinit to /sdcard, and create a symlink
/storage/self/primary to /system/system/bin/init. By these steps,
during `SwitchRoot`, AOSP init will try to mount move /sdcard to
/system/sdcard. And /system/sdcard is symlink to /storage/self/primary,
it will try to mount move /sdcard to /storage/self/primary. And
/storage/self/primary in ramfs is now a symlink that points to
/system/system/bin/init, thus AOSP will try to mount move /sdcard
(which is a bind mount to magiskinit) to /system/system/bin/init.
After chroot done by AOSP init, we then have a magiskinit bind mount
on /system/bin/init, which is the second stage init.

An edge case is that some devices (like meizu) use 2SI but
does not switch root. In this case, they must already have a /sdcard
in the ramfs, thus we can check if /sdcard exists and fallback to
hexpatch.
2024-02-29 23:40:00 -08:00
topjohnwu 953d44302c Remove ancient NDK binaries 2024-02-29 23:26:58 -08:00
topjohnwu 24e46a5971 Build magiskboot with crt0 2024-02-29 02:36:05 -08:00
topjohnwu b1297c4192 Less usage of C stdio 2024-02-28 15:52:03 -08:00
topjohnwu 9ae328fd84 Further reduce code size 2024-02-28 11:19:56 -08:00
topjohnwu 625a1d6f44 Remove seek support from streams 2024-02-28 11:07:53 -08:00
topjohnwu 987e5f5413 Address clippy warnings 2024-02-27 21:03:34 -08:00
topjohnwu 715284b70d Reorganize code 2024-02-27 18:14:30 -08:00
LoveSy 62fc7868ac Use self implemented parse_mount_info 2024-02-27 17:03:22 -08:00
topjohnwu 1a70796339 Replace all parse_mount_info usage with Rust 2024-02-27 03:49:17 -08:00
topjohnwu af6965eefa Update init logging implementation
Use less std::fs
2024-02-26 17:49:11 -08:00
topjohnwu 8f7d2e38f7 Make crt0 an external submodule 2024-02-26 17:34:17 -08:00
topjohnwu be433fa667 Use Rust for formatting
The fprintf implementation included in crt0 is too rudimental
2024-02-26 00:26:23 -08:00
topjohnwu 0ccd6e7381 Fix fread and fwrite implementation 2024-02-25 23:20:30 -08:00
topjohnwu 907bbbda41 Remove usage of patched static lib 2024-02-25 22:11:34 -08:00
topjohnwu 4393bc077d Implement string routines 2024-02-25 21:12:19 -08:00
topjohnwu 365b373480 Make it easy to build without crt0 2024-02-24 22:32:22 -08:00
topjohnwu 47e6dd286d Minor fixes 2024-02-24 22:00:09 -08:00
topjohnwu 0dbaf52566 Make all platforms build properly 2024-02-24 05:10:54 -08:00
topjohnwu 66f49dfab5 Remove unnecessary lock usage 2024-02-24 04:20:28 -08:00
topjohnwu f8967e9274 Implement strerror 2024-02-24 04:02:46 -08:00
topjohnwu a4f008fde5 Reorganize files 2024-02-24 03:41:22 -08:00
topjohnwu e9980c778b Implement stub functions 2024-02-24 03:41:03 -08:00
topjohnwu 06b6fb0c33 Add setenv 2024-02-24 03:27:09 -08:00
topjohnwu 38cb3d4105 Add dirent implementation 2024-02-24 03:26:53 -08:00
topjohnwu db99caf258 Use execve directly 2024-02-24 01:47:11 -08:00
topjohnwu 39dbffadfe Complete stdio 2024-02-24 01:28:58 -08:00
topjohnwu b7505c3c9c Remove fopen usage in magiskinit 2024-02-24 00:45:07 -08:00
topjohnwu 3185e5a7ca Introduce string/mem functions 2024-02-23 23:56:31 -08:00
topjohnwu e0cbe28711 Add the generic syscall function 2024-02-23 18:41:39 -08:00
topjohnwu 66cee19cea Add printf and sscanf family 2024-02-23 17:44:12 -08:00
topjohnwu 2ec29ade79 Add all missing syscalls 2024-02-23 14:35:12 -08:00
topjohnwu c865d4e187 Add memory allocator 2024-02-22 21:22:27 -08:00
topjohnwu a42a0a53ce Declare more symbols 2024-02-22 21:22:27 -08:00
topjohnwu 6d79de7d71 Initial crt0 implementation
Builds but cannot link, missing a lot of symbols
2024-02-22 21:22:27 -08:00
topjohnwu 7e9abe6e90 Update ONDK 2024-02-22 20:58:40 -08:00
残页 4d5510be4f Prompt users to use reboot button in System Updates
So the update engine can write verify info of partitions which fixes bootloop on newer Pixel devices
2024-02-19 02:14:12 -08:00
topjohnwu b04e1394c0 Update README 2024-02-07 14:39:53 -08:00
topjohnwu 2aa923191e Rename DownloadManager to DownloadEngine
Also add some documentation
2024-02-06 17:54:15 -08:00
topjohnwu 4bf1c74164 Disable foreground service on API 34+ 2024-02-06 17:29:42 -08:00
topjohnwu 472c7878b2 Update AGP 2024-02-06 17:04:48 -08:00
topjohnwu 38ad871e33 Use user-initiated jobs for download tasks on API 34+ 2024-02-06 17:04:39 -08:00
topjohnwu c5d34670c4 Isolate download logic from service lifecycle 2024-02-06 00:56:14 -08:00
topjohnwu 154121f3dd Release new canary build 2024-02-02 23:51:35 -08:00
topjohnwu 3d91a561fe Update README 2024-02-02 23:35:14 -08:00
topjohnwu 2c6adbc69b Release Magisk v27.0 2024-02-02 22:54:41 -08:00
topjohnwu 5280982363 Add v27.0 changelog 2024-02-02 22:47:35 -08:00
topjohnwu 18c45ae289 Update cxx and Rust dependencies 2024-02-02 14:35:30 -08:00
LoveSy 41fbd2a7be Upgrade gradle 2024-02-02 10:55:11 -08:00
LoveSy 5e45884af4 Use Apple Silicon for CI
https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/
2024-02-01 15:09:42 -08:00
topjohnwu d78ee171bc Release new canary build 2024-01-30 15:59:02 -08:00
LoveSy 356ee1febd Code clean up 2024-01-30 11:07:37 -08:00
LoveSy cc044ccc4c Fix zygisk unload 2024-01-30 11:07:37 -08:00
LoveSy 9c638cc463 Remove rust workaround 2024-01-29 15:07:21 -08:00
topjohnwu df786eb2b6 Separate Linux and other jobs 2024-01-29 01:53:09 -08:00
topjohnwu 8e7186eebb Try out composite actions 2024-01-29 01:36:51 -08:00
topjohnwu 74b7b84561 Test all APIs on Linux 2024-01-28 00:46:03 -08:00
topjohnwu 308c9999fa Properly detect package changes 2024-01-28 00:42:43 -08:00
topjohnwu 930bb8687f Minor zygisk refactoring 2024-01-25 00:17:47 -08:00
topjohnwu f2c4288d2d Run pthread_atfork only once
Close #7704
2024-01-25 00:17:05 -08:00
topjohnwu b44141ae39 Run tests on Linux 2024-01-22 18:10:26 -08:00
kam821 86e0020964 Update Polish translation
- Added missing strings, fixed translation, escaped quotas.
- Some context dependent values (like target_pid) may require better translation in the future.
- Also, 'DenyList' translation, although correct, could be replaced in the future by better fitting polish equivalent of 'block list' / 'rejection list'.
2024-01-17 16:13:20 -08:00
残页 94d3daeadf Fix Sony init.real check 2024-01-17 16:09:50 -08:00
LoveSy 79334b7702 One stage zygisk loading 2024-01-11 16:19:39 -08:00
LoveSy df66458db6 Check full path of init.rc instead of its dir
Some devices has `/system/etc/init/hw` but has no init.rc in it.
2024-01-11 16:18:57 -08:00
LoveSy 97705704e2 install or uninstall apk asynchronously 2024-01-11 16:16:36 -08:00
topjohnwu 1206179580 Update dependencies 2024-01-10 15:46:30 -08:00
topjohnwu a0b8aa4da6 Release new canary build 2023-12-27 01:42:42 +08:00
topjohnwu 65207f96c8 Create custom cxx binding to Utf8CStr 2023-12-26 23:10:55 +08:00
Abhishek Girish 062e498bdd
Update Malayalam translations 2023-12-25 18:46:08 +08:00
topjohnwu 1057cb3e3c Set serial on Rust binding 2023-12-24 04:36:58 +08:00
topjohnwu 2dd23b2518 Update system_properties 2023-12-24 04:36:58 +08:00
RafaeloxMC 8cab12998c Update strings.xml / German translation 2023-12-23 16:49:47 +08:00
topjohnwu 48b1c26dc8 Prevent race condition in wait 2023-12-23 06:33:12 +08:00
topjohnwu f1e0bc3e4a Use platform implementation if possible 2023-12-23 06:24:20 +08:00
topjohnwu 38527cd58f Slightly change wait usage and API 2023-12-23 06:23:29 +08:00
LoveSy e94d65b4b2 Add `resetprop -w` for waiting property change
It's very easy to wait for property change both in Java and C++,
but it's not the case in shell script. With this patch, developers
can now easily to wait for property change, just like what we have
in `.rc` files, and to wait for boot complete.
2023-12-23 00:12:42 +08:00
LoveSy 27ece3c7df Keep mirror shared before magic mount
This allows mounting during post-fs-data be kept after magic mount
2023-12-22 21:39:03 +08:00
LoveSy 06687abffc Fix magisk --stop by making mirror shared
Previously mirror is private and then unshared to zygote, which
makes magisk --stop cannot propagate umount mirror to zygote.
2023-12-22 21:39:03 +08:00
vvb2060 deedb462a0 Hide magisk internal mount point 2023-12-22 21:38:15 +08:00
igor c48962bdf7
Update Portuguese translation 2023-12-22 01:31:55 +08:00
Wang Han 1ef3f6e13b Remove useless rule for prctl PR_SET_MM
* There is no use-case for it now.
2023-12-22 00:36:06 +08:00
topjohnwu 83a34a9004 Update emulator 2023-12-21 21:30:35 +08:00
topjohnwu e30bda6c8d Rebase libsepol to AOSP main 2023-12-21 19:23:02 +08:00
vvb2060 00e9d76a5a Revert "Avoid doing any unmounts for SysUI" 2023-12-20 17:23:17 +08:00
LoveSy 6cda6c2fae Upgrade github action deps 2023-12-18 16:25:56 +08:00
VD $ VD171 @ Priv8 6dfda6dc39
Update Portuguese Translation 2023-12-18 16:24:14 +08:00
LoveSy f41994cb52 Skip svc for ro properties
ro properties' triggers should only be triggered once, otherwise it
may undefined behaviour.
This patch avoids triggering ro properties' actions again when using
resetprop to modify them.

Co-authored-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-12-18 16:21:08 +08:00
topjohnwu a003336497 Update system_properties for pre Android 10 2023-12-18 16:21:08 +08:00
LoveSy 401090d6fe Avoid zygiskd restarts when boot-complete 2023-12-18 16:21:08 +08:00
LoveSy 90dcc1cd30 Do not always zero initialize for rust resize vec 2023-12-18 16:21:08 +08:00
LoveSy 2ac464b186 Only compress regular file 2023-12-18 16:21:08 +08:00
LoveSy 8b7fae278b Support compressing during cpio backup 2023-12-18 16:21:08 +08:00
topjohnwu d73c2daf6d Use special emulator to make tests less flaky 2023-12-16 15:50:53 +08:00
topjohnwu ca25935de3 Release new canary build 2023-12-14 03:21:22 +08:00
LoveSy d7750b7220 uiautomator dump to /data/local/tmp 2023-12-13 03:28:30 +08:00
LoveSy 98861f0b5a Clone dir attr for tmpfs in advance 2023-12-13 03:28:30 +08:00
topjohnwu e35925d520 Properly version zygisk APIs 2023-12-13 03:27:38 +08:00
Kieron Quinn 685a2d2101 Fixes for Android 14 QPR2 B2
Added new method signatures and arguments
2023-12-13 00:16:54 +08:00
LoveSy f7e471616d Fix clone_attr for newly created dirs 2023-12-10 23:37:47 +08:00
残页 c013a349af Update install guide
- Remove boot vbmeta patching because the checkbox is removed in b1363ee
- Remove meaningless slot argument from `fastboot flash` as it will automatically flash the active slot. Fix #7571
2023-12-10 23:37:02 +08:00
topjohnwu 61ea59a27b API 34 AOSP ATD image is released 2023-12-08 17:59:24 +08:00
VD $ VD171 @ Priv8 e55f338367 Update Portuguese Translation 2023-12-08 17:03:48 +08:00
VD $ VD171 @ Priv8 1425cf4105 Update Portuguese Translation 2023-12-08 17:03:48 +08:00
topjohnwu b493a985b0 Update dependencies 2023-12-08 17:03:18 +08:00
canyie 1fe9ede940 Update selinux to disable validation for policydb 2023-12-08 16:50:45 +08:00
LoveSy 1fd49e4987 Make tmpfs mount of magic mount atomic
This avoid system libraries disappear temporarily during magic mount,
which causes some dynamic executables fails to run during post-fs-data.
2023-12-08 13:59:02 +08:00
LoveSy d49b02b274 Fix zygiskd not restart when zygote restarts 2023-12-07 20:44:44 +08:00
LoveSy d47e70cfaa Fix native symbol strips
`ndkVersion` is also needed by app for striping native symbols.
Set it in `setupCommon` instead.
2023-12-04 00:37:09 +08:00
topjohnwu 40cb031af5 Release new canary build 2023-12-04 00:30:46 +08:00
topjohnwu 1dcf325547 Minor cleanup 2023-12-03 19:32:58 +08:00
LoveSy 4e99997013 Upgrade AGP 2023-12-02 15:25:58 +08:00
LoveSy 334554697d Enable rust parallel front-end
See https://blog.rust-lang.org/2023/11/09/parallel-rustc.html
2023-12-02 15:25:41 +08:00
LoveSy e77cbd0c15 Upgrade gradle 2023-11-30 11:49:40 +08:00
topjohnwu 46ba008b9d Disable SCCACHE_DIRECT 2023-11-30 01:55:38 +08:00
LoveSy 58aded31c2 Enable iter_intersperse 2023-11-29 23:47:51 +08:00
LoveSy 6f6b0ade06 Correct cpio's norm_path 2023-11-29 23:47:51 +08:00
topjohnwu d9b67a207b Update ONDK 2023-11-27 17:41:11 +08:00
topjohnwu c7083659aa Directly guard boot state with mutex 2023-11-27 17:40:58 +08:00
topjohnwu a6d1803105 Update dependencies 2023-11-26 23:09:20 +08:00
Re*Index. (ot_inc) 66eef75673 Update strings.xml 2023-11-26 22:54:53 +08:00
Alessandro Sangiorgi 367f5f7b44
Update italian translation
Co-authored-by: Francesco Saltori <francescosaltori@gmail.com>
2023-11-26 22:54:33 +08:00
topjohnwu 0edcb03c45 Update test API levels 2023-11-26 21:41:43 +08:00
canyie 63eef153de Warn about unsupported installation methods 2023-11-17 13:58:41 -08:00
canyie 68442f38ac Misc changes
- actions: Update all actions/checkout references to v4
- magiskboot: Add missing new line to dtb help message
- docs: Update documents, fix some errors and remove outdated info
2023-11-17 13:58:41 -08:00
topjohnwu 8d5b9e5329 C++/Rust 2 way binding for MagiskD 2023-11-17 13:35:50 -08:00
topjohnwu 6c0966b795 Move some global state into Rust 2023-11-16 15:38:38 -08:00
topjohnwu 7c2e93d266 Introduce owned_fd 2023-11-16 15:38:38 -08:00
topjohnwu 1ff7b9055f Add LSPosed launch test 2023-11-16 15:38:38 -08:00
topjohnwu 49f241b77c Allow running scripts with incomplete env 2023-11-10 00:55:05 -08:00
topjohnwu cfb20b0f86 Zygisk refactoring part 2 2023-11-09 20:55:58 -08:00
topjohnwu 6d6f14fcb3 Use bitflags 2023-11-09 14:35:49 -08:00
topjohnwu 977c981265 Make sure native bridge is restored on daemon restart 2023-11-08 17:55:25 -08:00
topjohnwu ef48abf19d Reorganize zygisk code 2023-11-08 17:46:39 -08:00
topjohnwu 65c18f9c09 Restructure project files 2023-11-08 01:46:02 -08:00
残页 ecb31eed40
Prevent Zygisk from closing new fds created by Zygote itself 2023-11-08 00:34:38 -08:00
topjohnwu a80cadf587 Refactor hookJniNativeMethods
Utilize NativeBridgeRuntimeCallbacks we obtained from native bridge
to directly fetch and modify registered native JNI methods.
By doing so, we do not need to keep a copy of every single
JNINativeMethod registered in order to provide JNI hooking
functionality.

Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-11-07 23:57:55 -08:00
LoveSy fce1bf2365 Obtain NativeBridgeRuntimeCallbacks for future use
NativeBridgeRuntimeCallbacks can be used for better JNI method hooking

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-11-07 16:56:40 -08:00
LoveSy cbc6d40b2c Clean up codes 2023-11-07 14:25:57 -08:00
LoveSy 9fbd079560 Refactor zygisk to use native bridge to inject
Co-authored-by: vvb2060 <vvb2060@gmail.com>
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-11-07 14:25:57 -08:00
LoveSy 42eb928054 Inject zygisk.rc for sync `--zygisk-restart` 2023-11-06 15:39:48 -08:00
topjohnwu 577483fde1 Release new canary build 2023-11-05 23:49:35 -08:00
topjohnwu aa6c7c15cc Update README 2023-11-05 23:44:07 -08:00
topjohnwu 6c807d35b2 Release Magisk v26.4 2023-11-05 23:31:15 -08:00
topjohnwu 8ca8cdae97 Add v26.4 release notes 2023-11-05 23:07:53 -08:00
topjohnwu 75e37be6f3 Do not need to check pkg in magisk_env 2023-11-05 23:02:40 -08:00
WindowsFan9600 4985314ca6 Update language "tr" on main application 2023-11-05 22:37:39 -08:00
topjohnwu ac5ceb18c8 Guard log FIFO with SELinux 2023-11-04 23:59:11 -07:00
topjohnwu 72b39594d3 Always close logd_fd during fork 2023-11-04 02:36:14 -07:00
topjohnwu 16ae4aedf1 Remove usage of MAGISKTMP 2023-11-02 15:50:36 -07:00
topjohnwu 3ba00858e6 Allow avd_magisk on API 28 2023-11-01 09:15:17 -07:00
topjohnwu 489100c755 Fix fd sanitization 2023-11-01 02:01:29 -07:00
topjohnwu da766f2a4e Do not go through magiskd for getting the log pipe 2023-11-01 02:01:18 -07:00
topjohnwu c81d7ff76c Remove unnecessary RefCell usage 2023-10-31 18:22:48 -07:00
topjohnwu a6e50d3648 Make log pipe a FIFO instead of anonymous pipe 2023-10-31 18:05:22 -07:00
topjohnwu a177846044 Better logging in recv_fds 2023-10-31 17:40:59 -07:00
topjohnwu 19a4e11645 Make tmpfs path static strings 2023-10-29 00:47:28 -07:00
topjohnwu 67cc36268e Simplify zygisk log pipe 2023-10-26 18:13:56 -07:00
topjohnwu 28770b9a32 Support baseline profiles 2023-10-26 15:56:51 -07:00
WindowsFan9600 9f92e1bf15 [STRINGS] Improve Turkish (tr) language 2023-10-26 15:23:35 -07:00
topjohnwu 23fe5d5a19 Update build.yml 2023-10-26 14:50:42 -07:00
LoveSy 9088b584f6 Use official argh 2023-10-25 15:14:16 -07:00
vvb2060 beaf636415 Use ccache for C code 2023-10-25 15:05:41 -07:00
vvb2060 09bb2fe8dc Update dependencies 2023-10-25 14:58:02 -07:00
tzagim 1d6747d90e
Update Hebrew translation 2023-10-24 21:06:15 -07:00
南宫雪珊 efadd94de3 Update strings.xml 2023-10-24 21:02:32 -07:00
vvb2060 8c0b4e444a Update zh-rCN translation 2023-10-24 21:02:32 -07:00
Rom 32c7106e40 Update French translation 2023-10-24 21:01:53 -07:00
topjohnwu d2f2a9e4c8 Make avd_test less flaky 2023-10-24 16:45:24 -07:00
topjohnwu 985454afd4 Better logging 2023-10-24 16:41:49 -07:00
topjohnwu 9e1322de25 Make sure the shared preference is committed 2023-10-24 16:41:38 -07:00
topjohnwu 4e4ec73d94 Make gradle.properties optional 2023-10-19 15:44:34 -07:00
topjohnwu bb39a524d0 Switch to default images for faster boot time 2023-10-19 05:31:03 -07:00
topjohnwu 196d9af099 Add application and Zygisk tests to avd_test.sh 2023-10-19 05:15:53 -07:00
topjohnwu 1eeb2a34a1 Don't support alternative binary paths
The Magisk app will guide users through repair setup
2023-10-19 05:11:43 -07:00
Arbri çoçka cf43c56218 Update strings.xml sq 2023-10-18 14:29:55 -07:00
kubalav e6c1aec443 Update Slovak translation 2023-10-18 14:29:38 -07:00
topjohnwu 43fd1c4c1b Update stub version 2023-10-17 19:22:53 -07:00
topjohnwu 022caca979 Release new canary build 2023-10-17 19:13:16 -07:00
topjohnwu 0352ea2cca Rename biometrics to user authentication 2023-10-17 18:43:27 -07:00
topjohnwu e483d6befe Do not go through a fragment for auth 2023-10-17 17:39:31 -07:00
vvb2060 678c07fff5 suBiometric: remove biometric
use device credential to support more devices and second user
2023-10-17 17:39:05 -07:00
topjohnwu 91c92051f1 Simplify C++ SELinux routines 2023-10-17 16:04:59 -07:00
topjohnwu 4b8a0388e7 Make SELinux support a feature 2023-10-17 13:29:15 -07:00
topjohnwu 66788dc58c Cleanup SELinux support 2023-10-16 17:38:44 -07:00
topjohnwu dd8c28b1cb Upgrade AGP 2023-10-16 17:25:57 -07:00
残页 32c5153e8e Increase boot timeout to 600s 2023-10-16 01:20:25 -07:00
topjohnwu 36de62873a Fix error logging on the C++ side 2023-10-13 16:59:54 -07:00
topjohnwu 51e37880c6 Add repr(transparent) to guarantee soundness 2023-10-12 18:59:16 -07:00
topjohnwu 4b83c1e76c Cleanup messy error messages 2023-10-12 18:54:09 -07:00
topjohnwu b0b04690d5 Use newer bash version for avd_test.sh 2023-10-12 00:45:53 -07:00
topjohnwu 6d1e8d86cb Cleaner cstr code 2023-10-11 23:53:55 -07:00
topjohnwu eda8c70a80 Borrow value instead of moving in FsPath::from()
When accepting a value of AsRef<Utf8CStr> in FsPath::from(), the
existing code will move a value of Utf8CStrBufArr, creating a reference
that lives longer than the borrowing value, causing undefined behavior.

The issue is only visible on release builds, as more advanced
optimizations will be more aggressive re-using the stack of variables
that no longer lives.

Fix #7408
2023-10-11 23:48:54 -07:00
topjohnwu 587b6cfd41 Update avd_test.sh 2023-10-11 22:42:45 -07:00
topjohnwu e774408782 Allow AVD hacks on release builds 2023-10-11 14:17:31 -07:00
canyie 187f583c95 Fix $RECOVERYMODE from config being incorrectly overridden
Move legacy SAR checking logic into mount_partitions, and avoid calling get_flags before check_boot_ramdisk
Fix #7346
2023-10-10 15:53:18 -07:00
topjohnwu f5d3a71478 Update ONDK to r26.1 2023-10-10 15:52:59 -07:00
残页 d868ff3080 AVD test release builds as well 2023-10-10 15:52:41 -07:00
nkh0472 f80198a669
typo fix 2023-10-09 17:22:48 -07:00
topjohnwu 6076b52c48 Update libcxx 2023-10-03 17:22:25 -07:00
topjohnwu 79a1c39b30 Simplify fd sanitization 2023-09-28 20:38:16 -07:00
topjohnwu 5c92d39498 Enable Zygisk by default in emulators
Make sure CI tests Zygisk
2023-09-28 20:25:26 -07:00
topjohnwu 6e7a995716 Introduce UtfCString 2023-09-27 15:21:24 -07:00
topjohnwu a55d570213 Move more I/O operations into Rust 2023-09-27 02:28:43 -07:00
topjohnwu 5d07d0b964 Do not support systems without SELinux 2023-09-27 02:28:43 -07:00
Wang Han ec115cd7e3 Don't skip fd sanitization if fds_to_ignore does not exist 2023-09-25 09:45:03 -07:00
osm0sis 9b3896fd3d Retain PREINITDEVICE during A-only addon.d 2023-09-23 23:51:36 -07:00
topjohnwu a3f5918d25 Fix bug in libsepol
Fix #7308
2023-09-23 22:34:51 -07:00
topjohnwu b28326198c Use crates for cpio code 2023-09-22 01:39:21 -07:00
topjohnwu 46275b90c2 Generalize unxz 2023-09-21 05:47:21 -07:00
topjohnwu 15e13a8d8b Organize logging code 2023-09-19 03:02:30 -07:00
topjohnwu b750c89c87 Address clippy warnings 2023-09-19 01:11:50 -07:00
LoveSy 8d7c7c3dfb Refactor dtb in rust 2023-09-19 00:41:42 -07:00
topjohnwu 8e1a91509c Remove readlink_unsafe 2023-09-19 00:06:21 -07:00
LoveSy 927cd571f8 Fix `read_cert` crash when receive fd = -1 2023-09-18 22:32:32 -07:00
LoveSy 5fbd3e5c65 Fix buf len update of read_link to Utf8CStrBuf 2023-09-18 22:31:12 -07:00
LoveSy 877aeb66cb Upgrade to Gradle 8.3 2023-09-14 13:16:59 -07:00
topjohnwu 8a88d8465a Prevent OOM
Fix #7341
2023-09-14 13:14:30 -07:00
topjohnwu dda8cc85c9 Use bytemuck 2023-09-14 13:10:09 -07:00
topjohnwu 6a59939d9a Remove for_all_file 2023-09-13 18:09:16 -07:00
topjohnwu 4745e86c1b Fix #7301 2023-09-13 14:44:20 -07:00
topjohnwu 9aa466c773 Fix genfscon and filename_trans
Fix #7329
2023-09-12 21:31:31 -07:00
LoveSy 0243610c09 No trailing zeros if the signed boot img is larger 2023-09-12 18:09:20 -07:00
topjohnwu 0a2a590ab7 Use Utf8CStr for logging 2023-09-12 17:35:20 -07:00
topjohnwu 89aee6ffa7 Add more to the Utf8CStr family
Better C strings with path operations
2023-09-12 17:35:01 -07:00
topjohnwu 4eaf701cb7 Address clippy warnings 2023-09-06 21:45:12 -07:00
topjohnwu 4fff2aa7d8 Fix proto read and write 2023-09-06 20:45:59 -07:00
topjohnwu 35b3c8ba5c Cleanup persist props code 2023-09-06 15:52:14 -07:00
topjohnwu 1d7cff7703 Update Cargo dependencies 2023-09-06 13:57:43 -07:00
LoveSy 8d81bd0e33 resetprop: replace nanopb with quick-protobuf for persist 2023-09-05 22:20:57 -07:00
topjohnwu 7826d7527f Release new canary build 2023-09-04 00:35:17 -07:00
topjohnwu d4e552d08b Update README 2023-09-04 00:26:48 -07:00
topjohnwu 5a16418543 Release Magisk v26.3 2023-09-04 00:01:46 -07:00
topjohnwu 7297aba15a Add v26.3 release notes 2023-09-03 23:54:45 -07:00
Ylarod bc5d5f9502 Update details.md
The `MAGISKTMP` changed to `/debug_ramdisk` since https://github.com/topjohnwu/Magisk/pull/6931
2023-09-03 23:46:25 -07:00
vvb2060 1761986c1b Update zh-rCN translation 2023-09-03 23:43:07 -07:00
topjohnwu 1e034e3e0e Update libsu 2023-09-03 23:41:17 -07:00
topjohnwu bbf9756bfa Release new canary build 2023-09-02 06:20:10 -07:00
topjohnwu 96e559fb0e Skip cargo build if possible 2023-09-02 06:03:39 -07:00
topjohnwu 4c45775131 Update BusyBox 2023-09-02 05:45:18 -07:00
LoveSy c072b4254d Wrap rename and renameat 2023-09-02 04:24:24 -07:00
topjohnwu 2dbb812126 Disable stack protector on x86 static executables
Close #7274
2023-09-01 23:04:19 -07:00
topjohnwu be50f17f55 Update to ONDK r26.0
Close #7264
2023-09-01 23:01:49 -07:00
残页 6f77f190f2 Fix processPayload 2023-08-31 20:30:10 -07:00
topjohnwu 6bdc57cbe4 Release new canary build 2023-08-30 01:22:02 -07:00
残页 de00f1d5a9 Always check mounts to detect legacy SAR on bootmode 2023-08-30 01:02:19 -07:00
残页 e9b9bf987b Fix syntax error in util_functions.sh 2023-08-29 15:33:03 -07:00
topjohnwu f4b6385f9f Disable boot vbmeta patch when found vbmeta.img 2023-08-28 22:54:55 -07:00
topjohnwu 75d905a56d Fix device detection scripts and logic 2023-08-28 22:13:36 -07:00
topjohnwu b1363ee479 Do not allow user to configure boot vbmeta patching 2023-08-28 22:13:36 -07:00
topjohnwu 51afe43a30 Cleanup util_functions 2023-08-28 22:13:36 -07:00
残页 189c03c047 Add canyie to developer list 2023-08-28 13:21:58 -07:00
topjohnwu ae9d270a32 Release new canary build 2023-08-28 01:25:07 -07:00
topjohnwu e47e869f6b Update full changelog 2023-08-28 01:15:14 -07:00
topjohnwu c39038a439 Update README 2023-08-28 01:13:25 -07:00
topjohnwu 69174e2c13 Release Magisk v26.2 2023-08-28 01:04:28 -07:00
Chris Renshaw 474268a0af
manager.sh: add ro.boot.vbmeta.size + ro.product.ab_ota_partitions to vbmeta check
ro.boot.vbmeta.device doesn't seem to be in use on all A/B devices
2023-08-28 00:45:14 -07:00
topjohnwu eadb0307fa Add v26.2 release notes 2023-08-27 23:48:49 -07:00
topjohnwu 5a5d0d5d72 Add missing permissions 2023-08-27 23:29:34 -07:00
topjohnwu a1273bc467 Update dependencies 2023-08-27 22:44:51 -07:00
topjohnwu 0c28a916be Use cxx_name 2023-08-24 00:50:38 -07:00
BlackMesa123 0ba573b789 Additional Samsung devices install guide refactoring
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
2023-08-18 17:24:25 -07:00
BlackMesa123 ec42ee152c Refactor Samsung devices install guide
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
2023-08-18 17:24:25 -07:00
I3elphegor abcb487361 Update czech translation of strings.xml
Added and translated new strings.
2023-08-18 17:19:36 -07:00
vvb2060 d12d9e82f1 Force kernel to load rootfs only for legacy SAR devices 2023-08-18 17:18:34 -07:00
topjohnwu 275208e81b Update Rust dependencies 2023-08-17 21:24:29 -07:00
topjohnwu 41226c12b8 Update to ONDK r25.7 2023-08-15 17:10:20 -07:00
topjohnwu f86c66c99d Officially support API 34 2023-08-11 09:46:45 -07:00
topjohnwu 93876b5fd3 Update dependencies, AGP, and SDK level 2023-08-11 09:38:59 -07:00
topjohnwu b5b14ce343 Use cxx_name instead of rust_name 2023-08-10 21:22:53 -07:00
topjohnwu 350d0d600c Update build script 2023-08-08 01:05:32 -07:00
topjohnwu f924ffcbf3 Merge files 2023-08-08 00:57:58 -07:00
VD $ VD171 @ Priv8 0f5963f231
Update PORTUGUESE Translation 2023-08-08 00:54:01 -07:00
Arbri çoçka 1961ff2c40 Update strings.xml Albania 2023-08-08 00:53:38 -07:00
vvb2060 40003691d6 manager.sh: check vbmeta device by getprop 2023-08-08 00:53:21 -07:00
topjohnwu 8290358241 Release new canary build 2023-08-05 23:27:06 -07:00
kubalav ee34f775c3 Update Slovak translation 2023-08-05 23:19:34 -07:00
vvb2060 feb47cd88c sulog: add migration 2023-08-02 21:18:05 -07:00
vvb2060 c6efb51f61 sulog: add more info 2023-08-02 21:18:05 -07:00
Hen_Ry a5acf33ccd
Update De translation 2023-08-02 21:17:12 -07:00
vvb2060 ab9ee449e4 suBiometric: fix open app on second user will auto disable biometric
second user does not support biometric, but the config of app ignores user
2023-08-02 21:16:06 -07:00
vvb2060 9571b6f9be SuperuserViewModel: fix updatePolicy
Starting biometrics may cause the SuperuserFragment to lost focus. After onResume(), doLoadWork() will refresh the itemsPolicies, so notify property changed will work on wrong items. Fixed by snapshotting items to be refreshed before starting biometrics.
2023-08-02 09:29:14 -07:00
vvb2060 207d7fd3f6 SuRequestViewModel: fix await RootService on the main thread 2023-08-02 09:21:25 -07:00
南宫雪珊 bcdcfa1104 Update scripts/avd_magisk.sh 2023-08-02 09:12:00 -07:00
vvb2060 e0a4230dac avd_magisk: hide stderr 2023-08-02 09:12:00 -07:00
topjohnwu 17ba5cba3e Print permissive rules 2023-08-02 09:11:22 -07:00
topjohnwu f2e109ad7d Update libselinux and libsepol 2023-08-01 18:07:53 -07:00
topjohnwu c83e141a1c Support dumping sepolicy rules 2023-08-01 18:03:54 -07:00
topjohnwu 6089cc36de Update xperm parsing 2023-07-31 09:28:27 -07:00
topjohnwu 9638dc0a66 Fix perror 2023-07-25 21:03:04 -07:00
Andrew Gunnerson b191a14a23 magiskpolicy: Fix old xperms being cleared when adding new xperms
This commit updates sepol_impl::add_xperm_rule() so that it loads the
current xperm bits from the existing avtab entry before setting or
clearing xperm bits. This fixes new allowxperm rules causing old xperm
rules within the same xperm specified/driver to be removed.

Fixes: #7176

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-07-24 23:52:28 -07:00
topjohnwu cf1bc82537 Random small refactoring 2023-07-24 23:49:20 -07:00
残页 6141bb5bb3 Fix MagiskInstaller.patchBoot() error catching 2023-07-21 12:05:33 -07:00
topjohnwu 4d2b62da0d Do not override global variables in document 2023-07-21 12:04:42 -07:00
topjohnwu 39383229d1 Update dependencies
Close #7128
2023-07-20 18:35:53 -07:00
topjohnwu 08bfbb154a Release new canary build 2023-07-17 23:20:07 -07:00
残页 d390ca2fdf Avoid using IconCompat.createFromIcon() that doesn't support bitmap icon 2023-07-17 21:46:47 -07:00
topjohnwu 7ad77a14ae Remove unused line 2023-07-17 21:43:09 -07:00
topjohnwu f33343b4e6 Remove unused code and logic 2023-07-17 18:58:48 -07:00
topjohnwu af65d07456 Support AVB1.0 signing and verification in magiskboot 2023-07-17 18:57:50 -07:00
topjohnwu 16d728f379 Partially document global variables in scripts 2023-07-17 16:07:16 -07:00
topjohnwu c97ab690b6 Segment memory mapped boot image region 2023-07-13 21:01:49 -07:00
topjohnwu 4caed73fe0 Always include boot image when processing tar
Credits: @BlackMesa123

Fix #7132, close #7133
2023-07-09 02:04:42 -07:00
BlackMesa123 4856da1584 Ignore `userdata.img` in Samsung tar firmware packages
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
2023-07-06 17:37:12 -07:00
LoveSy 0a07018fec No need to use submodule for argh 2023-07-06 15:28:39 -07:00
LoveSy 64c82e1f2c Refine cpio argh
we can use argh to handle `--help` now
2023-07-06 15:07:06 -07:00
topjohnwu e8e8afa6c2 Properly handle visibility 2023-07-06 11:12:27 -07:00
LoveSy af2207433d Fix error logging
ok_or will unconditionally creates a LoggedResult, which prints
an error even it successes. Use ok_or_else which lazily creates
a LoggedResult only if it fails.
2023-07-06 11:01:57 -07:00
LoveSy 75ba62d588 Fix stub resource loading on Android 9, 10
They can only load resources from zip files

Co-authored-by: canyie <a1364259@163.com>
Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
2023-07-06 04:53:26 -07:00
LoveSy 606d97ae4d Trace location from LoggedError
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-07-05 18:55:23 -07:00
topjohnwu d778b0b0a7 Custom help message when using argh
Help messages generated from argh is nearly useless and very hard to
customize. Fork argh and disable all code for generating help messages.

Use a closure to print the help message when handling EarlyExit.
2023-07-05 17:05:39 -07:00
topjohnwu 5ee6daf126 Handle cpio commands properly 2023-07-03 21:57:28 -07:00
Fs00 43b9a09c9b Update Italian app strings 2023-06-30 15:57:56 -07:00
Fs00 8475a2bb94 Update Italian stub strings 2023-06-30 15:57:56 -07:00
Rom d8692de2f4 Update French translation 2023-06-30 15:57:37 -07:00
LoveSy 33a9abc946 Fix backup fails when ramdisk does not exist 2023-06-30 15:57:09 -07:00
topjohnwu ee943afbc9 Cleanup SHA hash implementation 2023-06-30 15:50:52 -07:00
LoveSy 1f7c3e9f14 Use rust to calculate sha 2023-06-30 14:06:02 -07:00
topjohnwu 46770db18b Rename stuffs 2023-06-30 03:03:51 -07:00
vvb2060 92f980601c Fix close 2023-06-30 02:43:38 -07:00
vvb2060 d0b8c16651 Fix file permission 2023-06-30 02:43:38 -07:00
LoveSy a470ee6f93 Fix mmap block device 2023-06-30 01:06:51 -07:00
vvb2060 ff1c56683d Skip magisk32 for 64bit only avd 2023-06-29 20:45:51 -07:00
topjohnwu 4ee4cbada6 Standardize logging and error handling
- Introduce new types: LoggedResult and LoggedError
- Introduce new extension methods to log and add additional msgs
- Never exit when logging error messages in Rust (all errors should be
  handled by using Result and Rust's error propagation)
- Remove all usages of anyhow as it doesn't fit Magisk's use cases
2023-06-29 17:14:53 -07:00
topjohnwu dbc2236dd2 Release new canary build 2023-06-23 02:39:07 -07:00
topjohnwu a8c4a33e91 Avoid using trait object 2023-06-23 02:32:29 -07:00
topjohnwu 279f955a84 Minor changes 2023-06-23 01:50:33 -07:00
topjohnwu fbd1dbb20c Manage MenuProvider with lifecycle state 2023-06-22 16:12:35 -07:00
topjohnwu 6c09fc2e64 Move addMenuProvider into onStart 2023-06-22 15:47:12 -07:00
LoveSy f3304b482c Fix sulog prompt always shows 2023-06-22 15:27:34 -07:00
LoveSy 0a85ef61c3 Call `removeMenuProvider` on `Fragment::onStop` 2023-06-22 15:27:23 -07:00
topjohnwu dc26ad7125 Address clippy warnings 2023-06-22 02:36:31 -07:00
LoveSy 24b1c607f3 Replace clap with argh 2023-06-22 02:36:31 -07:00
topjohnwu 732a161b67 Minor cleanup 2023-06-22 02:23:27 -07:00
topjohnwu 9c7cf340a1 Move pattern matching to Rust 2023-06-21 16:47:20 -07:00
topjohnwu 399b9e5eba Move hexpatch to Rust 2023-06-20 18:17:26 -07:00
topjohnwu 5805573625 Update clean operation 2023-06-20 14:50:02 -07:00
topjohnwu a6b1149b9f Minor cleanup 2023-06-20 14:36:07 -07:00
LoveSy 51e985ae7f Use quick-protobuf 2023-06-20 14:36:07 -07:00
vvb2060 9929b25339 Move su request path to magisk tmp 2023-06-20 03:29:06 -07:00
topjohnwu 2359cfc480 Small refactor 2023-06-20 00:21:51 -07:00
topjohnwu 81493475f9 Directly use rust::Vec 2023-06-20 00:21:51 -07:00
Arbri çoçka 0493829231 Update strings.xml sq 2023-06-16 14:15:31 -07:00
VD $ VD171 @ Priv8 e2d1952ad9
Update PORTUGUESE Translation 2023-06-16 14:14:46 -07:00
LoveSy 7450965458
Update Chinese translation
Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
2023-06-16 14:13:46 -07:00
Vladimír Kubala f45384685b
Update Slovak translation 2023-06-16 14:13:01 -07:00
topjohnwu 8abcccc262 Fix typo 2023-06-16 01:49:44 -07:00
LoveSy a9c89cbbbb Read certificate in Rust
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-06-16 01:49:44 -07:00
topjohnwu d2eaa6e6c1 Fix scripts on Windows 2023-06-15 05:57:19 -07:00
LoveSy 53257b6ea1 Fix `find_apk_path` 2023-06-15 04:09:45 -07:00
LoveSy c874391be4 Box CpioEntry 2023-06-15 04:09:17 -07:00
LoveSy 7e8e013832 Fix two typo 2023-06-15 04:09:17 -07:00
topjohnwu 037f46f7f0 Fix copy_cstr 2023-06-15 04:00:32 -07:00
topjohnwu d3e1c496ca Upgrade ONDK to r25.6 2023-06-15 01:26:54 -07:00
topjohnwu d7d0a44693 Remove randomness from Magisk 2023-06-14 17:05:49 -07:00
topjohnwu 9d6f6764cb Use Metadata instead of direct stat syscall 2023-06-12 14:58:13 -07:00
topjohnwu cb3ab63815 Replace all CStr usage to Utf8CStr 2023-06-12 13:57:15 -07:00
topjohnwu caae932117 Remove unnecessary lifetime markers 2023-06-12 13:56:20 -07:00
LoveSy e9cf27eb5a Fix map_file 2023-06-12 13:55:58 -07:00
LoveSy 6ee6685f4c AVD test on API 34 2023-06-12 03:23:27 -07:00
LoveSy d15017b777 Add arg requirement for cpio extract 2023-06-12 02:40:50 -07:00
LoveSy a9387e63e1 Fix Utf8CStr::as_ref() -> OsStr 2023-06-12 02:40:50 -07:00
topjohnwu 23c1f0111b Improve Rust implementation
- Move mmap_file implementation into Rust
- Introduce Utf8CStr as the better c-string type to use
2023-06-12 02:40:50 -07:00
LoveSy 866386e21f Use to_string instead of to_owned 2023-06-12 02:40:50 -07:00
LoveSy bf10496fa9 Add log for restore 2023-06-12 02:40:50 -07:00
LoveSy 607e6547a7 No check rm -r 2023-06-12 02:40:50 -07:00
topjohnwu 6b21091fe2 Fix compile errors and cleanup 2023-06-12 02:40:50 -07:00
topjohnwu e58f98e844 Update cargo files 2023-06-12 02:40:50 -07:00
LoveSy b8cb9cd84d Refactor magiskboot cpio 2023-06-12 02:40:50 -07:00
LoveSy c1038ac6f9 Remove permissve update_engine 2023-06-10 13:17:37 -07:00
LoveSy c556dd0aac
Increase sccache hit rate 2023-06-10 13:17:16 -07:00
LoveSy d2fbcd07b7 Use sccache on non CI env 2023-06-10 13:14:12 -07:00
LoveSy bf6359abaa Fix release build 2023-06-10 13:10:54 -07:00
topjohnwu d1621845b8 Fix typo 2023-06-10 01:50:18 -07:00
topjohnwu f33f1d25d0 Move find_apk_path to Rust 2023-06-10 01:40:45 -07:00
topjohnwu 40f25f4d56 Introduce directory traversal 2023-06-09 02:00:37 -07:00
topjohnwu e13775ec2c Directly use memmem in contains 2023-06-07 16:52:52 -07:00
topjohnwu ee4dad7a13 Bridge C++ bytes with Rust &[u8] 2023-06-07 16:49:40 -07:00
topjohnwu 5e2ef1b7f4 Better bytes support in C++ 2023-06-06 17:11:42 -07:00
topjohnwu f8c38eab74 Proper Windows support 2023-06-05 02:27:02 -07:00
topjohnwu 305e8b3d14 Improve bootimg const correctness 2023-06-03 05:10:22 -07:00
topjohnwu 2a654e5d7f Improve byte_data const correctness 2023-06-03 03:16:03 -07:00
topjohnwu 57afae3425 Cleanup cpio codebase 2023-06-03 00:31:20 -07:00
topjohnwu feb44f875e Migrate PREINITDEVICE in recovery mode
Close #6917
2023-06-02 16:49:04 -07:00
topjohnwu 7eebe62bb6 Do not realpath ANDROID_SDK_ROOT 2023-06-02 15:36:45 -07:00
topjohnwu 9ea9f01933 Resolve clippy errors and warnings 2023-05-31 01:08:33 -07:00
topjohnwu 665c6bdc4b Provide easy access to the cargo command 2023-05-31 01:08:33 -07:00
topjohnwu c79bc83275 Update dependencies 2023-05-30 01:32:43 -07:00
topjohnwu c30fbdf145 Simplify logging code 2023-05-29 01:27:40 -07:00
topjohnwu f12951bd1d Fix typo 2023-05-29 00:30:55 -07:00
nikk gitanes 52f2e8c4a0 allow fast switch access with d-pad on superuser tab 2023-05-28 23:51:37 -07:00
nikk gitanes 1b2af1ed6d correlate nextFocusRight 2023-05-28 23:51:18 -07:00
nikk gitanes 0f9b2a7df8 make module card clickable and highlight when focused 2023-05-28 23:51:18 -07:00
topjohnwu f2846694e1 Cleanup some code 2023-05-28 23:50:52 -07:00
topjohnwu e668dbf6f7 Update AGP 2023-05-28 17:57:53 -07:00
topjohnwu d77a368176 Move dependency version into workspace 2023-05-28 17:30:33 -07:00
topjohnwu ad0da08610 Update native clean operation 2023-05-28 17:30:20 -07:00
topjohnwu 0c52385ad4 Update to use ONDK r25.4 2023-05-27 01:57:02 -07:00
topjohnwu 5b8b48ccc1 Properly support streamable input 2023-05-26 14:07:11 -07:00
topjohnwu 659b9c6fee Support extracting any partition from payload.bin 2023-05-26 13:36:47 -07:00
LoveSy ec31cab5a7 Add zip and payload.bin support to Magisk app 2023-05-26 13:36:47 -07:00
vvb2060 dd93556ad8 Faster get magisk tmpfs path 2023-05-25 01:03:27 -07:00
topjohnwu 533aeadd38 Update cstr macro 2023-05-25 01:03:04 -07:00
topjohnwu 18d0cedbe2 Parse rule files with Rust 2023-05-24 19:11:56 -07:00
topjohnwu 5a94ef9106 Fix init rust code setup 2023-05-23 21:50:13 -07:00
topjohnwu 8e8f01f8b5 Move project common code into include 2023-05-23 21:30:30 -07:00
topjohnwu 7087badf87 Release new canary build 2023-05-23 21:02:33 -07:00
topjohnwu 47d2d4e3a5 Update su cmdline parsing 2023-05-23 20:51:23 -07:00
topjohnwu 6c47d8f556 Support 32 bit only AVD patch
Close #7010
2023-05-23 18:12:06 -07:00
topjohnwu 8c9d0314fb Use sccache for all native builds in CI 2023-05-23 17:52:10 -07:00
topjohnwu 69144942e3 Fix fortify
Close #7009, fix #7003
2023-05-23 16:31:24 -07:00
topjohnwu 5627053b74 Move su folder into core 2023-05-23 01:36:25 -07:00
topjohnwu 0f666de5e6 Organize headers 2023-05-22 21:36:15 -07:00
LoveSy eddc862fa3 Use POSIX format 2023-05-22 18:14:59 -07:00
LoveSy 4327682120 Add mnt ns attach support for `su` 2023-05-22 18:14:59 -07:00
LoveSy af5bdee78f Reimplement `su -z` 2023-05-22 18:14:59 -07:00
LoveSy 0e36e86dbf Support settings gids of su 2023-05-22 18:14:59 -07:00
LoveSy f95478f1f1 Truncate file only if needed 2023-05-22 00:51:42 -07:00
topjohnwu 9fe8741a02 Export get_prop to Rust 2023-05-21 23:51:30 -07:00
topjohnwu a5768e02ea Cleanup byte_channel implementation 2023-05-20 14:19:40 -07:00
topjohnwu f5aaff2b1e Cleanup filter_out_stream implementation 2023-05-20 01:28:10 -07:00
topjohnwu 655f778171 Better cxx binding codegen 2023-05-19 15:59:40 -07:00
topjohnwu 2e77a426b2 Fix build script 2023-05-19 15:32:14 -07:00
topjohnwu 2bcf2e76f1 Generate cxx binding in build.rs 2023-05-19 15:16:54 -07:00
topjohnwu 57bd450798 Split input and output streams 2023-05-19 04:19:43 -07:00
topjohnwu 582cad1b8d Cleanup libc hacks 2023-05-19 03:23:43 -07:00
topjohnwu 6ca2a3d841 Update libsystemproperties 2023-05-19 03:22:50 -07:00
topjohnwu 91773c3311 Support only read properties from storage 2023-05-19 01:53:40 -07:00
topjohnwu dc61033b2c Support persist props bypassing property_service 2023-05-18 23:36:46 -07:00
topjohnwu f8d62a4b6c Move resetprop under core 2023-05-18 22:15:49 -07:00
topjohnwu 1d2145b1b7 Improve argument parsing and help message 2023-05-18 21:54:54 -07:00
topjohnwu 1f7f84b74a Remove unnecessary class 2023-05-18 20:38:33 -07:00
topjohnwu cd7a335d0f Cleanup implementation 2023-05-18 20:26:20 -07:00
topjohnwu 17569005a4 Remove sysprop fallback
The library now supports mapping as ro
2023-05-18 15:47:50 -07:00
topjohnwu f36b21bae5 Support get property context
Co-authored-by: canyie <a1364259@163.com>
Co-authored-by: vvb2060 <vvb2060@gmail.com>
2023-05-18 14:46:36 -07:00
topjohnwu fe1ca52f6d Simplify prop_cb 2023-05-16 02:41:39 -07:00
topjohnwu 1be647a279 Put all FFI into same module 2023-05-16 02:41:39 -07:00
topjohnwu 2ea1a47bec Fix color printing 2023-05-16 02:41:39 -07:00
Ernest 2d799dae0d Update app/src/main/res/values-lt/strings.xml
Co-authored-by: LoveSy <631499712@qq.com>
2023-05-14 23:54:57 -07:00
Ernest c6408babac Update strings.xml
Updated all strings.
2023-05-14 23:54:57 -07:00
topjohnwu a8c1ed8795 Update development docs 2023-05-13 02:38:03 -07:00
topjohnwu e3cb5f8ddd Support setting ANDROID_STUDIO 2023-05-13 02:38:03 -07:00
topjohnwu e160e211dd Format build.py with black 2023-05-13 02:38:03 -07:00
topjohnwu 22d05ca399 Update time handling code 2023-05-13 02:38:03 -07:00
vvb2060 bd2651057d Simplify prefs migration 2023-05-11 16:29:01 -07:00
topjohnwu 1610092ec4 Increase wait timeout 2023-05-10 16:13:45 -07:00
LoveSy b9e6937996 Make magisk node ro as well 2023-05-10 00:13:18 -07:00
topjohnwu a207f03952 Run tests in parallel 2023-05-10 00:10:19 -07:00
topjohnwu 851153dd7c Fix avd_test.sh 2023-05-09 23:11:11 -07:00
topjohnwu 583ffc8177 Reduce cpp logging overhead 2023-05-09 19:14:08 -07:00
topjohnwu 7518092ad2 Implement logging purely in Rust 2023-05-09 18:54:38 -07:00
topjohnwu 8c2ad3883a Update avd_magisk.sh 2023-05-09 17:33:20 -07:00
topjohnwu d364554425 Remove unused code 2023-05-06 01:48:47 -07:00
vvb2060 726ffdcd98 Fix meizu rootfs type 2023-05-06 00:06:59 -07:00
vvb2060 f9d22cf8ee New magisk tmp dir: /debug_ramdisk
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-05-06 00:04:11 -07:00
vvb2060 ee50da566f Cancel recursive bind 2023-05-06 00:04:11 -07:00
vvb2060 9f7d410959 Use pathname local socket 2023-05-06 00:04:11 -07:00
vvb2060 bc94ea4334 Update SELinux policy 2023-05-06 00:04:11 -07:00
topjohnwu c0c9204848 Add ResultExt 2023-05-05 23:57:34 -07:00
topjohnwu c0d1bf63bc Clean up logging on C++ side 2023-05-05 01:14:56 -07:00
StoyanDimitrov bbda0cdffe Update strings.xml 2023-05-05 00:39:19 -07:00
topjohnwu 7b5ff99cd1 Reorganize code 2023-05-04 21:37:08 -07:00
topjohnwu 21ddb26db8 Perform proto codegen in build script 2023-05-04 21:37:08 -07:00
LoveSy 7bf2e3875f Support extract boot image from payload.bin 2023-05-04 21:37:08 -07:00
topjohnwu b136aba1e2 Implement magiskinit logging in Rust 2023-05-02 16:49:43 -07:00
topjohnwu 0d84f80b3c Update AGP 2023-05-02 16:28:14 -07:00
topjohnwu af45aeb771 Extract busybox from APK for AVD 2023-05-02 16:28:02 -07:00
topjohnwu 1c5a435e1f Update cxx-rs 2023-05-01 14:53:07 -07:00
Soo-Hwan Na 0ea1257dcd Update Korean translation 2023-05-01 12:08:12 -07:00
Mohamadreza Nakhleh 4c92677b5a (translate)
update some Persian (Farsi,فارسی) translations and add more Persian equivalent
2023-05-01 12:07:50 -07:00
fadlyas07 979260bd62 app: l10n: Update Indonesian translations
* Added new strings based on the latest source.
* Rephrase "bisa" (informal) to "dapat" (formal).

Change-Id: I7c29951adff7e5dc086e6044571ff4cdb6366966
2023-05-01 12:07:21 -07:00
topjohnwu f7de649a36 Update ODNK requirement to r25.3 2023-04-29 15:12:04 -07:00
topjohnwu 0cf0d2b821 Move avd_hack boolean out of init class 2023-04-25 23:34:45 -07:00
vvb2060 3733c9a091 CI: add avd test 2023-04-25 23:00:59 -07:00
vvb2060 e9f32e4f68 Set text and background color 2023-04-25 23:00:59 -07:00
vvb2060 68c2817d40 Enable avd hack for debug build 2023-04-25 23:00:59 -07:00
naxitoo 83d837d868
Update/refine Spanish translations 2023-04-18 11:53:22 -04:00
I3elphegor 093eb15ee1 Update strings.xml
Incorrectly placed punctuation marks have been removed. Word order and some expressions have been corrected. The style of the menu headings and descriptions has been unified in the settings.
2023-04-18 11:22:44 -04:00
VD $ VD171 @ Priv8 c6412c1b1b
Update PORTUGUESE translation 2023-04-18 11:22:03 -04:00
serkanege 1151393d74
tr language update 2023-04-18 11:21:32 -04:00
topjohnwu 468f3efb13 Update dependencies 2023-04-13 14:19:34 -07:00
LoveSy d6b19b9d4c Upgrade gradle 2023-04-13 14:19:30 -07:00
Ilya Kushnir 709f25f600 Fix changelog index 2023-04-12 02:46:21 -07:00
topjohnwu 4b16e4b026 Update README 2023-04-11 12:51:22 -07:00
topjohnwu cdfbc02922 Release new canary build 2023-04-11 02:04:15 -07:00
topjohnwu d0c9384233 Release Magisk v26.1 2023-04-11 01:57:30 -07:00
topjohnwu 2488668b06 Add v26.1 release notes 2023-04-11 01:52:45 -07:00
LoveSy 52a98cbd51 Temp workaround for module file context 2023-04-10 19:30:37 -07:00
serkanege 1840c4c486 Update strings.xml 2023-04-10 19:30:11 -07:00
serkanege 34080f3958 Update strings.xml 2023-04-10 19:30:11 -07:00
topjohnwu e9b76b6aa5 Add monochrome adaptive icon support
Close #6867
2023-04-10 19:29:32 -07:00
Jakub K b7799b53d9 Updated Czech translation
Added missing strings and updated few.
2023-04-09 21:24:55 -07:00
Lishoo 1e206515c7 Update PL strings
Update PL strings
2023-04-08 21:15:19 -07:00
sn-o-w 6bb313184d Update Romanian 2023-04-08 21:15:02 -07:00
l3ng 2763992434
Update Azerbaijani
Co-authored-by: LoveSy <631499712@qq.com>
2023-04-08 21:14:41 -07:00
osm0sis 18fe0e6442 Fix scripts
manager.sh + boot_patch.sh:
- all listed files from boot_patch.sh header are required for boot patching, but stub.apk was being removed so install_magisk via addon.d.sh would fail without it; leave it in place

addon.d.sh:
- remove old redundant recovery_actions call (it's also performed by setup_flashable in initialize)
- print ABI to match flash_script.sh output

boot_patch.sh:
- catch and abort on any errors from ramdisk.cpio patching in the future

util_functions.sh:
- fix hiding of mount_partitions /system_root umount stderr
- quote mount_apex .pb DEST name parsing charset for safety even though both work

Fixes #6828
2023-04-08 21:13:40 -07:00
zjw a70c73bffd Fix config file path
$MAGISKTMP was redefined in commit 4e2b88b
2023-04-08 21:10:54 -07:00
topjohnwu b4ae3493a6 Use ext4 partitions for preinit first
Fix #6841, close #6847
2023-04-08 20:30:40 -07:00
残页 1a16004b20
Add help message for `magisk --preinit-device` 2023-04-08 18:32:34 -07:00
topjohnwu 56707b8119 Make FilterList more accurate 2023-04-08 18:32:00 -07:00
LoveSy c3f9533ddc Fix inconsistency of FilterableDiffObservableList
`update` should also update sublist
2023-04-08 18:32:00 -07:00
Rom 3b3abd63cc Update FR translation 2023-04-07 03:08:27 -07:00
Hen_Ry 411d3ed4e9 Update DE strings 2023-04-07 03:07:46 -07:00
LoveSy f29cc26103 Correctly get displayName of live uri 2023-04-06 02:03:09 -07:00
Ilya Kushnir 1cd595a598 Update RU strings 2023-04-06 01:10:48 -07:00
topjohnwu 22e023b58d Set notes on main thread 2023-04-06 00:53:06 -07:00
topjohnwu 7be958e35d Fix crash when revoke root permission 2023-04-06 00:40:26 -07:00
topjohnwu 69b66ef637 Make core package more self contained 2023-04-05 23:04:33 -07:00
topjohnwu daf8653c38 Release new canary build 2023-04-05 11:13:51 -07:00
topjohnwu e2545e57cf Update README badges 2023-04-05 11:07:40 -07:00
topjohnwu 7cb0909c70 Release Magisk v26.0 2023-04-05 11:02:17 -07:00
topjohnwu cc5ff36165 Revert "Cancel recursive bind"
This reverts commit a18a440236.
2023-04-05 10:47:13 -07:00
topjohnwu 18b1ef6c29 Only restore JNIEnv in constructor 2023-04-05 10:13:50 -07:00
LoveSy 7fe012347a Restore JNIEnv in advance for clean env to modules 2023-04-05 09:29:59 -07:00
vvb2060 5c165c9bb0 Fix avd hack 2023-04-05 04:01:32 -07:00
topjohnwu 6c3519923d Make things more obvious 2023-04-05 04:01:07 -07:00
topjohnwu 9ea859810d Update api.hpp copyright notice 2023-04-05 01:54:56 -07:00
LoveSy 8dae7b5451
Update installation guide 2023-04-05 01:50:45 -07:00
vvb2060 f827755aaf Skip getSessionInfo 2023-04-05 01:46:33 -07:00
topjohnwu 637a8af234 Add v26.0 release notes 2023-04-05 01:42:42 -07:00
LoveSy b0fc580860 Avoid crash when calling `abandonSession` 2023-04-05 00:36:51 -07:00
vvb2060 9279f30e89 Upload mapping and native debug symbols 2023-04-05 00:14:51 -07:00
LoveSy b505819ca2 Fix a typo 2023-04-04 12:28:08 -07:00
topjohnwu 39d1d23909 Release new canary build 2023-04-04 03:00:50 -07:00
vvb2060 69529ac59c Fix restorecon 2023-04-04 02:34:16 -07:00
vvb2060 a18a440236 Cancel recursive bind 2023-04-04 02:12:07 -07:00
LoveSy aa7846c1c0 No need to mount ROOTMIR if tmp_dir != /sbin 2023-04-04 02:12:07 -07:00
topjohnwu 24ba4ab95b Better AVD support 2023-04-04 02:04:49 -07:00
topjohnwu 762b70ba9d Better string split implementation 2023-04-03 18:50:36 -07:00
topjohnwu 41b77e4f25 Make base as template argument for optimization 2023-04-03 18:32:11 -07:00
topjohnwu 2087e47300 Get random separately 2023-04-03 18:32:11 -07:00
vvb2060 46ce765860 Use stable random seed 2023-04-03 18:32:11 -07:00
topjohnwu 5117dc1a31 Reorganize code 2023-04-03 17:47:07 -07:00
Arbri çoçka 620fd7d124 Update sq strings.xml 2023-04-03 16:54:11 -07:00
kubalav 3e991dc003 Update Slovak translation 2023-04-03 16:53:59 -07:00
LoveSy 15cab86152 Make module mirror read only 2023-04-02 22:03:02 -07:00
LoveSy aa785b5845 Show confirm dialog when installing local module
It can avoid miss click

Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
2023-04-02 21:33:13 -07:00
LoveSy 97731a519a Update zygisk API to avoid mem leak 2023-04-02 03:54:33 -07:00
残页 b696dae808
Specify foregroundServiceType for DownloadService 2023-04-02 02:20:49 -07:00
topjohnwu 732a8260c2 Update dependencies 2023-04-02 02:13:47 -07:00
LoveSy 4ff60ef9a9 No more patching libc.a on 64bit platforms 2023-04-01 03:51:57 -07:00
topjohnwu 23b1b69110 Consolidate zygisk cleanup routines 2023-04-01 03:37:17 -07:00
LoveSy 3a4fe53f27 New way to unload zygisk
Co-authored-by: 残页 <a1364259@163.com>
2023-04-01 03:37:17 -07:00
LoveSy e48afff5e8 Compress jniLibs 2023-04-01 01:54:10 -07:00
topjohnwu 3f4f4598e8 Better AVD support 2023-03-31 17:21:12 -07:00
LoveSy 3921e9cb1b Fix release build 2023-03-31 00:06:55 -07:00
topjohnwu 0b987dd0b0 Cleanup more databinding implementation 2023-03-31 00:05:33 -07:00
Ilya Kushnir 1620e15f99 Update RU strings 2023-03-30 13:45:13 -07:00
topjohnwu b089511e91 Update Android Studio 2023-03-30 13:35:13 -07:00
Arbri çoçka 958788c1aa
Update Albania 2023-03-30 13:33:26 -07:00
LoveSy b5a8a27296 Update Chinese translation 2023-03-30 13:33:04 -07:00
kubalav 98123775ad Update Slovak translation 2023-03-30 13:32:40 -07:00
Thonsi c7133974be Clean up some codes 2023-03-26 20:48:23 -07:00
LoveSy 04324a7ebe Upgrade LSPlt to bypass a bionic bug 2023-03-23 20:56:35 -07:00
vvb2060 f54daa3469 Force ramdisk format to lz4_legacy for v4 2023-03-23 20:50:27 -07:00
LoveSy 07c22ccd39 Use app_dir to detect systemui instead
uid may be shared by other apps (e.g., in MIUI, systemui's uid
is 1000 and shared by many system apps).
2023-03-23 16:17:13 -07:00
LoveSy e893c13cf1 Unlink preinit device if bind mount fails 2023-03-23 00:40:13 -07:00
LoveSy dba5020e4f Refactor magiskrc 2023-03-22 17:53:15 -07:00
LoveSy 87e036a190 Update LSPlt to avoid crash when hooking libc 2023-03-22 12:02:33 -07:00
LoveSy 3dd94672b0 Fix preinit scripts 2023-03-22 03:07:34 -07:00
LoveSy 004b193f69 Fix installation crash 2023-03-22 02:44:14 -07:00
topjohnwu 4417997749 Make sure ro mmap region is not overwritten 2023-03-21 15:50:43 -07:00
LoveSy 2eef542054 Add amonet microloader support 2023-03-21 15:50:43 -07:00
LoveSy a07d4080b6 Upgrade termux-elf-cleaner to strip DT_PREINIT_ARRAY 2023-03-21 02:17:21 -07:00
LoveSy b9d0a3b3d4 Use partition name or devpath's name 2023-03-21 00:40:11 -07:00
topjohnwu 76405bd984 Add more comments 2023-03-21 00:40:11 -07:00
topjohnwu 4e2b88b3d0 Rename rules to preinit
It is possible that we will allow more preinit files for modules.
Rename the partition and folders from rules to preinit.
2023-03-21 00:40:11 -07:00
LoveSy 7048aa1014 Rename sepolicy.rules -> rules 2023-03-21 00:40:11 -07:00
LoveSy 1c2fcd14b5 Mount sepolicy.rules for migration 2023-03-21 00:40:11 -07:00
vvb2060 84e1bd7bc3 Refactor sepolicy.rules resolve app 2023-03-21 00:40:11 -07:00
vvb2060 362eea741f Refactor sepolicy.rules resolve native
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-03-21 00:40:11 -07:00
LoveSy 4de93cfd4b Use RV to show Magisk logs 2023-03-19 23:47:29 -07:00
LoveSy 03cee0b8d4 Remove meaningless umount in magiskinit
This is no longer required since we redirect to /data/magiskinit
2023-03-19 23:35:18 -07:00
LoveSy 54ecc001f4 Clean up more codes 2023-03-19 23:20:19 -07:00
LoveSy 5c325d9466 Fix su log with long command 2023-03-19 23:20:04 -07:00
topjohnwu 0e851cdcf8 Always fetch network state on process onStart 2023-03-19 04:11:59 -07:00
topjohnwu af054e4e31 Bump minSdk to 23 2023-03-17 04:24:26 -07:00
Chris Renshaw 33fb4653f0 Sanitize any bad chars from mount_apex apex_manifest.pb string parsing
For example, Lineage's com.android.ondevicepersonalization apex_manifest.pb has a # char, which strings keeps in its output, and breaks the mount for that apex before this fix
2023-03-17 02:44:36 -07:00
LoveSy d9f0aed571 Fix unpack -n and repack -n of ZIMAGE kernel 2023-03-17 02:00:46 -07:00
LoveSy 98813c24fb Drop trailing garbage of gzip decompress
I previously refered to minigzip from libz which copies all trailing
data to the output when decompressing. However, gzip, on the other
hand, drop trailing garbage by default. Consider ZIMAGE append
the kernel size with zero padding, we should drop trailing garbage
as well.
2023-03-17 02:00:25 -07:00
topjohnwu 3cc81bb3fd Cleanup ObservableList implementation 2023-03-17 01:40:28 -07:00
topjohnwu 366dd52419 Update AGP 2023-03-16 04:18:03 -07:00
topjohnwu fe6b658c02 Use MenuProvider 2023-03-09 18:05:37 -08:00
LoveSy 3cf66d1c57 Fix currentFocus
Looks like currentFocus does not always exist even after setContentView,
so I hereby use another way to check if setContentView is called
2023-03-09 17:52:38 -08:00
topjohnwu 382568bd3c Cleanup filterable list implementation 2023-03-09 17:45:00 -08:00
LoveSy d130aa02a1
Do not always create new adapter 2023-03-09 16:00:08 -08:00
LoveSy 1a1646795f Support `untrusted_app_32` 2023-03-09 02:17:30 -08:00
LoveSy d52ea1b068
Postpone `showMainUI` when activity has stopped
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-03-07 21:04:04 -08:00
LoveSy e14f7b6908 No KeyDispatch or OnBackPress when no currentFocus 2023-03-07 20:20:35 -08:00
南宫雪珊 4709a32641
Fix mkdir
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-03-07 01:47:26 -08:00
topjohnwu 71b7f52663 Release new canary build 2023-03-06 05:32:08 -08:00
LoveSy 981ccabbef No support for partitions w/o symlink in /system 2023-03-06 05:23:40 -08:00
vvb2060 9e07eb592c Fix make private before remount 2023-03-06 05:18:16 -08:00
LoveSy 9555380818 Replace parse_mnt with parse_mount_info 2023-03-06 05:09:12 -08:00
topjohnwu f80d5d858e Update AGP 2023-03-06 05:08:58 -08:00
topjohnwu a1ce6f5f12 Fix race condition when switching root manager
Before this change, the root manager package name is only written into
the database after the repackaged APK is installed. In the time between
the repackaged APK being installed and the package name being written
into the database, if some operation calls `get_manager`, the Magisk
daemon will cache this result and ignore the repackaged APK, even if
the package name is set afterwards, because the cache won't be
invalidated. The result is that the repackaged manager APK will not be
recognized as the root manager, breaking the hide manager feature.

This race condition is more likely to happen when Zygisk is enabled,
because `get_manager` is called with a very high frequency in that case.

To fix the issue, we have to set the new package name into the database
BEFORE installing the repackaged APK. We also stop pruning the
database if the repackaged manager is not found, moving this logic into
the Magisk app. By doing so, we can guarantee that the instant after
the repackaged manager APK is installed, the Magisk daemon will
immediately pick it up and treat it as the root manager.

Another small optimization: when the requester is root, simply bypass
the whole database + manager package check. Since the Magisk app hiding
APK installation proces will call `su` several times to run `pm` under
different UIDs, doing this opimization will reduce the amount of
unnecessary database query + filesystem traversals.
2023-03-06 03:58:58 -08:00
LoveSy 1aade8f8a8 No greedy match to find parent mount point
This fixes /sys is considered as a parent mount point of /system
2023-03-03 11:09:03 -08:00
LoveSy b9213b7043 Remove redundant stub.apk 2023-03-03 11:07:36 -08:00
LoveSy 4af72324f4 Fix gradle cache 2023-03-03 02:39:22 -08:00
LoveSy b6ea5b8984 Fix SYSTEM_ROOT not passed to boot patch 2023-03-02 23:39:50 -08:00
topjohnwu c279e08c88 Release new canary build 2023-03-02 21:35:14 -08:00
topjohnwu 2717feac21 Fix stub APK building in app 2023-03-02 21:27:48 -08:00
topjohnwu 8adf27859d Build script adjustments 2023-03-02 20:32:46 -08:00
LoveSy 307cf87215 Fix build script 2023-03-02 20:32:46 -08:00
Takeda-senpai ca31412c05 Update strings.xml 2023-03-02 14:34:08 -08:00
LoveSy f59fbd5dca Filter duplicate mount points
This prevents umounting existing overlay mount points
2023-03-02 14:33:50 -08:00
topjohnwu 2285f5e888 Fix build script 2023-03-02 03:02:10 -08:00
LoveSy da36e5bcd5 Make worker private 2023-03-02 02:25:44 -08:00
Prithvi 4ed9f57fdc
Update ota.md (#6374)
Specified the steps of the OTA install process so there is less confusion about what step 1 and 2 entail.
2023-03-02 02:23:19 -08:00
Daki Carnhof ea7be6162f install.md: Mention Heimdall beside Odin
Proven to work with Magisk 25.2, LineageOS 18.1, SM-A520F.
2023-03-02 02:21:14 -08:00
南宫雪珊 3726eb6032
Deny init relabel to adb_data_file
Co-authored-by: 残页 <a1364259@163.com>
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-03-02 02:20:38 -08:00
vvb2060 6e918ffd68 Remove fetchCustomUpdate 2023-03-02 02:05:38 -08:00
vvb2060 4772868d6a Move REMOUNT_ROOT 2023-03-02 02:05:38 -08:00
vvb2060 78df677a42 Use /data/adb/modules directly 2023-03-02 02:05:38 -08:00
vvb2060 85a4b249b3 Skip copy old rule 2023-03-02 02:05:38 -08:00
vvb2060 d06e9a0b51 Allow R8 to delete fetchCanary 2023-03-02 02:05:38 -08:00
vvb2060 5eb774a2ad Fix typo 2023-03-02 02:05:38 -08:00
topjohnwu cbbbbab483 Release new canary build 2023-02-27 23:16:54 -08:00
LoveSy e5641d5bdb Fix avd-magisk 2023-02-27 23:00:42 -08:00
topjohnwu a721206c6f Update items in the right thread 2023-02-27 23:00:21 -08:00
LoveSy c7a27481f9 Update proguard rules to fix module page crash 2023-02-27 20:50:24 -08:00
LoveSy 594c304634 Fix release build 2023-02-26 22:35:20 -08:00
topjohnwu d0ec387c28 Release new canary build 2023-02-26 15:06:33 -08:00
vvb2060 7dbfba76bf Umount by tmpfs id 2023-02-26 14:23:11 -08:00
vvb2060 2a4aa95a6f Identify tmpfs used by magisk 2023-02-26 14:23:11 -08:00
vvb2060 5520f0fbf7 Add stub version to apk comment 2023-02-26 14:23:02 -08:00
LoveSy a1a87c9956 Get rid of vtable hook 2023-02-26 04:59:21 -08:00
vvb2060 2c53356bfd Remove unexpected files 2023-02-26 03:47:38 -08:00
topjohnwu 85d9756f62 Update Zygisk API documentation 2023-02-26 03:16:13 -08:00
LoveSy 79586ece4c Update install.md 2023-02-26 01:17:11 -08:00
AndroPlus 6851d11a8e Update Japanese translation 2023-02-26 01:16:23 -08:00
LoveSy 996a857096 Upgrade kotlin 2023-02-26 01:15:32 -08:00
LoveSy d7158131e4 No need to manually parse mount flags 2023-02-26 01:15:18 -08:00
topjohnwu 3d3082bc82 Minor optimizations 2023-02-26 01:14:10 -08:00
topjohnwu 744ebca206 Don't let inter_node upgrade to module_node 2023-02-25 22:05:50 -08:00
topjohnwu 92077ebe53 Refactor module and node implementation 2023-02-25 18:19:46 -08:00
LoveSy 78ca682bc5 Always mount tmpfs for dirs
https://android-review.googlesource.com/c/platform/system/core/+/928592
2023-02-25 18:19:46 -08:00
LoveSy af01a36296 Refactor magic mount to support overlayfs
Previously, magic mount creates its own mirror devices and mount
mirror mount points. With these mirror mount points, magic mount
can get the original files and directory trees. However, some
devices use overlayfs to modify some mount points, and thus after
magic mount, the overlayed files are missing because the mirror
mount points do not contain the overlayed files. To address this
issue and make magic mount more compatible, this patch refactors
how magic mount works.

The new workflows are as follows:
1. make MAGISKTMP a private mount point so that we can create the
   private mount points there
2. for mirror mount points, we instead of creating our own mirror
   devices and mount the mirror mount points, we "copy" the
   original mount points by recursively mounting /
3. to prevent magic mount affecting the mirror mount points, we
   recursively set the mirror mount points private
4. to trace the mount points we created for reverting mounts, we
   again make the mirror mount points shared, and by this way we
   create a new peer group for each mirror mount points
5. as for tracing the newly created tmpfs mount point by magic
   mount, we create a dedicated tmpfs mount point for them, namely
   worker mount point, and obviously, it is shared as in a newly
   created peer group for tracing
6. when reverting mount points by magic mount, we can then trace
   the peer group id and unmount the mount points whose peer group
   ids are created by us

The advantages are as follows:
1. it is more compatible, (e.g., with overlayfs, fix #2359)
2. it can mount more partitions for which previous implementation
   cannot create mirror mount points (fix #3338)
2023-02-25 18:19:46 -08:00
LoveSy 97ed1b16d0 Fix gzip decompression 2023-02-20 18:26:04 -08:00
LoveSy 508a001753 Remove obsolete link 2023-02-20 17:25:32 -08:00
vvb2060 c1909d520b Fix gradle build cache 2023-02-20 01:23:56 -08:00
topjohnwu 9b1e173373 Update AGP 2023-02-20 01:03:35 -08:00
LoveSy 4ba365565f Upgrade gradle 2023-02-20 00:08:23 -08:00
残页 ae34659b26
No kernel repack if it isn't patched at all
It turns out that decompressing and recompressing the kernel is enough to break booting on many devices that use MT6763.
Fix #5124, fix #6204, fix #6566 

Co-authored-by: LoveSy <shana@zju.edu.cn>
Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
2023-02-17 15:00:14 -08:00
LoveSy 79a85f5937 Use mountinfo for revert unmount 2023-02-17 12:36:19 -08:00
LoveSy b249832571 Use statfs to check rootfs
This helps support adb remount
2023-02-12 22:49:27 -08:00
LoveSy 577b5912af Fix SKIP_FD_SANITIZATION false positive
Fix #6523
2023-02-12 00:40:09 -08:00
LoveSy 9e8c68af12
Refactor sepolicy.rules resolve
We resolve available partitions for sepolicy.rules when patching
boot and bind mount the partition by magiskinit.

For older devices, the previous logic won't work because the part name
is never readable.

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-02-12 00:36:38 -08:00
shìwēi nguyen 03418ddcbf
run module uninstall.sh on Magisk uninstallation 2023-02-09 20:36:58 -08:00
LoveSy 220a1c84ce Zygisk v4 module's plt commit should not use regex 2023-02-09 20:19:53 -08:00
南宫雪珊 9a4458ffac
Update appcompat 2023-02-09 20:13:40 -08:00
vvb2060 7a9e6d2ad2 Remove unexpected /sbin/overlay.d 2023-02-09 20:08:59 -08:00
LoveSy 9656cf2f86 Refine 2023-02-09 20:08:44 -08:00
BlackMesa123 584bad5314 Add `init_boot.img` patching for Samsung tar firmware packages
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
2023-02-09 20:08:44 -08:00
topjohnwu 459088024f Update dependencies 2023-02-09 19:53:40 -08:00
Chris Renshaw d740bbe058 Ignore AMLogic "normal" slot suffix in scripts as well
Fixes #6572
2023-02-03 10:44:43 -08:00
canyie 6ecc04a4df Fix auto install stub 2023-01-23 01:55:28 +08:00
canyie 15a7e9af57 Fix ResourcesProvider being closed 2023-01-20 19:32:22 +08:00
LoveSy 0329f00129 Upgrade LSPlt
Fix #6533
2023-01-20 19:29:55 +08:00
topjohnwu cd8a2edefb Reduce unnecessary formatting 2023-01-20 14:41:34 +08:00
LoveSy 4318ab5cd2 Reuse tmpfs for magic mount
As we already have a tmpfs (magisktmp), we can reuse them for
magic mount
2023-01-20 03:49:40 +08:00
topjohnwu 3517e6d752 Handle nullptr char* in Rust 2023-01-20 03:45:16 +08:00
LoveSy 67845f9c21
Clear sepolicy rules when disable/remove modules
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-01-19 04:25:44 +08:00
Kian-Meng Ang f562710438 Fix typos
Found via `codespell -S *.xml,*.kt,./native/src/external -L crate,bu`
2023-01-18 16:35:37 +08:00
vvb2060 e836909c50 umount old hijack binary 2023-01-18 13:06:17 +08:00
vvb2060 7769ba5f54 Remove READ_EXTERNAL_STORAGE permission added by AGP 1.8 2023-01-18 12:52:24 +08:00
topjohnwu 7fe9db90a1 Update AGP 2023-01-18 12:52:04 +08:00
topjohnwu 8f7d6dfb77 Cleanup unused functions 2023-01-12 23:17:41 +08:00
canyie 2839978cc1 Use null oat dir in root service only 2023-01-12 22:38:14 +08:00
canyie e73f87b758 Update LSPlt 2023-01-12 01:18:56 +08:00
canyie bd0409fd15 Fix busybox filename 2023-01-12 01:18:41 +08:00
canyie babdfe80cb Fix stub resources load on Android 5 2023-01-12 01:12:56 +08:00
topjohnwu 636223b289 Cleanup APIs 2023-01-10 02:42:45 +08:00
LoveSy aa0a2f77cf Add inode plt hook APIs 2023-01-10 02:42:45 +08:00
topjohnwu e38f35eab2 Update libsu
Fix #6488
2023-01-09 03:16:11 +08:00
canyie cb39514705 Fix NotificationService implementation
- Fix #6385. (Maybe the reason is, the call to stopForeground() with STOP_FOREGROUND_DETACH ensures the notification is shown so it reposts the notification?)
- Use FOREGROUND_SERVICE_IMMEDIATE on Android 12+ to make sure the downloading notification always shows immediately
2023-01-09 00:10:52 +08:00
topjohnwu 78a444d601 Wait for root service to bind 2022-12-30 15:52:41 -08:00
LoveSy 37b81ad1f6 Refine module preparation return value 2022-12-26 17:17:41 -08:00
vvb2060 7871c2f595 Update deps 2022-12-26 17:16:26 -08:00
topjohnwu 57d83635c6 Check stub.xz existence 2022-12-26 16:07:04 -08:00
topjohnwu 76fbf4634a Update scripts 2022-12-26 16:07:04 -08:00
topjohnwu 7ce4bd3330 Copy stub APK into output directory 2022-12-26 16:07:04 -08:00
vvb2060 ad0e6511e1 Stop embedding stub.apk in magiskinit 2022-12-26 16:07:04 -08:00
vvb2060 a4a734458b Fix network capabilities 2022-12-26 13:48:17 -08:00
Brian Kepha f989756b93
Added Swahili Translation 2022-12-26 03:30:44 -08:00
LoveSy 5763a3d908
Support replacing existing .rc by overlay.d
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2022-12-26 03:28:10 -08:00
topjohnwu 1b745ae1a0 Use latest build tools 2022-12-26 02:02:21 -08:00
topjohnwu b6d50bea2c Release new canary build 2022-12-26 00:33:02 -08:00
topjohnwu 831a398bf1 Check Python 3.8+ 2022-12-26 00:09:27 -08:00
topjohnwu a848783b97 Guard boot stages more precisely
Close #6468, fix #6148
2022-12-26 00:04:58 -08:00
LoveSy 4d876f0145 Support detecting safemode by ro.sys.safemode 2022-12-24 15:16:53 -08:00
LoveSy bdfedea4e0 Close missing fd
Fix #6463
2022-12-24 15:16:27 -08:00
LoveSy ea0e3a09ef Update install.md 2022-12-23 17:21:44 -08:00
topjohnwu dadae20960 Remove unused implementations 2022-12-23 17:20:39 -08:00
LoveSy 4ed34cd648 Eliminate unnecessarily copy on magiskinit
This patch reuses the abused /data tmpfs for magisktmp
2022-12-23 17:03:16 -08:00
osm0sis 0d38c94c9c scripts: fix root loss until reboot after Magisk addon.d-v2
- /system/bin/su was being removed in error from the live system so update remove_system_su to be aware of a running A/B OTA and generalize/simplify removal logic with relative paths to correctly run on the updated system slot
2022-12-23 16:36:44 -08:00
vvb2060 2a2a452bd4 CI uses java 17 2022-12-13 14:13:25 -08:00
vvb2060 13c2695e98 simplify denylist rm 2022-12-13 14:11:32 -08:00
fadlyas07 3ff60ed49f app: l10n: Update Indonesian translations
* Added new strings based on the recent source.

Change-Id: I40d6e0374a0356d93c61acace7ab48c3649e85e8
2022-12-13 14:05:54 -08:00
VD $ VD171 @ Priv8 bbb1786ec3 Fix typo PT translation
Fix typo PT translation
2022-12-13 14:05:40 -08:00
Davy Defaud 4bfd2dac54 Fix gender of ”install” as an adjective in French
In French, install as an adjective depends on the gender of its related noun. For instance, “Magisk installed” is translated “Magisk installé” (masculine form), whereas “Application installed” is translated “Application installée” (feminine form).
By using “Version installée”, “installed” is related to “version” which is feminine. For consistency of the GUI, I’m also changing "home_latest_version" string to ”Dernière version” instead of “Dernière”.
2022-12-13 14:05:24 -08:00
ysard 857c12372a Update French translations
Just a misspelling fix
2022-12-13 14:05:09 -08:00
残页 33f5154269
Inject binaries into /system if sbin not accessible
Some Android 11+ devices have the /sbin partition but not accessible by the global shell (`PATH` doesn't contain `/sbin`). Not only custom ROMs but also some stock ROMs have the same behavior so I believe it is something we need to deal with.
Fix #6427, fix #4309, fix #5728, fix #3593
2022-12-13 13:54:55 -08:00
topjohnwu ed37ddd570 Stricter validation 2022-11-22 14:47:37 -08:00
LoveSy cd5384f13e Fix crashes whenever a zygisk module has ver > 4 2022-11-22 14:47:37 -08:00
LoveSy 11b2ddbad8
Fix zygisk v4 ApiTable abi
Also refactor some code to let the compiler check the abi

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2022-11-22 11:49:31 -08:00
topjohnwu cf9957ce4d Properly detect SysUI appId
Fix #6322
2022-11-01 02:04:50 -07:00
topjohnwu 44643ad7b3 Restrict pointer aliasing
Close #6354, close #6353
2022-10-31 16:35:33 -07:00
topjohnwu 1e53a5555e Update AGP 2022-10-31 16:00:42 -07:00
topjohnwu 616adc22e1 Support Linux < 3.6 2022-10-31 16:00:42 -07:00
akhilkedia 916e373edb Update README.md to specify python version 3.8+
Current readme suggests python 3.6+
However, the file `build.py` on executing `build.py ndk` runs the command `shutil.copytree(src_dir, lib_dir, copy_function=cp, dirs_exist_ok=True)`
This command errors out on python 3.7, because the `dirs_exist_ok` parameter is new in Python 3.8 (https://docs.python.org/3/library/shutil.html#shutil.copytree)

So the README should suggest python 3.8+
2022-10-20 17:21:08 -07:00
Hen_Ry 021ae15395
Update German translation 2022-10-12 13:08:12 -07:00
vvb2060 52cf72002a Update resources load
addAssetPath supports apk and directory
2022-10-12 13:07:30 -07:00
topjohnwu 68874bf571 Release new canary build 2022-10-11 13:33:08 -07:00
残页 a468fd946d Fix #6314 2022-10-11 13:01:34 -07:00
topjohnwu e327565434 Release new canary build 2022-10-10 21:44:32 -07:00
topjohnwu c3b4678f6e Properly detect SysUI 2022-10-10 21:28:13 -07:00
vvb2060 978216eade local module: filter hidden dirs 2022-10-03 14:20:09 -07:00
残页 44cfe94e4d
Always cleanup init LD_PRELOAD hooks
Fix #6296
2022-10-03 08:26:33 -07:00
Nitrovenom f9e82c9e8a Update Bengali translation 2022-09-26 23:11:00 -07:00
theunknownKiran 25b4b107d3 Create strings.xml
Added Malayalam translation
2022-09-26 23:10:48 -07:00
theunknownKiran db651fa9ec Create strings.xml
Added Malayalam translation
2022-09-26 23:10:34 -07:00
LoveSy 23ad611566 Use sccache for rust build 2022-09-26 01:35:52 +02:00
topjohnwu 095d821240 Don't use xopen in readlink 2022-09-25 16:35:28 -07:00
topjohnwu e23f23a8b7 Update AGP 2022-09-21 03:09:53 +02:00
topjohnwu 48f829b76e Minor refactoring 2022-09-21 03:09:46 +02:00
topjohnwu 0b82fe197c Update avd_test.sh 2022-09-15 19:45:58 -07:00
topjohnwu af99c1b843 Don't crash when nullptr paired with len = 0 2022-09-15 16:56:22 -07:00
topjohnwu c6646efe68 Move all xwrap to Rust 2022-09-15 01:17:05 -07:00
Nitrovenom 66a7ef5615 Update Bengali translation 2022-09-13 04:30:01 -07:00
canyie 9474750bdf Close fd of erroneous daemon socket connections 2022-09-13 04:29:29 -07:00
LoveSy e86db0bd61 Reset stack guard after fork from Zygote 2022-09-13 04:18:34 -07:00
topjohnwu a29fc11798 Update libsu
Fix #6255
2022-09-13 04:17:19 -07:00
topjohnwu a66a3b7438 Make sure logs are always ended with newline 2022-09-09 04:29:50 -07:00
topjohnwu 44029875a6 Add new API exemptFd 2022-09-09 03:27:19 -07:00
topjohnwu ccf21b0992 Zygisk code refactor 2022-09-07 13:48:20 -07:00
topjohnwu 4e14dab60a Specialize does not need to close logd_fd 2022-09-06 03:01:39 -07:00
topjohnwu 6e299018a4 Preserve logd_fd after specialization
Also add more comments regarding FD checks
2022-09-02 01:49:17 -07:00
topjohnwu 555a54ec53 Avoid doing any unmounts for SysUI 2022-08-31 00:15:15 -07:00
topjohnwu 1565bf5442 Make Zygisk API 0BSD 2022-08-30 01:58:40 -07:00
topjohnwu 14b830027b Cleanup zygisk headers 2022-08-30 01:40:14 -07:00
topjohnwu 38325e708e Make private applets hidden 2022-08-27 14:50:28 -07:00
topjohnwu 646260ad6d Fix typo 2022-08-27 05:27:23 -07:00
topjohnwu d1d26f4481 Fix building individual applet 2022-08-27 05:06:28 -07:00
topjohnwu 357d913f18 Dynamically generate component names at runtime 2022-08-26 06:31:51 -07:00
topjohnwu 71b0c8b42b Make stub patching 100% offline 2022-08-26 06:31:51 -07:00
topjohnwu cdc66c1ac8 Move patching logic out of AXML 2022-08-26 06:31:51 -07:00
topjohnwu e9af773901 Remove usage of AndroidX CoreComponentFactory 2022-08-26 06:31:51 -07:00
Rom eadf6e8b96 Update French translation 2022-08-25 02:27:35 -07:00
topjohnwu 87bec70d9f Setup notification on app init
Close #6216
2022-08-25 02:24:30 -07:00
Ilya Kushnir 3668b28f62 Update RU strings 2022-08-24 03:20:55 -07:00
Arbri çoçka 933e4bd163 update Albania strings 2022-08-24 03:20:33 -07:00
vvb2060 e3ab9e9a1e Update zh-rCN translation 2022-08-24 03:20:19 -07:00
VD $ VD171 @ Priv8 58ad2c1416
Update Portuguese & Portuguese Brazilian Translations 2022-08-24 03:19:31 -07:00
kubalav c5291ad33b Update Slovak translation 2022-08-24 03:19:09 -07:00
vvb2060 77d8445bfd Avoid hardcode package name 2022-08-24 03:18:55 -07:00
topjohnwu f8395a7dc6 Make sure UI and state is in sync 2022-08-23 05:38:19 -07:00
topjohnwu 727c70005e Release new canary build 2022-08-23 05:20:44 -07:00
topjohnwu 38ab6858f0 Do not crash root service in stub 2022-08-23 05:10:18 -07:00
topjohnwu a54114f149 Pre-grant permissions if possible 2022-08-23 05:09:50 -07:00
topjohnwu 7a4a5c8992 Ensure JobService is rescheduled 2022-08-23 04:14:06 -07:00
topjohnwu 928a16d8cc Update app to target API 33
Close #6206
2022-08-23 03:59:09 -07:00
topjohnwu 3f7f6e619a Use VERSION_CODES instead of raw numbers 2022-08-23 01:43:53 -07:00
vvb2060 c2f96975ce Pre grant as much as possible 2022-08-23 01:33:10 -07:00
vvb2060 8bd4760b00 Fix build 2022-08-23 01:32:32 -07:00
vvb2060 4f4aeb893d Update deps 2022-08-23 01:31:48 -07:00
canyie fed4f1b50f Collect kernel version and environment variables in log 2022-08-22 12:52:13 -07:00
vvb2060 e11087cd1a Update kotlin R8 rules 2022-08-22 12:51:54 -07:00
南宫雪珊 e6eb51551c
Fix ci text color 2022-08-22 12:51:36 -07:00
topjohnwu c5c608f0d3 Release new canary build 2022-08-20 18:31:18 -07:00
topjohnwu 4737c5117a Update to ONDK r25.2 2022-08-19 16:26:25 -07:00
topjohnwu 9806b38d8e Introduce zygisk loader
Use a separate library for 1st stage
2022-08-19 04:49:19 -07:00
topjohnwu 6bfe34e5a8 Support testing magiskinit on arm64 AVD 2022-08-19 02:40:33 -07:00
topjohnwu 34dd9eb7d6 More Rust 2022-08-19 02:21:52 -07:00
topjohnwu 2d8beabbd4 Better build script 2022-08-17 01:59:23 -07:00
topjohnwu 4d9b7e7114 More Rust 2022-08-15 11:53:51 -07:00
topjohnwu 40aab13601 Make IDE recognize we are targeting Android 2022-08-09 14:09:39 -07:00
topjohnwu 4c0f72f68f Move part of libbase to Rust 2022-08-08 22:53:37 -07:00
vvb2060 dd565a11ea Fix outdated state 2022-08-08 13:59:56 -07:00
残页 1735a713cb
Use `ANDROID_DLEXT_FORCE_LOAD` to load second stage if possible
Fix #6095
2022-08-08 02:43:19 -07:00
残页 52ba6d11bc
Don't let remote errors crash Zygisk
Fix #6095
2022-08-07 05:09:46 -07:00
topjohnwu 7357a35f8d Fix build errors 2022-08-07 05:03:18 -07:00
Acetylcholine aeb7fd7cb3
Fix denylist add_list
Signed-off-by: ACh Sulfate <xenonhydride@gmail.com>
Co-authored-by: John Wu <topjohnwu@gmail.com>
2022-08-07 04:48:47 -07:00
topjohnwu 1b4a6850b8 Ensure parent folders exist before extract 2022-08-07 04:06:18 -07:00
Cristian Silaghi 07b45f39df Update Romanian 2022-08-07 03:38:57 -07:00
canyie 1d0b873950 Fix sepolicy `attribute` rule parsing
Fix #6166
2022-08-07 03:35:50 -07:00
topjohnwu d449f49d73 Update AGP 2022-08-05 15:47:46 -07:00
canyie e8787b5cfd Fix UB when remote process died
If remote process died, `xreadlink` fails and leaves `buf` uninitialized. Then the daemon calls `str_ends`, creates a temp `std::string_view` with the uninitialized buffer and undefined behavior occurs.
2022-08-02 12:54:54 -07:00
topjohnwu d17ed2b979 Support patching AVD with release builds 2022-08-02 03:37:04 -07:00
topjohnwu b496923cbb Update Cargo.toml 2022-07-24 06:14:49 -07:00
topjohnwu 759d196aad Update cxx.rs 2022-07-24 05:45:23 -07:00
topjohnwu a7ab8216ce Proper build scripts 2022-07-24 05:39:14 -07:00
topjohnwu b9e89a1a2d Restructure the native module
Consolidate all code into the src folder
2022-07-23 13:51:56 -07:00
vvb2060 c7c9fb9576 Restore context before copy
fix magiskpolicy context
2022-07-23 03:57:43 -07:00
vvb2060 8b095de04d Fix app_zygote context 2022-07-23 03:14:44 -07:00
vvb2060 468325b51a Fix CRLF 2022-07-23 03:14:44 -07:00
gidano e5058bfb8b
Hungarian translation 2022-07-23 02:43:00 -07:00
vvb2060 d4b9ef736d Check magisk32 exists 2022-07-23 02:41:36 -07:00
vvb2060 00d3cb0908 magisk_node: check target exists 2022-07-23 02:40:13 -07:00
vvb2060 d35072d4e6 Match app_zygote by context 2022-07-23 02:39:28 -07:00
canyie 1a964e78dd Support 32-bit emulators
This would be helpful when debugging arch-related bugs, there is no reason to prevent it.
2022-07-23 02:27:50 -07:00
topjohnwu 4264ae49c0 Format with rustfmt 2022-07-22 03:56:09 -07:00
topjohnwu f08712cd0a Update to ONDK r25.1 2022-07-22 03:56:09 -07:00
LoveSy 3906fe75dc Clean up code 2022-07-21 00:52:28 -07:00
topjohnwu 2497e548c9 Update to ONDK r25.0 2022-07-21 00:07:09 -07:00
topjohnwu e4635684e9 Release new canary build 2022-07-20 20:21:49 -07:00
topjohnwu 9b61bdfc9a Update README 2022-07-20 20:17:02 -07:00
topjohnwu 6066b5cf86 Release Magisk v25.2 2022-07-20 20:13:26 -07:00
topjohnwu 5cdf95a4d0 Update v25.2 docs 2022-07-20 20:09:02 -07:00
topjohnwu 910a36fdc1 Make sepolicy.rules relative if possible 2022-07-20 19:28:38 -07:00
topjohnwu 8331206acb Clean only java projects when clean java 2022-07-20 11:20:03 -07:00
canyie 8423dc8d63 Later check persistent_properties
`daemon_entry` calls `getprop` which initializes sysprop impl and checks whether we need to load persistent property file. On FDE devices, magiskd starts before /data is actually decrypted, and the check always fails. Thus `persist_getprop("persist.sys.safemode")` will always fail.
2022-07-20 09:58:20 -07:00
Yann 6077c989a7 app: fix typo 2022-07-20 03:34:48 -07:00
topjohnwu c97d1044fa Release new canary build 2022-07-19 17:44:19 -07:00
Hen_Ry f42c089b26 Fix 2022-07-18 13:34:14 -07:00
Andrew Gunnerson 1f8c063dc6 Fix booting into recovery with Android 13 GKI kernels
With Android 13 GKI kernels, the boot partition has no ramdisk, so
Magisk constructs one from scratch. In this scenario, there's no backup
init binary at /.backup/init. For normal boot, magiskinit will symlink
/init -> /system/bin/init if needed. This commit implements the same
for booting into recovery. Before, magiskinit would just exec itself
over and over again because it couldn't restore the backup init.

Signed-off-by: Andrew Gunnerson <chillermillerlong@hotmail.com>
2022-07-18 13:33:50 -07:00
Hen_Ry 4874520d65 Update german translation 2022-07-14 12:32:55 -07:00
Nguyen Hoang The Vi 5e53639969
Add Bengali translation 2022-07-14 12:32:33 -07:00
Grammatopoulos Apostolos 83ab0ca6cd Greek translation updated 2022-07-14 12:31:47 -07:00
topjohnwu 70fd03d5fc Rearchitect logging 2022-07-06 01:16:08 -07:00
topjohnwu 2e52875b50 Move all logging into Rust 2022-07-05 21:13:09 -07:00
topjohnwu fd9b990ad7 Update to ONDK r24.2 2022-07-05 01:34:48 -07:00
LONE DEVIL 69978a9442
Update russian translation 2022-07-01 15:16:44 -07:00
残页 d155da52ce
More friendly and clear error message 2022-07-01 15:15:54 -07:00
Weslley Almeida 9c5b131913 Update Brazilian translation 2022-07-01 15:15:07 -07:00
Syuugo 9d740cec1a
Partially fixed Japanese translation 2022-07-01 15:14:18 -07:00
vvb2060 c2978eb9c3 More log for get_manager 2022-07-01 15:13:38 -07:00
vvb2060 38abad1e44 Fix app state 2022-07-01 15:12:50 -07:00
topjohnwu b4863eb51b Setup logging infra in the Rust side 2022-07-01 04:54:00 -07:00
LoveSy 3817167ba1 Correct `ro.crypto.state` check
Fix #6042

Co-authored-by: vvb2060 <vvb2060@gmail.com>
2022-06-30 19:32:43 -07:00
topjohnwu d1a35dd2ba Fix cargo builds on Windows 2022-06-30 18:12:07 -07:00
topjohnwu 26116ac414 Setup preliminary rust infrastructure 2022-06-30 14:50:21 -07:00
topjohnwu 0b26882fce Build dynamic stub resource APK at runtime
Close #6013

Co-authored-by: vvb2060 <vvb2060@gmail.com>
2022-06-22 05:19:27 -07:00
Nicolás a2495fb5fb Update spanish translations 2022-06-22 04:08:52 -07:00
vvb2060 0beb3bf16a Make CI builds reproducible 2022-06-22 04:08:18 -07:00
vvb2060 b68658e974 Rebuild manifest 2022-06-22 04:06:22 -07:00
LoveSy 3ae7344747
Create `/dev` on stub cpio 2022-06-22 04:05:50 -07:00
topjohnwu 4eb71830b3 Release new canary build 2022-06-19 03:24:36 -07:00
topjohnwu 9183a0a6ea Update README 2022-06-19 03:06:14 -07:00
topjohnwu bb64ba0ef6 Release Magisk v25.1 2022-06-19 02:54:22 -07:00
topjohnwu d89a568897 Update v25.1 docs 2022-06-19 02:35:05 -07:00
topjohnwu 9fd1f41e8b Always relaunch process after package migration 2022-06-19 02:09:14 -07:00
孟武.尼德霍格.龍 c1ab348673
Improve Traditional Chinese strings
Co-authored-by: John Wu <topjohnwu@gmail.com>
2022-06-19 01:50:43 -07:00
canyie 00247c7901 Fix meizu non-SAR 2SI compatibility again
Meizu devices using 2SI won't switch root to /system and still on rootfs, and /init is the 1st stage's, which cannot handle the 2nd stage. So we have to manually execute /system/bin/init for the 2nd stage.
2022-06-19 01:22:18 -07:00
topjohnwu 3c75f474c6 Embed version info in prop format 2022-06-19 00:43:38 -07:00
topjohnwu db1f5b0397 Reduce files relying on flags.h 2022-06-19 00:43:38 -07:00
fadlyas07 db277c3e55 app: l10n: Update Indonesian translations
* Added new strings based on the recent source.
* Fixed some words based on Indonesian National Dictionary (KBBI).

Link: https://kbbi.kemdikbud.go.id
Signed-off-by: fadlyas07 <mhmmdfdlyas@gmail.com>
2022-06-18 10:43:25 -07:00
vvb2060 b9c93c66f6 Force app version not lower than daemon 2022-06-17 11:53:16 -07:00
vvb2060 a250e2b56c Set version comment in apk 2022-06-17 11:53:16 -07:00
残页 cd96454886
Fix finding recovery image on direct install
Fix #5972, fix #5673
2022-06-17 02:53:18 -07:00
topjohnwu 741b679306 Cleanup libbase 2022-06-17 02:36:04 -07:00
topjohnwu 90013e486d Use AtomicBoolean 2022-06-17 02:03:09 -07:00
LoveSy 4e2ecdb920
Fix env overflow
Fix #5989
2022-06-17 02:02:44 -07:00
topjohnwu 6e5df1f06b Abort when unsupported dtb is detected 2022-06-16 01:47:23 -07:00
topjohnwu 9469e79e3c Proper namespacing
The IDE will get confused when #include is in a namespace
2022-06-15 02:38:56 -07:00
topjohnwu db78c20161 Add dtb test command 2022-06-15 02:26:50 -07:00
topjohnwu 1699da1754 Update help message and make behavior consistent 2022-06-14 21:19:17 -07:00
canyie 754e690274 Fix config backup for legacy SAR 2022-06-14 02:57:47 -07:00
topjohnwu 6f74ed6ceb Cleanup manager.sh 2022-06-13 01:21:24 -07:00
canyie 71205bc530 Anchor Snackbar above reboot FAB in FlashFragment 2022-06-12 11:00:36 -07:00
Chris Renshaw 10e236abdf scripts: fix remaining instances of && ||
Looks like I may have missed this in ce84f1762c originally
2022-06-12 11:00:09 -07:00
残页 2248af00f3
Fix #5673
util_functions.sh overrides `get_flags` function (defined in manager.sh), which sets `RECOVERYMODE` and causes `check_boot_ramdisk` not overriding the incorrect value.
2022-06-12 00:32:34 -07:00
topjohnwu 7e61716277 Update Kotlin to 1.7.0 2022-06-11 03:41:02 -07:00
topjohnwu 50edb8d072 Better network detection and invalidation 2022-06-10 04:25:34 -07:00
topjohnwu 515f81944c Move coroutine job into its own class 2022-06-10 04:12:31 -07:00
topjohnwu 46d4708386 Decouple state from BaseViewModel 2022-06-10 02:13:25 -07:00
topjohnwu aabc36f86b Maintain separate flash screen state 2022-06-10 00:33:53 -07:00
nikk gitanes e0d5d90267 fix restart button focus on flash result 2022-06-10 00:33:53 -07:00
topjohnwu 482a5b991b Don't always refresh on network state change 2022-06-09 23:28:46 -07:00
CDzungx 20124fe410 Update vi translation 2022-06-09 21:03:26 -07:00
Softastur f8dcec116a
Fix Asturian translation 2022-06-09 21:03:09 -07:00
Ilya Kushnir 343a339aae Update RU strings (fix) 2022-06-09 21:02:45 -07:00
vvb2060 42606efe56 Always remove task 2022-06-09 21:02:31 -07:00
vvb2060 cae58c8790 Update hijack bins 2022-06-08 23:30:22 -07:00
topjohnwu 3a39dd4049 Update ramdisk restore implementation 2022-06-08 23:23:39 -07:00
canyie 89ff3c6572 Don't backup ramdisk created by Magisk
Fix topjohnwu#5938, fix topjohnwu#5944
2022-06-08 04:53:43 -07:00
topjohnwu 7bf9c74216 Don't skip backup even if original does not exist
Close #5945, fix #5944
2022-06-08 03:58:25 -07:00
topjohnwu e2f3753551 Release new canary build 2022-06-07 03:36:21 -07:00
topjohnwu cacf873645 Release Magisk v25.0 2022-06-07 03:11:29 -07:00
NeoHBz 11e1e7ee36 updated: `hi` translations, matched with source 2022-06-07 02:58:43 -07:00
vvb2060 87801b6f23 Fix mv file when install module 2022-06-07 02:46:16 -07:00
topjohnwu 7ce4789e17 Add v25.0 release notes 2022-06-07 02:44:26 -07:00
topjohnwu 9dc6d9afce Restore AVD after testing 2022-06-07 01:06:27 -07:00
MCPEngu d6a5354bff fix typo 2022-06-07 01:00:39 -07:00
Ilya Kushnir 07af37475b Update RU strings 2022-06-07 00:08:10 -07:00
Oliver Cervera 1b9c273b10
Italian translation update 2022-06-07 00:07:52 -07:00
AndroPlus-org 262c52db56 Update Japanese translation 2022-06-07 00:06:31 -07:00
topjohnwu eb777296d4 Suppress AppLinkUrlError 2022-06-06 05:26:53 -07:00
topjohnwu fc70a384d3 Release new canary build 2022-06-06 03:43:23 -07:00
vvb2060 34b2f525a3 Update proguard-rules.pro 2022-06-06 02:59:57 -07:00
vvb2060 569e9ad937 Use noHistory attribute for SuRequestActivity 2022-06-06 02:58:52 -07:00
vvb2060 c495b3d183 Remove request uninstall code
unreachable
2022-06-06 02:51:11 -07:00
Softastur 8b16bfbb54
Add Asturian language support 2022-06-06 01:22:05 -07:00
Arbri çoçka b2f1fd9966 update albania 2022-06-06 00:36:22 -07:00
topjohnwu 317153c53a Better stub launch flow 2022-06-05 19:15:43 -07:00
topjohnwu fa60daf9b5 Verify caller before uninstallation 2022-06-05 07:03:26 -07:00
canyie aadb2d825c Also set snackbar container for FlashFragment 2022-06-05 05:36:04 -07:00
kubalav 0e7fe537e3 Update Slovak translation 2022-06-05 05:35:39 -07:00
VD $ VD171 @ Priv8 409de3ac44
Update Portugueses Translations 2022-06-05 05:34:45 -07:00
RV7PR 759055eaa5 Replace logo 2022-06-05 05:34:19 -07:00
topjohnwu 9016e6727d Fix stub app loading on older Android versions 2022-06-05 01:09:30 -07:00
残页 a3381da7ed
Bypass DexFile's security check for RootService (#5911)
Old Android (pre 8.0) enforces `optimizedDirectory` to have the same uid with the process. If repackaged, root service (uid=0) will crash when trying to load current.apk. So we set `optimizedDirectory` to null to bypass the check.
2022-06-04 04:21:57 -07:00
topjohnwu 351e094440 Release new canary build 2022-06-03 03:38:12 -07:00
残页 2106751ea4
Fix SnackBar shows behind window insets 2022-06-03 03:21:56 -07:00
vvb2060 7ae3cd1c43 Fix D-pad navigation on android 8- 2022-06-03 03:03:41 -07:00
topjohnwu edfd4dcddf Fix kotlin jvmTarget 2022-06-03 01:13:29 -07:00
topjohnwu fb89cf1367 Fix typo 2022-06-03 00:25:10 -07:00
Rom b7b345cf8a Update FR translation 2022-06-02 23:47:11 -07:00
残页 0be487e47e Update zh-rCN translation 2022-06-02 23:45:54 -07:00
topjohnwu 5471147422 Remove usage of BindingCollectionAdapter (part 2) 2022-06-02 23:40:10 -07:00
topjohnwu 6305159c5e Remove usage of BindingCollectionAdapter (part 1) 2022-06-02 20:55:19 -07:00
topjohnwu 2ed092c9db Update contributors in app 2022-06-02 06:08:47 -07:00
topjohnwu 5c6a7ffa6f Simplify context hacks 2022-06-02 04:22:25 -07:00
topjohnwu 9ab7550970 Use weak reference to track activity 2022-06-02 02:18:11 -07:00
topjohnwu 47e7a0a434 Update libsu 2022-06-02 02:04:35 -07:00
RikkaW 4cc5e9f986 Let module remove button support disable state 2022-06-01 09:04:47 -07:00
RikkaW 6a2ae89846 Fix card view background color on API 21 & 22 2022-06-01 09:04:35 -07:00
残页 3c93539e02
Fix log save 2022-06-01 03:10:07 -07:00
topjohnwu 05e5ac2ad2 Bump min version to v22 2022-06-01 03:05:29 -07:00
topjohnwu 10b1782732 Update version gating 2022-06-01 03:01:56 -07:00
topjohnwu e029994ef8 Move Zygisk out of beta 2022-06-01 02:59:02 -07:00
vvb2060 9679874874 Disable repack on android 5.0
am does not support -p
2022-06-01 02:05:15 -07:00
topjohnwu 8186f253e8 Fix zygisk code unloading 2022-06-01 01:50:42 -07:00
topjohnwu d4fe8632ec Support SELinux disabled on debug builds 2022-05-31 22:24:13 -07:00
vvb2060 d7776f6597 Return empty on failure to get context 2022-05-31 18:35:56 -07:00
残页 3219d945f5 Prevent multi animators setting property concurrently
It crashes on Android 5.0 (API 21) platform.
Fix topjohnwu#5793
2022-05-31 18:35:40 -07:00
Takeda-senpai 8a73a16029
Update VN translation 2022-05-30 23:46:53 -07:00
VD $ VD171 @ Priv8 ce90f9b60d Update Portugueses Translations by VD171
Update Portugueses Translations by VD171
2022-05-30 23:46:33 -07:00
VD $ VD171 @ Priv8 bdf54d562f Update Portugueses Translations by VD171
Update Portugueses Translations by VD171
2022-05-30 23:46:33 -07:00
Rom e744cc8ea6 Update French translation 2022-05-30 23:46:11 -07:00
Ilya Kushnir babcf36495 Update RU strings 2022-05-30 23:45:59 -07:00
topjohnwu e4094c0caa Update build scripts 2022-05-30 03:47:31 -07:00
topjohnwu 2e51fe20a1 Move things to the correct location 2022-05-30 02:09:07 -07:00
vvb2060 c29636c452 Update zh-rTW translation 2022-05-30 01:54:12 -07:00
vvb2060 22017a5543 Update zh-rCN translation 2022-05-30 01:54:12 -07:00
topjohnwu 50e2f33d1c More debug indication in UI
Close #5874
2022-05-30 01:53:07 -07:00
topjohnwu 5e6eb8dd01 Avoid non-blocking I/O 2022-05-30 01:21:38 -07:00
topjohnwu 18acb97dfe Make SYSTEM_UID a special case 2022-05-30 00:49:42 -07:00
topjohnwu bf2f823b8c Prune unused UID at boot 2022-05-29 23:43:22 -07:00
topjohnwu d0c4226997 Proper package state management 2022-05-29 23:31:57 -07:00
topjohnwu 4ea8bd0229 Fix incorrect use of compare_exchange 2022-05-29 22:19:56 -07:00
topjohnwu ee0d58a9b8 Release new canary build 2022-05-29 11:24:39 -07:00
topjohnwu bf04fa134b Indicate debug builds
Close #5859
2022-05-29 11:14:39 -07:00
Arbri çoçka 297662cafb update Albanian 2022-05-29 11:03:23 -07:00
kubalav f464a9b269 Update Slovak translation 2022-05-29 11:03:11 -07:00
vvb2060 d19fcd5e21 Check path when start daemon 2022-05-29 09:08:05 -07:00
topjohnwu c0981174a8 Use LiveData instead of Observable 2022-05-29 03:57:42 -07:00
vvb2060 0b5f973b31 Print message when getting original app_process fails 2022-05-29 03:46:31 -07:00
topjohnwu 4159b3871c Fix #5867 2022-05-29 02:49:38 -07:00
南宫雪珊 580c993c0b
Display module status 2022-05-29 01:40:20 -07:00
canyie 0cc29350a0 Navigate only if user has not left the fragment 2022-05-28 22:40:09 -07:00
topjohnwu 490a784993 Handle zygote restarts 2022-05-28 22:39:44 -07:00
topjohnwu 9c774f96db Use exec for boot_complete 2022-05-28 16:53:04 -07:00
topjohnwu 99afe7ac07 Update AGP 2022-05-28 04:46:58 -07:00
topjohnwu b3f05fd925 Update setup 2022-05-27 00:44:20 -07:00
topjohnwu 683cfee88b Cleanup and move things around 2022-05-26 22:05:28 -07:00
topjohnwu 3bcaf0ed5b Move more files into core 2022-05-25 05:48:02 -07:00
topjohnwu edb76503d3 Update README 2022-05-24 06:13:51 -07:00
topjohnwu 484038638f Release new canary build 2022-05-24 05:56:59 -07:00
topjohnwu 8dfb30fefe Skip cert check on debug builds 2022-05-24 05:39:16 -07:00
topjohnwu 2a252d13b8 Enforce dyn APK signature in stub app 2022-05-24 05:21:36 -07:00
topjohnwu afa364cfc3 Update dependencies 2022-05-22 20:11:24 -07:00
topjohnwu dfa36fb25d Move things around 2022-05-22 19:36:47 -07:00
topjohnwu c8492b0c58 Use official APIs to load dynamic resources 2022-05-22 19:20:24 -07:00
topjohnwu 083ef803fe Enforce package signature verification 2022-05-20 04:37:58 -07:00
topjohnwu 351f0269ae Install stub if necessary 2022-05-19 22:54:49 -07:00
topjohnwu a29ae15ff7 Proper get_manager implementation 2022-05-19 02:39:57 -07:00
topjohnwu 34dded3b25 Fix denylist on shared UID apps 2022-05-18 01:59:45 -07:00
topjohnwu 975b1a5e36 Prune unused UIDs from su policies 2022-05-18 01:55:58 -07:00
vvb2060 e11508f84d Respond deny when pkg name not found 2022-05-16 20:44:18 -07:00
topjohnwu 0772f6dcaf Fix debug channel preference not persisting 2022-05-16 20:16:50 -07:00
topjohnwu d3fe3a711a Release new canary build 2022-05-15 01:35:02 -07:00
topjohnwu 756d8356ca Show canary channel option on canary builds 2022-05-15 01:28:49 -07:00
topjohnwu 42003b4006 Release new canary build 2022-05-15 01:14:07 -07:00
topjohnwu dc65a2b884 Introduce new debug channel 2022-05-15 01:01:54 -07:00
topjohnwu 071ae79fa8 Release new canary build 2022-05-13 04:34:27 -07:00
topjohnwu c11ccbae2d Extract vbmeta from footer
Do not scan manually, extract properly from footer like libavb
2022-05-13 02:49:18 -07:00
topjohnwu 6ef86d8d20 Release new canary build 2022-05-12 03:16:16 -07:00
topjohnwu 985249c3d0 Support GKIs without ramdisk
Fix #5819
2022-05-12 03:04:55 -07:00
topjohnwu 622e09862a Restructure native codebase 2022-05-12 02:03:51 -07:00
残页 7505599ea0 Skip invalid slot_suffix argument
Many Amlogic devices (e.g. FireTV 2nd gen Cube, Vero 4k+, MI Smart Speaker, etc.) are A-only with androidboot.slot_suffix=normal argument. I think "normal" actually means A-only in this case so just ignore it.

Fix topjohnwu#5806
2022-05-12 00:37:22 -07:00
topjohnwu 575c417403 More detailed comments and documentation 2022-05-11 21:12:37 -07:00
topjohnwu 9f7a3db8be Move cert extraction to its own file 2022-05-11 21:12:37 -07:00
topjohnwu 029422679c Remove enforcement
Enforcement will be re-implemented later
2022-05-11 21:12:37 -07:00
vvb2060 05d6d2b51b Verify app signature 2022-05-11 21:12:37 -07:00
capntrips 4cff0384f7 Remove temporary note about OTA update no longer working 2022-05-10 00:11:34 -07:00
vvb2060 68db366696 Delete outdated policies 2022-05-10 00:11:17 -07:00
南宫雪珊 358538717c
Reduce number of loop 2022-05-10 00:10:26 -07:00
topjohnwu 24603b3cef Update Android Studio 2022-05-09 20:53:47 -07:00
topjohnwu 4eb9240806 Handle Activty recreation on content result
Credits to @canyie for the initial PR and finding the bug
Close #5791, fix #5789
2022-05-08 14:29:59 -07:00
vvb2060 0469f0b5ae Add uid check for getAppProcess 2022-05-08 04:51:39 -07:00
vvb2060 0b8577d02b Set tag for root service 2022-05-08 00:39:37 -07:00
Rei Ryuki 97135879a1
Fix sepolicy rules dir is not found in recovery 2022-05-07 02:43:26 -07:00
vvb2060 fef41f68c0 Update dependencies 2022-05-07 02:42:20 -07:00
topjohnwu 0ac19e3a4e Fix app running without root 2022-05-07 01:16:55 -07:00
topjohnwu 2793d209a4 Allow requesting root from non app process 2022-05-07 00:46:23 -07:00
topjohnwu 71e9c044e6 Release new canary build 2022-05-06 01:57:24 -07:00
Kazurin Nanako 42e5f5150a Fix "double install" caused by config changes
Configuration changes in FlashFragment may cause the installation process to be triggered twice. The simplest way to reproduce this behavior is to choose a module ZIP file in landscape mode (which is the default on some tablets).

This commit fixes the problem by ensuring `savedInstanceState == null` before starting installation.
2022-05-06 01:47:02 -07:00
topjohnwu 90545057e9 Always initialize module_list
Close #5712
2022-05-06 01:40:19 -07:00
vvb2060 cffd024e9e Ignore the response until showDialog done 2022-05-06 01:04:28 -07:00
人工知能 8c858592c4 Update strings.xml
Update and fix translations.
2022-05-06 01:03:50 -07:00
canyie 4f1a1879e5 Misc QoL changes
- su: Preserve correct capacity to avoid vector reallocation
- su: Properly format code
- daemon: Remove useless `if`
- docs: Remove outdated info
2022-05-06 01:01:58 -07:00
JumbomanXDA e88eed9a8d Update util_functions.sh 2022-05-06 00:03:38 -07:00
RikkaW 9581ae8245 Use Locale.ROOT in JcaX509v3CertificateBuilder (X509v3CertificateBuilder)
Or in languages like Arabic, an "IllegalArgumentException: invalid date string" will be thrown.

Since JcaX509v3CertificateBuilder does not accepts Locales, we must switch to its super class, X509v3CertificateBuilder.
2022-05-06 00:03:01 -07:00
vvb2060 4202b7a9dc Enable gms provider for stub 2022-05-06 00:00:41 -07:00
LoveSy b4c398542a Fix signboot signature 2022-05-06 00:00:20 -07:00
topjohnwu 081148b2d7 Update dependencies 2022-05-04 22:00:48 -07:00
topjohnwu a32c4561ed Release new canary build 2022-05-03 01:38:43 -07:00
topjohnwu cc79a96fa3 Update libsu 2022-05-03 01:25:26 -07:00
topjohnwu ff340ce3d8 Suppress verbose output to stderr 2022-04-29 04:57:28 -07:00
topjohnwu 134508193d Mock selinuxfs load with regular file
The hijacked load node does not need to be a FIFO. A FIFO is only
required for blocking init's control flow, which is already achieved
by hijacking the enforce node.
2022-04-16 07:28:20 -07:00
topjohnwu c2b74aa83e Update avd_test.sh 2022-04-16 07:28:20 -07:00
topjohnwu 3358eab991 Switch to use ONDK 2022-04-15 12:20:18 -07:00
残页 a609e0aad4 Update tools.md
magiskpolicy is no longer an applet of magiskinit
2022-04-13 23:19:36 -07:00
vvb2060 f97866a961 Close stub fd 2022-04-13 23:19:14 -07:00
vvb2060 e1987c42c4 Cleanup SELinux mock files 2022-04-13 23:18:55 -07:00
canyie 18566715e1 Fix MAGISKTMP unmount for CLI 2022-04-10 01:44:16 -07:00
topjohnwu 79f0f3230c Release new canary build 2022-04-08 02:51:25 -07:00
topjohnwu 63a89d9f04 Fix init dmesg logs 2022-04-08 02:38:30 -07:00
南宫雪珊 f639f39e79
More friendly info 2022-04-08 02:26:11 -07:00
canyie b4099fc5f9 Support sepolicy.unlocked
Fix topjohnwu#4914
2022-04-08 02:24:20 -07:00
topjohnwu ff2513e276 Use LD_PRELOAD to intercept sepolicy on 2SI init 2022-04-08 02:13:31 -07:00
topjohnwu f24d52436b Deduplicate logic 2022-04-08 00:20:21 -07:00
vvb2060 9de6e8846b Dump stub app to MAGISKTMP/stub.apk 2022-04-07 23:20:42 -07:00
vvb2060 01a1213463 /data/adb/magisk/magisk.apk no longer exists 2022-04-07 23:20:42 -07:00
vvb2060 f0fbd9214a Remove test key 2022-04-07 21:49:15 -07:00
hnliuzesen c4f37c550f Update internal details 2022-04-06 21:15:28 -07:00
canyie 448384af06 Guard su request IPC
Previously `read_string()` calls `std::string.resize()` with a int read from remote process. When I/O error occurs, -1 will be used for resizing the string, `std::bad_alloc` is thrown and since magisk is compiled with `-fno-exceptions`, it will crash the whole daemon process.

May fix topjohnwu#5681
2022-04-06 21:15:07 -07:00
canyie 3f840f53a0 Check device tree fstab entries are compatible
Fix topjohnwu#5664
2022-04-02 04:28:30 -07:00
Lishoo d8718d8ac8 Update polish strings 2022-04-02 04:27:11 -07:00
vvb2060 2fb46a11dc Check MAGISKBIN/magiskpolicy 2022-04-02 04:26:47 -07:00
vvb2060 9a11412719 Fix superuser snackbar text 2022-04-02 04:26:21 -07:00
topjohnwu 98874be171 Release new canary build 2022-03-30 01:58:36 -07:00
topjohnwu 704f91545e Reorganize magiskpolicy source code 2022-03-29 22:26:38 -07:00
topjohnwu efb3239cbd Drop package_name column 2022-03-28 02:05:09 -07:00
topjohnwu 7e7ddeb9e2 Cleanup database migration code 2022-03-28 00:59:16 -07:00
LoveSy 9e8218089b Only dlopen valid fd 2022-03-26 13:48:53 -07:00
VD $ VD171 @ Priv8 3f660a3963 Fix Portuguese & PT-Brazilian Translations 2022-03-26 13:48:23 -07:00
VD $ VD171 @ Priv8 daeb6711b0 Fix Portuguese & PT-Brazilian Translations 2022-03-26 13:48:23 -07:00
CDzungx 4e1aec28a0 Update Vietnamese Translation
Quick fix: Yes - "Đồng ý" -> "Có" to be more versatile.
2022-03-26 13:47:10 -07:00
vvb2060 5512917ec1 Hide incorrect "Factory data reset" message 2022-03-26 13:46:01 -07:00
vvb2060 cd1edc5d56 Use svc for reboot to recovery 2022-03-26 13:46:01 -07:00
topjohnwu 4f52587586 Support ADB shell if app shares its UID 2022-03-26 13:43:43 -07:00
topjohnwu d7ee4ef5f5 Fix SQL command syntax 2022-03-26 00:36:01 -07:00
topjohnwu 31f88e0f05 Update UI for sharedUID support 2022-03-25 16:56:21 -07:00
topjohnwu 9f1740cc4f Add preliminary shared UID app support 2022-03-25 13:08:13 -07:00
topjohnwu f2c15c7701 Ensure RootService is launched 2022-03-23 18:44:05 -07:00
topjohnwu e67d0678f9 Use viewModelScope instead of GlobalScope 2022-03-23 18:03:41 -07:00
topjohnwu b1faa5eed4 Update BusyBox
Close #5620
2022-03-22 04:18:12 -07:00
LoveSy 7f1f0b9048 Proper support multiple modules adding same dir 2022-03-21 15:53:49 -07:00
LoveSy 183e5f2ecc
Fix xhook cannot hook app_process
Co-authored-by: canyie <31466456+canyie@users.noreply.github.com>
Co-authored-by: John Wu <topjohnwu@gmail.com>
2022-03-21 15:52:38 -07:00
topjohnwu 14efe4939a Release new canary build 2022-03-21 00:35:25 -07:00
topjohnwu 3dc7d77ea9 Patch monolithic sepolicy only if not treble 2022-03-19 20:21:31 -07:00
残页 0f07bbb3e5 Device using split policy can still have monolithic sepolicy file 2022-03-19 12:37:48 -07:00
LoveSy dd5a3416bf Fix multiple modules adding the same subdirectory 2022-03-19 12:28:54 -07:00
LoveSy 2fb49ad780 Don't always mock selinux enforce as "0" 2022-03-19 12:28:32 -07:00
topjohnwu 92f0e53fee Release new canary build 2022-03-18 05:05:17 -07:00
topjohnwu 876132694d Make /dev always writable 2022-03-18 04:58:37 -07:00
topjohnwu 1257ba41c6 Add MagiskInit AVD automation test 2022-03-18 04:56:19 -07:00
topjohnwu 2cc71ac7ed Release new canary build 2022-03-18 01:56:19 -07:00
topjohnwu 753808a4ce Also hijack plat_file_contexts if necessary
Since Android 13, sepolicy are also loaded from APEX modules. Part
of the change is to run restorecon before SELinux is set to enforce.
In order to support this situation, we also hijack plat_file_contexts
if necessary to properly order our operations.

Original idea credits to @yujincheng08, close #5603
2022-03-18 00:46:34 -07:00
topjohnwu 32cd694ad5 SAR can also have monolithic sepolicy 2022-03-17 22:32:49 -07:00
topjohnwu f008420891 Make magiskinit not magiskpolicy 2022-03-17 03:36:40 -07:00
topjohnwu fa8900be65 Use standalone magiskpolicy 2022-03-17 03:15:39 -07:00
LoveSy 69c2f407d6 Log if failed to dlopen a zygisk module 2022-03-17 02:25:31 -07:00
topjohnwu ffcd093db1 Fix #5589
Close #5598
2022-03-17 02:25:31 -07:00
topjohnwu 8dbf93750f Reorganize magiskinit code 2022-03-16 21:41:20 -07:00
topjohnwu e266a81167 Remove unused code 2022-03-16 21:31:22 -07:00
topjohnwu e841aab9e7 Add hijack sepolicy support for rootfs devices
On older Android versions, pre-mounting selinuxfs will lead to errors,
so we have to use a different method to block init's control flow.
Since all devices that falls in this catagory must both:

1. Be Android 8.0 - 9.0
2. Have early mount fstab in its device tree

We can actually use the same FIFO trick, but this time not on selinuxfs,
but on the read-only device tree nodes in sysfs or procfs. By mocking
the fstab/compatible node in the device tree, we can block init when
it attempts to do early mount; at that point, we can then mock selinuxfs
as we normally would, successfully hijack and inject patched sepolicy.
2022-03-16 20:01:28 -07:00
topjohnwu 49f259065d Introduce new sepolicy injection mechanism
In the current implementation, Magisk will either have to recreate
all early mount implementation (for legacy SAR and rootfs devices) or
delegate early mount to first stage init (for 2SI devices) to access
required partitions for loading sepolicy. It then has to recreate the
split sepolicy loading implementation in-house, apply patches, then
dump the compiled + patched policies into monolithic format somewhere.
Finally, it patches the original init to force it to load the sepolicy
file we just created.

With the increasing complexity involved in early mount and split
sepolicy (there is even APEX module involved in the future!),
it is about time to rethink Magisk's sepolicy strategy as rebuilding
init's functionality is not scalable and easy to maintain.

In this commit, instead of building sepolicy ourselves, we mock
selinuxfs with FIFO files connected to a pre-init daemon, waiting
for the actual init process to directly write the sepolicy file into
MagiskInit. We then patch the file and load it into the kernel. Some
FIFO tricks has to be used to hijack the original init process's
control flow and prevent race conditions, details are directly in the
comments in code.

At the moment, only system-as-root (read-only root) support is added.
Support for legacy rootfs devices will come with a follow up commit.
2022-03-16 00:31:55 -07:00
topjohnwu b10379e700 Cleanup inheritance 2022-03-14 04:22:09 -07:00
topjohnwu 810d27a618 Use /data as tmpfs mount point in 2SI setup
Design credit to @yujincheng08
Close #5146. Fix #5491, fix #3752

Previously, Magisk changes the mount point from /system to /system_root
by patching fstab to prevent the original init from changing root.
The reason why we want to prevent the original init from switching the
root directory is because it will then be read-only, making patching
and injecting magiskinit into the boot chain difficult.

This commit (ab)uses the fact that the /data folder will never be part
of early mount (because it is handled very late in the boot by vold),
so that we can use it as the mount point of tmpfs to store files.

Some advantages of this method:

- No need to switch root manually
- No need to modify fstab, which significantly improves compatibility
  e.g. avoid hacks for weird devices like those using oplus.fstab,
  and avoid hacking init to bypass fstab in device trees
- Supports skip_mount.cfg
- Support DSU
2022-03-13 05:06:08 -07:00
topjohnwu 9b60c005c7 Support multiple CPIO concatenated 2022-03-13 04:23:00 -07:00
topjohnwu cc6ca0bda2 Update README 2022-03-10 00:45:51 -08:00
topjohnwu 4512232637 Release new canary build 2022-03-10 00:44:42 -08:00
topjohnwu 2c092ffdef Release Magisk v24.3 2022-03-10 00:32:07 -08:00
topjohnwu 66406227d6 Add v24.3 release notes 2022-03-10 00:24:02 -08:00
topjohnwu a11d25bb44 Update libsu 2022-03-10 00:00:11 -08:00
VD $ VD171 @ Priv8 2e58d902b7
Update Portuguese Portugal Translation & Fix Portuguese Brazilian Translation by VD171 2022-03-09 20:44:33 -08:00
vvb2060 237794b05c Add root install back 2022-03-09 20:44:11 -08:00
topjohnwu 563a587882 Initialize local variables
Fix #5542
2022-03-09 20:43:42 -08:00
canyie 24505cd111 Prevent destroyed activities from being reused
The adapter will cache a LayoutInflater which refers the current activity, and the ViewModel object will keep alive until activity finished. After activity recreates (e.g. split-screen), it will use the cached LayoutInflater which refers a destroyed activity and crashes. This also is a memory-leak, according to Google's official document, ViewModel shouldn't refer activity. See https://developer.android.com/topic/libraries/architecture/viewmodel

Fix topjohnwu#5413
2022-03-07 01:54:02 -08:00
topjohnwu 0c681cdab4 Check null before dereferencing fds_to_ignore 2022-03-03 21:34:53 -08:00
VD $ VD171 @ Priv8 13ef3058c6 Update Portuguese Brazilian Translation by VD171
Update Portuguese Brazilian Translation by VD171
2022-03-03 10:36:45 -08:00
vvb2060 50b159b43d Add init_boot parition 2022-03-02 22:50:05 -08:00
Rom 8c6c328730 Update French translation 2022-03-02 22:48:24 -08:00
sn-o-w c9812ddf08 Update Romanian 2022-03-02 22:48:07 -08:00
owen151128 2ef0449c2c Update Korean translation 2022-03-02 22:33:52 -08:00
Ilya Kushnir 5edc750c47 Update RU strings 2022-03-02 22:33:21 -08:00
vvb2060 2f0e396d7f Update gradle 2022-03-02 22:32:35 -08:00
vvb2060 000a163beb Match components which are direct boot unaware 2022-03-02 22:32:35 -08:00
topjohnwu 80dd37ee31 Add missing specialize arguments 2022-03-02 22:01:35 -08:00
topjohnwu e0b5645064 Revert "Directly use getrandom system call if possible"
This reverts commit e7c82f20e3.
Fix #5516
2022-03-02 19:50:47 -08:00
topjohnwu e51aacb0b7 Update README 2022-03-01 23:54:39 -08:00
topjohnwu 2d6af94aa0 Release new canary build 2022-03-01 23:53:39 -08:00
topjohnwu 7cfce9ff7a Release Magisk v24.2 2022-03-01 23:35:56 -08:00
topjohnwu 7f088d6241 Add v24.2 release notes 2022-03-01 23:26:44 -08:00
vvb2060 d11038f3de Directly stream apk into install session 2022-03-01 23:05:06 -08:00
vvb2060 6df42a4be7 Handle install failure 2022-03-01 23:05:06 -08:00
Francesco Saltori 7fd111b91f
Bring English strings changes to Italian translation 2022-03-01 22:51:07 -08:00
Sirichai Chulee dd7dc2ec5a Fix typo in thai translation 2022-03-01 22:50:37 -08:00
Vladimír Kubala 86c586d882
Update Slovak translation 2022-03-01 22:50:12 -08:00
Arbri çoçka 66ac6f72fc update Albania translator 2022-03-01 22:49:44 -08:00
CDzungx f21f448099 Update Vietnamese Translation
Fix, added some translations.
Added note for technicality word in case user don't know the word mean in English like "boot image", "image" is translated to "đĩa ảnh", I can't really understand it if I use Vietnamese lang 😂.
2022-03-01 22:49:29 -08:00
topjohnwu 548d70f30c Mount with original option
Fix #5481, close #5486
2022-03-01 20:09:59 -08:00
topjohnwu 39e714c6d8 Release new canary build 2022-03-01 03:44:21 -08:00
topjohnwu 9968af0785 Move all permission check into daemon.cpp 2022-03-01 03:15:38 -08:00
topjohnwu be7586137c Reduce C++ wizardry 2022-03-01 03:15:38 -08:00
LoveSy 7999b66c3c Refactor daemon connection 2022-03-01 03:15:38 -08:00
vvb2060 c82a46c1ee Check property before switch mem cgroup 2022-02-28 23:27:23 -08:00
vvb2060 666ab1941f Fix app request fifo wait 2022-02-28 23:26:59 -08:00
topjohnwu 71e37345b4 Update libsu 2022-02-28 20:14:58 -08:00
topjohnwu e7c82f20e3 Directly use getrandom system call if possible 2022-02-16 23:57:28 -08:00
LoveSy afa771a980 Set dlopen reserved size to unlimited 2022-02-16 23:05:17 -08:00
vvb2060 0d1de98cca Update zh-rCN translation 2022-02-16 23:04:57 -08:00
vvb2060 02bf7dca01 Check apk before patch 2022-02-16 23:04:39 -08:00
vvb2060 8cc76b1d86 Fix restore dialog 2022-02-16 23:04:19 -08:00
vvb2060 77a275cbcd Show notification when stub is updated to full 2022-02-16 23:04:03 -08:00
vvb2060 3956cbe2d2 ActivityTracker ignore SuRequestActivity 2022-02-16 23:03:42 -08:00
vvb2060 945de8d9a0 Directly stream APK into install session 2022-02-16 23:03:32 -08:00
vvb2060 6dabd3bb2d Abandon unsuccessful session 2022-02-16 23:03:01 -08:00
topjohnwu 4c80808997 Check packages.xml inode to trigger app rescan 2022-02-14 02:57:33 -08:00
topjohnwu 5a39f7cdde Reduce duplicate initialization 2022-02-14 02:28:48 -08:00
topjohnwu 5d400fbe90 Check REQUEST_INSTALL_PACKAGES before actions 2022-02-14 02:15:50 -08:00
topjohnwu e36596470c Minor adjustments 2022-02-13 20:16:23 -08:00
topjohnwu 668e549208 Refactor APKInstall 2022-02-13 19:54:59 -08:00
topjohnwu 256ff31d11 Show notification after app upgrade 2022-02-13 18:35:35 -08:00
topjohnwu 2414d5d7f5 Minor changes 2022-02-13 14:23:06 -08:00
topjohnwu b7fc15d399 Code refactoring 2022-02-13 07:24:34 -08:00
topjohnwu c09b4dabc4 Generate class mapping at runtime 2022-02-13 06:22:42 -08:00
topjohnwu a4aa4a91a3 Refactor DynLoad 2022-02-13 03:32:11 -08:00
topjohnwu 8f0ea5925a Relaunch process without second process 2022-02-13 02:58:55 -08:00
南宫雪珊 936ad1aa20
Handle download fail
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2022-02-13 02:30:09 -08:00
topjohnwu d021bca6ef Prevent app_process from setting umask
Fix #5435
2022-02-11 01:26:24 -08:00
topjohnwu 55ed6109c1 Use dynamic_bitset.emplace_back() 2022-02-11 01:10:26 -08:00
vvb2060 f6d765bf81 Su request activity has no affinity for any task 2022-02-11 01:08:04 -08:00
LoveSy 88e8f2bf83
Proper escape `:` and `\` when binding intent 2022-02-11 01:07:28 -08:00
LoveSy c849759682 Use magiskboot to patch avd
Fix #5421
2022-02-11 00:25:07 -08:00
topjohnwu 605eae21bc Remove unnecessary read/write
Close #5425
2022-02-11 00:24:12 -08:00
topjohnwu 93eb277a88 Update error messages 2022-02-11 00:01:51 -08:00
LoveSy 8edf556c9e Fix lz4_lg compress 2022-02-10 23:50:19 -08:00
topjohnwu 7fcb63230f Support lz4_legacy archive with multiple magic
Multiple lz4_legacy archives can be directly concatenated
2022-02-10 23:49:17 -08:00
LoveSy 12093a3dad Update elf-cleaner 2022-02-08 00:53:02 -08:00
canyie ebb0ec6c42 Make xmmap() returns nullptr when fails
In the constructor of mmap_data, there are two possible values when fails: nullptr if fstat() fails, and MAP_FAILED if mmap() fails, but mmap_data treated MAP_FAILED as valid address and crashes.
2022-02-08 00:49:47 -08:00
LoveSy 188546515c
Fix UID tracking 2022-02-08 00:49:22 -08:00
topjohnwu c8990b0f68 Rewrite UID tracking 2022-02-07 02:46:47 -08:00
topjohnwu 7dced4b9d9 Update AGP 2022-02-07 00:19:36 -08:00
topjohnwu 3145e67feb Update data structure 2022-02-07 00:17:07 -08:00
topjohnwu e9348d9b6a Release new canary build 2022-02-06 07:19:27 -08:00
topjohnwu 1a1b346c05 Fix #5377 2022-02-06 07:12:26 -08:00
Donatello 920d059837 Update italian translation
Added missing string.

Co-authored-by: Madis Otenurm <Madis0@users.noreply.github.com>
2022-02-06 06:51:49 -08:00
xDonatello bef5c3bd1b Update italian translation 2022-02-06 06:51:49 -08:00
Madis Otenurm 97037f7d03 Update strings.xml 2022-02-06 06:51:11 -08:00
topjohnwu a7392ed3d7 Fix MULTIUSER_MODE_OWNER_MANAGED 2022-02-06 06:46:09 -08:00
Madis Otenurm 3eb1a7e384
Update Estonian 2022-02-06 05:59:09 -08:00
Arbri çoçka 1ecdc78c2f fix translante in Albania language 2022-02-06 05:58:39 -08:00
孟武.尼德霍格.龍 d279dba37e
Update Traditional Chinese Strings
Co-authored-by: LoveSy <631499712@qq.com>
2022-02-06 05:58:03 -08:00
topjohnwu a4f97fa151 Fix buffer overflow in connect.cpp 2022-02-06 05:52:11 -08:00
LoveSy ff7ac582f0
Refactor Zygisk loading
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2022-02-06 00:27:31 -08:00
LoveSy d2c2456fbe
Don't use `getmntent_r` from system's libc
Fix #5354

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2022-02-04 23:19:12 -08:00
LoveSy e9f562a8b7 Fix abuse of `fdopendir`
After `fdopendir`, the fd is no longer usable. Should dup and
make use of RAII

Co-authored-by: 残页 <31466456+canyie@users.noreply.github.com>
2022-02-04 22:54:34 -08:00
topjohnwu 084e0a73dc Cleanup DownloadService 2022-02-03 03:50:52 -08:00
topjohnwu 10f991b8d0 Directly stream APK into install session 2022-02-03 03:50:52 -08:00
残页 79620c97d1
Invalidate Samsung's persist.sys.zygote.early
Samsung FDE devices with the "persist.sys.zygote.early=true" property will cause Zygote to start before post-fs-data. According to Magisk's document, the post-fs-data phase should always happen before Zygote is started. Features assuming this behavior (like Zygisk and modules that need to control zygote) will not work. To avoid breaking existing modules, we simply invalidate this property to prevent this non-standard behavior from happening

Fix #5299, fix #5328, fix #5308

Co-authored-by: LoveSy <shana@zju.edu.cn>
2022-02-03 00:46:52 -08:00
topjohnwu ffec9a4ddd Minor changes 2022-02-02 05:06:12 -08:00
topjohnwu 9b18960bbd Getting APK doesn't need ContentProvider 2022-02-02 04:58:31 -08:00
topjohnwu a009fdbdc3 Fix root service on stub 2022-02-02 04:49:23 -08:00
topjohnwu c1fc3f373c Proper app relaunch for stub 2022-02-02 04:44:22 -08:00
topjohnwu f4cf5dc0cd Rename class 2022-02-02 02:50:27 -08:00
topjohnwu 355341f0ab Use AppComponentFactory to replace ClassLoader 2022-02-01 22:43:44 -08:00
topjohnwu 7f65f7d3ca Separate libc.a hacks into its own component 2022-01-31 02:09:08 -08:00
topjohnwu 9fa096c6f4 Add runtime FORTIFY support
Gingerbread libc.a missing symbols
2022-01-31 01:49:37 -08:00
LoveSy 70415a396a Do not filter uid == 1000 for process info 2022-01-30 08:25:24 -08:00
canyie c921964938 Make sure busybox can be executed recursively
Busybox will execute itself. On some older Samsung devices, when it is located in /data, it will not have rights to execute other programs including itself. We should also relocate busybox in this case to workaround Samsung bullshit.
See topjohnwu/ndk-busybox@bdc8655
Fix the "app doesn't detect installed Magisk" issue in topjohnwu#4174
2022-01-30 08:24:32 -08:00
topjohnwu 3bf47a6838 Update selinux 2022-01-30 08:18:04 -08:00
topjohnwu d3d28f0623 Update to NDK r23b
Credits: @yujincheng08

Close #5193
2022-01-30 07:11:51 -08:00
topjohnwu f880b57544 Update README 2022-01-28 04:02:57 -08:00
topjohnwu 32b7a26fa6 Release new canary build 2022-01-28 03:58:53 -08:00
topjohnwu 32fc34f922 Release Magisk v24.1 2022-01-28 03:43:42 -08:00
topjohnwu b82a393692 Add v24.1 release notes 2022-01-28 03:37:00 -08:00
LoveSy 3c7e792167
Catch `PendingIntent.CanceledException` thrown from `send` 2022-01-27 05:29:32 -08:00
LoveSy 0ad66875ab
Fix crash when zip is malformat
Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
Co-authored-by: 残页 <31466456+canyie@users.noreply.github.com>
2022-01-27 05:26:31 -08:00
Arbri çoçka 1191ac2671 update Albania translation 2022-01-27 05:25:13 -08:00
topjohnwu 928b3425e3 Embed module installer in APK 2022-01-27 05:24:05 -08:00
topjohnwu 0726a00e3b Fix download notifications 2022-01-27 05:17:52 -08:00
LoveSy 5a88984d34 Guard synchronizedList's iteration
It's needed to guard a synchronizedList when iterating it
2022-01-27 02:01:30 -08:00
LoveSy 18de60f68c Fix NPE of `SuRequestViewModel`
countdown timer may have not initialized when backpressed
2022-01-27 02:01:04 -08:00
LoveSy 1893359142
Fix crash when fragment is detached from activity 2022-01-27 01:54:24 -08:00
topjohnwu f5e5ab2436 Update Android Studio 2022-01-27 01:46:00 -08:00
topjohnwu ff5ea1a70d Clarify what 64-bit only means 2022-01-26 04:39:14 -08:00
topjohnwu 54ee63a409 Minor install guide changes 2022-01-26 02:55:25 -08:00
topjohnwu f095606b50 Release new canary build 2022-01-26 02:41:46 -08:00
topjohnwu e8f31c78d7 Update README 2022-01-26 02:33:22 -08:00
topjohnwu b34c477d5e Release Magisk v24.0 2022-01-26 02:21:22 -08:00
topjohnwu 28611304f7 Add v24.0 release notes 2022-01-26 02:08:49 -08:00
CISZEK Anthony 76af9e6e1f
Update French translations 2022-01-26 00:56:39 -08:00
topjohnwu 7b3b965ed7 Fix some typos 2022-01-26 00:52:51 -08:00
topjohnwu 567b905ef1 Update install guides 2022-01-26 00:48:16 -08:00
topjohnwu a94268329c Update developer guide 2022-01-25 23:05:03 -08:00
Oliver Cervera a11a18686a Update italian translation
Contains last changes, it is also synced with the main file.
2022-01-25 21:56:04 -08:00
AndroPlus c58e3a99ee Update Japanese translation 2022-01-25 21:55:50 -08:00
topjohnwu b166663e89 Release new canary build 2022-01-25 05:03:34 -08:00
topjohnwu ac13ac14f6 Remove deploy.md 2022-01-25 04:26:59 -08:00
topjohnwu 06531f6d06 Add annotations to suppress warnings 2022-01-25 04:16:14 -08:00
topjohnwu f6274d94f6 Add setprop warnings 2022-01-25 03:52:46 -08:00
topjohnwu 2b303a7e23 Add a missing busybox patch back
Hopefully, fix #4174
2022-01-25 03:37:59 -08:00
topjohnwu 2bb074a5ad Update developer guides 2022-01-25 03:04:23 -08:00
topjohnwu 3b2db56243 Update documentation 2022-01-25 02:32:52 -08:00
topjohnwu 45483fde74 Update CLI usages 2022-01-25 02:04:15 -08:00
topjohnwu d742cfa48f Label Zygisk as beta 2022-01-25 01:41:51 -08:00
topjohnwu 95353ce9eb Fix language settings 2022-01-25 01:31:15 -08:00
topjohnwu ab2cc72814 Remove unnecessary root service connection wait 2022-01-25 01:21:21 -08:00
topjohnwu 5c54a2c008 Update version check logic 2022-01-25 01:10:17 -08:00
topjohnwu 2fe3082518 Update busybox 2022-01-24 23:20:36 -08:00
topjohnwu 5a889d28c8 Pick a more reasonable zopfli config
Close #4980
2022-01-24 23:07:49 -08:00
Vlad 45e7c1c030
Update RU strings 2022-01-24 22:30:53 -08:00
topjohnwu c6dcff0ae7 Minor dynamic_bitset changes 2022-01-24 22:30:19 -08:00
Hen Ry b791dc5e1a Update de translation 2022-01-24 21:32:07 -08:00
DanGLES3 46db281006 Update pt-BR strings 2022-01-24 21:31:48 -08:00
vvb2060 636479b15b Fix dynLoad 2022-01-24 21:31:27 -08:00
vvb2060 dcbb4eabb5 Fix string 2022-01-24 21:27:17 -08:00
vvb2060 068cedaa84 Update zh-rCN translation 2022-01-24 03:05:45 -08:00
LoveSy 02dd962601 Don't load zygisk module for magisk app 2022-01-24 03:05:12 -08:00
topjohnwu 256d715648 Release new canary build 2022-01-23 07:07:21 -08:00
topjohnwu cbe97cdfde Fix dynamic_bitset implementation 2022-01-23 04:39:00 -08:00
topjohnwu 407dfc7547 Always write 0 to fd 2022-01-23 04:19:07 -08:00
Arbri çoçka a8e4e077ec Update Albania translator 2022-01-23 02:45:18 -08:00
vvb2060 3d06ba1878 Use WindowCompat 2022-01-23 02:44:59 -08:00
topjohnwu 8a23d1da58 Do not run setMarkdown on I/O thread 2022-01-23 02:41:56 -08:00
topjohnwu d3eb61e0e4 Fix string resources 2022-01-23 02:41:56 -08:00
vvb2060 7cdf2d244d Cleanup su handler 2022-01-23 01:38:17 -08:00
topjohnwu c59a41a607 Minor code refactoring 2022-01-23 01:08:09 -08:00
topjohnwu e0410b6f10 TLS only on release builds 2022-01-22 22:57:34 -08:00
topjohnwu 8eac6c0b48 Cleanup arch classes 2022-01-22 14:44:46 -08:00
vvb2060 bf8b74e996 Module json add changelog 2022-01-22 14:44:37 -08:00
kubalav 691e41e22e Update Slovak translation 2022-01-22 05:31:28 -08:00
AioiLight 15e91d42ee Update strings.xml 2022-01-22 05:31:14 -08:00
vvb2060 5e8e94fd0f Remove emoji compat 2022-01-22 05:30:59 -08:00
topjohnwu 5313a46aa2 Overhaul SettingsItem
Close #5021
2022-01-22 05:25:36 -08:00
topjohnwu 761a8dde65 Slightly change update UI logic 2022-01-21 05:37:47 -08:00
topjohnwu a73acfb9c2 Show unloaded Zygisk modules in UI 2022-01-21 05:37:47 -08:00
topjohnwu fbe17dde03 Add flag for unloaded Zygisk modules 2022-01-21 05:37:47 -08:00
vvb2060 a01a3404fe Ignore duplicate clicks on BottomNavigationView
Co-authored-by: RikkaW <rikka@shizuku.moe>
2022-01-21 02:12:41 -08:00
canyie 454e5dfc5d Show confirmation dialog before restore app 2022-01-21 01:35:11 -08:00
topjohnwu 47545b45b8 Clean up MagiskDialog 2022-01-21 00:50:02 -08:00
topjohnwu 7c9908d953 Release new canary build 2022-01-20 03:58:16 -08:00
canyie 5f4cd50cc4 Properly prevent fix env dialog from constantly showing
ViewModel has been reconstructed when switching fragment so we lost previous state.
2022-01-20 03:38:28 -08:00
canyie b0fba6ce5b Optimize navigation back stack
Fix topjohnwu#4333

Co-authored-by: LoveSy <shana@zju.edu.cn>
2022-01-20 03:38:00 -08:00
canyie 1f5992f2c2 Fix classloader when restoring bottom nav state 2022-01-20 03:36:31 -08:00
topjohnwu abfd3c3e5d Remove unused resources 2022-01-20 03:32:08 -08:00
LoveSy 97da7f9691 Do not trust format of 3rd party json 2022-01-20 02:02:39 -08:00
Ilya Kushnir 2752083d29
Update RU strings 2022-01-20 01:59:33 -08:00
John Wu c826318da4 Update CLI usage text 2022-01-20 01:59:01 -08:00
LoveSy 6582a4abd9 Make magiskpolicy supports multiple --apply 2022-01-20 01:59:01 -08:00
topjohnwu a699dab5b3 Add option to skip building for AVD commands 2022-01-20 01:42:41 -08:00
topjohnwu 21c8ad5b9e Fix up some AVD scripts 2022-01-20 01:33:59 -08:00
topjohnwu 195d885887 Reduce log spamming 2022-01-20 00:18:46 -08:00
topjohnwu 519bd2f30f Disable AVD hacks by default 2022-01-19 20:28:01 -08:00
topjohnwu 20ef724fad Add new build command avd_patch 2022-01-19 05:12:11 -08:00
vvb2060 f443cbaa2b Revert "Always run non disabled module post-fs-data scripts"
This reverts commit 4dfb193d10.
2022-01-18 04:48:47 -08:00
vvb2060 dbf45da8ab Avoid constantly check env 2022-01-18 04:47:53 -08:00
topjohnwu 6b67902d53 Uninstalled app su requests should still show in logs 2022-01-18 04:44:11 -08:00
topjohnwu 0ad0ef485c Bump min Magisk version to v21.0
Close #5220
2022-01-18 04:27:40 -08:00
topjohnwu 7dfe3e53d5 Optimize imports 2022-01-18 03:58:47 -08:00
vvb2060 5be3bd1e64 Show User-Agent 2022-01-18 02:02:41 -08:00
vvb2060 bc0c1980db Support modules update 2022-01-18 02:02:41 -08:00
vvb2060 2997258fd0 Cleanup code 2022-01-18 02:02:41 -08:00
vvb2060 11600fc116 Use libs instead of copy code 2022-01-18 02:02:41 -08:00
vvb2060 a8640f52ef Merge into one file 2022-01-18 02:02:41 -08:00
Arbri çoçka 0f4e44c38f Update values-sq 2022-01-18 00:35:55 -08:00
capntrips 053f4d481d disable virtualAB check for noSecondSlot 2022-01-18 00:35:38 -08:00
capntrips f466c27da9 disable pixel check for noSecondSlot 2022-01-18 00:35:38 -08:00
RikkaW bfe6bc3095 Fix bottom nav sometimes not hide correctly
Replace homemade animation with StateListAnimator
2022-01-18 00:24:16 -08:00
vvb2060 ff8f3e766e Update zh-rCN translation 2022-01-18 00:23:40 -08:00
vvb2060 6635ea3e29 Allow offline hide manager 2022-01-18 00:20:49 -08:00
LoveSy 591788c0df Fix an NPE
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.text.Layout.getEllipsisCount(int)' on a null object reference
2022-01-17 22:55:44 -08:00
vvb2060 571b8986a4 Don't assume installed version matches app 2022-01-17 20:29:11 -08:00
topjohnwu bb7a74e4b4 Add Zygisk API getFlags() 2022-01-17 19:54:33 -08:00
topjohnwu 76ddfeb93a Allow modifying denylist without enforcement 2022-01-15 23:46:22 -08:00
LoveSy c38b826abf Skip overlayfs for post-fs-data mount
adb remount will introduce overlayfs for /system and /vendor, we should
skip mounting as overlayfs. This also helps us support overlayfs Magisk
later.
2022-01-14 03:42:37 -08:00
topjohnwu 21d7db0959 Add new Zygisk API to get module dir 2022-01-14 03:10:02 -08:00
topjohnwu d7b51d2807 Update dependencies 2022-01-14 00:07:19 -08:00
topjohnwu a7af8b5722 Add DoH back
JSDelivr is no longer China friendly
2022-01-13 03:50:29 -08:00
topjohnwu 9c93fe6003 Update bootctl
Close #5134
2022-01-13 02:24:02 -08:00
topjohnwu 21505a7470 Update scripts for PATCHVBMETAFLAG 2022-01-12 02:29:34 -08:00
topjohnwu ba6e6cc15a Update vbmeta option hiding criteria
Hide when Samsung, A/B, or vbmeta partition exists
2022-01-12 02:29:34 -08:00
vvb2060 fd7bf2bc3a Support PATCHVBMETAFLAG env variable 2022-01-12 02:29:34 -08:00
LoveSy b2cd24ed1b Fix an UB when cil compile error 2022-01-11 03:01:27 -08:00
vvb2060 66cf2c984a Don't fix env when patch boot 2022-01-11 02:50:12 -08:00
残页 de1b2b19b0 Only store sepolicy rules into partitions in ext4 format
Fix topjohnwu#5013
When installing from recovery, previous implementation may select f2fs partitions to store sepolicy rules, but magiskinit won't mount them and unable to load sepolicy rules.
2022-01-11 02:25:34 -08:00
LoveSy e31583485d Don't prefetch env to avoid deadlock
Fix #5178
2022-01-11 00:47:06 -08:00
topjohnwu 490e51c1d7 Don't set RECOVERYMODE if recovery_dtbo exists
Apparently some boot images with ramdisk still have recovery_dtbo,
so this assumption is no longer safe to do. Expect the user to
set this option properly themselves in the app.

Fix #4976, close #5070, close #5184
2022-01-11 00:17:47 -08:00
RikkaW 1df2a04713 Find suitable anchor view for SnackBar
This will fix the SnackBar position if BottomNav or FAB is visible.

Fix #5163
Fix #5135
2022-01-10 23:10:42 -08:00
vvb2060 42804d5314 Fix stub clean task 2022-01-10 23:02:51 -08:00
vvb2060 558710bbdd Fix gradle task dependencies 2022-01-02 16:10:44 -08:00
topjohnwu f4926cb822 Small refactoring 2022-01-02 16:09:03 -08:00
topjohnwu 1e77e0862a Separate fstab finding to its own function 2022-01-02 15:49:12 -08:00
topjohnwu 8c696cb8ca Minor code refactoring 2021-12-28 23:37:06 -08:00
LoveSy 62ef8ade8f
Skip loading Magisk when detecting DSU
Fix #4402

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2021-12-28 21:04:09 -08:00
LoveSy 3d88dd3123 Update dtc to fix a UB
See https://github.com/dgibson/dtc/pull/65
2021-12-28 17:18:32 -08:00
残页 880b348ce6 Add an old cgroup path
Fix topjohnwu#5125
cgroup root path might be mem cgroup instead of acct, especially on low-ram devices.
bc131c3244%5E%21/#F0
2021-12-28 17:12:15 -08:00
残页 31fe3a1cd8 Java keywords cannot be used as package/class name 2021-12-28 17:11:38 -08:00
LoveSy 19182ffddf If dt fstab contains error, fallback to default fstab
See https://cs.android.com/android/platform/superproject/+/master:system/core/init/first_stage_mount.cpp;drc=master;l=155

From the source of `FirstStageMount`, dt fstab can fail gracefully and
if any error occurs it will fall back to default fstab. Magisk now
replaces the default fstab and dt fstab unconditionally, bringing potential
errors to the default fstab and causing init fails to load partitions.
2021-12-28 17:10:52 -08:00
vvb2060 afcc60066e Fix toast 2021-12-27 12:17:35 -08:00
vvb2060 d3ade06421 Use InputStream transfer 2021-12-27 12:17:35 -08:00
topjohnwu f1a3ef9590 Update dependencies 2021-12-27 12:17:09 -08:00
Arbri çoçka d1d73f11a5
fix same text in Albania 2021-12-27 12:01:05 -08:00
topjohnwu 05697372f8 Remove issues action
Handled by external MagiskBot
2021-12-25 17:26:03 -08:00
topjohnwu 0c1f68816e Release new canary build 2021-12-14 21:40:40 -08:00
kubalav 92546e8a74 Update Slovak translation 2021-12-14 21:25:58 -08:00
John Wu a4faa3f392
Update stub strings.xml path 2021-12-14 05:31:12 -08:00
南宫雪珊 df191cd2b5
Use AGP to compile resources 2021-12-14 05:30:15 -08:00
南宫雪珊 baa19f0ccf
Rewrite app installation
Fix  #4960
2021-12-14 05:20:29 -08:00
vvb2060 5a49bd3ac9 Add OkHttp cache 2021-12-14 05:01:12 -08:00
LoveSy b37d7e0500 Use default icon when failed to get app icon
Fix #5051
2021-12-14 04:58:18 -08:00
topjohnwu f4ed6274a4 Invert vbmeta header patching config
vbmeta header should not be patched in most cases
2021-12-14 04:52:25 -08:00
LoveSy 56eb1a1cf9 Load fstab from system/etc
Caused by this commit: e98afa2687

Fix #5057
2021-12-14 03:51:55 -08:00
LoveSy a7c156a9e3
Further fix `oplus.fstab` support
* Further fix `oplus.fstab` support

In some oneplus devices, `oplus.fstab` does exists but `init` never
loaded it and those entries in `oplus.fstab` are written directly to
`fstab.qcom`. Previous implementation will introduce duplicate entries
to `fstab.qcom` and brick the device. This commit filters those entries
from `oplus.fstab` that are already in `fstab.qcom` and further filters
duplicated entries in `oplus.fstab` (keep only the last entry).

Fix #5016

* Fix UB

Since we moved entry, we need to explicitly copy its member.
For c++23 we can use `auto{}`.
2021-12-14 03:40:23 -08:00
南宫雪珊 d81ca77231 Update gradle/wrapper/gradle-wrapper.properties 2021-12-14 03:36:01 -08:00
南宫雪珊 bf013f6ebb Fix Build 2021-12-14 03:36:01 -08:00
vvb2060 dd8116e285 Update Dependencies 2021-12-14 03:36:01 -08:00
残页 b5d80a88d1 Only care about mount namespace isolating 2021-12-14 03:08:55 -08:00
vvb2060 7f4f95cf83 Fix certificate start time 2021-12-14 03:07:38 -08:00
LoveSy 87c2f6ad14
xhook_clear after xhook_refresh 2021-12-14 03:06:57 -08:00
topjohnwu ad47dba064 Rename magiskVersion* -> version* 2021-12-14 03:01:11 -08:00
LoveSy 41b701846f ensureEnv even if getRemote fails
Co-authored-by: vvb2060 <vvb2060@gmail.com>
2021-12-14 02:57:58 -08:00
xz-dev 5c42830328 l10n: Fix typo of chinese Simplified translations 2021-12-14 02:52:38 -08:00
Allan Nordhøy 69617309f8
English language string fixes 2021-12-14 02:51:54 -08:00
topjohnwu 48e2d6a8da Simplify several hacks 2021-12-13 19:48:17 -08:00
topjohnwu b4120cddfb IODispatcherExecutor -> DispatcherExecutor 2021-12-13 04:05:42 -08:00
topjohnwu 54e3f1998a Support RootService on stub APKs 2021-12-13 04:05:42 -08:00
topjohnwu edcf9f1b0c Introduce RootServices to the app 2021-12-13 04:05:42 -08:00
topjohnwu de3747d65e Copy APK from external storage in stub
Much faster and easier development
2021-12-13 04:05:42 -08:00
vvb2060 b76a3614da Fix isolated process comparisons 2021-12-10 04:35:38 -08:00
topjohnwu 94cc64c51b Update dependencies 2021-12-10 04:32:16 -08:00
HeroBuxx 0f71edee96 magisk: README: Correct string path for stub
Signed-off-by: HeroBuxx <herobuxx@conqueros.co>
2021-12-01 21:20:24 -08:00
topjohnwu e097c097fe Rename persist_properties.cpp -> persist.cpp 2021-11-30 01:58:31 -08:00
topjohnwu 1443a5b175 Use mmap_data more widely 2021-11-30 01:50:55 -08:00
topjohnwu 2d82ad93dd Macro -> template 2021-11-29 19:56:37 -08:00
vvb2060 384c257a74 Disable CompatVectorFromResources 2021-11-29 00:06:28 -08:00
vvb2060 49dfa2c3a0 Fix update from notification will fail 2021-11-29 00:05:54 -08:00
vvb2060 7bd3e768db Remove bytecode compatibility workaround 2021-11-29 00:05:20 -08:00
vvb2060 65224ed22b Fix NPE when apk could not be parsed 2021-11-29 00:04:51 -08:00
topjohnwu 0a28dfe1e2 AVB blobs expect to be 4096-byte aligned 2021-11-28 13:21:05 -08:00
topjohnwu 1c8ebfacb0 Release new canary build 2021-11-23 22:39:15 -08:00
HuskyDG 5d6d241791
Update VN strings.xml 2021-11-23 22:24:51 -08:00
jontaix 4f116d15b9 Fix PT-rBR translation
Some translation fixes.
2021-11-23 22:24:22 -08:00
topjohnwu 228570640e Introduce KEEPVBMETAFLAG env variable
Close #4447, close #4906, close #4901, close #4964
2021-11-23 22:14:12 -08:00
topjohnwu 65a79610aa Fix crash and warnings 2021-11-23 18:46:06 -08:00
topjohnwu 24984ea4f2 Optimize stream for full-file writes 2021-11-23 18:08:14 -08:00
topjohnwu 048b2af0fc Improve zopfli encoder
Write in chunks for CLI compression
2021-11-23 16:50:08 -08:00
topjohnwu 449989ddd9 Always use zopfli for zImage compression 2021-11-23 14:24:05 -08:00
topjohnwu 01ebe5724a Cleanup zImage parsing code 2021-11-23 13:39:15 -08:00
topjohnwu 95fb230b8c Update to BusyBox 1.34.1 2021-11-22 19:46:52 -08:00
topjohnwu 632971af15 Properly support v4 image headers 2021-11-21 06:07:21 -08:00
topjohnwu 5787aa1078 Stream should always write all bytes 2021-11-21 06:05:59 -08:00
topjohnwu d8b9265484 Pull out buffer-chunk logic into separate class 2021-11-21 06:05:55 -08:00
topjohnwu 9ea3169ca9 Do not allow modifying page sizes 2021-11-20 22:51:22 -08:00
topjohnwu aebf2672cd Fix unpacking vendor boot images 2021-11-20 22:44:38 -08:00
osm0sis 68ac409bfd Scripts fixes and improvements
- ensure all scripts use $NVBASE $MAGISKBIN $POSTFSDATAD and $SERVICED where appropriate
- simplify new grep_cmdline() using xargs and more sed
- show correct active sepolicy $RULESDIR on devices with no encryption
- add support for Android 12 .capex (compressed apex) files
2021-11-20 14:17:02 -08:00
topjohnwu fef44bd24f Allow boot scripts to know Zygisk status 2021-11-20 13:05:15 -08:00
HuskyDG e4a7617dde
Update VN strings.xml 2021-11-16 21:31:21 -08:00
topjohnwu 4dfb193d10 Always run non disabled module post-fs-data scripts 2021-11-16 21:29:13 -08:00
dark-basic c248d94995 Update strings.xml 2021-11-16 21:15:02 -08:00
vvb2060 d4ac458d17 Ignore zygisk modules when zygisk is not enabled 2021-11-16 21:14:35 -08:00
Ilya Kushnir 93e443c4ad Update RU strings 2021-11-16 21:14:11 -08:00
DanGLES3 4b3988cef9
Update pt-BR translation 2021-11-16 21:13:50 -08:00
Rom 4eb5ee17b4 Fix typo in French translation 2021-11-16 21:13:13 -08:00
topjohnwu e1b63d7dec Initialize mt19937 statically in function
This reduces startup time
2021-11-16 03:20:07 -08:00
topjohnwu 4b5651bd6f Revert logging after pre specialize 2021-11-16 03:12:01 -08:00
topjohnwu 50515d9128 Close unclosed fds from modules 2021-11-16 01:59:45 -08:00
RikkaW 28b5faab0c Visual changes 2021-11-14 03:58:35 -08:00
topjohnwu 82a01c22d3 Cleanup resources 2021-11-14 00:45:39 -08:00
LoveSy be9b0c2e8f Move flow.concurrentMap to ktx 2021-11-13 11:28:11 -08:00
LoveSy b6affe06a5 Fix flow parallel 2021-11-13 11:28:11 -08:00
topjohnwu 1e05f8c646 Release new canary build 2021-11-12 03:14:56 -08:00
topjohnwu 7e9d4512b6 Update zh-rTW 2021-11-12 03:07:18 -08:00
RikkaW 5fa127c415 Disable modules section if Magisk is not active
Fix #4925
2021-11-12 03:02:26 -08:00
kubalav ac26681fe7 Update Slovak translation 2021-11-12 03:01:49 -08:00
残页 3c62636133 Update zh-rCN translation 2021-11-12 03:01:30 -08:00
Arbri çoçka ca874fa12c Update Albania translation 2021-11-12 03:00:55 -08:00
Rom c3508bbb99 Update French translation 2021-11-12 03:00:37 -08:00
topjohnwu 6935033db5 Prevent dangling pointers 2021-11-12 02:02:05 -08:00
topjohnwu 421277d730 Prevent race conditions in connect_companion 2021-11-12 01:55:55 -08:00
topjohnwu 56988944b5 No need to dup fd 2021-11-12 01:54:48 -08:00
topjohnwu 528601d25a Fix integer overflow and workaround seccomp
- Use ftruncate64 instead of ftruncate to workaround seccomp
- Cast uint32_t to off64_t before making it negative

Note: Using ftruncate with a modern NDK libc should actually be
fine as the syscall wrapper in bionic will use ftruncate64 internally.
However, since we are using the libc.a from r10e built for Gingerbread,
seccomp wasn't a thing back then, and also the ftruncate64 symbol is
missing; we have to create our own wrapper and call it instead on
32-bit ABIs.

Props to @jnotuo for discovering the overflow bug and seccomp issue

Fix #3703, close #4915
2021-11-10 03:07:20 -08:00
topjohnwu ddd153c00d Show module suspend notice
Close #4862
2021-11-09 23:59:37 -08:00
topjohnwu b8c1588284 Always unload zygisk after specialize 2021-11-07 13:05:44 -08:00
LoveSy 4dac9e40bd Support bootconfig on `util_functions.sh`
Close #4869
2021-11-07 11:22:21 -08:00
Arbri çoçka def1811d48 Fix strings in sq 2021-11-07 11:03:28 -08:00
孟武.尼德霍格.龍 c53e507713 Update Traditional Chinese Language 2021-11-07 11:03:15 -08:00
LoveSy e0ea777249 Use `ProcessCompat`
Fix #4895
2021-11-07 11:02:58 -08:00
topjohnwu 4c1962f3c7 Release new canary build 2021-11-06 23:56:50 -07:00
Chris Renshaw 258e89c964 Fix script typo for Sony init.real support
- though may still be broken on magiskinit side, see #4885
2021-11-06 23:44:43 -07:00
topjohnwu 3d3bfb42e5 Don't copy ApplicationInfo 2021-11-06 23:34:46 -07:00
topjohnwu 6dbd8baa7e Cleanup DownloadService 2021-11-06 17:45:41 -07:00
topjohnwu e660fabc57 Remove BaseDownloader 2021-11-06 04:37:06 -07:00
topjohnwu 2115bcd8b0 Relaunch and recreate is slightly different 2021-11-05 16:05:12 -07:00
topjohnwu 1bdd6e1a9d Migrate to Activity Result APIs 2021-11-05 15:53:34 -07:00
topjohnwu 98deec232b Minor adjustments 2021-11-05 13:55:18 -07:00
topjohnwu 022c217cfe Migrate to SplashScreen API 2021-11-05 04:16:58 -07:00
topjohnwu 81f57949ed Remove WorkManager as a dependency 2021-11-04 23:39:35 -07:00
topjohnwu fca5eb083f Always show checked app in list 2021-11-04 20:09:19 -07:00
topjohnwu a3695cc66b Use Kotlin coroutine instead of Java parallelStream 2021-11-04 20:09:19 -07:00
topjohnwu 6723d20616 Cleanup AppProcessInfo 2021-11-04 20:09:19 -07:00
RikkaW 627ec91687 Fix visual issues for ActionBar 2021-11-04 20:09:19 -07:00
vvb2060 9126cf0c73 Rewrite deny list UI 2021-11-04 20:09:19 -07:00
Chaosmaster 16322ab30c Use full gzip-signature to find gzip-data.
Fall back to raw image if gzip is not found.

Fixes #4849
2021-11-03 22:23:21 -07:00
Chaosmaster 5682917356 Speed up zopfli compression
See #4810 for example
2021-11-03 22:22:29 -07:00
LoveSy c91ccc8b4e Fix UB on dtb
`operator==` of string_view will create a tmp `string_view`.
It's an UB if the `const char *` is a nullptr.
`fdt_get_name` however will return a nullptr.
2021-11-03 22:21:48 -07:00
topjohnwu 63f670fc36 Move first stage unload before fork 2021-11-02 21:53:33 -07:00
LoveSy e20b07fa24 Fix #4853 2021-11-02 19:31:17 -07:00
topjohnwu 472656517f Release new canary build 2021-11-02 04:18:30 -07:00
topjohnwu d232cba02d Fix first stage unload 2021-11-02 04:12:56 -07:00
vvb2060 e49d29a914 Fix fragments lifecycleOwner 2021-11-02 03:10:29 -07:00
RikkaW 3aa1a68cdc Fix activity relaunches constantly on WSA
It's magic 💢 since change configuration should not trigger activity relaunch.
2021-11-02 03:09:06 -07:00
Hen Ry f94452083f
German Update 2021-11-02 03:08:32 -07:00
Arbri çoçka ce1ee5cb9d Fix strings in stub Albania 2021-11-02 03:07:44 -07:00
topjohnwu 48df6b8485 Use memmem instead of strstr
It might not be null terminated
2021-10-31 11:46:56 -07:00
topjohnwu ae23ae2d37 Remove code in scripts that should be removed 2021-10-31 11:30:48 -07:00
Nullptr e34e04af04 Make Api functions inline
Make Api functions inline to avoid duplicate symbols when including api.hpp in multiple cpps
2021-10-31 10:55:41 -07:00
osm0sis ff3f377911 scripts: touch up print_title
- stars aren't pounds, let's just call it a title bar :P
2021-10-31 10:53:55 -07:00
osm0sis 18065826b9 scripts: improve basic module setup
- expand utility of the basic module setup (zip without customize.sh) by setting more default perms, since really it couldn't do any simple binary files additions correctly withonly 0755 0644
- ensure CON stays local
2021-10-31 10:53:55 -07:00
topjohnwu 84e19ceef0 Tidy up bootimg.h
Close #4796
2021-10-31 10:52:12 -07:00
Chris Renshaw 59161efd08 Support Samsung 2SI with skip_initramfs in dtb cmdline
Samsung Galaxy A21S and Galaxy M12, probably others, are hdr_v2 boot.img with 2SI judging by the ramdisk contents, but the dtb contains an extra cmdline with skip_initramfs present, even though this shouldn't exist on 2SI and the kernel apparently doesn't even contain a skip_initramfs function

I can't find examples of other devices where skip_initramfs is present in the dtb other than these so patch it out like we do the kernel

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2021-10-30 21:20:10 -07:00
Chris Renshaw 6663fd3526
Support custom legacy Sony devices with init.real setup
Custom ROM bring-ups of legacy Sony devices contain the following:
/init (symlink to /bin/init_sony)
/init.real (the "real" Android init)
/bin/init_sony (this was /sbin/init_sony on Android <11)

Kernel loads the ramdisk and starts /init -> /bin/init_sony
/bin/init_sony does low-level device setup (see: https://github.com/LineageOS/android_device_sony_common/blob/lineage-18.1/init/init_main.cpp)
/bin/init_sony unlinks /init and renames /init.real to /init
/bin/init_sony starts /init

Since init_sony needs to run first magiskinit needs to replace init.real instead, so add workarounds based on detection of init.real to boot patcher and uninstaller

Thanks @115ek and @bleckdeth

Fixes #3636

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2021-10-30 18:59:20 -07:00
topjohnwu 2c44e1bb93 Update rules again 2021-10-29 03:37:14 -07:00
残页 e3f6399473 Don't use xwrite() when patching legacy rootfs init
Fix topjohnwu#4810
> [    2.927463]  [1:           init:    1] magiskinit: Replace [/system/etc/selinux/plat_sepolicy.cil] -> [xxx]
[    2.936801]  [1:           init:    1] magiskinit: write failed with 14: Bad address

Since topjohnwu#4596, magisk fails to patch `/init`, xwrite() fails with EFAULT, break the original `/init` file and make the device unbootable. Reverting this commit for legacy rootfs devices fixes the problem. I think this is a Samsung kernel magic since currently I can't reproduce this on other devices or find something special in the log currently we have.
2021-10-29 03:23:34 -07:00
残页 89c2c21774
Fix init.rc path detection
Fix #4319
Some devices store init.rc into the new path but still have the legacy /init.rc file
2021-10-29 03:21:20 -07:00
vvb2060 2954eb4bdc Remove CustomTab 2021-10-29 03:17:11 -07:00
vvb2060 e08de91666 Clean sent proguard rules 2021-10-29 03:15:31 -07:00
残页 a170acb9d7 Fix compilation when init debug toggle enabled 2021-10-29 03:15:16 -07:00
vvb2060 6a086bb222 Load *_compat_cil_file from system_ext
https://android-review.googlesource.com/c/platform/system/core/+/1650271
2021-10-29 03:14:26 -07:00
vvb2060 b2f152e641 realpath /proc/pid/cwd
prevent cross mount namespace
2021-10-29 03:13:20 -07:00
topjohnwu 6c5b261804 Update spolicy rules 2021-10-29 03:12:48 -07:00
topjohnwu 8bd0c44e83 Replace module fd with memfd if possible 2021-10-28 00:26:18 -07:00
topjohnwu 34c36984e9 Stop extreme verbose logging 2021-10-27 04:00:40 -07:00
topjohnwu 8bd6aca0dd DenyList unmount without magiskd 2021-10-27 04:00:40 -07:00
topjohnwu 983b74be77 Pass MAGISKTMP over to zygote 2021-10-27 03:25:54 -07:00
topjohnwu a3eafdd2c6 Release new canary build 2021-10-27 02:37:18 -07:00
topjohnwu ea75a09f95 Make zygisk survive zygote restarts
Close #4777
2021-10-27 01:53:16 -07:00
LoveSy 4c747c4148 Add rule: `allow * magisk_file lnk_file { * }` 2021-10-26 00:41:04 -07:00
LoveSy 49abfcafed Fix nullptr dereference when env abnormal 2021-10-26 00:40:00 -07:00
topjohnwu 50710c72ad Cleanup magiskinit code 2021-10-26 00:35:55 -07:00
vvb2060 2e299b3814 Add an old cgroup v2 path
https://android-review.googlesource.com/c/platform/system/core/+/1324649
2021-10-25 20:54:19 -07:00
topjohnwu 43d11d877d Release new canary build 2021-10-24 00:22:13 -07:00
Arbri çoçka d7e7df3bd9 Add translate Albania in stub 2021-10-23 23:55:57 -07:00
0purple 8d8ba11221 Update strings.xml 2021-10-23 23:55:38 -07:00
Ilya Kushnir 2536a18c00 Update RU strings 2021-10-23 23:55:09 -07:00
sn-o-w 11728b2b15 Update Romanian 2021-10-23 23:54:15 -07:00
green1052 627501b9ba
Update Korean translation 2021-10-23 23:53:53 -07:00
vvb2060 3599384b38 Allow fallback to /dev/pts 2021-10-23 23:31:44 -07:00
topjohnwu 4b307cad2c Random minor changes 2021-10-23 22:20:07 -07:00
topjohnwu 7496d51580 Make zygiskd ABI aware 2021-10-23 14:38:30 -07:00
topjohnwu 4194ac894c Support setting more options 2021-10-21 03:20:04 -07:00
topjohnwu ffb5d9ea9c Update libcxx 2021-10-21 02:08:13 -07:00
topjohnwu 770b28ca30 Build on API 21 headers 2021-10-20 03:17:42 -07:00
topjohnwu 62e464f706 Upgrade Android Studio 2021-10-20 01:43:33 -07:00
topjohnwu 8d0dc37ec0 Use SO_PEERSEC to get client secontext 2021-10-19 23:46:38 -07:00
topjohnwu fe41df87bb pthread_cond_signal might wake multiple threads
Close #4759
2021-10-19 21:32:37 -07:00
topjohnwu 8276a0775d Fix API doc 2021-10-17 05:42:33 -07:00
LoveSy abfb3bb3bb
Fix always log hook fails 2021-10-17 05:00:31 -07:00
LoveSy e184eb4a23 Fix UB of loading modules
- The lambda here infers its return type as `std::string`,
  and since `info` is `const`, the labmda copies `info.name`
  and returns a `std::string&&`. After captured by the
  `std::string_view`, the `std::string&&` return value
  deconstructs and makes `std::string_view` refers to a
  dangling pointer.
2021-10-17 04:38:56 -07:00
topjohnwu d0fc372ecd Implement Zygisk companion process 2021-10-17 04:36:18 -07:00
topjohnwu 6f54c57647 Allow fork in thread pool 2021-10-17 04:24:25 -07:00
topjohnwu e8ae103d5f Update jni hooks 2021-10-14 02:43:56 -07:00
topjohnwu b0198dab6c Update Zygisk logging 2021-10-14 02:13:23 -07:00
topjohnwu b75ec09998 Load Zygisk modules even if no magic mount is needed
Close #4767
2021-10-14 01:35:29 -07:00
topjohnwu c8ac6c07b0 Load Zygisk modules 2021-10-13 04:52:02 -07:00
topjohnwu 27814e3015 Minor Zygisk API changes 2021-10-09 11:53:40 -07:00
topjohnwu f59309a445 Minor changes 2021-10-09 11:36:01 -07:00
vvb2060 b0292d7319 Add execmem to allow hook 2021-10-09 02:59:03 -07:00
topjohnwu 7f18616cc0 Prune unused nodes from trie 2021-10-09 02:15:03 -07:00
topjohnwu 2fef98a5af Wipe out prop_info data after delete 2021-10-09 00:27:11 -07:00
topjohnwu 36765caedc Fix thread pool implementation
Close #4759
2021-10-08 23:28:14 -07:00
topjohnwu f7aed10ea2 Fix friend template function 2021-10-08 19:17:31 -07:00
topjohnwu 410bbb8285 Update Zygisk APIs 2021-10-05 22:42:55 -07:00
topjohnwu f56ea52932 Add public Zygisk API
Still WIP
2021-10-05 03:53:22 -07:00
vvb2060 cb4361b7b7 Fix terminal on android 8
No need to handle untrusted_app_all_devpts on modern devices, but devpts policy is different for older devices.
2021-09-25 12:08:35 -07:00
vvb2060 ecd332c573 Close fd 2021-09-25 12:07:52 -07:00
StoyanDimitrov a0fe78a728 Update Bulgarian translation
Small fixes.
2021-09-24 01:30:37 -07:00
Aryan Sinha 49cc9c529e Magisk: values-hi: Update Hindi Translation
* Fix Some Grammatical Mistakes
* Simplify Some Words
2021-09-24 01:29:42 -07:00
Arbri çoçka 7635b2c33f Update and fix some bugs in values-sq 2021-09-24 01:28:51 -07:00
Ilya Kushnir 50c26d33ab Update RU strings 2021-09-24 01:28:30 -07:00
topjohnwu f642fb3b99 Properly handle child zygote
Close #4720
2021-09-24 01:23:58 -07:00
topjohnwu e68dd866a3 Only create app_id_map if necessary 2021-09-24 00:22:19 -07:00
topjohnwu 73d36fdff0 Riru and its modules are not compatible with zygisk 2021-09-23 23:54:46 -07:00
vvb2060 5561cd3c77 Update zh-rCN translation 2021-09-23 02:45:02 -07:00
usrDottik 32a9acb913 Updated values-es strings
Added DenyList and Zygisk translations
2021-09-23 02:44:47 -07:00
DanGLES3 f7f23c6e77 Update Brazilian Portugues translation 2021-09-23 02:44:29 -07:00
Arbri çoçka 3d4edbd9dc Update strings-sq 2021-09-23 02:43:45 -07:00
kubalav bdf385f374 Update Slovak translation 2021-09-23 02:43:01 -07:00
Rom 9f78c3e64b Update French translation 2021-09-23 02:42:44 -07:00
taras f370052815 Update Ukrainian translation 2021-09-23 02:42:20 -07:00
Oliver Cervera 9df4b10067
Update Italian translation 2021-09-23 02:41:53 -07:00
vvb2060 d20517483e Prevent multiple mounts of devpts 2021-09-23 02:40:24 -07:00
Thonsi 713ce4719b Cleanup unused code 2021-09-23 02:39:48 -07:00
topjohnwu f3d39e7515 Update BusyBox
Fix #4657, close #4602
2021-09-23 02:31:42 -07:00
残页 61783ffc82 Prevent original libselinux.so to be unmounted
libselinux.so will be unmounted when magiskd starts. If magiskd restarts (like it died before boot completed), the files we want to unmount is the original files because the modified files is unmounted in previous start, which will causes many crashes due to missing libselinux.so.
2021-09-22 19:34:44 -07:00
topjohnwu 05c4ad01d5 Move first stage unload into second stage 2021-09-22 19:33:08 -07:00
topjohnwu 12647dcf30 Improve memory map tricks
- In `unmap_all`, replace readable pages atomically with mmap + mremap
- Create new function `remap_all` to replace pages with equivalent
  anonymous copies to prevent simple maps name scanning
2021-09-22 19:14:05 -07:00
topjohnwu da38f59e62 Only run destructor if necessary 2021-09-22 02:52:33 -07:00
topjohnwu cf4ef54dc5 Unload first stage on main thread 2021-09-22 02:46:07 -07:00
topjohnwu 12e9873514 Update zygisk entry implementation 2021-09-22 00:14:22 -07:00
RikkaW f7c0e407ca Fix downgrade database 2021-09-20 05:50:34 -07:00
topjohnwu 82c7662cdf Cache Magisk app ID for performance 2021-09-20 05:47:15 -07:00
topjohnwu 4f0bced53e Track app ID instead of UID 2021-09-20 05:08:25 -07:00
topjohnwu f1b6c9f4aa Refresh uid_map on package.xml change 2021-09-20 04:42:06 -07:00
topjohnwu 0ab31ab0df Fix log writer 2021-09-19 13:41:45 -07:00
topjohnwu 46e8f0779f Move denylist code into zygisk 2021-09-18 14:50:11 -07:00
topjohnwu 3fb72a4d20 Support polling on multiple fds 2021-09-18 14:40:12 -07:00
topjohnwu db20f65d7c On denylist != do unmount 2021-09-18 12:44:42 -07:00
topjohnwu 63cfe7b47b Make sanitize_environ work properly 2021-09-18 05:11:10 -07:00
topjohnwu db590091b3 Propagate Zygisk state to Magisk app 2021-09-18 02:38:53 -07:00
topjohnwu 7b25e74418 Simplify get manager app info logic 2021-09-17 02:07:32 -07:00
vvb2060 82f303e1c6 Allow save app log when not activated
may be useful for patch boot.
2021-09-16 19:44:45 -07:00
Vladimír Kubala c038683b54
Update Slovak translation 2021-09-16 19:41:43 -07:00
vvb2060 3a37ed6b60 Update zh-rCN translation 2021-09-16 19:41:19 -07:00
topjohnwu 706a492218 Update denylist config implementation 2021-09-16 05:27:34 -07:00
topjohnwu c0be5383de Support enable/disable Zygisk 2021-09-15 02:49:54 -07:00
topjohnwu 3b8ce85092 Enable Zygisk 2021-09-15 01:59:43 -07:00
topjohnwu b6298f8602 Remove more code 2021-09-13 02:00:04 -07:00
topjohnwu abfec57972 Move files around 2021-09-13 01:47:36 -07:00
topjohnwu 470fc97d1f Remove SafetyNet check 2021-09-13 01:41:31 -07:00
topjohnwu 8d59caf635 Cleanup unused code 2021-09-13 01:06:43 -07:00
topjohnwu acf25aa4d3 Remove DoH 2021-09-13 00:44:49 -07:00
topjohnwu 16de4674ec Move denylist fragment to settings 2021-09-13 00:33:36 -07:00
topjohnwu 65b0ea792e MagiskHide is no more 2021-09-12 12:40:34 -07:00
topjohnwu fc6b02f607 Move denylist fragment to its own section 2021-09-12 00:39:24 -07:00
topjohnwu 136d8c39d9 Move more code into buildSrc 2021-09-09 20:19:49 -07:00
topjohnwu 24a8b41182 Fix indentation 2021-09-09 01:37:49 -07:00
vvb2060 810cf4dee8 Move config to buildSrc 2021-09-09 01:37:20 -07:00
LoveSy 9bf835e810 Fix ccache 2021-09-08 08:53:51 -07:00
topjohnwu eca37bce38 Separate dependency and build cache 2021-09-08 01:16:26 -07:00
topjohnwu 3ee6a2baf2 Enable ccache for faster builds 2021-09-08 01:13:36 -07:00
topjohnwu 69fa7f238d Don't cache NDK 2021-09-08 01:13:36 -07:00
topjohnwu de2306bd12 Proper incremental builds
Auto generate flag.h for precise rebuilding
2021-09-07 19:35:28 -07:00
topjohnwu 714feeb9a7 Support building on case insensitive filesystems
Fix #4643, close #4641
2021-09-07 04:02:29 -07:00
topjohnwu ca99808fd2 Update AVD support
- Support arm64 AVD images
- Support setup on Windows

 Close #4637
2021-09-07 03:03:02 -07:00
topjohnwu f8f8c28fec Switch zopfli to official repo 2021-09-03 10:39:23 -07:00
vvb2060 f497867ba5 Update submodules 2021-09-03 10:24:33 -07:00
RikkaW 383192784d Use standard BottomNav & Remove hide on scroll for AppBar and BottomNav 2021-09-03 10:22:46 -07:00
vvb2060 605189bc6e Hide overlay windows 2021-09-02 23:17:01 -07:00
残页 c0a2e3674c Reset file context from adb_data_file
In some cases (like weird ROMs that allow init to relabel context from system_file), module files will have an incorrent context, which will causes module not working properly.
See https://github.com/RikkaApps/Riru/wiki/Explanation-about-incorrect-SELinux-rules-from-third-party-ROMs-cause-Riru-not-working
2021-09-02 21:55:08 -07:00
vvb2060 76f0602684 Make busybox cflag stable 2021-09-02 21:48:38 -07:00
vvb2060 477ff12cde print sepolicy rules dir 2021-09-02 21:46:43 -07:00
topjohnwu 9c09ad3b62 Open source fully obfuscated stub 2021-09-02 21:31:33 -07:00
topjohnwu a967afc629 Update macOS JAVA_HOME path 2021-09-02 02:27:05 -07:00
vvb2060 dcc1fd3ee4
Use PACKAGE_FULLY_REMOVED for magiskhide
Remove from magiskhide only if pkg fully deleted
2021-09-02 02:15:25 -07:00
vvb2060 933f020b3c Show dialog when hide or restore app 2021-09-02 02:13:31 -07:00
vvb2060 f5c02be5bf Add new targetSdk domain
https://android-review.googlesource.com/c/platform/system/sepolicy/+/1752122
2021-09-02 01:48:25 -07:00
vvb2060 68fbdd474c Target SDK 31 2021-09-02 01:39:34 -07:00
vvb2060 2cbc048352 Add mount info to log file 2021-09-02 00:48:44 -07:00
Wang Han e990ffd4a0 Remove leftover DISABLE_ZYGISK flag 2021-09-01 20:14:53 -07:00
topjohnwu 743c7c9326 App code reorganization 2021-09-01 01:17:27 -07:00
topjohnwu 067248da75 Cleanup RvItems 2021-09-01 01:17:27 -07:00
topjohnwu f5c982355a Remove online section in modules fragment 2021-09-01 01:17:27 -07:00
vvb2060 f98c68a280 Clean up unneeded rules 2021-08-29 13:03:50 -07:00
vvb2060 773bf0c6bc Do not use glob in the system's unzip parameter
https://android.googlesource.com/platform/system/core/+/refs/tags/android-10.0.0_r47/libziparchive/unzip.cpp#57 unzip did not support glob before Android 11
2021-08-29 13:03:41 -07:00
Arbri çoçka 080ab6032c update and fix some text in strings-sq 2021-08-29 11:36:07 -07:00
vvb2060 350144df29 Do not allow remove the module to be updated 2021-08-29 11:35:46 -07:00
Antikruk 9ac0f11d9a
Update Belarusian translation 2021-08-29 03:27:57 -07:00
LoveSy 8079d456ab Use std::map instead 2021-08-29 03:27:15 -07:00
vvb2060 acf166cf9d Support oplus.fstab 2021-08-29 03:27:15 -07:00
vvb2060 439d497a13 boot image header v4 2021-08-29 03:14:23 -07:00
Allan Nordhøy 0580932610 Norwegian Bokmål translation 2021-08-29 03:10:39 -07:00
Arbri çoçka 85399f609c Fix and update strings-sq 2021-08-29 03:10:01 -07:00
LoveSy 4bcfee397b Remove unnecessary umount 2021-08-29 02:45:49 -07:00
vvb2060 34bcb1dd26 Fix line editing on Android 8.0+ 2021-08-29 02:45:49 -07:00
LoveSy 117d1ed080 Fix always enter safe mode
`getprop("persist.sys.safemode", true) == "1"` -> `getprop("persist.sys.safemode", true) == ""`
2021-08-29 02:45:49 -07:00
vvb2060 f324252681 Use isolated devpts if kernel support
kernel version >= 4.7 or CONFIG_DEVPTS_MULTIPLE_INSTANCES=y
2021-08-29 02:45:49 -07:00
LoveSy 0dad06cdfe Fix meizu compatibility 2021-08-28 21:03:12 -07:00
vvb2060 9396288ca2 Check util_functions.sh version 2021-08-28 20:52:05 -07:00
LoveSy f89f08833e Generic parsing methods 2021-08-28 20:50:17 -07:00
vvb2060 79e8962854 Support bootconfig
https://android-review.googlesource.com/c/platform/system/core/+/1615298
2021-08-28 20:50:17 -07:00
topjohnwu 34e5a7cd24 Zopfli is not always smaller 2021-08-28 17:16:20 -07:00
topjohnwu 7343c195b7 Cleanup compress.cpp 2021-08-28 17:01:08 -07:00
topjohnwu 0af041b54e Cleanup magiskboot code 2021-08-28 14:07:34 -07:00
Chaosmaster 92a8a3e91f Add zopfli gzip encoder for better compression 2021-08-28 11:00:30 -07:00
Chaosmaster f41575d8b0 Add support for legacy ARM32 zImage 2021-08-28 10:53:45 -07:00
topjohnwu d93c4a5103 Update README 2021-08-28 10:45:32 -07:00
topjohnwu 6fe9b69aad Cleanup module.cpp 2021-08-28 10:27:45 -07:00
topjohnwu 5d162f81c4 Modernize db.hpp 2021-08-27 01:06:03 -07:00
topjohnwu 4771c2810b Significantly better AVD support 2021-08-26 03:09:56 -07:00
topjohnwu 0cd99712fa Implement cached thread pool 2021-08-24 02:39:54 -07:00
topjohnwu b591af7803 Minor bug fixes 2021-08-22 03:26:48 -07:00
topjohnwu 171d68ca72 Connect to magiskd log daemon 2021-08-22 03:26:48 -07:00
topjohnwu bade4f2c6a Make xhook log as Magisk 2021-08-22 03:26:48 -07:00
topjohnwu 5754782a4e Generalize gen_jni_hooks.py 2021-08-22 03:26:48 -07:00
topjohnwu decdd54c19 Hook up denylist IPC routines 2021-08-22 03:26:48 -07:00
topjohnwu ffe47300a1 Update recv/send fd function 2021-08-22 03:26:48 -07:00
topjohnwu 6f9c3c4ff3 Refactor hook.cpp 2021-08-19 01:54:12 -07:00
topjohnwu 9b3efffba9 Use magiskd to setup files 2021-08-18 03:44:32 -07:00
topjohnwu 003fea52b1 Remove all non-Magisk hiding code
Magisk no longer interferes with any signals/info that were not created
or caused by Magisk itself.
2021-08-18 02:01:54 -07:00
topjohnwu 2b17c77195 Make Zygisk 1st class citizen 2021-08-17 23:57:49 -07:00
topjohnwu c252a50fd7 The name is Zygisk 2021-08-17 23:38:40 -07:00
topjohnwu cf8f042a20 Cleanup magiskboot cpio code 2021-08-13 04:53:11 -07:00
topjohnwu 844bc2d808 Remove unused code 2021-08-13 03:30:58 -07:00
topjohnwu 27f7fa7153 Extend stream support 2021-08-13 02:08:56 -07:00
topjohnwu b325aa4555 Fix log file writing 2021-08-13 00:13:44 -07:00
topjohnwu c2c3bf0ba4 Don't depend on vtable ABI layout 2021-08-12 06:41:59 -07:00
topjohnwu 0d977b54f7 Revise logging code 2021-08-12 03:26:54 -07:00
topjohnwu 20860da4b4 Cleaner daemon handlers 2021-08-11 22:57:08 -07:00
topjohnwu 3ea10b7cf9 Reorganize injection code 2021-08-11 22:56:18 -07:00
topjohnwu 1ec33863bc Android 5.0 is actually supported 2021-08-11 17:14:22 -07:00
topjohnwu a260e99090 Support code injection on Android 12 2021-08-11 00:00:21 -07:00
topjohnwu 25efdd3d6f Use code generator for jni_hooks 2021-08-02 03:20:19 -07:00
topjohnwu 00a1e18959 Store all native JNI methods in data structures 2021-08-01 14:35:16 -07:00
topjohnwu c59f8adc4a Update Android Studio 2021-07-30 14:23:20 -07:00
topjohnwu 1eb83ad812 Update Android Studio 2021-05-16 01:26:54 -07:00
topjohnwu 7717f0a6b0 Support Android S AVD 2021-05-13 04:45:13 -07:00
topjohnwu 5e1fba3603 Build a single APK for all ABIs 2021-05-13 00:21:04 -07:00
vvb2060 66cc9bc545 Pure 64bit support 2021-05-12 16:38:34 -07:00
vvb2060 12aa5838d9 Stop gradle daemon 2021-05-12 16:38:34 -07:00
topjohnwu 4f73534837 Update installation instructions 2021-05-12 02:14:41 -07:00
topjohnwu c4d145835c Release new canary build 2021-05-11 22:40:40 -07:00
topjohnwu f822ca5b23 Update changelogs 2021-05-11 22:31:12 -07:00
topjohnwu 8aaa45c62a Release Magisk v23.0 2021-05-11 22:15:52 -07:00
topjohnwu 2f4f257070 Publish v23.0 docs 2021-05-11 22:08:02 -07:00
topjohnwu 97c1e181c5 Remove unused file 2021-05-11 21:47:46 -07:00
topjohnwu ea80cddd57 Switch to official snet.jar link 2021-05-11 21:42:58 -07:00
topjohnwu 09a294c219 Fix release builds 2021-05-11 18:40:45 -07:00
bela333 408399eae0 Update install.md 2021-05-11 11:46:23 -07:00
Davy Defaud 391852a102 Various fixes in the French translation 2021-05-11 11:45:31 -07:00
topjohnwu 5b37de8fe5 Build our own zlib 2021-05-10 18:46:03 -07:00
topjohnwu 7df23ceb74 Prevent undefined behavior in magiskboot 2021-05-10 18:38:30 -07:00
topjohnwu 6099f3b015 Always resolve to canonical path 2021-05-10 01:14:53 -07:00
topjohnwu a5cc31783c Release new canary build 2021-05-10 00:02:07 -07:00
topjohnwu 6b34ec3ab9 Fix #4194 2021-05-09 22:56:54 -07:00
topjohnwu 5c333dec33 Minor changes 2021-05-09 20:45:53 -07:00
topjohnwu 775d095b3c Update busybox
Fix #4225
2021-05-08 16:45:31 -07:00
GithubUser699 7679b5d516 Removed two "the"
At least I couldn't find a Magisk app named "The magisk app", so I removed the two "the".
2021-05-06 19:03:34 -07:00
topjohnwu 7702094053 Update dependencies 2021-05-06 11:37:21 -07:00
Wang Han 3798d50457 Kill processes with SIGKILL rather than SIGTERM 2021-05-04 22:14:46 -07:00
Shaka Huang 95e1e57407 Fix #4140 2021-05-04 22:12:18 -07:00
vvb2060 93ba4cca68 Fix copy sepolicy rules when install module 2021-05-04 22:11:10 -07:00
jenslody fe4981da21 Fix strings fallback in find_manager_apk
There is no preceding character (at least on some devices).
This regex should work in any cases, with and without preceding character.
2021-04-23 18:10:02 -07:00
jenslody e4f94c4c52 Adapt find_magisk_apk for A11
Add a fallback for Android 11's new app location.
2021-04-23 18:10:02 -07:00
vvb2060 708fe514f8 Always use mirror path 2021-04-23 16:56:23 -07:00
vvb2060 11c882380f Add warning for custom recovery users 2021-04-23 16:56:23 -07:00
vvb2060 fb93af665d Remove obsolete SDK_INT check 2021-04-23 16:56:23 -07:00
topjohnwu 0db405f2cc Release new canary build 2021-04-20 03:45:40 -07:00
topjohnwu fb8000b58b Handle invalid SafetyNet results
Fix #4253
2021-04-20 03:39:47 -07:00
topjohnwu 1b9d8e068a Remove/move unused files 2021-04-18 05:04:14 -07:00
topjohnwu 038f73a5f7 Remove Koin
Non static DI is bad
2021-04-18 04:46:11 -07:00
topjohnwu 649b49ff45 Don't hold resources in Settings objects 2021-04-18 04:14:43 -07:00
topjohnwu 1418bc454d Don't hold resources in ViewModels 2021-04-18 02:12:53 -07:00
vvb2060 29cc372bfa Fix proguard rules 2021-04-17 23:44:19 -07:00
vvb2060 69b00d3782 Update dependencies
Jcenter will sunset
2021-04-17 23:44:19 -07:00
topjohnwu a328e2bf3c Hide annoying stack traces when hidden 2021-04-17 22:35:36 -07:00
topjohnwu 4c1ea0e421 Update stub implementation
Prevent some potential issues
2021-04-17 22:14:54 -07:00
topjohnwu 7e01f9c95e Minor changes 2021-04-17 19:57:47 -07:00
topjohnwu 8b28baabd7 Release new canary build 2021-04-15 23:58:38 -07:00
Clement f49966d86e
Update french translations 2021-04-15 23:09:45 -07:00
vvb2060 f4ac7c8e7c
Ignore validating class name of isolated process name
Fix #4176

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2021-04-15 23:08:51 -07:00
Arbri çoçka 2b65e1ffc2 Update strings-sq 2021-04-15 05:02:12 -07:00
tzagim c81a3fa286
Update HE translation 2021-04-15 05:01:39 -07:00
Wang Han 44f005077d Don't copy sepolicy.rule to /persist on boot
* This seems to be a logic that has been abandoned for a
   long time. Now we automatically choose which partition
   to store sepolicy.rule. Furthermore, touching /persist is
   what we should avoid doing whenever possible.
2021-04-15 05:01:03 -07:00
LoveSy 013b6e68ec Fix perfect forwarding 2021-04-15 04:58:30 -07:00
LoveSy 95c964673d Initialized `_root` properly
Fix #4204

`_root` is uninitialized for non-root nodes. And it will cause `module_node::mount` fail because it uses `root()`. Once the bug is triggered, signal 11 is received but Magisk catch all signals and therefore stuck forever.
2021-04-15 04:58:30 -07:00
topjohnwu 94ec11db58 Update snet.jar extension
The existing API key was revoked for some reason.
Release an updated extension jar with a new API key.

In addition, add some offline signature verification and change how
results are parsed to workaround some dumbass Xposed module "faking"
success results, since many users really don't know better.
2021-04-15 04:47:57 -07:00
topjohnwu c4e8dda37c Release new canary build 2021-04-09 21:47:58 -07:00
Wang Han e136fb3a4f Remove outdated sepolicies
* Support deodexed ROM: This should not be done and dexpreopt is mandatory since P
   Xposed: Xposed handles them just fine, at least in the latest version 89.3
   suMiscL6: For whatever audio mods, a leftover of phh time
   Liveboot and suBackL6: Was for CF.lumen and LiveBoot, not needed now

 * Also cleanup binder sepolicies since we allow all binder transactions.
2021-04-09 21:34:51 -07:00
topjohnwu 01b985eded Remove more pre SDK 21 stuffs 2021-04-09 21:29:42 -07:00
topjohnwu 1f0a35f073 Set minSdkVersion to 21 2021-04-09 20:01:32 -07:00
topjohnwu 2b9b019093 It's 2021 already 2021-04-09 03:51:54 -07:00
vvb2060 10186a9e3d Refresh flag 2021-04-09 03:30:55 -07:00
topjohnwu 89d8fea7d2 Release new canary build 2021-04-09 03:28:13 -07:00
topjohnwu f623b98858 Update README 2021-04-09 03:23:52 -07:00
topjohnwu 632cee1613 Release Magisk v22.1 2021-04-09 03:05:57 -07:00
topjohnwu c0f2164bc5 Magisk v22.1 release notes 2021-04-09 02:50:41 -07:00
Wang Han f6e4a27fdd Don't export $API when initializing shell
* This becomes meanless after 9c0e189.
2021-04-09 01:47:52 -07:00
topjohnwu 257ceb99f7 SDK < 21 is EOL 2021-04-09 01:40:08 -07:00
topjohnwu 706d53065b Rename TransitiveText 2021-04-09 01:32:37 -07:00
topjohnwu 0f95a7babe Do not hold resources in SuperuserViewModel 2021-04-09 01:00:26 -07:00
topjohnwu 7cb2806878 Release new canary build 2021-04-06 04:13:41 -07:00
topjohnwu 9c0e18975c Fallback to getprop when reading system props
Close #4153
2021-04-06 03:56:49 -07:00
Shaka Huang 3da318b48e
Fix random return value of faccessat() in x86
faccessat() should return 0 when success, but it returns random number with errno == 0 in x86 platform.

It’s a side effect of commit bf80b08b5f when magisk binaries ‘corretly’ linked with library of API16 .. lol

Co-authored-by: John Wu <topjohnwu@gmail.com>
2021-04-04 03:04:09 -07:00
Shaka Huang dfe1f2c108 Call freecon() when fgetfilecon() succeeds 2021-04-04 01:58:59 -07:00
Thomas Bertels f42a87b51a
Fix spelling in French translation 2021-03-29 09:15:23 -07:00
ahmouse15 ab25857176
Update docs to use the Magisk Manager's revised name 2021-03-29 09:14:21 -07:00
topjohnwu 7da36079c1 Always delete existing ro props at setprop
Close #4113
2021-03-29 04:16:18 -07:00
topjohnwu 2bef967af1 Make systemproperties more match AOSP 2021-03-29 03:46:07 -07:00
topjohnwu 7e4194418a Update libcxx 2021-03-28 04:55:56 -07:00
topjohnwu aa02057895 Do not use -f flag for readlink
Close #4104, fix #4098
2021-03-28 04:47:13 -07:00
topjohnwu fb8dc07599 Release new canary build 2021-03-25 02:09:51 -07:00
topjohnwu 66e30a7723 Build libc++ ourselves 2021-03-25 01:00:10 -07:00
vvb2060 0298ab99c4 Update AGP 2021-03-24 04:43:45 -07:00
vvb2060 d11358671e Fix isolated process display 2021-03-24 04:43:45 -07:00
vvb2060 8b5cb4c7b0 Fix #3735 2021-03-24 04:43:45 -07:00
vvb2060 aad52ae743 Fix UID removed action 2021-03-24 04:43:45 -07:00
vvb2060 8ddab84745 Don't auto hide microG
close #3559
2021-03-24 04:43:45 -07:00
vvb2060 6865652125 Fix process name in MagiskHide
close #3997
2021-03-24 04:43:45 -07:00
topjohnwu ed4d0867e8 Make sure navigation happens on main thread
Fix #4044
2021-03-24 03:23:11 -07:00
Kazuki H 1c71e02454 Update Japanese translations 2021-03-24 03:10:21 -07:00
Matthew Mirvish f332e87cab
Ensure the installer knows the API version when running from addon.d 2021-03-24 03:08:59 -07:00
osm0sis 023dbc6cb5 scripts: fix empty module cleanup
- should be sufficient for all basic modules, see https://github.com/topjohnwu/Magisk/issues/3119#issuecomment-704000783 for ideas for fixing it further on the daemon module-processing side

Fixes #3119
2021-03-24 03:06:57 -07:00
osm0sis 4dd3f55407 App: add versionCode to magisk_patched.img filenames 2021-03-24 03:06:57 -07:00
osm0sis 7b9a71c9af scripts: improve boot_patch 64bit detection
- use existing api_level_arch_detect function

Fixes #3961
2021-03-24 03:06:57 -07:00
osm0sis 901d22cdfa scripts: add boot_patch unpack error catching
- failure to unpack wasn't being caught so boot_patch would continue to build a new cpio out of nothing and attempt to repack it (identified in #4049)
2021-03-24 03:06:57 -07:00
osm0sis 93e1266ee7 scripts: fix find_boot_image using wrong partition list on non-slot
- revert logic changes introduced by ec8fffe61c which break find_boot_image on NAND devices and any others using non-standard naming supported by the A-only device boot partition name list
- despite being accepted equivalents in modern shells -n does not work on Android in some shell/env scenarios where ! -z always does
2021-03-24 03:06:57 -07:00
osm0sis 0a4e7eea41 scripts: clean up remaining Manager references 2021-03-24 03:06:57 -07:00
Shaka Huang e3801d6965
Fix overflow
`totalsize` might be a big (invalid) number so instead of checking the end address we check the size of the image.

Fix #4049
2021-03-24 03:02:46 -07:00
topjohnwu 336f1687c1 Be more careful with signals
Fix #4040
2021-03-18 03:28:02 -07:00
topjohnwu d4e2f2df6e Release new canary build 2021-03-16 05:47:29 -07:00
topjohnwu f152b4c26e Make LiveData nullable 2021-03-16 05:34:54 -07:00
topjohnwu bd935b0553 Cleanup fragment navigations 2021-03-16 04:58:02 -07:00
topjohnwu a9b3b7a359 Update dependencies 2021-03-16 03:44:25 -07:00
vvb2060 7a007b342a Correct comment
For file-based encryption, /data/adb is always required to encrypt
https://android.googlesource.com/platform/system/extras/+/refs/tags/android-7.0.0_r36/ext4_utils/ext4_crypt_init_extensions.cpp
68258e8444%5E%21/
2021-03-13 21:10:02 -08:00
vvb2060 0783f3d5b6 Fix mount rules dir
close #4006
2021-03-13 21:10:02 -08:00
Rikka afe3c2bc1b
Fix "rm_rf" in build.py on Windows
prebuilt/windows-x86_64/bin/libpython2.7.dll
prebuilt/windows-x86_64/lib/python2.7/config/libpython2.7.dll.a

These two files in NDK has read-only attribute on Windows, remove these files with Python will get "WindowsError: [Error 5] Access is denied". It will finally make "build.py ndk" unable to remove the "magisk" folder.

This commit add a onerror callback for "shutil.rmtree" which clear the "read-only" attribute and retry.
2021-03-13 17:51:39 -08:00
topjohnwu 82f8948fd4 Separate setting log functions and starting log daemon 2021-03-13 17:50:48 -08:00
Shaka Huang b9cdc755d1 Returned fds[0] in socketpair() might be STDOUT
* There will be garbage output when executing `su` (#4016)
* Failed to check root status and showing N/A in status (#4005)

Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2021-03-13 17:50:48 -08:00
topjohnwu a6f81c66e5 Bypass stdio 2021-03-13 16:17:28 -08:00
topjohnwu 1ff45ac5f5 Proper pattern matching
Fix #3998
2021-03-09 04:08:34 -08:00
Alexandru Scurtu 48bde7375f
uninstaller: consistency improvements
since there's no more "Magisk Manager"
2021-03-09 03:05:47 -08:00
topjohnwu 0601fa3b3d Release new canary build 2021-03-09 02:59:07 -08:00
vvb2060 d0d3c8dbfd Disable blank issues 2021-03-09 02:51:20 -08:00
vvb2060 8057de1973 Auto close issues 2021-03-09 02:51:20 -08:00
topjohnwu 43c1105d62 Use dedicated thread for writing logfile 2021-03-09 02:40:12 -08:00
topjohnwu 6adf516b30 Release new canary build 2021-03-07 04:39:47 -08:00
topjohnwu bf80b08b5f Fix build script 2021-03-07 04:34:06 -08:00
topjohnwu 3e0b1df46d Update README 2021-03-07 04:12:32 -08:00
topjohnwu 84811c80b6 Release new canary build 2021-03-07 02:51:10 -08:00
LLZN 45e0df9c57 Update strings.xml 2021-03-07 01:56:02 -08:00
vvb2060 bc51ce7c7b Fix reboot menu 2021-03-07 01:55:19 -08:00
vvb2060 b693d13b93 Proper implementation of cgroup migration
https://www.kernel.org/doc/Documentation/admin-guide/cgroup-v1/cgroups.rst
https://www.kernel.org/doc/Documentation/admin-guide/cgroup-v2.rst
2021-03-07 01:55:19 -08:00
topjohnwu 39982d57ef Fix logging implementation
- Block signals in logging routine (fix #3976)
- Prevent possible deadlock after fork (stdio locks internally)
  by creating a new FILE pointer per logging call (thread/stack local)
2021-03-06 13:55:30 -08:00
topjohnwu 15e27e54fb Migrate to new endpoints 2021-03-05 05:09:25 -08:00
topjohnwu 851404205b Update NDK to r21e 2021-03-02 23:18:44 -08:00
topjohnwu 117ae71025 Use custom class instead of std::map 2021-03-02 23:16:10 -08:00
topjohnwu 027ec70262 Patch AVB structures
Disable vbmeta verification in flags
2021-03-02 20:37:37 -08:00
topjohnwu 55fdee4d65 Use memmem for searching byte patterns 2021-02-28 14:37:12 -08:00
topjohnwu 0d42f937dd Refactor magiskboot 2021-02-28 14:37:12 -08:00
vvb2060 ac8372dd26 Add cgroup2 path
https://android-review.googlesource.com/c/platform/system/core/+/1585101
2021-02-26 21:36:58 -08:00
vvb2060 5e56a6bbee Fix isolated process name before Android 10 2021-02-26 21:36:58 -08:00
etmatrix 3c6c409df0 Fix #3916 2021-02-25 21:25:21 -08:00
vvb2060 d05408c89f Delete outdated policies when refresh 2021-02-25 20:08:42 -08:00
vvb2060 ee0ec3fbfa Use UID_REMOVED action for multi-user and shared user id compatibility 2021-02-25 20:08:42 -08:00
vvb2060 122a73e086 Always show hidden apps 2021-02-25 20:08:42 -08:00
omerakgoz34 29a9b18c4c Update Turkish translation 2021-02-25 19:56:05 -08:00
孟武.尼德霍格.龍 1561272109 更新繁體中文
更新並改善繁體中文的翻譯
2021-02-25 19:55:25 -08:00
Ilya Kushnir 3e61ab0d25
Update RU strings 2021-02-25 19:54:58 -08:00
Francesco Saltori a49dc6ccb7
Update Italian translation 2021-02-25 19:54:21 -08:00
topjohnwu 60f3d62f00 Proper synchronization 2021-02-24 02:50:55 -08:00
topjohnwu e613855a4f Do not check PXA header signatures
Fix #3876
2021-02-24 02:27:42 -08:00
sn-o-w 22662d7e03 Update Romanian 2021-02-24 02:08:46 -08:00
Arbri çoçka 6e7e5be1a2
Update values-sq 2021-02-24 02:06:42 -08:00
vvb2060 8b2ab778c9 Fix show canary channel on stable build 2021-02-24 02:06:20 -08:00
vvb2060 35f3766ecf Update zh-rCN translation 2021-02-24 02:05:33 -08:00
Rom 995304dabb
Update French translation 2021-02-24 02:05:16 -08:00
topjohnwu 803982a271 Prevent multiple installation sessions running in parallel 2021-02-24 01:45:10 -08:00
topjohnwu 9164bf22c2 Update terminology 2021-02-23 23:56:58 -08:00
topjohnwu 911a576893 Publish new canary build 2021-02-23 04:36:49 -08:00
topjohnwu 79ee85c0f9 Update README 2021-02-23 04:22:32 -08:00
772 changed files with 40636 additions and 35758 deletions

View File

@ -1,21 +1,22 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
title: ""
labels: ""
assignees: ""
---
<!--
## READ BEFORE OPENING ISSUES
All bug reports require you to **USE CANARY BUILDS**. Please include the version name and version code in the bug report.
All bug reports require you to **USE DEBUG BUILDS**. Please include the version name and version code in the bug report.
If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT ROOT**.
If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT MAGISK**.
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk Manager, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
If you experience a crash of Magisk Manager, dump the full `logcat` **when the crash happens**. **DO NOT** upload `magisk.log`.
If you experience a crash of Magisk app, dump the full `logcat` **when the crash happens**.
If you experience other issues related to Magisk, upload `magisk.log`, and preferably also include a boot `logcat` (start dumping `logcat` when the device boots up)
@ -26,3 +27,10 @@ If you experience other issues related to Magisk, upload `magisk.log`, and prefe
**DO NOT** report issues if you have any modules installed.
Without following the rules above, your issue will be closed without explanation.
-->
Device:
Android version:
Magisk version name:
Magisk version code:

6
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,6 @@
blank_issues_enabled: false
contact_links:
- name: XDA Community Support
url: https://forum.xda-developers.com/f/magisk.5903/
about: Please ask and answer questions here.

44
.github/actions/setup/action.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Magisk Setup
runs:
using: "composite"
steps:
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
- name: Set up Python 3
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Set up sccache
uses: hendrikmuhs/ccache-action@v1.2
with:
variant: sccache
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }}
max-size: 10000M
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Cache build cache
uses: actions/cache@v4
with:
path: |
~/.gradle/caches/build-cache-*
key: ${{ runner.os }}-build-cache-${{ github.sha }}
restore-keys: ${{ runner.os }}-build-cache-
- name: Set up NDK
run: python build.py -v ndk
shell: bash

View File

@ -2,89 +2,153 @@ name: Magisk Build
on:
push:
branches: [ master ]
branches: [master]
paths:
- 'app/**'
- 'native/**'
- 'stub/**'
- 'buildSrc/**'
- 'build.py'
- 'gradle.properties'
- "app/**"
- "native/**"
- "stub/**"
- "buildSrc/**"
- "build.py"
- "gradle.properties"
- ".github/workflows/build.yml"
pull_request:
branches: [ master ]
branches: [master]
workflow_dispatch:
jobs:
build:
name: Build on ${{ matrix.os }}
name: Build Magisk artifacts
runs-on: ubuntu-latest
env:
SCCACHE_DIRECT: false
strategy:
fail-fast: false
steps:
- name: Check out
uses: actions/checkout@v4
with:
submodules: "recursive"
fetch-depth: 0
- name: Setup environment
uses: ./.github/actions/setup
- name: Build release
run: ./build.py -vr all
- name: Build debug
run: ./build.py -v all
- name: Stop gradle daemon
run: ./gradlew --stop
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ${{ github.sha }}
path: out
compression-level: 9
- name: Upload mapping and native debug symbols
uses: actions/upload-artifact@v4
with:
name: ${{ github.sha }}-symbols
path: app/build/outputs
compression-level: 9
test-build:
name: Test building on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
SCCACHE_DIRECT: false
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macOS-latest ]
os: [windows-latest, macos-14]
steps:
- name: Check out
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: 'recursive'
submodules: "recursive"
fetch-depth: 0
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: '11'
- name: Set up Python 3
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set up GitHub env (Windows)
if: runner.os == 'Windows'
run: |
$ndk_ver = Select-String -Path "gradle.properties" -Pattern "^magisk.fullNdkVersion=" | % { $_ -replace ".*=" }
echo "ANDROID_SDK_ROOT=$env:ANDROID_SDK_ROOT" >> $env:GITHUB_ENV
echo "MAGISK_NDK_VERSION=$ndk_ver" >> $env:GITHUB_ENV
echo "GRADLE_OPTS=-Dorg.gradle.daemon=false" >> $env:GITHUB_ENV
- name: Set up GitHub env (Unix)
if: runner.os != 'Windows'
run: |
ndk_ver=$(sed -n 's/^magisk.fullNdkVersion=//p' gradle.properties)
echo ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT >> $GITHUB_ENV
echo MAGISK_NDK_VERSION=$ndk_ver >> $GITHUB_ENV
- name: Cache Gradle
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Cache NDK
id: ndk-cache
uses: actions/cache@v2
with:
path: ${{ env.ANDROID_SDK_ROOT }}/ndk/magisk
key: ${{ runner.os }}-ndk-${{ env.MAGISK_NDK_VERSION }}
- name: Set up NDK
if: steps.ndk-cache.outputs.cache-hit != 'true'
run: python build.py ndk
- name: Build release
run: python build.py -vr all
- name: Setup environment
uses: ./.github/actions/setup
- name: Build debug
run: python build.py -v all
# Only upload artifacts built on Linux
- name: Upload build artifact
if: runner.os == 'Linux'
uses: actions/upload-artifact@v2
- name: Stop gradle daemon
run: ./gradlew --stop
test:
name: Test x86_64 on API ${{ matrix.api }}
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
api: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: ${{ github.sha }}
path: out
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: AVD test
run: scripts/avd_test.sh ${{ matrix.api }}
test-32:
name: Test x86 on API ${{ matrix.api }}
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
api: [23, 24, 25, 26, 27, 28, 29, 30]
steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: ${{ github.sha }}
path: out
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: AVD test
env:
FORCE_32_BIT: 1
run: scripts/avd_test.sh ${{ matrix.api }}

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ out
*.apk
/config.prop
/update.sh
/dict.txt
# Built binaries
native/out

45
.gitmodules vendored
View File

@ -1,33 +1,42 @@
[submodule "selinux"]
path = native/jni/external/selinux
path = native/src/external/selinux
url = https://github.com/topjohnwu/selinux.git
[submodule "busybox"]
path = native/jni/external/busybox
path = native/src/external/busybox
url = https://github.com/topjohnwu/ndk-busybox.git
[submodule "dtc"]
path = native/jni/external/dtc
url = https://github.com/dgibson/dtc
[submodule "lz4"]
path = native/jni/external/lz4
path = native/src/external/lz4
url = https://github.com/lz4/lz4.git
[submodule "bzip2"]
path = native/jni/external/bzip2
path = native/src/external/bzip2
url = https://github.com/nemequ/bzip2.git
[submodule "xz"]
path = native/jni/external/xz
path = native/src/external/xz
url = https://github.com/xz-mirror/xz.git
[submodule "nanopb"]
path = native/jni/external/nanopb
url = https://github.com/nanopb/nanopb.git
[submodule "mincrypt"]
path = native/jni/external/mincrypt
url = https://github.com/topjohnwu/mincrypt.git
[submodule "pcre"]
path = native/jni/external/pcre
path = native/src/external/pcre
url = https://android.googlesource.com/platform/external/pcre
[submodule "xhook"]
path = native/jni/external/xhook
url = https://github.com/iqiyi/xHook.git
[submodule "libcxx"]
path = native/src/external/libcxx
url = https://github.com/topjohnwu/libcxx.git
[submodule "zlib"]
path = native/src/external/zlib
url = https://android.googlesource.com/platform/external/zlib
[submodule "zopfli"]
path = native/src/external/zopfli
url = https://github.com/google/zopfli.git
[submodule "cxx-rs"]
path = native/src/external/cxx-rs
url = https://github.com/topjohnwu/cxx.git
[submodule "lsplt"]
path = native/src/external/lsplt
url = https://github.com/LSPosed/LSPlt.git
[submodule "system_properties"]
path = native/src/external/system_properties
url = https://github.com/topjohnwu/system_properties.git
[submodule "crt0"]
path = native/src/external/crt0
url = https://github.com/topjohnwu/crt0.git
[submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git

View File

@ -1,74 +1,45 @@
![](docs/images/logo.png)
![ZIP Downloads](https://img.shields.io/badge/dynamic/json?color=blue&label=ZIP%20Downloads&query=magisk&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk_files%2Fcount%2Fcount.json&cacheSeconds=1800)
![APK Downloads](https://img.shields.io/badge/dynamic/json?color=green&label=APK%20Downloads&query=manager&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk_files%2Fcount%2Fcount.json&cacheSeconds=1800)
[![Downloads](https://img.shields.io/badge/dynamic/json?color=green&label=Downloads&query=totalString&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk-files%2Fcount%2Fcount.json&cacheSeconds=1800)](https://raw.githubusercontent.com/topjohnwu/magisk-files/count/count.json)
#### This is not an officially supported Google product
## Introduction
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2. It covers fundamental parts of Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 6.0.<br>
Some highlight features:
Here are some feature highlights:
- **MagiskSU**: Provide root access to your device
- **MagiskSU**: Provide root access for applications
- **Magisk Modules**: Modify read-only partitions by installing modules
- **MagiskHide**: Hide Magisk from root detections / system integrity checks
- **MagiskBoot**: The most complete tool for unpacking and repacking Android boot images
- **Zygisk**: Run code in every Android applications' processes
## Downloads
Please note that the only source of official Magisk information and downloads is [Github](https://github.com/topjohnwu/Magisk/). Beware of any other websites.
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
[![](https://img.shields.io/badge/Magisk%20Manager-v8.0.7-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.7/MagiskManager-v8.0.7.apk)
[![](https://img.shields.io/badge/Magisk%20Manager-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
<br>
[![](https://img.shields.io/badge/Magisk-v21.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.4)
[![](https://img.shields.io/badge/Magisk%20Beta-v21.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.4)
[![](https://img.shields.io/badge/Magisk-v27.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
[![](https://img.shields.io/badge/Magisk%20Beta-v27.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk)
[![](https://img.shields.io/badge/Magisk-Debug-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
## Useful Links
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
- [Frequently Asked Questions](https://topjohnwu.github.io/Magisk/faq.html)
- [Building and Development](https://topjohnwu.github.io/Magisk/build.html)
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
- [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan))
## Android Version Support
- Android 4.2+: MagiskSU and Magisk Modules Only
- Android 4.4+: All core features available
- Android 6.0+: Guaranteed MagiskHide support
- Android 7.0+: Full MagiskHide protection
- Android 9.0+: Magisk Manager stealth mode
## Bug Reports
Canary Channels are cutting edge builds for those adventurous. To access canary builds, install the Canary Magisk Manager, switch to the Canary Channel in settings and upgrade.
**Only bug reports from Canary builds will be accepted.**
**Only bug reports from Debug builds will be accepted.**
For installation issues, upload both boot image and install logs.<br>
For Magisk issues, upload boot logcat or dmesg.<br>
For Magisk Manager crashes, record and upload the logcat when the crash occurs.
## Building and Development
- Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups.
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
- Install Python 3.6+ \
(Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install)
- Configure to use the JDK bundled in Android Studio:
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"`
- Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"`
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
- Set environment variable `ANDROID_SDK_ROOT` to the Android SDK folder (can be found in Android Studio settings)
- Run `./build.py ndk` to let the script download and install NDK for you
- To start building, run `build.py` to see your options. \
For each action, use `-h` to access help (e.g. `./build.py all -h`)
- To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native (C++/C) sources.
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
- To sign APKs and zips with your own private keys, set signing configs in `config.prop`. For more info, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key).
For Magisk app crashes, record and upload the logcat when the crash occurs.
## Translation Contributions
Default string resources for Magisk Manager and its stub APK are located here:
Default string resources for the Magisk app and its stub APK are located here:
- `app/src/main/res/values/strings.xml`
- `stub/src/main/res/values/strings.xml`

7
app/.gitignore vendored
View File

@ -3,10 +3,9 @@
/local.properties
.idea/
/build
app/release
*.hprof
.externalNativeBuild/
*.apk
src/main/assets
src/main/jniLibs
src/main/resources
src/*/assets
src/*/jniLibs
src/*/resources

View File

@ -1,6 +1,3 @@
import org.apache.tools.ant.filters.FixCrLfFilter
import java.io.PrintStream
plugins {
id("com.android.application")
kotlin("android")
@ -16,243 +13,109 @@ kapt {
javacOptions {
option("-Xmaxerrs", 1000)
}
arguments {
arg("room.incremental", "true")
}
}
android {
namespace = "com.topjohnwu.magisk"
defaultConfig {
applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
versionName = Config.version
versionCode = Config.versionCode
ndk.abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
javaCompileOptions.annotationProcessorOptions.arguments(
mapOf("room.incremental" to "true")
)
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
debugSymbolLevel = "FULL"
}
}
buildTypes {
getByName("release") {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
proguardFiles("proguard-rules.pro")
}
}
buildFeatures {
dataBinding = true
aidl = true
}
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
packagingOptions {
exclude("/META-INF/*")
exclude("/org/bouncycastle/**")
exclude("/kotlin/**")
exclude("/kotlinx/**")
exclude("/okhttp3/**")
exclude("/*.txt")
exclude("/*.bin")
doNotStrip("**/*.so")
}
kotlinOptions {
jvmTarget = "1.8"
packaging {
resources {
excludes += "/META-INF/*"
excludes += "/META-INF/versions/**"
excludes += "/org/bouncycastle/**"
excludes += "/kotlin/**"
excludes += "/kotlinx/**"
excludes += "/okhttp3/**"
excludes += "/*.txt"
excludes += "/*.bin"
excludes += "/*.json"
}
}
}
val syncLibs by tasks.registering(Sync::class) {
into("src/main/jniLibs")
into("armeabi-v7a") {
from(rootProject.file("native/out/armeabi-v7a")) {
include("busybox", "magiskboot", "magiskinit", "magisk")
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
}
from(rootProject.file("native/out/arm64-v8a")) {
include("magisk")
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
}
}
into("x86") {
from(rootProject.file("native/out/x86")) {
include("busybox", "magiskboot", "magiskinit", "magisk")
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
}
from(rootProject.file("native/out/x86_64")) {
include("magisk")
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
}
}
onlyIf {
if (inputs.sourceFiles.files.size != 10)
throw StopExecutionException("Please build binaries first! (./build.py binary)")
true
}
}
setupApp()
val createStubLibs by tasks.registering {
dependsOn(syncLibs)
doLast {
val arm64 = project.file("src/main/jniLibs/arm64-v8a/libstub.so")
arm64.parentFile.mkdirs()
arm64.createNewFile()
val x64 = project.file("src/main/jniLibs/x86_64/libstub.so")
x64.parentFile.mkdirs()
x64.createNewFile()
}
}
val syncAssets by tasks.registering(Sync::class) {
dependsOn(createStubLibs)
inputs.property("version", Config.version)
inputs.property("versionCode", Config.versionCode)
into("src/main/assets")
from(rootProject.file("scripts")) {
include("util_functions.sh", "boot_patch.sh", "uninstaller.sh", "addon.d.sh")
}
into("chromeos") {
from(rootProject.file("tools/futility"))
from(rootProject.file("tools/keys")) {
include("kernel_data_key.vbprivk", "kernel.keyblock")
}
}
filesMatching("**/util_functions.sh") {
filter {
it.replace("#MAGISK_VERSION_STUB",
"MAGISK_VER='${Config.version}'\n" +
"MAGISK_VER_CODE=${Config.versionCode}")
}
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
}
}
val syncResources by tasks.registering(Sync::class) {
dependsOn(syncAssets)
into("src/main/resources/META-INF/com/google/android")
from(rootProject.file("scripts/update_binary.sh")) {
rename { "update-binary" }
}
from(rootProject.file("scripts/flash_script.sh")) {
rename { "updater-script" }
}
}
tasks["preBuild"]?.dependsOn(syncResources)
android.applicationVariants.all {
val keysDir = rootProject.file("tools/keys")
val outSrcDir = File(buildDir, "generated/source/keydata/$name")
val outSrc = File(outSrcDir, "com/topjohnwu/signing/KeyData.java")
fun PrintStream.newField(name: String, file: File) {
println("public static byte[] $name() {")
print("byte[] buf = {")
val bytes = file.readBytes()
print(bytes.joinToString(",") { "(byte)(${it.toInt() and 0xff})" })
println("};")
println("return buf;")
println("}")
}
val genSrcTask = tasks.register("generate${name.capitalize()}KeyData") {
inputs.dir(keysDir)
outputs.file(outSrc)
doLast {
outSrc.parentFile.mkdirs()
PrintStream(outSrc).use {
it.println("package com.topjohnwu.signing;")
it.println("public final class KeyData {")
it.newField("testCert", File(keysDir, "testkey.x509.pem"))
it.newField("testKey", File(keysDir, "testkey.pk8"))
it.newField("verityCert", File(keysDir, "verity.x509.pem"))
it.newField("verityKey", File(keysDir, "verity.pk8"))
it.println("}")
}
}
}
registerJavaGeneratingTask(genSrcTask.get(), outSrcDir)
configurations.all {
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7")
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(kotlin("stdlib"))
implementation(project(":app:shared"))
implementation("com.github.topjohnwu:jtar:1.0.0")
implementation("com.github.topjohnwu:jtar:1.1.0")
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
implementation("com.github.topjohnwu:lz4-java:1.7.1")
implementation("com.jakewharton.timber:timber:4.7.1")
implementation("com.jakewharton.timber:timber:5.0.1")
implementation("org.bouncycastle:bcpkix-jdk18on:1.77")
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2")
implementation("io.noties.markwon:core:4.6.2")
val vBC = "1.68"
implementation("org.bouncycastle:bcprov-jdk15on:${vBC}")
implementation("org.bouncycastle:bcpkix-jdk15on:${vBC}")
val vBAdapt = "4.0.0"
val bindingAdapter = "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter"
implementation("${bindingAdapter}:${vBAdapt}")
implementation("${bindingAdapter}-recyclerview:${vBAdapt}")
val vMarkwon = "4.6.1"
implementation("io.noties.markwon:core:${vMarkwon}")
implementation("io.noties.markwon:html:${vMarkwon}")
implementation("io.noties.markwon:image:${vMarkwon}")
implementation("com.caverock:androidsvg:1.4")
val vLibsu = "3.1.1"
val vLibsu = "5.2.2"
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
implementation("com.github.topjohnwu.libsu:io:${vLibsu}")
val vKoin = "2.1.6"
implementation("org.koin:koin-core:${vKoin}")
implementation("org.koin:koin-android:${vKoin}")
implementation("org.koin:koin-androidx-viewmodel:${vKoin}")
implementation("com.github.topjohnwu.libsu:service:${vLibsu}")
implementation("com.github.topjohnwu.libsu:nio:${vLibsu}")
val vRetrofit = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
val vOkHttp = "3.12.12"
implementation("com.squareup.okhttp3:okhttp") {
version {
strictly(vOkHttp)
}
}
val vOkHttp = "4.12.0"
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
val vMoshi = "1.11.0"
val vMoshi = "1.15.0"
implementation("com.squareup.moshi:moshi:${vMoshi}")
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
val vRoom = "2.3.0-beta01"
val vRoom = "2.6.1"
implementation("androidx.room:room-runtime:${vRoom}")
implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}")
val vNav: String by rootProject.extra
val vNav = "2.7.7"
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
implementation("androidx.biometric:biometric:1.1.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.browser:browser:1.3.0")
implementation("androidx.preference:preference:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.1.0")
implementation("androidx.fragment:fragment-ktx:1.2.5")
implementation("androidx.work:work-runtime-ktx:2.5.0")
implementation("androidx.transition:transition:1.4.0")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.core:core-ktx:1.3.2")
implementation("com.google.android.material:material:1.3.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.transition:transition:1.4.1")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
implementation("com.google.android.material:material:1.11.0")
}

View File

@ -1,36 +1,31 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/topjohnwu/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Parcelable
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
# Kotlin
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void check*(...);
public static void throw*(...);
}
# Snet
-keepclassmembers class com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback
-keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback {
void onResponse(org.json.JSONObject);
-assumenosideeffects class java.util.Objects {
public static ** requireNonNull(...);
}
-assumenosideeffects public class kotlin.coroutines.jvm.internal.DebugMetadataKt {
private static ** getDebugMetadataAnnotation(...) return null;
}
# Stub
-keep class com.topjohnwu.magisk.core.App { <init>(java.lang.Object); }
-keepclassmembers class androidx.appcompat.app.AppCompatDelegateImpl {
boolean mActivityHandlesConfigFlagsChecked;
int mActivityHandlesConfigFlags;
}
# main
-keep,allowoptimization public class com.topjohnwu.magisk.signing.SignBoot {
public static void main(java.lang.String[]);
}
# Strip Timber verbose and debug logging
-assumenosideeffects class timber.log.Timber$Tree {
@ -38,11 +33,31 @@
public void d(**);
}
# https://github.com/square/retrofit/issues/3751#issuecomment-1192043644
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# Excessive obfuscation
-repackageclasses 'a'
-allowaccessmodification
# QOL
-dontnote **
-dontwarn com.caverock.androidsvg.**
-dontwarn ru.noties.markwon.**
-obfuscationdictionary ../dict.txt
-classobfuscationdictionary ../dict.txt
-packageobfuscationdictionary ../dict.txt
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.commonmark.ext.gfm.strikethrough.Strikethrough
-dontwarn org.conscrypt.Conscrypt*
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

View File

@ -2,13 +2,8 @@ plugins {
id("com.android.library")
}
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
consumerProguardFiles("proguard-rules.pro")
}
}
setupCommon()
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
android {
namespace = "com.topjohnwu.shared"
}

View File

@ -1,25 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepclassmembers class * extends javax.net.ssl.SSLSocketFactory {
** delegate;
}

View File

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute" />
</manifest>

View File

@ -1,12 +1,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.shared"
android:installLocation="internalOnly">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
android:maxSdkVersion="33" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
@ -20,8 +28,6 @@
android:label="Magisk"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute" />
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
</manifest>

View File

@ -1,68 +0,0 @@
package com.topjohnwu.magisk;
import android.content.Context;
import android.content.res.AssetManager;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Map;
import static android.os.Build.VERSION.SDK_INT;
public class DynAPK {
// Indices of the object array
private static final int STUB_VERSION_ENTRY = 0;
private static final int CLASS_COMPONENT_MAP = 1;
private static File dynDir;
private static Method addAssetPath;
private static File getDynDir(Context c) {
if (dynDir == null) {
if (SDK_INT >= 24) {
// Use protected context to allow directBootAware
c = c.createDeviceProtectedStorageContext();
}
dynDir = new File(c.getFilesDir().getParent(), "dyn");
dynDir.mkdir();
}
return dynDir;
}
public static File current(Context c) {
return new File(getDynDir(c), "current.apk");
}
public static File update(Context c) {
return new File(getDynDir(c), "update.apk");
}
public static Data load(Object o) {
Object[] arr = (Object[]) o;
Data data = new Data();
data.version = (int) arr[STUB_VERSION_ENTRY];
data.classToComponent = (Map<String, String>) arr[CLASS_COMPONENT_MAP];
return data;
}
public static Object pack(Data data) {
Object[] arr = new Object[2];
arr[STUB_VERSION_ENTRY] = data.version;
arr[CLASS_COMPONENT_MAP] = data.classToComponent;
return arr;
}
public static void addAssetPath(AssetManager asset, String path) {
try {
if (addAssetPath == null)
addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(asset, path);
} catch (Exception ignored) {}
}
public static class Data {
public int version;
public Map<String, String> classToComponent;
}
}

View File

@ -1,341 +0,0 @@
package com.topjohnwu.magisk;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Modified from androidx.core.content.FileProvider
*/
public class FileProvider extends ContentProvider {
private static final String[] COLUMNS = {
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
private static final File DEVICE_ROOT = new File("/");
private static HashMap<String, PathStrategy> sCache = new HashMap<>();
private PathStrategy mStrategy;
@Override
public boolean onCreate() {
return true;
}
@Override
public void attachInfo(Context context, ProviderInfo info) {
super.attachInfo(context, info);
if (info.exported) {
throw new SecurityException("Provider must not be exported");
}
if (!info.grantUriPermissions) {
throw new SecurityException("Provider must grant uri permissions");
}
mStrategy = getPathStrategy(context, info.authority);
}
public static Uri getUriForFile(Context context, String authority,
File file) {
final PathStrategy strategy = getPathStrategy(context, authority);
return strategy.getUriForFile(file);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs,
String sortOrder) {
final File file = mStrategy.getFileForUri(uri);
if (projection == null) {
projection = COLUMNS;
}
String[] cols = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = file.getName();
} else if (OpenableColumns.SIZE.equals(col)) {
cols[i] = OpenableColumns.SIZE;
values[i++] = file.length();
}
}
cols = copyOf(cols, i);
values = copyOf(values, i);
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
@Override
public String getType(Uri uri) {
final File file = mStrategy.getFileForUri(uri);
final int lastDot = file.getName().lastIndexOf('.');
if (lastDot >= 0) {
final String extension = file.getName().substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "application/octet-stream";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("No external inserts");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException("No external updates");
}
@Override
public int delete(Uri uri, String selection,
String[] selectionArgs) {
final File file = mStrategy.getFileForUri(uri);
return file.delete() ? 1 : 0;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
final File file = mStrategy.getFileForUri(uri);
final int fileMode = modeToMode(mode);
return ParcelFileDescriptor.open(file, fileMode);
}
private static PathStrategy getPathStrategy(Context context, String authority) {
PathStrategy strat;
synchronized (sCache) {
strat = sCache.get(authority);
if (strat == null) {
strat = createPathStrategy(context, authority);
sCache.put(authority, strat);
}
}
return strat;
}
private static PathStrategy createPathStrategy(Context context, String authority) {
final SimplePathStrategy strat = new SimplePathStrategy(authority);
strat.addRoot("root_files", buildPath(DEVICE_ROOT, "."));
strat.addRoot("internal_files", buildPath(context.getFilesDir(), "."));
strat.addRoot("cache_files", buildPath(context.getCacheDir(), "."));
strat.addRoot("external_files", buildPath(Environment.getExternalStorageDirectory(), "."));
{
File[] externalFilesDirs = getExternalFilesDirs(context, null);
if (externalFilesDirs.length > 0) {
strat.addRoot("external_file_files", buildPath(externalFilesDirs[0], "."));
}
}
{
File[] externalCacheDirs = getExternalCacheDirs(context);
if (externalCacheDirs.length > 0) {
strat.addRoot("external_cache_files", buildPath(externalCacheDirs[0], "."));
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
File[] externalMediaDirs = context.getExternalMediaDirs();
if (externalMediaDirs.length > 0) {
strat.addRoot("external_media_files", buildPath(externalMediaDirs[0], "."));
}
}
return strat;
}
interface PathStrategy {
Uri getUriForFile(File file);
File getFileForUri(Uri uri);
}
static class SimplePathStrategy implements PathStrategy {
private final String mAuthority;
private final HashMap<String, File> mRoots = new HashMap<>();
SimplePathStrategy(String authority) {
mAuthority = authority;
}
void addRoot(String name, File root) {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException("Name must not be empty");
}
try {
root = root.getCanonicalFile();
} catch (IOException e) {
throw new IllegalArgumentException(
"Failed to resolve canonical path for " + root, e);
}
mRoots.put(name, root);
}
@Override
public Uri getUriForFile(File file) {
String path;
try {
path = file.getCanonicalPath();
} catch (IOException e) {
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
}
Map.Entry<String, File> mostSpecific = null;
for (Map.Entry<String, File> root : mRoots.entrySet()) {
final String rootPath = root.getValue().getPath();
if (path.startsWith(rootPath) && (mostSpecific == null
|| rootPath.length() > mostSpecific.getValue().getPath().length())) {
mostSpecific = root;
}
}
if (mostSpecific == null) {
throw new IllegalArgumentException(
"Failed to find configured root that contains " + path);
}
final String rootPath = mostSpecific.getValue().getPath();
if (rootPath.endsWith("/")) {
path = path.substring(rootPath.length());
} else {
path = path.substring(rootPath.length() + 1);
}
path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
return new Uri.Builder().scheme("content")
.authority(mAuthority).encodedPath(path).build();
}
@Override
public File getFileForUri(Uri uri) {
String path = uri.getEncodedPath();
final int splitIndex = path.indexOf('/', 1);
final String tag = Uri.decode(path.substring(1, splitIndex));
path = Uri.decode(path.substring(splitIndex + 1));
final File root = mRoots.get(tag);
if (root == null) {
throw new IllegalArgumentException("Unable to find configured root for " + uri);
}
File file = new File(root, path);
try {
file = file.getCanonicalFile();
} catch (IOException e) {
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
}
if (!file.getPath().startsWith(root.getPath())) {
throw new SecurityException("Resolved path jumped beyond configured root");
}
return file;
}
}
private static int modeToMode(String mode) {
int modeBits;
if ("r".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
} else if ("w".equals(mode) || "wt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else if ("wa".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_APPEND;
} else if ("rw".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE;
} else if ("rwt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else {
throw new IllegalArgumentException("Invalid mode: " + mode);
}
return modeBits;
}
private static File buildPath(File base, String... segments) {
File cur = base;
for (String segment : segments) {
if (segment != null) {
cur = new File(cur, segment);
}
}
return cur;
}
private static String[] copyOf(String[] original, int newLength) {
final String[] result = new String[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
private static Object[] copyOf(Object[] original, int newLength) {
final Object[] result = new Object[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
private static File[] getExternalFilesDirs(Context context, String type) {
if (Build.VERSION.SDK_INT >= 19) {
return context.getExternalFilesDirs(type);
} else {
return new File[] { context.getExternalFilesDir(type) };
}
}
private static File[] getExternalCacheDirs(Context context) {
if (Build.VERSION.SDK_INT >= 19) {
return context.getExternalCacheDirs();
} else {
return new File[] { context.getExternalCacheDir() };
}
}
}

View File

@ -0,0 +1,21 @@
package com.topjohnwu.magisk;
import android.content.Context;
public class ProviderInstaller {
public static boolean install(Context context) {
try {
// Try installing new SSL provider from Google Play Service
Context gms = context.createPackageContext("com.google.android.gms",
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
gms.getClassLoader()
.loadClass("com.google.android.gms.common.security.ProviderInstallerImpl")
.getMethod("insertProvider", Context.class)
.invoke(null, gms);
} catch (Exception e) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,120 @@
package com.topjohnwu.magisk;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.loader.ResourcesLoader;
import android.content.res.loader.ResourcesProvider;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
public class StubApk {
private static File dynDir;
private static Method addAssetPath;
private static File getDynDir(ApplicationInfo info) {
if (dynDir == null) {
final String dataDir;
if (SDK_INT >= Build.VERSION_CODES.N) {
// Use device protected path to allow directBootAware
dataDir = info.deviceProtectedDataDir;
} else {
dataDir = info.dataDir;
}
dynDir = new File(dataDir, "dyn");
dynDir.mkdirs();
}
return dynDir;
}
public static File current(Context c) {
return new File(getDynDir(c.getApplicationInfo()), "current.apk");
}
public static File current(ApplicationInfo info) {
return new File(getDynDir(info), "current.apk");
}
public static File update(Context c) {
return new File(getDynDir(c.getApplicationInfo()), "update.apk");
}
public static File update(ApplicationInfo info) {
return new File(getDynDir(info), "update.apk");
}
@TargetApi(Build.VERSION_CODES.R)
private static ResourcesLoader getResourcesLoader(File path) throws IOException {
var loader = new ResourcesLoader();
ResourcesProvider provider;
if (path.isDirectory()) {
provider = ResourcesProvider.loadFromDirectory(path.getPath(), null);
} else {
var fd = ParcelFileDescriptor.open(path, MODE_READ_ONLY);
provider = ResourcesProvider.loadFromApk(fd);
}
loader.addProvider(provider);
return loader;
}
public static void addAssetPath(Resources res, String path) {
if (SDK_INT >= Build.VERSION_CODES.R) {
try {
res.addLoaders(getResourcesLoader(new File(path)));
} catch (IOException ignored) {}
} else {
AssetManager asset = res.getAssets();
try {
if (addAssetPath == null)
addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(asset, path);
} catch (Exception ignored) {}
}
}
public static void restartProcess(Activity activity) {
Intent intent = activity.getPackageManager()
.getLaunchIntentForPackage(activity.getPackageName());
activity.finishAffinity();
activity.startActivity(intent);
Runtime.getRuntime().exit(0);
}
public static class Data {
// Indices of the object array
private static final int STUB_VERSION = 0;
private static final int CLASS_COMPONENT_MAP = 1;
private static final int ROOT_SERVICE = 2;
private static final int ARR_SIZE = 3;
private final Object[] arr;
public Data() { arr = new Object[ARR_SIZE]; }
public Data(Object o) { arr = (Object[]) o; }
public Object getObject() { return arr; }
public int getVersion() { return (int) arr[STUB_VERSION]; }
public void setVersion(int version) { arr[STUB_VERSION] = version; }
public Map<String, String> getClassToComponent() {
// noinspection unchecked
return (Map<String, String>) arr[CLASS_COMPONENT_MAP];
}
public void setClassToComponent(Map<String, String> map) {
arr[CLASS_COMPONENT_MAP] = map;
}
public Class<?> getRootService() { return (Class<?>) arr[ROOT_SERVICE]; }
public void setRootService(Class<?> service) { arr[ROOT_SERVICE] = service; }
}
}

View File

@ -1,70 +0,0 @@
package com.topjohnwu.magisk.net;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class NoSSLv3SocketFactory extends SSLSocketFactory {
private final static SSLSocketFactory delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
private Socket createSafeSocket(Socket socket) {
if (socket instanceof SSLSocket)
return new SSLSocketWrapper((SSLSocket) socket) {
@Override
public void setEnabledProtocols(String[] protocols) {
List<String> proto = new ArrayList<>(Arrays.asList(getSupportedProtocols()));
proto.remove("SSLv3");
super.setEnabledProtocols(proto.toArray(new String[0]));
}
};
return socket;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return createSafeSocket(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket() throws IOException {
return createSafeSocket(delegate.createSocket());
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return createSafeSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return createSafeSocket(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return createSafeSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return createSafeSocket(delegate.createSocket(address, port, localAddress, localPort));
}
}

View File

@ -1,333 +0,0 @@
package com.topjohnwu.magisk.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
class SSLSocketWrapper extends SSLSocket {
private SSLSocket mBase;
SSLSocketWrapper(SSLSocket socket) {
mBase = socket;
}
@Override
public String[] getSupportedCipherSuites() {
return mBase.getSupportedCipherSuites();
}
@Override
public String[] getEnabledCipherSuites() {
return mBase.getEnabledCipherSuites();
}
@Override
public void setEnabledCipherSuites(String[] suites) {
mBase.setEnabledCipherSuites(suites);
}
@Override
public String[] getSupportedProtocols() {
return mBase.getSupportedProtocols();
}
@Override
public String[] getEnabledProtocols() {
return mBase.getEnabledProtocols();
}
@Override
public void setEnabledProtocols(String[] protocols) {
mBase.setEnabledProtocols(protocols);
}
@Override
public SSLSession getSession() {
return mBase.getSession();
}
@Override
public SSLSession getHandshakeSession() {
throw new UnsupportedOperationException();
}
@Override
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
mBase.addHandshakeCompletedListener(listener);
}
@Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
mBase.removeHandshakeCompletedListener(listener);
}
@Override
public void startHandshake() throws IOException {
mBase.startHandshake();
}
@Override
public void setUseClientMode(boolean mode) {
mBase.setUseClientMode(mode);
}
@Override
public boolean getUseClientMode() {
return mBase.getUseClientMode();
}
@Override
public void setNeedClientAuth(boolean need) {
mBase.setNeedClientAuth(need);
}
@Override
public boolean getNeedClientAuth() {
return mBase.getNeedClientAuth();
}
@Override
public void setWantClientAuth(boolean want) {
mBase.setWantClientAuth(want);
}
@Override
public boolean getWantClientAuth() {
return mBase.getWantClientAuth();
}
@Override
public void setEnableSessionCreation(boolean flag) {
mBase.setEnableSessionCreation(flag);
}
@Override
public boolean getEnableSessionCreation() {
return mBase.getEnableSessionCreation();
}
@Override
public SSLParameters getSSLParameters() {
return mBase.getSSLParameters();
}
@Override
public void setSSLParameters(SSLParameters params) {
mBase.setSSLParameters(params);
}
@Override
public String toString() {
return mBase.toString();
}
@Override
public void connect(SocketAddress endpoint) throws IOException {
mBase.connect(endpoint);
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
mBase.connect(endpoint, timeout);
}
@Override
public void bind(SocketAddress bindpoint) throws IOException {
mBase.bind(bindpoint);
}
@Override
public InetAddress getInetAddress() {
return mBase.getInetAddress();
}
@Override
public InetAddress getLocalAddress() {
return mBase.getLocalAddress();
}
@Override
public int getPort() {
return mBase.getPort();
}
@Override
public int getLocalPort() {
return mBase.getLocalPort();
}
@Override
public SocketAddress getRemoteSocketAddress() {
return mBase.getRemoteSocketAddress();
}
@Override
public SocketAddress getLocalSocketAddress() {
return mBase.getLocalSocketAddress();
}
@Override
public SocketChannel getChannel() {
return mBase.getChannel();
}
@Override
public InputStream getInputStream() throws IOException {
return mBase.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
return mBase.getOutputStream();
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
mBase.setTcpNoDelay(on);
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return mBase.getTcpNoDelay();
}
@Override
public void setSoLinger(boolean on, int linger) throws SocketException {
mBase.setSoLinger(on, linger);
}
@Override
public int getSoLinger() throws SocketException {
return mBase.getSoLinger();
}
@Override
public void sendUrgentData(int data) throws IOException {
mBase.sendUrgentData(data);
}
@Override
public void setOOBInline(boolean on) throws SocketException {
mBase.setOOBInline(on);
}
@Override
public boolean getOOBInline() throws SocketException {
return mBase.getOOBInline();
}
@Override
public void setSoTimeout(int timeout) throws SocketException {
mBase.setSoTimeout(timeout);
}
@Override
public int getSoTimeout() throws SocketException {
return mBase.getSoTimeout();
}
@Override
public void setSendBufferSize(int size) throws SocketException {
mBase.setSendBufferSize(size);
}
@Override
public int getSendBufferSize() throws SocketException {
return mBase.getSendBufferSize();
}
@Override
public void setReceiveBufferSize(int size) throws SocketException {
mBase.setReceiveBufferSize(size);
}
@Override
public int getReceiveBufferSize() throws SocketException {
return mBase.getReceiveBufferSize();
}
@Override
public void setKeepAlive(boolean on) throws SocketException {
mBase.setKeepAlive(on);
}
@Override
public boolean getKeepAlive() throws SocketException {
return mBase.getKeepAlive();
}
@Override
public void setTrafficClass(int tc) throws SocketException {
mBase.setTrafficClass(tc);
}
@Override
public int getTrafficClass() throws SocketException {
return mBase.getTrafficClass();
}
@Override
public void setReuseAddress(boolean on) throws SocketException {
mBase.setReuseAddress(on);
}
@Override
public boolean getReuseAddress() throws SocketException {
return mBase.getReuseAddress();
}
@Override
public void close() throws IOException {
mBase.close();
}
@Override
public void shutdownInput() throws IOException {
mBase.shutdownInput();
}
@Override
public void shutdownOutput() throws IOException {
mBase.shutdownOutput();
}
@Override
public boolean isConnected() {
return mBase.isConnected();
}
@Override
public boolean isBound() {
return mBase.isBound();
}
@Override
public boolean isClosed() {
return mBase.isClosed();
}
@Override
public boolean isInputShutdown() {
return mBase.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return mBase.isOutputShutdown();
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
mBase.setPerformancePreferences(connectionTime, latency, bandwidth);
}
}

View File

@ -1,48 +1,169 @@
package com.topjohnwu.magisk.utils;
import android.app.Activity;
import static android.content.pm.PackageInstaller.EXTRA_SESSION_ID;
import static android.content.pm.PackageInstaller.EXTRA_STATUS;
import static android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID;
import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
import static android.content.pm.PackageInstaller.STATUS_SUCCESS;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInstaller.SessionParams;
import android.net.Uri;
import android.os.Build;
import com.topjohnwu.magisk.FileProvider;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class APKInstall {
public final class APKInstall {
public static Intent installIntent(Context c, File apk) {
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) {
intent.setData(FileProvider.getUriForFile(c, c.getPackageName() + ".provider", apk));
} else {
//noinspection ResultOfMethodCallIgnored SetWorldReadable
apk.setReadable(true, false);
intent.setData(Uri.fromFile(apk));
public static void transfer(InputStream in, OutputStream out) throws IOException {
int size = 8192;
var buffer = new byte[size];
int read;
while ((read = in.read(buffer, 0, size)) >= 0) {
out.write(buffer, 0, read);
}
return intent;
}
public static void install(Context c, File apk) {
c.startActivity(installIntent(c, apk));
public static void registerReceiver(
Context context, BroadcastReceiver receiver, IntentFilter filter) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// noinspection InlinedApi
context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
context.registerReceiver(receiver, filter);
}
}
public static void registerInstallReceiver(Context c, BroadcastReceiver r) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
c.getApplicationContext().registerReceiver(r, filter);
public static Session startSession(Context context) {
return startSession(context, null, null, null);
}
public static void installHideResult(Activity c, File apk) {
Intent intent = installIntent(c, apk);
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
c.startActivityForResult(intent, 0); // Ignore result, use install receiver
public static Session startSession(Context context, String pkg,
Runnable onFailure, Runnable onSuccess) {
var receiver = new InstallReceiver(pkg, onSuccess, onFailure);
context = context.getApplicationContext();
if (pkg != null) {
// If pkg is not null, look for package added event
var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
registerReceiver(context, receiver, filter);
}
registerReceiver(context, receiver, new IntentFilter(receiver.sessionId));
return receiver;
}
public interface Session {
// @WorkerThread
OutputStream openStream(Context context) throws IOException;
// @WorkerThread @Nullable
Intent waitIntent();
}
private static class InstallReceiver extends BroadcastReceiver implements Session {
private final String packageName;
private final Runnable onSuccess;
private final Runnable onFailure;
private final CountDownLatch latch = new CountDownLatch(1);
private Intent userAction = null;
final String sessionId = UUID.randomUUID().toString();
private InstallReceiver(String packageName, Runnable onSuccess, Runnable onFailure) {
this.packageName = packageName;
this.onSuccess = onSuccess;
this.onFailure = onFailure;
}
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
Uri data = intent.getData();
if (data == null)
return;
String pkg = data.getSchemeSpecificPart();
if (pkg.equals(packageName)) {
onSuccess(context);
}
} else if (sessionId.equals(intent.getAction())) {
int status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID);
switch (status) {
case STATUS_PENDING_USER_ACTION ->
userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT);
case STATUS_SUCCESS -> {
if (packageName == null) {
onSuccess(context);
}
}
default -> {
int id = intent.getIntExtra(EXTRA_SESSION_ID, 0);
var installer = context.getPackageManager().getPackageInstaller();
try {
installer.abandonSession(id);
} catch (SecurityException ignored) {
}
if (onFailure != null) {
onFailure.run();
}
context.getApplicationContext().unregisterReceiver(this);
}
}
latch.countDown();
}
}
private void onSuccess(Context context) {
if (onSuccess != null)
onSuccess.run();
context.getApplicationContext().unregisterReceiver(this);
}
@Override
public Intent waitIntent() {
try {
// noinspection ResultOfMethodCallIgnored
latch.await(5, TimeUnit.SECONDS);
} catch (Exception ignored) {}
return userAction;
}
@Override
public OutputStream openStream(Context context) throws IOException {
// noinspection InlinedApi
var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;
var intent = new Intent(sessionId).setPackage(context.getPackageName());
var pending = PendingIntent.getBroadcast(context, 0, intent, flag);
var installer = context.getPackageManager().getPackageInstaller();
var params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED);
}
var session = installer.openSession(installer.createSession(params));
var out = session.openWrite(sessionId, 0, -1);
return new FilterOutputStream(out) {
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
@Override
public void close() throws IOException {
super.close();
session.commit(pending.getIntentSender());
session.close();
}
};
}
}
}

View File

@ -1,30 +1,35 @@
package com.topjohnwu.magisk.utils;
import android.os.Process;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import dalvik.system.DexClassLoader;
import dalvik.system.BaseDexClassLoader;
public class DynamicClassLoader extends DexClassLoader {
public class DynamicClassLoader extends BaseDexClassLoader {
private ClassLoader base = Object.class.getClassLoader();
public DynamicClassLoader(File apk) {
this(apk, getSystemClassLoader());
}
public DynamicClassLoader(File apk, ClassLoader parent) {
super(apk.getPath(), apk.getParent(), null, parent);
// Set optimizedDirectory to null for RootService to bypass DexFile's security checks
super(apk.getPath(), Process.myUid() == 0 ? null : apk.getParentFile(), null, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// First check if already loaded
Class cls = findLoadedClass(name);
Class<?> cls = findLoadedClass(name);
if (cls != null)
return cls;
try {
// Then check boot classpath
return base.loadClass(name);
return getSystemClassLoader().loadClass(name);
} catch (ClassNotFoundException ignored) {
try {
// Next try current dex
@ -42,7 +47,7 @@ public class DynamicClassLoader extends DexClassLoader {
@Override
public URL getResource(String name) {
URL resource = base.getResource(name);
URL resource = getSystemClassLoader().getResource(name);
if (resource != null)
return resource;
resource = findResource(name);
@ -54,7 +59,7 @@ public class DynamicClassLoader extends DexClassLoader {
@Override
public Enumeration<URL> getResources(String name) throws IOException {
return new CompoundEnumeration<>(base.getResources(name),
return new CompoundEnumeration<>(getSystemClassLoader().getResources(name),
findResources(name), getParent().getResources(name));
}
}

View File

@ -1,20 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"
tools:node="remove" />
<uses-permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
tools:node="remove" />
<application
android:name=".core.App"
android:extractNativeLibs="true"
android:icon="@drawable/ic_launcher"
android:multiArch="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
tools:remove="android:appComponentFactory">
<!-- Splash -->
<activity
android:name=".core.SplashActivity"
android:name=".ui.MainActivity"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -26,15 +32,11 @@
</intent-filter>
</activity>
<!-- Main -->
<activity android:name=".ui.MainActivity" />
<!-- Superuser -->
<activity
android:name=".ui.surequest.SuRequestActivity"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="false"
android:taskAffinity=""
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -42,13 +44,13 @@
</intent-filter>
</activity>
<!-- Receiver -->
<receiver
android:name=".core.Receiver"
android:directBootAware="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="android.intent.action.UID_REMOVED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
@ -58,10 +60,17 @@
</intent-filter>
</receiver>
<!-- DownloadService -->
<service android:name=".core.download.DownloadService" />
<service
android:name=".core.Service"
android:exported="false"
android:enabled="@bool/enable_fg_service"
android:foregroundServiceType="dataSync" />
<service
android:name=".core.JobService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
<!-- FileProvider -->
<provider
android:name=".core.Provider"
android:authorities="${applicationId}.provider"
@ -69,23 +78,22 @@
android:exported="false"
android:grantUriPermissions="true" />
<!-- Hardcode GMS version -->
<meta-data
android:name="com.google.android.gms.version"
android:value="12451000" />
<!-- Initialize WorkManager on-demand -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove"
tools:ignore="ExportedContentProvider" />
<!-- We don't invalidate Room -->
<service
android:name="androidx.room.MultiInstanceInvalidationService"
tools:node="remove" />
<!-- We handle initialization ourselves -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<!-- We handle profile installation ourselves -->
<receiver
android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
</application>
</manifest>

View File

@ -0,0 +1,9 @@
// IRootUtils.aidl
package com.topjohnwu.magisk.core.utils;
// Declare any non-default types here with import statements
interface IRootUtils {
android.app.ActivityManager.RunningAppProcessInfo getAppProcess(int pid);
IBinder getFileSystem();
}

View File

@ -0,0 +1,22 @@
package com.topjohnwu.magisk.arch
import androidx.annotation.MainThread
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
abstract class AsyncLoadViewModel : BaseViewModel() {
private var loadingJob: Job? = null
@MainThread
fun startLoading() {
if (loadingJob?.isActive == true) {
// Prevent multiple jobs from running at the same time
return
}
loadingJob = viewModelScope.launch { doLoadWork() }
}
protected abstract suspend fun doLoadWork()
}

View File

@ -5,28 +5,28 @@ import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.Insets
import androidx.core.view.MenuProvider
import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.ktx.startAnimations
abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
Fragment(), BaseUIComponent<VM> {
abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHolder {
val activity get() = requireActivity() as BaseUIActivity<*, *>
val activity get() = getActivity() as? NavigationActivity<*>
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
override val viewRoot: View get() = binding.root
private val navigation get() = activity.navigation
private val navigation get() = activity?.navigation
open val snackbarView: View? get() = null
open val snackbarAnchorView: View? get() = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startObserveEvents()
startObserveLiveData()
}
override fun onCreateView(
@ -36,19 +36,27 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
): View? {
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).also {
it.setVariable(BR.viewModel, viewModel)
it.lifecycleOwner = this
it.lifecycleOwner = viewLifecycleOwner
}
if (this is MenuProvider) {
activity?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.STARTED)
}
savedInstanceState?.let { viewModel.onRestoreState(it) }
return binding.root
}
override fun onSaveInstanceState(outState: Bundle) {
viewModel.onSaveState(outState)
}
override fun onStart() {
super.onStart()
activity.supportActionBar?.subtitle = null
activity?.supportActionBar?.subtitle = null
}
override fun onEventDispatched(event: ViewEvent) = when(event) {
is ContextExecutor -> event(requireContext())
is ActivityExecutor -> event(activity)
is ActivityExecutor -> activity?.let { event(it) } ?: Unit
is FragmentExecutor -> event(this)
else -> Unit
}
@ -64,7 +72,7 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
super.onViewCreated(view, savedInstanceState)
binding.addOnRebindCallback(object : OnRebindCallback<Binding>() {
override fun onPreBind(binding: Binding): Boolean {
this@BaseUIFragment.onPreBind(binding)
this@BaseFragment.onPreBind(binding)
return true
}
})
@ -72,7 +80,10 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
override fun onResume() {
super.onResume()
viewModel.requestRefresh()
viewModel.let {
if (it is AsyncLoadViewModel)
it.startLoading()
}
}
protected open fun onPreBind(binding: Binding) {
@ -80,13 +91,6 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
}
fun NavDirections.navigate() {
navigation?.navigate(this)
navigation?.currentDestination?.getAction(actionId)?.let { navigation!!.navigate(this) }
}
}
interface ReselectionTarget {
fun onReselected()
}

View File

@ -1,140 +0,0 @@
package com.topjohnwu.magisk.arch
import android.content.res.Resources
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.res.use
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.MutableLiveData
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.ui.inflater.LayoutInflaterFactory
import com.topjohnwu.magisk.ui.theme.Theme
abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
BaseActivity(), BaseUIComponent<VM> {
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
protected open val themeRes: Int = Theme.selected.themeRes
private val navHostFragment by lazy {
supportFragmentManager.findFragmentById(navHost) as? NavHostFragment
}
private val topFragment get() = navHostFragment?.childFragmentManager?.fragments?.getOrNull(0)
protected val currentFragment get() = topFragment as? BaseUIFragment<*, *>
override val viewRoot: View get() = binding.root
open val navigation: NavController? get() = navHostFragment?.navController
open val navHost: Int = 0
open val snackbarView get() = binding.root
init {
val theme = Config.darkTheme
AppCompatDelegate.setDefaultNightMode(theme)
}
override fun onCreate(savedInstanceState: Bundle?) {
layoutInflater.factory2 = LayoutInflaterFactory(delegate)
setTheme(themeRes)
super.onCreate(savedInstanceState)
startObserveEvents()
// We need to set the window background explicitly since for whatever reason it's not
// propagated upstream
obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
.use { it.getDrawable(0) }
.also { window.setBackgroundDrawable(it) }
directionsDispatcher.observe(this) {
it?.navigate()
// we don't want the directions to be re-dispatched, so we preemptively set them to null
if (it != null) {
directionsDispatcher.value = null
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window?.decorView?.let {
it.systemUiVisibility = (it.systemUiVisibility
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window?.decorView?.post {
// If navigation bar is short enough (gesture navigation enabled), make it transparent
if (window.decorView.rootWindowInsets?.systemWindowInsetBottom ?: 0 < Resources.getSystem().displayMetrics.density * 40) {
window.navigationBarColor = Color.TRANSPARENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.navigationBarDividerColor = Color.TRANSPARENT
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
window.isStatusBarContrastEnforced = false
}
}
}
}
}
}
fun setContentView() {
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).also {
it.setVariable(BR.viewModel, viewModel)
it.lifecycleOwner = this
}
}
fun setAccessibilityDelegate(delegate: View.AccessibilityDelegate?) {
viewRoot.rootView.accessibilityDelegate = delegate
}
override fun onResume() {
super.onResume()
viewModel.requestRefresh()
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
return currentFragment?.onKeyEvent(event) == true || super.dispatchKeyEvent(event)
}
override fun onEventDispatched(event: ViewEvent) = when (event) {
is ContextExecutor -> event(this)
is ActivityExecutor -> event(this)
else -> Unit
}
override fun onBackPressed() {
if (navigation == null || currentFragment?.onBackPressed()?.not() == true) {
super.onBackPressed()
}
}
fun NavDirections.navigate() {
navigation?.navigate(this)
}
companion object {
private val directionsDispatcher = MutableLiveData<NavDirections?>()
fun postDirections(navDirections: NavDirections) =
directionsDispatcher.postValue(navDirections)
}
}

View File

@ -1,21 +0,0 @@
package com.topjohnwu.magisk.arch
import android.view.View
import androidx.lifecycle.LifecycleOwner
interface BaseUIComponent<VM : BaseViewModel> : LifecycleOwner {
val viewRoot: View
val viewModel: VM
fun startObserveEvents() {
viewModel.viewEvents.observe(this) {
onEventDispatched(it)
}
}
/**
* Called for all [ViewEvent]s published by associated viewModel.
*/
fun onEventDispatched(event: ViewEvent) {}
}

View File

@ -1,93 +1,41 @@
package com.topjohnwu.magisk.arch
import android.Manifest
import android.os.Build
import androidx.annotation.CallSuper
import androidx.core.graphics.Insets
import androidx.databinding.Bindable
import androidx.databinding.Observable
import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.events.*
import com.topjohnwu.magisk.utils.ObservableHost
import com.topjohnwu.magisk.utils.set
import kotlinx.coroutines.Job
import org.koin.core.KoinComponent
import com.topjohnwu.magisk.databinding.ObservableHost
import com.topjohnwu.magisk.events.BackPressEvent
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.events.DialogEvent
import com.topjohnwu.magisk.events.NavigationEvent
import com.topjohnwu.magisk.events.PermissionEvent
import com.topjohnwu.magisk.events.SnackbarEvent
abstract class BaseViewModel(
initialState: State = State.LOADING
) : ViewModel(), ObservableHost, KoinComponent {
abstract class BaseViewModel : ViewModel(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
enum class State {
LOADED, LOADING, LOADING_FAILED
}
@get:Bindable
val loading get() = state == State.LOADING
@get:Bindable
val loaded get() = state == State.LOADED
@get:Bindable
val loadFailed get() = state == State.LOADING_FAILED
val isConnected get() = Info.isConnected
private val _viewEvents = MutableLiveData<ViewEvent>()
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
@get:Bindable
var insets = Insets.NONE
set(value) = set(value, field, { field = it }, BR.insets)
var state= initialState
set(value) = set(value, field, { field = it }, BR.loading, BR.loaded, BR.loadFailed)
private val _viewEvents = MutableLiveData<ViewEvent>()
private var runningJob: Job? = null
private val refreshCallback = object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
requestRefresh()
}
}
init {
isConnected.addOnPropertyChangedCallback(refreshCallback)
}
/** This should probably never be called manually, it's called manually via delegate. */
@Synchronized
fun requestRefresh() {
if (runningJob?.isActive == true) {
return
}
runningJob = refresh()
}
protected open fun refresh(): Job? = null
@CallSuper
override fun onCleared() {
isConnected.removeOnPropertyChangedCallback(refreshCallback)
super.onCleared()
}
fun withView(action: BaseActivity.() -> Unit) {
ViewActionEvent(action).publish()
}
open fun onSaveState(state: Bundle) {}
open fun onRestoreState(state: Bundle) {}
open fun onNetworkChanged(network: Boolean) {}
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
PermissionEvent(permission, callback).publish()
}
fun withExternalRW(callback: () -> Unit) {
withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
inline fun withExternalRW(crossinline callback: () -> Unit) {
withPermission(WRITE_EXTERNAL_STORAGE) {
if (!it) {
SnackbarEvent(R.string.external_rw_permission_denied).publish()
} else {
@ -96,19 +44,40 @@ abstract class BaseViewModel(
}
}
@SuppressLint("InlinedApi")
inline fun withInstallPermission(crossinline callback: () -> Unit) {
withPermission(REQUEST_INSTALL_PACKAGES) {
if (!it) {
SnackbarEvent(R.string.install_unknown_denied).publish()
} else {
callback()
}
}
}
@SuppressLint("InlinedApi")
inline fun withPostNotificationPermission(crossinline callback: () -> Unit) {
withPermission(POST_NOTIFICATIONS) {
if (!it) {
SnackbarEvent(R.string.post_notifications_denied).publish()
} else {
callback()
}
}
}
fun back() = BackPressEvent().publish()
fun <Event : ViewEvent> Event.publish() {
fun ViewEvent.publish() {
_viewEvents.postValue(this)
}
fun <Event : ViewEventWithScope> Event.publish() {
scope = viewModelScope
_viewEvents.postValue(this)
fun DialogBuilder.show() {
DialogEvent(this).publish()
}
fun NavDirections.publish() {
_viewEvents.postValue(NavigationEvent(this))
fun NavDirections.navigate(pop: Boolean = false) {
_viewEvents.postValue(NavigationEvent(this, pop))
}
}

View File

@ -1,48 +0,0 @@
package com.topjohnwu.magisk.arch
import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FilterableDiffObservableList
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
import me.tatarka.bindingcollectionadapter2.ItemBinding
import me.tatarka.bindingcollectionadapter2.OnItemBind
fun <T : ComparableRvItem<*>> diffListOf(
vararg newItems: T
) = diffListOf(newItems.toList())
fun <T : ComparableRvItem<*>> diffListOf(
newItems: List<T>
) = DiffObservableList(object : DiffObservableList.Callback<T> {
override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem)
override fun areContentsTheSame(oldItem: T, newItem: T) = oldItem.genericContentSameAs(newItem)
}).also { it.update(newItems) }
fun <T : ComparableRvItem<*>> filterableListOf(
vararg newItems: T
) = FilterableDiffObservableList(object : DiffObservableList.Callback<T> {
override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem)
override fun areContentsTheSame(oldItem: T, newItem: T) = oldItem.genericContentSameAs(newItem)
}).also { it.update(newItems.toList()) }
fun <T : RvItem> adapterOf() = object : BindingRecyclerViewAdapter<T>() {
override fun onBindBinding(
binding: ViewDataBinding,
variableId: Int,
layoutRes: Int,
position: Int,
item: T
) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
item.onBindingBound(binding)
}
}
inline fun <T : RvItem> itemBindingOf(
crossinline body: (ItemBinding<*>) -> Unit = {}
) = OnItemBind<T> { itemBinding, _, item ->
item.bind(itemBinding)
body(itemBinding)
}

View File

@ -0,0 +1,37 @@
package com.topjohnwu.magisk.arch
import android.view.KeyEvent
import androidx.databinding.ViewDataBinding
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Binding>() {
abstract val navHostId: Int
private val navHostFragment by lazy {
supportFragmentManager.findFragmentById(navHostId) as NavHostFragment
}
protected val currentFragment get() =
navHostFragment.childFragmentManager.fragments.getOrNull(0) as? BaseFragment<*>
val navigation: NavController get() = navHostFragment.navController
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
return if (binded && currentFragment?.onKeyEvent(event) == true) true else super.dispatchKeyEvent(event)
}
override fun onBackPressed() {
if (binded) {
if (currentFragment?.onBackPressed() == false) {
super.onBackPressed()
}
}
}
fun NavDirections.navigate() {
navigation.navigate(this)
}
}

View File

@ -1,17 +0,0 @@
package com.topjohnwu.magisk.arch
import android.os.Handler
import androidx.core.os.postDelayed
import com.topjohnwu.superuser.internal.UiThreadHandler
interface Queryable {
val queryDelay: Long
val queryHandler: Handler get() = UiThreadHandler.handler
fun submitQuery() {
queryHandler.postDelayed(queryDelay) { query() }
}
fun query()
}

View File

@ -0,0 +1,115 @@
package com.topjohnwu.magisk.arch
import android.content.res.Resources
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.res.use
import androidx.core.view.WindowCompat
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity
import rikka.insets.WindowInsetsHelper
import rikka.layoutinflater.view.LayoutInflaterFactory
abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModelHolder {
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
protected val binded get() = ::binding.isInitialized
open val snackbarView get() = binding.root
open val snackbarAnchorView: View? get() = null
init {
AppCompatDelegate.setDefaultNightMode(Config.darkTheme)
}
override fun onCreate(savedInstanceState: Bundle?) {
layoutInflater.factory2 = LayoutInflaterFactory(delegate)
.addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
super.onCreate(savedInstanceState)
startObserveLiveData()
// We need to set the window background explicitly since for whatever reason it's not
// propagated upstream
obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
.use { it.getDrawable(0) }
.also { window.setBackgroundDrawable(it) }
WindowCompat.setDecorFitsSystemWindows(window, false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window?.decorView?.post {
// If navigation bar is short enough (gesture navigation enabled), make it transparent
if ((window.decorView.rootWindowInsets?.systemWindowInsetBottom
?: 0) < Resources.getSystem().displayMetrics.density * 40) {
window.navigationBarColor = Color.TRANSPARENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.navigationBarDividerColor = Color.TRANSPARENT
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
window.isStatusBarContrastEnforced = false
}
}
}
}
}
fun setContentView() {
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).also {
it.setVariable(BR.viewModel, viewModel)
it.lifecycleOwner = this
}
}
fun setAccessibilityDelegate(delegate: View.AccessibilityDelegate?) {
binding.root.rootView.accessibilityDelegate = delegate
}
fun showSnackbar(
message: CharSequence,
length: Int = Snackbar.LENGTH_SHORT,
builder: Snackbar.() -> Unit = {}
) = Snackbar.make(snackbarView, message, length)
.setAnchorView(snackbarAnchorView).apply(builder).show()
override fun onResume() {
super.onResume()
viewModel.let {
if (it is AsyncLoadViewModel)
it.startLoading()
}
}
override fun onEventDispatched(event: ViewEvent) = when (event) {
is ContextExecutor -> event(this)
is ActivityExecutor -> event(this)
else -> Unit
}
}
fun ViewGroup.startAnimations() {
val transition = AutoTransition()
.setInterpolator(FastOutSlowInInterpolator())
.setDuration(400)
.excludeTarget(R.id.main_toolbar, true)
TransitionManager.beginDelayedTransition(
this,
transition
)
}

View File

@ -1,7 +1,6 @@
package com.topjohnwu.magisk.arch
import android.content.Context
import kotlinx.coroutines.CoroutineScope
/**
* Class for passing events from ViewModels to Activities/Fragments
@ -9,18 +8,14 @@ import kotlinx.coroutines.CoroutineScope
*/
abstract class ViewEvent
abstract class ViewEventWithScope: ViewEvent() {
lateinit var scope: CoroutineScope
}
interface ContextExecutor {
operator fun invoke(context: Context)
}
interface ActivityExecutor {
operator fun invoke(activity: BaseUIActivity<*, *>)
operator fun invoke(activity: UIActivity<*>)
}
interface FragmentExecutor {
operator fun invoke(fragment: BaseUIFragment<*, *>)
operator fun invoke(fragment: BaseFragment<*>)
}

View File

@ -0,0 +1,49 @@
package com.topjohnwu.magisk.arch
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.ui.home.HomeViewModel
import com.topjohnwu.magisk.ui.install.InstallViewModel
import com.topjohnwu.magisk.ui.log.LogViewModel
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
interface ViewModelHolder : LifecycleOwner, ViewModelStoreOwner {
val viewModel: BaseViewModel
fun startObserveLiveData() {
viewModel.viewEvents.observe(this, this::onEventDispatched)
Info.isConnected.observe(this, viewModel::onNetworkChanged)
}
/**
* Called for all [ViewEvent]s published by associated viewModel.
*/
fun onEventDispatched(event: ViewEvent) {}
}
object VMFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return when (modelClass) {
HomeViewModel::class.java -> HomeViewModel(ServiceLocator.networkService)
LogViewModel::class.java -> LogViewModel(ServiceLocator.logRepo)
SuperuserViewModel::class.java -> SuperuserViewModel(ServiceLocator.policyDB)
InstallViewModel::class.java ->
InstallViewModel(ServiceLocator.networkService, ServiceLocator.markwon)
SuRequestViewModel::class.java ->
SuRequestViewModel(ServiceLocator.policyDB, ServiceLocator.timeoutPrefs)
else -> modelClass.newInstance()
} as T
}
}
inline fun <reified VM : ViewModel> ViewModelHolder.viewModel() =
lazy(LazyThreadSafetyMode.NONE) {
ViewModelProvider(this, VMFactory)[VM::class.java]
}

View File

@ -5,105 +5,123 @@ import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import androidx.work.WorkManager
import android.system.Os
import androidx.profileinstaller.ProfileInstaller
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.utils.AppShellInit
import com.topjohnwu.magisk.core.utils.BusyBoxInit
import com.topjohnwu.magisk.core.utils.IODispatcherExecutor
import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.ktx.unwrap
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.utils.DispatcherExecutor
import com.topjohnwu.magisk.core.utils.NetworkObserver
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.ShellInit
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.setConfig
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.superuser.ipc.RootService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.lang.ref.WeakReference
import kotlin.system.exitProcess
open class App() : Application() {
constructor(o: Any) : this() {
Info.stub = DynAPK.load(o)
val data = StubApk.Data(o)
// Add the root service name mapping
data.classToComponent[RootUtils::class.java.name] = data.rootService.name
// Send back the actual root service class
data.rootService = RootUtils::class.java
Info.stub = data
}
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
Shell.setDefaultBuilder(Shell.Builder.create()
.setFlags(Shell.FLAG_MOUNT_MASTER)
.setInitializers(BusyBoxInit::class.java, AppShellInit::class.java)
.setTimeout(2))
Shell.EXECUTOR = IODispatcherExecutor()
// Always log full stack trace with Timber
Timber.plant(Timber.DebugTree())
Thread.setDefaultUncaughtExceptionHandler { _, e ->
Timber.e(e)
exitProcess(1)
}
Os.setenv("PATH", "${Os.getenv("PATH")}:/debug_ramdisk:/sbin", true)
}
override fun attachBaseContext(base: Context) {
// Basic setup
if (BuildConfig.DEBUG)
MultiDex.install(base)
// Some context magic
override fun attachBaseContext(context: Context) {
// Get the actual ContextImpl
val app: Application
val impl: Context
if (base is Application) {
app = base
impl = base.baseContext
val base: Context
if (context is Application) {
app = context
base = context.baseContext
AppApkPath = StubApk.current(base).path
} else {
app = this
impl = base
base = context
AppApkPath = base.packageResourcePath
}
val wrapped = impl.wrap()
super.attachBaseContext(wrapped)
super.attachBaseContext(base)
ServiceLocator.context = base
app.registerActivityLifecycleCallbacks(ActivityTracker)
val info = base.applicationInfo
val libDir = runCatching {
info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
}.getOrNull() ?: info.nativeLibraryDir
Const.NATIVE_LIB_DIR = File(libDir)
Shell.setDefaultBuilder(Shell.Builder.create()
.setFlags(Shell.FLAG_MOUNT_MASTER)
.setInitializers(ShellInit::class.java)
.setContext(base)
.setTimeout(2))
Shell.EXECUTOR = DispatcherExecutor(Dispatchers.IO)
RootUtils.bindTask = RootService.bindOrTask(
intent<RootUtils>(),
UiThreadHandler.executor,
RootUtils.Connection
)
// Pre-heat the shell ASAP
Shell.getShell(null) {}
// Normal startup
startKoin {
androidContext(wrapped)
modules(koinModules)
}
AssetHack.init(impl)
app.registerActivityLifecycleCallbacks(ForegroundTracker)
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
refreshLocale()
resources.patch()
Notifications.setup()
}
// This is required as some platforms expect ContextImpl
override fun getBaseContext(): Context {
return super.getBaseContext().unwrap()
override fun onCreate() {
super.onCreate()
ProcessLifecycle.init(this)
NetworkObserver.init(this)
if (!BuildConfig.DEBUG && !isRunningAsStub) {
GlobalScope.launch(Dispatchers.IO) {
ProfileInstaller.writeProfile(this@App)
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
resources.updateConfig(newConfig)
if (resources.configuration.diff(newConfig) != 0) {
resources.setConfig(newConfig)
}
if (!isRunningAsStub)
super.onConfigurationChanged(newConfig)
}
}
object ForegroundTracker : Application.ActivityLifecycleCallbacks {
object ActivityTracker : Application.ActivityLifecycleCallbacks {
val foreground: Activity? get() = ref.get()
@Volatile
var foreground: Activity? = null
val hasForeground get() = foreground != null
private var ref = WeakReference<Activity>(null)
override fun onActivityResumed(activity: Activity) {
foreground = activity
if (activity is SuRequestActivity) return
ref = WeakReference(activity)
}
override fun onActivityPaused(activity: Activity) {
foreground = null
if (activity is SuRequestActivity) return
ref.clear()
}
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}

View File

@ -1,38 +1,36 @@
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.util.Xml
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.magiskdb.StringDao
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.repository.BoolDBPropertyNoWrite
import com.topjohnwu.magisk.core.repository.DBConfig
import com.topjohnwu.magisk.core.repository.PreferenceConfig
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.data.preference.PreferenceModel
import com.topjohnwu.magisk.data.repository.DBConfig
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.ktx.inject
import com.topjohnwu.magisk.ui.theme.Theme
import org.xmlpull.v1.XmlPullParser
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
import java.io.File
import java.io.InputStream
import java.io.IOException
object Config : PreferenceModel, DBConfig {
object Config : PreferenceConfig, DBConfig {
override val stringDao: StringDao by inject()
override val settingsDao: SettingsDao by inject()
override val context: Context by inject(Protected)
override val stringDB get() = ServiceLocator.stringDB
override val settingsDB get() = ServiceLocator.settingsDB
override val context get() = ServiceLocator.deContext
override val coroutineScope get() = GlobalScope
@get:SuppressLint("ApplySharedPref")
val prefsFile: File get() {
// Flush prefs to disk
prefs.edit().apply {
remove(Key.ASKED_HOME)
}.commit()
return File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
private val prefsFile = File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
@SuppressLint("ApplySharedPref")
fun getPrefsFile(): File {
prefs.edit().remove(Key.ASKED_HOME).commit()
return prefsFile
}
object Key {
@ -41,6 +39,9 @@ object Config : PreferenceModel, DBConfig {
const val SU_MULTIUSER_MODE = "multiuser_mode"
const val SU_MNT_NS = "mnt_ns"
const val SU_BIOMETRIC = "su_biometric"
const val ZYGISK = "zygisk"
const val BOOTLOOP = "bootloop"
const val DENYLIST = "denylist"
const val SU_MANAGER = "requester"
const val KEYSTORE = "keystore"
@ -63,9 +64,6 @@ object Config : PreferenceModel, DBConfig {
const val BOOT_ID = "boot_id"
const val ASKED_HOME = "asked_home"
const val DOH = "doh"
// system state
const val MAGISKHIDE = "magiskhide"
}
object Value {
@ -75,6 +73,7 @@ object Config : PreferenceModel, DBConfig {
const val BETA_CHANNEL = 1
const val CUSTOM_CHANNEL = 2
const val CANARY_CHANNEL = 3
const val DEBUG_CHANNEL = 4
// root access mode
const val ROOT_ACCESS_DISABLED = 0
@ -111,13 +110,15 @@ object Config : PreferenceModel, DBConfig {
private val defaultChannel =
if (BuildConfig.DEBUG)
Value.DEBUG_CHANNEL
else if (Const.APP_IS_CANARY)
Value.CANARY_CHANNEL
else
Value.DEFAULT_CHANNEL
@JvmStatic var keepVerity = false
@JvmStatic var keepEnc = false
@JvmStatic var recovery = false
@JvmField var keepVerity = false
@JvmField var keepEnc = false
@JvmField var recovery = false
var bootId by preference(Key.BOOT_ID, "")
var askedHome by preference(Key.ASKED_HOME, false)
@ -135,9 +136,16 @@ object Config : PreferenceModel, DBConfig {
var themeOrdinal by preference(Key.THEME_ORDINAL, Theme.Piplup.ordinal)
var suReAuth by preference(Key.SU_REAUTH, false)
var suTapjack by preference(Key.SU_TAPJACK, true)
var checkUpdate by preference(Key.CHECK_UPDATES, true)
private var checkUpdatePrefs by preference(Key.CHECK_UPDATES, true)
var checkUpdate
get() = checkUpdatePrefs
set(value) {
if (checkUpdatePrefs != value) {
checkUpdatePrefs = value
JobService.schedule(AppContext)
}
}
var doh by preference(Key.DOH, false)
var magiskHide by preference(Key.MAGISKHIDE, true)
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
@ -152,7 +160,15 @@ object Config : PreferenceModel, DBConfig {
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
private var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
var userAuth
get() = Info.isDeviceSecure && suBiometric
set(value) {
suBiometric = value
}
var zygisk by dbSettings(Key.ZYGISK, false)
var bootloop by dbSettings(Key.BOOTLOOP, 0)
var denyList by BoolDBPropertyNoWrite(Key.DENYLIST, false)
var suManager by dbStrings(Key.SU_MANAGER, "", true)
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
@ -160,10 +176,15 @@ object Config : PreferenceModel, DBConfig {
fun load(pkg: String?) {
// Only try to load prefs when fresh install and a previous package name is set
if (pkg != null && prefs.all.isEmpty()) runCatching {
context.contentResolver.openInputStream(Provider.PREFS_URI(pkg))?.use {
prefs.edit { parsePrefs(it) }
if (pkg != null && prefs.all.isEmpty()) {
runBlocking {
try {
context.contentResolver
.openInputStream(Provider.preferencesUri(pkg))
?.writeTo(prefsFile, dispatcher = Dispatchers.Unconfined)
} catch (ignored: IOException) {}
}
return
}
prefs.edit {
@ -172,63 +193,10 @@ object Config : PreferenceModel, DBConfig {
suBiometric = true
remove(SU_FINGERPRINT)
prefs.getString(Key.UPDATE_CHANNEL, null).also {
if (it == null)
if (it == null ||
it.toInt() > Value.DEBUG_CHANNEL ||
it.toInt() < Value.DEFAULT_CHANNEL) {
putString(Key.UPDATE_CHANNEL, defaultChannel.toString())
else if (it.toInt() > Value.CANARY_CHANNEL)
putString(Key.UPDATE_CHANNEL, Value.CANARY_CHANNEL.toString())
}
// Write database configs
putString(Key.ROOT_ACCESS, rootMode.toString())
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
putBoolean(Key.SU_BIOMETRIC, BiometricHelper.isEnabled)
}
}
private fun SharedPreferences.Editor.parsePrefs(input: InputStream) {
runCatching {
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(input, "UTF-8")
parser.nextTag()
parser.require(XmlPullParser.START_TAG, null, "map")
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG)
continue
val key: String = parser.getAttributeValue(null, "name")
fun value() = parser.getAttributeValue(null, "value")!!
when (parser.name) {
"string" -> {
parser.require(XmlPullParser.START_TAG, null, "string")
putString(key, parser.nextText())
parser.require(XmlPullParser.END_TAG, null, "string")
}
"boolean" -> {
parser.require(XmlPullParser.START_TAG, null, "boolean")
putBoolean(key, value().toBoolean())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "boolean")
}
"int" -> {
parser.require(XmlPullParser.START_TAG, null, "int")
putInt(key, value().toInt())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "int")
}
"long" -> {
parser.require(XmlPullParser.START_TAG, null, "long")
putLong(key, value().toLong())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "long")
}
"float" -> {
parser.require(XmlPullParser.START_TAG, null, "int")
putFloat(key, value().toFloat())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "int")
}
else -> parser.next()
}
}
}

View File

@ -3,68 +3,53 @@ package com.topjohnwu.magisk.core
import android.os.Build
import android.os.Process
import com.topjohnwu.magisk.BuildConfig
import java.io.File
@Suppress("DEPRECATION")
object Const {
val CPU_ABI: String
val CPU_ABI_32: String
val CPU_ABI: String get() = Build.SUPPORTED_ABIS[0]
init {
if (Build.VERSION.SDK_INT >= 21) {
CPU_ABI = Build.SUPPORTED_ABIS[0]
CPU_ABI_32 = Build.SUPPORTED_32_BIT_ABIS.firstOrNull() ?: CPU_ABI
} else {
CPU_ABI = Build.CPU_ABI
CPU_ABI_32 = CPU_ABI
}
}
// Null if 32-bit only or 64-bit only
val CPU_ABI_32 =
if (Build.SUPPORTED_64_BIT_ABIS.isEmpty()) null
else Build.SUPPORTED_32_BIT_ABIS.firstOrNull()
// Paths
lateinit var MAGISKTMP: String
lateinit var NATIVE_LIB_DIR: File
val MAGISK_PATH get() = "$MAGISKTMP/modules"
const val MAGISK_PATH = "/data/adb/modules"
const val TMPDIR = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log"
// Versions
const val SNET_EXT_VER = 15
const val SNET_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880"
const val BOOTCTL_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880"
// Misc
val USER_ID = Process.myUid() / 100000
val APP_IS_CANARY get() = Version.isCanary(BuildConfig.VERSION_CODE)
object Version {
const val MIN_VERSION = "v20.4"
const val MIN_VERCODE = 20400
const val MIN_VERSION = "v22.0"
const val MIN_VERCODE = 22000
fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary()
fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
fun isCanary() = Info.env.magiskVersionCode % 100 != 0
fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary()
fun atLeast_25_0() = Info.env.versionCode >= 25000 || isCanary()
fun isCanary() = isCanary(Info.env.versionCode)
fun isCanary(ver: Int) = ver > 0 && ver % 100 != 0
}
object ID {
// notifications
const val APK_UPDATE_NOTIFICATION_ID = 5
const val UPDATE_NOTIFICATION_CHANNEL = "update"
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
const val CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update"
const val DOWNLOAD_JOB_ID = 6
const val CHECK_UPDATE_JOB_ID = 7
}
object Url {
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
val CHANGELOG_URL = if (BuildConfig.VERSION_CODE % 100 != 0) Info.remote.magisk.note
val CHANGELOG_URL = if (APP_IS_CANARY) Info.remote.magisk.note
else "https://topjohnwu.github.io/Magisk/releases/${BuildConfig.VERSION_CODE}.md"
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
const val GITHUB_API_URL = "https://api.github.com/"
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk_files/"
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk-files/"
const val JS_DELIVR_URL = "https://cdn.jsdelivr.net/gh/"
const val OFFICIAL_REPO = "https://magisk-modules-repo.github.io/submission/modules.json"
}
object Key {
@ -84,7 +69,6 @@ object Const {
object Nav {
const val HOME = "home"
const val SETTINGS = "settings"
const val HIDE = "hide"
const val MODULES = "modules"
const val SUPERUSER = "superuser"
}

View File

@ -2,11 +2,6 @@
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.app.Activity
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.app.job.JobWorkItem
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
@ -15,112 +10,53 @@ import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import android.util.DisplayMetrics
import androidx.annotation.RequiresApi
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.unwrap
import com.topjohnwu.magisk.core.utils.syncLocale
fun AssetManager.addAssetPath(path: String) {
DynAPK.addAssetPath(this, path)
lateinit var AppApkPath: String
fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path)
fun Resources.patch(): Resources {
if (isRunningAsStub)
addAssetPath(AppApkPath)
syncLocale()
return this
}
fun Context.wrap(inject: Boolean = false): Context =
if (inject) ReInjectedContext(this) else InjectedContext(this)
fun Context.patch(): Context {
unwrap().resources.patch()
return this
}
fun Context.wrapJob(): Context = object : InjectedContext(this) {
override fun getApplicationContext() = this
@SuppressLint("NewApi")
override fun getSystemService(name: String): Any? {
return super.getSystemService(name).let {
when {
!isRunningAsStub -> it
name == JOB_SCHEDULER_SERVICE -> JobSchedulerWrapper(it as JobScheduler)
else -> it
}
// Wrapping is only necessary for ContextThemeWrapper to support configuration overrides
fun Context.wrap(): Context {
patch()
return object : ContextWrapper(this) {
override fun createConfigurationContext(config: Configuration): Context {
return super.createConfigurationContext(config).wrap()
}
}
}
fun createNewResources(): Resources {
val asset = AssetManager::class.java.newInstance()
val config = Configuration(AppContext.resources.configuration)
val metrics = DisplayMetrics()
metrics.setTo(AppContext.resources.displayMetrics)
val res = Resources(asset, metrics, config)
res.addAssetPath(AppApkPath)
return res
}
fun Class<*>.cmp(pkg: String) =
ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)
inline fun <reified T> Activity.redirect() = Intent(intent)
.setComponent(T::class.java.cmp(packageName))
.setFlags(0)
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
private open class InjectedContext(base: Context) : ContextWrapper(base) {
open val res: Resources get() = AssetHack.resource
override fun getAssets(): AssetManager = res.assets
override fun getResources() = res
override fun getClassLoader() = javaClass.classLoader!!
override fun createConfigurationContext(config: Configuration): Context {
return super.createConfigurationContext(config).wrap(true)
}
}
private class ReInjectedContext(base: Context) : InjectedContext(base) {
override val res by lazy { base.resources.patch() }
private fun Resources.patch(): Resources {
updateConfig()
if (isRunningAsStub)
assets.addAssetPath(AssetHack.apk)
return this
}
}
object AssetHack {
lateinit var resource: Resources
lateinit var apk: String
fun init(context: Context) {
resource = context.resources
refreshLocale()
if (isRunningAsStub) {
apk = DynAPK.current(context).path
resource.assets.addAssetPath(apk)
} else {
apk = context.packageResourcePath
}
}
fun newResource(): Resources {
val asset = AssetManager::class.java.newInstance()
asset.addAssetPath(apk)
val config = Configuration(resource.configuration)
val metrics = DisplayMetrics()
metrics.setTo(resource.displayMetrics)
return Resources(asset, metrics, config)
}
}
@RequiresApi(28)
private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() {
override fun schedule(job: JobInfo) = base.schedule(job.patch())
override fun enqueue(job: JobInfo, work: JobWorkItem) = base.enqueue(job.patch(), work)
override fun cancel(jobId: Int) = base.cancel(jobId)
override fun cancelAll() = base.cancelAll()
override fun getAllPendingJobs(): List<JobInfo> = base.allPendingJobs
override fun getPendingJob(jobId: Int) = base.getPendingJob(jobId)
private fun JobInfo.patch(): JobInfo {
// Swap out the service of JobInfo
val component = service.run {
ComponentName(packageName,
Info.stub?.classToComponent?.get(className) ?: className)
}
javaClass.getDeclaredField("service").apply {
isAccessible = true
}.set(this, component)
return this
}
}
// Keep a reference to these resources to prevent it from
// being removed when running "remove unused resources"
val shouldKeepResources = listOf(
@ -128,10 +64,11 @@ val shouldKeepResources = listOf(
R.string.release_notes,
R.string.invalid_update_channel,
R.string.update_available,
R.string.safetynet_api_error,
R.raw.changelog,
R.drawable.ic_device,
R.drawable.ic_hide_select_md2,
R.drawable.ic_more,
R.drawable.ic_magisk_delete
R.drawable.ic_magisk_delete,
R.drawable.ic_refresh_data_md2,
R.drawable.ic_order_date,
R.drawable.ic_order_name,
R.array.allow_timeout,
)

View File

@ -1,24 +1,19 @@
package com.topjohnwu.magisk.core
import android.os.Build
import androidx.databinding.ObservableBoolean
import com.topjohnwu.magisk.DynAPK
import android.app.KeyguardManager
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.getProperty
import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.utils.net.NetworkObserver
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.getProperty
import com.topjohnwu.superuser.Shell
import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.superuser.ShellUtils.fastCmd
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File
import java.io.IOException
val isRunningAsStub get() = Info.stub != null
object Info {
var stub: DynAPK.Data? = null
var stub: StubApk.Data? = null
val EMPTY_REMOTE = UpdateInfo()
var remote = EMPTY_REMOTE
@ -31,59 +26,50 @@ object Info {
// Device state
@JvmStatic val env by lazy { loadState() }
@JvmField var isSAR = false
@JvmField var isAB = false
@JvmField val isVirtualAB = getProperty("ro.virtual_ab.enabled", "false") == "true"
var legacySAR = false
var isAB = false
@JvmField val isZygiskEnabled = System.getenv("ZYGISK_ENABLED") == "1"
@JvmStatic val isFDE get() = crypto == "block"
@JvmField var ramdisk = false
@JvmField var hasGMS = true
@JvmField val isPixel = Build.BRAND == "google"
@JvmField val isEmulator = getProperty("ro.kernel.qemu", "0") == "1"
var patchBootVbmeta = false
var crypto = ""
var noDataExec = false
var isRooted = false
val isConnected by lazy {
ObservableBoolean(false).also { field ->
NetworkObserver.observe(get()) {
UiThreadHandler.run { field.set(it) }
}
}
@JvmField var hasGMS = true
@JvmField val isEmulator =
getProperty("ro.kernel.qemu", "0") == "1" ||
getProperty("ro.boot.qemu", "0") == "1"
val isConnected = MutableLiveData(false)
val showSuperUser: Boolean get() {
return env.isActive && (Const.USER_ID == 0
|| Config.suMultiuserMode == Config.Value.MULTIUSER_MODE_USER)
}
val isNewReboot by lazy {
try {
val id = File("/proc/sys/kernel/random/boot_id").readText()
if (id != Config.bootId) {
Config.bootId = id
true
} else {
false
}
} catch (e: IOException) {
false
}
}
val isDeviceSecure get() =
AppContext.getSystemService(KeyguardManager::class.java).isDeviceSecure
private fun loadState() = Env(
fastCmd("magisk -v").split(":".toRegex())[0],
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1),
Shell.su("magiskhide --status").exec().isSuccess
)
private fun loadState(): Env {
val v = fastCmd("magisk -v").split(":".toRegex())
return Env(
v[0], v.size >= 3 && v[2] == "D",
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1)
)
}
class Env(
val magiskVersionString: String = "",
code: Int = -1,
hide: Boolean = false
val versionString: String = "",
val isDebug: Boolean = false,
code: Int = -1
) {
val magiskHide get() = Config.magiskHide
val magiskVersionCode = when {
val versionCode = when {
code < Const.Version.MIN_VERCODE -> -1
else -> if (Shell.rootAccess()) code else -1
isRooted -> code
else -> -1
}
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
val isActive = magiskVersionCode >= 0
init {
Config.magiskHide = hide
}
val isActive = versionCode > 0
}
}

View File

@ -0,0 +1,104 @@
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Notification
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.content.Context
import androidx.core.content.getSystemService
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.base.BaseJobService
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
class JobService : BaseJobService() {
private var mSession: Session? = null
@TargetApi(value = 34)
inner class Session(
private var params: JobParameters
) : DownloadEngine.Session {
override val context get() = this@JobService
val engine = DownloadEngine(this)
fun updateParams(params: JobParameters) {
this.params = params
engine.reattach()
}
override fun attachNotification(id: Int, builder: Notification.Builder) {
setNotification(params, id, builder.build(), JOB_END_NOTIFICATION_POLICY_REMOVE)
}
override fun onDownloadComplete() {
jobFinished(params, false)
}
}
@SuppressLint("NewApi")
override fun onStartJob(params: JobParameters): Boolean {
return when (params.jobId) {
Const.ID.CHECK_UPDATE_JOB_ID -> checkUpdate(params)
Const.ID.DOWNLOAD_JOB_ID -> downloadFile(params)
else -> false
}
}
override fun onStopJob(params: JobParameters?) = false
@TargetApi(value = 34)
private fun downloadFile(params: JobParameters): Boolean {
params.transientExtras.classLoader = Subject::class.java.classLoader
val subject = params.transientExtras
.getParcelable(DownloadEngine.SUBJECT_KEY, Subject::class.java) ?:
return false
val session = mSession?.also {
it.updateParams(params)
} ?: run {
Session(params).also { mSession = it }
}
session.engine.download(subject)
return true
}
private fun checkUpdate(params: JobParameters): Boolean {
GlobalScope.launch(Dispatchers.IO) {
ServiceLocator.networkService.fetchUpdate()?.let {
Info.remote = it
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
Notifications.updateAvailable()
jobFinished(params, false)
}
}
return true
}
companion object {
fun schedule(context: Context) {
val scheduler = context.getSystemService<JobScheduler>() ?: return
if (Config.checkUpdate) {
val cmp = JobService::class.java.cmp(context.packageName)
val info = JobInfo.Builder(Const.ID.CHECK_UPDATE_JOB_ID, cmp)
.setPeriodic(TimeUnit.HOURS.toMillis(12))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setRequiresDeviceIdle(true)
.build()
scheduler.schedule(info)
} else {
scheduler.cancel(Const.ID.CHECK_UPDATE_JOB_ID)
}
}
}
}

View File

@ -1,39 +1,34 @@
package com.topjohnwu.magisk.core
import android.content.Context
import android.content.pm.ProviderInfo
import android.net.Uri
import android.os.Bundle
import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
import com.topjohnwu.magisk.FileProvider
import com.topjohnwu.magisk.core.base.BaseProvider
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import java.io.File
import com.topjohnwu.magisk.core.su.TestHandler
open class Provider : FileProvider() {
override fun attachInfo(context: Context, info: ProviderInfo?) {
super.attachInfo(context.wrap(), info)
}
class Provider : BaseProvider() {
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
SuCallbackHandler(context!!, method, extras)
return Bundle.EMPTY
return when (method) {
SuCallbackHandler.LOG, SuCallbackHandler.NOTIFY -> {
SuCallbackHandler.run(context!!, method, extras)
Bundle.EMPTY
}
else -> TestHandler.run(method)
}
}
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
return when (uri.encodedPath ?: return null) {
"/apk_file" -> ParcelFileDescriptor.open(File(context!!.packageCodePath), MODE_READ_ONLY)
"/prefs_file" -> ParcelFileDescriptor.open(Config.prefsFile, MODE_READ_ONLY)
"/prefs_file" -> ParcelFileDescriptor.open(Config.getPrefsFile(), MODE_READ_ONLY)
else -> super.openFile(uri, mode)
}
}
companion object {
fun APK_URI(pkg: String) =
Uri.Builder().scheme("content").authority("$pkg.provider").path("apk_file").build()
fun PREFS_URI(pkg: String) =
fun preferencesUri(pkg: String): Uri =
Uri.Builder().scheme("content").authority("$pkg.provider").path("prefs_file").build()
}
}

View File

@ -1,46 +1,68 @@
package com.topjohnwu.magisk.core
import android.content.ContextWrapper
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import androidx.core.content.IntentCompat
import com.topjohnwu.magisk.core.base.BaseReceiver
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.core.inject
open class Receiver : BaseReceiver() {
private val policyDB: PolicyDao by inject()
private val policyDB get() = ServiceLocator.policyDB
private fun getPkg(intent: Intent): String {
return intent.data?.encodedSchemeSpecificPart.orEmpty()
@SuppressLint("InlinedApi")
private fun getPkg(intent: Intent): String? {
val pkg = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
return pkg ?: intent.data?.schemeSpecificPart
}
override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent ?: return
private fun getUid(intent: Intent): Int? {
val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)
return if (uid == -1) null else uid
}
fun rmPolicy(pkg: String) = GlobalScope.launch {
policyDB.delete(pkg)
override fun onReceive(context: Context, intent: Intent?) {
intent ?: return
super.onReceive(context, intent)
fun rmPolicy(uid: Int) = GlobalScope.launch {
policyDB.delete(uid)
}
when (intent.action ?: return) {
Intent.ACTION_REBOOT -> {
SuCallbackHandler(context, intent.getStringExtra("action"), intent.extras)
DownloadEngine.ACTION -> {
IntentCompat.getParcelableExtra(
intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)?.let {
DownloadEngine.start(context, it)
}
}
Intent.ACTION_PACKAGE_REPLACED -> {
// This will only work pre-O
if (Config.suReAuth)
rmPolicy(getPkg(intent))
getUid(intent)?.let { rmPolicy(it) }
}
Intent.ACTION_UID_REMOVED -> {
getUid(intent)?.let { rmPolicy(it) }
}
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
val pkg = getPkg(intent)
rmPolicy(pkg)
Shell.su("magiskhide --rm $pkg").submit()
getPkg(intent)?.let { Shell.cmd("magisk --denylist rm $it").submit() }
}
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setupDynamic(context)
Intent.ACTION_MY_PACKAGE_REPLACED -> {
@Suppress("DEPRECATION")
val installer = context.packageManager.getInstallerPackageName(context.packageName)
if (installer == context.packageName) {
Notifications.updateDone()
}
}
}
}
}

View File

@ -0,0 +1,38 @@
package com.topjohnwu.magisk.core
import android.app.Notification
import android.content.Intent
import android.os.Build
import androidx.core.app.ServiceCompat
import androidx.core.content.IntentCompat
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject
class Service : BaseService(), DownloadEngine.Session {
private var mEngine: DownloadEngine? = null
override val context get() = this
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
if (intent.action == DownloadEngine.ACTION) {
IntentCompat
.getParcelableExtra(intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)
?.let { subject ->
val engine = mEngine ?: DownloadEngine(this).also { mEngine = it }
engine.download(subject)
}
}
return START_NOT_STICKY
}
override fun attachNotification(id: Int, builder: Notification.Builder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
startForeground(id, builder.build())
}
override fun onDownloadComplete() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
}
}

View File

@ -1,92 +0,0 @@
package com.topjohnwu.magisk.core
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import java.util.concurrent.CountDownLatch
open class SplashActivity : BaseActivity() {
private val latch = CountDownLatch(1)
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.SplashTheme)
super.onCreate(savedInstanceState)
// Pre-initialize root shell
Shell.getShell(null) { initAndStart() }
}
private fun handleRepackage(pkg: String?) {
if (packageName != APPLICATION_ID) {
runCatching {
// Hidden, remove com.topjohnwu.magisk if exist as it could be malware
packageManager.getApplicationInfo(APPLICATION_ID, 0)
Shell.su("(pm uninstall $APPLICATION_ID)& >/dev/null 2>&1").exec()
}
} else {
if (Config.suManager.isNotEmpty())
Config.suManager = ""
pkg ?: return
if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess)
uninstallApp(pkg)
}
}
private fun initAndStart() {
if (isRunningAsStub && !Shell.rootAccess()) {
runOnUiThread {
MagiskDialog(this)
.applyTitle(R.string.unsupport_nonroot_stub_title)
.applyMessage(R.string.unsupport_nonroot_stub_msg)
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
titleRes = R.string.install
onClick { HideAPK.restore(this@SplashActivity) }
}
.cancellable(false)
.reveal()
}
return
}
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)
Config.load(prevPkg)
handleRepackage(prevPkg)
Notifications.setup(this)
UpdateCheckService.schedule(this)
Shortcuts.setupDynamic(this)
// Pre-fetch network services
get<NetworkService>()
DONE = true
startActivity(redirect<MainActivity>())
finish()
}
@Suppress("DEPRECATION")
private fun uninstallApp(pkg: String) {
val uri = Uri.Builder().scheme("package").opaquePart(pkg).build()
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri)
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
startActivityForResult(intent) { _, _ ->
latch.countDown()
}
latch.await()
}
companion object {
var DONE = false
}
}

View File

@ -1,46 +0,0 @@
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.content.Context
import androidx.work.*
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.view.Notifications
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.concurrent.TimeUnit
class UpdateCheckService(context: Context, workerParams: WorkerParameters)
: CoroutineWorker(context, workerParams), KoinComponent {
private val svc: NetworkService by inject()
override suspend fun doWork(): Result {
return svc.fetchUpdate()?.run {
if (Info.env.isActive && BuildConfig.VERSION_CODE < magisk.versionCode)
Notifications.managerUpdate(applicationContext)
Result.success()
} ?: Result.failure()
}
companion object {
@SuppressLint("NewApi")
fun schedule(context: Context) {
if (Config.checkUpdate) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(true)
.build()
val request = PeriodicWorkRequestBuilder<UpdateCheckService>(12, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
ExistingPeriodicWorkPolicy.REPLACE, request)
} else {
WorkManager.getInstance(context)
.cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID)
}
}
}
}

View File

@ -1,107 +1,123 @@
package com.topjohnwu.magisk.core.base
import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.widget.Toast
import androidx.annotation.CallSuper
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.SparseArrayCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.reflectField
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.utils.RequestAuthentication
import com.topjohnwu.magisk.core.utils.RequestInstall
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.set
import com.topjohnwu.magisk.utils.Utils
import kotlin.random.Random
typealias ActivityResultCallback = BaseActivity.(Int, Intent?) -> Unit
interface ContentResultCallback: ActivityResultCallback<Uri>, Parcelable {
fun onActivityLaunch() {}
// Make the result type explicitly non-null
override fun onActivityResult(result: Uri)
}
abstract class BaseActivity : AppCompatActivity() {
private val resultCallbacks by lazy { SparseArrayCompat<ActivityResultCallback>() }
private val newRequestCode: Int get() {
var requestCode: Int
do {
requestCode = Random.nextInt(0, 1 shl 15)
} while (resultCallbacks.containsKey(requestCode))
return requestCode
private var permissionCallback: ((Boolean) -> Unit)? = null
private val requestPermission = registerForActivityResult(RequestPermission()) {
permissionCallback?.invoke(it)
permissionCallback = null
}
override fun applyOverrideConfiguration(config: Configuration?) {
// Force applying our preferred local
config?.setLocale(currentLocale)
super.applyOverrideConfiguration(config)
private var installCallback: ((Boolean) -> Unit)? = null
private val requestInstall = registerForActivityResult(RequestInstall()) {
installCallback?.invoke(it)
installCallback = null
}
var authenticateCallback: ((Boolean) -> Unit)? = null
val requestAuthenticate = registerForActivityResult(RequestAuthentication()) {
authenticateCallback?.invoke(it)
authenticateCallback = null
}
private var contentCallback: ContentResultCallback? = null
private val getContent = registerForActivityResult(GetContent()) {
if (it != null) contentCallback?.onActivityResult(it)
contentCallback = null
}
private val mReferrerField by lazy(LazyThreadSafetyMode.NONE) {
Activity::class.java.reflectField("mReferrer")
}
val realCallingPackage: String? get() {
callingPackage?.let { return it }
mReferrerField.get(this)?.let { return it as String }
return null
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap(true))
super.attachBaseContext(base.wrap())
}
fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
override fun onCreate(savedInstanceState: Bundle?) {
if (isRunningAsStub) {
// Overwrite private members to avoid nasty "false" stack traces being logged
val delegate = delegate
val clz = delegate.javaClass
clz.reflectField("mActivityHandlesConfigFlagsChecked").set(delegate, true)
clz.reflectField("mActivityHandlesConfigFlags").set(delegate, 0)
}
contentCallback = savedInstanceState?.getParcelable(CONTENT_CALLBACK_KEY)
super.onCreate(savedInstanceState)
}
if (permission == WRITE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= 30) {
// We do not need external rw on 30+
request.onSuccess()
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
contentCallback?.let {
outState.putParcelable(CONTENT_CALLBACK_KEY, it)
}
}
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
permission == WRITE_EXTERNAL_STORAGE) {
// We do not need external rw on R+
callback(true)
return
}
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
request.onSuccess()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU &&
permission == POST_NOTIFICATIONS) {
// All apps have notification permissions before T
callback(true)
return
}
if (permission == REQUEST_INSTALL_PACKAGES) {
installCallback = callback
requestInstall.launch(Unit)
} else {
val requestCode = newRequestCode
resultCallbacks[requestCode] = { result, _ ->
if (result > 0)
request.onSuccess()
else
request.onFailure()
}
ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
permissionCallback = callback
requestPermission.launch(permission)
}
}
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
withPermission(WRITE_EXTERNAL_STORAGE, builder = builder)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
var success = true
for (res in grantResults) {
if (res != PackageManager.PERMISSION_GRANTED) {
success = false
break
}
}
resultCallbacks[requestCode]?.also {
resultCallbacks.remove(requestCode)
it(this, if (success) 1 else -1, null)
}
}
@CallSuper
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultCallbacks[requestCode]?.also { callback ->
resultCallbacks.remove(requestCode)
callback(this, resultCode, data)
}
}
fun startActivityForResult(intent: Intent, callback: ActivityResultCallback) {
val requestCode = newRequestCode
resultCallbacks[requestCode] = callback
fun getContent(type: String, callback: ContentResultCallback) {
contentCallback = callback
try {
startActivityForResult(intent, requestCode)
getContent.launch(type)
callback.onActivityLaunch()
} catch (e: ActivityNotFoundException) {
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
toast(R.string.app_not_found, Toast.LENGTH_SHORT)
}
}
@ -110,4 +126,12 @@ abstract class BaseActivity : AppCompatActivity() {
finish()
}
fun relaunch() {
startActivity(Intent(intent).setFlags(0))
finish()
}
companion object {
private const val CONTENT_CALLBACK_KEY = "content_callback"
}
}

View File

@ -0,0 +1,11 @@
package com.topjohnwu.magisk.core.base
import android.app.job.JobService
import android.content.Context
import com.topjohnwu.magisk.core.patch
abstract class BaseJobService : JobService() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.patch())
}
}

View File

@ -0,0 +1,21 @@
package com.topjohnwu.magisk.core.base
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.net.Uri
import com.topjohnwu.magisk.core.patch
open class BaseProvider : ContentProvider() {
override fun attachInfo(context: Context, info: ProviderInfo) {
super.attachInfo(context.patch(), info)
}
override fun onCreate() = true
override fun getType(uri: Uri): String? = null
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?) = 0
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?) = 0
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
}

View File

@ -2,16 +2,13 @@ package com.topjohnwu.magisk.core.base
import android.content.BroadcastReceiver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import com.topjohnwu.magisk.core.wrap
import org.koin.core.KoinComponent
import androidx.annotation.CallSuper
import com.topjohnwu.magisk.core.patch
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
final override fun onReceive(context: Context, intent: Intent?) {
onReceive(context.wrap() as ContextWrapper, intent)
abstract class BaseReceiver : BroadcastReceiver() {
@CallSuper
override fun onReceive(context: Context, intent: Intent?) {
context.patch()
}
abstract fun onReceive(context: ContextWrapper, intent: Intent?)
}

View File

@ -2,11 +2,13 @@ package com.topjohnwu.magisk.core.base
import android.app.Service
import android.content.Context
import com.topjohnwu.magisk.core.wrap
import org.koin.core.KoinComponent
import android.content.Intent
import android.os.IBinder
import com.topjohnwu.magisk.core.patch
abstract class BaseService : Service(), KoinComponent {
open class BaseService : Service() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap())
super.attachBaseContext(base.patch())
}
override fun onBind(intent: Intent?): IBinder? = null
}

View File

@ -1,59 +0,0 @@
package com.topjohnwu.magisk.core.base
import android.content.Context
import android.net.Network
import android.net.Uri
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.work.Data
import androidx.work.ListenableWorker
import com.google.common.util.concurrent.ListenableFuture
import java.util.*
abstract class BaseWorkerWrapper {
private lateinit var worker: ListenableWorker
val applicationContext: Context
get() = worker.applicationContext
val id: UUID
get() = worker.id
val inputData: Data
get() = worker.inputData
val tags: Set<String>
get() = worker.tags
val triggeredContentUris: List<Uri>
@RequiresApi(24)
get() = worker.triggeredContentUris
val triggeredContentAuthorities: List<String>
@RequiresApi(24)
get() = worker.triggeredContentAuthorities
val network: Network?
@RequiresApi(28)
get() = worker.network
val runAttemptCount: Int
get() = worker.runAttemptCount
val isStopped: Boolean
get() = worker.isStopped
abstract fun doWork(): ListenableWorker.Result
fun onStopped() {}
fun attachWorker(w: ListenableWorker) {
worker = w
}
@MainThread
fun startWork(): ListenableFuture<ListenableWorker.Result> {
return worker.startWork()
}
}

View File

@ -1,40 +0,0 @@
package com.topjohnwu.magisk.core.base
typealias SimpleCallback = () -> Unit
typealias PermissionRationaleCallback = (List<String>) -> Unit
class PermissionRequestBuilder {
private var onSuccessCallback: SimpleCallback = {}
private var onFailureCallback: SimpleCallback = {}
private var onShowRationaleCallback: PermissionRationaleCallback = {}
fun onSuccess(callback: SimpleCallback) {
onSuccessCallback = callback
}
fun onFailure(callback: SimpleCallback) {
onFailureCallback = callback
}
fun onShowRationale(callback: PermissionRationaleCallback) {
onShowRationaleCallback = callback
}
fun build(): PermissionRequest {
return PermissionRequest(onSuccessCallback, onFailureCallback, onShowRationaleCallback)
}
}
class PermissionRequest(
private val onSuccessCallback: SimpleCallback,
private val onFailureCallback: SimpleCallback,
private val onShowRationaleCallback: PermissionRationaleCallback
) {
fun onSuccess() = onSuccessCallback()
fun onFailure() = onFailureCallback()
fun onShowRationale(permissions: List<String>) = onShowRationaleCallback(permissions)
}

View File

@ -0,0 +1,45 @@
package com.topjohnwu.magisk.core.data
import com.topjohnwu.magisk.core.model.BranchInfo
import com.topjohnwu.magisk.core.model.ModuleJson
import com.topjohnwu.magisk.core.model.UpdateInfo
import okhttp3.ResponseBody
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Path
import retrofit2.http.Streaming
import retrofit2.http.Url
private const val BRANCH = "branch"
private const val REPO = "repo"
private const val FILE = "file"
interface GithubPageServices {
@GET
suspend fun fetchUpdateJSON(@Url file: String): UpdateInfo
}
interface RawServices {
@GET
@Streaming
suspend fun fetchFile(@Url url: String): ResponseBody
@GET
suspend fun fetchString(@Url url: String): String
@GET
suspend fun fetchModuleJson(@Url url: String): ModuleJson
}
interface GithubApiServices {
@GET("repos/{$REPO}/branches/{$BRANCH}")
@Headers("Accept: application/vnd.github.v3+json")
suspend fun fetchBranch(
@Path(REPO, encoded = true) repo: String,
@Path(BRANCH) branch: String
): BranchInfo
}

View File

@ -1,15 +1,31 @@
package com.topjohnwu.magisk.data.database
package com.topjohnwu.magisk.core.data
import androidx.room.*
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Insert
import androidx.room.Query
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.topjohnwu.magisk.core.model.su.SuLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import java.util.Calendar
@Database(version = 1, entities = [SuLog::class], exportSchema = false)
@Database(version = 2, entities = [SuLog::class], exportSchema = false)
abstract class SuLogDatabase : RoomDatabase() {
abstract fun suLogDao(): SuLogDao
companion object {
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) = with(database) {
execSQL("ALTER TABLE logs ADD COLUMN target INTEGER NOT NULL DEFAULT -1")
execSQL("ALTER TABLE logs ADD COLUMN context TEXT NOT NULL DEFAULT ''")
execSQL("ALTER TABLE logs ADD COLUMN gids TEXT NOT NULL DEFAULT ''")
}
}
}
}
@Dao

View File

@ -0,0 +1,47 @@
package com.topjohnwu.magisk.core.data.magiskdb
import com.topjohnwu.magisk.core.ktx.await
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
open class MagiskDB {
suspend fun <R> exec(
query: String,
mapper: suspend (Map<String, String>) -> R
): List<R> {
return withContext(Dispatchers.IO) {
val out = Shell.cmd("magisk --sqlite '$query'").await().out
out.map { line ->
line.split("\\|".toRegex())
.map { it.split("=", limit = 2) }
.filter { it.size == 2 }
.associate { it[0] to it[1] }
.let { mapper(it) }
}
}
}
suspend inline fun exec(query: String) {
exec(query) {}
}
fun Map<String, Any>.toQuery(): String {
val keys = this.keys.joinToString(",")
val values = this.values.joinToString(",") {
when (it) {
is Boolean -> if (it) "1" else "0"
is Number -> it.toString()
else -> "\"$it\""
}
}
return "($keys) VALUES($values)"
}
object Table {
const val POLICY = "policies"
const val SETTINGS = "settings"
const val STRINGS = "strings"
}
}

View File

@ -0,0 +1,53 @@
package com.topjohnwu.magisk.core.data.magiskdb
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.model.su.SuPolicy
import java.util.concurrent.TimeUnit
class PolicyDao : MagiskDB() {
suspend fun deleteOutdated() {
val nowSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
val query = "DELETE FROM ${Table.POLICY} WHERE " +
"(until > 0 AND until < $nowSeconds) OR until < 0"
exec(query)
}
suspend fun delete(uid: Int) {
val query = "DELETE FROM ${Table.POLICY} WHERE uid == $uid"
exec(query)
}
suspend fun fetch(uid: Int): SuPolicy? {
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1"
return exec(query, ::toPolicy).firstOrNull()
}
suspend fun update(policy: SuPolicy) {
val map = policy.toMap()
if (!Const.Version.atLeast_25_0()) {
// Put in package_name for old database
map["package_name"] = AppContext.packageManager.getNameForUid(policy.uid)!!
}
val query = "REPLACE INTO ${Table.POLICY} ${map.toQuery()}"
exec(query)
}
suspend fun fetchAll(): List<SuPolicy> {
val query = "SELECT * FROM ${Table.POLICY} WHERE uid/100000 == ${Const.USER_ID}"
return exec(query, ::toPolicy).filterNotNull()
}
private fun toPolicy(map: Map<String, String>): SuPolicy? {
val uid = map["uid"]?.toInt() ?: return null
val policy = SuPolicy(uid)
map["policy"]?.toInt()?.let { policy.policy = it }
map["until"]?.toLong()?.let { policy.until = it }
map["logging"]?.toInt()?.let { policy.logging = it != 0 }
map["notification"]?.toInt()?.let { policy.notification = it != 0 }
return policy
}
}

View File

@ -0,0 +1,20 @@
package com.topjohnwu.magisk.core.data.magiskdb
class SettingsDao : MagiskDB() {
suspend fun delete(key: String) {
val query = "DELETE FROM ${Table.SETTINGS} WHERE key == \"$key\""
exec(query)
}
suspend fun put(key: String, value: Int) {
val kv = mapOf("key" to key, "value" to value)
val query = "REPLACE INTO ${Table.SETTINGS} ${kv.toQuery()}"
exec(query)
}
suspend fun fetch(key: String, default: Int = -1): Int {
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key == \"$key\" LIMIT 1"
return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default
}
}

View File

@ -0,0 +1,20 @@
package com.topjohnwu.magisk.core.data.magiskdb
class StringDao : MagiskDB() {
suspend fun delete(key: String) {
val query = "DELETE FROM ${Table.STRINGS} WHERE key == \"$key\""
exec(query)
}
suspend fun put(key: String, value: String) {
val kv = mapOf("key" to key, "value" to value)
val query = "REPLACE INTO ${Table.STRINGS} ${kv.toQuery()}"
exec(query)
}
suspend fun fetch(key: String, default: String = ""): String {
val query = "SELECT value FROM ${Table.STRINGS} WHERE key == \"$key\" LIMIT 1"
return exec(query) { it["value"] }.firstOrNull() ?: default
}
}

View File

@ -1,55 +1,36 @@
package com.topjohnwu.magisk.di
package com.topjohnwu.magisk.core.di
import android.content.Context
import android.os.Build
import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.ProviderInstaller
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.data.network.GithubApiServices
import com.topjohnwu.magisk.data.network.GithubPageServices
import com.topjohnwu.magisk.data.network.JSDelivrServices
import com.topjohnwu.magisk.data.network.RawServices
import com.topjohnwu.magisk.ktx.precomputedText
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.net.NoSSLv3SocketFactory
import com.topjohnwu.magisk.utils.MarkwonImagePlugin
import io.noties.markwon.Markwon
import io.noties.markwon.html.HtmlPlugin
import com.topjohnwu.magisk.core.utils.currentLocale
import okhttp3.Cache
import okhttp3.ConnectionSpec
import okhttp3.Dns
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import java.io.File
import java.net.InetAddress
import java.net.UnknownHostException
val networkingModule = module {
single { createOkHttpClient(get()) }
single { createRetrofit(get()) }
single { createApiService<RawServices>(get(), Const.Url.GITHUB_RAW_URL) }
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
single { createApiService<GithubPageServices>(get(), Const.Url.GITHUB_PAGE_URL) }
single { createApiService<JSDelivrServices>(get(), Const.Url.JS_DELIVR_URL) }
single { createMarkwon(get(), get()) }
}
private class DnsResolver(client: OkHttpClient) : Dns {
private val doh by lazy {
DnsOverHttps.Builder().client(client)
.url(HttpUrl.get("https://cloudflare-dns.com/dns-query"))
.url("https://cloudflare-dns.com/dns-query".toHttpUrl())
.bootstrapDnsHosts(listOf(
InetAddress.getByName("162.159.36.1"),
InetAddress.getByName("162.159.46.1"),
InetAddress.getByName("1.1.1.1"),
InetAddress.getByName("1.0.0.1"),
InetAddress.getByName("162.159.132.53"),
InetAddress.getByName("2606:4700:4700::1111"),
InetAddress.getByName("2606:4700:4700::1001"),
InetAddress.getByName("2606:4700:4700::0064"),
@ -69,23 +50,32 @@ private class DnsResolver(client: OkHttpClient) : Dns {
}
}
@Suppress("DEPRECATION")
fun createOkHttpClient(context: Context): OkHttpClient {
val builder = OkHttpClient.Builder()
val appCache = Cache(File(context.cacheDir, "okhttp"), 10 * 1024 * 1024)
val builder = OkHttpClient.Builder().cache(appCache)
if (BuildConfig.DEBUG) {
builder.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
})
} else {
builder.connectionSpecs(listOf(ConnectionSpec.MODERN_TLS))
}
if (!Networking.init(context)) {
Info.hasGMS = false
if (Build.VERSION.SDK_INT < 21)
builder.sslSocketFactory(NoSSLv3SocketFactory())
}
builder.dns(DnsResolver(builder.build()))
builder.addInterceptor { chain ->
val request = chain.request().newBuilder()
request.header("User-Agent", "Magisk/${BuildConfig.VERSION_CODE}")
request.header("Accept-Language", currentLocale.toLanguageTag())
chain.proceed(request.build())
}
if (!ProviderInstaller.install(context)) {
Info.hasGMS = false
}
return builder.build()
}
@ -107,14 +97,3 @@ inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseU
.build()
.create(T::class.java)
}
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
return Markwon.builder(context)
.textSetter { textView, spanned, _, onComplete ->
textView.tag = onComplete
textView.precomputedText = spanned
}
.usePlugin(HtmlPlugin.create())
.usePlugin(MarkwonImagePlugin(okHttpClient))
.build()
}

View File

@ -0,0 +1,60 @@
package com.topjohnwu.magisk.core.di
import android.annotation.SuppressLint
import android.content.Context
import android.text.method.LinkMovementMethod
import androidx.room.Room
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.data.SuLogDatabase
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.data.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.data.magiskdb.StringDao
import com.topjohnwu.magisk.core.ktx.deviceProtectedContext
import com.topjohnwu.magisk.core.repository.LogRepository
import com.topjohnwu.magisk.core.repository.NetworkService
import io.noties.markwon.Markwon
import io.noties.markwon.utils.NoCopySpannableFactory
val AppContext: Context inline get() = ServiceLocator.context
@SuppressLint("StaticFieldLeak")
object ServiceLocator {
lateinit var context: Context
val deContext by lazy { context.deviceProtectedContext }
val timeoutPrefs by lazy { deContext.getSharedPreferences("su_timeout", 0) }
// Database
val policyDB = PolicyDao()
val settingsDB = SettingsDao()
val stringDB = StringDao()
val sulogDB by lazy { createSuLogDatabase(deContext).suLogDao() }
val logRepo by lazy { LogRepository(sulogDB) }
// Networking
val okhttp by lazy { createOkHttpClient(context) }
val retrofit by lazy { createRetrofit(okhttp) }
val markwon by lazy { createMarkwon(context) }
val networkService by lazy {
NetworkService(
createApiService(retrofit, Const.Url.GITHUB_PAGE_URL),
createApiService(retrofit, Const.Url.GITHUB_RAW_URL),
)
}
}
private fun createSuLogDatabase(context: Context) =
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
.addMigrations(SuLogDatabase.MIGRATION_1_2)
.fallbackToDestructiveMigration()
.build()
private fun createMarkwon(context: Context) =
Markwon.builder(context).textSetter { textView, spanned, bufferType, onComplete ->
textView.apply {
movementMethod = LinkMovementMethod.getInstance()
setSpannableFactory(NoCopySpannableFactory.getInstance())
setText(spanned, bufferType)
onComplete.run()
}
}.build()

View File

@ -1,194 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Intent
import android.os.IBinder
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.ForegroundTracker
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import okhttp3.ResponseBody
import org.koin.android.ext.android.inject
import org.koin.core.KoinComponent
import timber.log.Timber
import java.io.IOException
import java.io.InputStream
import java.util.*
import kotlin.collections.HashMap
import kotlin.random.Random.Default.nextInt
abstract class BaseDownloader : BaseService(), KoinComponent {
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
private val coroutineScope = CoroutineScope(Dispatchers.IO)
val service: NetworkService by inject()
// -- Service overrides
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
intent.getParcelableExtra<Subject>(ACTION_KEY)?.let { subject ->
update(subject.notifyID())
coroutineScope.launch {
try {
subject.startDownload()
} catch (e: IOException) {
Timber.e(e)
notifyFail(subject)
}
}
}
return START_REDELIVER_INTENT
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { cancel(it.key) }
notifications.clear()
}
override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel()
}
// -- Download logic
private suspend fun Subject.startDownload() {
val stream = service.fetchFile(url).toProgressStream(this)
when (this) {
is Subject.Module -> // Download and process on-the-fly
stream.toModule(file, service.fetchInstaller().byteStream())
is Subject.Manager -> handleAPK(this, stream)
}
val newId = notifyFinish(this)
if (ForegroundTracker.hasForeground)
onFinish(this, newId)
if (!hasNotifications)
stopSelf()
}
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
val max = contentLength()
val total = max.toFloat() / 1048576
val id = subject.notifyID()
update(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it.toFloat() / 1048576
update(id) { notification ->
if (max > 0) {
broadcast(progress / total, subject)
notification
.setProgress(max.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, total))
} else {
broadcast(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
// --- Notification managements
fun Subject.notifyID() = hashCode()
private fun notifyFail(subject: Subject) = lastNotify(subject.notifyID()) {
broadcast(-2f, subject)
it.setContentText(getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
private fun notifyFinish(subject: Subject) = lastNotify(subject.notifyID()) {
broadcast(1f, subject)
it.setIntent(subject)
.setContentTitle(subject.title)
.setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false)
.setOngoing(false)
.setAutoCancel(true)
}
private fun create() = Notifications.progress(this, "")
fun update(id: Int, editor: (Notification.Builder) -> Unit = {}) {
val wasEmpty = !hasNotifications
val notification = notifications.getOrPut(id, ::create).also(editor)
if (wasEmpty)
updateForeground()
else
notify(id, notification.build())
}
private fun lastNotify(
id: Int,
editor: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
val notification = remove(id)?.run(editor) ?: return -1
val newId: Int = nextInt()
notify(newId, notification.build())
return newId
}
protected fun remove(id: Int) = notifications.remove(id)
?.also { updateForeground(); cancel(id) }
?: { cancel(id); null }()
private fun notify(id: Int, notification: Notification) {
Notifications.mgr.notify(id, notification)
}
private fun cancel(id: Int) {
Notifications.mgr.cancel(id)
}
private fun updateForeground() {
if (hasNotifications) {
val (id, notification) = notifications.entries.first()
startForeground(id, notification.build())
} else {
stopForeground(false)
}
}
// --- Implement custom logic
protected abstract suspend fun onFinish(subject: Subject, id: Int)
protected abstract fun Notification.Builder.setIntent(subject: Subject): Notification.Builder
// ---
companion object : KoinComponent {
const val ACTION_KEY = "download_action"
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>>()
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
progressBroadcast.value = null
progressBroadcast.observe(owner) {
val (progress, subject) = it ?: return@observe
callback(progress, subject)
}
}
private fun broadcast(progress: Float, subject: Subject) {
progressBroadcast.postValue(progress to subject)
}
}
}

View File

@ -0,0 +1,373 @@
package com.topjohnwu.magisk.core.download
import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.collection.SparseArrayCompat
import androidx.collection.isNotEmpty
import androidx.core.content.getSystemService
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.ActivityTracker
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.JobService
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.cmp
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.forEach
import com.topjohnwu.magisk.core.ktx.set
import com.topjohnwu.magisk.core.ktx.withStreams
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import okhttp3.ResponseBody
import timber.log.Timber
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
/**
* This class drives the execution of file downloads and notification management.
*
* Each download engine instance has to be paired with a "session" that is managed by the operating
* system. A session is an Android component that allows executing long lasting operations and
* have its state tied to a notification to show progress.
*
* A session can only have one single notification representing its state, and the operating system
* also uses the notification to manage the lifecycle of a session. One goal of this class is
* to support concurrent download tasks using only one single session, so internally it manages
* all active tasks and notifications and properly re-assign notifications to be attached to
* the session to make sure all download operations can be completed without the operating system
* killing the session.
*
* For API 23 - 33, we use a foreground service as a session.
* For API 34 and higher, we use user-initiated job services as a session.
*/
class DownloadEngine(
private val session: Session
) {
interface Session {
val context: Context
fun attachNotification(id: Int, builder: Notification.Builder)
fun onDownloadComplete()
}
companion object {
const val ACTION = "com.topjohnwu.magisk.DOWNLOAD"
const val SUBJECT_KEY = "subject"
private const val REQUEST_CODE = 1
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
private fun broadcast(progress: Float, subject: Subject) {
progressBroadcast.postValue(progress to subject)
}
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
progressBroadcast.value = null
progressBroadcast.observe(owner) {
val (progress, subject) = it ?: return@observe
callback(progress, subject)
}
}
private fun createIntent(context: Context, subject: Subject) =
if (Build.VERSION.SDK_INT >= 34) {
context.intent<com.topjohnwu.magisk.core.Receiver>()
.setAction(ACTION)
.putExtra(SUBJECT_KEY, subject)
} else {
context.intent<com.topjohnwu.magisk.core.Service>()
.setAction(ACTION)
.putExtra(SUBJECT_KEY, subject)
}
@SuppressLint("InlinedApi")
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
val flag = PendingIntent.FLAG_IMMUTABLE or
PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_ONE_SHOT
val intent = createIntent(context, subject)
return if (Build.VERSION.SDK_INT >= 34) {
// On API 34+, download tasks are handled with a user-initiated job.
// However, there is no way to schedule a new job directly with a pending intent.
// As a workaround, we send the subject to a broadcast receiver and have it
// schedule the job for us.
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
} else if (Build.VERSION.SDK_INT >= 26) {
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
} else {
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
}
}
@SuppressLint("InlinedApi")
fun startWithActivity(activity: BaseActivity, subject: Subject) {
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
// Always download regardless of notification permission status
start(activity.applicationContext, subject)
}
}
fun start(context: Context, subject: Subject) {
if (Build.VERSION.SDK_INT >= 34) {
val scheduler = context.getSystemService<JobScheduler>()!!
val cmp = JobService::class.java.cmp(context.packageName)
val extras = Bundle()
extras.putParcelable(SUBJECT_KEY, subject)
val info = JobInfo.Builder(Const.ID.DOWNLOAD_JOB_ID, cmp)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setUserInitiated(true)
.setTransientExtras(extras)
.build()
scheduler.schedule(info)
} else if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(createIntent(context, subject))
} else {
context.startService(createIntent(context, subject))
}
}
}
fun download(subject: Subject) {
notifyUpdate(subject.notifyId)
CoroutineScope(job + Dispatchers.IO).launch {
try {
val stream = network.fetchFile(subject.url).toProgressStream(subject)
when (subject) {
is Subject.App -> handleApp(stream, subject)
is Subject.Module -> handleModule(stream, subject.file)
else -> stream.copyAndClose(subject.file.outputStream())
}
val activity = ActivityTracker.foreground
if (activity != null && subject.autoLaunch) {
notifyRemove(subject.notifyId)
subject.pendingIntent(activity)?.send()
} else {
notifyFinish(subject)
}
} catch (e: IOException) {
Timber.e(e)
notifyFail(subject)
}
}
}
@Synchronized
fun reattach() {
val builder = notifications[attachedId] ?: return
session.attachNotification(attachedId, builder)
}
private val notifications = SparseArrayCompat<Notification.Builder>()
private var attachedId = -1
private val job = Job()
private val context get() = session.context
private val network get() = ServiceLocator.networkService
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
val notification = notifyRemove(id)?.also(editor) ?: return -1
val newId = Notifications.nextId()
Notifications.mgr.notify(newId, notification.build())
return newId
}
private fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(-2f, subject)
it.setContentText(context.getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(1f, subject)
it.setContentTitle(subject.title)
.setContentText(context.getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false)
.setOngoing(false)
.setAutoCancel(true)
subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }
}
private fun attachNotification(id: Int, notification: Notification.Builder) {
attachedId = id
session.attachNotification(id, notification)
}
@Synchronized
private fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
val notification = (notifications[id] ?: Notifications.startProgress("").also {
notifications[id] = it
}).apply(editor)
if (attachedId < 0)
attachNotification(id, notification)
else
Notifications.mgr.notify(id, notification.build())
}
@Synchronized
private fun notifyRemove(id: Int): Notification.Builder? {
val idx = notifications.indexOfKey(id)
var n: Notification.Builder? = null
if (idx >= 0) {
n = notifications.valueAt(idx)
notifications.removeAt(idx)
// The cancelled notification is the one attached to the session, need special handling
if (attachedId == id) {
if (notifications.isNotEmpty()) {
// There are still remaining notifications, pick one and attach to the session
val anotherId = notifications.keyAt(0)
val notification = notifications.valueAt(0)
attachNotification(anotherId, notification)
} else {
// No more notifications left, terminate the session
attachedId = -1
session.onDownloadComplete()
}
}
}
Notifications.mgr.cancel(id)
return n
}
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
val external = subject.file.outputStream()
if (isRunningAsStub) {
val updateApk = StubApk.update(context)
try {
// Download full APK to stub update path
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
// Also upgrade stub
notifyUpdate(subject.notifyId) {
it.setProgress(0, 0, true)
.setContentTitle(context.getString(R.string.hide_app_title))
.setContentText("")
}
// Extract stub
val zf = ZipFile(updateApk)
val apk = context.cachedFile("stub.apk")
apk.delete()
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
zf.close()
// Patch and install
subject.intent = HideAPK.upgrade(context, apk)
?: throw IOException("HideAPK patch error")
apk.delete()
} catch (e: Exception) {
// If any error occurred, do not let stub load the new APK
updateApk.delete()
throw e
}
} else {
val session = APKInstall.startSession(context)
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
subject.intent = session.waitIntent()
}
}
private suspend fun handleModule(src: InputStream, file: Uri) {
val input = ZipInputStream(src)
val output = ZipOutputStream(file.outputStream())
withStreams(input, output) { zin, zout ->
zout.putNextEntry(ZipEntry("META-INF/"))
zout.putNextEntry(ZipEntry("META-INF/com/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray())
zin.forEach { entry ->
val path = entry.name
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
zout.putNextEntry(ZipEntry(path))
if (!entry.isDirectory) {
zin.copyAll(zout)
}
}
}
}
}
private class TeeOutputStream(
private val o1: OutputStream,
private val o2: OutputStream
) : OutputStream() {
override fun write(b: Int) {
o1.write(b)
o2.write(b)
}
override fun write(b: ByteArray?, off: Int, len: Int) {
o1.write(b, off, len)
o2.write(b, off, len)
}
override fun close() {
o1.close()
o2.close()
}
}
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
val max = contentLength()
val total = max.toFloat() / 1048576
val id = subject.notifyId
notifyUpdate(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it.toFloat() / 1048576
notifyUpdate(id) { notification ->
if (max > 0) {
broadcast(progress / total, subject)
notification
.setProgress(max.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, total))
} else {
broadcast(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
}

View File

@ -1,87 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.download.Action.Flash
import com.topjohnwu.magisk.core.download.Subject.Manager
import com.topjohnwu.magisk.core.download.Subject.Module
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall
import kotlin.random.Random.Default.nextInt
@SuppressLint("Registered")
open class DownloadService : BaseDownloader() {
private val context get() = this
override suspend fun onFinish(subject: Subject, id: Int) = when (subject) {
is Module -> subject.onFinish(id)
is Manager -> subject.onFinish(id)
}
private fun Module.onFinish(id: Int) = when (action) {
Flash -> FlashFragment.install(file, id)
else -> Unit
}
private fun Manager.onFinish(id: Int) {
remove(id)
APKInstall.install(context, file.toFile())
}
// --- Customize finish notification
override fun Notification.Builder.setIntent(subject: Subject)
= when (subject) {
is Module -> setIntent(subject)
is Manager -> setIntent(subject)
}
private fun Notification.Builder.setIntent(subject: Module)
= when (subject.action) {
Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
else -> setContentIntent(Intent())
}
private fun Notification.Builder.setIntent(subject: Manager)
= setContentIntent(APKInstall.installIntent(context, subject.file.toFile()))
private fun Notification.Builder.setContentIntent(intent: Intent) =
setContentIntent(
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
)
// ---
companion object {
private fun intent(context: Context, subject: Subject) =
context.intent<DownloadService>().putExtra(ACTION_KEY, subject)
fun pendingIntent(context: Context, subject: Subject): PendingIntent {
return if (Build.VERSION.SDK_INT >= 26) {
PendingIntent.getForegroundService(context, nextInt(),
intent(context, subject), PendingIntent.FLAG_UPDATE_CURRENT)
} else {
PendingIntent.getService(context, nextInt(),
intent(context, subject), PendingIntent.FLAG_UPDATE_CURRENT)
}
}
fun start(context: Context, subject: Subject) {
val app = context.applicationContext
if (Build.VERSION.SDK_INT >= 26) {
app.startForegroundService(intent(app, subject))
} else {
app.startService(intent(app, subject))
}
}
}
}

View File

@ -1,75 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.content.Context
import androidx.core.net.toFile
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.relaunchApp
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo
import java.io.File
import java.io.InputStream
import java.io.OutputStream
private fun Context.patch(apk: File) {
val patched = File(apk.parent, "patched.apk")
HideAPK.patch(this, apk, patched, packageName, applicationInfo.nonLocalizedLabel)
apk.delete()
patched.renameTo(apk)
}
private fun BaseDownloader.notifyHide(id: Int) {
update(id) {
it.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_app_title))
.setContentText("")
}
}
private class DupOutputStream(
private val o1: OutputStream,
private val o2: OutputStream
) : OutputStream() {
override fun write(b: Int) {
o1.write(b)
o2.write(b)
}
override fun write(b: ByteArray?, off: Int, len: Int) {
o1.write(b, off, len)
o2.write(b, off, len)
}
override fun close() {
o1.close()
o2.close()
}
}
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager, stream: InputStream) {
fun write(output: OutputStream) {
val ext = subject.externalFile.outputStream()
val o = DupOutputStream(ext, output)
withStreams(stream, o) { src, out -> src.copyTo(out) }
}
if (isRunningAsStub) {
val apk = subject.file.toFile()
val id = subject.notifyID()
write(DynAPK.update(this).outputStream())
if (Info.stub!!.version < subject.stub.versionCode) {
// Also upgrade stub
notifyHide(id)
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
patch(apk)
} else {
// Simply relaunch the app
stopSelf()
relaunchApp(this)
}
} else {
write(subject.file.outputStream())
}
}

View File

@ -1,43 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.net.Uri
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.forEach
import com.topjohnwu.magisk.ktx.withStreams
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
fun InputStream.toModule(file: Uri, installer: InputStream) {
val input = ZipInputStream(buffered())
val output = ZipOutputStream(file.outputStream().buffered())
withStreams(input, output) { zin, zout ->
zout.putNextEntry(ZipEntry("META-INF/"))
zout.putNextEntry(ZipEntry("META-INF/com/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
installer.copyTo(zout)
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
var off = -1
zin.forEach { entry ->
if (off < 0) {
off = entry.name.indexOf('/') + 1
}
val path = entry.name.substring(off)
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
zout.putNextEntry(ZipEntry(path))
if (!entry.isDirectory) {
zin.copyTo(zout)
}
}
}
}
}

View File

@ -1,64 +1,84 @@
package com.topjohnwu.magisk.core.download
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Parcelable
import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.StubJson
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.view.Notifications
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
private fun cachedFile(name: String) = get<Context>().cachedFile(name).apply { delete() }.toUri()
import java.io.File
import java.util.UUID
sealed class Subject : Parcelable {
abstract val url: String
abstract val file: Uri
abstract val action: Action
abstract val title: String
abstract val notifyId: Int
open val autoLaunch: Boolean get() = true
open fun pendingIntent(context: Context): PendingIntent? = null
@Parcelize
class Module(
val module: OnlineModule,
override val action: Action
private val module: OnlineModule,
override val autoLaunch: Boolean,
override val notifyId: Int = Notifications.nextId()
) : Subject() {
override val url: String get() = module.zip_url
override val url: String get() = module.zipUrl
override val title: String get() = module.downloadFilename
@IgnoredOnParcel
override val file by lazy {
MediaStoreUtils.getFile(title).uri
}
override fun pendingIntent(context: Context) =
FlashFragment.installIntent(context, file)
}
@Parcelize
class Manager(
class App(
private val json: MagiskJson = Info.remote.magisk,
val stub: StubJson = Info.remote.stub
override val notifyId: Int = Notifications.nextId()
) : Subject() {
override val action get() = Action.Download
override val title: String get() = "Magisk-${json.version}(${json.versionCode})"
override val url: String get() = json.link
@IgnoredOnParcel
override val file by lazy {
cachedFile("manager.apk")
MediaStoreUtils.getFile("${title}.apk").uri
}
val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri
@IgnoredOnParcel
var intent: Intent? = null
override fun pendingIntent(context: Context) = intent?.toPending(context)
}
@Parcelize
class Test(
override val notifyId: Int = Notifications.nextId(),
override val title: String = UUID.randomUUID().toString().substring(0, 6)
) : Subject() {
override val url get() = "https://link.testfile.org/250MB"
override val file get() = File("/dev/null").toUri()
override val autoLaunch get() = false
}
@SuppressLint("InlinedApi")
protected fun Intent.toPending(context: Context): PendingIntent {
return PendingIntent.getActivity(context, notifyId, this,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT)
}
}
sealed class Action : Parcelable {
@Parcelize
object Flash : Action()
@Parcelize
object Download : Action()
}

View File

@ -0,0 +1,152 @@
package com.topjohnwu.magisk.core.ktx
import android.annotation.SuppressLint
import android.app.Activity
import android.content.*
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Process
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.getSystemService
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File
import kotlin.String
fun Context.rawResource(id: Int) = resources.openRawResource(id)
fun Context.getBitmap(id: Int): Bitmap {
var drawable = AppCompatResources.getDrawable(this, id)!!
if (drawable is BitmapDrawable)
return drawable.bitmap
if (SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable) {
drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))
}
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
val Context.deviceProtectedContext: Context get() =
if (SDK_INT >= Build.VERSION_CODES.N) {
createDeviceProtectedStorageContext()
} else { this }
fun Context.cachedFile(name: String) = File(cacheDir, name)
fun ApplicationInfo.getLabel(pm: PackageManager): String {
runCatching {
if (labelRes > 0) {
val res = pm.getResourcesForApplication(this)
val config = Configuration()
config.setLocale(currentLocale)
res.updateConfiguration(config, res.displayMetrics)
return res.getString(labelRes)
}
}
return loadLabel(pm).toString()
}
fun Context.unwrap(): Context {
var context = this
while (context is ContextWrapper)
context = context.baseContext
return context
}
fun Activity.hideKeyboard() {
val view = currentFocus ?: return
getSystemService<InputMethodManager>()
?.hideSoftInputFromWindow(view.windowToken, 0)
view.clearFocus()
}
val View.activity: Activity get() {
var context = context
while(true) {
if (context !is ContextWrapper)
error("View is not attached to activity")
if (context is Activity)
return context
context = context.baseContext
}
}
@SuppressLint("PrivateApi")
fun getProperty(key: String, def: String): String {
runCatching {
val clazz = Class.forName("android.os.SystemProperties")
val get = clazz.getMethod("get", String::class.java, String::class.java)
return get.invoke(clazz, key, def) as String
}
return def
}
@SuppressLint("InlinedApi")
@Throws(PackageManager.NameNotFoundException::class)
fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
val flag = PackageManager.MATCH_UNINSTALLED_PACKAGES
val pkgs = getPackagesForUid(uid) ?: throw PackageManager.NameNotFoundException()
if (pkgs.size > 1) {
if (pid <= 0) {
return null
}
// Try to find package name from PID
val proc = RootUtils.obj?.getAppProcess(pid)
if (proc == null) {
if (uid == Process.SHELL_UID) {
// It is possible that some apps installed are sharing UID with shell.
// We will not be able to find a package from the active process list,
// because the client is forked from ADB shell, not any app process.
return getPackageInfo("com.android.shell", flag)
}
} else if (uid == proc.uid) {
return getPackageInfo(proc.pkgList[0], flag)
}
return null
}
if (pkgs.size == 1) {
return getPackageInfo(pkgs[0], flag)
}
throw PackageManager.NameNotFoundException()
}
fun Context.registerRuntimeReceiver(receiver: BroadcastReceiver, filter: IntentFilter) {
APKInstall.registerReceiver(this, receiver, filter)
}
fun Context.selfLaunchIntent(): Intent {
val pm = packageManager
val intent = pm.getLaunchIntentForPackage(packageName)!!
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}
fun Context.toast(msg: CharSequence, duration: Int) {
UiThreadHandler.run { Toast.makeText(this, msg, duration).show() }
}
fun Context.toast(resId: Int, duration: Int) {
UiThreadHandler.run { Toast.makeText(this, resId, duration).show() }
}

View File

@ -0,0 +1,110 @@
package com.topjohnwu.magisk.core.ktx
import androidx.collection.SparseArrayCompat
import com.topjohnwu.magisk.core.utils.currentLocale
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Collections
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
var entry: ZipEntry? = nextEntry
while (entry != null) {
callback(entry)
entry = nextEntry
}
}
inline fun <In : InputStream, Out : OutputStream> withStreams(
inStream: In,
outStream: Out,
withBoth: (In, Out) -> Unit
) {
inStream.use { reader ->
outStream.use { writer ->
withBoth(reader, writer)
}
}
}
@Throws(IOException::class)
suspend fun InputStream.copyAll(
out: OutputStream,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
dispatcher: CoroutineDispatcher = Dispatchers.IO
): Long {
return withContext(dispatcher) {
var bytesCopied: Long = 0
val buffer = ByteArray(bufferSize)
var bytes = read(buffer)
while (isActive && bytes >= 0) {
out.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = read(buffer)
}
bytesCopied
}
}
@Throws(IOException::class)
suspend inline fun InputStream.copyAndClose(
out: OutputStream,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
@Throws(IOException::class)
suspend inline fun InputStream.writeTo(
file: File,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = copyAndClose(file.outputStream(), bufferSize, dispatcher)
operator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {
put(key, value)
}
fun <T> MutableList<T>.synchronized(): MutableList<T> = Collections.synchronizedList(this)
fun <T> MutableSet<T>.synchronized(): MutableSet<T> = Collections.synchronizedSet(this)
fun <K, V> MutableMap<K, V>.synchronized(): MutableMap<K, V> = Collections.synchronizedMap(this)
fun Class<*>.reflectField(name: String): Field =
getDeclaredField(name).apply { isAccessible = true }
inline fun <T, R> Flow<T>.concurrentMap(crossinline transform: suspend (T) -> R): Flow<R> {
return flatMapMerge { value ->
flow { emit(transform(value)) }
}
}
fun Long.toTime(format: DateFormat) = format.format(this).orEmpty()
// Some devices don't allow filenames containing ":"
val timeFormatStandard by lazy {
SimpleDateFormat(
"yyyy-MM-dd'T'HH.mm.ss",
currentLocale
)
}
val timeDateFormat: DateFormat by lazy {
DateFormat.getDateTimeInstance(
DateFormat.DEFAULT,
DateFormat.DEFAULT,
currentLocale
)
}

View File

@ -0,0 +1,16 @@
package com.topjohnwu.magisk.core.ktx
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
fun reboot(reason: String = if (Config.recovery) "recovery" else "") {
if (reason == "recovery") {
// KEYCODE_POWER = 26, hide incorrect "Factory data reset" message
Shell.cmd("/system/bin/input keyevent 26").submit()
}
Shell.cmd("/system/bin/svc power reboot $reason || /system/bin/reboot $reason").submit()
}
suspend fun Shell.Job.await() = withContext(Dispatchers.IO) { exec() }

View File

@ -1,28 +0,0 @@
package com.topjohnwu.magisk.core.magiskdb
import androidx.annotation.StringDef
abstract class BaseDao {
object Table {
const val POLICY = "policies"
const val LOG = "logs"
const val SETTINGS = "settings"
const val STRINGS = "strings"
}
@StringDef(Table.POLICY, Table.LOG, Table.SETTINGS, Table.STRINGS)
@Retention(AnnotationRetention.SOURCE)
annotation class TableStrict
@TableStrict
abstract val table: String
inline fun <reified Builder : Query.Builder> buildQuery(builder: Builder.() -> Unit = {}) =
Builder::class.java.newInstance()
.apply { table = this@BaseDao.table }
.apply(builder)
.toString()
.let { Query(it) }
}

View File

@ -1,77 +0,0 @@
package com.topjohnwu.magisk.core.magiskdb
import android.content.Context
import android.content.pm.PackageManager
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.toMap
import com.topjohnwu.magisk.core.model.su.toPolicy
import com.topjohnwu.magisk.ktx.now
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.TimeUnit
class PolicyDao(
private val context: Context
) : BaseDao() {
override val table: String = Table.POLICY
suspend fun deleteOutdated() = buildQuery<Delete> {
condition {
greaterThan("until", "0")
and {
lessThan("until", TimeUnit.MILLISECONDS.toSeconds(now).toString())
}
or {
lessThan("until", "0")
}
}
}.commit()
suspend fun delete(packageName: String) = buildQuery<Delete> {
condition {
equals("package_name", packageName)
}
}.commit()
suspend fun delete(uid: Int) = buildQuery<Delete> {
condition {
equals("uid", uid)
}
}.commit()
suspend fun fetch(uid: Int) = buildQuery<Select> {
condition {
equals("uid", uid)
}
}.query().first().toPolicyOrNull()
suspend fun update(policy: SuPolicy) = buildQuery<Replace> {
values(policy.toMap())
}.commit()
suspend fun <R: Any> fetchAll(mapper: (SuPolicy) -> R) = buildQuery<Select> {
condition {
equals("uid/100000", Const.USER_ID)
}
}.query {
it.toPolicyOrNull()?.let(mapper)
}
private fun Map<String, String>.toPolicyOrNull(): SuPolicy? {
return runCatching { toPolicy(context.packageManager) }.getOrElse {
Timber.e(it)
if (it is PackageManager.NameNotFoundException) {
val uid = getOrElse("uid") { null } ?: return null
GlobalScope.launch {
delete(uid.toInt())
}
}
null
}
}
}

View File

@ -1,161 +0,0 @@
package com.topjohnwu.magisk.core.magiskdb
import androidx.annotation.StringDef
import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
class Query(private val _query: String) {
val query get() = "magisk --sqlite '$_query'"
interface Builder {
val requestType: String
var table: String
}
suspend inline fun <R : Any> query(crossinline mapper: (Map<String, String>) -> R?): List<R> =
withContext(Dispatchers.Default) {
Shell.su(query).await().out.map { line ->
async {
line.split("\\|".toRegex())
.map { it.split("=", limit = 2) }
.filter { it.size == 2 }
.map { it[0] to it[1] }
.toMap()
.let(mapper)
}
}.awaitAll().filterNotNull()
}
suspend inline fun query() = query { it }
suspend inline fun commit() = Shell.su(query).to(null).await()
}
class Delete : Query.Builder {
override val requestType: String = "DELETE FROM"
override var table = ""
private var condition = ""
fun condition(builder: Condition.() -> Unit) {
condition = Condition().apply(builder).toString()
}
override fun toString(): String {
return listOf(requestType, table, condition).joinToString(" ")
}
}
class Select : Query.Builder {
override val requestType: String get() = "SELECT $fields FROM"
override lateinit var table: String
private var fields = "*"
private var condition = ""
private var orderField = ""
fun fields(vararg newFields: String) {
if (newFields.isEmpty()) {
fields = "*"
return
}
fields = newFields.joinToString(", ")
}
fun condition(builder: Condition.() -> Unit) {
condition = Condition().apply(builder).toString()
}
fun orderBy(field: String, @OrderStrict order: String) {
orderField = "ORDER BY $field $order"
}
override fun toString(): String {
return listOf(requestType, table, condition, orderField).joinToString(" ")
}
}
class Replace : Insert() {
override val requestType: String = "REPLACE INTO"
}
open class Insert : Query.Builder {
override val requestType: String = "INSERT INTO"
override lateinit var table: String
private val keys get() = _values.keys.joinToString(",")
private val values get() = _values.values.joinToString(",") {
when (it) {
is Boolean -> if (it) "1" else "0"
is Number -> it.toString()
else -> "\"$it\""
}
}
private var _values: Map<String, Any> = mapOf()
fun values(vararg pairs: Pair<String, Any>) {
_values = pairs.toMap()
}
fun values(values: Map<String, Any>) {
_values = values
}
override fun toString(): String {
return listOf(requestType, table, "($keys) VALUES($values)").joinToString(" ")
}
}
class Condition {
private val conditionWord = "WHERE %s"
private var condition: String = ""
fun equals(field: String, value: Any) {
condition = when (value) {
is String -> "$field=\"$value\""
else -> "$field=$value"
}
}
fun greaterThan(field: String, value: String) {
condition = "$field > $value"
}
fun lessThan(field: String, value: String) {
condition = "$field < $value"
}
fun greaterOrEqualTo(field: String, value: String) {
condition = "$field >= $value"
}
fun lessOrEqualTo(field: String, value: String) {
condition = "$field <= $value"
}
fun and(builder: Condition.() -> Unit) {
condition = "($condition AND ${Condition().apply(builder).condition})"
}
fun or(builder: Condition.() -> Unit) {
condition = "($condition OR ${Condition().apply(builder).condition})"
}
override fun toString(): String {
return conditionWord.format(condition)
}
}
object Order {
const val ASC = "ASC"
const val DESC = "DESC"
}
@StringDef(Order.ASC, Order.DESC)
@Retention(AnnotationRetention.SOURCE)
annotation class OrderStrict

View File

@ -1,22 +0,0 @@
package com.topjohnwu.magisk.core.magiskdb
class SettingsDao : BaseDao() {
override val table = Table.SETTINGS
suspend fun delete(key: String) = buildQuery<Delete> {
condition { equals("key", key) }
}.commit()
suspend fun put(key: String, value: Int) = buildQuery<Replace> {
values("key" to key, "value" to value)
}.commit()
suspend fun fetch(key: String, default: Int = -1) = buildQuery<Select> {
fields("value")
condition { equals("key", key) }
}.query {
it["value"]?.toIntOrNull()
}.firstOrNull() ?: default
}

View File

@ -1,22 +0,0 @@
package com.topjohnwu.magisk.core.magiskdb
class StringDao : BaseDao() {
override val table = Table.STRINGS
suspend fun delete(key: String) = buildQuery<Delete> {
condition { equals("key", key) }
}.commit()
suspend fun put(key: String, value: String) = buildQuery<Replace> {
values("key" to key, "value" to value)
}.commit()
suspend fun fetch(key: String, default: String = "") = buildQuery<Select> {
fields("value")
condition { equals("key", key) }
}.query {
it["value"]
}.firstOrNull() ?: default
}

View File

@ -7,7 +7,6 @@ import kotlinx.parcelize.Parcelize
@JsonClass(generateAdapter = true)
data class UpdateInfo(
val magisk: MagiskJson = MagiskJson(),
val stub: StubJson = StubJson()
)
@Parcelize
@ -16,31 +15,15 @@ data class MagiskJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = "",
val md5: String = ""
) : Parcelable
@Parcelize
@JsonClass(generateAdapter = true)
data class StubJson(
val versionCode: Int = -1,
val link: String = ""
val note: String = ""
) : Parcelable
@JsonClass(generateAdapter = true)
data class ModuleJson(
val id: String,
val last_update: Long,
val prop_url: String,
val zip_url: String,
val notes_url: String
)
@JsonClass(generateAdapter = true)
data class RepoJson(
val name: String,
val last_update: Long,
val modules: List<ModuleJson>
val version: String,
val versionCode: Int,
val zipUrl: String,
val changelog: String,
)
@JsonClass(generateAdapter = true)

View File

@ -1,42 +1,52 @@
package com.topjohnwu.magisk.core.model.module
import com.squareup.moshi.JsonDataException
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.IOException
import java.util.*
data class LocalModule(
private val path: String,
) : Module() {
private val svc get() = ServiceLocator.networkService
class LocalModule(path: String) : Module() {
override var id: String = ""
override var name: String = ""
override var author: String = ""
override var version: String = ""
override var versionCode: Int = -1
override var description: String = ""
var author: String = ""
var description: String = ""
var updateInfo: OnlineModule? = null
var outdated = false
private val removeFile = SuFile(path, "remove")
private val disableFile = SuFile(path, "disable")
private val updateFile = SuFile(path, "update")
private val ruleFile = SuFile(path, "sepolicy.rule")
private var updateUrl: String = ""
private val removeFile = RootUtils.fs.getFile(path, "remove")
private val disableFile = RootUtils.fs.getFile(path, "disable")
private val updateFile = RootUtils.fs.getFile(path, "update")
private val riruFolder = RootUtils.fs.getFile(path, "riru")
private val zygiskFolder = RootUtils.fs.getFile(path, "zygisk")
private val unloaded = RootUtils.fs.getFile(zygiskFolder, "unloaded")
val updated: Boolean get() = updateFile.exists()
val isRiru: Boolean get() = (id == "riru-core") || riruFolder.exists()
val isZygisk: Boolean get() = zygiskFolder.exists()
val zygiskUnloaded: Boolean get() = unloaded.exists()
var enable: Boolean
get() = !disableFile.exists()
set(enable) {
val dir = "$PERSIST/$id"
if (enable) {
disableFile.delete()
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit()
Shell.cmd("copy_preinit_files").submit()
} else {
!disableFile.createNewFile()
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("rm -rf $dir").submit()
Shell.cmd("copy_preinit_files").submit()
}
}
@ -44,23 +54,42 @@ class LocalModule(path: String) : Module() {
get() = removeFile.exists()
set(remove) {
if (remove) {
if (updateFile.exists()) return
removeFile.createNewFile()
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("rm -rf $PERSIST/$id").submit()
Shell.cmd("copy_preinit_files").submit()
} else {
!removeFile.delete()
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("cp -af $ruleFile $PERSIST/$id").submit()
removeFile.delete()
Shell.cmd("copy_preinit_files").submit()
}
}
@Throws(NumberFormatException::class)
private fun parseProps(props: List<String>) {
for (line in props) {
val prop = line.split("=".toRegex(), 2).map { it.trim() }
if (prop.size != 2)
continue
val key = prop[0]
val value = prop[1]
if (key.isEmpty() || key[0] == '#')
continue
when (key) {
"id" -> id = value
"name" -> name = value
"version" -> version = value
"versionCode" -> versionCode = value.toInt()
"author" -> author = value
"description" -> description = value
"updateJson" -> updateUrl = value
}
}
}
init {
runCatching {
parseProps(Shell.su("dos2unix < $path/module.prop").exec().out)
parseProps(Shell.cmd("dos2unix < $path/module.prop").exec().out)
}
if (id.isEmpty()) {
@ -73,17 +102,35 @@ class LocalModule(path: String) : Module() {
}
}
suspend fun fetch(): Boolean {
if (updateUrl.isEmpty())
return false
try {
val json = svc.fetchModuleJson(updateUrl)
updateInfo = OnlineModule(this, json)
outdated = json.versionCode > versionCode
return true
} catch (e: IOException) {
Timber.w(e)
} catch (e: JsonDataException) {
Timber.w(e)
}
return false
}
companion object {
private val PERSIST get() = "${Const.MAGISKTMP}/mirror/persist/magisk"
fun loaded() = RootUtils.fs.getFile(Const.MAGISK_PATH).exists()
suspend fun installed() = withContext(Dispatchers.IO) {
SuFile(Const.MAGISK_PATH)
.listFiles { _, name -> name != "lost+found" && name != ".core" }
RootUtils.fs.getFile(Const.MAGISK_PATH)
.listFiles()
.orEmpty()
.filter { !it.isFile }
.filter { !it.isFile && !it.isHidden }
.map { LocalModule("${Const.MAGISK_PATH}/${it.name}") }
.sortedBy { it.name.toLowerCase() }
.sortedBy { it.name.lowercase(Locale.ROOT) }
}
}
}

View File

@ -5,37 +5,10 @@ abstract class Module : Comparable<Module> {
protected set
abstract var name: String
protected set
abstract var author: String
protected set
abstract var version: String
protected set
abstract var versionCode: Int
protected set
abstract var description: String
protected set
@Throws(NumberFormatException::class)
protected fun parseProps(props: List<String>) {
for (line in props) {
val prop = line.split("=".toRegex(), 2).map { it.trim() }
if (prop.size != 2)
continue
val key = prop[0]
val value = prop[1]
if (key.isEmpty() || key[0] == '#')
continue
when (key) {
"id" -> id = value
"name" -> name = value
"version" -> version = value
"versionCode" -> versionCode = value.toInt()
"author" -> author = value
"description" -> description = value
}
}
}
override operator fun compareTo(other: Module) = name.compareTo(other.name, true)
override operator fun compareTo(other: Module) = id.compareTo(other.id)
}

View File

@ -1,66 +1,27 @@
package com.topjohnwu.magisk.core.model.module
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.core.model.ModuleJson
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.legalFilename
import kotlinx.parcelize.Parcelize
import java.text.DateFormat
import java.util.*
@Entity(tableName = "modules")
@Parcelize
data class OnlineModule(
@PrimaryKey override var id: String,
override var name: String = "",
override var author: String = "",
override var version: String = "",
override var versionCode: Int = -1,
override var description: String = "",
val last_update: Long,
val prop_url: String,
val zip_url: String,
val notes_url: String
override var id: String,
override var name: String,
override var version: String,
override var versionCode: Int,
val zipUrl: String,
val changelog: String,
) : Module(), Parcelable {
constructor(local: LocalModule, json: ModuleJson) :
this(local.id, local.name, json.version, json.versionCode, json.zipUrl, json.changelog)
private val svc: NetworkService get() = get()
constructor(info: ModuleJson) : this(
id = info.id,
last_update = info.last_update,
prop_url = info.prop_url,
zip_url = info.zip_url,
notes_url = info.notes_url
)
val lastUpdate get() = Date(last_update)
val lastUpdateString get() = DATE_FORMAT.format(lastUpdate)
val downloadFilename get() = "$name-$version($versionCode).zip".legalFilename()
suspend fun notes() = svc.fetchString(notes_url)
@Throws(IllegalRepoException::class)
suspend fun load() {
try {
val rawProps = svc.fetchString(prop_url)
val props = rawProps.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }
parseProps(props)
} catch (e: Exception) {
throw IllegalRepoException("Repo [$id] parse error:", e)
}
if (versionCode < 0) {
throw IllegalRepoException("Repo [$id] does not contain versionCode")
}
}
class IllegalRepoException(msg: String, cause: Throwable? = null) : Exception(msg, cause)
companion object {
private val DATE_FORMAT = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
}
private fun String.legalFilename() = replace(" ", "_")
.replace("'", "").replace("\"", "")
.replace("$", "").replace("`", "")
.replace("*", "").replace("/", "_")
.replace("#", "").replace("@", "")
.replace("\\", "_")
}

View File

@ -1,30 +1,73 @@
package com.topjohnwu.magisk.core.model.su
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
import com.topjohnwu.magisk.ktx.now
import com.topjohnwu.magisk.ktx.timeFormatTime
import com.topjohnwu.magisk.ktx.toTime
import com.topjohnwu.magisk.core.ktx.getLabel
@Entity(tableName = "logs")
data class SuLog(
class SuLog(
val fromUid: Int,
val toUid: Int,
val fromPid: Int,
val packageName: String,
val appName: String,
val command: String,
val action: Boolean,
val time: Long = -1
val action: Int,
val target: Int,
val context: String,
val gids: String,
val time: Long = System.currentTimeMillis()
) {
@PrimaryKey(autoGenerate = true) var id: Int = 0
@Ignore val timeString = time.toTime(timeFormatTime)
}
fun SuPolicy.toLog(
fun PackageManager.createSuLog(
info: PackageInfo,
toUid: Int,
fromPid: Int,
command: String
) = SuLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, now)
command: String,
policy: Int,
target: Int,
context: String,
gids: String,
): SuLog {
val appInfo = info.applicationInfo
return SuLog(
fromUid = appInfo.uid,
toUid = toUid,
fromPid = fromPid,
packageName = getNameForUid(appInfo.uid)!!,
appName = appInfo.getLabel(this),
command = command,
action = policy,
target = target,
context = context,
gids = gids,
)
}
fun createSuLog(
fromUid: Int,
toUid: Int,
fromPid: Int,
command: String,
policy: Int,
target: Int,
context: String,
gids: String,
): SuLog {
return SuLog(
fromUid = fromUid,
toUid = toUid,
fromPid = fromPid,
packageName = "[UID] $fromUid",
appName = "[UID] $fromUid",
command = command,
action = policy,
target = target,
context = context,
gids = gids,
)
}

View File

@ -1,72 +1,22 @@
@file:SuppressLint("InlinedApi")
package com.topjohnwu.magisk.core.model.su
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.INTERACTIVE
import com.topjohnwu.magisk.ktx.getLabel
data class SuPolicy(
var uid: Int,
val packageName: String,
val appName: String,
val icon: Drawable,
var policy: Int = INTERACTIVE,
var until: Long = -1L,
val logging: Boolean = true,
val notification: Boolean = true
) {
class SuPolicy(val uid: Int) {
companion object {
const val INTERACTIVE = 0
const val DENY = 1
const val ALLOW = 2
}
}
var policy: Int = INTERACTIVE
var until: Long = -1L
var logging: Boolean = true
var notification: Boolean = true
fun SuPolicy.toMap() = mapOf(
"uid" to uid,
"package_name" to packageName,
"policy" to policy,
"until" to until,
"logging" to logging,
"notification" to notification
)
@Throws(PackageManager.NameNotFoundException::class)
fun Map<String, String>.toPolicy(pm: PackageManager): SuPolicy {
val uid = get("uid")?.toIntOrNull() ?: -1
val packageName = get("package_name").orEmpty()
val info = pm.getApplicationInfo(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES)
if (info.uid != uid)
throw PackageManager.NameNotFoundException()
return SuPolicy(
uid = uid,
packageName = packageName,
appName = info.getLabel(pm),
icon = info.loadIcon(pm),
policy = get("policy")?.toIntOrNull() ?: INTERACTIVE,
until = get("until")?.toLongOrNull() ?: -1L,
logging = get("logging")?.toIntOrNull() != 0,
notification = get("notification")?.toIntOrNull() != 0
)
}
@Throws(PackageManager.NameNotFoundException::class)
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): SuPolicy {
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
?: throw PackageManager.NameNotFoundException()
val info = pm.getApplicationInfo(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES)
return SuPolicy(
uid = info.uid,
packageName = pkg,
appName = info.getLabel(pm),
icon = info.loadIcon(pm),
policy = policy
fun toMap(): MutableMap<String, Any> = mutableMapOf(
"uid" to uid,
"policy" to policy,
"until" to until,
"logging" to logging,
"notification" to notification
)
}

View File

@ -1,48 +1,47 @@
package com.topjohnwu.magisk.data.repository
package com.topjohnwu.magisk.core.repository
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.magiskdb.StringDao
import kotlinx.coroutines.GlobalScope
import com.topjohnwu.magisk.core.data.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.data.magiskdb.StringDao
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface DBConfig {
val settingsDao: SettingsDao
val stringDao: StringDao
val settingsDB: SettingsDao
val stringDB: StringDao
val coroutineScope: CoroutineScope
fun dbSettings(
name: String,
default: Int
) = DBSettingsValue(name, default)
) = IntDBProperty(name, default)
fun dbSettings(
name: String,
default: Boolean
) = DBBoolSettings(name, default)
) = BoolDBProperty(name, default)
fun dbStrings(
name: String,
default: String,
sync: Boolean = false
) = DBStringsValue(name, default, sync)
) = StringDBProperty(name, default, sync)
}
class DBSettingsValue(
class IntDBProperty(
private val name: String,
private val default: Int
) : ReadWriteProperty<DBConfig, Int> {
private var value: Int? = null
var value: Int? = null
@Synchronized
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {
if (value == null)
value = runBlocking {
thisRef.settingsDao.fetch(name, default)
}
value = runBlocking { thisRef.settingsDB.fetch(name, default) }
return value as Int
}
@ -50,18 +49,18 @@ class DBSettingsValue(
synchronized(this) {
this.value = value
}
GlobalScope.launch {
thisRef.settingsDao.put(name, value)
thisRef.coroutineScope.launch {
thisRef.settingsDB.put(name, value)
}
}
}
class DBBoolSettings(
open class BoolDBProperty(
name: String,
default: Boolean
) : ReadWriteProperty<DBConfig, Boolean> {
val base = DBSettingsValue(name, if (default) 1 else 0)
val base = IntDBProperty(name, if (default) 1 else 0)
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean =
base.getValue(thisRef, property) != 0
@ -70,7 +69,18 @@ class DBBoolSettings(
base.setValue(thisRef, property, if (value) 1 else 0)
}
class DBStringsValue(
class BoolDBPropertyNoWrite(
name: String,
default: Boolean
) : BoolDBProperty(name, default) {
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) {
synchronized(base) {
base.value = if (value) 1 else 0
}
}
}
class StringDBProperty(
private val name: String,
private val default: String,
private val sync: Boolean
@ -82,7 +92,7 @@ class DBStringsValue(
override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {
if (value == null)
value = runBlocking {
thisRef.stringDao.fetch(name, default)
thisRef.stringDB.fetch(name, default)
}
return value!!
}
@ -94,21 +104,21 @@ class DBStringsValue(
if (value.isEmpty()) {
if (sync) {
runBlocking {
thisRef.stringDao.delete(name)
thisRef.stringDB.delete(name)
}
} else {
GlobalScope.launch {
thisRef.stringDao.delete(name)
thisRef.coroutineScope.launch {
thisRef.stringDB.delete(name)
}
}
} else {
if (sync) {
runBlocking {
thisRef.stringDao.put(name, value)
thisRef.stringDB.put(name, value)
}
} else {
GlobalScope.launch {
thisRef.stringDao.put(name, value)
thisRef.coroutineScope.launch {
thisRef.stringDB.put(name, value)
}
}
}

View File

@ -1,9 +1,10 @@
package com.topjohnwu.magisk.data.repository
package com.topjohnwu.magisk.core.repository
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.data.SuLogDao
import com.topjohnwu.magisk.core.ktx.await
import com.topjohnwu.magisk.core.model.su.SuLog
import com.topjohnwu.magisk.data.database.SuLogDao
import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.superuser.Shell
@ -27,14 +28,18 @@ class LogRepository(
}
}
}
Shell.su("cat ${Const.MAGISK_LOG}").to(list).await()
if (Info.env.isActive) {
Shell.cmd("cat ${Const.MAGISK_LOG} || logcat -d -s Magisk").to(list).await()
} else {
Shell.cmd("logcat -d").to(list).await()
}
return list.buf.toString()
}
suspend fun clearLogs() = logDao.deleteAll()
fun clearMagiskLogs(cb: (Shell.Result) -> Unit) =
Shell.su("echo -n > ${Const.MAGISK_LOG}").submit(cb)
Shell.cmd("echo -n > ${Const.MAGISK_LOG}").submit(cb)
suspend fun insert(log: SuLog) = logDao.insert(log)

View File

@ -0,0 +1,69 @@
package com.topjohnwu.magisk.core.repository
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Config.Value.BETA_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.CANARY_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.CUSTOM_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.DEBUG_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.DEFAULT_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.data.GithubPageServices
import com.topjohnwu.magisk.core.data.RawServices
import retrofit2.HttpException
import timber.log.Timber
import java.io.IOException
class NetworkService(
private val pages: GithubPageServices,
private val raw: RawServices
) {
suspend fun fetchUpdate() = safe {
var info = when (Config.updateChannel) {
DEFAULT_CHANNEL, STABLE_CHANNEL -> fetchStableUpdate()
BETA_CHANNEL -> fetchBetaUpdate()
CANARY_CHANNEL -> fetchCanaryUpdate()
DEBUG_CHANNEL -> fetchDebugUpdate()
CUSTOM_CHANNEL -> fetchCustomUpdate(Config.customChannelUrl)
else -> throw IllegalArgumentException()
}
if (info.magisk.versionCode < Info.env.versionCode &&
Config.updateChannel == DEFAULT_CHANNEL) {
Config.updateChannel = BETA_CHANNEL
info = fetchBetaUpdate()
}
info
}
// UpdateInfo
private suspend fun fetchStableUpdate() = pages.fetchUpdateJSON("stable.json")
private suspend fun fetchBetaUpdate() = pages.fetchUpdateJSON("beta.json")
private suspend fun fetchCanaryUpdate() = pages.fetchUpdateJSON("canary.json")
private suspend fun fetchDebugUpdate() = pages.fetchUpdateJSON("debug.json")
private suspend fun fetchCustomUpdate(url: String) = pages.fetchUpdateJSON(url)
private inline fun <T> safe(factory: () -> T): T? {
return try {
if (Info.isConnected.value == true)
factory()
else
null
} catch (e: Exception) {
Timber.e(e)
null
}
}
private inline fun <T> wrap(factory: () -> T): T {
return try {
factory()
} catch (e: HttpException) {
throw IOException(e)
}
}
// Fetch files
suspend fun fetchFile(url: String) = wrap { raw.fetchFile(url) }
suspend fun fetchString(url: String) = wrap { raw.fetchString(url) }
suspend fun fetchModuleJson(url: String) = wrap { raw.fetchModuleJson(url) }
}

View File

@ -0,0 +1,230 @@
package com.topjohnwu.magisk.core.repository
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface PreferenceConfig {
val context: Context
val fileName: String
get() = "${context.packageName}_preferences"
val prefs: SharedPreferences
get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
fun preferenceStrInt(
name: String,
default: Int,
commit: Boolean = false
) = object: ReadWriteProperty<PreferenceConfig, Int> {
val base = StringProperty(name, default.toString(), commit)
override fun getValue(thisRef: PreferenceConfig, property: KProperty<*>): Int =
base.getValue(thisRef, property).toInt()
override fun setValue(thisRef: PreferenceConfig, property: KProperty<*>, value: Int) =
base.setValue(thisRef, property, value.toString())
}
fun preference(
name: String,
default: Boolean,
commit: Boolean = false
) = BooleanProperty(name, default, commit)
fun preference(
name: String,
default: Float,
commit: Boolean = false
) = FloatProperty(name, default, commit)
fun preference(
name: String,
default: Int,
commit: Boolean = false
) = IntProperty(name, default, commit)
fun preference(
name: String,
default: Long,
commit: Boolean = false
) = LongProperty(name, default, commit)
fun preference(
name: String,
default: String,
commit: Boolean = false
) = StringProperty(name, default, commit)
fun preference(
name: String,
default: Set<String>,
commit: Boolean = false
) = StringSetProperty(name, default, commit)
}
abstract class PreferenceProperty {
fun SharedPreferences.Editor.put(name: String, value: Boolean) = putBoolean(name, value)
fun SharedPreferences.Editor.put(name: String, value: Float) = putFloat(name, value)
fun SharedPreferences.Editor.put(name: String, value: Int) = putInt(name, value)
fun SharedPreferences.Editor.put(name: String, value: Long) = putLong(name, value)
fun SharedPreferences.Editor.put(name: String, value: String) = putString(name, value)
fun SharedPreferences.Editor.put(name: String, value: Set<String>) = putStringSet(name, value)
fun SharedPreferences.get(name: String, value: Boolean) = getBoolean(name, value)
fun SharedPreferences.get(name: String, value: Float) = getFloat(name, value)
fun SharedPreferences.get(name: String, value: Int) = getInt(name, value)
fun SharedPreferences.get(name: String, value: Long) = getLong(name, value)
fun SharedPreferences.get(name: String, value: String) = getString(name, value) ?: value
fun SharedPreferences.get(name: String, value: Set<String>) = getStringSet(name, value) ?: value
}
class BooleanProperty(
private val name: String,
private val default: Boolean,
private val commit: Boolean
) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, Boolean> {
override operator fun getValue(
thisRef: PreferenceConfig,
property: KProperty<*>
): Boolean {
val prefName = name.ifBlank { property.name }
return thisRef.prefs.get(prefName, default)
}
override operator fun setValue(
thisRef: PreferenceConfig,
property: KProperty<*>,
value: Boolean
) {
val prefName = name.ifBlank { property.name }
thisRef.prefs.edit(commit) { put(prefName, value) }
}
}
class FloatProperty(
private val name: String,
private val default: Float,
private val commit: Boolean
) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, Float> {
override operator fun getValue(
thisRef: PreferenceConfig,
property: KProperty<*>
): Float {
val prefName = name.ifBlank { property.name }
return thisRef.prefs.get(prefName, default)
}
override operator fun setValue(
thisRef: PreferenceConfig,
property: KProperty<*>,
value: Float
) {
val prefName = name.ifBlank { property.name }
thisRef.prefs.edit(commit) { put(prefName, value) }
}
}
class IntProperty(
private val name: String,
private val default: Int,
private val commit: Boolean
) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, Int> {
override operator fun getValue(
thisRef: PreferenceConfig,
property: KProperty<*>
): Int {
val prefName = name.ifBlank { property.name }
return thisRef.prefs.get(prefName, default)
}
override operator fun setValue(
thisRef: PreferenceConfig,
property: KProperty<*>,
value: Int
) {
val prefName = name.ifBlank { property.name }
thisRef.prefs.edit(commit) { put(prefName, value) }
}
}
class LongProperty(
private val name: String,
private val default: Long,
private val commit: Boolean
) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, Long> {
override operator fun getValue(
thisRef: PreferenceConfig,
property: KProperty<*>
): Long {
val prefName = name.ifBlank { property.name }
return thisRef.prefs.get(prefName, default)
}
override operator fun setValue(
thisRef: PreferenceConfig,
property: KProperty<*>,
value: Long
) {
val prefName = name.ifBlank { property.name }
thisRef.prefs.edit(commit) { put(prefName, value) }
}
}
class StringProperty(
private val name: String,
private val default: String,
private val commit: Boolean
) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, String> {
override operator fun getValue(
thisRef: PreferenceConfig,
property: KProperty<*>
): String {
val prefName = name.ifBlank { property.name }
return thisRef.prefs.get(prefName, default)
}
override operator fun setValue(
thisRef: PreferenceConfig,
property: KProperty<*>,
value: String
) {
val prefName = name.ifBlank { property.name }
thisRef.prefs.edit(commit) { put(prefName, value) }
}
}
class StringSetProperty(
private val name: String,
private val default: Set<String>,
private val commit: Boolean
) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, Set<String>> {
override operator fun getValue(
thisRef: PreferenceConfig,
property: KProperty<*>
): Set<String> {
val prefName = name.ifBlank { property.name }
return thisRef.prefs.get(prefName, default)
}
override operator fun setValue(
thisRef: PreferenceConfig,
property: KProperty<*>,
value: Set<String>
) {
val prefName = name.ifBlank { property.name }
thisRef.prefs.edit(commit) { put(prefName, value) }
}
}

View File

@ -1,27 +1,18 @@
package com.topjohnwu.magisk.core.su
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Process
import android.widget.Toast
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.ktx.getPackageInfo
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.toLog
import com.topjohnwu.magisk.core.model.su.toPolicy
import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.startActivity
import com.topjohnwu.magisk.ktx.startActivityWithRoot
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import com.topjohnwu.magisk.core.model.su.createSuLog
import kotlinx.coroutines.runBlocking
import timber.log.Timber
object SuCallbackHandler {
@ -29,9 +20,8 @@ object SuCallbackHandler {
const val REQUEST = "request"
const val LOG = "log"
const val NOTIFY = "notify"
const val TEST = "test"
operator fun invoke(context: Context, action: String?, data: Bundle?) {
fun run(context: Context, action: String?, data: Bundle?) {
data ?: return
// Debug messages
@ -45,94 +35,68 @@ object SuCallbackHandler {
}
when (action) {
REQUEST -> handleRequest(context, data)
LOG -> handleLogging(context, data)
NOTIFY -> handleNotify(context, data)
TEST -> {
val mode = data.getInt("mode", 2)
Shell.su(
"magisk --connect-mode $mode",
"magisk --use-broadcast"
).submit()
}
}
}
private fun Any?.toInt(): Int? {
return when (this) {
is Number -> this.toInt()
else -> null
}
}
private fun handleRequest(context: Context, data: Bundle) {
val intent = context.intent<SuRequestActivity>()
.setAction(REQUEST)
.putExtras(data)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
if (Build.VERSION.SDK_INT >= 29) {
// Android Q does not allow starting activity from background
intent.startActivityWithRoot()
} else {
intent.startActivity(context)
// https://android.googlesource.com/platform/frameworks/base/+/547bf5487d52b93c9fe183aa6d56459c170b17a4
private fun Bundle.getIntComp(key: String, defaultValue: Int): Int {
val value = get(key) ?: return defaultValue
return when (value) {
is Int -> value
is Long -> value.toInt()
else -> defaultValue
}
}
private fun handleLogging(context: Context, data: Bundle) {
val fromUid = data["from.uid"].toInt() ?: return
if (fromUid == Process.myUid())
return
val fromUid = data.getIntComp("from.uid", -1)
val notify = data.getBoolean("notify", true)
val policy = data.getIntComp("policy", SuPolicy.ALLOW)
val toUid = data.getIntComp("to.uid", -1)
val pid = data.getIntComp("pid", -1)
val command = data.getString("command", "")
val target = data.getIntComp("target", -1)
val seContext = data.getString("context", "")
val gids = data.getString("gids", "")
val pm = context.packageManager
val notify = data.getBoolean("notify", true)
val allow = data["policy"].toInt() ?: return
val policy = runCatching { fromUid.toPolicy(pm, allow) }.getOrElse { return }
val log = runCatching {
pm.getPackageInfo(fromUid, pid)?.let {
pm.createSuLog(it, toUid, pid, command, policy, target, seContext, gids)
}
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
if (notify)
notify(context, policy)
notify(context, log.action == SuPolicy.ALLOW, log.appName)
val toUid = data["to.uid"].toInt() ?: return
val pid = data["pid"].toInt() ?: return
val command = data.getString("command") ?: return
val log = policy.toLog(
toUid = toUid,
fromPid = pid,
command = command
)
val logRepo = get<LogRepository>()
GlobalScope.launch {
logRepo.insert(log)
}
runBlocking { ServiceLocator.logRepo.insert(log) }
}
private fun handleNotify(context: Context, data: Bundle) {
val fromUid = data["from.uid"].toInt() ?: return
if (fromUid == Process.myUid())
return
val uid = data.getIntComp("from.uid", -1)
val pid = data.getIntComp("pid", -1)
val policy = data.getIntComp("policy", SuPolicy.ALLOW)
val pm = context.packageManager
val allow = data["policy"].toInt() ?: return
runCatching {
val policy = fromUid.toPolicy(pm, allow)
if (policy.policy >= 0)
notify(context, policy)
}
val appName = runCatching {
pm.getPackageInfo(uid, pid)?.applicationInfo?.getLabel(pm)
}.getOrNull() ?: "[UID] $uid"
notify(context, policy == SuPolicy.ALLOW, appName)
}
private fun notify(context: Context, policy: SuPolicy) {
if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
val resId = if (policy.policy == SuPolicy.ALLOW)
private fun notify(context: Context, granted: Boolean, appName: String) {
if (Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
val resId = if (granted)
R.string.su_allow_toast
else
R.string.su_deny_toast
Utils.toast(context.getString(resId, policy.appName), Toast.LENGTH_SHORT)
context.toast(context.getString(resId, appName), Toast.LENGTH_SHORT)
}
}
}

View File

@ -1,30 +1,31 @@
package com.topjohnwu.magisk.core.su
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.LocalSocket
import android.net.LocalSocketAddress
import androidx.collection.ArrayMap
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.ktx.getPackageInfo
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.toPolicy
import com.topjohnwu.magisk.ktx.now
import kotlinx.coroutines.*
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.*
import java.io.DataOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.SECONDS
class SuRequestHandler(
private val pm: PackageManager,
val pm: PackageManager,
private val policyDB: PolicyDao
) : Closeable {
) {
private lateinit var output: DataOutputStream
lateinit var policy: SuPolicy
private lateinit var output: File
private lateinit var policy: SuPolicy
lateinit var pkgInfo: PackageInfo
private set
// Return true to indicate undetermined policy, require user interaction
@ -33,8 +34,10 @@ class SuRequestHandler(
return false
// Never allow com.topjohnwu.magisk (could be malware)
if (policy.packageName == BuildConfig.APPLICATION_ID)
if (pkgInfo.packageName == BuildConfig.APPLICATION_ID) {
Shell.cmd("(pm uninstall ${BuildConfig.APPLICATION_ID} >/dev/null 2>&1)&").exec()
return false
}
when (Config.suAutoResponse) {
Config.Value.SU_AUTO_DENY -> {
@ -50,90 +53,56 @@ class SuRequestHandler(
return true
}
private suspend fun <T> Deferred<T>.timedAwait() : T? {
return withTimeoutOrNull(SECONDS.toMillis(1)) {
await()
private suspend fun init(intent: Intent): Boolean {
val uid = intent.getIntExtra("uid", -1)
val pid = intent.getIntExtra("pid", -1)
val fifo = intent.getStringExtra("fifo")
if (uid <= 0 || pid <= 0 || fifo == null) {
Timber.e("Unexpected extras: uid=[${uid}], pid=[${pid}], fifo=[${fifo}]")
return false
}
}
@Throws(IOException::class)
override fun close() {
if (::output.isInitialized)
output.close()
}
private class SuRequestError : IOException()
private suspend fun init(intent: Intent) = withContext(Dispatchers.IO) {
output = File(fifo)
policy = SuPolicy(uid)
try {
val uid: Int
if (Const.Version.atLeast_21_0()) {
val name = intent.getStringExtra("fifo") ?: throw SuRequestError()
uid = intent.getIntExtra("uid", -1).also { if (it < 0) throw SuRequestError() }
output = DataOutputStream(FileOutputStream(name).buffered())
} else {
val name = intent.getStringExtra("socket") ?: throw SuRequestError()
val socket = LocalSocket()
socket.connect(LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT))
output = DataOutputStream(BufferedOutputStream(socket.outputStream))
val input = DataInputStream(BufferedInputStream(socket.inputStream))
val map = async { input.readRequest() }.timedAwait() ?: throw SuRequestError()
uid = map["uid"]?.toIntOrNull() ?: throw SuRequestError()
}
policy = uid.toPolicy(pm)
true
} catch (e: Exception) {
when (e) {
is IOException, is PackageManager.NameNotFoundException -> {
Timber.e(e)
runCatching { close() }
false
}
else -> throw e // Unexpected error
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
// We only fill in sharedUserId and leave other fields uninitialized
sharedUserId = name.split(":")[0]
}
} catch (e: PackageManager.NameNotFoundException) {
Timber.e(e)
respond(SuPolicy.DENY, -1)
return false
}
if (!output.canWrite()) {
Timber.e("Cannot write to $output")
return false
}
return true
}
fun respond(action: Int, time: Int) {
suspend fun respond(action: Int, time: Int) {
val until = if (time > 0)
TimeUnit.MILLISECONDS.toSeconds(now) + TimeUnit.MINUTES.toSeconds(time.toLong())
TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) +
TimeUnit.MINUTES.toSeconds(time.toLong())
else
time.toLong()
policy.policy = action
policy.until = until
policy.uid = policy.uid % 100000 + Const.USER_ID * 100000
GlobalScope.launch(Dispatchers.IO) {
withContext(Dispatchers.IO) {
try {
output.writeInt(policy.policy)
output.flush()
DataOutputStream(FileOutputStream(output)).use {
it.writeInt(policy.policy)
it.flush()
}
} catch (e: IOException) {
Timber.e(e)
} finally {
runCatching { close() }
if (until >= 0)
policyDB.update(policy)
}
if (until >= 0) {
policyDB.update(policy)
}
}
}
@Throws(IOException::class)
private fun DataInputStream.readRequest(): Map<String, String> {
fun readString(): String {
val len = readInt()
val buf = ByteArray(len)
readFully(buf)
return String(buf, Charsets.UTF_8)
}
val ret = ArrayMap<String, String>()
while (true) {
val name = readString()
if (name == "eof")
break
ret[name] = readString()
}
return ret
}
}

View File

@ -0,0 +1,68 @@
package com.topjohnwu.magisk.core.su
import android.os.Bundle
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.NOPList
import kotlinx.coroutines.runBlocking
object TestHandler {
fun run(method: String): Bundle {
val r = Bundle()
fun setup(): Boolean {
val nop = NOPList.getInstance()
return runBlocking {
MagiskInstaller.Emulator(nop, nop).exec()
}
}
fun test(): Boolean {
// Make sure Zygisk works correctly
if (!Info.isZygiskEnabled) {
r.putString("reason", "zygisk not enabled")
return false
}
// Make sure the Magisk app can get root
val shell = Shell.getShell()
if (!shell.isRoot) {
r.putString("reason", "shell not root")
return false
}
// Make sure the root service is running
RootUtils.Connection.await()
// Clear existing grant for ADB shell
runBlocking {
ServiceLocator.policyDB.delete(2000)
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
Config.prefs.edit().commit()
}
return true
}
val b = runCatching {
when (method) {
"setup" -> setup()
"test" -> test()
else -> {
r.putString("reason", "unknown method")
false
}
}
}.getOrElse {
r.putString("reason", it.stackTraceToString())
false
}
r.putBoolean("result", b)
return r
}
}

View File

@ -1,18 +1,16 @@
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber
import java.io.File
import java.io.FileNotFoundException
@ -22,14 +20,13 @@ open class FlashZip(
private val mUri: Uri,
private val console: MutableList<String>,
private val logs: MutableList<String>
): KoinComponent {
) {
private val context: Context by inject()
private val installDir = File(context.cacheDir, "flash")
private val installDir = File(AppContext.cacheDir, "flash")
private lateinit var zipFile: File
@Throws(IOException::class)
private fun flash(): Boolean {
private suspend fun flash(): Boolean {
installDir.deleteRecursively()
installDir.mkdirs()
@ -50,13 +47,13 @@ open class FlashZip(
}
}
val isValid = runCatching {
val isValid = try {
zipFile.unzip(installDir, "META-INF/com/google/android", true)
val script = File(installDir, "updater-script")
script.readText().contains("#MAGISK")
}.getOrElse {
} catch (e: IOException) {
console.add("! Unzip error")
throw it
throw e
}
if (!isValid) {
@ -66,7 +63,7 @@ open class FlashZip(
console.add("- Installing ${mUri.displayName}")
return Shell.su("sh $installDir/update-binary dummy 1 \"$zipFile\"")
return Shell.cmd("sh $installDir/update-binary dummy 1 \'$zipFile\'")
.to(console, logs).exec().isSuccess
}
@ -82,7 +79,7 @@ open class FlashZip(
Timber.e(e)
false
} finally {
Shell.su("cd /", "rm -rf $installDir ${Const.TMPDIR}").submit()
Shell.cmd("cd /", "rm -rf $installDir ${Const.TMPDIR}").submit()
}
}
}

View File

@ -1,49 +1,46 @@
package com.topjohnwu.magisk.core.tasks
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.annotation.WorkerThread
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.Provider
import com.topjohnwu.magisk.core.ktx.await
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.AXML
import com.topjohnwu.magisk.core.utils.Keygen
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.inject
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.signing.JarMap
import com.topjohnwu.magisk.signing.SignApk
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.signing.JarMap
import com.topjohnwu.signing.SignApk
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.lang.ref.WeakReference
import java.io.OutputStream
import java.security.SecureRandom
import kotlin.random.asKotlinRandom
object HideAPK {
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
private const val ALPHADOTS = "$ALPHA....."
private const val APP_NAME = "Magisk"
private const val ANDROID_MANIFEST = "AndroidManifest.xml"
// Some arbitrary limit
const val MAX_LABEL_LENGTH = 32
private val svc: NetworkService by inject()
private val Context.APK_URI get() = Provider.APK_URI(packageName)
private val Context.PREFS_URI get() = Provider.PREFS_URI(packageName)
const val PLACEHOLDER = "COMPONENT_PLACEHOLDER"
private fun genPackageName(): String {
val random = SecureRandom()
@ -68,66 +65,114 @@ object HideAPK {
return builder.toString()
}
fun patch(
context: Context,
apk: File, out: File,
pkg: String, label: CharSequence
): Boolean {
try {
val jar = JarMap.open(apk, true)
val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je))
private fun classNameGenerator() = sequence {
val c1 = mutableListOf<String>()
val c2 = mutableListOf<String>()
val c3 = mutableListOf<String>()
val random = SecureRandom()
val kRandom = random.asKotlinRandom()
if (!xml.findAndPatch(APPLICATION_ID to pkg, APP_NAME to label.toString()))
return false
// Write apk changes
jar.getOutputStream(je).write(xml.bytes)
val keys = Keygen(context)
SignApk.sign(keys.cert, keys.key, jar, FileOutputStream(out))
} catch (e: Exception) {
Timber.e(e)
return false
fun <T> chain(vararg iters: Iterable<T>) = sequence {
iters.forEach { it.forEach { v -> yield(v) } }
}
return true
}
private class WaitPackageReceiver(
private val pkg: String,
activity: Activity
) : BroadcastReceiver() {
private val activity = WeakReference(activity)
private fun launchApp(): Unit = activity.get()?.run {
val intent = packageManager.getLaunchIntentForPackage(pkg) ?: return
Config.suManager = if (pkg == APPLICATION_ID) "" else pkg
grantUriPermission(pkg, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
grantUriPermission(pkg, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.putExtra(Const.Key.PREV_PKG, packageName)
startActivity(intent)
finish()
} ?: Unit
override fun onReceive(context: Context, intent: Intent) {
when (intent.action ?: return) {
Intent.ACTION_PACKAGE_REPLACED, Intent.ACTION_PACKAGE_ADDED -> {
val newPkg = intent.data?.encodedSchemeSpecificPart.orEmpty()
if (newPkg == pkg) {
context.unregisterReceiver(this)
launchApp()
}
for (a in chain('a'..'z', 'A'..'Z')) {
if (a != 'a' && a != 'A') {
c1.add("$a")
}
for (b in chain('a'..'z', 'A'..'Z', '0'..'9')) {
c2.add("$a$b")
for (c in chain('a'..'z', 'A'..'Z', '0'..'9')) {
c3.add("$a$b$c")
}
}
}
c1.shuffle(random)
c2.shuffle(random)
c3.shuffle(random)
fun notJavaKeyword(name: String) = when (name) {
"do", "if", "for", "int", "new", "try" -> false
else -> true
}
fun List<String>.process() = asSequence().filter(::notJavaKeyword)
val names = mutableListOf<String>()
names.addAll(c1)
names.addAll(c2.process().take(30))
names.addAll(c3.process().take(30))
while (true) {
val seg = 2 + random.nextInt(4)
val cls = StringBuilder()
for (i in 0 until seg) {
cls.append(names.random(kRandom))
if (i != seg - 1)
cls.append('.')
}
// Old Android does not support capitalized package names
// Check Android 7.0.0 PackageParser#buildClassName
cls[0] = cls[0].lowercaseChar()
yield(cls.toString())
}
}.distinct().iterator()
private fun patch(
context: Context,
apk: File, out: OutputStream,
pkg: String, label: CharSequence
): Boolean {
val info = context.packageManager.getPackageArchiveInfo(apk.path, 0) ?: return false
val origLabel = info.applicationInfo.nonLocalizedLabel.toString()
try {
JarMap.open(apk, true).use { jar ->
val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je))
val generator = classNameGenerator()
if (!xml.patchStrings {
for (i in it.indices) {
val s = it[i]
if (s.contains(APPLICATION_ID)) {
it[i] = s.replace(APPLICATION_ID, pkg)
} else if (s.contains(PLACEHOLDER)) {
it[i] = generator.next()
} else if (s == origLabel) {
it[i] = label.toString()
}
}
}) {
return false
}
// Write apk changes
jar.getOutputStream(je).use { it.write(xml.bytes) }
val keys = Keygen()
SignApk.sign(keys.cert, keys.key, jar, out)
return true
}
} catch (e: Exception) {
Timber.e(e)
return false
}
}
private suspend fun patchAndHide(activity: Activity, label: String): Boolean {
private fun launchApp(activity: Activity, pkg: String) {
val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return
val self = activity.packageName
val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
activity.grantUriPermission(pkg, Provider.preferencesUri(self), flag)
intent.putExtra(Const.Key.PREV_PKG, self)
activity.startActivity(intent)
activity.finish()
}
private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
val stub = File(activity.cacheDir, "stub.apk")
try {
svc.fetchFile(Info.remote.stub.link).byteStream().writeTo(stub)
activity.assets.open("stub.apk").writeTo(stub)
} catch (e: IOException) {
Timber.e(e)
return false
@ -138,31 +183,89 @@ object HideAPK {
val pkg = genPackageName()
Config.keyStoreRaw = ""
if (!patch(activity, stub, repack, pkg, label))
if (!patch(activity, stub, FileOutputStream(repack), pkg, label))
return false
// Install and auto launch app
APKInstall.registerInstallReceiver(activity, WaitPackageReceiver(pkg, activity))
if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
APKInstall.installHideResult(activity, repack)
val session = APKInstall.startSession(activity, pkg, onFailure) {
launchApp(activity, pkg)
}
Config.suManager = pkg
val cmd = "adb_pm_install $repack $pkg"
if (Shell.cmd(cmd).exec().isSuccess) return true
try {
repack.inputStream().copyAndClose(session.openStream(activity))
} catch (e: IOException) {
Timber.e(e)
return false
}
session.waitIntent()?.let { activity.startActivity(it) } ?: return false
return true
}
@Suppress("DEPRECATION")
suspend fun hide(activity: Activity, label: String) {
val result = withContext(Dispatchers.IO) {
patchAndHide(activity, label)
val dialog = android.app.ProgressDialog(activity).apply {
setTitle(activity.getString(R.string.hide_app_title))
isIndeterminate = true
setCancelable(false)
show()
}
if (!result) {
Utils.toast(R.string.failure, Toast.LENGTH_LONG)
val onFailure = Runnable {
dialog.dismiss()
activity.toast(R.string.failure, Toast.LENGTH_LONG)
}
val success = withContext(Dispatchers.IO) {
patchAndHide(activity, label, onFailure)
}
if (!success) onFailure.run()
}
fun restore(activity: Activity) {
val apk = DynAPK.current(activity)
APKInstall.registerInstallReceiver(activity, WaitPackageReceiver(APPLICATION_ID, activity))
Shell.su("adb_pm_install $apk").submit {
if (!it.isSuccess)
APKInstall.installHideResult(activity, apk)
@Suppress("DEPRECATION")
suspend fun restore(activity: Activity) {
val dialog = android.app.ProgressDialog(activity).apply {
setTitle(activity.getString(R.string.restore_img_msg))
isIndeterminate = true
setCancelable(false)
show()
}
val onFailure = Runnable {
dialog.dismiss()
activity.toast(R.string.failure, Toast.LENGTH_LONG)
}
val apk = StubApk.current(activity)
val session = APKInstall.startSession(activity, APPLICATION_ID, onFailure) {
launchApp(activity, APPLICATION_ID)
dialog.dismiss()
}
Config.suManager = ""
val cmd = "adb_pm_install $apk $APPLICATION_ID"
if (Shell.cmd(cmd).await().isSuccess) return
val success = withContext(Dispatchers.IO) {
try {
apk.inputStream().copyAndClose(session.openStream(activity))
} catch (e: IOException) {
Timber.e(e)
return@withContext false
}
session.waitIntent()?.let { activity.startActivity(it) } ?: return@withContext false
return@withContext true
}
if (!success) onFailure.run()
}
@WorkerThread
fun upgrade(context: Context, apk: File): Intent? {
val label = context.applicationInfo.nonLocalizedLabel
val pkg = context.packageName
val session = APKInstall.startSession(context)
session.openStream(context).use {
if (!patch(context, apk, it, pkg, label)) {
return null
}
}
return session.waitIntent()
}
}

View File

@ -1,32 +1,38 @@
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import android.os.Process
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import android.system.OsConstants.O_WRONLY
import android.widget.Toast
import androidx.annotation.WorkerThread
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.AppApkPath
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.reboot
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.ktx.reboot
import com.topjohnwu.magisk.ktx.symlink
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.signing.SignBoot
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.NOPList
import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
import com.topjohnwu.superuser.io.SuFileOutputStream
import com.topjohnwu.superuser.nio.ExtendedFile
import com.topjohnwu.superuser.nio.FileSystemManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.jpountz.lz4.LZ4FrameInputStream
@ -34,35 +40,39 @@ import org.kamranzafar.jtar.TarEntry
import org.kamranzafar.jtar.TarHeader
import org.kamranzafar.jtar.TarInputStream
import org.kamranzafar.jtar.TarOutputStream
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber
import java.io.*
import java.nio.ByteBuffer
import java.security.SecureRandom
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
abstract class MagiskInstallImpl protected constructor(
protected val console: MutableList<String> = NOPList.getInstance(),
private val logs: MutableList<String> = NOPList.getInstance()
) : KoinComponent {
) {
protected var installDir = File("xxx")
private lateinit var srcBoot: File
protected lateinit var installDir: ExtendedFile
private lateinit var srcBoot: ExtendedFile
private val shell = Shell.getShell()
private val service: NetworkService by inject()
protected val context: Context by inject(Protected)
private val service get() = ServiceLocator.networkService
protected val context get() = ServiceLocator.deContext
private val useRootDir = shell.isRoot && Info.noDataExec
private val rootFS get() = RootUtils.fs
private val localFS get() = FileSystemManager.getLocal()
private fun findImage(): Boolean {
val bootPath = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
val bootPath = "RECOVERYMODE=${Config.recovery} find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
if (bootPath.isEmpty()) {
console.add("! Unable to detect target image")
return false
}
srcBoot = SuFile(bootPath)
srcBoot = rootFS.getFile(bootPath)
console.add("- Target image: $bootPath")
return true
}
@ -80,43 +90,61 @@ abstract class MagiskInstallImpl protected constructor(
console.add("! Unable to detect target image")
return false
}
srcBoot = SuFile(bootPath)
srcBoot = rootFS.getFile(bootPath)
console.add("- Target image: $bootPath")
return true
}
private fun extractFiles(): Boolean {
private suspend fun extractFiles(): Boolean {
console.add("- Device platform: ${Const.CPU_ABI}")
console.add("- Installing: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
installDir = File(context.filesDir.parent, "install")
installDir = localFS.getFile(context.filesDir.parent, "install")
installDir.deleteRecursively()
installDir.mkdirs()
try {
// Extract binaries
if (isRunningAsStub) {
val zf = ZipFile(DynAPK.current(context))
zf.entries().asSequence().filter {
!it.isDirectory && it.name.startsWith("lib/${Const.CPU_ABI_32}/")
}.forEach {
val n = it.name.substring(it.name.lastIndexOf('/') + 1)
val name = n.substring(3, n.length - 3)
val dest = File(installDir, name)
zf.getInputStream(it).writeTo(dest)
ZipFile(StubApk.current(context)).use { zf ->
zf.entries().asSequence().filter {
!it.isDirectory && it.name.startsWith("/lib/${Const.CPU_ABI}/")
}.forEach {
val n = it.name.substring(it.name.lastIndexOf('/') + 1)
val name = n.substring(3, n.length - 3)
val dest = File(installDir, name)
zf.getInputStream(it).writeTo(dest)
dest.setExecutable(true)
}
val abi32 = Const.CPU_ABI_32
if (Process.is64Bit() && abi32 != null) {
val magisk32 = File(installDir, "magisk32")
zf.getInputStream(ZipEntry("lib/$abi32/libmagisk.so")).writeTo(magisk32)
magisk32.setExecutable(true)
}
}
} else {
val libs = Const.NATIVE_LIB_DIR.listFiles { _, name ->
val info = context.applicationInfo
var libs = File(info.nativeLibraryDir).listFiles { _, name ->
name.startsWith("lib") && name.endsWith(".so")
} ?: emptyArray()
for (lib in libs) {
val name = lib.name.substring(3, lib.name.length - 3)
symlink(lib.path, "$installDir/$name")
Os.symlink(lib.path, "$installDir/$name")
}
// Also symlink magisk32 on 64-bit devices that supports 32-bit
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir")
.get(info) as String?
if (lib32 != null) {
Os.symlink("$lib32/libmagisk.so", "$installDir/magisk32");
}
}
// Extract scripts
for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh")) {
for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh", "stub.apk")) {
val dest = File(installDir, script)
context.assets.open(script).writeTo(dest)
}
@ -135,7 +163,7 @@ abstract class MagiskInstallImpl protected constructor(
if (useRootDir) {
// Move everything to tmpfs to workaround Samsung bullshit
SuFile(Const.TMPDIR).also {
rootFS.getFile(Const.TMPDIR).also {
arrayOf(
"rm -rf $it",
"mkdir -p $it",
@ -149,153 +177,295 @@ abstract class MagiskInstallImpl protected constructor(
return true
}
// Optimization for SuFile I/O streams to skip an internal trial and error
private fun installDirFile(name: String): File {
return if (useRootDir)
SuFile(installDir, name)
else
File(installDir, name)
}
private fun InputStream.cleanPump(out: OutputStream) = withStreams(this, out) { src, _ ->
src.copyTo(out)
}
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) }
private fun newTarEntry(name: String, size: Long): TarEntry {
console.add("-- Writing: $name")
return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */))
}
@Throws(IOException::class)
private fun processTar(input: InputStream, output: OutputStream): OutputStream {
console.add("- Processing tar file")
val tarOut = TarOutputStream(output)
TarInputStream(input).use { tarIn ->
lateinit var entry: TarEntry
fun decompressedStream(): InputStream {
val src = if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
return object : FilterInputStream(src) {
override fun available() = 0 /* Workaround bug in LZ4FrameInputStream */
override fun close() { /* Never close src stream */ }
}
}
while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.startsWith("boot.img") ||
(Config.recovery && entry.name.contains("recovery.img"))) {
val name = entry.name.replace(".lz4", "")
console.add("-- Extracting: $name")
val extract = installDirFile(name)
decompressedStream().cleanPump(SuFileOutputStream.open(extract))
} else if (entry.name.contains("vbmeta.img")) {
val rawData = decompressedStream().readBytes()
// Valid vbmeta.img should be at least 256 bytes
if (rawData.size < 256)
continue
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
console.add("-- Patching: vbmeta.img")
ByteBuffer.wrap(rawData).putInt(120, 3)
tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
tarOut.write(rawData)
} else {
console.add("-- Copying: ${entry.name}")
tarOut.putNextEntry(entry)
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
}
}
}
val boot = installDirFile("boot.img")
val recovery = installDirFile("recovery.img")
if (Config.recovery && recovery.exists() && boot.exists()) {
// Install to recovery
srcBoot = recovery
// Repack boot image to prevent auto restore
arrayOf(
"cd $installDir",
"./magiskboot unpack boot.img",
"./magiskboot repack boot.img",
"cat new-boot.img > boot.img",
"./magiskboot cleanup",
"rm -f new-boot.img",
"cd /").sh()
SuFileInputStream.open(boot).use {
tarOut.putNextEntry(newTarEntry("boot.img", boot.length()))
it.copyTo(tarOut)
}
boot.delete()
} else {
if (!boot.exists()) {
console.add("! No boot image found")
throw IOException()
}
srcBoot = boot
}
return tarOut
private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {
// Make sure available is never called on the actual stream and always return 0
// 1. Workaround bug in LZ4FrameInputStream
// 2. Reduce max buffer size to prevent OOM
override fun available() = 0
}
private fun handleFile(uri: Uri): Boolean {
private class NoBootException : IOException()
@Throws(IOException::class)
private suspend fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile {
console.add("- Processing tar file")
lateinit var entry: TarEntry
fun decompressedStream(): InputStream {
val stream = if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
return NoAvailableStream(stream)
}
while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.startsWith("boot.img") ||
entry.name.startsWith("init_boot.img") ||
(Config.recovery && entry.name.contains("recovery.img"))) {
val name = entry.name.replace(".lz4", "")
console.add("-- Extracting: $name")
val extract = installDir.getChildFile(name)
decompressedStream().copyAndCloseOut(extract.newOutputStream())
} else if (entry.name.contains("vbmeta.img")) {
val rawData = decompressedStream().readBytes()
// Valid vbmeta.img should be at least 256 bytes
if (rawData.size < 256)
continue
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
console.add("-- Patching: vbmeta.img")
ByteBuffer.wrap(rawData).putInt(120, 3)
tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
tarOut.write(rawData)
// vbmeta partition exist, disable boot vbmeta patch
Info.patchBootVbmeta = false
} else if (entry.name.contains("userdata.img")) {
continue
} else {
console.add("-- Copying: ${entry.name}")
tarOut.putNextEntry(entry)
tarIn.copyAll(tarOut, bufferSize = 1024 * 1024)
}
}
val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
val recovery = installDir.getChildFile("recovery.img")
suspend fun ExtendedFile.copyToTar() {
newInputStream().use {
tarOut.putNextEntry(newTarEntry(name, length()))
it.copyAll(tarOut)
}
delete()
}
// Patch priority: recovery > init_boot > boot
return when {
recovery.exists() -> {
if (boot.exists()) {
// Repack boot image to prevent auto restore
arrayOf(
"cd $installDir",
"chmod -R 755 .",
"./magiskboot unpack boot.img",
"./magiskboot repack boot.img",
"cat new-boot.img > boot.img",
"./magiskboot cleanup",
"rm -f new-boot.img",
"cd /").sh()
boot.copyToTar()
}
recovery
}
initBoot.exists() -> {
if (boot.exists())
boot.copyToTar()
initBoot
}
boot.exists() -> boot
else -> throw NoBootException()
}
}
@Throws(IOException::class)
private suspend fun processZip(zipIn: ZipInputStream): ExtendedFile {
console.add("- Processing zip file")
val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
lateinit var entry: ZipEntry
while (zipIn.nextEntry?.also { entry = it } != null) {
if (entry.isDirectory) continue
when (entry.name.substringAfterLast('/')) {
"payload.bin" -> {
try {
return processPayload(zipIn)
} catch (e: IOException) {
// No boot image in payload.bin, continue to find boot images
}
}
"init_boot.img" -> {
console.add("- Extracting init_boot.img")
zipIn.copyAndCloseOut(initBoot.newOutputStream())
return initBoot
}
"boot.img" -> {
console.add("- Extracting boot.img")
zipIn.copyAndCloseOut(boot.newOutputStream())
// Don't return here since there might be an init_boot.img
}
}
}
if (boot.exists()) {
return boot
} else {
throw NoBootException()
}
}
@Throws(IOException::class)
private fun processPayload(input: InputStream): ExtendedFile {
var fifo: File? = null
try {
console.add("- Processing payload.bin")
fifo = File.createTempFile("payload-fifo-", null, installDir)
fifo.delete()
Os.mkfifo(fifo.path, 420 /* 0644 */)
// Enqueue the shell command first, or the subsequent FIFO open will block
val future = arrayOf(
"cd $installDir",
"./magiskboot extract $fifo",
"cd /"
).eq()
val fd = Os.open(fifo.path, O_WRONLY, 0)
try {
val bufSize = 1024 * 1024
val buf = ByteBuffer.allocate(bufSize)
buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()
while (buf.hasRemaining()) {
try {
Os.write(fd, buf)
} catch (e: ErrnoException) {
if (e.errno != OsConstants.EPIPE)
throw e
// If SIGPIPE, then the other side is closed, we're done
break
}
if (!buf.hasRemaining()) {
buf.limit(bufSize)
buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()
}
}
} finally {
Os.close(fd)
}
val success = try { future.get().isSuccess } catch (e: Exception) { false }
if (!success) {
console.add("! Error while extracting payload.bin")
throw IOException()
}
val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
return when {
initBoot.exists() -> {
console.add("-- Extract init_boot.img")
initBoot
}
boot.exists() -> {
console.add("-- Extract boot.img")
boot
}
else -> {
throw NoBootException()
}
}
} catch (e: ErrnoException) {
throw IOException(e)
} finally {
fifo?.delete()
}
}
private suspend fun handleFile(uri: Uri): Boolean {
val outStream: OutputStream
var outFile: MediaStoreUtils.UriFile? = null
val outFile: MediaStoreUtils.UriFile
// Process input file
try {
uri.inputStream().buffered().use { src ->
src.mark(500)
val magic = ByteArray(5)
if (src.skip(257) != 257L || src.read(magic) != magic.size) {
PushbackInputStream(uri.inputStream(), 512).use { src ->
val head = ByteArray(512)
if (src.read(head) != head.size) {
console.add("! Invalid input file")
return false
}
src.reset()
src.unread(head)
val magic = head.copyOf(4)
val tarMagic = Arrays.copyOfRange(head, 257, 262)
val alpha = "abcdefghijklmnopqrstuvwxyz"
val alphaNum = "$alpha${alpha.toUpperCase(Locale.ROOT)}0123456789"
val alphaNum = "$alpha${alpha.uppercase(Locale.ROOT)}0123456789"
val random = SecureRandom()
val filename = StringBuilder("magisk_patched_").run {
val filename = StringBuilder("magisk_patched-${BuildConfig.VERSION_CODE}_").run {
for (i in 1..5) {
append(alphaNum[random.nextInt(alphaNum.length)])
}
toString()
}
outStream = if (magic.contentEquals("ustar".toByteArray())) {
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
// tar file
outFile = MediaStoreUtils.getFile("$filename.tar", true)
processTar(src, outFile!!.uri.outputStream())
outStream = TarOutputStream(outFile.uri.outputStream())
try {
processTar(TarInputStream(src), outStream)
} catch (e: IOException) {
outStream.close()
outFile.delete()
throw e
}
} else {
// raw image
srcBoot = installDirFile("boot.img")
console.add("- Copying image to cache")
src.cleanPump(SuFileOutputStream.open(srcBoot))
outFile = MediaStoreUtils.getFile("$filename.img", true)
outFile!!.uri.outputStream()
outStream = outFile.uri.outputStream()
try {
if (magic.contentEquals("CrAU".toByteArray())) {
processPayload(src)
} else if (magic.contentEquals("PK\u0003\u0004".toByteArray())) {
processZip(ZipInputStream(src))
} else {
console.add("- Copying image to cache")
installDir.getChildFile("boot.img").also {
src.copyAndCloseOut(it.newOutputStream())
}
}
} catch (e: IOException) {
outStream.close()
outFile.delete()
throw e
}
}
}
} catch (e: IOException) {
if (e is NoBootException)
console.add("! No boot image found")
console.add("! Process error")
outFile?.delete()
Timber.e(e)
return false
}
// Patch file
if (!patchBoot()) {
outFile!!.delete()
outFile.delete()
return false
}
// Output file
try {
val newBoot = installDirFile("new-boot.img")
val newBoot = installDir.getChildFile("new-boot.img")
if (outStream is TarOutputStream) {
val name = if (srcBoot.path.contains("recovery")) "recovery.img" else "boot.img"
val name = with(srcBoot.path) {
when {
contains("recovery") -> "recovery.img"
contains("init_boot") -> "init_boot.img"
else -> "boot.img"
}
}
outStream.putNextEntry(newTarEntry(name, newBoot.length()))
}
SuFileInputStream.open(newBoot).cleanPump(outStream)
newBoot.newInputStream().copyAndClose(outStream)
newBoot.delete()
console.add("")
@ -305,40 +475,20 @@ abstract class MagiskInstallImpl protected constructor(
console.add("****************************")
} catch (e: IOException) {
console.add("! Failed to output to $outFile")
outFile!!.delete()
outFile.delete()
Timber.e(e)
return false
}
// Fix up binaries
srcBoot.delete()
if (shell.isRoot) {
"fix_env $installDir".sh()
} else {
"cp_readlink $installDir".sh()
}
"cp_readlink $installDir".sh()
return true
}
private fun patchBoot(): Boolean {
var isSigned = false
if (srcBoot.let { it !is SuFile || !it.isCharacter }) {
try {
SuFileInputStream.open(srcBoot).use {
if (SignBoot.verifySignature(it, null)) {
isSigned = true
console.add("- Boot image is signed with AVB 1.0")
}
}
} catch (e: IOException) {
console.add("! Unable to check signature")
Timber.e(e)
return false
}
}
val newBoot = installDirFile("new-boot.img")
val newBoot = installDir.getChildFile("new-boot.img")
if (!useRootDir) {
// Create output files before hand
newBoot.createNewFile()
@ -349,32 +499,15 @@ abstract class MagiskInstallImpl protected constructor(
"cd $installDir",
"KEEPFORCEENCRYPT=${Config.keepEnc} " +
"KEEPVERITY=${Config.keepVerity} " +
"PATCHVBMETAFLAG=${Info.patchBootVbmeta} " +
"RECOVERYMODE=${Config.recovery} " +
"LEGACYSAR=${Info.legacySAR} " +
"sh boot_patch.sh $srcBoot")
val isSuccess = cmds.sh().isSuccess
if (!cmds.sh().isSuccess)
return false
shell.newJob().add("./magiskboot cleanup", "cd /").exec()
val job = shell.newJob().add("./magiskboot cleanup", "cd /")
if (isSigned) {
console.add("- Signing boot image with verity keys")
val signed = File.createTempFile("signed", ".img", context.cacheDir)
try {
val src = SuFileInputStream.open(newBoot).buffered()
val out = signed.outputStream().buffered()
withStreams(src, out) { _, _ ->
SignBoot.doSignature(null, null, src, out, "/boot")
}
} catch (e: IOException) {
console.add("! Unable to sign image")
Timber.e(e)
return false
}
job.add("cat $signed > $newBoot", "rm -f $signed")
}
job.exec()
return true
return isSuccess
}
private fun flashBoot() = "direct_install $installDir $srcBoot".sh().isSuccess
@ -382,7 +515,7 @@ abstract class MagiskInstallImpl protected constructor(
private suspend fun postOTA(): Boolean {
try {
val bootctl = File.createTempFile("bootctl", null, context.cacheDir)
service.fetchBootctl().byteStream().writeTo(bootctl)
context.assets.open("bootctl").writeTo(bootctl)
"post_ota $bootctl".sh()
} catch (e: IOException) {
console.add("! Unable to download bootctl")
@ -390,32 +523,44 @@ abstract class MagiskInstallImpl protected constructor(
return false
}
console.add("***************************************")
console.add("*************************************************************")
console.add(" Next reboot will boot to second slot!")
console.add("***************************************")
console.add(" Go back to System Updates and press Restart to complete OTA")
console.add("*************************************************************")
return true
}
private fun Array<String>.eq() = shell.newJob().add(*this).to(console, logs).enqueue()
private fun String.sh() = shell.newJob().add(this).to(console, logs).exec()
private fun Array<String>.sh() = shell.newJob().add(*this).to(console, logs).exec()
private fun String.fsh() = ShellUtils.fastCmd(shell, this)
private fun Array<String>.fsh() = ShellUtils.fastCmd(shell, *this)
protected fun doPatchFile(patchFile: Uri) = extractFiles() && handleFile(patchFile)
protected suspend fun patchFile(file: Uri) = extractFiles() && handleFile(file)
protected fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
protected suspend fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
protected suspend fun secondSlot() =
findSecondary() && extractFiles() && patchBoot() && flashBoot() && postOTA()
protected fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
protected suspend fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
protected fun uninstall() = "run_uninstaller ${AssetHack.apk}".sh().isSuccess
protected fun uninstall() = "run_uninstaller $AppApkPath".sh().isSuccess
@WorkerThread
protected abstract suspend fun operations(): Boolean
open suspend fun exec() = withContext(Dispatchers.IO) { operations() }
open suspend fun exec(): Boolean {
if (haveActiveSession.getAndSet(true))
return false
val result = withContext(Dispatchers.IO) { operations() }
haveActiveSession.set(false)
return result
}
companion object {
private var haveActiveSession = AtomicBoolean(false)
}
}
abstract class MagiskInstaller(
@ -428,7 +573,7 @@ abstract class MagiskInstaller(
if (success) {
console.add("- All done!")
} else {
Shell.sh("rm -rf $installDir").submit()
Shell.cmd("rm -rf $installDir").submit()
console.add("! Installation failed")
}
return success
@ -439,7 +584,7 @@ abstract class MagiskInstaller(
console: MutableList<String>,
logs: MutableList<String>
) : MagiskInstaller(console, logs) {
override suspend fun operations() = doPatchFile(uri)
override suspend fun operations() = patchFile(uri)
}
class SecondSlot(
@ -473,7 +618,7 @@ abstract class MagiskInstaller(
val success = super.exec()
if (success) {
UiThreadHandler.handler.postDelayed(3000) {
Shell.su("pm uninstall ${context.packageName}").exec()
Shell.cmd("pm uninstall ${context.packageName}").exec()
}
}
return success
@ -486,7 +631,7 @@ abstract class MagiskInstaller(
override suspend fun exec(): Boolean {
val success = super.exec()
callback()
Utils.toast(
context.toast(
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
Toast.LENGTH_LONG
)

View File

@ -1,42 +0,0 @@
package com.topjohnwu.magisk.core.tasks
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.synchronized
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.*
class RepoUpdater(
private val svc: NetworkService,
private val repoDB: RepoDao
) {
suspend fun run(forced: Boolean) = withContext(Dispatchers.IO) {
val cachedMap = HashMap<String, Date>().also { map ->
repoDB.getModuleStubs().forEach { map[it.id] = Date(it.last_update) }
}.synchronized()
svc.fetchRepoInfo()?.let { info ->
coroutineScope {
info.modules.forEach {
launch {
val lastUpdated = cachedMap.remove(it.id)
if (forced || lastUpdated?.before(Date(it.last_update)) != false) {
try {
val repo = OnlineModule(it).apply { load() }
repoDB.addModule(repo)
} catch (e: OnlineModule.IllegalRepoException) {
Timber.e(e)
}
}
}
}
}
repoDB.removeModules(cachedMap.keys)
}
}
}

View File

@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder.LITTLE_ENDIAN
import java.nio.charset.Charset
import java.util.*
class AXML(b: ByteArray) {
@ -30,7 +29,7 @@ class AXML(b: ByteArray) {
* Followed by an array of uint32_t with size = number of strings
* Each entry points to an offset into the string data
*/
fun findAndPatch(vararg patterns: Pair<String, String>): Boolean {
fun patchStrings(patchFn: (Array<String>) -> Unit): Boolean {
val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)
fun findStringPool(): Int {
@ -43,7 +42,6 @@ class AXML(b: ByteArray) {
return -1
}
var patch = false
val start = findStringPool()
if (start < 0)
return false
@ -58,34 +56,26 @@ class AXML(b: ByteArray) {
val dataOff = start + intBuf.get()
intBuf.get()
val strings = ArrayList<String>(count)
// Read and patch all strings
loop@ for (i in 0 until count) {
val strList = ArrayList<String>(count)
// Collect all strings in the pool
for (i in 0 until count) {
val off = dataOff + intBuf.get()
val len = buffer.getShort(off)
val str = String(bytes, off + 2, len * 2, UTF_16LE)
for ((from, to) in patterns) {
if (str.contains(from)) {
strings.add(str.replace(from, to))
patch = true
continue@loop
}
}
strings.add(str)
strList.add(String(bytes, off + 2, len * 2, UTF_16LE))
}
if (!patch)
return false
val strArr = strList.toTypedArray()
patchFn(strArr)
// Write everything before string data, will patch values later
val baos = RawByteStream()
baos.write(bytes, 0, dataOff)
// Write string data
val strList = IntArray(count)
val offList = IntArray(count)
for (i in 0 until count) {
strList[i] = baos.size() - dataOff
val str = strings[i]
offList[i] = baos.size() - dataOff
val str = strArr[i]
baos.write(str.length.toShortBytes())
baos.write(str.toByteArray(UTF_16LE))
// Null terminate
@ -104,7 +94,7 @@ class AXML(b: ByteArray) {
// Patch index table
newBuffer.position(start + STRING_INDICES_OFF)
val newIntBuf = newBuffer.asIntBuffer()
strList.forEach { newIntBuf.put(it) }
offList.forEach { newIntBuf.put(it) }
// Write the rest of the chunks
val nextOff = start + size

View File

@ -1,60 +0,0 @@
package com.topjohnwu.magisk.core.utils
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import org.koin.core.KoinComponent
import org.koin.core.get
object BiometricHelper: KoinComponent {
private val mgr by lazy { BiometricManager.from(get()) }
val isSupported get() = when (mgr.canAuthenticate()) {
BiometricManager.BIOMETRIC_SUCCESS -> true
else -> false
}
val isEnabled: Boolean get() {
val enabled = Config.suBiometric
if (enabled && !isSupported) {
Config.suBiometric = false
return false
}
return enabled
}
fun authenticate(
activity: FragmentActivity,
onError: () -> Unit = {},
onSuccess: () -> Unit): BiometricPrompt {
val prompt = BiometricPrompt(activity,
ContextCompat.getMainExecutor(activity),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
onError()
}
override fun onAuthenticationFailed() {
onError()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
onSuccess()
}
}
)
val info = BiometricPrompt.PromptInfo.Builder()
.setConfirmationRequired(true)
.setDeviceCredentialAllowed(false)
.setTitle(activity.getString(R.string.authenticate))
.setNegativeButtonText(activity.getString(android.R.string.cancel))
.build()
prompt.authenticate(info)
return prompt
}
}

View File

@ -0,0 +1,40 @@
package com.topjohnwu.magisk.core.utils
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.util.concurrent.AbstractExecutorService
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class DispatcherExecutor(dispatcher: CoroutineDispatcher) : AbstractExecutorService() {
private val job = SupervisorJob()
private val scope = CoroutineScope(job + dispatcher)
private val latch = CountDownLatch(1)
init {
job.invokeOnCompletion { latch.countDown() }
}
override fun execute(command: Runnable) {
scope.launch {
command.run()
}
}
override fun shutdown() = job.cancel()
override fun shutdownNow(): List<Runnable> {
job.cancel()
return emptyList()
}
override fun isShutdown() = job.isCancelled
override fun isTerminated() = job.isCancelled && job.isCompleted
override fun awaitTermination(timeout: Long, unit: TimeUnit) = latch.await(timeout, unit)
}

View File

@ -1,36 +0,0 @@
package com.topjohnwu.magisk.core.utils
import kotlinx.coroutines.*
import java.util.concurrent.*
class IODispatcherExecutor : AbstractExecutorService() {
private val job = SupervisorJob().apply { invokeOnCompletion { future.run() } }
private val scope = CoroutineScope(job + Dispatchers.IO)
private val future = FutureTask(Callable { true })
override fun execute(command: Runnable) {
scope.launch {
command.run()
}
}
override fun shutdown() = job.cancel()
override fun shutdownNow(): List<Runnable> {
job.cancel()
return emptyList()
}
override fun isShutdown() = job.isCancelled
override fun isTerminated() = job.isCancelled && job.isCompleted
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
return try {
future.get(timeout, unit)
} catch (e: TimeoutException) {
false
}
}
}

View File

@ -1,26 +1,22 @@
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.content.pm.PackageManager
import android.util.Base64
import android.util.Base64OutputStream
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.signing.CryptoUtils.readCertificate
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
import com.topjohnwu.signing.KeyData
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.cert.X509Certificate
import java.util.*
import java.util.Calendar
import java.util.Locale
import java.util.Random
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
@ -29,63 +25,21 @@ private interface CertKeyProvider {
val key: PrivateKey
}
@Suppress("DEPRECATION")
class Keygen(context: Context) : CertKeyProvider {
class Keygen : CertKeyProvider {
companion object {
private const val ALIAS = "magisk"
private val PASSWORD get() = "magisk".toCharArray()
private const val TESTKEY_CERT = "61ed377e85d386a8dfee6b864bd85b0bfaa5af81"
private const val DNAME = "C=US,ST=California,L=Mountain View,O=Google Inc.,OU=Android,CN=Android"
private const val BASE64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP
}
private val start = Calendar.getInstance().apply { add(Calendar.MONTH, -3) }
private val end = start.apply { add(Calendar.YEAR, 30) }
private val end = (start.clone() as Calendar).apply { add(Calendar.YEAR, 30) }
override val cert get() = provider.cert
override val key get() = provider.key
private val provider: CertKeyProvider
inner class KeyStoreProvider :
CertKeyProvider {
private val ks by lazy { init() }
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
override val key by lazy { ks.getKey(
ALIAS,
PASSWORD
) as PrivateKey }
}
class TestProvider : CertKeyProvider {
override val cert by lazy {
readCertificate(ByteArrayInputStream(KeyData.testCert()))
}
override val key by lazy {
readPrivateKey(ByteArrayInputStream(KeyData.testKey()))
}
}
init {
val pm = context.packageManager
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
val sig = info.signatures[0]
val digest = MessageDigest.getInstance("SHA1")
val chksum = digest.digest(sig.toByteArray())
val sb = StringBuilder()
for (b in chksum) {
sb.append("%02x".format(0xFF and b.toInt()))
}
provider = if (sb.toString() == TESTKEY_CERT) {
// The app was signed by the test key, continue to use it (legacy mode)
TestProvider()
} else {
KeyStoreProvider()
}
}
private val ks = init()
override val cert = ks.getCertificate(ALIAS) as X509Certificate
override val key = ks.getKey(ALIAS, PASSWORD) as PrivateKey
private fun init(): KeyStore {
val raw = Config.keyStoreRaw
@ -93,12 +47,8 @@ class Keygen(context: Context) : CertKeyProvider {
if (raw.isEmpty()) {
ks.load(null)
} else {
GZIPInputStream(Base64.decode(raw,
BASE64_FLAG
).inputStream()).use {
ks.load(it,
PASSWORD
)
GZIPInputStream(Base64.decode(raw, BASE64_FLAG).inputStream()).use {
ks.load(it, PASSWORD)
}
}
@ -109,22 +59,19 @@ class Keygen(context: Context) : CertKeyProvider {
// Generate new private key and certificate
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(4096) }.genKeyPair()
val dname = X500Name(DNAME)
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
start.time, end.time, dname, kp.public)
val builder = X509v3CertificateBuilder(
dname, BigInteger(160, Random()),
start.time, end.time, Locale.ROOT, dname,
SubjectPublicKeyInfo.getInstance(kp.public.encoded)
)
val signer = JcaContentSignerBuilder("SHA1WithRSA").build(kp.private)
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
// Store them into keystore
ks.setKeyEntry(
ALIAS, kp.private,
PASSWORD, arrayOf(cert))
ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert))
val bytes = ByteArrayOutputStream()
GZIPOutputStream(Base64OutputStream(bytes,
BASE64_FLAG
)).use {
ks.store(it,
PASSWORD
)
GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use {
ks.store(it, PASSWORD)
}
Config.keyStoreRaw = bytes.toString("UTF-8")

View File

@ -6,14 +6,13 @@ import android.annotation.SuppressLint
import android.content.res.Configuration
import android.content.res.Resources
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.AssetHack
import com.topjohnwu.magisk.core.ActivityTracker
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.ktx.langTagToLocale
import com.topjohnwu.magisk.ktx.toLangTag
import com.topjohnwu.magisk.core.createNewResources
import com.topjohnwu.magisk.core.di.AppContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import kotlin.collections.ArrayList
import java.util.Locale
var currentLocale: Locale = Locale.getDefault()
@ -27,7 +26,12 @@ withContext(Dispatchers.Default) {
val compareId = R.string.app_changelog
// Create a completely new resource to prevent cross talk over active configs
val res = AssetHack.newResource()
val res = createNewResources()
fun changeLocale(locale: Locale) {
res.configuration.setLocale(locale)
res.updateConfiguration(res.configuration, res.displayMetrics)
}
val locales = ArrayList<String>().apply {
// Add default locale
@ -40,15 +44,15 @@ withContext(Dispatchers.Default) {
// Then add all supported locales
addAll(Resources.getSystem().assets.locales)
}.map {
it.langTagToLocale()
Locale.forLanguageTag(it)
}.distinctBy {
res.updateLocale(it)
changeLocale(it)
res.getString(compareId)
}.sortedWith { a, b ->
a.getDisplayName(a).compareTo(b.getDisplayName(b), true)
}
res.updateLocale(defaultLocale)
changeLocale(defaultLocale)
val defName = res.getString(R.string.system_default)
val names = ArrayList<String>(locales.size + 1)
@ -59,28 +63,26 @@ withContext(Dispatchers.Default) {
locales.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(locale.toLangTag())
values.add(locale.toLanguageTag())
}
(names.toTypedArray() to values.toTypedArray()).also { cachedLocales = it }
}
fun Resources.updateConfig(config: Configuration = configuration) {
fun Resources.setConfig(config: Configuration) {
config.setLocale(currentLocale)
updateConfiguration(config, displayMetrics)
}
fun Resources.updateLocale(locale: Locale) {
configuration.setLocale(locale)
updateConfiguration(configuration, displayMetrics)
}
fun Resources.syncLocale() = setConfig(configuration)
fun refreshLocale() {
val localeConfig = Config.locale
currentLocale = when {
localeConfig.isEmpty() -> defaultLocale
else -> localeConfig.langTagToLocale()
else -> Locale.forLanguageTag(localeConfig)
}
Locale.setDefault(currentLocale)
AssetHack.resource.updateConfig()
AppContext.resources.syncLocale()
ActivityTracker.foreground?.recreate()
}

View File

@ -1,9 +1,7 @@
package com.topjohnwu.magisk.core.utils
import android.content.ContentResolver
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Environment
@ -13,18 +11,15 @@ import androidx.annotation.RequiresApi
import androidx.core.net.toFile
import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.core.di.AppContext
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.OutputStream
import java.security.MessageDigest
import kotlin.experimental.and
@Suppress("DEPRECATION")
object MediaStoreUtils {
private val cr: ContentResolver by lazy { get<Context>().contentResolver }
private val cr get() = AppContext.contentResolver
@get:RequiresApi(api = 29)
private val tableUri
@ -89,7 +84,7 @@ object MediaStoreUtils {
@Throws(IOException::class)
fun getFile(displayName: String, skipQuery: Boolean = false): UriFile {
if (Build.VERSION.SDK_INT < 30) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
// Fallback to file based I/O pre Android 11
val parent = File(Environment.getExternalStorageDirectory(), relativePath)
parent.mkdirs()
@ -104,6 +99,8 @@ object MediaStoreUtils {
fun Uri.outputStream() = cr.openOutputStream(this, "rwt") ?: throw FileNotFoundException()
fun Uri.fileDescriptor(mode: String) = cr.openFileDescriptor(this, mode) ?: throw FileNotFoundException()
val Uri.displayName: String get() {
if (scheme == "file") {
// Simple uri wrapper over file, directly get file name
@ -120,24 +117,6 @@ object MediaStoreUtils {
return this.toString()
}
fun Uri.checkSum(alg: String, reference: String) = runCatching {
this.inputStream().use {
val digest = MessageDigest.getInstance(alg)
it.copyTo(object : OutputStream() {
override fun write(b: Int) {
digest.update(b.toByte())
}
override fun write(b: ByteArray, off: Int, len: Int) {
digest.update(b, off, len)
}
})
val sb = StringBuilder()
digest.digest().forEach { b -> sb.append("%02x".format(b and 0xff.toByte())) }
sb.toString() == reference
}
}.getOrElse { false }
interface UriFile {
val uri: Uri
fun delete(): Boolean

View File

@ -0,0 +1,80 @@
package com.topjohnwu.magisk.core.utils
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.PowerManager
import androidx.collection.ArraySet
import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver
class NetworkObserver(context: Context): DefaultLifecycleObserver {
private val manager = context.getSystemService<ConnectivityManager>()!!
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
private val activeList = ArraySet<Network>()
override fun onAvailable(network: Network) {
activeList.add(network)
postValue(true)
}
override fun onLost(network: Network) {
activeList.remove(network)
postValue(!activeList.isEmpty())
}
}
private val receiver = object : BroadcastReceiver() {
private fun Context.isIdleMode(): Boolean {
val pwm = getSystemService<PowerManager>() ?: return true
val isIgnoringOptimizations = pwm.isIgnoringBatteryOptimizations(packageName)
return pwm.isDeviceIdleMode && !isIgnoringOptimizations
}
override fun onReceive(context: Context, intent: Intent) {
if (context.isIdleMode()) {
postValue(false)
} else {
postCurrentState()
}
}
}
init {
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
manager.registerNetworkCallback(request, networkCallback)
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
context.applicationContext.registerRuntimeReceiver(receiver, filter)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
override fun onStart(owner: LifecycleOwner) {
postCurrentState()
}
private fun postCurrentState() {
postValue(manager.getNetworkCapabilities(manager.activeNetwork)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ?: false)
}
private fun postValue(b: Boolean) {
Info.remote = Info.EMPTY_REMOTE
Info.isConnected.postValue(b)
}
companion object {
fun init(context: Context): NetworkObserver {
return NetworkObserver(context).apply { postCurrentState() }
}
}
}

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