mirror of
https://github.com/topjohnwu/Magisk
synced 2025-11-02 14:12:29 +01:00
Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
483dbcdc40 | ||
|
|
a1096b5bf0 | ||
|
|
5ac0e64edb | ||
|
|
60b2624607 | ||
|
|
d2e2847b03 | ||
|
|
b9669f54f7 | ||
|
|
8c7bd77d33 | ||
|
|
ba1ce16b8b | ||
|
|
68090943f4 | ||
|
|
a4fb1297b0 | ||
|
|
860a05abf2 | ||
|
|
8bb2f356c0 | ||
|
|
4950020635 | ||
|
|
0a6140c6eb | ||
|
|
bba2ac8817 | ||
|
|
331b1f542f | ||
|
|
ccb55205e6 | ||
|
|
9cc91b30b3 | ||
|
|
e836caf31e | ||
|
|
beaa1e5be2 | ||
|
|
ea545bae26 | ||
|
|
1c9ec2df45 | ||
|
|
b76c80e2ce | ||
|
|
236990f4a3 | ||
|
|
1ed32df20d | ||
|
|
8476eb9f4b | ||
|
|
735af7843b | ||
|
|
ded73e958b | ||
|
|
6dcb84d4f4 | ||
|
|
501bc9f438 | ||
|
|
f88e812b63 | ||
|
|
be6386c410 | ||
|
|
2af4fd17c4 | ||
|
|
f870418bd0 | ||
|
|
00659e4795 | ||
|
|
cdda10207e | ||
|
|
701700279f | ||
|
|
a9d804724a | ||
|
|
e033a9ab47 | ||
|
|
059e5fb8aa | ||
|
|
a78f255928 | ||
|
|
1d10e69288 | ||
|
|
63590d379c | ||
|
|
5f63e88984 | ||
|
|
75584e2b19 | ||
|
|
1426ee2ebd | ||
|
|
b6643b7bfc | ||
|
|
721dfdf553 | ||
|
|
2963747d14 | ||
|
|
e7350d5041 | ||
|
|
f37e8f4ca8 | ||
|
|
594c2accc0 | ||
|
|
7acfac6a91 | ||
|
|
0646f48e14 | ||
|
|
37565fd067 | ||
|
|
26b2e7dc5d | ||
|
|
c3313623e4 | ||
|
|
2089223690 | ||
|
|
52e1b84d41 | ||
|
|
8794141b7f | ||
|
|
f6126dd20e | ||
|
|
18acfda99b | ||
|
|
bec5edca84 | ||
|
|
6fb20b3ee5 | ||
|
|
eaf4d8064b | ||
|
|
2a5f5b1bba | ||
|
|
c538a77937 | ||
|
|
aa9e7b1ed1 | ||
|
|
a3066eddab | ||
|
|
d1729fa787 | ||
|
|
93961dde2c | ||
|
|
1024e68eb6 | ||
|
|
6ae2c9387d | ||
|
|
fba83e2330 | ||
|
|
f1295cb7d6 | ||
|
|
dc61dfbde6 | ||
|
|
21466426da | ||
|
|
3f0136362b | ||
|
|
e92d77bbec | ||
|
|
07bd36c94b | ||
|
|
b1dbbdef12 | ||
|
|
3e479726ec | ||
|
|
4cc41eccb3 | ||
|
|
8f08ae59ac | ||
|
|
e8d4e492d6 | ||
|
|
b8090a8e18 | ||
|
|
c609a01e55 | ||
|
|
c97fb385cd | ||
|
|
da6c57750e | ||
|
|
6951d926f7 | ||
|
|
6623195bd5 | ||
|
|
ec31bb9a82 | ||
|
|
8618cc383a | ||
|
|
4b01e3a3c7 | ||
|
|
7f748c23c1 | ||
|
|
963d248cc7 | ||
|
|
657056e636 | ||
|
|
9d5efea66e | ||
|
|
658d74e026 | ||
|
|
5113f6d375 | ||
|
|
96405c26d0 | ||
|
|
4ea5f34bf3 | ||
|
|
dbd13a2019 | ||
|
|
06773235da | ||
|
|
e57556a8af | ||
|
|
b54b78c29d | ||
|
|
317336f771 | ||
|
|
b4e52f6135 | ||
|
|
f2ca042915 | ||
|
|
1060dd2906 | ||
|
|
2e0f7a82fa | ||
|
|
5798536559 | ||
|
|
ab9a83c82f | ||
|
|
c87fdbea0f | ||
|
|
ec8fffe61c | ||
|
|
61d52991f1 | ||
|
|
9100186dce | ||
|
|
d2bc2cfcf8 | ||
|
|
5a71998b4e | ||
|
|
42278f12ff | ||
|
|
f5593e051c | ||
|
|
a27e30cf54 | ||
|
|
79140c7636 | ||
|
|
1f4c595cd3 | ||
|
|
b5b62e03af | ||
|
|
67e2a4720e | ||
|
|
f5c2d72429 | ||
|
|
2f5331ab48 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
|
||||
10
README.MD
10
README.MD
@@ -15,11 +15,13 @@ Here are some feature highlights:
|
||||
|
||||
## Downloads
|
||||
|
||||
[](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://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.7/MagiskManager-v8.0.7.apk)
|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
|
||||
<br>
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v21.2)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v21.2)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v21.4)
|
||||
[](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
5
app/.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
17
app/proguard-rules.pro
vendored
17
app/proguard-rules.pro
vendored
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
102
app/src/main/java/com/topjohnwu/magisk/core/utils/ShellInit.kt
Normal file
102
app/src/main/java/com/topjohnwu/magisk/core/utils/ShellInit.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user