1
mirror of https://github.com/topjohnwu/Magisk synced 2025-11-02 14:12:29 +01:00

Compare commits

...

128 Commits
v21.3 ... v22.0

Author SHA1 Message Date
topjohnwu
483dbcdc40 Release v22.0 2021-02-23 04:09:26 -08:00
topjohnwu
a1096b5bf0 Do not run pm install on main thread 2021-02-23 04:09:13 -08:00
Chris Renshaw
5ac0e64edb Update guides.md for system_ext 2021-02-23 03:27:36 -08:00
Lishoo
60b2624607 Update polish translations
Add missing strings
2021-02-23 03:26:47 -08:00
topjohnwu
d2e2847b03 Fix stub 2021-02-23 03:24:51 -08:00
topjohnwu
b9669f54f7 Update docs 2021-02-23 03:06:00 -08:00
topjohnwu
8c7bd77d33 Do not wrap twice 2021-02-23 01:49:15 -08:00
Shaka Huang
ba1ce16b8b Fix error in pure 64-bit environment
In Android S preview, there’s no 32-bit libraries in x86_64 system image for emulator.

Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2021-02-22 03:28:54 -08:00
topjohnwu
68090943f4 Several changes
- Change error message strings
- Move non-root stub error to SplashActivity
- Skip shell init in non-root stub
2021-02-22 03:28:19 -08:00
vvb2060
a4fb1297b0 Fix crash in pure 64-bit devices 2021-02-22 03:08:51 -08:00
vvb2060
860a05abf2 Simplify UpdateChannel 2021-02-22 03:08:51 -08:00
vvb2060
8bb2f356c0 Allow offline restore app 2021-02-22 03:08:51 -08:00
vvb2060
4950020635 Prevent dot in the first position again 2021-02-22 03:08:51 -08:00
vvb2060
0a6140c6eb Try install with root first 2021-02-22 03:08:51 -08:00
vvb2060
bba2ac8817 Add unsupport env check 2021-02-22 03:08:51 -08:00
topjohnwu
331b1f542f Use standard Android APIs for install and launch 2021-02-20 20:12:35 -08:00
topjohnwu
ccb55205e6 Fix pre 21 support 2021-02-20 03:38:39 -08:00
topjohnwu
9cc91b30b3 Fix #3871 2021-02-20 02:49:43 -08:00
grmasa
e836caf31e Update Greek translation 2021-02-20 01:51:39 -08:00
Lishoo
beaa1e5be2 Add missing strings and small updates. 2021-02-20 01:51:02 -08:00
Lishoo
ea545bae26 Update polish translations 2021-02-20 01:50:44 -08:00
topjohnwu
1c9ec2df45 Publish new canary build 2021-02-14 13:43:24 -08:00
vvb2060
b76c80e2ce Fix apex path 2021-02-14 13:37:38 -08:00
topjohnwu
236990f4a3 Fix stub app crashing 2021-02-14 13:37:13 -08:00
topjohnwu
1ed32df20d Publish new canary build 2021-02-13 17:26:53 -08:00
topjohnwu
8476eb9f4b Avoid patching vendor_boot.img 2021-02-13 17:15:04 -08:00
JoanVC100
735af7843b Add new ca-strings 2021-02-13 17:09:46 -08:00
MC Naveen
ded73e958b Added Tamil Translation 2021-02-13 17:09:28 -08:00
Ooggle
6dcb84d4f4 French translation of newest commit 2021-02-13 17:08:53 -08:00
topjohnwu
501bc9f438 Restore init from backup rather than symlink
Because of course Samsung don't follow AOSP norms.
I mean, why would they?
2021-02-13 16:43:06 -08:00
topjohnwu
f88e812b63 Move behavior to XML 2021-02-13 15:26:32 -08:00
Tornike Khintibidze
be6386c410 Updated Georgian translation 2021-02-12 03:59:35 -08:00
Didgeridoohan
2af4fd17c4 Minor fixes and changes to Swedish transaltions 2021-02-12 03:59:07 -08:00
Mikael Bjurström
f870418bd0 Update Swedish translation 2021-02-12 00:07:40 -08:00
vvb2060
00659e4795 Hide OTA option on virtual A/B devices 2021-02-12 00:07:15 -08:00
Jose Manuel Estrada-Nora Muñoz
cdda10207e Spanish strings 2021-02-11 23:32:24 -08:00
Ilya Kushnir
701700279f Update RU strings 2021-02-11 23:32:04 -08:00
alex26052005
a9d804724a Update strings.xml
Updated German language
2021-02-11 23:31:04 -08:00
DanGLES3
e033a9ab47 Update Portugues Brazilian translation 2021-02-11 23:30:35 -08:00
kubalav
059e5fb8aa Update Slovak translation 2021-02-11 23:28:49 -08:00
vvb2060
a78f255928 Update zh-rCN translation 2021-02-11 23:25:24 -08:00
AndroPlus
1d10e69288 Update Japanese translation 2021-02-11 23:23:36 -08:00
topjohnwu
63590d379c Update hide icon strategy 2021-02-11 22:38:41 -08:00
topjohnwu
5f63e88984 Hide icons when things don't fit 2021-02-11 05:08:40 -08:00
topjohnwu
75584e2b19 App string resources overhaul 2021-02-11 02:34:27 -08:00
vvb2060
1426ee2ebd Fix Android build version sdk in script 2021-02-10 22:22:50 -08:00
topjohnwu
b6643b7bfc Publish new canary build 2021-02-07 21:39:00 -08:00
Hen Ry
721dfdf553 Added translation of new strings 2021-02-07 17:42:33 -08:00
topjohnwu
2963747d14 Fix LZ4_LG format decompression
Fix #3802, fix #3722, fix #3770, fix #3635, fix #3787, close #3812
2021-02-07 17:40:59 -08:00
topjohnwu
e7350d5041 Fix unable to patch images when app is hidden 2021-02-07 06:42:06 -08:00
topjohnwu
f37e8f4ca8 Fix boot image patching 2021-02-07 01:54:08 -08:00
topjohnwu
594c2accc0 Update dependencies 2021-02-05 04:41:01 -08:00
topjohnwu
7acfac6a91 Publish new canary build 2021-01-30 12:54:51 -08:00
Laz M
0646f48e14 [README] Warn users that the official website is github
Google puts a number of cheeky looking websites in the results for Magisk.

I only found out they were unofficial is though issue #3435. This deserves to be shown more prominently.
2021-01-30 11:59:39 -08:00
tzagim
37565fd067 Fix TYPOs 2021-01-30 11:58:43 -08:00
vvb2060
26b2e7dc5d Care version code changes 2021-01-30 11:58:10 -08:00
vvb2060
c3313623e4 Fix release build 2021-01-30 11:58:10 -08:00
topjohnwu
2089223690 Fix #3785 2021-01-30 11:51:15 -08:00
topjohnwu
52e1b84d41 Symlink pre API 21 2021-01-30 01:12:49 -08:00
topjohnwu
8794141b7f Support super old emulators 2021-01-30 00:56:16 -08:00
topjohnwu
f6126dd20e Support Shortcuts pre API 26
Close #3778
2021-01-29 23:16:09 -08:00
topjohnwu
18acfda99b Remove Windows NDK symlink
https://github.com/actions/virtual-environments/pull/2343
2021-01-29 05:43:41 -08:00
topjohnwu
bec5edca84 Avoiding using shell I/O 2021-01-29 05:15:22 -08:00
topjohnwu
6fb20b3ee5 Proper proguard rules 2021-01-27 04:56:39 -08:00
topjohnwu
eaf4d8064b Also download to external storage 2021-01-27 04:09:07 -08:00
topjohnwu
2a5f5b1bba Workaround zip extraction bug on older devices 2021-01-27 03:00:09 -08:00
topjohnwu
c538a77937 Tweak build configs and scripts 2021-01-27 02:36:32 -08:00
sominn
aa9e7b1ed1 Update strings.xml
CS string update
2021-01-27 01:00:10 -08:00
Arbri çoçka
a3066eddab Fix string in values-sq 2021-01-27 00:59:49 -08:00
Arbri çoçka
d1729fa787 Fix string in values-sq 2021-01-27 00:59:49 -08:00
vvb2060
93961dde2c Fix version on continuous build 2021-01-27 00:54:11 -08:00
topjohnwu
1024e68eb6 Remove class mapping in full APK 2021-01-26 07:27:35 -08:00
topjohnwu
6ae2c9387d Use stub APK hiding method for Android 5.0+
At the same time, disable app hiding on devices lower than 5.0
to simplify the logic in the app. By doing so, a hidden app always
implies running as stub.
2021-01-26 07:27:35 -08:00
topjohnwu
fba83e2330 Support stub APK loading down to Android 5.0 2021-01-26 07:27:35 -08:00
topjohnwu
f1295cb7d6 Fix root on Android 7.0 and lower 2021-01-26 02:16:11 -08:00
topjohnwu
dc61dfbde6 Cache update check results 2021-01-25 04:13:08 -08:00
topjohnwu
21466426da Some code cleanup 2021-01-25 03:44:38 -08:00
topjohnwu
3f0136362b Move nand flash handling into boot_patch.sh 2021-01-25 03:37:41 -08:00
topjohnwu
e92d77bbec Some optimizations 2021-01-25 03:02:43 -08:00
topjohnwu
07bd36c94b Fix patching files
Fix #3765
2021-01-25 02:24:12 -08:00
topjohnwu
b1dbbdef12 Remove unneeded busybox redirection 2021-01-25 00:23:42 -08:00
topjohnwu
3e479726ec Fix legacy rootfs devices 2021-01-25 00:19:10 -08:00
vvb2060
4cc41eccb3 Skip download notes when loading notes url 2021-01-24 21:02:51 -08:00
vvb2060
8f08ae59ac Fix permission 2021-01-24 21:02:43 -08:00
vvb2060
e8d4e492d6 Fix CHANGELOG_URL 2021-01-24 21:02:37 -08:00
topjohnwu
b8090a8e18 Ensure cwd is writable in module scripts
Close #3763
2021-01-24 20:58:30 -08:00
topjohnwu
c609a01e55 Proper shortcut name 2021-01-24 08:00:17 -08:00
Wagg13
c97fb385cd New update values-pt-rBR
update brazilian strings.xml
2021-01-24 07:36:07 -08:00
LLZN
da6c57750e correction czech translat
change and fix some strings after trying a new version of the application (v8.0.6)
2021-01-24 07:35:49 -08:00
topjohnwu
6951d926f7 Rename app name to just Magisk 2021-01-24 07:35:00 -08:00
topjohnwu
6623195bd5 Cleanup scripts 2021-01-24 07:24:13 -08:00
topjohnwu
ec31bb9a82 Rename scripts 2021-01-24 07:18:14 -08:00
vvb2060
8618cc383a Fix install modules
Fix #3759
2021-01-24 07:03:19 -08:00
vvb2060
4b01e3a3c7 Cleanup more kotlin stuffs 2021-01-24 07:03:06 -08:00
vvb2060
7f748c23c1 Use Java debugger 2021-01-24 07:02:44 -08:00
vvb2060
963d248cc7 Rename apk to be uninstaller 2021-01-24 07:02:36 -08:00
topjohnwu
657056e636 Cache changelog files 2021-01-24 06:55:43 -08:00
topjohnwu
9d5efea66e Remove ManagerJson
Everything is now Magisk
2021-01-24 05:14:46 -08:00
topjohnwu
658d74e026 Update home fragment 2021-01-24 00:02:49 -08:00
vvb2060
5113f6d375 Fix stop magiskhide 2021-01-23 18:13:15 -08:00
vvb2060
96405c26d0 writeTo has closed InputStream 2021-01-23 18:12:19 -08:00
vvb2060
4ea5f34bf3 Remove unused action 2021-01-23 18:11:08 -08:00
vvb2060
dbd13a2019 Clean code 2021-01-23 18:10:26 -08:00
vvb2060
06773235da Fix Windows build 2021-01-23 18:06:01 -08:00
vvb2060
e57556a8af Use ro.kernel.qemu to check emulator 2021-01-23 18:05:38 -08:00
vvb2060
b54b78c29d Fix prevent dot in the first position 2021-01-23 17:31:18 -08:00
vvb2060
317336f771 Add isolated processes log 2021-01-23 17:31:11 -08:00
topjohnwu
b4e52f6135 Better development workflow 2021-01-23 16:50:55 -08:00
topjohnwu
f2ca042915 Fix script for handling .apex files 2021-01-23 16:09:30 -08:00
topjohnwu
1060dd2906 Random refactoring 2021-01-23 13:26:28 -08:00
topjohnwu
2e0f7a82fa More complete stub sources 2021-01-22 20:45:37 -08:00
topjohnwu
5798536559 Remove unnecessary hacks 2021-01-22 20:25:37 -08:00
topjohnwu
ab9a83c82f Bump target SDK to 30 2021-01-22 05:03:33 -08:00
topjohnwu
c87fdbea0f Fix erroneous stream close 2021-01-22 03:07:39 -08:00
topjohnwu
ec8fffe61c Merge Magisk install zip into Magisk Manager
Distribute Magisk directly with Magisk Manager APK. The APK will
contain all required binaries and scripts for installation and
uninstallation. App versions will now align with Magisk releases.

Extra effort is spent to make the APK itself also a flashable zip that
can be used in custom recoveries, so those still prefer to install
Magisk with recoveries will not be affected with this change.

As a bonus, this makes the whole installation and uninstallation
process 100% offline. The existing Magisk Manager was not really
functional without an Internet connection, as the installation process
was highly tied to zips hosted on the server.

An additional bonus: since all binaries are now shipped as "native
libraries" of the APK, we can finally bump the target SDK version
higher than 28. The target SDK version was stuck at 28 for a long time
because newer SELinux restricts running executables from internal
storage. More details can be found here: https://github.com/termux/termux-app/issues/1072
The target SDK bump will be addressed in a future commit.

Co-authored with @vvb2060
2021-01-22 02:29:54 -08:00
topjohnwu
61d52991f1 Update BusyBox 2021-01-21 00:35:22 -08:00
topjohnwu
9100186dce Make emulator direct install env fix 2021-01-18 13:32:10 -08:00
topjohnwu
d2bc2cfcf8 Install both 32 and 64 bit binaries 2021-01-18 12:37:08 -08:00
topjohnwu
5a71998b4e Stop embedding magisk in magiskinit 2021-01-18 04:25:26 -08:00
topjohnwu
42278f12ff Fix typo in init daemon 2021-01-18 04:13:54 -08:00
topjohnwu
f5593e051c Update README 2021-01-17 06:19:56 -08:00
topjohnwu
a27e30cf54 Update release notes 2021-01-17 06:08:15 -08:00
topjohnwu
79140c7636 Proper xxread and xwrite implementation 2021-01-17 01:42:45 -08:00
topjohnwu
1f4c595cd3 Revert to old su -c behavior 2021-01-16 23:59:31 -08:00
topjohnwu
b5b62e03af Fix copySepolicyRules logic 2021-01-16 21:45:45 -08:00
topjohnwu
67e2a4720e Fix xxread false negatives
Fix #3710
2021-01-16 21:43:53 -08:00
topjohnwu
f5c2d72429 Also log pid and tid 2021-01-16 16:10:47 -08:00
topjohnwu
2f5331ab48 Update README 2021-01-16 05:02:39 -08:00
184 changed files with 3276 additions and 3480 deletions

View File

@@ -43,12 +43,8 @@ jobs:
- name: Set up GitHub env (Windows)
if: runner.os == 'Windows'
run: |
$oldAndroidPath = $env:ANDROID_SDK_ROOT
$sdk_root = "C:\Android"
New-Item -Path $sdk_root -ItemType SymbolicLink -Value $oldAndroidPath
$ndk_ver = Select-String -Path "gradle.properties" -Pattern "^magisk.fullNdkVersion=" | % { $_ -replace ".*=" }
echo "ANDROID_SDK_ROOT=$sdk_root" >> $env:GITHUB_ENV
echo "ANDROID_HOME=$sdk_root" >> $env:GITHUB_ENV
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

View File

@@ -15,11 +15,13 @@ Here are some feature highlights:
## Downloads
[![](https://img.shields.io/badge/Magisk%20Manager-v8.0.5-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.5/MagiskManager-v8.0.5.apk)
Please note that the only source of official Magisk information and downloads is [Github](https://github.com/topjohnwu/Magisk/). Beware of any other websites.
[![](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.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.2)
[![](https://img.shields.io/badge/Magisk%20Beta-v21.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.2)
[![](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)
## Useful Links
@@ -60,7 +62,7 @@ For Magisk Manager crashes, record and upload the logcat when the crash occurs.
- 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 in Android Studio. Both app (Kotlin/Java) and native (C++/C) source code can be properly developed using the IDE, but *always* use `build.py` for building.
- 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).

5
app/.gitignore vendored
View File

@@ -6,6 +6,7 @@
app/release
*.hprof
.externalNativeBuild/
public.certificate.x509.pem
private.key.pk8
*.apk
src/main/assets
src/main/jniLibs
src/main/resources

View File

@@ -1,3 +1,4 @@
import org.apache.tools.ant.filters.FixCrLfFilter
import java.io.PrintStream
plugins {
@@ -22,8 +23,9 @@ android {
applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
versionName = Config.appVersion
versionCode = Config.appVersionCode
versionName = Config.version
versionCode = Config.versionCode
ndk.abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
javaCompileOptions.annotationProcessorOptions.arguments(
mapOf("room.incremental" to "true")
@@ -51,13 +53,14 @@ android {
}
packagingOptions {
exclude("/META-INF/**")
exclude("/META-INF/*")
exclude("/org/bouncycastle/**")
exclude("/kotlin/**")
exclude("/kotlinx/**")
exclude("/okhttp3/**")
exclude("/*.txt")
exclude("/*.bin")
doNotStrip("**/*.so")
}
kotlinOptions {
@@ -65,10 +68,83 @@ android {
}
}
tasks["preBuild"]?.dependsOn(tasks.register("copyUtils", Copy::class) {
from(rootProject.file("scripts/util_functions.sh"))
into("src/main/res/raw")
})
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
}
}
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")
@@ -125,13 +201,13 @@ dependencies {
implementation("${bindingAdapter}:${vBAdapt}")
implementation("${bindingAdapter}-recyclerview:${vBAdapt}")
val vMarkwon = "4.6.0"
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.0.2"
val vLibsu = "3.1.1"
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
implementation("com.github.topjohnwu.libsu:io:${vLibsu}")
@@ -158,7 +234,7 @@ dependencies {
implementation("com.squareup.moshi:moshi:${vMoshi}")
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
val vRoom = "2.3.0-alpha04"
val vRoom = "2.3.0-beta01"
implementation("androidx.room:room-runtime:${vRoom}")
implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}")
@@ -167,17 +243,16 @@ dependencies {
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
implementation("androidx.biometric:biometric:1.0.1")
implementation("androidx.biometric:biometric:1.1.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.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.4.0")
implementation("androidx.transition:transition:1.3.1")
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("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
implementation("com.google.android.material:material:1.2.1")
implementation("com.google.android.material:material:1.3.0")
}

View File

@@ -18,16 +18,10 @@
# Kotlin
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void checkExpressionValueIsNotNull(...);
public static void checkNotNullExpressionValue(...);
public static void checkReturnedValueIsNotNull(...);
public static void checkFieldIsNotNull(...);
public static void checkParameterIsNotNull(...);
public static void check*(...);
public static void throw*(...);
}
# Stubs
-keep class a.* { *; }
# Snet
-keepclassmembers class com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback
@@ -35,14 +29,17 @@
void onResponse(org.json.JSONObject);
}
# Stub
-keep class com.topjohnwu.magisk.core.App { <init>(java.lang.Object); }
# Strip Timber verbose and debug logging
-assumenosideeffects class timber.log.Timber.Tree {
-assumenosideeffects class timber.log.Timber$Tree {
public void v(**);
public void d(**);
}
# Excessive obfuscation
-repackageclasses
-repackageclasses 'a'
-allowaccessmodification
# QOL

View File

@@ -1,17 +1,27 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.shared">
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.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
android:label="Magisk Manager"
android:installLocation="internalOnly"
android:usesCleartextTraffic="true"
android:allowBackup="false"
android:label="Magisk"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:ignore="UnusedAttribute">
</application>
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute" />
</manifest>

View File

@@ -1,7 +1,10 @@
package com.topjohnwu.magisk.utils;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
@@ -10,20 +13,36 @@ import com.topjohnwu.magisk.FileProvider;
import java.io.File;
public 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));
}
return intent;
}
public static void install(Context c, File apk) {
c.startActivity(installIntent(c, apk));
}
public static Intent installIntent(Context c, File apk) {
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
install.setData(FileProvider.getUriForFile(c, c.getPackageName() + ".provider", apk));
} else {
apk.setReadable(true, false);
install.setData(Uri.fromFile(apk));
}
return install;
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 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
}
}

View File

@@ -3,21 +3,18 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<application
android:name=".core.App"
android:extractNativeLibs="true"
android:icon="@drawable/ic_launcher"
android:name="a.e"
android:allowBackup="false"
android:multiArch="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
<!-- Splash -->
<activity
android:name="a.c"
android:name=".core.SplashActivity"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -30,27 +27,27 @@
</activity>
<!-- Main -->
<activity android:name="a.b" />
<activity android:name=".ui.MainActivity" />
<!-- Superuser -->
<activity
android:name="a.m"
android:name=".ui.surequest.SuRequestActivity"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="false"
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- Receiver -->
<receiver
android:name="a.h"
android:directBootAware="true">
android:name=".core.Receiver"
android:directBootAware="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
<intent-filter>
@@ -62,16 +59,15 @@
</receiver>
<!-- DownloadService -->
<service android:name="a.j" />
<service android:name=".core.download.DownloadService" />
<!-- FileProvider -->
<provider
android:name="a.p"
android:name=".core.Provider"
android:authorities="${applicationId}.provider"
android:directBootAware="true"
android:exported="false"
android:grantUriPermissions="true">
</provider>
android:grantUriPermissions="true" />
<!-- Hardcode GMS version -->
<meta-data
@@ -82,16 +78,12 @@
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
tools:node="remove"
tools:ignore="ExportedContentProvider" />
<!-- We don't invalidate Room -->
<service
android:name="androidx.room.MultiInstanceInvalidationService"
tools:node="remove"/>
<!-- We don't use Device Credentials -->
<activity
android:name="androidx.biometric.DeviceCredentialHandlerActivity"
tools:node="remove" />
</application>

View File

@@ -1,32 +0,0 @@
@file:JvmName("a")
package a
import com.topjohnwu.magisk.core.App
import com.topjohnwu.magisk.core.Provider
import com.topjohnwu.magisk.core.Receiver
import com.topjohnwu.magisk.core.SplashActivity
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.signing.SignBoot
fun main(args: Array<String>) {
SignBoot.main(args)
}
class b : MainActivity()
class c : SplashActivity()
class e : App {
constructor() : super()
constructor(o: Any) : super(o)
}
class h : Receiver()
class j : DownloadService()
class m : SuRequestActivity()
class p : Provider()

View File

@@ -10,8 +10,9 @@ import androidx.multidex.MultiDex
import androidx.work.WorkManager
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.RootInit
import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.ktx.unwrap
@@ -19,6 +20,7 @@ import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import timber.log.Timber
import java.io.File
import kotlin.system.exitProcess
open class App() : Application() {
@@ -31,7 +33,7 @@ open class App() : Application() {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
Shell.setDefaultBuilder(Shell.Builder.create()
.setFlags(Shell.FLAG_MOUNT_MASTER)
.setInitializers(RootInit::class.java)
.setInitializers(BusyBoxInit::class.java, AppShellInit::class.java)
.setTimeout(2))
Shell.EXECUTOR = IODispatcherExecutor()
@@ -61,12 +63,18 @@ open class App() : Application() {
val wrapped = impl.wrap()
super.attachBaseContext(wrapped)
val info = base.applicationInfo
val libDir = runCatching {
info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
}.getOrNull() ?: info.nativeLibraryDir
Const.NATIVE_LIB_DIR = File(libDir)
// Normal startup
startKoin {
androidContext(wrapped)
modules(koinModules)
}
ResMgr.init(impl)
AssetHack.init(impl)
app.registerActivityLifecycleCallbacks(ForegroundTracker)
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
}

View File

@@ -1,13 +1,31 @@
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
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
}
}
// Paths
lateinit var MAGISKTMP: String
lateinit var NATIVE_LIB_DIR: File
val MAGISK_PATH get() = "$MAGISKTMP/modules"
const val TMP_FOLDER_PATH = "/dev/tmp"
const val TMPDIR = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log"
// Versions
@@ -19,23 +37,16 @@ object Const {
val USER_ID = Process.myUid() / 100000
object Version {
const val MIN_VERSION = "v19.0"
const val MIN_VERCODE = 19000
const val MIN_VERSION = "v20.4"
const val MIN_VERCODE = 20400
fun atLeast_20_2() = Info.env.magiskVersionCode >= 20200 || isCanary()
fun atLeast_20_4() = Info.env.magiskVersionCode >= 20400 || isCanary()
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
}
object ID {
const val FETCH_ZIP = 2
const val SELECT_FILE = 3
const val MAX_ACTIVITY_RESULT = 10
// notifications
const val MAGISK_UPDATE_NOTIFICATION_ID = 4
const val APK_UPDATE_NOTIFICATION_ID = 5
const val UPDATE_NOTIFICATION_CHANNEL = "update"
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
@@ -46,6 +57,9 @@ object Const {
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
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/"

View File

@@ -14,44 +14,38 @@ import android.content.Intent
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.download.DownloadService
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.ktx.forceGetDeclaredField
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
fun AssetManager.addAssetPath(path: String) {
DynAPK.addAssetPath(this, path)
}
fun Context.wrap(global: Boolean = true): Context =
if (global) GlobalResContext(this) else ResContext(this)
fun Context.wrap(inject: Boolean = false): Context =
if (inject) ReInjectedContext(this) else InjectedContext(this)
fun Context.wrapJob(): Context = object : GlobalResContext(this) {
fun Context.wrapJob(): Context = object : InjectedContext(this) {
override fun getApplicationContext(): Context {
return this
}
override fun getApplicationContext() = this
@SuppressLint("NewApi")
override fun getSystemService(name: String): Any? {
return if (!isRunningAsStub) super.getSystemService(name) else
when (name) {
Context.JOB_SCHEDULER_SERVICE ->
JobSchedulerWrapper(super.getSystemService(name) as JobScheduler)
else -> super.getSystemService(name)
return super.getSystemService(name).let {
when {
!isRunningAsStub -> it
name == JOB_SCHEDULER_SERVICE -> JobSchedulerWrapper(it as JobScheduler)
else -> it
}
}
}
}
fun Class<*>.cmp(pkg: String): ComponentName {
val name = ClassMap[this].name
return ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)
}
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))
@@ -59,34 +53,27 @@ inline fun <reified T> Activity.redirect() = Intent(intent)
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
private open class GlobalResContext(base: Context) : ContextWrapper(base) {
open val mRes: Resources get() = ResMgr.resource
override fun getResources(): Resources {
return mRes
}
override fun getClassLoader(): ClassLoader {
return javaClass.classLoader!!
}
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 ResContext(super.createConfigurationContext(config))
return super.createConfigurationContext(config).wrap(true)
}
}
private class ResContext(base: Context) : GlobalResContext(base) {
override val mRes by lazy { base.resources.patch() }
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(ResMgr.apk)
assets.addAssetPath(AssetHack.apk)
return this
}
}
object ResMgr {
object AssetHack {
lateinit var resource: Resources
lateinit var apk: String
@@ -101,62 +88,39 @@ object ResMgr {
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): Int {
return base.schedule(job.patch())
}
override fun enqueue(job: JobInfo, work: JobWorkItem): Int {
return base.enqueue(job.patch(), work)
}
override fun cancel(jobId: Int) {
base.cancel(jobId)
}
override fun cancelAll() {
base.cancelAll()
}
override fun getAllPendingJobs(): List<JobInfo> {
return base.allPendingJobs
}
override fun getPendingJob(jobId: Int): JobInfo? {
return base.getPendingJob(jobId)
}
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 {
// We need to swap out the service of JobInfo
val name = service.className
val component = ComponentName(
service.packageName,
Info.stubChk.classToComponent[name] ?: name
)
// 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)
javaClass.forceGetDeclaredField("service")?.set(this, component)
return this
}
}
private object ClassMap {
private val map = mapOf(
App::class.java to a.e::class.java,
MainActivity::class.java to a.b::class.java,
SplashActivity::class.java to a.c::class.java,
Receiver::class.java to a.h::class.java,
DownloadService::class.java to a.j::class.java,
SuRequestActivity::class.java to a.m::class.java
)
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
}
// Keep a reference to these resources to prevent it from
// being removed when running "remove unused resources"
val shouldKeepResources = listOf(

View File

@@ -1,41 +1,45 @@
package com.topjohnwu.magisk.core
import android.os.Build
import androidx.databinding.ObservableBoolean
import com.topjohnwu.magisk.DynAPK
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.utils.CachedValue
import com.topjohnwu.magisk.ktx.getProperty
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils.fastCmd
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.FileInputStream
import java.io.File
import java.io.IOException
import java.util.*
val isRunningAsStub get() = Info.stub != null
object Info {
val envRef = CachedValue { loadState() }
@JvmStatic val env by envRef
var stub: DynAPK.Data? = null
val stubChk: DynAPK.Data
get() = stub as DynAPK.Data
var remote = UpdateInfo()
val EMPTY_REMOTE = UpdateInfo()
var remote = EMPTY_REMOTE
suspend fun getRemote(svc: NetworkService): UpdateInfo? {
return if (remote === EMPTY_REMOTE) {
svc.fetchUpdate()?.apply { remote = this }
} else remote
}
// Device state
var crypto = ""
@JvmStatic var isSAR = false
@JvmStatic var isAB = false
@JvmStatic val env by lazy { loadState() }
@JvmField var isSAR = false
@JvmField var isAB = false
@JvmField val isVirtualAB = getProperty("ro.virtual_ab.enabled", "false") == "true"
@JvmStatic val isFDE get() = crypto == "block"
@JvmStatic var ramdisk = false
@JvmStatic var hasGMS = true
@JvmStatic var isPixel = false
@JvmStatic val cryptoText get() = crypto.capitalize(Locale.US)
@JvmField var ramdisk = false
@JvmField var hasGMS = true
@JvmField val isPixel = Build.BRAND == "google"
@JvmField val isEmulator = getProperty("ro.kernel.qemu", "0") == "1"
var crypto = ""
var noDataExec = false
val isConnected by lazy {
ObservableBoolean(false).also { field ->
@@ -47,14 +51,12 @@ object Info {
val isNewReboot by lazy {
try {
FileInputStream("/proc/sys/kernel/random/boot_id").bufferedReader().use {
val id = it.readLine()
if (id != Config.bootId) {
Config.bootId = id
true
} else {
false
}
val id = File("/proc/sys/kernel/random/boot_id").readText()
if (id != Config.bootId) {
Config.bootId = id
true
} else {
false
}
} catch (e: IOException) {
false
@@ -73,8 +75,8 @@ object Info {
hide: Boolean = false
) {
val magiskHide get() = Config.magiskHide
val magiskVersionCode = when (code) {
in Int.MIN_VALUE..Const.Version.MIN_VERCODE -> -1
val magiskVersionCode = when {
code < Const.Version.MIN_VERCODE -> -1
else -> if (Shell.rootAccess()) code else -1
}
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE

View File

@@ -1,32 +1,30 @@
package com.topjohnwu.magisk.core
import android.app.Activity
import android.content.Context
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.CountDownLatch
open class SplashActivity : Activity() {
open class SplashActivity : BaseActivity() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap())
}
private val latch = CountDownLatch(1)
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.SplashTheme)
super.onCreate(savedInstanceState)
GlobalScope.launch(Dispatchers.IO) {
initAndStart()
}
// Pre-initialize root shell
Shell.getShell(null) { initAndStart() }
}
private fun handleRepackage(pkg: String?) {
@@ -40,13 +38,26 @@ open class SplashActivity : Activity() {
if (Config.suManager.isNotEmpty())
Config.suManager = ""
pkg ?: return
Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec()
if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess)
uninstallApp(pkg)
}
}
private fun initAndStart() {
// Pre-initialize root shell
Shell.getShell()
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)
@@ -60,11 +71,21 @@ open class SplashActivity : Activity() {
get<NetworkService>()
DONE = true
redirect<MainActivity>().also { startActivity(it) }
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,13 +1,11 @@
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 com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.concurrent.TimeUnit
@@ -18,20 +16,15 @@ class UpdateCheckService(context: Context, workerParams: WorkerParameters)
private val svc: NetworkService by inject()
override suspend fun doWork(): Result {
// Make sure shell initializer was ran
withContext(Dispatchers.IO) {
Shell.getShell()
}
return svc.fetchUpdate()?.let {
if (BuildConfig.VERSION_CODE < it.app.versionCode)
return svc.fetchUpdate()?.run {
if (Info.env.isActive && BuildConfig.VERSION_CODE < magisk.versionCode)
Notifications.managerUpdate(applicationContext)
else if (Info.env.isActive && Info.env.magiskVersionCode < it.magisk.versionCode)
Notifications.magiskUpdate(applicationContext)
Result.success()
} ?: Result.failure()
}
companion object {
@SuppressLint("NewApi")
fun schedule(context: Context) {
if (Config.checkUpdate) {
val constraints = Constraints.Builder()

View File

@@ -14,7 +14,6 @@ import androidx.collection.SparseArrayCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.set
@@ -26,6 +25,13 @@ typealias ActivityResultCallback = BaseActivity.(Int, Intent?) -> Unit
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
}
override fun applyOverrideConfiguration(config: Configuration?) {
// Force applying our preferred local
@@ -34,7 +40,7 @@ abstract class BaseActivity : AppCompatActivity() {
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap(false))
super.attachBaseContext(base.wrap(true))
}
fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) {
@@ -49,10 +55,7 @@ abstract class BaseActivity : AppCompatActivity() {
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
request.onSuccess()
} else {
var requestCode: Int
do {
requestCode = Random.nextInt(Const.ID.MAX_ACTIVITY_RESULT + 1, 1 shl 15)
} while (resultCallbacks.containsKey(requestCode))
val requestCode = newRequestCode
resultCallbacks[requestCode] = { result, _ ->
if (result > 0)
request.onSuccess()
@@ -92,7 +95,8 @@ abstract class BaseActivity : AppCompatActivity() {
}
}
fun startActivityForResult(intent: Intent, requestCode: Int, callback: ActivityResultCallback) {
fun startActivityForResult(intent: Intent, callback: ActivityResultCallback) {
val requestCode = newRequestCode
resultCallbacks[requestCode] = callback
try {
startActivityForResult(intent, requestCode)

View File

@@ -1,31 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.net.Uri
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
sealed class Action : Parcelable {
sealed class Flash : Action() {
@Parcelize
object Primary : Flash()
@Parcelize
object Secondary : Flash()
}
@Parcelize
object Download : Action()
@Parcelize
object Uninstall : Action()
@Parcelize
object EnvFix : Action()
@Parcelize
data class Patch(val fileUri: Uri) : Action()
}

View File

@@ -8,11 +8,8 @@ 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.MediaStoreUtils.checkSum
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -69,18 +66,11 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
// -- Download logic
private suspend fun Subject.startDownload() {
val skip = this is Subject.Magisk && file.checkSum("MD5", magisk.md5)
if (!skip) {
val stream = service.fetchFile(url).toProgressStream(this)
when (this) {
is Subject.Module -> // Download and process on-the-fly
stream.toModule(file, service.fetchInstaller().byteStream())
else -> {
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
if (this is Subject.Manager)
handleAPK(this)
}
}
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)

View File

@@ -7,11 +7,10 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.download.Action.*
import com.topjohnwu.magisk.core.download.Action.Flash.Secondary
import com.topjohnwu.magisk.core.download.Subject.*
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.core.tasks.EnvFixTask
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall
import kotlin.random.Random.Default.nextInt
@@ -22,25 +21,12 @@ open class DownloadService : BaseDownloader() {
private val context get() = this
override suspend fun onFinish(subject: Subject, id: Int) = when (subject) {
is Magisk -> subject.onFinish(id)
is Module -> subject.onFinish(id)
is Manager -> subject.onFinish(id)
}
private suspend fun Magisk.onFinish(id: Int) = when (val action = action) {
Uninstall -> FlashFragment.uninstall(file, id)
EnvFix -> {
remove(id)
EnvFixTask(file).exec()
Unit
}
is Patch -> FlashFragment.patch(file, action.fileUri, id)
is Flash -> FlashFragment.flash(file, action is Secondary, id)
else -> Unit
}
private fun Module.onFinish(id: Int) = when (action) {
is Flash -> FlashFragment.install(file, id)
Flash -> FlashFragment.install(file, id)
else -> Unit
}
@@ -53,22 +39,13 @@ open class DownloadService : BaseDownloader() {
override fun Notification.Builder.setIntent(subject: Subject)
= when (subject) {
is Magisk -> setIntent(subject)
is Module -> setIntent(subject)
is Manager -> setIntent(subject)
}
private fun Notification.Builder.setIntent(subject: Magisk)
= when (val action = subject.action) {
Uninstall -> setContentIntent(FlashFragment.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashFragment.flashIntent(context, subject.file, action is Secondary))
is Patch -> setContentIntent(FlashFragment.patchIntent(context, subject.file, action.fileUri))
else -> setContentIntent(Intent())
}
private fun Notification.Builder.setIntent(subject: Module)
= when (subject.action) {
is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
else -> setContentIntent(Intent())
}

View File

@@ -2,19 +2,22 @@ package com.topjohnwu.magisk.core.download
import android.content.Context
import androidx.core.net.toFile
import com.topjohnwu.magisk.BuildConfig
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.path, patched.path, packageName, applicationInfo.nonLocalizedLabel)
HideAPK.patch(this, apk, patched, packageName, applicationInfo.nonLocalizedLabel)
apk.delete()
patched.renameTo(apk)
}
@@ -22,30 +25,51 @@ private fun Context.patch(apk: File) {
private fun BaseDownloader.notifyHide(id: Int) {
update(id) {
it.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_manager_title))
.setContentTitle(getString(R.string.hide_app_title))
.setContentText("")
}
}
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager) {
val apk = subject.file.toFile()
val id = subject.notifyID()
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) {
// Move to upgrade location
apk.copyTo(DynAPK.update(this), overwrite = true)
apk.delete()
if (Info.stubChk.version < subject.stub.versionCode) {
notifyHide(id)
val apk = subject.file.toFile()
val id = subject.notifyID()
write(DynAPK.update(this).outputStream())
if (Info.stub!!.version < subject.stub.versionCode) {
// Also upgrade stub
service.fetchFile(subject.stub.link).byteStream().use { it.writeTo(apk) }
notifyHide(id)
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
patch(apk)
} else {
// Simply relaunch the app
stopSelf()
relaunchApp(this)
}
} else if (packageName != BuildConfig.APPLICATION_ID) {
notifyHide(id)
patch(apk)
} else {
write(subject.file.outputStream())
}
}

View File

@@ -1,8 +1,9 @@
package com.topjohnwu.magisk.core.download
import android.net.Uri
import com.topjohnwu.magisk.ktx.withStreams
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
@@ -25,8 +26,7 @@ fun InputStream.toModule(file: Uri, installer: InputStream) {
zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
var off = -1
var entry: ZipEntry? = zin.nextEntry
while (entry != null) {
zin.forEach { entry ->
if (off < 0) {
off = entry.name.indexOf('/') + 1
}
@@ -38,8 +38,6 @@ fun InputStream.toModule(file: Uri, installer: InputStream) {
zin.copyTo(zout)
}
}
entry = zin.nextEntry
}
}
}

View File

@@ -6,7 +6,6 @@ import android.os.Parcelable
import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.ManagerJson
import com.topjohnwu.magisk.core.model.StubJson
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@@ -40,70 +39,26 @@ sealed class Subject : Parcelable {
@Parcelize
class Manager(
private val app: ManagerJson = Info.remote.app,
private val json: MagiskJson = Info.remote.magisk,
val stub: StubJson = Info.remote.stub
) : Subject() {
override val action get() = Action.Download
override val title: String get() = "MagiskManager-${app.version}(${app.versionCode})"
override val url: String get() = app.link
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")
}
val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri
}
abstract class Magisk : Subject() {
val magisk: MagiskJson = Info.remote.magisk
@Parcelize
private class Internal(
override val action: Action
) : Magisk() {
override val url: String get() = magisk.link
override val title: String get() = "Magisk-${magisk.version}(${magisk.versionCode})"
@IgnoredOnParcel
override val file by lazy {
cachedFile("magisk.zip")
}
}
@Parcelize
private class Uninstall : Magisk() {
override val action get() = Action.Uninstall
override val url: String get() = Info.remote.uninstaller.link
override val title: String get() = "uninstall.zip"
@IgnoredOnParcel
override val file by lazy {
cachedFile(title)
}
}
@Parcelize
private class Download : Magisk() {
override val action get() = Action.Download
override val url: String get() = magisk.link
override val title: String get() = "Magisk-${magisk.version}(${magisk.versionCode}).zip"
@IgnoredOnParcel
override val file by lazy {
MediaStoreUtils.getFile(title).uri
}
}
companion object {
operator fun invoke(config: Action) = when (config) {
Action.Download -> Download()
Action.Uninstall -> Uninstall()
Action.EnvFix, is Action.Flash, is Action.Patch -> Internal(config)
}
}
}
}
sealed class Action : Parcelable {
@Parcelize
object Flash : Action()
@Parcelize
object Download : Action()
}

View File

@@ -6,17 +6,11 @@ import kotlinx.parcelize.Parcelize
@JsonClass(generateAdapter = true)
data class UpdateInfo(
val app: ManagerJson = ManagerJson(),
val uninstaller: UninstallerJson = UninstallerJson(),
val magisk: MagiskJson = MagiskJson(),
val stub: StubJson = StubJson()
)
@JsonClass(generateAdapter = true)
data class UninstallerJson(
val link: String = ""
)
@Parcelize
@JsonClass(generateAdapter = true)
data class MagiskJson(
val version: String = "",
@@ -24,15 +18,6 @@ data class MagiskJson(
val link: String = "",
val note: String = "",
val md5: String = ""
)
@Parcelize
@JsonClass(generateAdapter = true)
data class ManagerJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = ""
) : Parcelable
@Parcelize

View File

@@ -1,11 +1,13 @@
@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,
@@ -38,7 +40,7 @@ fun SuPolicy.toMap() = mapOf(
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.GET_UNINSTALLED_PACKAGES)
val info = pm.getApplicationInfo(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES)
if (info.uid != uid)
throw PackageManager.NameNotFoundException()
@@ -59,7 +61,7 @@ fun Map<String, String>.toPolicy(pm: PackageManager): SuPolicy {
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): SuPolicy {
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
?: throw PackageManager.NameNotFoundException()
val info = pm.getApplicationInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES)
val info = pm.getApplicationInfo(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES)
return SuPolicy(
uid = info.uid,
packageName = pkg,

View File

@@ -2,14 +2,13 @@ package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import androidx.core.os.postDelayed
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.unzip
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 com.topjohnwu.superuser.internal.UiThreadHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
@@ -25,61 +24,50 @@ open class FlashZip(
private val logs: MutableList<String>
): KoinComponent {
val context: Context by inject()
private val installFolder = File(context.cacheDir, "flash").apply {
if (!exists()) mkdirs()
}
private val tmpFile: File = File(installFolder, "install.zip")
@Throws(IOException::class)
private fun unzipAndCheck(): Boolean {
val parentFile = tmpFile.parentFile ?: return false
tmpFile.unzip(parentFile, "META-INF/com/google/android", true)
val updaterScript = File(parentFile, "updater-script")
return Shell
.su("grep -q '#MAGISK' $updaterScript")
.exec()
.isSuccess
}
private val context: Context by inject()
private val installDir = File(context.cacheDir, "flash")
private lateinit var zipFile: File
@Throws(IOException::class)
private fun flash(): Boolean {
console.add("- Copying zip to temp directory")
installDir.deleteRecursively()
installDir.mkdirs()
runCatching {
mUri.inputStream().writeTo(tmpFile)
}.getOrElse {
when (it) {
is FileNotFoundException -> console.add("! Invalid Uri")
is IOException -> console.add("! Cannot copy to cache")
zipFile = if (mUri.scheme == "file") {
mUri.toFile()
} else {
File(installDir, "install.zip").also {
console.add("- Copying zip to temp directory")
try {
mUri.inputStream().writeTo(it)
} catch (e: IOException) {
when (e) {
is FileNotFoundException -> console.add("! Invalid Uri")
else -> console.add("! Cannot copy to cache")
}
throw e
}
}
throw it
}
val isMagiskModule = runCatching {
unzipAndCheck()
val isValid = runCatching {
zipFile.unzip(installDir, "META-INF/com/google/android", true)
val script = File(installDir, "updater-script")
script.readText().contains("#MAGISK")
}.getOrElse {
console.add("! Unzip error")
throw it
}
if (!isMagiskModule) {
console.add("! This zip is not a Magisk Module!")
if (!isValid) {
console.add("! This zip is not a Magisk module!")
return false
}
console.add("- Installing ${mUri.displayName}")
val parentFile = tmpFile.parent ?: return false
return Shell
.su(
"cd $parentFile",
"BOOTMODE=true sh update-binary dummy 1 $tmpFile"
)
.to(console, logs)
.exec().isSuccess
return Shell.su("sh $installDir/update-binary dummy 1 \"$zipFile\"")
.to(console, logs).exec().isSuccess
}
open suspend fun exec() = withContext(Dispatchers.IO) {
@@ -94,25 +82,7 @@ open class FlashZip(
Timber.e(e)
false
} finally {
Shell.su("cd /", "rm -rf ${tmpFile.parent} ${Const.TMP_FOLDER_PATH}").submit()
Shell.su("cd /", "rm -rf $installDir ${Const.TMPDIR}").submit()
}
}
class Uninstall(
uri: Uri,
console: MutableList<String>,
log: MutableList<String>
) : FlashZip(uri, console, log) {
override suspend fun exec(): Boolean {
val success = super.exec()
if (success) {
UiThreadHandler.handler.postDelayed(3000) {
Shell.su("pm uninstall " + context.packageName).exec()
}
}
return success
}
}
}

View File

@@ -1,38 +1,41 @@
package com.topjohnwu.magisk.core.tasks
import android.app.ProgressDialog
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION.SDK_INT
import android.widget.Toast
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.*
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.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.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.GlobalScope
import kotlinx.coroutines.launch
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.security.SecureRandom
object HideAPK {
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
private const val ALPHADOTS = "$ALPHA....."
private const val APP_NAME = "Magisk Manager"
private const val APP_NAME = "Magisk"
private const val ANDROID_MANIFEST = "AndroidManifest.xml"
// Some arbitrary limit
@@ -49,7 +52,7 @@ object HideAPK {
var next: Char
var prev = 0.toChar()
for (i in 0 until len) {
next = if (prev == '.' || prev == 0.toChar() || i == len - 1) {
next = if (prev == '.' || i == 0 || i == len - 1) {
ALPHA[random.nextInt(ALPHA.length)]
} else {
ALPHADOTS[random.nextInt(ALPHADOTS.length)]
@@ -59,19 +62,19 @@ object HideAPK {
}
if (!builder.contains('.')) {
// Pick a random index and set it as dot
val idx = random.nextInt(len - 1)
builder[idx] = '.'
val idx = random.nextInt(len - 2)
builder[idx + 1] = '.'
}
return builder.toString()
}
fun patch(
context: Context,
apk: String, out: String,
apk: File, out: File,
pkg: String, label: CharSequence
): Boolean {
try {
val jar = JarMap.open(apk)
val jar = JarMap.open(apk, true)
val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je))
@@ -90,103 +93,76 @@ object HideAPK {
return true
}
private suspend fun patchAndHide(context: Context, label: String): Boolean {
val dlStub = !isRunningAsStub && SDK_INT >= 28 && Const.Version.atLeast_20_2()
val src = if (dlStub) {
val stub = File(context.cacheDir, "stub.apk")
try {
svc.fetchFile(Info.remote.stub.link).byteStream().use {
it.writeTo(stub)
}
} catch (e: IOException) {
Timber.e(e)
return false
}
stub.path
} else {
context.packageCodePath
}
private class WaitPackageReceiver(
private val pkg: String,
activity: Activity
) : BroadcastReceiver() {
// Generate a new random package name and signature
val repack = File(context.cacheDir, "patched.apk")
val pkg = genPackageName()
Config.keyStoreRaw = ""
private val activity = WeakReference(activity)
if (!patch(context, src, repack.path, pkg, label))
return false
// Install the application
if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
return false
context.apply {
val intent = packageManager.getLaunchIntentForPackage(pkg) ?: return false
Config.suManager = pkg
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
return true
}
@Suppress("DEPRECATION")
fun hide(context: Context, label: String) {
val dialog = ProgressDialog.show(context, context.getString(R.string.hide_manager_title), "", true)
GlobalScope.launch {
val result = withContext(Dispatchers.IO) {
patchAndHide(context, label)
}
if (!result) {
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
dialog.dismiss()
}
}
}
private suspend fun downloadAndRestore(context: Context): Boolean {
val apk = if (isRunningAsStub) {
DynAPK.current(context)
} else {
File(context.cacheDir, "manager.apk").also { apk ->
try {
svc.fetchFile(Info.remote.app.link).byteStream().use {
it.writeTo(apk)
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()
}
} catch (e: IOException) {
Timber.e(e)
return false
}
}
}
if (!Shell.su("adb_pm_install $apk").exec().isSuccess)
return false
}
context.apply {
val intent = packageManager.getLaunchIntentForPackage(APPLICATION_ID) ?: return false
Config.suManager = ""
grantUriPermission(APPLICATION_ID, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
grantUriPermission(APPLICATION_ID, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.putExtra(Const.Key.PREV_PKG, packageName)
startActivity(intent)
private suspend fun patchAndHide(activity: Activity, label: String): Boolean {
val stub = File(activity.cacheDir, "stub.apk")
try {
svc.fetchFile(Info.remote.stub.link).byteStream().writeTo(stub)
} catch (e: IOException) {
Timber.e(e)
return false
}
// Generate a new random package name and signature
val repack = File(activity.cacheDir, "patched.apk")
val pkg = genPackageName()
Config.keyStoreRaw = ""
if (!patch(activity, stub, 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)
return true
}
@Suppress("DEPRECATION")
fun restore(context: Context) {
val dialog = ProgressDialog.show(context, context.getString(R.string.restore_img_msg), "", true)
GlobalScope.launch {
val result = withContext(Dispatchers.IO) {
downloadAndRestore(context)
}
if (!result) {
Utils.toast(R.string.restore_manager_fail_toast, Toast.LENGTH_LONG)
dialog.dismiss()
}
suspend fun hide(activity: Activity, label: String) {
val result = withContext(Dispatchers.IO) {
patchAndHide(activity, label)
}
if (!result) {
Utils.toast(R.string.failure, Toast.LENGTH_LONG)
}
}
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)
}
}
}

View File

@@ -3,20 +3,16 @@
package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint
import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import android.util.DisplayMetrics
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.AssetHack
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.ResMgr
import com.topjohnwu.magisk.core.addAssetPath
import com.topjohnwu.magisk.ktx.langTagToLocale
import com.topjohnwu.magisk.ktx.toLangTag
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList
var currentLocale: Locale = Locale.getDefault()
@@ -30,11 +26,8 @@ suspend fun availableLocales() = cachedLocales ?:
withContext(Dispatchers.Default) {
val compareId = R.string.app_changelog
// Create a completely new resource to prevent cross talk over app's configs
val asset = AssetManager::class.java.newInstance().apply { addAssetPath(ResMgr.apk) }
val config = Configuration(ResMgr.resource.configuration)
val metrics = DisplayMetrics().apply { setTo(ResMgr.resource.displayMetrics) }
val res = Resources(asset, metrics, config)
// Create a completely new resource to prevent cross talk over active configs
val res = AssetHack.newResource()
val locales = ArrayList<String>().apply {
// Add default locale
@@ -45,19 +38,17 @@ withContext(Dispatchers.Default) {
add("pt-BR")
// Then add all supported locales
addAll(res.assets.locales)
addAll(Resources.getSystem().assets.locales)
}.map {
it.langTagToLocale()
}.distinctBy {
config.setLocale(it)
res.updateConfiguration(config, metrics)
res.updateLocale(it)
res.getString(compareId)
}.sortedWith(Comparator { a, b ->
}.sortedWith { a, b ->
a.getDisplayName(a).compareTo(b.getDisplayName(b), true)
})
}
config.setLocale(defaultLocale)
res.updateConfiguration(config, metrics)
res.updateLocale(defaultLocale)
val defName = res.getString(R.string.system_default)
val names = ArrayList<String>(locales.size + 1)
@@ -79,6 +70,11 @@ fun Resources.updateConfig(config: Configuration = configuration) {
updateConfiguration(config, displayMetrics)
}
fun Resources.updateLocale(locale: Locale) {
configuration.setLocale(locale)
updateConfiguration(configuration, displayMetrics)
}
fun refreshLocale() {
val localeConfig = Config.locale
currentLocale = when {
@@ -86,5 +82,5 @@ fun refreshLocale() {
else -> localeConfig.langTagToLocale()
}
Locale.setDefault(currentLocale)
ResMgr.resource.updateConfig()
AssetHack.resource.updateConfig()
}

View File

@@ -1,59 +0,0 @@
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.os.Build
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.rawResource
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
class RootInit : Shell.Initializer() {
override fun onInit(context: Context, shell: Shell): Boolean {
return init(context.wrap(), shell)
}
fun init(context: Context, shell: Shell): Boolean {
shell.newJob().apply {
add("export SDK_INT=${Build.VERSION.SDK_INT}")
if (Const.Version.atLeast_20_4()) {
add("export MAGISKTMP=\$(magisk --path)/.magisk")
} else {
add("export MAGISKTMP=/sbin/.magisk")
}
if (Const.Version.atLeast_21_0()) {
add("export ASH_STANDALONE=1")
add("[ -x /data/adb/magisk/busybox ] && exec /data/adb/magisk/busybox sh")
} else {
add("export PATH=\"\$MAGISKTMP/busybox:\$PATH\"")
}
add(context.rawResource(R.raw.manager))
if (shell.isRoot) {
add(context.rawResource(R.raw.util_functions))
}
add("mm_init")
}.exec()
fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd)
fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean()
Const.MAGISKTMP = getVar("MAGISKTMP")
Info.isSAR = getBool("SYSTEM_ROOT")
Info.ramdisk = getBool("RAMDISKEXIST")
Info.isAB = getBool("ISAB")
Info.crypto = getVar("CRYPTOTYPE")
Info.isPixel = fastCmd("getprop ro.product.brand") == "google"
// Default presets
Config.recovery = getBool("RECOVERYMODE")
Config.keepVerity = getBool("KEEPVERITY")
Config.keepEnc = getBool("KEEPFORCEENCRYPT")
return true
}
}

View File

@@ -0,0 +1,102 @@
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.os.Build
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ktx.deviceProtectedContext
import com.topjohnwu.magisk.ktx.rawResource
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import java.io.File
import java.util.jar.JarFile
abstract class BaseShellInit : Shell.Initializer() {
final override fun onInit(context: Context, shell: Shell): Boolean {
return init(context.wrap(), shell)
}
abstract fun init(context: Context, shell: Shell): Boolean
}
class BusyBoxInit : BaseShellInit() {
override fun init(context: Context, shell: Shell): Boolean {
shell.newJob().apply {
add("export ASH_STANDALONE=1")
val localBB: File
if (isRunningAsStub) {
if (!shell.isRoot)
return true
val jar = JarFile(DynAPK.current(context))
val bb = jar.getJarEntry("lib/${Const.CPU_ABI_32}/libbusybox.so")
localBB = context.deviceProtectedContext.cachedFile("busybox")
localBB.delete()
jar.getInputStream(bb).writeTo(localBB)
localBB.setExecutable(true)
} else {
localBB = File(Const.NATIVE_LIB_DIR, "libbusybox.so")
}
if (shell.isRoot) {
add("export MAGISKTMP=\$(magisk --path)/.magisk")
// Test if we can properly execute stuff in /data
Info.noDataExec = !shell.newJob().add("$localBB true").exec().isSuccess
}
if (Info.noDataExec) {
// Copy it out of /data to workaround Samsung bullshit
add(
"if [ -x \$MAGISKTMP/busybox/busybox ]; then",
" cp -af $localBB \$MAGISKTMP/busybox/busybox",
" exec \$MAGISKTMP/busybox/busybox sh",
"else",
" cp -af $localBB /dev/.busybox",
" exec /dev/.busybox sh",
"fi"
)
} else {
// Directly execute the file
add("exec $localBB sh")
}
}.exec()
return true
}
}
class AppShellInit : BaseShellInit() {
override fun init(context: Context, shell: Shell): Boolean {
fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd)
fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean()
shell.newJob().apply {
add("export API=${Build.VERSION.SDK_INT}")
add(context.rawResource(R.raw.manager))
if (shell.isRoot) {
add(context.assets.open("util_functions.sh"))
}
add("app_init")
}.exec()
Const.MAGISKTMP = getVar("MAGISKTMP")
Info.isSAR = getBool("SYSTEM_ROOT")
Info.ramdisk = getBool("RAMDISKEXIST")
Info.isAB = getBool("ISAB")
Info.crypto = getVar("CRYPTOTYPE")
// Default presets
Config.recovery = getBool("RECOVERYMODE")
Config.keepVerity = getBool("KEEPVERITY")
Config.keepEnc = getBool("KEEPFORCEENCRYPT")
return true
}
}

View File

@@ -36,7 +36,7 @@ fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
dest = SuFile(folder, name)
dest.parentFile!!.mkdirs()
}
SuFileOutputStream(dest).use { out -> zin.copyTo(out) }
SuFileOutputStream.open(dest).use { out -> zin.copyTo(out) }
}
} catch (e: IOException) {
e.printStackTrace()

View File

@@ -8,7 +8,9 @@ import com.topjohnwu.magisk.core.Config.Value.DEFAULT_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.model.*
import com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.StubJson
import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.data.network.*
import retrofit2.HttpException
import timber.log.Timber
@@ -34,7 +36,6 @@ class NetworkService(
Config.updateChannel = BETA_CHANNEL
info = fetchBetaUpdate()
}
Info.remote = info
info
}
@@ -47,16 +48,12 @@ class NetworkService(
val info = jsd.fetchCanaryUpdate(sha)
fun genCDNUrl(name: String) = "${Const.Url.JS_DELIVR_URL}${MAGISK_FILES}@${sha}/${name}"
fun ManagerJson.updateCopy() = copy(link = genCDNUrl(link), note = genCDNUrl(note))
fun MagiskJson.updateCopy() = copy(link = genCDNUrl(link), note = genCDNUrl(note))
fun StubJson.updateCopy() = copy(link = genCDNUrl(link))
fun UninstallerJson.updateCopy() = copy(link = genCDNUrl(link))
return info.copy(
app = info.app.updateCopy(),
magisk = info.magisk.updateCopy(),
stub = info.stub.updateCopy(),
uninstaller = info.uninstaller.updateCopy()
stub = info.stub.updateCopy()
)
}

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