Compare commits

...

59 Commits

Author SHA1 Message Date
Dmitry Val'd
13bf1b27b4 Update strings.xml
Added new lines from original
2017-10-15 03:15:39 +08:00
topjohnwu
f742bb1c47 Hot fix for detecting MagiskHide 2017-10-15 03:12:13 +08:00
topjohnwu
aa0b9e2db2 Bump version 2017-10-14 04:18:14 +08:00
topjohnwu
c10076f7ed Remove debug logs 2017-10-14 04:05:41 +08:00
topjohnwu
bcd92499f2 Massive improvement on Online Repo fetching 2017-10-14 04:05:41 +08:00
topjohnwu
b2bb0d4f72 Fix some external storage permission issues 2017-10-14 00:36:10 +08:00
topjohnwu
e140481f14 Wrap wrapper with buffer 2017-10-13 20:47:14 +08:00
topjohnwu
186bd11463 Reconnect until we got content length 2017-10-13 03:25:56 +08:00
topjohnwu
a0490d6687 Update Trad. Chinese translation 2017-10-13 03:10:35 +08:00
killer7mod
beef740ade update strings.xml for PT-BR 2017-10-13 02:45:02 +08:00
Frieder Bluemle
2ac7786a90 Update commonmark to 0.10.0 2017-10-13 02:44:42 +08:00
Frieder Bluemle
a3fb5e910f Update bouncycastle libs to 1.58 2017-10-13 02:44:42 +08:00
Frieder Bluemle
319afe86b5 Update Gradle wrapper to 4.2.1 2017-10-13 02:44:42 +08:00
Frieder Bluemle
762ab66b86 Fix Lint errors 2017-10-13 02:44:42 +08:00
topjohnwu
0c239a42de Allow secondary users to control Superuser settings except multiuser options 2017-10-13 02:41:43 +08:00
dark-basic #DarkBasic BasicHD
e9322fba26 Update strings.xml
New Lines Added
2017-10-07 23:44:10 +08:00
RoySchutte
39b6df27b3 Update strings.xml 2017-10-07 20:55:00 +08:00
topjohnwu
b1ee284e7f Rename resource -> common 2017-10-07 20:48:45 +08:00
topjohnwu
e986332bf2 Several small snet fixes 2017-10-07 20:47:44 +08:00
topjohnwu
48f9b27381 Seperate JarSigner and add task for host 2017-10-07 20:31:49 +08:00
topjohnwu
42a6e0dd10 Seperate Google proprietary code 2017-10-07 17:12:36 +08:00
topjohnwu
d4798b02ac Move functions 2017-10-04 22:27:14 +08:00
topjohnwu
963edfe8ab Add InputStream mode for signing zips 2017-10-04 22:09:59 +08:00
topjohnwu
53237f3ae0 Update Android Studio and Proguard configs 2017-10-04 15:23:08 +08:00
topjohnwu
64da9281a4 Show progress while downloading modules 2017-10-01 02:38:25 +08:00
topjohnwu
ab7fd9799d Remove cache module exception 2017-10-01 01:38:25 +08:00
topjohnwu
f6bcc84251 Improve repo fetching 2017-10-01 01:28:50 +08:00
topjohnwu
35dc3d9df9 Update WebService 2017-10-01 01:12:45 +08:00
topjohnwu
566714a75d Use override functions 2017-09-30 03:25:50 +08:00
topjohnwu
c92f30b122 Re-organize classes 2017-09-30 03:04:23 +08:00
topjohnwu
294ad094c4 Show repo loading progress by showing repos already loaded 2017-09-30 01:15:34 +08:00
topjohnwu
c1a0f520f9 Prevent flash screen close when tapping outside 2017-09-29 13:20:34 +08:00
topjohnwu
773c24b7fc Bump version 2017-09-28 03:55:53 +08:00
topjohnwu
8f926c7ca9 Load scripts in memory 2017-09-28 03:33:56 +08:00
topjohnwu
c562cbc2bb Update zip and magisk installation 2017-09-26 20:46:58 +08:00
topjohnwu
3fbbb0865a Update trad. Chinese 2017-09-26 02:13:39 +08:00
Naboleo
7d5f612a48 Update strings.xml 2017-09-26 03:07:55 +09:00
linar10
4a5a36440b Update strings.xml 2017-09-26 03:07:41 +09:00
Dmitry Val'd
43dd5cfea1 Update RU translation
Added new or missing lines
2017-09-26 03:07:33 +09:00
dark-basic #DarkBasic BasicHD
7b5fec1842 Update strings.xml 2017-09-26 03:07:20 +09:00
topjohnwu
5762ded601 Properly detect hosts file 2017-09-25 17:55:40 +08:00
topjohnwu
a3abb86daa Only place files in de on FDE enabled devices 2017-09-24 21:29:01 +08:00
topjohnwu
4f5c656b05 Update uninstall method 2017-09-16 03:53:13 +08:00
topjohnwu
a31cddbe7b Prevent NPE 2017-09-16 02:41:24 +08:00
topjohnwu
b4ecd93f1c Proper FBE support: place files in DE 2017-09-15 18:03:25 +08:00
topjohnwu
0acc23e058 Allow dialog to popup 2017-09-15 13:55:36 +08:00
topjohnwu
cdd5f9b628 Fix busybox installation 2017-09-15 13:34:53 +08:00
topjohnwu
4c9f5f4655 Support patching second slot 2017-09-15 13:03:10 +08:00
topjohnwu
b80ba13cb4 Fix strings 2017-09-15 03:47:18 +08:00
Santiago Pintos
8260bdc09c Update translations into spanish
Add two strings: "zip_download_title" and "zip_download_msg"
2017-09-13 10:12:13 -05:00
RoySchutte
24f856e02b Update strings.xml 2017-09-13 10:12:03 -05:00
Mevlüt TOPÇU
3aa619b928 Update
Merge please

Thank you
2017-09-13 10:11:53 -05:00
Taras Korzhak
4cb5e98d94 Update Ukrainian translation 2017-09-13 10:11:25 -05:00
Primokorn
272910575e Update FR strings.xml
Stupid typo
Unhide Magisk Manager should not be translated
2017-09-13 10:09:37 -05:00
topjohnwu
a15a62f4bc Move logic to external script file 2017-09-13 23:07:59 +08:00
topjohnwu
53cf11db8c Fix failure if MagiskManager folder doesn't exist 2017-09-13 23:07:59 +08:00
Dmitry Val'd
01052fbe47 Update strings.xml 2017-09-07 10:45:27 +08:00
dark-basic #DarkBasic BasicHD
a5e1e075c7 Update Strings (6-9-17)
Small Update
New Line Added.
2017-09-07 10:45:12 +08:00
c727
6be32ac688 update german strings
small improvements for new strings
also unified some strings

@topjohnwu:
what do you thing about calling the hidden Magsik Manager also "Magisk Manager" instead of "Unhide Magisk Manager"
The hidden status could be symbolized by an incognito style version of the app icon
advantages:
-same position in app drawer
-no need to translate it
2017-09-07 10:45:02 +08:00
99 changed files with 2183 additions and 1570 deletions

View File

@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "com.topjohnwu.magisk"
minSdkVersion 21
targetSdkVersion 26
versionCode 54
versionName "5.3.0"
versionCode 57
versionName "5.4.0"
ndk {
moduleName 'zipadjust'
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
@@ -53,16 +53,14 @@ repositories {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':resource')
implementation 'com.android.support:recyclerview-v7:26.0.2'
implementation 'com.android.support:cardview-v7:26.0.2'
implementation 'com.android.support:design:26.0.2'
implementation 'com.android.support:support-v4:26.0.2'
implementation project(':common')
implementation project(':jarsigner')
implementation 'com.android.support:recyclerview-v7:26.1.0'
implementation 'com.android.support:cardview-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support:support-v4:26.1.0'
implementation 'com.jakewharton:butterknife:8.8.1'
implementation 'com.atlassian.commonmark:commonmark:0.9.0'
implementation 'org.bouncycastle:bcprov-jdk15on:1.57'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.57'
implementation 'com.atlassian.commonmark:commonmark:0.10.0'
implementation 'org.kamranzafar:jtar:2.3'
implementation 'com.google.android.gms:play-services-safetynet:9.0.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

View File

@@ -16,10 +16,9 @@
# public *;
#}
# Keep all names, we are open source anyway :)
-keepnames class ** { *; }
# BouncyCastle
-keep class org.bouncycastle.** { *; }
-keep class org.bouncycastle.jcajce.provider.** { *; }
-dontwarn javax.naming.**
-dontwarn android.content.**
-dontwarn android.animation.**

View File

@@ -17,7 +17,8 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
android:directBootAware="true"
tools:ignore="UnusedAttribute">
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
@@ -89,9 +90,10 @@
android:resource="@xml/file_paths" />
</provider>
<!-- Hardcode GMS version -->
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
android:value="11400000" />
</application>

View File

@@ -20,6 +20,7 @@ import com.topjohnwu.magisk.components.AlertDialogBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import butterknife.BindView;
import butterknife.ButterKnife;
@@ -57,7 +58,7 @@ public class AboutActivity extends Activity {
ab.setDisplayHomeAsUpEnabled(true);
}
appVersionInfo.setSummary(BuildConfig.VERSION_NAME);
appVersionInfo.setSummary(String.format(Locale.US, "%s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
String changes = null;
try (InputStream is = getAssets().open("changelog.html")) {

View File

@@ -16,7 +16,7 @@ import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.FlashZip;
import com.topjohnwu.magisk.asyncs.InstallMagisk;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.container.AdaptiveList;
import com.topjohnwu.magisk.utils.Shell;
import java.util.List;
@@ -63,6 +63,7 @@ public class FlashActivity extends Activity {
ab.setTitle(R.string.flashing);
}
setFloating();
setFinishOnTouchOutside(false);
if (!Shell.rootAccess())
reboot.setVisibility(View.GONE);
@@ -148,4 +149,5 @@ public class FlashActivity extends Activity {
ButterKnife.bind(this, itemView);
}
}
}

View File

@@ -4,6 +4,7 @@ import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.CardView;
@@ -19,7 +20,9 @@ import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.CheckSafetyNet;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.ExpandableView;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.components.SnackbarMaker;
@@ -39,6 +42,13 @@ import butterknife.Unbinder;
public class MagiskFragment extends Fragment
implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
public static final int CAUSE_SERVICE_DISCONNECTED = 0x00001;
public static final int CAUSE_NETWORK_LOST = 0x00010;
public static final int RESPONSE_ERR = 0x00100;
public static final int BASIC_PASS = 0x01000;
public static final int CTS_PASS = 0x10000;
private Container expandableContainer = new Container();
private MagiskManager mm;
@@ -85,11 +95,26 @@ public class MagiskFragment extends Fragment
@OnClick(R.id.safetyNet_title)
void safetyNet() {
safetyNetProgress.setVisibility(View.VISIBLE);
safetyNetRefreshIcon.setVisibility(View.GONE);
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
Utils.checkSafetyNet(getActivity());
collapse();
Runnable task = () -> {
safetyNetProgress.setVisibility(View.VISIBLE);
safetyNetRefreshIcon.setVisibility(View.GONE);
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
new CheckSafetyNet(getActivity()).exec();
collapse();
};
if (mm.snet_version < 0) {
// Show dialog
new AlertDialogBuilder(getActivity())
.setTitle(R.string.proprietary_title)
.setMessage(R.string.proprietary_notice)
.setCancelable(true)
.setPositiveButton(R.string.yes, (d, i) -> task.run())
.setNegativeButton(R.string.no_thanks, null)
.show();
} else {
task.run();
}
}
@OnClick(R.id.install_button)
@@ -158,11 +183,11 @@ public class MagiskFragment extends Fragment
}
@Override
public void onTopicPublished(Topic topic) {
public void onTopicPublished(Topic topic, Object result) {
if (topic == mm.updateCheckDone) {
updateCheckUI();
} else if (topic == mm.safetyNetDone) {
updateSafetyNetUI();
updateSafetyNetUI((int) result);
}
}
@@ -258,7 +283,8 @@ public class MagiskFragment extends Fragment
spinner.setEnabled(false);
} else {
items.add(getString(R.string.cannot_auto_detect));
items.addAll(mm.blockList);
if (mm.blockList != null)
items.addAll(mm.blockList);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_spinner_item, items);
@@ -301,37 +327,41 @@ public class MagiskFragment extends Fragment
mSwipeRefreshLayout.setRefreshing(false);
}
private void updateSafetyNetUI() {
int image, color;
private void updateSafetyNetUI(int response) {
safetyNetProgress.setVisibility(View.GONE);
safetyNetRefreshIcon.setVisibility(View.VISIBLE);
if (mm.SNCheckResult.failed) {
safetyNetStatusText.setText(mm.SNCheckResult.errmsg);
collapse();
} else {
if (response < 0) {
safetyNetStatusText.setText(R.string.safetyNet_api_error);
} else if ((response & 0x111) == 0) {
safetyNetStatusText.setText(R.string.safetyNet_check_success);
if (mm.SNCheckResult.ctsProfile) {
color = colorOK;
image = R.drawable.ic_check_circle;
} else {
color = colorBad;
image = R.drawable.ic_cancel;
}
ctsStatusText.setText("ctsProfile: " + mm.SNCheckResult.ctsProfile);
ctsStatusIcon.setImageResource(image);
ctsStatusIcon.setColorFilter(color);
if (mm.SNCheckResult.basicIntegrity) {
color = colorOK;
image = R.drawable.ic_check_circle;
} else {
color = colorBad;
image = R.drawable.ic_cancel;
}
basicStatusText.setText("basicIntegrity: " + mm.SNCheckResult.basicIntegrity);
basicStatusIcon.setImageResource(image);
basicStatusIcon.setColorFilter(color);
boolean b;
b = (response & CTS_PASS) != 0;
ctsStatusText.setText("ctsProfile: " + b);
ctsStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
ctsStatusIcon.setColorFilter(b ? colorOK : colorBad);
b = (response & BASIC_PASS) != 0;
basicStatusText.setText("basicIntegrity: " + b);
basicStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
basicStatusIcon.setColorFilter(b ? colorOK : colorBad);
expand();
} else {
@StringRes int resid;
switch (response) {
case CAUSE_SERVICE_DISCONNECTED:
resid = R.string.safetyNet_network_loss;
break;
case CAUSE_NETWORK_LOST:
resid = R.string.safetyNet_service_disconnected;
break;
case RESPONSE_ERR:
default:
resid = R.string.safetyNet_res_invalid;
break;
}
safetyNetStatusText.setText(resid);
}
}
}

View File

@@ -84,7 +84,7 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
}
@Override
public void onTopicPublished(Topic topic) {
public void onTopicPublished(Topic topic, Object result) {
mSwipeRefreshLayout.setRefreshing(false);
appAdapter.filter(lastFilter);
}

View File

@@ -20,18 +20,18 @@ import com.topjohnwu.magisk.asyncs.DownloadBusybox;
import com.topjohnwu.magisk.asyncs.LoadModules;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.asyncs.UpdateRepos;
import com.topjohnwu.magisk.container.Module;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.services.UpdateCheckService;
import com.topjohnwu.magisk.superuser.SuReceiver;
import com.topjohnwu.magisk.superuser.SuRequestActivity;
import com.topjohnwu.magisk.utils.SafetyNetHelper;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -40,6 +40,7 @@ import java.util.concurrent.ExecutionException;
public class MagiskManager extends Application {
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
public static final String MAGISK_HOST_FILE = "/magisk/.core/hosts";
public static final String TMP_FOLDER_PATH = "/dev/tmp";
public static final String MAGISK_PATH = "/magisk";
public static final String INTENT_SECTION = "section";
@@ -70,7 +71,6 @@ public class MagiskManager extends Application {
public String remoteManagerVersionString;
public int remoteManagerVersionCode = -1;
public String managerLink;
public SafetyNetHelper.Result SNCheckResult;
public String bootBlock = null;
public boolean isSuClient = false;
public String suVersion = null;
@@ -82,8 +82,6 @@ public class MagiskManager extends Application {
public List<Locale> locales;
// Configurations
public static boolean shellLogging;
public static boolean devLogging;
public static Locale locale;
public static Locale defaultLocale;
@@ -101,6 +99,7 @@ public class MagiskManager extends Application {
public String localeConfig;
public int updateChannel;
public String bootFormat;
public int snet_version;
// Global resources
public SharedPreferences prefs;
@@ -132,9 +131,15 @@ public class MagiskManager extends Application {
@Override
public void onCreate() {
super.onCreate();
new File(getApplicationInfo().dataDir).mkdirs(); /* Create the app data directory */
prefs = PreferenceManager.getDefaultSharedPreferences(this);
suDB = new SuDatabaseHelper(this);
if (getDatabasePath(SuDatabaseHelper.DB_NAME).exists()) {
// Don't migrate yet, wait and check Magisk version
suDB = new SuDatabaseHelper(this);
} else {
suDB = new SuDatabaseHelper(Utils.getEncContext(this));
}
repoDB = new RepoDatabaseHelper(this);
defaultLocale = Locale.getDefault();
setLocale();
@@ -156,13 +161,6 @@ public class MagiskManager extends Application {
public void loadConfig() {
isDarkTheme = prefs.getBoolean("dark_theme", false);
if (BuildConfig.DEBUG) {
devLogging = prefs.getBoolean("developer_logging", false);
shellLogging = prefs.getBoolean("shell_logging", false);
} else {
devLogging = false;
shellLogging = false;
}
// su
suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
@@ -176,6 +174,7 @@ public class MagiskManager extends Application {
updateNotification = prefs.getBoolean("notification", true);
updateChannel = Utils.getPrefsInt(prefs, "update_channel", CheckUpdates.STABLE_CHANNEL);
bootFormat = prefs.getString("boot_format", ".img");
snet_version = prefs.getInt("snet_version", -1);
}
public void toast(String msg, int duration) {
@@ -194,27 +193,58 @@ public class MagiskManager extends Application {
boolean hasNetwork = Utils.checkNetworkStatus(this);
getMagiskInfo();
new LoadLocale(this).exec();
// Force synchronous, make sure we have busybox to use
if (hasNetwork && Shell.rootAccess()
&& !Utils.itemExist(shell, BUSYBOXPATH + "/busybox")) {
try {
new DownloadBusybox(this).exec().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
// Check if we need to migrate suDB
if (getDatabasePath(SuDatabaseHelper.DB_NAME).exists() && Utils.useFDE(this)) {
if (magiskVersionCode >= 1410) {
suDB.close();
Context de = createDeviceProtectedStorageContext();
de.moveDatabaseFrom(this, SuDatabaseHelper.DB_NAME);
suDB = new SuDatabaseHelper(de);
}
}
shell.su_raw("export PATH=" + BUSYBOXPATH + ":$PATH");
updateBlockInfo();
new LoadLocale(this).exec();
// Root actions
if (Shell.rootAccess()) {
if (hasNetwork && !Utils.itemExist(shell, BUSYBOXPATH + "/busybox")) {
try {
// Force synchronous, make sure we have busybox to use
new DownloadBusybox(this).exec().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
try (InputStream in = getAssets().open(Utils.UTIL_FUNCTIONS)) {
shell.loadInputStream(in);
} catch (IOException e) {
e.printStackTrace();
}
shell.su_raw(
"export PATH=" + BUSYBOXPATH + ":$PATH",
"mount_partitions",
"BOOTIMAGE=",
"find_boot_image",
"migrate_boot_backup"
);
List<String> res = shell.su("echo \"$BOOTIMAGE\"");
if (Utils.isValidShellResponse(res)) {
bootBlock = res.get(0);
} else {
blockList = shell.su("find /dev/block -type b | grep -vE 'dm|ram|loop'");
}
}
// Write back default values
prefs.edit()
.putBoolean("dark_theme", isDarkTheme)
.putBoolean("magiskhide", magiskHide)
.putBoolean("notification", updateNotification)
.putBoolean("hosts", new File("/magisk/.core/hosts").exists())
.putBoolean("hosts", Utils.itemExist(shell, MAGISK_HOST_FILE))
.putBoolean("disable", Utils.itemExist(shell, MAGISK_DISABLE_FILE))
.putBoolean("su_reauth", suReauth)
.putString("su_request_timeout", String.valueOf(suRequestTimeout))
@@ -232,7 +262,7 @@ public class MagiskManager extends Application {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL,
getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
getSystemService(NotificationManager.class).createNotificationChannel(channel);
}
LoadModules loadModuleTask = new LoadModules(this);
@@ -245,7 +275,7 @@ public class MagiskManager extends Application {
.setPeriodic(8 * 60 * 60 * 1000)
.build();
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo);
loadModuleTask.setCallBack(() -> new UpdateRepos(this).exec());
loadModuleTask.setCallBack(() -> new UpdateRepos(this, false).exec());
}
// Fire asynctasks
loadModuleTask.exec();
@@ -282,28 +312,15 @@ public class MagiskManager extends Application {
} catch (NumberFormatException e) {
disabled = false;
}
ret = shell.sh("getprop " + MAGISKHIDE_PROP);
if (magiskVersionCode > 1435) {
ret = shell.su("resetprop -p " + MAGISKHIDE_PROP);
} else {
ret = shell.sh("getprop " + MAGISKHIDE_PROP);
}
try {
magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
magiskHide = true;
}
}
private void updateBlockInfo() {
List<String> res = shell.su(
"for BLOCK in boot_a kern-a android_boot kernel boot lnx; do",
" BOOTIMAGE=`find /dev/block -iname $BLOCK | head -n 1` 2>/dev/null",
" [ ! -z $BOOTIMAGE ] && break",
"done",
"[ ! -z \"$BOOTIMAGE\" -a -L \"$BOOTIMAGE\" ] && BOOTIMAGE=`readlink $BOOTIMAGE`",
"echo \"$BOOTIMAGE\""
);
if (Utils.isValidShellResponse(res)) {
bootBlock = res.get(0);
} else {
blockList = shell.su("find /dev/block -type b | grep -vE 'dm-0|ram|loop'");
}
}
}

View File

@@ -102,7 +102,7 @@ public class MainActivity extends Activity
}
@Override
public void onTopicPublished(Topic topic) {
public void onTopicPublished(Topic topic, Object result) {
recreate();
}

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -14,9 +15,9 @@ import android.widget.TextView;
import com.topjohnwu.magisk.adapters.ModulesAdapter;
import com.topjohnwu.magisk.asyncs.LoadModules;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.container.Module;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -36,9 +37,11 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
@BindView(R.id.empty_rv) TextView emptyRv;
@OnClick(R.id.fab)
public void selectFile() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("application/zip");
startActivityForResult(intent, FETCH_ZIP_CODE);
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("application/zip");
startActivityForResult(intent, FETCH_ZIP_CODE);
});
}
private List<Module> listModules = new ArrayList<>();
@@ -72,8 +75,7 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
}
@Override
public void onTopicPublished(Topic topic) {
Logger.dev("ModulesFragment: UI refresh triggered");
public void onTopicPublished(Topic topic, Object result) {
updateUI();
}

View File

@@ -15,7 +15,6 @@ import android.widget.TextView;
import com.topjohnwu.magisk.adapters.ReposAdapter;
import com.topjohnwu.magisk.asyncs.UpdateRepos;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Topic;
import butterknife.BindView;
@@ -29,7 +28,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
private ReposAdapter adapter;
public static ReposAdapter adapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -43,14 +42,12 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
View view = inflater.inflate(R.layout.fragment_repos, container, false);
unbinder = ButterKnife.bind(this, view);
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
recyclerView.setAdapter(adapter);
mSwipeRefreshLayout.setRefreshing(true);
mSwipeRefreshLayout.setOnRefreshListener(() -> {
recyclerView.setVisibility(View.GONE);
new UpdateRepos(getActivity()).exec();
recyclerView.setVisibility(View.VISIBLE);
emptyRv.setVisibility(View.GONE);
new UpdateRepos(getActivity(), true).exec();
});
getActivity().setTitle(R.string.downloads);
@@ -59,10 +56,21 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
}
@Override
public void onTopicPublished(Topic topic) {
Logger.dev("ReposFragment: UI refresh triggered");
public void onResume() {
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
recyclerView.setAdapter(adapter);
super.onResume();
}
@Override
public void onPause() {
super.onPause();
adapter = null;
}
@Override
public void onTopicPublished(Topic topic, Object result) {
mSwipeRefreshLayout.setRefreshing(false);
adapter.notifyDBChanged();
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}

View File

@@ -19,7 +19,6 @@ import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.HideManager;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
@@ -62,7 +61,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
}
@Override
public void onTopicPublished(Topic topic) {
public void onTopicPublished(Topic topic, Object result) {
recreate();
}
@@ -94,7 +93,6 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
generalCatagory = (PreferenceCategory) findPreference("general");
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
PreferenceCategory developer = (PreferenceCategory) findPreference("developer");
updateChannel = (ListPreference) findPreference("update_channel");
suAccess = (ListPreference) findPreference("su_access");
@@ -110,8 +108,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
// Disable dangerous settings in user mode if selected owner manage
if (getActivity().getApplicationInfo().uid > 99999) {
prefScreen.removePreference(magiskCategory);
prefScreen.removePreference(suCategory);
suCategory.removePreference(multiuserMode);
generalCatagory.removePreference(hideManager);
}
@@ -132,10 +129,6 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
return true;
});
if (!BuildConfig.DEBUG) {
prefScreen.removePreference(developer);
}
if (!Shell.rootAccess()) {
prefScreen.removePreference(magiskCategory);
prefScreen.removePreference(suCategory);
@@ -190,7 +183,6 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
Logger.dev("Settings: Prefs change " + key);
boolean enabled;
switch (key) {
@@ -221,12 +213,12 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
enabled = prefs.getBoolean("hosts", false);
if (enabled) {
getShell().su_raw(
"cp -af /system/etc/hosts /magisk/.core/hosts",
"mount -o bind /magisk/.core/hosts /system/etc/hosts");
"cp -af /system/etc/hosts " + MagiskManager.MAGISK_HOST_FILE,
"mount -o bind " + MagiskManager.MAGISK_HOST_FILE + " /system/etc/hosts");
} else {
getShell().su_raw(
"umount -l /system/etc/hosts",
"rm -f /magisk/.core/hosts");
"rm -f " + MagiskManager.MAGISK_HOST_FILE);
}
break;
case "su_access":
@@ -273,7 +265,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
}
@Override
public void onTopicPublished(Topic topic) {
public void onTopicPublished(Topic topic, Object result) {
setLocalePreference((ListPreference) findPreference("locale"));
}

View File

@@ -11,7 +11,7 @@ import android.widget.TextView;
import com.topjohnwu.magisk.adapters.PolicyAdapter;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.container.Policy;
import java.util.List;

View File

@@ -13,7 +13,7 @@ import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.container.Module;
import com.topjohnwu.magisk.utils.Shell;
import java.util.List;

View File

@@ -15,8 +15,8 @@ import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.ExpandableView;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.Policy;
import java.util.HashSet;
import java.util.List;

View File

@@ -17,9 +17,9 @@ import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.container.Module;
import com.topjohnwu.magisk.container.Repo;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.Utils;
import java.util.ArrayList;
@@ -44,6 +44,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
repoDB = db;
moduleMap = map;
repoPairs = new ArrayList<>();
notifyDBChanged();
}

View File

@@ -12,8 +12,8 @@ import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.ExpandableView;
import com.topjohnwu.magisk.container.SuLogEntry;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.Collections;
import java.util.HashSet;

View File

@@ -0,0 +1,84 @@
package com.topjohnwu.magisk.asyncs;
import android.support.v4.app.FragmentActivity;
import com.topjohnwu.jarsigner.ByteArrayStream;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.WebService;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Proxy;
import java.net.HttpURLConnection;
import dalvik.system.DexClassLoader;
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
public static final int SNET_VER = 2;
private static final String SNET_URL = "https://github.com/topjohnwu/MagiskManager/releases/download/v5.4.0/snet.apk";
private static final String PKG = "com.topjohnwu.snet";
private File dexPath;
private DexClassLoader loader;
public CheckSafetyNet(FragmentActivity activity) {
super(activity);
dexPath = new File(activity.getCacheDir().getParent() + "/snet", "snet.apk");
}
@Override
protected void onPreExecute() {
MagiskManager mm = getMagiskManager();
if (mm.snet_version != CheckSafetyNet.SNET_VER) {
getShell().sh("rm -rf " + dexPath.getParent());
}
mm.snet_version = CheckSafetyNet.SNET_VER;
mm.prefs.edit().putInt("snet_version", CheckSafetyNet.SNET_VER).apply();
}
@Override
protected Exception doInBackground(Void... voids) {
try {
if (!dexPath.exists()) {
HttpURLConnection conn = WebService.request(SNET_URL, null);
ByteArrayStream bas = new ByteArrayStream();
bas.readFrom(conn.getInputStream());
conn.disconnect();
dexPath.getParentFile().mkdir();
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath))) {
bas.writeTo(out);
out.flush();
}
}
loader = new DexClassLoader(dexPath.toString(), dexPath.getParent(),
null, ClassLoader.getSystemClassLoader());
} catch (Exception e) {
return e;
}
return null;
}
@Override
protected void onPostExecute(Exception err) {
try {
if (err != null) throw err;
Class<?> helperClazz = loader.loadClass(PKG + ".SafetyNetHelper");
Class<?> callbackClazz = loader.loadClass(PKG + ".SafetyNetCallback");
Object helper = helperClazz.getConstructors()[0].newInstance(
getActivity(), Proxy.newProxyInstance(
loader, new Class[] { callbackClazz }, (proxy, method, args) -> {
getMagiskManager().safetyNetDone.publish(false, args[0]);
return null;
}));
helperClazz.getMethod("attest").invoke(helper);
} catch (Exception e) {
e.printStackTrace();
getMagiskManager().safetyNetDone.publish(false, -1);
}
super.onPostExecute(err);
}
}

View File

@@ -11,7 +11,7 @@ import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
public class DownloadBusybox extends ParallelTask<Void, Void, Void> {
@@ -31,27 +31,26 @@ public class DownloadBusybox extends ParallelTask<Void, Void, Void> {
Utils.removeItem(getShell(), context.getApplicationInfo().dataDir + "/busybox");
try {
FileOutputStream out = new FileOutputStream(busybox);
InputStream in = WebService.request(WebService.GET,
HttpURLConnection conn = WebService.request(
Build.SUPPORTED_32_BIT_ABIS[0].contains("x86") ?
BUSYBOX_X86 :
BUSYBOX_ARM,
null
);
if (in == null) throw new IOException();
BufferedInputStream bis = new BufferedInputStream(in);
if (conn == null) throw new IOException();
BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
byte[] buffer = new byte[4096];
int len;
while ((len = bis.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
bis.close();
conn.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
if (busybox.exists()) {
getShell().su_raw(
getShell().su(
"rm -rf " + MagiskManager.BUSYBOXPATH,
"mkdir -p " + MagiskManager.BUSYBOXPATH,
"cp " + busybox + " " + MagiskManager.BUSYBOXPATH,

View File

@@ -6,10 +6,12 @@ import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.container.AdaptiveList;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -21,27 +23,19 @@ import java.util.List;
public class FlashZip extends ParallelTask<Void, Void, Integer> {
private Uri mUri;
private File mCachedFile, mScriptFile, mCheckFile;
private String mFilename;
private File mCachedFile;
private AdaptiveList<String> mList;
public FlashZip(Activity context, Uri uri, AdaptiveList<String> list) {
super(context);
mUri = uri;
mList = list;
mCachedFile = new File(context.getCacheDir(), "install.zip");
mScriptFile = new File(context.getCacheDir(), "/META-INF/com/google/android/update-binary");
mCheckFile = new File(mScriptFile.getParent(), "updater-script");
// Try to get the filename ourselves
mFilename = Utils.getNameFromUri(context, mUri);
}
private boolean unzipAndCheck() throws Exception {
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", false);
List<String> ret = Utils.readFile(getShell(), mCheckFile.getPath());
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
List<String> ret = Utils.readFile(getShell(), new File(mCachedFile.getParentFile(), "updater-script").getPath());
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
}
@@ -66,12 +60,13 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
mCachedFile.delete();
try (
InputStream in = mm.getContentResolver().openInputStream(mUri);
OutputStream out = new FileOutputStream(mCachedFile)
OutputStream out = new BufferedOutputStream(new FileOutputStream(mCachedFile))
) {
if (in == null) throw new FileNotFoundException();
byte buffer[] = new byte[1024];
InputStream buf= new BufferedInputStream(in);
byte buffer[] = new byte[4096];
int length;
while ((length = in.read(buffer)) > 0)
while ((length = buf.read(buffer)) > 0)
out.write(buffer, 0, length);
} catch (FileNotFoundException e) {
mList.add("! Invalid Uri");
@@ -81,9 +76,10 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
throw e;
}
if (!unzipAndCheck()) return 0;
mList.add("- Installing " + mFilename);
mList.add("- Installing " + Utils.getNameFromUri(mm, mUri));
getShell().su(mList,
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile +
"cd " + mCachedFile.getParent(),
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile +
" && echo 'Success!' || echo 'Failed!'"
);
if (TextUtils.equals(mList.get(mList.size() - 1), "Success!"))

View File

@@ -5,17 +5,23 @@ import android.content.pm.PackageManager;
import android.os.Environment;
import android.widget.Toast;
import com.topjohnwu.jarsigner.JarMap;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.File;
import java.util.List;
import java.util.jar.JarEntry;
public class HideManager extends ParallelTask<Void, Void, Boolean> {
private static final String UNHIDE_APK = "unhide.apk";
private static final String ANDROID_MANIFEST = "AndroidManifest.xml";
private static final byte[] UNHIDE_PKG_NAME = "com.topjohnwu.unhide\0".getBytes();
public HideManager(Context context) {
super(context);
}
@@ -34,7 +40,42 @@ public class HideManager extends ParallelTask<Void, Void, Boolean> {
// Generate a new unhide app with random package name
File unhideAPK = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", "unhide.apk");
unhideAPK.getParentFile().mkdirs();
String pkg = ZipUtils.generateUnhide(mm, unhideAPK);
String pkg;
try {
JarMap asset = new JarMap(mm.getAssets().open(UNHIDE_APK));
JarEntry je = new JarEntry(ANDROID_MANIFEST);
byte xml[] = asset.getRawData(je);
int offset = -1;
// Linear search pattern offset
for (int i = 0; i < xml.length - UNHIDE_PKG_NAME.length; ++i) {
boolean match = true;
for (int j = 0; j < UNHIDE_PKG_NAME.length; ++j) {
if (xml[i + j] != UNHIDE_PKG_NAME[j]) {
match = false;
break;
}
}
if (match) {
offset = i;
break;
}
}
if (offset < 0)
return false;
// Patch binary XML with new package name
pkg = Utils.genPackageName("com.", UNHIDE_PKG_NAME.length - 1);
System.arraycopy(pkg.getBytes(), 0, xml, offset, pkg.length());
asset.getOutputStream(je).write(xml);
// Sign the APK
ZipUtils.signZip(mm, asset, unhideAPK, false);
} catch (Exception e) {
e.printStackTrace();
return false;
}
// Install the application
List<String> ret = getShell().su("pm install " + unhideAPK + ">/dev/null && echo true || echo false");

View File

@@ -7,9 +7,9 @@ import android.os.Environment;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.container.AdaptiveList;
import com.topjohnwu.magisk.container.TarEntry;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.TarEntry;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
@@ -75,7 +75,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
MagiskManager mm = getMagiskManager();
if (mm == null) return false;
File install = new File(mm.getApplicationInfo().dataDir, "install");
File install = new File(Utils.getEncContext(mm).getFilesDir().getParent(), "install");
getShell().sh_raw("rm -rf " + install);
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
@@ -114,8 +114,8 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
boot = new File(install, "boot.img");
// Copy boot image to local
try (
InputStream in = mm.getContentResolver().openInputStream(mBootImg);
OutputStream out = new FileOutputStream(boot)
InputStream in = mm.getContentResolver().openInputStream(mBootImg);
OutputStream out = new FileOutputStream(boot)
) {
InputStream source;
if (in == null) throw new FileNotFoundException();
@@ -131,7 +131,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
source = tar;
} else {
// Direct copy raw image
source = in;
source = new BufferedInputStream(in);
}
byte buffer[] = new byte[1024];
int length;
@@ -204,8 +204,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
break;
case DIRECT_MODE:
// Direct flash boot image
getShell().su_raw("cat " + patched_boot + " /dev/zero | dd of=" + mBootLocation + " bs=4096");
mList.add("Flashing patched boot to " + mBootLocation);
getShell().su(mList, "flash_boot_image " + patched_boot + " " + mBootLocation);
break;
default:
return false;

View File

@@ -3,11 +3,9 @@ package com.topjohnwu.magisk.asyncs;
import android.content.Context;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.module.BaseModule;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.container.Module;
import com.topjohnwu.magisk.container.ValueSortedMap;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ValueSortedMap;
public class LoadModules extends ParallelTask<Void, Void, Void> {
@@ -19,19 +17,13 @@ public class LoadModules extends ParallelTask<Void, Void, Void> {
protected Void doInBackground(Void... voids) {
MagiskManager mm = getMagiskManager();
if (mm == null) return null;
Logger.dev("LoadModules: Loading modules");
mm.moduleMap = new ValueSortedMap<>();
for (String path : Utils.getModList(getShell(), MagiskManager.MAGISK_PATH)) {
Logger.dev("LoadModules: Adding modules from " + path);
try {
Module module = new Module(getShell(), path);
mm.moduleMap.put(module.getId(), module);
} catch (BaseModule.CacheModException ignored) {}
Module module = new Module(getShell(), path);
mm.moduleMap.put(module.getId(), module);
}
Logger.dev("LoadModules: Data load done");
return null;
}

View File

@@ -6,11 +6,12 @@ import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.widget.Toast;
import com.topjohnwu.magisk.FlashActivity;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.container.InputStreamWrapper;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.WebService;
@@ -21,34 +22,81 @@ import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
private ProgressDialog progressDialog;
private boolean mInstall;
private String mLink, mFile;
private String mLink;
private File mFile;
private int progress = 0, total = -1;
private static final int UPDATE_DL_PROG = 0;
private static final int SHOW_PROCESSING = 1;
public ProcessRepoZip(Activity context, String link, String filename, boolean install) {
super(context);
mLink = link;
mFile = Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename;
mFile = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", filename);
mInstall = install;
}
private void removeTopFolder(InputStream in, File output) throws IOException {
JarInputStream source = new JarInputStream(in);
JarOutputStream dest = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output)));
JarEntry entry;
String path;
int size;
byte buffer[] = new byte[4096];
while ((entry = source.getNextJarEntry()) != null) {
// Remove the top directory from the path
path = entry.getName().substring(entry.getName().indexOf("/") + 1);
// If it's the top folder, ignore it
if (path.isEmpty()) {
continue;
}
// Don't include placeholder
if (path.equals("system/placeholder")) {
continue;
}
dest.putNextEntry(new JarEntry(path));
while((size = source.read(buffer)) != -1) {
dest.write(buffer, 0, size);
}
}
source.close();
dest.close();
in.close();
}
@Override
protected void onPreExecute() {
Activity activity = getActivity();
progressDialog = ProgressDialog.show(activity,
activity.getString(R.string.zip_download_title),
activity.getString(R.string.zip_download_msg));
mFile.getParentFile().mkdirs();
progressDialog = ProgressDialog.show(activity, activity.getString(R.string.zip_download_title), activity.getString(R.string.zip_download_msg, 0));
}
@Override
protected void onProgressUpdate(Void... values) {
progressDialog.setTitle(R.string.zip_process_title);
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
protected void onProgressUpdate(Object... values) {
int mode = (int) values[0];
switch (mode) {
case UPDATE_DL_PROG:
int add = (int) values[1];
progress += add;
progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg, 100 * progress / total));
break;
case SHOW_PROCESSING:
progressDialog.setTitle(R.string.zip_process_title);
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
break;
}
}
@Override
@@ -58,9 +106,18 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
try {
// Request zip from Internet
InputStream in = WebService.request(WebService.GET, mLink, null);
if (in == null) return false;
in = new BufferedInputStream(in);
HttpURLConnection conn;
do {
conn = WebService.request(mLink, null);
if (conn == null) return null;
total = conn.getContentLength();
if (total < 0)
conn.disconnect();
else
break;
} while (true);
InputStream in = new BufferedInputStream(new ProgressInputStream(conn.getInputStream()));
// Temp files
File temp1 = new File(activity.getCacheDir(), "1.zip");
@@ -68,9 +125,10 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
temp1.getParentFile().mkdir();
// First remove top folder in Github source zip, Web -> temp1
ZipUtils.removeTopFolder(in, temp1);
removeTopFolder(in, temp1);
publishProgress();
conn.disconnect();
publishProgress(SHOW_PROCESSING);
// Then sign the zip for the first time, temp1 -> temp2
ZipUtils.signZip(activity, temp1, temp2, false);
@@ -97,7 +155,6 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
return true;
} catch (Exception e) {
Logger.error("ProcessRepoZip: Error!");
e.printStackTrace();
return false;
}
@@ -108,7 +165,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
Activity activity = getActivity();
if (activity == null) return;
progressDialog.dismiss();
Uri uri = Uri.fromFile(new File(mFile));
Uri uri = Uri.fromFile(mFile);
if (result) {
if (Shell.rootAccess() && mInstall) {
Intent intent = new Intent(getActivity(), FlashActivity.class);
@@ -124,8 +181,34 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
}
@Override
public ParallelTask<Void, Void, Boolean> exec(Void... voids) {
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> super.exec(voids));
public ParallelTask<Void, Object, Boolean> exec(Void... voids) {
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE,
() -> super.exec(voids));
return this;
}
private class ProgressInputStream extends InputStreamWrapper {
ProgressInputStream(InputStream in) {
super(in);
}
@Override
public synchronized int read() throws IOException {
publishProgress(UPDATE_DL_PROG, 1);
return super.read();
}
@Override
public int read(@NonNull byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
int read = super.read(b, off, len);
publishProgress(UPDATE_DL_PROG, read);
return read;
}
}
}

View File

@@ -25,7 +25,7 @@ public class RestoreStockBoot extends ParallelTask<Void, Void, Boolean> {
String stock_boot = "/data/stock_boot_" + ret.get(0).substring(ret.get(0).indexOf('=') + 1) + ".img.gz";
if (!Utils.itemExist(getShell(), stock_boot))
return false;
getShell().su_raw("gzip -d < " + stock_boot + " | cat - /dev/zero | dd of=" + mBoot + " bs=4096");
getShell().su_raw("flash_boot_image " + stock_boot + " " + mBoot);
return true;
}

View File

@@ -2,12 +2,12 @@ package com.topjohnwu.magisk.asyncs;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.os.AsyncTask;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.ReposFragment;
import com.topjohnwu.magisk.container.Repo;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.module.BaseModule;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.WebService;
@@ -15,6 +15,7 @@ import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -26,9 +27,9 @@ import java.util.Map;
public class UpdateRepos extends ParallelTask<Void, Void, Void> {
public static final String ETAG_KEY = "ETag";
private static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
public static final String ETAG_KEY = "ETag";
private static final String IF_NONE_MATCH = "If-None-Match";
private static final String LINK_KEY = "Link";
@@ -36,30 +37,34 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
private static final int LOAD_NEXT = 1;
private static final int LOAD_PREV = 2;
private List<String> etags;
private List<String> cached;
private List<String> cached, etags, newEtags = new ArrayList<>();
private RepoDatabaseHelper repoDB;
private SharedPreferences prefs;
private boolean forceUpdate;
public UpdateRepos(Context context) {
private int tasks = 0;
public UpdateRepos(Context context, boolean force) {
super(context);
prefs = getMagiskManager().prefs;
repoDB = getMagiskManager().repoDB;
String prefsPath = context.getApplicationInfo().dataDir + "/shared_prefs";
getMagiskManager().repoLoadDone.hasPublished = false;
// Legacy data cleanup
File old = new File(prefsPath, "RepoMap.xml");
if (old.exists() || !prefs.getString("repomap", "empty").equals("empty")) {
File old = new File(context.getApplicationInfo().dataDir + "/shared_prefs", "RepoMap.xml");
if (old.exists() || prefs.getString("repomap", null) != null) {
old.delete();
prefs.edit().remove("version").remove("repomap").remove(ETAG_KEY).apply();
repoDB.clearRepo();
}
etags = new ArrayList<>(
Arrays.asList(prefs.getString(ETAG_KEY, "").split(",")));
forceUpdate = force;
}
private void loadJSON(String jsonString) throws Exception {
JSONArray jsonArray = new JSONArray(jsonString);
// Empty page, throw error
if (jsonArray.length() == 0) throw new Exception();
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonobject = jsonArray.getJSONObject(i);
String id = jsonobject.getString("description");
@@ -67,116 +72,140 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
String lastUpdate = jsonobject.getString("pushed_at");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
Date updatedDate = format.parse(lastUpdate);
Repo repo = repoDB.getRepo(id);
try {
++tasks;
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
Repo repo = repoDB.getRepo(id);
Boolean updated;
if (repo == null) {
Logger.dev("UpdateRepos: Create new repo " + id);
repo = new Repo(name, updatedDate);
updated = true;
} else {
// Popout from cached
cached.remove(id);
updated = repo.update(updatedDate);
try {
if (repo == null) {
repo = new Repo(name, updatedDate);
updated = true;
} else {
// Popout from cached
cached.remove(id);
if (forceUpdate) {
repo.update();
updated = true;
} else {
updated = repo.update(updatedDate);
}
}
if (updated) {
repoDB.addRepo(repo);
publishProgress();
}
if (!id.equals(repo.getId())) {
Logger.error("Repo [" + name + "] id=[" + repo.getId() + "] has illegal repo id");
}
} catch (Repo.IllegalRepoException e) {
Logger.error(e.getMessage());
repoDB.removeRepo(id);
}
if (updated) {
repoDB.addRepo(repo);
}
} catch (BaseModule.CacheModException ignored) {}
--tasks;
});
}
}
private boolean loadPage(int page, String url, int mode) {
Logger.dev("UpdateRepos: Loading page: " + (page + 1));
private boolean loadPage(int page, int mode) {
Map<String, String> header = new HashMap<>();
if (mode == CHECK_ETAG && page < etags.size() && !TextUtils.isEmpty(etags.get(page))) {
Logger.dev("ETAG: " + etags.get(page));
header.put(IF_NONE_MATCH, etags.get(page));
}
if (url == null) {
url = String.format(Locale.US, REPO_URL, page + 1);
}
String jsonString = WebService.getString(url, header);
if (TextUtils.isEmpty(jsonString)) {
// At least check the pages we know
return page + 1 < etags.size() && loadPage(page + 1, null, CHECK_ETAG);
String etag = "";
if (mode == CHECK_ETAG && page < etags.size()) {
etag = etags.get(page);
}
header.put(IF_NONE_MATCH, etag);
String url = String.format(Locale.US, REPO_URL, page + 1);
HttpURLConnection conn = WebService.request(url, header);
// The getString succeed, parse the new stuffs
try {
loadJSON(jsonString);
if (conn == null) throw new Exception();
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
newEtags.add(etag);
return page + 1 < etags.size() && loadPage(page + 1, CHECK_ETAG);
}
loadJSON(WebService.getString(conn));
} catch (Exception e) {
e.printStackTrace();
return false;
// Don't continue
return true;
}
// Update the ETAG
String newEtag = header.get(ETAG_KEY);
newEtag = newEtag.substring(newEtag.indexOf('\"'), newEtag.lastIndexOf('\"') + 1);
Logger.dev("New ETAG: " + newEtag);
if (page < etags.size()) {
etags.set(page, newEtag);
} else {
etags.add(newEtag);
}
// Update ETAG
etag = header.get(ETAG_KEY);
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
newEtags.add(etag);
String links = header.get(LINK_KEY);
if (links != null) {
if (mode == CHECK_ETAG || mode == LOAD_NEXT) {
// Try to check next page URL
url = null;
for (String s : links.split(", ")) {
if (s.contains("next")) {
url = s.substring(s.indexOf("<") + 1, s.indexOf(">; "));
break;
}
}
if (url != null) {
loadPage(page + 1, url, LOAD_NEXT);
}
}
if (mode == CHECK_ETAG || mode == LOAD_PREV) {
// Try to check prev page URL
url = null;
for (String s : links.split(", ")) {
if (s.contains("prev")) {
url = s.substring(s.indexOf("<") + 1, s.indexOf(">; "));
break;
}
}
if (url != null) {
loadPage(page - 1, url, LOAD_PREV);
for (String s : links.split(", ")) {
if (mode != LOAD_PREV && s.contains("next")) {
// Force load all next pages
loadPage(page + 1, LOAD_NEXT);
} else if (mode != LOAD_NEXT && s.contains("prev")) {
// Back propagation
loadPage(page - 1, LOAD_PREV);
}
}
}
return true;
}
private Void waitTasks() {
while (tasks > 0) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Void... values) {
if (ReposFragment.adapter != null)
ReposFragment.adapter.notifyDBChanged();
}
@Override
protected Void doInBackground(Void... voids) {
Logger.dev("UpdateRepos: Loading repos");
etags = new ArrayList<>(Arrays.asList(prefs.getString(ETAG_KEY, "").split(",")));
cached = repoDB.getRepoIDList();
if (!loadPage(0, null, CHECK_ETAG)) {
Logger.dev("UpdateRepos: No updates, use DB");
return null;
if (!loadPage(0, CHECK_ETAG)) {
// Nothing changed online
if (forceUpdate) {
for (String id : cached) {
if (id == null) continue;
++tasks;
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
Repo repo = repoDB.getRepo(id);
try {
repo.update();
repoDB.addRepo(repo);
} catch (Repo.IllegalRepoException e) {
Logger.error(e.getMessage());
repoDB.removeRepo(repo);
}
--tasks;
});
}
}
return waitTasks();
}
// Wait till all tasks are done
waitTasks();
// The leftover cached means they are removed from online repo
repoDB.removeRepo(cached);
// Update ETag
StringBuilder etagBuilder = new StringBuilder();
for (int i = 0; i < etags.size(); ++i) {
for (int i = 0; i < newEtags.size(); ++i) {
if (i != 0) etagBuilder.append(",");
etagBuilder.append(etags.get(i));
etagBuilder.append(newEtags.get(i));
}
prefs.edit().putString(ETAG_KEY, etagBuilder.toString()).apply();
Logger.dev("UpdateRepos: Done");
return null;
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils;
package com.topjohnwu.magisk.container;
import android.support.v7.widget.RecyclerView;

View File

@@ -1,17 +1,16 @@
package com.topjohnwu.magisk.module;
package com.topjohnwu.magisk.container;
import android.content.ContentValues;
import android.database.Cursor;
import android.support.annotation.NonNull;
import com.topjohnwu.magisk.utils.Logger;
import java.util.List;
public abstract class BaseModule implements Comparable<BaseModule> {
private String mId, mName, mVersion, mAuthor, mDescription;
private int mVersionCode = 0, templateVersion = 0;
private String mId = null, mName, mVersion, mAuthor, mDescription;
private int mVersionCode = -1, templateVersion = -1;
protected BaseModule() {}
@@ -25,9 +24,21 @@ public abstract class BaseModule implements Comparable<BaseModule> {
templateVersion = c.getInt(c.getColumnIndex("template"));
}
protected void parseProps(List<String> props) throws CacheModException { parseProps(props.toArray(new String[props.size()])); }
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("id", mId);
values.put("name", mName);
values.put("version", mVersion);
values.put("versionCode", mVersionCode);
values.put("author", mAuthor);
values.put("description", mDescription);
values.put("template", templateVersion);
return values;
}
protected void parseProps(String[] props) throws CacheModException {
protected void parseProps(List<String> props) { parseProps(props.toArray(new String[0])); }
protected void parseProps(String[] props) throws NumberFormatException {
for (String line : props) {
String[] prop = line.split("=", 2);
if (prop.length != 2)
@@ -48,9 +59,7 @@ public abstract class BaseModule implements Comparable<BaseModule> {
mVersion = prop[1];
break;
case "versionCode":
try {
mVersionCode = Integer.parseInt(prop[1]);
} catch (NumberFormatException ignored) {}
mVersionCode = Integer.parseInt(prop[1]);
break;
case "author":
mAuthor = prop[1];
@@ -59,12 +68,7 @@ public abstract class BaseModule implements Comparable<BaseModule> {
mDescription = prop[1];
break;
case "template":
try {
templateVersion = Integer.parseInt(prop[1]);
} catch (NumberFormatException ignored) {}
case "cacheModule":
if (Boolean.parseBoolean(prop[1]))
throw new CacheModException(mId);
templateVersion = Integer.parseInt(prop[1]);
break;
default:
break;
@@ -108,12 +112,6 @@ public abstract class BaseModule implements Comparable<BaseModule> {
return templateVersion;
}
public static class CacheModException extends Exception {
public CacheModException(String id) {
Logger.error("Cache mods are no longer supported! id: " + id);
}
}
@Override
public int compareTo(@NonNull BaseModule module) {
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());

View File

@@ -0,0 +1,59 @@
package com.topjohnwu.magisk.container;
import android.support.annotation.NonNull;
import java.io.IOException;
import java.io.InputStream;
public class InputStreamWrapper extends InputStream {
private InputStream in;
public InputStreamWrapper(InputStream in) {
this.in = in;
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
@Override
public boolean markSupported() {
return in.markSupported();
}
@Override
public synchronized int read() throws IOException {
return in.read();
}
@Override
public int read(@NonNull byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
return in.read(b, off, len);
}
@Override
public synchronized void reset() throws IOException {
in.reset();
}
@Override
public long skip(long n) throws IOException {
return in.skip(n);
}
}

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.module;
package com.topjohnwu.magisk.container;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
@@ -9,9 +8,11 @@ public class Module extends BaseModule {
private String mRemoveFile, mDisableFile, mUpdateFile;
private boolean mEnable, mRemove, mUpdated;
public Module(Shell shell, String path) throws CacheModException {
public Module(Shell shell, String path) {
parseProps(Utils.readFile(shell, path + "/module.prop"));
try {
parseProps(Utils.readFile(shell, path + "/module.prop"));
} catch (NumberFormatException ignored) {}
mRemoveFile = path + "/remove";
mDisableFile = path + "/disable";
@@ -26,8 +27,6 @@ public class Module extends BaseModule {
setName(getId());
}
Logger.dev("Creating Module, id: " + getId());
mEnable = !Utils.itemExist(shell, mDisableFile);
mRemove = Utils.itemExist(shell, mRemoveFile);
mUpdated = Utils.itemExist(shell, mUpdateFile);

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.superuser;
package com.topjohnwu.magisk.container;
import android.content.ContentValues;
import android.content.pm.ApplicationInfo;

View File

@@ -1,22 +1,23 @@
package com.topjohnwu.magisk.module;
package com.topjohnwu.magisk.container;
import android.content.ContentValues;
import android.database.Cursor;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.WebService;
import java.util.Date;
public class Repo extends BaseModule {
public static final int MIN_TEMPLATE_VER = 4;
private static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
private static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
private String repoName;
private Date mLastUpdate;
public Repo(String name, Date lastUpdate) throws CacheModException {
public Repo(String name, Date lastUpdate) throws IllegalRepoException {
mLastUpdate = lastUpdate;
repoName = name;
update();
@@ -28,14 +29,27 @@ public class Repo extends BaseModule {
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
}
public void update() throws CacheModException {
public void update() throws IllegalRepoException {
String props = WebService.getString(getManifestUrl());
String lines[] = props.split("\\n");
parseProps(lines);
Logger.dev("Repo: Fetching prop: " + getId());
try {
parseProps(lines);
} catch (NumberFormatException e) {
throw new IllegalRepoException("Repo [" + repoName + "] parse error: " + e.getMessage());
}
if (getId() == null) {
throw new IllegalRepoException("Repo [" + repoName + "] does not contain id");
}
if (getVersionCode() < 0) {
throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
}
if (getTemplateVersion() < MIN_TEMPLATE_VER) {
throw new IllegalRepoException("Repo [" + repoName + "] is outdated");
}
}
public boolean update(Date lastUpdate) throws CacheModException {
public boolean update(Date lastUpdate) throws IllegalRepoException {
if (lastUpdate.after(mLastUpdate)) {
mLastUpdate = lastUpdate;
update();
@@ -45,19 +59,16 @@ public class Repo extends BaseModule {
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("id", getId());
values.put("name", getName());
values.put("version", getVersion());
values.put("versionCode", getVersionCode());
values.put("author", getAuthor());
values.put("description", getDescription());
ContentValues values = super.getContentValues();
values.put("repo_name", repoName);
values.put("last_update", mLastUpdate.getTime());
values.put("template", getTemplateVersion());
return values;
}
public String getRepoName() {
return repoName;
}
public String getZipUrl() {
return String.format(ZIP_URL, repoName);
}
@@ -73,4 +84,10 @@ public class Repo extends BaseModule {
public Date getLastUpdate() {
return mLastUpdate;
}
public class IllegalRepoException extends Exception {
IllegalRepoException(String message) {
super(message);
}
}
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.superuser;
package com.topjohnwu.magisk.container;
import android.content.ContentValues;
import android.database.Cursor;

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils;
package com.topjohnwu.magisk.container;
import org.kamranzafar.jtar.TarHeader;

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils;
package com.topjohnwu.magisk.container;
import android.support.annotation.NonNull;

View File

@@ -6,8 +6,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.container.Repo;
import com.topjohnwu.magisk.utils.Utils;
import java.util.LinkedList;
@@ -17,7 +16,6 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 2;
private static final String TABLE_NAME = "repos";
private static final int MIN_TEMPLATE_VER = 3;
private SQLiteDatabase mDb;
private MagiskManager mm;
@@ -26,6 +24,10 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
super(context, "repo.db", null, DATABASE_VER);
mDb = getWritableDatabase();
mm = Utils.getMagiskManager(context);
// Clear bad repos
mDb.delete(TABLE_NAME, "template<?",
new String[] { String.valueOf(Repo.MIN_TEMPLATE_VER) });
}
@Override
@@ -53,9 +55,18 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
mDb.delete(TABLE_NAME, null, null);
}
public void removeRepo(String id) {
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
}
public void removeRepo(Repo repo) {
mDb.delete(TABLE_NAME, "repo_name=?", new String[] { repo.getRepoName() });
}
public void removeRepo(List<String> list) {
for (String id : list) {
Logger.dev("Remove from DB: " + id);
if (id == null) continue;
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
}
}
@@ -74,7 +85,9 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
}
public Cursor getRepoCursor() {
return mDb.query(TABLE_NAME, null, "template>=? AND template<=?", new String[] { String.valueOf(MIN_TEMPLATE_VER), String.valueOf(mm.magiskVersionCode) }, null, null, "name COLLATE NOCASE");
return mDb.query(TABLE_NAME, null, "template<=?",
new String[] { String.valueOf(mm.magiskVersionCode) },
null, null, "name COLLATE NOCASE");
}
public List<String> getRepoIDList() {

View File

@@ -10,8 +10,8 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.container.SuLogEntry;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
@@ -39,18 +39,19 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
public static final int NAMESPACE_MODE_REQUESTER = 1;
public static final int NAMESPACE_MODE_ISOLATE = 2;
public static final String DB_NAME = "su.db";
private static final int DATABASE_VER = 3;
private static final String POLICY_TABLE = "policies";
private static final String LOG_TABLE = "logs";
private static final String SETTINGS_TABLE = "settings";
private MagiskManager magiskManager;
private MagiskManager mm;
private PackageManager pm;
private SQLiteDatabase mDb;
public SuDatabaseHelper(Context context) {
super(context, "su.db", null, DATABASE_VER);
magiskManager = Utils.getMagiskManager(context);
super(context, DB_NAME, null, DATABASE_VER);
mm = Utils.getMagiskManager(context);
pm = context.getPackageManager();
mDb = getWritableDatabase();
cleanup();
@@ -81,10 +82,10 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
"FROM " + POLICY_TABLE + "_old");
db.execSQL("DROP TABLE " + POLICY_TABLE + "_old");
File oldDB = magiskManager.getDatabasePath("sulog.db");
File oldDB = mm.getDatabasePath("sulog.db");
if (oldDB.exists()) {
migrateLegacyLogList(oldDB, db);
magiskManager.deleteDatabase("sulog.db");
mm.deleteDatabase("sulog.db");
}
++oldVersion;
}
@@ -120,7 +121,7 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
new String[] { String.valueOf(System.currentTimeMillis() / 1000) });
// Clear outdated logs
mDb.delete(LOG_TABLE, "time < ?", new String[] { String.valueOf(
System.currentTimeMillis() - magiskManager.suLogTimeout * 86400000) });
System.currentTimeMillis() - mm.suLogTimeout * 86400000) });
}
public void deletePolicy(Policy policy) {
@@ -178,7 +179,7 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
Policy policy = new Policy(c, pm);
// The application changed UID for some reason, check user config
if (policy.info.uid != policy.uid) {
if (magiskManager.suReauth) {
if (mm.suReauth) {
// Reauth required, remove from DB
deletePolicy(policy);
continue;

View File

@@ -5,7 +5,7 @@ import android.content.Context;
import android.content.Intent;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.utils.Utils;
public class PackageReceiver extends BroadcastReceiver {

View File

@@ -9,6 +9,8 @@ import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.container.SuLogEntry;
import java.util.Date;

View File

@@ -21,6 +21,7 @@ import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.container.Policy;
import java.io.DataInputStream;
import java.io.IOException;

View File

@@ -8,8 +8,8 @@ import java.util.Locale;
public class Logger {
public static final String MAIN_TAG = "Magisk";
public static final String DEBUG_TAG = "MagiskManager";
private static final boolean SHELL_LOGGING = false;
public static void debug(String line) {
Log.d(DEBUG_TAG, "DEBUG: " + line);
@@ -20,25 +20,15 @@ public class Logger {
}
public static void error(String line) {
Log.e(MAIN_TAG, "MANAGERERROR: " + line);
Log.e(DEBUG_TAG, "ERROR: " + line);
}
public static void error(String fmt, Object... args) {
error(String.format(Locale.US, fmt, args));
}
public static void dev(String line) {
if (MagiskManager.devLogging) {
Log.d(DEBUG_TAG, line);
}
}
public static void dev(String fmt, Object... args) {
dev(String.format(Locale.US, fmt, args));
}
public static void shell(String line) {
if (MagiskManager.shellLogging) {
if (SHELL_LOGGING) {
Log.d(DEBUG_TAG, "SHELL: " + line);
}
}

View File

@@ -1,114 +0,0 @@
package com.topjohnwu.magisk.utils;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Base64;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.safetynet.SafetyNet;
import com.topjohnwu.magisk.R;
import org.json.JSONException;
import org.json.JSONObject;
import java.security.SecureRandom;
public abstract class SafetyNetHelper
implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
private static boolean isRunning = false;
private GoogleApiClient mGoogleApiClient;
private Result ret;
protected FragmentActivity mActivity;
public SafetyNetHelper(FragmentActivity activity) {
ret = new Result();
mActivity = activity;
}
// Entry point to start test
public void requestTest() {
if (isRunning)
return;
// Connect Google Service
mGoogleApiClient = new GoogleApiClient.Builder(mActivity)
.enableAutoManage(mActivity, this)
.addApi(SafetyNet.API)
.addConnectionCallbacks(this)
.build();
mGoogleApiClient.connect();
isRunning = true;
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult result) {
Logger.dev("SN: Google API fail");
ret.errmsg = result.getErrorMessage();
handleResults(ret);
}
@Override
public void onConnectionSuspended(int i) {
Logger.dev("SN: Google API Suspended");
switch (i) {
case CAUSE_NETWORK_LOST:
ret.errmsg = mActivity.getString(R.string.safetyNet_network_loss);
break;
case CAUSE_SERVICE_DISCONNECTED:
ret.errmsg = mActivity.getString(R.string.safetyNet_service_disconnected);
break;
}
handleResults(ret);
}
@Override
public void onConnected(@Nullable Bundle bundle) {
Logger.dev("SN: Google API Connected");
// Create nonce
byte[] nonce = new byte[24];
new SecureRandom().nextBytes(nonce);
Logger.dev("SN: Check with nonce: " + Base64.encodeToString(nonce, Base64.DEFAULT));
// Call SafetyNet
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
.setResultCallback(result -> {
Status status = result.getStatus();
if (status.isSuccess()) {
String json = new String(Base64.decode(result.getJwsResult().split("\\.")[1], Base64.DEFAULT));
Logger.dev("SN: Response: " + json);
try {
JSONObject decoded = new JSONObject(json);
ret.ctsProfile = decoded.getBoolean("ctsProfileMatch");
ret.basicIntegrity = decoded.getBoolean("basicIntegrity");
ret.failed = false;
} catch (JSONException e) {
ret.errmsg = mActivity.getString(R.string.safetyNet_res_invalid);
}
} else {
Logger.dev("SN: No response");
ret.errmsg = mActivity.getString(R.string.safetyNet_no_response);
}
// Disconnect
mGoogleApiClient.stopAutoManage(mActivity);
mGoogleApiClient.disconnect();
isRunning = false;
handleResults(ret);
});
}
// Callback function to save the results
public abstract void handleResults(Result result);
public static class Result {
public boolean failed = true;
public String errmsg;
public boolean ctsProfile = false;
public boolean basicIntegrity = false;
}
}

View File

@@ -9,6 +9,7 @@ import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
@@ -132,6 +133,19 @@ public class Shell {
return rootStatus > 0;
}
public void loadInputStream(InputStream in) {
try {
int read;
byte[] bytes = new byte[4096];
while ((read = in.read(bytes)) != -1) {
STDIN.write(bytes, 0, read);
}
STDIN.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public List<String> sh(String... commands) {
List<String> res = new ArrayList<>();
if (!isValid) return res;

View File

@@ -31,15 +31,19 @@ public class Topic {
}
public void publish() {
publish(true);
publish(true, null);
}
public void publish(boolean record) {
publish(record, null);
}
public void publish(boolean record, Object result) {
hasPublished = record;
if (subscribers != null) {
for (WeakReference<Subscriber> subscriber : subscribers) {
if (subscriber.get() != null)
subscriber.get().onTopicPublished(this);
subscriber.get().onTopicPublished(this, result);
}
}
}
@@ -60,9 +64,12 @@ public class Topic {
}
}
default void onTopicPublished() {
onTopicPublished(null);
onTopicPublished(null, null);
}
void onTopicPublished(Topic topic);
default void onTopicPublished(Topic topic) {
onTopicPublished(topic, null);
}
void onTopicPublished(Topic topic, Object result);
Topic[] getSubscription();
}
}

View File

@@ -5,7 +5,7 @@ import android.app.Activity;
import android.app.DownloadManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -16,13 +16,12 @@ import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.CountDownTimer;
import android.os.Build;
import android.os.Environment;
import android.provider.OpenableColumns;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.ContextCompat;
@@ -150,16 +149,6 @@ public class Utils {
return (MagiskManager) context.getApplicationContext();
}
public static void checkSafetyNet(FragmentActivity activity) {
new SafetyNetHelper(activity) {
@Override
public void handleResults(Result result) {
getMagiskManager(mActivity).SNCheckResult = result;
getMagiskManager(mActivity).safetyNetDone.publish(false);
}
}.requestTest();
}
public static void clearRepoCache(Context context) {
MagiskManager mm = getMagiskManager(context);
mm.prefs.edit().remove(UpdateRepos.ETAG_KEY).apply();
@@ -332,101 +321,137 @@ public class Utils {
MagiskManager mm = getMagiskManager(fragment.getActivity());
String filename = getLegalFilename("Magisk-v" + mm.remoteMagiskVersionString + ".zip");
new AlertDialogBuilder(fragment.getActivity())
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)))
.setMessage(mm.getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.setPositiveButton(R.string.install, (d, i) -> {
List<String> options = new ArrayList<>();
options.add(mm.getString(R.string.download_zip_only));
options.add(mm.getString(R.string.patch_boot_file));
if (Shell.rootAccess()) {
options.add(mm.getString(R.string.direct_install));
}
new AlertDialog.Builder(fragment.getActivity())
.setTitle(R.string.select_method)
.setItems(
options.toArray(new String [0]),
(dialog, idx) -> {
DownloadReceiver receiver = null;
switch (idx) {
case 1:
if (mm.remoteMagiskVersionCode < 1400) {
mm.toast(R.string.no_boot_file_patch_support, Toast.LENGTH_LONG);
return;
}
mm.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
fragment.startActivityForResult(intent, SELECT_BOOT_IMG,
(requestCode, resultCode, data) -> {
if (requestCode == SELECT_BOOT_IMG
&& resultCode == Activity.RESULT_OK && data != null) {
dlAndReceive(
fragment.getActivity(),
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Intent intent = new Intent(mm, FlashActivity.class);
intent.setData(uri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(FlashActivity.SET_BOOT, data.getData())
.putExtra(FlashActivity.SET_ENC, enc)
.putExtra(FlashActivity.SET_VERITY, verity)
.putExtra(FlashActivity.SET_ACTION, FlashActivity.PATCH_BOOT);
mm.startActivity(intent);
}
},
mm.magiskLink,
filename
);
}
});
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)))
.setMessage(mm.getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.setPositiveButton(R.string.install, (d, i) -> {
List<String> options = new ArrayList<>();
options.add(mm.getString(R.string.download_zip_only));
options.add(mm.getString(R.string.patch_boot_file));
if (Shell.rootAccess()) {
options.add(mm.getString(R.string.direct_install));
}
List<String> res = Shell.getShell(mm).su("echo $SLOT");
if (isValidShellResponse(res)) {
options.add(mm.getString(R.string.install_second_slot));
}
char[] slot = isValidShellResponse(res) ? res.get(0).toCharArray() : null;
new AlertDialog.Builder(fragment.getActivity())
.setTitle(R.string.select_method)
.setItems(
options.toArray(new String [0]),
(dialog, idx) -> {
String boot;
DownloadReceiver receiver = null;
switch (idx) {
case 1:
if (mm.remoteMagiskVersionCode < 1400) {
mm.toast(R.string.no_boot_file_patch_support, Toast.LENGTH_LONG);
return;
case 0:
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
showUriSnack(fragment.getActivity(), uri);
}
mm.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
fragment.startActivityForResult(intent, SELECT_BOOT_IMG,
(requestCode, resultCode, data) -> {
if (requestCode == SELECT_BOOT_IMG
&& resultCode == Activity.RESULT_OK && data != null) {
dlAndReceive(
fragment.getActivity(),
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Intent intent = new Intent(mm, FlashActivity.class);
intent.setData(uri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(FlashActivity.SET_BOOT, data.getData())
.putExtra(FlashActivity.SET_ENC, enc)
.putExtra(FlashActivity.SET_VERITY, verity)
.putExtra(FlashActivity.SET_ACTION, FlashActivity.PATCH_BOOT);
mm.startActivity(intent);
}
},
mm.magiskLink,
filename
);
}
};
break;
case 2:
final String boot = fragment.getSelectedBootImage();
if (boot == null)
return;
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Intent intent = new Intent(mm, FlashActivity.class);
intent.setData(uri)
});
return;
case 0:
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
showUriSnack(fragment.getActivity(), uri);
}
};
break;
case 2:
boot = fragment.getSelectedBootImage();
if (boot == null)
return;
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Intent intent = new Intent(mm, FlashActivity.class);
intent.setData(uri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(FlashActivity.SET_BOOT, boot)
.putExtra(FlashActivity.SET_ENC, enc)
.putExtra(FlashActivity.SET_VERITY, verity)
.putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_MAGISK);
mm.startActivity(intent);
}
};
break;
case 3:
assert (slot != null);
// Choose the other slot
if (slot[1] == 'a') slot[1] = 'b';
else slot[1] = 'a';
// Then find the boot image again
List<String> ret = Shell.getShell(mm).su(
"BOOTIMAGE=",
"SLOT=" + String.valueOf(slot),
"find_boot_image",
"echo \"$BOOTIMAGE\""
);
boot = isValidShellResponse(ret) ? ret.get(ret.size() - 1) : null;
Shell.getShell(mm).su_raw("mount_partitions");
if (boot == null)
return;
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Intent intent = new Intent(mm, FlashActivity.class);
intent.setData(uri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(FlashActivity.SET_BOOT, boot)
.putExtra(FlashActivity.SET_ENC, enc)
.putExtra(FlashActivity.SET_VERITY, verity)
.putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_MAGISK);
mm.startActivity(intent);
}
};
break;
}
Utils.dlAndReceive(
mm,
receiver,
mm.magiskLink,
filename
);
mm.startActivity(intent);
}
};
default:
}
).show();
})
.setNeutralButton(R.string.release_notes, (d, i) -> {
if (mm.releaseNoteLink != null) {
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.releaseNoteLink));
openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mm.startActivity(openLink);
}
})
.setNegativeButton(R.string.no_thanks, null)
.show();
Utils.dlAndReceive(
fragment.getActivity(),
receiver,
mm.magiskLink,
filename
);
}
).show();
})
.setNeutralButton(R.string.release_notes, (d, i) -> {
if (mm.releaseNoteLink != null) {
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.releaseNoteLink));
openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mm.startActivity(openLink);
}
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
public static void showManagerInstallDialog(Activity activity) {
@@ -450,58 +475,60 @@ public class Utils {
public static void showUninstallDialog(MagiskFragment fragment) {
MagiskManager mm = Utils.getMagiskManager(fragment.getActivity());
new AlertDialogBuilder(fragment.getActivity())
.setTitle(R.string.uninstall_magisk_title)
.setMessage(R.string.uninstall_magisk_msg)
.setPositiveButton(R.string.complete_uninstall, (d, i) -> {
try {
InputStream in = mm.getAssets().open(UNINSTALLER);
File uninstaller = new File(mm.getCacheDir(), UNINSTALLER);
FileOutputStream out = new FileOutputStream(uninstaller);
byte[] bytes = new byte[1024];
int read;
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
in.close();
out.close();
in = mm.getAssets().open(UTIL_FUNCTIONS);
File utils = new File(mm.getCacheDir(), UTIL_FUNCTIONS);
out = new FileOutputStream(utils);
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
in.close();
out.close();
ProgressDialog progress = new ProgressDialog(fragment.getActivity());
progress.setTitle(R.string.reboot);
progress.show();
new CountDownTimer(5000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
progress.setMessage(mm.getString(R.string.reboot_countdown,
millisUntilFinished / 1000));
}
@Override
public void onFinish() {
progress.setMessage(mm.getString(R.string.reboot_countdown, 0));
Shell.getShell(mm).su_raw(
"mv -f " + uninstaller + " /cache/" + UNINSTALLER,
"mv -f " + utils + " /data/magisk/" + UTIL_FUNCTIONS,
"reboot"
);
}
}.start();
} catch (IOException e) {
e.printStackTrace();
.setTitle(R.string.uninstall_magisk_title)
.setMessage(R.string.uninstall_magisk_msg)
.setPositiveButton(R.string.complete_uninstall, (d, i) -> {
try {
InputStream in = mm.getAssets().open(UNINSTALLER);
File uninstaller = new File(mm.getCacheDir(), UNINSTALLER);
FileOutputStream out = new FileOutputStream(uninstaller);
byte[] bytes = new byte[1024];
int read;
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
})
.setNeutralButton(R.string.restore_stock_boot, (d, i) -> {
String boot = fragment.getSelectedBootImage();
if (boot == null) return;
new RestoreStockBoot(mm, boot).exec();
})
.setNegativeButton(R.string.no_thanks, null)
.show();
in.close();
out.close();
in = mm.getAssets().open(UTIL_FUNCTIONS);
File utils = new File(mm.getCacheDir(), UTIL_FUNCTIONS);
out = new FileOutputStream(utils);
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
in.close();
out.close();
Shell.getShell(mm).su(
"cat " + uninstaller + " > /cache/" + UNINSTALLER,
"cat " + utils + " > /data/magisk/" + UTIL_FUNCTIONS
);
mm.toast(R.string.uninstall_toast, Toast.LENGTH_LONG);
Shell.getShell(mm).su_raw(
"sleep 5",
"pm uninstall " + mm.getApplicationInfo().packageName
);
} catch (IOException e) {
e.printStackTrace();
}
})
.setNeutralButton(R.string.restore_stock_boot, (d, i) -> {
String boot = fragment.getSelectedBootImage();
if (boot == null) return;
new RestoreStockBoot(mm, boot).exec();
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
public static boolean useFDE(Context context) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& context.getSystemService(DevicePolicyManager.class).getStorageEncryptionStatus()
== DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
}
public static Context getEncContext(Context context) {
if (useFDE(context))
return context.createDeviceProtectedStorageContext();
else
return context;
}
}

View File

@@ -2,57 +2,50 @@ package com.topjohnwu.magisk.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
public class WebService {
public final static int GET = 1;
public final static int POST = 2;
public static String getString(String url) {
return getString(url, null);
}
public static String getString(String url, Map<String, String> header) {
InputStream in = request(GET, url, header);
if (in == null) return "";
BufferedReader br = new BufferedReader(new InputStreamReader(in));
int len;
StringBuilder builder = new StringBuilder();
char buf[] = new char[4096];
try {
while ((len = br.read(buf)) != -1) {
builder.append(buf, 0, len);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return builder.toString();
HttpURLConnection conn = request(url, header);
if (conn == null) return "";
return getString(conn);
}
public static InputStream request(int method, String address, Map<String, String> header) {
Logger.dev("WebService: Service call " + address);
public static String getString(HttpURLConnection conn) {
try {
StringBuilder builder = new StringBuilder();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
int len;
char buf[] = new char[4096];
while ((len = br.read(buf)) != -1) {
builder.append(buf, 0, len);
}
}
conn.disconnect();
return builder.toString();
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
public static HttpURLConnection request(String address, Map<String, String> header) {
try {
URL url = new URL(address);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(15000);
conn.setConnectTimeout(15000);
conn.setDoInput(true);
if (method == POST) {
conn.setRequestMethod("POST");
} else if (method == GET) {
conn.setRequestMethod("GET");
}
if (header != null) {
for (Map.Entry<String, String> entry : header.entrySet()) {
@@ -60,20 +53,20 @@ public class WebService {
}
}
if (conn.getResponseCode() == HttpsURLConnection.HTTP_OK) {
if (header != null) {
header.clear();
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
List<String> l = entry.getValue();
header.put(entry.getKey(), l.get(l.size() - 1));
}
conn.connect();
if (header != null) {
header.clear();
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
List<String> l = entry.getValue();
header.put(entry.getKey(), l.get(l.size() - 1));
}
return conn.getInputStream();
}
return conn;
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
}

View File

@@ -1,184 +1,30 @@
package com.topjohnwu.magisk.utils;
import android.content.Context;
import android.text.TextUtils;
import android.content.res.AssetManager;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.encoders.Base64;
import com.topjohnwu.jarsigner.JarMap;
import com.topjohnwu.jarsigner.SignAPK;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
/*
* Modified from from AOSP(Marshmallow) SignAPK.java
* */
public class ZipUtils {
// File name in assets
private static final String PUBLIC_KEY_NAME = "public.certificate.x509.pem";
private static final String PRIVATE_KEY_NAME = "private.key.pk8";
private static final String UNHIDE_APK = "unhide.apk";
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
private static final String CERT_SIG_NAME = "META-INF/CERT.%s";
private static final String ANDROID_MANIFEST = "AndroidManifest.xml";
private static final byte[] UNHIDE_PKG_NAME = "com.topjohnwu.unhide\0".getBytes();
private static Provider sBouncyCastleProvider;
// bitmasks for which hash algorithms we need the manifest to include.
private static final int USE_SHA1 = 1;
private static final int USE_SHA256 = 2;
static {
sBouncyCastleProvider = new BouncyCastleProvider();
Security.insertProviderAt(sBouncyCastleProvider, 1);
System.loadLibrary("zipadjust");
}
public native static void zipAdjust(String filenameIn, String filenameOut);
public static String generateUnhide(Context context, File output) {
File temp = new File(context.getCacheDir(), "temp.apk");
String pkg = "";
try {
JarInputStream source = new JarInputStream(context.getAssets().open(UNHIDE_APK));
JarOutputStream dest = new JarOutputStream(new FileOutputStream(temp));
JarEntry entry;
int size;
byte buffer[] = new byte[4096];
while ((entry = source.getNextJarEntry()) != null) {
dest.putNextEntry(new JarEntry(entry.getName()));
if (TextUtils.equals(entry.getName(), ANDROID_MANIFEST)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while((size = source.read(buffer)) != -1) {
baos.write(buffer, 0, size);
}
int offset = -1;
byte xml[] = baos.toByteArray();
// Linear search pattern offset
for (int i = 0; i < xml.length - UNHIDE_PKG_NAME.length; ++i) {
boolean match = true;
for (int j = 0; j < UNHIDE_PKG_NAME.length; ++j) {
if (xml[i + j] != UNHIDE_PKG_NAME[j]) {
match = false;
break;
}
}
if (match) {
offset = i;
break;
}
}
if (offset < 0)
return "";
// Patch binary XML with new package name
pkg = Utils.genPackageName("com.", UNHIDE_PKG_NAME.length - 1);
System.arraycopy(pkg.getBytes(), 0, xml, offset, pkg.length());
dest.write(xml);
} else {
while((size = source.read(buffer)) != -1) {
dest.write(buffer, 0, size);
}
}
}
source.close();
dest.close();
signZip(context, temp, output, false);
temp.delete();
} catch (IOException e) {
e.printStackTrace();
return pkg;
}
return pkg;
}
public static void removeTopFolder(InputStream in, File output) throws IOException {
try {
JarInputStream source = new JarInputStream(in);
JarOutputStream dest = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output)));
JarEntry entry;
String path;
int size;
byte buffer[] = new byte[4096];
while ((entry = source.getNextJarEntry()) != null) {
// Remove the top directory from the path
path = entry.getName().substring(entry.getName().indexOf("/") + 1);
// If it's the top folder, ignore it
if (path.isEmpty()) {
continue;
}
// Don't include placeholder
if (path.equals("system/placeholder")) {
continue;
}
dest.putNextEntry(new JarEntry(path));
while((size = source.read(buffer)) != -1) {
dest.write(buffer, 0, size);
}
}
source.close();
dest.close();
in.close();
} catch (IOException e) {
Logger.dev("ZipUtils: removeTopFolder IO error!");
throw e;
}
}
public static void unzip(File zip, File folder, String path, boolean junkPath) throws Exception {
InputStream in = new BufferedInputStream(new FileInputStream(zip));
unzip(in, folder, path, junkPath);
@@ -201,7 +47,6 @@ public class ZipUtils {
} else {
name = entry.getName();
}
Logger.dev("ZipUtils: Extracting: " + entry);
File dest = new File(folder, name);
dest.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(dest);
@@ -218,478 +63,18 @@ public class ZipUtils {
}
}
public static void signZip(Context context, File input, File output, boolean minSign) {
int alignment = 4;
JarFile inputJar = null;
BufferedOutputStream outputFile = null;
int hashes = 0;
try {
X509Certificate publicKey = readPublicKey(context.getAssets().open(PUBLIC_KEY_NAME));
hashes |= getDigestAlgorithm(publicKey);
// Set the ZIP file timestamp to the starting valid time
// of the 0th certificate plus one hour (to match what
// we've historically done).
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
PrivateKey privateKey = readPrivateKey(context.getAssets().open(PRIVATE_KEY_NAME));
outputFile = new BufferedOutputStream(new FileOutputStream(output));
if (minSign) {
ZipUtils.signWholeFile(input, publicKey, privateKey, outputFile);
} else {
inputJar = new JarFile(input, false); // Don't verify.
JarOutputStream outputJar = new JarOutputStream(outputFile);
// For signing .apks, use the maximum compression to make
// them as small as possible (since they live forever on
// the system partition). For OTA packages, use the
// default compression level, which is much much faster
// and produces output that is only a tiny bit larger
// (~0.1% on full OTA packages I tested).
outputJar.setLevel(9);
Manifest manifest = addDigestsToManifest(inputJar, hashes);
copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
signFile(manifest, inputJar, publicKey, privateKey, outputJar);
outputJar.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputJar != null) inputJar.close();
if (outputFile != null) outputFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void signZip(Context context, InputStream is, File output, boolean minSign) throws Exception {
signZip(context, new JarMap(is, false), output, minSign);
}
/**
* Return one of USE_SHA1 or USE_SHA256 according to the signature
* algorithm specified in the cert.
*/
private static int getDigestAlgorithm(X509Certificate cert) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
if ("SHA1WITHRSA".equals(sigAlg) ||
"MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above.
return USE_SHA1;
} else if (sigAlg.startsWith("SHA256WITH")) {
return USE_SHA256;
} else {
throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
"\" in cert [" + cert.getSubjectDN());
}
}
/** Returns the expected signature algorithm for this key type. */
private static String getSignatureAlgorithm(X509Certificate cert) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
if ("RSA".equalsIgnoreCase(keyType)) {
if (getDigestAlgorithm(cert) == USE_SHA256) {
return "SHA256withRSA";
} else {
return "SHA1withRSA";
}
} else if ("EC".equalsIgnoreCase(keyType)) {
return "SHA256withECDSA";
} else {
throw new IllegalArgumentException("unsupported key type: " + keyType);
}
}
// Files matching this pattern are not copied to the output.
private static Pattern stripPattern =
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
private static X509Certificate readPublicKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(input);
} finally {
input.close();
}
public static void signZip(Context context, File input, File output, boolean minSign) throws Exception {
signZip(context, new JarMap(input, false), output, minSign);
}
/** Read a PKCS#8 format private key. */
private static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
byte[] buffer = new byte[4096];
int size = input.read(buffer);
byte[] bytes = Arrays.copyOf(buffer, size);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
/**
* Add the hash(es) of every file to the manifest, creating it if
* necessary.
*/
private static Manifest addDigestsToManifest(JarFile jar, int hashes)
throws IOException, GeneralSecurityException {
Manifest input = jar.getManifest();
Manifest output = new Manifest();
Attributes main = output.getMainAttributes();
if (input != null) {
main.putAll(input.getMainAttributes());
} else {
main.putValue("Manifest-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
}
MessageDigest md_sha1 = null;
MessageDigest md_sha256 = null;
if ((hashes & USE_SHA1) != 0) {
md_sha1 = MessageDigest.getInstance("SHA1");
}
if ((hashes & USE_SHA256) != 0) {
md_sha256 = MessageDigest.getInstance("SHA256");
}
byte[] buffer = new byte[4096];
int num;
// We sort the input entries by name, and add them to the
// output manifest in sorted order. We expect that the output
// map will be deterministic.
TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
JarEntry entry = e.nextElement();
byName.put(entry.getName(), entry);
}
for (JarEntry entry: byName.values()) {
String name = entry.getName();
if (!entry.isDirectory() &&
(stripPattern == null || !stripPattern.matcher(name).matches())) {
InputStream data = jar.getInputStream(entry);
while ((num = data.read(buffer)) > 0) {
if (md_sha1 != null) md_sha1.update(buffer, 0, num);
if (md_sha256 != null) md_sha256.update(buffer, 0, num);
}
Attributes attr = null;
if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes();
if (md_sha1 != null) {
attr.putValue("SHA1-Digest",
new String(Base64.encode(md_sha1.digest()), "ASCII"));
}
if (md_sha256 != null) {
attr.putValue("SHA-256-Digest",
new String(Base64.encode(md_sha256.digest()), "ASCII"));
}
output.getEntries().put(name, attr);
}
}
return output;
}
/** Write to another stream and track how many bytes have been
* written.
*/
private static class CountOutputStream extends FilterOutputStream {
private int mCount;
public CountOutputStream(OutputStream out) {
super(out);
mCount = 0;
}
@Override
public void write(int b) throws IOException {
super.write(b);
mCount++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
mCount += len;
}
public int size() {
return mCount;
}
}
/** Write a .SF file with a digest of the specified manifest. */
private static void writeSignatureFile(Manifest manifest, OutputStream out,
int hash)
throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
Attributes main = sf.getMainAttributes();
main.putValue("Signature-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
MessageDigest md = MessageDigest.getInstance(
hash == USE_SHA256 ? "SHA256" : "SHA1");
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
true, "UTF-8");
// Digest of the entire manifest
manifest.write(print);
print.flush();
main.putValue(hash == USE_SHA256 ? "SHA-256-Digest-Manifest" : "SHA1-Digest-Manifest",
new String(Base64.encode(md.digest()), "ASCII"));
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
print.print("Name: " + entry.getKey() + "\r\n");
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
}
print.print("\r\n");
print.flush();
Attributes sfAttr = new Attributes();
sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest-Manifest",
new String(Base64.encode(md.digest()), "ASCII"));
sf.getEntries().put(entry.getKey(), sfAttr);
}
CountOutputStream cout = new CountOutputStream(out);
sf.write(cout);
// A bug in the java.util.jar implementation of Android platforms
// up to version 1.6 will cause a spurious IOException to be thrown
// if the length of the signature file is a multiple of 1024 bytes.
// As a workaround, add an extra CRLF in this case.
if ((cout.size() % 1024) == 0) {
cout.write('\r');
cout.write('\n');
}
}
/** Sign data and write the digital signature to 'out'. */
private static void writeSignatureBlock(
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
OutputStream out)
throws IOException,
CertificateEncodingException,
OperatorCreationException,
CMSException {
ArrayList<X509Certificate> certList = new ArrayList<>(1);
certList.add(publicKey);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))
.setProvider(sBouncyCastleProvider)
.build(privateKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder()
.setProvider(sBouncyCastleProvider)
.build())
.setDirectSignature(true)
.build(signer, publicKey));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(data, false);
ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
DEROutputStream dos = new DEROutputStream(out);
dos.writeObject(asn1.readObject());
}
/**
* Copy all the files in a manifest from input to output. We set
* the modification times in the output to a fixed time, so as to
* reduce variation in the output file and make incremental OTAs
* more efficient.
*/
private static void copyFiles(Manifest manifest, JarFile in, JarOutputStream out,
long timestamp, int alignment) throws IOException {
byte[] buffer = new byte[4096];
int num;
Map<String, Attributes> entries = manifest.getEntries();
ArrayList<String> names = new ArrayList<String>(entries.keySet());
Collections.sort(names);
boolean firstEntry = true;
long offset = 0L;
// We do the copy in two passes -- first copying all the
// entries that are STORED, then copying all the entries that
// have any other compression flag (which in practice means
// DEFLATED). This groups all the stored entries together at
// the start of the file and makes it easier to do alignment
// on them (since only stored entries are aligned).
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
JarEntry outEntry = null;
if (inEntry.getMethod() != JarEntry.STORED) continue;
// Preserve the STORED method of the input entry.
outEntry = new JarEntry(inEntry);
outEntry.setTime(timestamp);
// 'offset' is the offset into the file at which we expect
// the file data to begin. This is the value we need to
// make a multiple of 'alignement'.
offset += JarFile.LOCHDR + outEntry.getName().length();
if (firstEntry) {
// The first entry in a jar file has an extra field of
// four bytes that you can't get rid of; any extra
// data you specify in the JarEntry is appended to
// these forced four bytes. This is JAR_MAGIC in
// JarOutputStream; the bytes are 0xfeca0000.
offset += 4;
firstEntry = false;
}
if (alignment > 0 && (offset % alignment != 0)) {
// Set the "extra data" of the entry to between 1 and
// alignment-1 bytes, to make the file data begin at
// an aligned offset.
int needed = alignment - (int)(offset % alignment);
outEntry.setExtra(new byte[needed]);
offset += needed;
}
out.putNextEntry(outEntry);
InputStream data = in.getInputStream(inEntry);
while ((num = data.read(buffer)) > 0) {
out.write(buffer, 0, num);
offset += num;
}
out.flush();
}
// Copy all the non-STORED entries. We don't attempt to
// maintain the 'offset' variable past this point; we don't do
// alignment on these entries.
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
JarEntry outEntry = null;
if (inEntry.getMethod() == JarEntry.STORED) continue;
// Create a new entry so that the compressed len is recomputed.
outEntry = new JarEntry(name);
outEntry.setTime(timestamp);
out.putNextEntry(outEntry);
InputStream data = in.getInputStream(inEntry);
while ((num = data.read(buffer)) > 0) {
out.write(buffer, 0, num);
}
out.flush();
}
}
// This class is to provide a file's content, but trimming out the last two bytes
// Used for signWholeFile
private static class CMSProcessableFile implements CMSTypedData {
private File file;
private ASN1ObjectIdentifier type;
private byte[] buffer;
int bufferSize = 0;
CMSProcessableFile(File file) {
this.file = file;
type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
buffer = new byte[4096];
}
@Override
public ASN1ObjectIdentifier getContentType() {
return type;
}
@Override
public void write(OutputStream out) throws IOException, CMSException {
FileInputStream input = new FileInputStream(file);
long len = file.length() - 2;
while ((bufferSize = input.read(buffer)) > 0) {
if (len <= bufferSize) {
out.write(buffer, 0, (int) len);
break;
} else {
out.write(buffer, 0, bufferSize);
}
len -= bufferSize;
}
}
@Override
public Object getContent() {
return file;
}
byte[] getTail() {
return Arrays.copyOfRange(buffer, 0, bufferSize);
}
}
private static void signWholeFile(File input, X509Certificate publicKey,
PrivateKey privateKey, OutputStream outputStream)
throws Exception {
ByteArrayOutputStream temp = new ByteArrayOutputStream();
// put a readable message and a null char at the start of the
// archive comment, so that tools that display the comment
// (hopefully) show something sensible.
// TODO: anything more useful we can put in this message?
byte[] message = "signed by SignApk".getBytes("UTF-8");
temp.write(message);
temp.write(0);
CMSProcessableFile cmsFile = new CMSProcessableFile(input);
writeSignatureBlock(cmsFile, publicKey, privateKey, temp);
// For a zip with no archive comment, the
// end-of-central-directory record will be 22 bytes long, so
// we expect to find the EOCD marker 22 bytes from the end.
byte[] zipData = cmsFile.getTail();
if (zipData[zipData.length-22] != 0x50 ||
zipData[zipData.length-21] != 0x4b ||
zipData[zipData.length-20] != 0x05 ||
zipData[zipData.length-19] != 0x06) {
throw new IllegalArgumentException("zip data already has an archive comment");
}
int total_size = temp.size() + 6;
if (total_size > 0xffff) {
throw new IllegalArgumentException("signature is too big for ZIP file comment");
}
// signature starts this many bytes from the end of the file
int signature_start = total_size - message.length - 1;
temp.write(signature_start & 0xff);
temp.write((signature_start >> 8) & 0xff);
// Why the 0xff bytes? In a zip file with no archive comment,
// bytes [-6:-2] of the file are the little-endian offset from
// the start of the file to the central directory. So for the
// two high bytes to be 0xff 0xff, the archive would have to
// be nearly 4GB in size. So it's unlikely that a real
// commentless archive would have 0xffs here, and lets us tell
// an old signed archive from a new one.
temp.write(0xff);
temp.write(0xff);
temp.write(total_size & 0xff);
temp.write((total_size >> 8) & 0xff);
temp.flush();
// Signature verification checks that the EOCD header is the
// last such sequence in the file (to avoid minzip finding a
// fake EOCD appended after the signature in its scan). The
// odds of producing this sequence by chance are very low, but
// let's catch it here if it does.
byte[] b = temp.toByteArray();
for (int i = 0; i < b.length-3; ++i) {
if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) {
throw new IllegalArgumentException("found spurious EOCD header at " + i);
}
}
cmsFile.write(outputStream);
outputStream.write(total_size & 0xff);
outputStream.write((total_size >> 8) & 0xff);
temp.writeTo(outputStream);
}
private static void signFile(Manifest manifest, JarFile inputJar,
X509Certificate publicKey, PrivateKey privateKey,
JarOutputStream outputJar)
throws Exception {
// Assume the certificate is valid for at least an hour.
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
// MANIFEST.MF
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey));
byte[] signedData = baos.toByteArray();
outputJar.write(signedData);
// CERT.{EC,RSA} / CERT#.{EC,RSA}
final String keyType = publicKey.getPublicKey().getAlgorithm();
je = new JarEntry(String.format(CERT_SIG_NAME, keyType));
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData),
publicKey, privateKey, outputJar);
public static void signZip(Context context, JarMap input, File output, boolean minSign) throws Exception {
AssetManager assets = context.getAssets();
SignAPK.signZip(
assets.open(PUBLIC_KEY_NAME), assets.open(PRIVATE_KEY_NAME),
input, output, minSign);
}
}

View File

@@ -9,5 +9,5 @@
android:fillColor="#000000"
android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
<path
android:pathData="M0-.75h24v24H0z" />
android:pathData="M0-0.75h24v24H0z" />
</vector>

View File

@@ -56,9 +56,9 @@
android:ellipsize="marquee"
android:gravity="center_vertical"
android:marqueeRepeatLimit="marquee_forever"
android:maxLines="1"
android:paddingEnd="3dp"
android:paddingStart="3dp"/>
android:paddingStart="3dp"
android:singleLine="true"/>
</LinearLayout>
<LinearLayout

View File

@@ -4,6 +4,7 @@
<item
android:id="@+id/app_search"
android:title=""
app:actionViewClass="android.widget.SearchView"
app:showAsAction="always"/>
</menu>

View File

@@ -4,6 +4,7 @@
<item
android:id="@+id/repo_search"
android:title=""
app:actionViewClass="android.widget.SearchView"
app:showAsAction="always"/>
</menu>

View File

@@ -20,7 +20,6 @@
<string name="safetyNet_check_text">انقر لبدء فحص SafetyNet</string>
<string name="checking_safetyNet_status">التحقق من حالة SafetyNet…</string>
<string name="safetyNet_check_success">نجح فحص SafetyNet</string>
<string name="safetyNet_no_response">لا يمكن التحقق من SafetyNet، لا يوجد إنترنت؟</string>
<string name="safetyNet_network_loss">فقدان الاتصال بالشبكة</string>
<string name="safetyNet_service_disconnected">تم إنهاء الخدمة</string>
<string name="safetyNet_res_invalid">الاستجابة غير صالحه</string>
@@ -36,7 +35,6 @@
<string name="current_magisk_title">نسخة Magisk المثبته: %1$s</string>
<string name="install_magisk_title">آخر نسخة Magisk: %1$s</string>
<string name="uninstall">إلغاء التثبيت</string>
<string name="reboot_countdown">إعادة التشغيل في %1$d</string>
<string name="uninstall_magisk_title">إلغاء تثبيت Magisk</string>
<!--Module Fragment-->
@@ -137,12 +135,6 @@
<string name="user_indepenent_summary">كل مستخدم لديه قواعد روت منفصلة خاصة به</string>
<string name="multiuser_hint_owner_request">تم إرسال طلب إلى مالك الجهاز. يرجى التبديل إلى المالك ومنح الإذن</string>
<string name="settings_development_category">تطوير التطبيق</string>
<string name="settings_developer_logging_title">تمكين تصحيح السجلات المتقدمة</string>
<string name="settings_developer_logging_summary">حدد هذا الخيار لتمكين سجل مطول أكثر.</string>
<string name="settings_shell_logging_title">تمكين سجل تصحيح الأوامر الدفعية</string>
<string name="settings_shell_logging_summary">حدد هذا الخيار لتمكين سجل جميع الأوامر الدفعية والمخرجات</string>
<!--Superuser-->
<string name="su_request_title">Superuser طلبات</string>
<string name="deny_with_str">رفض%1$s</string>

View File

@@ -19,7 +19,6 @@
<string name="not_rooted">Nemáte root</string>
<string name="safetyNet_check_text">Kliknutím zahájíte SafetyNet kontrolu</string>
<string name="checking_safetyNet_status">Kontrola stavu SafetyNet…</string>
<string name="safetyNet_no_response">Nelze zkontrolovat SafetyNet. Jste připojeni k Internetu?</string>
<!--Install Fragment-->
<string name="auto_detect">(Auto) %1$s</string>
@@ -32,7 +31,6 @@
<string name="current_magisk_title">Nainstalovaná verze Magisk: %1$s</string>
<string name="install_magisk_title">Poslední verze Magisk: %1$s</string>
<string name="uninstall">Odinstalovat</string>
<string name="reboot_countdown">Restart za %1$d</string>
<string name="uninstall_magisk_title">Odinstalovat Magisk</string>
<!--Module Fragment-->
@@ -122,12 +120,6 @@
<string name="superuser_notification">Oznámení Superuser</string>
<string name="request_timeout_summary">%1$s sekund</string>
<string name="settings_development_category">Vývoj aplikace</string>
<string name="settings_developer_logging_title">Povolit pokročilé ladění logování</string>
<string name="settings_developer_logging_summary">Zkuste zapnout toto pro rozsáhlejší logování</string>
<string name="settings_shell_logging_title">Povolit logování pro příkazy shellu</string>
<string name="settings_shell_logging_summary">Logování všech příkazů shellu a jejich výstup</string>
<!--Superuser-->
<string name="su_request_title">Požadavek Superuser</string>
<string name="deny_with_str">Zamítnout%1$s</string>

View File

@@ -23,7 +23,6 @@
<string name="safetyNet_check_text">SafetyNet-Status abfragen</string>
<string name="checking_safetyNet_status">Prüfe SafetyNet-Status…</string>
<string name="safetyNet_check_success">SafetyNet-Test erfolgreich</string>
<string name="safetyNet_no_response">SafetyNet-Status konnte nicht geprüft werden. Ist eine Internetverbindung verfügbar?</string>
<string name="safetyNet_network_loss">Netzwerkverbindung verloren</string>
<string name="safetyNet_service_disconnected">Der Dienst wurde beendet</string>
<string name="safetyNet_res_invalid">Die Antwort ist ungültig</string>
@@ -39,7 +38,6 @@
<string name="current_magisk_title">Installierte Magisk-Version: %1$s</string>
<string name="install_magisk_title">Neueste Magisk-Version: %1$s</string>
<string name="uninstall">Deinstallieren</string>
<string name="reboot_countdown">Neustart in %1$d</string>
<string name="uninstall_magisk_title">Magisk deinstallieren</string>
<!--Module Fragment-->
@@ -99,7 +97,7 @@
<string name="process_error">Prozessfehler</string>
<string name="internal_storage">Die zip-Datei ist gespeichert unter:\n[Interner Speicher]%1$s</string>
<string name="zip_download_title">Herunterladen</string>
<string name="zip_download_msg">Lade Zip-Datei herunter …</string>
<string name="zip_download_msg">Lade Zip-Datei herunter (%1$d%%)</string>
<string name="zip_process_title">Verarbeite</string>
<string name="zip_process_msg">Verarbeite Zip-Datei…</string>
<string name="manual_boot_image">Bitte Boot-Image auswählen!</string>
@@ -107,13 +105,13 @@
<string name="manager_download_install">Herunterladen und installieren</string>
<string name="magisk_updates">Magisk Updates</string>
<string name="flashing">Flashing</string>
<string name="hide_manager_toast">Magisk Manager verstecken</string>
<string name="hide_manager_fail_toast">Magisk Manager verstecken fehlgeschlagen…</string>
<string name="hide_manager_toast">Verberge Magisk Manager…</string>
<string name="hide_manager_fail_toast">Verbergen von Magisk Manager fehlgeschlagen…</string>
<string name="download_zip_only">Zip-Datei nur herunterladen</string>
<string name="patch_boot_file">Bootimage-Datei patchen</string>
<string name="patch_boot_file">Boot-Image-Datei patchen</string>
<string name="direct_install">Direkt installieren (empfohlen)</string>
<string name="select_method">Methode auswählen</string>
<string name="no_boot_file_patch_support">Magisk Zielversion unterstützt das Patchen des Bootimages nicht</string>
<string name="no_boot_file_patch_support">Magisk Zielversion unterstützt das Patchen des Boot-Images nicht</string>
<!--Settings Activity -->
<string name="settings_general_category">Allgemein</string>
@@ -123,16 +121,16 @@
<string name="settings_notification_summary">Benachrichtigung, wenn eine neue Version verfügbar ist</string>
<string name="settings_clear_cache_title">Repo-Cache löschen</string>
<string name="settings_clear_cache_summary">Löscht die zwischengespeicherten Informationen der Online-Repos. Erzwingt eine Aktualisierung</string>
<string name="settings_hide_manager_title">Verstecke Magisk Manager</string>
<string name="settings_hide_manager_summary">Magisk Manager zeitweise verstecken.\nDies installiert eine neue App namens \"Unhide Magisk Manager\"</string>
<string name="settings_hide_manager_title">Magisk Manager verbergen</string>
<string name="settings_hide_manager_summary">Magisk Manager temporär verbergen.\nDies installiert eine neue App namens \"Unhide Magisk Manager\"</string>
<string name="language">Sprache</string>
<string name="system_default">(System Standard)</string>
<string name="settings_update">Update Einstellungen</string>
<string name="settings_update_channel_title">Update Kanal</string>
<string name="system_default">(Systemstandard)</string>
<string name="settings_update">Update-Einstellungen</string>
<string name="settings_update_channel_title">Update-Kanal</string>
<string name="settings_update_stable">Stabil</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_boot_format_title">Ausgabeformat des gepatchten Bootimages</string>
<string name="settings_boot_format_summary">Wähle das Ausgabeformat des gepatchten Bootimages.\nWähle .img um mit \"fastboot/download mode\" zu schreiben; wähle .img.tar zum Schreiben mit ODIN.</string>
<string name="settings_boot_format_title">Ausgabeformat des gepatchten Boot-Images</string>
<string name="settings_boot_format_summary">Wähle das Ausgabeformat des gepatchten Boot-Images.\nWähle .img, um mit \"fastboot/download mode\" zu flashen; wähle .img.tar zum Flashen mit ODIN.</string>
<string name="settings_core_only_title">Nur Kernfunktionen</string>
<string name="settings_core_only_summary">Aktiviert lediglich die Kernfunktionen, Module werden nicht geladen. MagiskSU, Magisk Hide und Systemless hosts bleiben weiterhin aktiv</string>
@@ -172,12 +170,6 @@
<string name="global_summary">Alle root-Sitzungen benutzen den global angelegten Namensraum</string>
<string name="requester_summary">Root-Sitzungen erben den Namensraum des Abfragenden</string>
<string name="isolate_summary">Jede root-Sitzung hat ihren isolierten Namensraum</string>
<string name="settings_development_category">Entwickler</string>
<string name="settings_developer_logging_title">Erweiterte Fehlerprotokolle</string>
<string name="settings_developer_logging_summary">Für ausführliches Logging aktivieren</string>
<string name="settings_shell_logging_title">Fehlerprotokolle für Shell-Befehle</string>
<string name="settings_shell_logging_summary">Logging aller Shell-Befehle sowie deren Ausgabe</string>
<!--Superuser-->
<string name="su_request_title">Superuser-Anfrage</string>

View File

@@ -18,7 +18,6 @@
<string name="safetyNet_check_text">Πατήστε για έλεγχο του SafetyNet</string>
<string name="checking_safetyNet_status">Έλεγχος κατάστασης SafetyNet…</string>
<string name="safetyNet_check_success">Ο Έλεγχος του SafetyNet Ήταν Επιτυχής</string>
<string name="safetyNet_no_response">Αδυναμία επαλήθευσης SafetyNet, δεν έχει internet;</string>
<string name="safetyNet_network_loss">Αδυναμία σύνδεσης στο δίκτυο</string>
<string name="safetyNet_service_disconnected">Η υπηρεσία τερματίστηκε</string>
<string name="safetyNet_res_invalid">Η απόκριση είναι άκυρη</string>
@@ -34,7 +33,6 @@
<string name="current_magisk_title">Εγκατεστημένη έκδοση Magisk: %1$s</string>
<string name="install_magisk_title">Τελευταία έκδοση Magisk: %1$s</string>
<string name="uninstall">Απεγκατάσταση</string>
<string name="reboot_countdown">Επανεκκίνηση σε %1$d</string>
<string name="uninstall_magisk_title">Απεγκατάσταση Magisk</string>
<string name="update">Ενημέρωση %1$s</string>
@@ -166,12 +164,6 @@
<string name="requester_summary">Οι συνεδρίες root θα κληρονομούν το χώρο ονομάτων του αιτούντα τους</string>
<string name="isolate_summary">Κάθε συνεδρία root θα έχει το δικό της απομονωμένο χώρο ονομάτων</string>
<string name="settings_development_category">Ανάπτυξη Εφαρμογής</string>
<string name="settings_developer_logging_title">Ενεργοποίηση προηγμένης καταγραφής ελέγχου σφαλμάτων</string>
<string name="settings_developer_logging_summary">Επιλέξτε αυτό για να ενεργοποιήσετε τη verbose καταγραφή</string>
<string name="settings_shell_logging_title">Ενεργοποίηση καταγραφής ελέγχου σφαλμάτων εντολών κελύφους</string>
<string name="settings_shell_logging_summary">Επιλέξτε αυτό για να ενεργοποιήσετε την καταγραφή όλων των εντολών κελύφους και την έξοδο τους</string>
<!--Superuser-->
<string name="su_request_title">Αίτημα υπερχρήστη</string>
<string name="deny_with_str">Άρνηση%1$s</string>

View File

@@ -20,7 +20,7 @@
<string name="safetyNet_check_text">Toque para empezar la comprobación de SafetyNet</string>
<string name="checking_safetyNet_status">Comprobando estado de SafetyNet…</string>
<string name="safetyNet_check_success">La comprobación de SafetyNet fue exitosa</string>
<string name="safetyNet_no_response">No puede comprobar SafetyNet, ¿No tiene internet?</string>
<string name="safetyNet_api_error">Error en la API de SafetyNet</string>
<string name="safetyNet_network_loss">Conexión de red perdida</string>
<string name="safetyNet_service_disconnected">Se ha detenido el servicio</string>
<string name="safetyNet_res_invalid">La respuesta no es válida</string>
@@ -36,7 +36,6 @@
<string name="current_magisk_title">Versión de Magisk instalada: %1$s</string>
<string name="install_magisk_title">Última versión de Magisk: %1$s</string>
<string name="uninstall">Desinstalar</string>
<string name="reboot_countdown">Reiniciando en %1$d</string>
<string name="uninstall_magisk_msg">Todos los módulos serán desactivados / eliminados. El acceso Root se eliminará y, posiblemente, encriptará los datos si los datos no están cifrados actualmente.</string>
<string name="uninstall_magisk_title">Desinstalar Magisk</string>
<string name="update">Actualización %1$s</string>
@@ -98,6 +97,8 @@
<string name="safetyNet_hide_notice">Esta aplicación usa SafetyNet\nYa manejado por defecto por MagiskHide</string>
<string name="process_error">Error de proceso</string>
<string name="internal_storage">El zip es almacenado en:\n[Internal Storage]%1$s</string>
<string name="zip_download_title">Descargando</string>
<string name="zip_download_msg">Descargando el archivo zip (%1$d%%) …</string>
<string name="zip_process_title">Procesando</string>
<string name="manual_boot_image">¡Selecciona manualmente una imagen boot!</string>
<string name="manager_update_title">Nueva actualización de Magisk Manager disponible!</string>
@@ -109,12 +110,18 @@
<string name="download_zip_only">Descargar sólo el archivo ZIP</string>
<string name="patch_boot_file">Parcheo de la imagen boot</string>
<string name="direct_install">Instalación Directa (Recomendado)</string>
<string name="install_second_slot">Instalación en el segundo espacio (Después de OTA)</string>
<string name="select_method">Seleccionar Método</string>
<string name="no_boot_file_patch_support">La versión de Magisk no admite el parcheo de la imagen boot</string>
<string name="boot_file_patch_msg">Seleccione el volcado de la imagen boot en formato .img o .img.tar</string>
<string name="complete_uninstall">Desinstalación completa</string>
<string name="restore_stock_boot">Restaurar imagen boot Stock</string>
<string name="restore_done">¡Restauración Terminada!</string>
<string name="restore_fail">¡El respaldo de la imagen boot Stock no existe!</string>
<string name="uninstall_toast">Desinstalación de Magisk Manager en 5 segundos, por favor después reinicie manualmente </string>
<string name="proprietary_title">Descargar Código Propietario</string>
<string name="proprietary_notice">Magisk Manager es un Software Libre por lo que no contiene el código API de SafetyNet (Código Propietario de Google).\n\n ¿Puede permitir que Magisk Manager descargue una extensión (contiene el GoogleApiClient) para los cheques de SafetyNet?</string>
<!--Settings Activity -->
<string name="settings_general_category">General</string>
@@ -171,13 +178,7 @@
<string name="settings_ns_isolate">Aislar Namespace</string>
<string name="global_summary">Todas las sesiones de root utilizan el soporte Global Namespace</string>
<string name="requester_summary">Las sesiones de root heredarán las peticiones Namespace</string>
<string name="isolate_summary">Cada sesión root tendrá su propia Namespace</string>
<string name="settings_development_category">Desarrollo de la aplicación</string>
<string name="settings_developer_logging_title">Habilitar información avanzada de depuración en el registro</string>
<string name="settings_developer_logging_summary">Activar esto para grabar más información en el registro</string>
<string name="settings_shell_logging_title">Grabar comandos de terminal en el registro</string>
<string name="settings_shell_logging_summary">Activar esto para grabar en el log todos los comandos ejecutados y su resultado</string>
<string name="isolate_summary">Cada sesión root tendrá su propia Namespace</string>
<!--Superuser-->
<string name="su_request_title">Petición de superusuario</string>

View File

@@ -16,15 +16,14 @@
<string name="not_rooted">Non rooté</string>
<string name="safetyNet_check_text">Appuyer pour lancer le contrôle SafetyNet</string>
<string name="checking_safetyNet_status">Vérification de l\'état de SafetyNet…</string>
<string name="safetyNet_no_response">Impossible de contrôler SafetyNet, pas d\'Internet?</string>
<string name="safetyNet_check_success">Contrôle SafetyNet passé avec succès</string>
<string name="safetyNet_check_success">Contrôle SafetyNet passé avec succès</string>
<string name="safetyNet_network_loss">Connexion réseau indisponible unavailable</string>
<string name="safetyNet_service_disconnected">Le service a été tué</string>
<string name="safetyNet_res_invalid">La réponse est invalide</string>
<!--Install Fragment-->
<string name="auto_detect">(Auto) %1$s</string>
<string name="cannot_auto_detect">(Auto détection impossible)</string>
<string name="boot_image_title">Emplacement de l\'Image Boot</string>
<string name="boot_image_title">Emplacement de l\'image Boot</string>
<string name="detect_button">Détection</string>
<string name="advanced_settings_title">Paramètres avancés</string>
<string name="keep_force_encryption">Garder le chiffrement forcé</string>
@@ -32,9 +31,8 @@
<string name="current_magisk_title">Version Magisk Installée : %1$s</string>
<string name="install_magisk_title">Dernière Version Magisk : %1$s</string>
<string name="uninstall">Désinstaller</string>
<string name="reboot_countdown">Redémarrage dans %1$d</string>
<string name="uninstall_magisk_title">Désinstaller Magisk</string>
<string name="uninstall_magisk_msg">Tous les modules seront désactivés/effacés. Le root sera enlevé et vos données seront potentiellement chiffrées si ce n'est actuellement pas le cas</string>
<string name="uninstall_magisk_msg">Tous les modules seront désactivés/effacés. Le root sera enlevé et vos données seront potentiellement chiffrées si ce n\'est actuellement pas le cas</string>
<string name="update">Mise à jour %1$s</string>
<!--Module Fragment-->
<string name="no_info_provided">(Aucune information transmise)</string>
@@ -55,7 +53,7 @@
<string name="menuClearLog">Effacer le journal maintenant</string>
<string name="logs_cleared">Journal effacé avec succès</string>
<string name="log_is_empty">Journal vide</string>
<string name="logs_save_failed">Impossible d\'écrire le journal sur la SD card:</string>
<string name="logs_save_failed">Impossible d\'écrire le journal sur la carte SD:</string>
<!--About Activity-->
<string name="about">À propos</string>
<string name="app_developers">Principaux développeurs</string>
@@ -92,7 +90,7 @@
<string name="zip_process_title">Exécution</string>
<string name="manual_boot_image">Veuillez sélectionner l\'image boot manuellement !</string>
<string name="zip_download_title">Téléchargement</string>
<string name="zip_download_msg">Téléchargement du fichier zip...</string>
<string name="zip_download_msg">Téléchargement du fichier zip (%1$d%%) ...</string>
<string name="manager_update_title">Nouvelle mise à jour Magisk Manager disponible !</string>
<string name="manager_download_install">Appuyez pour télécharger et installer</string>
<string name="magisk_updates">Mises à jour Magisk</string>
@@ -134,13 +132,8 @@
<string name="request_timeout">Délai de requête</string>
<string name="superuser_notification">Notification Superuser</string>
<string name="request_timeout_summary">%1$s secondes</string>
<string name="settings_development_category">Dévellopement de l\'application</string>
<string name="settings_developer_logging_title">Activer le journal de débogage avancé</string>
<string name="settings_developer_logging_summary">Cocher ceci pour activer la journalisation "verbose" (tout)</string>
<string name="settings_shell_logging_title">Activer la journalisation de débogage de commande shell</string>
<string name="settings_shell_logging_summary">Cocher ceci pour activer la journalisation de toutes les commandes shell et leurs données de sortie</string>
<string name="settings_hide_manager_title">Masquer Magisk Manager</string>
<string name="settings_hide_manager_summary">Masquer temporellement Magisk Manager.\nCeci installera une nouvelle appli appelée \"Démasquer Magisk Manager\"</string>
<string name="settings_hide_manager_title">Masquer Magisk Manager</string>
<string name="settings_hide_manager_summary">Masquer temporairement Magisk Manager.\nCeci installera une nouvelle appli appelée \"Unhide Magisk Manager\"</string>
<string name="language">Langue</string>
<string name="system_default">(Selon système)</string>
<string name="settings_update">Paramètres de mises à jour</string>
@@ -148,7 +141,7 @@
<string name="settings_update_stable">Stable</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_boot_format_title">Format de sortie du boot patché</string>
<string name="settings_boot_format_summary">Sélectionner le format de \'image boot patchée finale.\nChoisir .img pour flasher via fastboot/mode download ; chosiir .img.tar pour flasher via ODIN.</string>
<string name="settings_boot_format_summary">Sélectionner le format de l\'image boot patchée finale.\nChoisir .img pour flasher via fastboot/mode download ; choisir .img.tar pour flasher via ODIN.</string>
<string name="settings_su_reauth_title">Ré-authentifier après mise à jour</string>
<string name="settings_su_reauth_summary">Ré-authentifier les permissions superuser après les mises à jour d\'une application</string>
<string name="multiuser_mode">Mode multi-utilisateurs</string>

View File

@@ -20,7 +20,6 @@
<string name="safetyNet_check_text">Tocca per avviare controllo SafetyNet</string>
<string name="checking_safetyNet_status">Controllo stato SafetyNet</string>
<string name="safetyNet_check_success">Controllo SafetyNet OK</string>
<string name="safetyNet_no_response">Impossibile controllare SafetyNet. Nessuna connessione internet?</string>
<string name="safetyNet_network_loss">persa conenssioen di rete</string>
<string name="safetyNet_service_disconnected">Ils ervizio è stato terminato</string>
<string name="safetyNet_res_invalid">La risposta non è valida</string>
@@ -36,7 +35,6 @@
<string name="current_magisk_title">Versione Magisk installata: %1$s</string>
<string name="install_magisk_title">Versione Magisk aggiornata: %1$s</string>
<string name="uninstall">Disinstalla</string>
<string name="reboot_countdown">Riavvio tra %1$d</string>
<string name="uninstall_magisk_title">Disinstalla Magisk</string>
<string name="update">Aggiorna %1$s</string>
@@ -144,12 +142,6 @@
<string name="user_indepenent_summary">Ogni utente ha le sue regole di root separate</string>
<string name="multiuser_hint_owner_request">Una richiestà è stata inviata al propietario dipositivo. Accedi come propietario dispositivo e concedi i permessi.</string>
<string name="settings_development_category">Sviluppo app</string>
<string name="settings_developer_logging_title">Abilita registro eventi avanzato debug</string>
<string name="settings_developer_logging_summary">Spunta questo per abilitare il registro eventi dettagliato</string>
<string name="settings_shell_logging_title">Abilita registro eventi debug comandi shell</string>
<string name="settings_shell_logging_summary">Spunta questo per abilitare il registro eventi dettagliato dei comandi shell e dei relativi risultati</string>
<!--Superuser-->
<string name="su_request_title">Richiesta Superuser</string>
<string name="deny_with_str">Nega %1$s</string>

View File

@@ -19,7 +19,6 @@
<string name="not_rooted">Root化されていません</string>
<string name="safetyNet_check_text">タップしてSafetyNetチェックを開始</string>
<string name="checking_safetyNet_status">SafetyNet Statusをチェック中…</string>
<string name="safetyNet_no_response">SafetyNetをチェックできませんでした。インターネットに接続されていますか</string>
<!--Install Fragment-->
<string name="auto_detect">(自動) %1$s</string>
@@ -32,7 +31,6 @@
<string name="current_magisk_title">インストール済のMagisk: %1$s</string>
<string name="install_magisk_title">最新のMagisk: %1$s</string>
<string name="uninstall">アンインストール</string>
<string name="reboot_countdown">%1$dで再起動します</string>
<string name="uninstall_magisk_title">Magiskをアンインストールします</string>
<!--Module Fragment-->
@@ -124,12 +122,6 @@
<string name="superuser_notification">スーパーユーザー通知</string>
<string name="request_timeout_summary">%1$s秒</string>
<string name="settings_development_category">アプリケーション開発</string>
<string name="settings_developer_logging_title">高度なデバッグログを有効にする</string>
<string name="settings_developer_logging_summary">詳細なログを有効にするにはここをチェック</string>
<string name="settings_shell_logging_title">シェルコマンドのデバッグログを有効にする</string>
<string name="settings_shell_logging_summary">すべてのシェルコマンドとその出力をログに記録します</string>
<!--Superuser-->
<string name="su_request_title">スーパーユーザーリクエスト</string>
<string name="deny_with_str">拒否 %1$s</string>

View File

@@ -19,7 +19,6 @@
<string name="not_rooted">루팅이 되어 있지 않음</string>
<string name="safetyNet_check_text">SafetyNet 체크를 시작하려면 누르기</string>
<string name="checking_safetyNet_status">SafetyNet 상태 확인 중…</string>
<string name="safetyNet_no_response">SafetyNet 체크 실패. 인터넷 연결을 확인하세요.</string>
<!--Install Fragment-->
<string name="auto_detect">(자동) %1$s</string>
@@ -32,7 +31,6 @@
<string name="current_magisk_title">설치된 Magisk 버전: %1$s</string>
<string name="install_magisk_title">최신 Magisk 버전: %1$s</string>
<string name="uninstall">제거</string>
<string name="reboot_countdown">%1$d초 안에 다시 시작됨</string>
<string name="uninstall_magisk_title">Magisk 제거</string>
<!--Module Fragment-->
@@ -123,12 +121,6 @@
<string name="superuser_notification">슈퍼유저 알림</string>
<string name="request_timeout_summary">%1$s초</string>
<string name="settings_development_category">앱 개발</string>
<string name="settings_developer_logging_title">고급 디버그 로깅 사용</string>
<string name="settings_developer_logging_summary">더 자세한 로그를 보려면 체크하세요.</string>
<string name="settings_shell_logging_title">셸 커맨드 디버그 로깅 사용</string>
<string name="settings_shell_logging_summary">모든 셸 커맨드와 그 출력을 로그에 기록하려면 체크하세요.</string>
<!--Superuser-->
<string name="su_request_title">슈퍼유저 요청</string>
<string name="deny_with_str">거부%1$s</string>

View File

@@ -20,7 +20,6 @@
<string name="safetyNet_check_text">Tik om SafetyNet controle te starten</string>
<string name="checking_safetyNet_status">SafetyNet status controleren…</string>
<string name="safetyNet_check_success">SafetyNet controle succesvol</string>
<string name="safetyNet_no_response">Kan SafetyNet niet controleren. Geen internet?</string>
<string name="safetyNet_network_loss">Netwerkverbinding verloren</string>
<string name="safetyNet_service_disconnected">Service is beëindigd</string>
<string name="safetyNet_res_invalid">Reactie is ongeldig</string>
@@ -36,8 +35,8 @@
<string name="current_magisk_title">Geïnstalleerde Magisk versie: %1$s</string>
<string name="install_magisk_title">Recentste Magisk versie: %1$s</string>
<string name="uninstall">Deïnstalleren</string>
<string name="reboot_countdown">Herstarten in %1$d</string>
<string name="uninstall_magisk_title">Magisk deïnstalleren</string>
<string name="uninstall_magisk_msg">Alle modules worden uitgeschakeld/verwijderd. Root wordt verwijderd, en je data wordt mogelijk versleuteld als deze dat momenteel niet is</string>
<string name="update">Bijwerken %1$s</string>
<!--Module Fragment-->
@@ -88,7 +87,6 @@
<string name="install_error">Installatiefout!</string>
<string name="invalid_zip">De zip is geen Magisk module!</string>
<string name="reboot">Herstarten</string>
<string name="zip_process_msg">Zip-bestand verwerken…</string>
<string name="downloading_toast">%1$s downloaden</string>
<string name="magisk_update_title">Nieuwe Magisk update beschikbaar!</string>
<string name="settings_reboot_toast">Herstarten om instellingen toe te passen</string>
@@ -97,8 +95,29 @@
<string name="safetyNet_hide_notice">Deze app gebruikt SafetyNet\nReeds afgehandeld door standaard MagiskHide</string>
<string name="process_error">Verwerkingsfout</string>
<string name="internal_storage">De zip is opgeslagen in:\n[Interne opslag]%1$s</string>
<string name="zip_download_title">Downloaden</string>
<string name="zip_download_msg">Zip-bestand downloaden (%1$d%%) …</string>
<string name="zip_process_title">Verwerken</string>
<string name="zip_process_msg">Zip-bestand verwerken…</string>
<string name="manual_boot_image">Gelieve handmatig een boot image te selecteren!</string>
<string name="manager_update_title">Nieuwe Magisk Manager update beschikbaar!</string>
<string name="manager_download_install">Tik om te downloaden en installeren</string>
<string name="magisk_updates">Magisk updates</string>
<string name="flashing">Flashen</string>
<string name="hide_manager_toast">Magisk Manager verbergen…</string>
<string name="hide_manager_fail_toast">Magisk Manager verbergen mislukt…</string>
<string name="download_zip_only">Alleen zip downloaden</string>
<string name="patch_boot_file">Boot image-bestand patchen</string>
<string name="direct_install">Direct installeren (aangeraden)</string>
<string name="install_second_slot">Op tweede positie installeren (na OTA)</string>
<string name="select_method">Methode kiezen</string>
<string name="no_boot_file_patch_support">Magisk versie ondersteund geen boot image-bestand patchen</string>
<string name="boot_file_patch_msg">Kies originele boot image-dump in .img- of .img.tar-formaat</string>
<string name="complete_uninstall">Compleet deïnstalleren</string>
<string name="restore_stock_boot">Originele boot herstellen</string>
<string name="restore_done">Herstel voltooid!</string>
<string name="restore_fail">Originele back-up bestaat niet!</string>
<string name="uninstall_toast">Magisk Manager wordt over 5 seconden verwijderd, hierna handmatig herstarten aub</string>
<!--Settings Activity -->
<string name="settings_general_category">Algemeen</string>
@@ -108,8 +127,16 @@
<string name="settings_notification_summary">Een update melding weergeven wanneer een nieuwe versie beschikbaar is</string>
<string name="settings_clear_cache_title">Opslagcache wissen</string>
<string name="settings_clear_cache_summary">Wis de gecachte informatie voor online opslagplaatsen. Dit dwingt de app om online te verversen</string>
<string name="settings_hide_manager_title">Magisk Manager verbergen</string>
<string name="settings_hide_manager_summary">Magisk Manager tijdelijk verbergen.\nDit installeert een nieuwe app genaamd \"Unhide Magisk Manager\"</string>
<string name="language">Taal</string>
<string name="system_default">(Systeem standaard)</string>
<string name="settings_update">Instellingen bijwerken</string>
<string name="settings_update_channel_title">Update-kanaal</string>
<string name="settings_update_stable">Stabiel</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_boot_format_title">Gepatchte boot uitvoerformaat</string>
<string name="settings_boot_format_summary">Kies het formaat van de boot image uitvoer.\nKies .img om via fastboot/downloadmodus te flashen; kies .img.tar om via ODIN te flashen.</string>
<string name="settings_core_only_title">Magisk basismodus</string>
<string name="settings_core_only_summary">Alleen kernfuncties inschakelen. Alle modules worden niet geladen. MagiskSU, MagiskHide, en systeemloze hosts blijven ingeschakeld</string>
@@ -149,13 +176,7 @@
<string name="global_summary">Alle rootsessies gebruiken de globale naamruimte</string>
<string name="requester_summary">Rootsessies verkrijgen de verzoeker\'s naamruimte</string>
<string name="isolate_summary">Iedere rootsessie heeft een eigen geïsoleerde naamruimte</string>
<string name="settings_development_category">App-ontwikkeling</string>
<string name="settings_developer_logging_title">Geavanceerde debug logs inschakelen</string>
<string name="settings_developer_logging_summary">Aanvinken om uitgebreide logs in te schakelen</string>
<string name="settings_shell_logging_title">Shell-opdracht debug logs inschakelen</string>
<string name="settings_shell_logging_summary">Aanvinken om alle shell-opdracht en uitvoer logs in te schakelen</string>
<!--Superuser-->
<string name="su_request_title">Superuser verzoek</string>
<string name="deny_with_str">Weigeren %1$s</string>

View File

@@ -23,7 +23,6 @@
<string name="safetyNet_check_text">Dotknij aby sprawdzić SafetyNet</string>
<string name="checking_safetyNet_status">Sprawdzanie statusu SafetyNet…</string>
<string name="safetyNet_check_success">Sprawdzanie SafetyNet z Powodzeniem</string>
<string name="safetyNet_no_response">Nie można sprawdzić SafetyNet bez internetu</string>
<string name="safetyNet_network_loss">Brak połączenia z internetem</string>
<string name="safetyNet_service_disconnected">Usługa została zamknięta</string>
<string name="safetyNet_res_invalid">Usługa nie odpowiada</string>
@@ -39,8 +38,8 @@
<string name="current_magisk_title">Zainstalowana Wersja Magisk: %1$s</string>
<string name="install_magisk_title">Ostatnia Wersja Magisk: %1$s</string>
<string name="uninstall">Odinstaluj</string>
<string name="reboot_countdown">Restartuj do %1$d</string>
<string name="uninstall_magisk_title">Odinstaluj Magisk</string>
<string name="uninstall_magisk_msg">Wszystkie moduły będą wyłączone/usunięte.Root zostanie usunięty i przywrócone szyfrowanie danych, jeśli nie są te dane obecnie szyfrowane</string>
<string name="update">Aktualizacja %1$s</string>
<!--Module Fragment-->
@@ -91,7 +90,6 @@
<string name="install_error">Błąd instalacji!</string>
<string name="invalid_zip">Ten zip nie jest Modułem Magisk!!</string>
<string name="reboot">Restart</string>
<string name="zip_process_msg">Przetwarzanie pliku zip …</string>
<string name="downloading_toast">Pobieranie %1$s</string>
<string name="magisk_update_title">Nowa Wersja Magisk Dostępna!</string>
<string name="settings_reboot_toast">Uruchom ponownie, aby zastosować ustawienia</string>
@@ -100,8 +98,11 @@
<string name="safetyNet_hide_notice">Ta aplikacja wykorzystuje SafetyNet\nJest już domyślnie obsługiwana przez MagiskHide</string>
<string name="process_error">Błąd procesu</string>
<string name="internal_storage">Zip jest przechowywany w:\n[Pamięć Wewnętrzna]%1$s</string>
<string name="zip_process_title">Przetwarzanie</string>
<string name="manual_boot_image">Proszę ręcznie wybrać boot image!</string>
<string name="zip_download_title">Pobieranie</string>
<string name="zip_download_msg">Pobieranie pliku zip (%1$d%%) …</string>
<string name="zip_process_title">Przetwarzanie</string>
<string name="zip_process_msg">Przetwarzanie pliku zip …</string>
<string name="manual_boot_image">Proszę ręcznie wybrać boot image!</string>
<string name="manager_update_title">Nowa Wersja Magisk Manager Jest Dostępna!</string>
<string name="manager_download_install">Naciśnij aby pobrać i zainstalować</string>
<string name="magisk_updates">Aktualizacja Magisk</string>
@@ -111,9 +112,16 @@
<string name="download_zip_only">Pobierz Tylko Zip</string>
<string name="patch_boot_file">Patchowanie Pliku Boot Image</string>
<string name="direct_install">Bezpośrednia instalacja (Zalecane)</string>
<string name="install_second_slot">Zainstaluj na Drugim Slocie (Po OTA)</string>
<string name="select_method">Wybierz Metodę</string>
<string name="no_boot_file_patch_support">Wersja docelowa programu Magisk nie obsługuje ładowania pliku boot image</string>
<string name="boot_file_patch_msg">Wybierz stock boot image w formacie .img lub .img.tar</string>
<string name="complete_uninstall">Odinstalowywanie Zakończone</string>
<string name="restore_stock_boot">Przywróć Stockowy Boot</string>
<string name="restore_done">Przywracanie zakonczone!</string>
<string name="restore_fail">Stock backup nie istnieje!</string>
<string name="uninstall_toast">Odinstalowanie Magisk Manager w ciągu 5 sekund, proszę następnie ręcznie ponownie uruchomić urządzenie</string>
<!--Settings Activity -->
<string name="settings_general_category">Ogólne</string>
<string name="settings_dark_theme_title">Ciemny Motyw</string>
@@ -171,13 +179,7 @@
<string name="global_summary">Wszystkie sesje root za pomocą globalnej przestrzeni montowań nazw</string>
<string name="requester_summary">Sesje Root będzie dziedziczyć prośby i nazwy</string>
<string name="isolate_summary">W każdej sesji root będzie miał własną odosobnioną nazwę</string>
<string name="settings_development_category">Dla Developerów</string>
<string name="settings_developer_logging_title">Włącz zaawansowane logowanie debugowania</string>
<string name="settings_developer_logging_summary">Zaznacz, aby umożliwić pełne rejestrowanie</string>
<string name="settings_shell_logging_title">Włącz rejestrowanie poleceń powłoki debugowania</string>
<string name="settings_shell_logging_summary">Włącz, aby rejestrować wszystkie polecenia powłoki i wyjścia</string>
<!--Superuser-->
<string name="su_request_title">Prośba o dostęp Superusera</string>
<string name="deny_with_str">Odmów%1$s</string>

View File

@@ -23,7 +23,7 @@
<string name="safetyNet_check_text">Pressione para checar o SafetyNet</string>
<string name="checking_safetyNet_status">Verificando status do SafetyNet…</string>
<string name="safetyNet_check_success">SafetyNet verificado</string>
<string name="safetyNet_no_response">Não é possível verificar o SafetyNet, sem Internet?</string>
<string name="safetyNet_api_error">SafetyNet erro na API</string>
<string name="safetyNet_network_loss">Conexão com a rede perdida</string>
<string name="safetyNet_service_disconnected">O serviço foi morto</string>
<string name="safetyNet_res_invalid">A resposta é inválida</string>
@@ -39,8 +39,9 @@
<string name="current_magisk_title">Versão instalada do Magisk: %1$s</string>
<string name="install_magisk_title">Última versão do Magisk: %1$s</string>
<string name="uninstall">Desinstalar</string>
<string name="reboot_countdown">Reiniciando em %1$d</string>
<string name="uninstall_magisk_title">Desinstalar Magisk</string>
<string name="uninstall_magisk_msg">Todos os modulos magisk será desativado/removido. O root será removido, e possivelmente a encriptação de seus dados se seus dados não foram encriptado.</string>
<string name="update">Atualizar %1$s</string>
<!--Module Fragment-->
<string name="no_info_provided">(Nenhuma informação fornecida)</string>
@@ -90,7 +91,6 @@
<string name="install_error">Erro na instalação!</string>
<string name="invalid_zip">O zip não é um Módulo Magisk!!</string>
<string name="reboot">Reiniciar</string>
<string name="zip_process_msg">Processando arquivo zip …</string>
<string name="downloading_toast">Baixando %1$s</string>
<string name="magisk_update_title">Nova atualização do Magisk disponível!</string>
<string name="settings_reboot_toast">Reinicie para aplicar configurações</string>
@@ -99,11 +99,31 @@
<string name="safetyNet_hide_notice">Este aplicativo usa SafetyNet\nJá manipulado pelo MagiskHide por padrão</string>
<string name="process_error">Erro no processo</string>
<string name="internal_storage">O zip foi salvo em:\n[Armazenamento interno]%1$s</string>
<string name="zip_download_title">Baixando</string>
<string name="zip_download_msg">Baixando o arquivo zip (%1$d%%) …</string>
<string name="zip_process_title">Processando</string>
<string name="zip_process_msg">Processando arquivo zip …</string>
<string name="manual_boot_image">Por Favor, selecione manualmente a Boot Image</string>
<string name="manager_update_title">Nova atualização do Magisk Manager disponível!</string>
<string name="manager_download_install">Pressione para baixar e instalar</string>
<string name="magisk_updates">Atualizações do Magisk</string>
<string name="flashing">Flasheando</string>
<string name="hide_manager_toast">Ocultando Magisk Manager…</string>
<string name="hide_manager_fail_toast">Falha ao ocultar o Magisk Manager…</string>
<string name="download_zip_only">Baixar somente o zip</string>
<string name="patch_boot_file">Arquivo Patch Boot Image</string>
<string name="direct_install">Instalação Direta (Recomendado)</string>
<string name="install_second_slot">Instalação no segundo Slot (Depois do OTA)</string>
<string name="select_method">Selecionar Método</string>
<string name="no_boot_file_patch_support">Versão do Magisk escolhida não suporta arquivo de patch boot image</string>
<string name="boot_file_patch_msg">Selecione a stock boot image despejada(dump) no formato .img ou img.tar</string>
<string name="complete_uninstall">Desinstalação Completa</string>
<string name="restore_stock_boot">Restaurar Stock Boot</string>
<string name="restore_done">Restauração Completa!</string>
<string name="restore_fail">backup da Stock não existe!</string>
<string name="uninstall_toast">Desinstalando o Magisk Manager em 5 segundos, por favor reinicie manualmente depois</string>
<string name="proprietary_title">Baixar Código do Proprietário</string>
<string name="proprietary_notice">Magisk Manager é FOSS então não contém o código proprietário da API SafetyNet do Google.\n\nVocê permite que o Magisk Manager baixe uma extensão (contém o GoogleApiClient) para verificações SafetyNet?</string>
<!--Settings Fragment -->
<string name="settings_general_category">Geral</string>
@@ -113,7 +133,17 @@
<string name="settings_notification_summary">Mostrar notificações de atualização quando a nova versão estiver disponível</string>
<string name="settings_clear_cache_title">Limpar Repo Cache</string>
<string name="settings_clear_cache_summary">Limpe as informações armazenadas em cache para repos. online, forçando o aplicativo a atualizar online</string>
<string name="settings_hide_manager_title">Ocultar Magisk Manager</string>
<string name="settings_hide_manager_summary">Oculta temporariamente o Magisk Manager.\nIsso irá instalar um novo aplicativo chamado \"Unhide Magisk Manager\"</string>
<string name="language">Linguagem</string>
<string name="system_default">(Padrão do Sistema)</string>
<string name="settings_update">Atualizar Configurações</string>
<string name="settings_update_channel_title">Canal de atualizações</string>
<string name="settings_update_stable">Estável</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_boot_format_title">Formato de Saida do Boot Patcheado</string>
<string name="settings_boot_format_summary">Selecione o formato de saida do Boot Image Patcheado.\nSelecione .img para flashear através do fastboot/modo de download; Selecione .img.tar para flashear com o ODIN.</string>
<string name="settings_core_only_title">Magisk modo somente Core</string>
<string name="settings_core_only_summary">Ativar somente recursos principais, todos os módulos não serão carregados. MagiskSU, MagiskHide, e systemless hosts ainda estará ativado</string>
<string name="settings_magiskhide_summary">Ocultar Magisk de várias detecções</string>
@@ -152,14 +182,8 @@
<string name="global_summary">Todas as sessões raiz usam o namespace de montagem global</string>
<string name="requester_summary">As sessões de raiz herdarão o namespace do seu solicitante</string>
<string name="isolate_summary">Cada sessão raiz terá seu próprio namespace isolado</string>
<string name="settings_development_category">Desenvolvimento</string>
<string name="settings_developer_logging_title">Ativar registro mais detalhado</string>
<string name="settings_developer_logging_summary">Marque essa opção para habilitar um registro mais detalhado</string>
<string name="settings_shell_logging_title">Ativar registro da shell de comando</string>
<string name="settings_shell_logging_summary">Marque esta opção para habilitar o registro de todos os comandos shell e de saída</string>
<!--Superuser-->
<!--Superuser-->
<string name="su_request_title">Solicitação de superusuário</string>
<string name="deny_with_str">Negar%1$s</string>
<string name="deny">Negar</string>

View File

@@ -20,7 +20,6 @@
<string name="safetyNet_check_text">Pressione para verificar SafetyNet</string>
<string name="checking_safetyNet_status">A verificar o estado do SafetyNet…</string>
<string name="safetyNet_check_success">SafetyNet verificado com sucesso</string>
<string name="safetyNet_no_response">Não é possível verificar o SafetyNet, sem Internet?</string>
<string name="safetyNet_network_loss">Perda de ligação</string>
<string name="safetyNet_service_disconnected">Serviço foi interrompido</string>
<string name="safetyNet_res_invalid">A resposta é inválida</string>
@@ -36,8 +35,7 @@
<string name="current_magisk_title">Versão instalada do Magisk: %1$s</string>
<string name="install_magisk_title">Última versão do Magisk: %1$s</string>
<string name="uninstall">Desinstalar</string>
<string name="reboot_countdown">A reiniciar em %1$d</string>
<string name="uninstall_magisk_title">Desinstalar Magisk</string>
<string name="uninstall_magisk_title">Desinstalar Magisk</string>
<string name="update">Atualizar %1$s</string>
<!--Module Fragment-->
@@ -155,13 +153,7 @@
<string name="requester_summary">As sessões de root herdarão a identificação do seu solicitante</string>
<string name="isolate_summary">Cada sessão root terá sua própria identificação isolada</string>
<string name="settings_development_category">Desenvolvimento</string>
<string name="settings_developer_logging_title">Ativar registo mais detalhado</string>
<string name="settings_developer_logging_summary">Marque essa opção para permitir um registo mais detalhado</string>
<string name="settings_shell_logging_title">Ativar registo da linha de comandos</string>
<string name="settings_shell_logging_summary">Marque esta opção para permitir o registo de todos os comandos</string>
<!--Superuser-->
<!--Superuser-->
<string name="su_request_title">Pedido de Super-Utilizador</string>
<string name="deny_with_str">Negar%1$s</string>
<string name="deny">Negar</string>

View File

@@ -17,7 +17,6 @@
<string name="not_rooted">Nu este obţinut root</string>
<string name="safetyNet_check_text">Verificare SafetyNet</string>
<string name="checking_safetyNet_status">Verificarea stării SafetyNet…</string>
<string name="safetyNet_no_response">Nu se poate verifica SafetyNet, nu este internet?</string>
<!--Install Fragment-->
<string name="auto_detect">(Auto) %1$s</string>
@@ -30,7 +29,6 @@
<string name="current_magisk_title">Versiune Magisk instalată: %1$s</string>
<string name="install_magisk_title">Ultima versiune Magisk: %1$s</string>
<string name="uninstall">Dezinstalare</string>
<string name="reboot_countdown">Repornire în %1$d</string>
<string name="uninstall_magisk_title">Dezinstalare Magisk</string>
<string name="update">Actualizare %1$s</string>
@@ -153,12 +151,6 @@
<string name="requester_summary">Sesiunile de root vor moșteni spațiul de nume al solicitantului</string>
<string name="isolate_summary">Fiecare sesiune de root va avea propriul spațiu de nume izolat</string>
<string name="settings_development_category">Dezvoltare aplicaţie</string>
<string name="settings_developer_logging_title">Activați logarea avansată de depanare</string>
<string name="settings_developer_logging_summary">Bifați această opțiune pentru a activa înregistrarea detaliată</string>
<string name="settings_shell_logging_title">Activați logarea comenzilor shell</string>
<string name="settings_shell_logging_summary">Bifați această opțiune pentru a permite logarea tuturor comenzilor shell și ieșirea acestora</string>
<!--Superuser-->
<string name="su_request_title">Solicitare Superuser</string>
<string name="deny_with_str">Refuzaţi %1$s</string>

View File

@@ -20,7 +20,7 @@
<string name="safetyNet_check_text">Проверить статус SafetyNet</string>
<string name="checking_safetyNet_status">Проверка статуса SafetyNet…</string>
<string name="safetyNet_check_success">Проверка SafetyNet пройдена</string>
<string name="safetyNet_no_response">Невозможно выполнить проверку SafetyNet</string>
<string name="safetyNet_api_error">Ошибка в API SafetyNet</string>
<string name="safetyNet_network_loss">Подключение к интернету прервано</string>
<string name="safetyNet_service_disconnected">Служба была остановлена</string>
<string name="safetyNet_res_invalid">Некорректный ответ</string>
@@ -28,7 +28,7 @@
<!--Install Fragment-->
<string name="auto_detect">(Авто) %1$s</string>
<string name="cannot_auto_detect">(Автоопределение невозможно)</string>
<string name="boot_image_title">Распоожение boot-образа</string>
<string name="boot_image_title">Расположение boot-образа</string>
<string name="detect_button">Определить</string>
<string name="advanced_settings_title">Расширенные настройки</string>
<string name="keep_force_encryption">Оставить принуд. шифрование</string>
@@ -36,8 +36,8 @@
<string name="current_magisk_title">Текущая версия Magisk: %1$s</string>
<string name="install_magisk_title">Последняя версия Magisk: %1$s</string>
<string name="uninstall">Удалить</string>
<string name="reboot_countdown">Перезагрузка через %1$d</string>
<string name="uninstall_magisk_title">Удалить Magisk</string>
<string name="uninstall_magisk_msg">Все модули были отключены либо удалены. Root-доступ будет удален, данные зашифруются, если до сих пор не были зашифрованы</string>
<string name="update">Обновить %1$s</string>
<!--Module Fragment-->
@@ -88,7 +88,6 @@
<string name="install_error">Ошибка во время установки!</string>
<string name="invalid_zip">В архив отсутствует модуль Magisk!</string>
<string name="reboot">Перезагрузка</string>
<string name="zip_process_msg">Обработка архива …</string>
<string name="downloading_toast">Скачивание %1$s</string>
<string name="magisk_update_title">Доступно новое обновление Magisk!</string>
<string name="settings_reboot_toast">Для применения настроек выполните перезагрузку</string>
@@ -97,12 +96,31 @@
<string name="safetyNet_hide_notice">Данное приложение использует SafetyNet.\nУже обработано MagiskHide по умолчанию</string>
<string name="process_error">Ошибка обработки</string>
<string name="internal_storage">Архив расположен:\n[Внутреннее Хранилище]%1$s</string>
<string name="zip_download_title">Загрузка</string>
<string name="zip_download_msg">Загрузка архива (%1$d%%) …</string>
<string name="zip_process_title">Обработка</string>
<string name="zip_process_msg">Обработка архива…</string>
<string name="manual_boot_image">Пожалуйста, выберите вручную boot-образ!</string>
<string name="manager_update_title">Доступно новое обновление менеджера Magisk!</string>
<string name="manager_download_install">Нажмите, чтобы скачать и установить</string>
<string name="magisk_updates">Обновления Magisk</string>
<string name="flashing">Прошивка…</string>
<string name="hide_manager_toast">Скрытие Менеджера Magisk…</string>
<string name="hide_manager_fail_toast">Скрытие Менеджера Magisk неудачно…</string>
<string name="download_zip_only">Загрузка только архива</string>
<string name="patch_boot_file">Пропатчить boot-образ</string>
<string name="direct_install">Непосредственная установка (рекомендуется)</string>
<string name="install_second_slot">Установить во Второй Слот (после OTA)</string>
<string name="select_method">Выбрать способ</string>
<string name="no_boot_file_patch_support">Целевая версия Magisk не поддерживает патчинг boot-образа</string>
<string name="boot_file_patch_msg">Выберите оригинальный дамп boot-образа, .img либо .img.tar формата</string>
<string name="complete_uninstall">Удаление завершено</string>
<string name="restore_stock_boot">Восстановить оригинальный boot-образ</string>
<string name="restore_done">Восстановление завершено!</string>
<string name="restore_fail">Резервная копия отсутствует!</string>
<string name="uninstall_toast">Удаление менеджера Magisk в течении 5 секунд, затем, пожалуйста, вручную выполните перезагрузку</string>
<string name="proprietary_title">Загрузка собственного кода</string>
<string name="proprietary_notice">Менеджер Magisk является свободно распространяемым приложением, поэтому он не содержит собственный код API SafetyNet от Google.\n\nРазрешите ли Вы менеджеру Magisk загрузить расширение (содержит GoogleApiClient) для проверки SafetyNet?</string>
<!--Settings Activity -->
<string name="settings_general_category">Основные</string>
@@ -162,12 +180,6 @@
<string name="requester_summary">Сессии Суперпользователя наследуют пространство имен запрашивающего</string>
<string name="isolate_summary">Каждая сессия Суперпользователя будет иметь собственное изолированное пространство имен</string>
<string name="settings_development_category">Разработка</string>
<string name="settings_developer_logging_title">Расширенная история</string>
<string name="settings_developer_logging_summary">Включить подробнейшее ведение истории отладки</string>
<string name="settings_shell_logging_title">История команд оболочки</string>
<string name="settings_shell_logging_summary">Включить подробную запись всех команд оболочки и их вывод</string>
<!--Superuser-->
<string name="su_request_title">Запрос прав Суперпользователя</string>
<string name="deny_with_str">Отказать %1$s</string>

View File

@@ -20,7 +20,6 @@
<string name="safetyNet_check_text">Tryck för att starta SafetyNet-kontroll</string>
<string name="checking_safetyNet_status">Kontrollerar SafetyNet-status…</string>
<string name="safetyNet_check_success">SafetyNet-kontroll lyckades</string>
<string name="safetyNet_no_response">Kan inte kontrollera SafetyNet, inget internet?</string>
<string name="safetyNet_network_loss">Tappade nätverksanslutningen</string>
<string name="safetyNet_service_disconnected">Tjänsten har dödats</string>
<string name="safetyNet_res_invalid">Svaret är ogiltigt</string>
@@ -36,7 +35,6 @@
<string name="current_magisk_title">Installerad Magisk: %1$s</string>
<string name="install_magisk_title">Senaste Magisk: %1$s</string>
<string name="uninstall">Avinstallera</string>
<string name="reboot_countdown">Omstart om %1$d</string>
<string name="uninstall_magisk_title">Avinstallera Magisk</string>
<string name="update">Uppdatera %1$s</string>
@@ -151,12 +149,6 @@
<string name="requester_summary">Root-sessioner kommer att ärva den sökandes namnrymd</string>
<string name="isolate_summary">Varje root-session kommer att ha sin egen isolerade namnrymd</string>
<string name="settings_development_category">Apputveckling</string>
<string name="settings_developer_logging_title">Aktivera avancerad debug-loggning</string>
<string name="settings_developer_logging_summary">Markera för att aktivera detaljerad loggning</string>
<string name="settings_shell_logging_title">Aktivera felsökningsloggning av shell-kommandon</string>
<string name="settings_shell_logging_summary">Markera för att aktivera loggning av alla shell-kommandon och dess utmatning</string>
<!--Superuser-->
<string name="su_request_title">Superuser-förfrågan</string>
<string name="deny_with_str">Neka%1$s</string>

View File

@@ -1,30 +1,29 @@
<resources>
<resources>
<!--Universal-->
<!--Welcome Activity-->
<string name="modules">Modüller</string>
<string name="modules">Modüller</string>
<string name="downloads">İndir</string>
<string name="superuser">Yetkili kullanıcı</string>
<string name="log">Günlük</string>
<string name="settings">Ayarlar</string>
<string name="install">Yükle</string>
<string name="install">Yükle</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk yüklü değil</string>
<string name="magisk_version_error">Magisk yüklü değil</string>
<string name="checking_for_updates">Güncelleştirmeler denetleniyor…</string>
<string name="magisk_update_available">Magisk v%1$s mevcut!</string>
<string name="cannot_check_updates">Güncelleştirmeler denetlenemiyor, İnternet bağlantınız yok mu?</string>
<string name="root_error">Kök erişimli ama kök erişimi izni yok, izin verilmedi mi?</string>
<string name="root_error">Kök erişimli ama kök erişimi izni yok, izin verilmedi mi?</string>
<string name="not_rooted">Kök erişimli değil</string>
<string name="safetyNet_check_text">SafetyNet kontrolünü başlatmak için dokunun</string>
<string name="safetyNet_check_text">SafetyNet kontrolünü başlatmak için dokunun</string>
<string name="checking_safetyNet_status">SafetyNet durumu kontrol ediliyor…</string>
<string name="safetyNet_check_success">SafetyNet Kontrolü Başarılı</string>
<string name="safetyNet_no_response">SafetyNet doğrulanamıyor, İnternet bağlantınız yok mu?</string>
<string name="safetyNet_network_loss">Ağ bağlantısı kullanılamıyor</string>
<string name="safetyNet_network_loss">Ağ bağlantısı kullanılamıyor</string>
<string name="safetyNet_service_disconnected">Servis sonlandırıldı</string>
<string name="safetyNet_res_invalid">Yanıt geçersiz</string>
<!--Install Fragment-->
<!--Install Fragment-->
<string name="auto_detect">(Otomatik) %1$s</string>
<string name="cannot_auto_detect">(Otomatik algılanamıyor)</string>
<string name="boot_image_title">Önyükleme İmajı Konumu</string>
@@ -35,9 +34,9 @@
<string name="current_magisk_title">Yüklenmiş Magisk Sürümü: %1$s</string>
<string name="install_magisk_title">Son Magisk Sürümü: %1$s</string>
<string name="uninstall">Kaldır</string>
<string name="reboot_countdown">%1$d saniye içinde yeniden başlatılacak</string>
<string name="uninstall_magisk_title">"Magisk\'i kaldır"</string>
<string name="update">Güncelle %1$s</string>
<string name="uninstall_magisk_msg">Tüm modüller devre dışı bırakılacak/kaldırılacaktır. Kök erişimi kaldırılacak ve verileriniz şu anda şifrelenmemişse potansiyel olarak verilerinizi şifrelenecek</string>
<string name="update">Güncelle %1$s</string>
<!--Module Fragment-->
<string name="no_info_provided">(Hiçbir açıklama sağlanmadı)</string>
@@ -49,7 +48,7 @@
<string name="disable_file_removed">Modül sonraki yeniden başlatmada etkinleştirilecek</string>
<string name="author">Yapımcı: %1$s</string>
<!--Repo Fragment-->
<!--Repo Fragment-->
<string name="update_available">Güncelleme Mevcut</string>
<string name="installed">Yüklenmiş</string>
<string name="not_installed">Yüklenmemiş</string>
@@ -82,27 +81,41 @@
<string name="close">Kapat</string>
<string name="repo_install_title">%1$s yükle</string>
<string name="repo_install_msg">%1$s yüklensin mi?</string>
<string name="download">İndir</string>
<string name="download_file_error">Dosya indirme hatası</string>
<string name="download">İndir</string>
<string name="download_file_error">Dosya indirme hatası</string>
<string name="install_error">Yükleme hatası!</string>
<string name="invalid_zip">Zip Magisk Modülü değil!!</string>
<string name="reboot">Yeniden başlat</string>
<string name="zip_process_msg">Zip dosyası işleniyor</string>
<string name="downloading_toast">%1$s indiriliyor</string>
<string name="reboot">Yeniden başlat</string>
<string name="downloading_toast">%1$s indiriliyor</string>
<string name="magisk_update_title">Yeni Magisk Güncellemesi Mevcut!</string>
<string name="settings_reboot_toast">Ayarları uygulamak için yeniden başlatın</string>
<string name="release_notes">Sürüm notları</string>
<string name="repo_cache_cleared">Repo önbelleği temizlendi</string>
<string name="safetyNet_hide_notice">Bu uygulama, SafetyNet kullanıyor\nZaten MagiskHide tarafından varsayılan olarak ele alındı</string>
<string name="process_error">İşlem hatası</string>
<string name="process_error">İşlem hatası</string>
<string name="internal_storage">Zip şuraya depolandı:\n[Dahili Hafıza]%1$s</string>
<string name="zip_download_title">İndiriliyor</string>
<string name="zip_download_msg">Zip dosyası indiriliyor (%1$d%%) …</string>
<string name="zip_process_title">İşleniyor</string>
<string name="zip_process_msg">Zip dosyası işleniyor …</string>
<string name="manual_boot_image">Lütfen elle bir boot imajı seçin!</string>
<string name="manager_update_title">Yeni Magisk Manager Güncellemesi Mevcut!</string>
<string name="manager_download_install">İndirmek ve yüklemek için dokunun</string>
<string name="magisk_updates">Magisk Güncellemeleri</string>
<string name="flashing">Yükleyin</string>
<string name="hide_manager_toast">Magisk Manager\'ı Gizle…</string>
<string name="hide_manager_fail_toast">Hide Magisk Manager\'ı Gizleme başarısız oldu…</string>
<string name="download_zip_only">Yalnızca Zip Dosyasını İndir</string>
<string name="patch_boot_file">Önyükleme İmaj Dosyasını Yamala</string>
<string name="direct_install">Doğrudan Yükleme (Önerilen)</string>
<string name="select_method">Yöntem Seçin</string>
<string name="no_boot_file_patch_support">Hedef Magisk sürümü önyükleme imaj dosyasını yamalamayı desteklemez</string>
<string name="boot_file_patch_msg">.img veya .img.tar formatında stok önyükleme imajını seçin</string>
<string name="complete_uninstall">Tamamen Kaldır</string>
<string name="restore_stock_boot">Stok Önyükleme Dosyasını Geri Yükle</string>
<string name="restore_done">Yenileme tamamlandı!</string>
<string name="restore_fail">Stok önyükleme yedeği yok!</string>
<!--Settings Activity -->
<string name="settings_general_category">Genel</string>
<string name="settings_dark_theme_title">Karanlık Tema</string>
@@ -111,13 +124,21 @@
<string name="settings_notification_summary">Yeni sürüm kullanılabilir olduğunda güncelleme bildirimlerini göster</string>
<string name="settings_clear_cache_title">Repo Önbelleğini Temizle</string>
<string name="settings_clear_cache_summary">Çevrimiçi repolar için önbellek bilgilerini temizle, uygulamayı çevrimiçi yenilemeye zorla</string>
<string name="settings_hide_manager_title">Magisk Manager\'ı Gizle</string>
<string name="settings_hide_manager_summary">Magisk Manager\'ı geçici olarak gizle.\nBu, \"Unhide Magisk Manager\" adlı yeni bir uygulama yükleyecektir.</string>
<string name="language">Dil</string>
<string name="system_default">(Sistem Varsayılanı)</string>
<string name="settings_update">Güncelleme Ayarları</string>
<string name="settings_update_channel_title">Güncelleme Kanalı</string>
<string name="settings_update_stable">Kararlı</string>
<string name="settings_update_beta">Beta</string>
<string name="settings_boot_format_title">Yamalı Önyükleme Formatı</string>
<string name="settings_boot_format_summary">Yamalı önyükleme imaj dosyasının formatını seçin\nFastboot/indirme modunda yüklemek için .img seçeneğini seçin; ODIN ile yüklemek için .img.tar\'ı seçin.</string>
<string name="settings_core_only_title">Magisk Yalnızca Çekirdek Modu</string>
<string name="settings_core_only_summary">Yalnızca temel özellikleri etkinleştirin, tüm modüller yüklenmez. MagiskSU, MagiskHide ve host yine de etkinleştirilecektir</string>
<string name="settings_magiskhide_summary">"Magisk\'i çeşitli algılamalardan gizle"</string>
<string name="settings_hosts_title">Sistemsiz host</string>
<string name="settings_hosts_title">Sistemsiz host</string>
<string name="settings_hosts_summary">Reklam engelleme uygulamaları için sistemsiz host desteği</string>
<string name="settings_su_app_adb">Uygulamalar ve ADB</string>
@@ -153,12 +174,6 @@
<string name="requester_summary">Kök oturumları, istekte bulunanın ad alanını devralır</string>
<string name="isolate_summary">Her bir kök oturumunun kendi izole ad alanı olacaktır</string>
<string name="settings_development_category">Uygulama Geliştirme</string>
<string name="settings_developer_logging_title">Gelişmiş hata ayıklama günlüğünü etkinleştir</string>
<string name="settings_developer_logging_summary">Ayrıntılı günlüğü etkinleştirmek için bunu işaretleyin</string>
<string name="settings_shell_logging_title">Kabuk komut hata ayıklama günlüğünü etkinleştir</string>
<string name="settings_shell_logging_summary">Tüm kabuk komutlarını ve çıktısını günlüğe kaydetmeyi etkinleştirmek için bunu işaretleyin</string>
<!--Superuser-->
<string name="su_request_title">Yetkili Kullanıcı İsteği</string>
<string name="deny_with_str">Reddet%1$s</string>

View File

@@ -6,6 +6,8 @@
<string name="superuser">Суперкористувач</string>
<string name="log">Логи</string>
<string name="settings">Налаштування</string>
<!--Status Fragment-->
<string name="install">Встановлення</string>
<!--Status Fragment-->
@@ -18,8 +20,7 @@
<string name="not_rooted">Root відсутній</string>
<string name="safetyNet_check_text">Перевірити статус SafetyNet</string>
<string name="checking_safetyNet_status">Перевірка статусу SafetyNet…</string>
<string name="safetyNet_check_success">Перевірку SafetyNet пройдено</string>
<string name="safetyNet_no_response">Неможливо виконати перевірку SafetyNet</string>
<string name="safetyNet_check_success">Перевірку SafetyNet завершено</string>
<string name="safetyNet_network_loss">Підключення до інтернету втрачено</string>
<string name="safetyNet_service_disconnected">Службу зупинено</string>
<string name="safetyNet_res_invalid">Невірна відповідь</string>
@@ -35,10 +36,9 @@
<string name="current_magisk_title">Поточна версія Magisk: %1$s</string>
<string name="install_magisk_title">Найновіша версія Magisk: %1$s</string>
<string name="uninstall">Видалити</string>
<string name="reboot_countdown">Перезавантаження через %1$d</string>
<string name="uninstall_magisk_title">Видалити Magisk</string>
<string name="update">Оновити %1$s</string>
<string name="uninstall_magisk_msg">Ця дія призведе до видалення всіх модулів, MagiskSU, і може зашифрувати дані, якщо вони не зашифровані.\nВпевнені, що бажаєте продовжити?</string>
<!--Module Fragment-->
<string name="no_info_provided">(Немає наданої інформації)</string>
<string name="no_modules_found">Модулів не знайдено</string>
@@ -48,6 +48,7 @@
<string name="disable_file_created">Модуль вимкнеться при перезавантаженні</string>
<string name="disable_file_removed">Модуль увімкнеться при перезавантаженні</string>
<string name="author">Автор: %1$s</string>
<string name="update">Оновити %1$s</string>
<!--Repo Fragment-->
<string name="update_available">Доступне оновлення</string>
@@ -67,7 +68,7 @@
<string name="app_developers">Головні розробники</string>
<string name="app_developers_"><![CDATA[Програму створив <a href="https://github.com/topjohnwu">topjohnwu</a>, разом з <a href="https://github.com/d8ahazard">Digitalhigh</a> і <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
<string name="app_changelog">Список змін</string>
<string name="translators">Перекладачі</string>
<string name="translators" />
<string name="app_version">Версія</string>
<string name="app_source_code">Вихідний код</string>
<string name="donation">Підтримати проект</string>
@@ -94,6 +95,8 @@
<string name="release_notes">Особливості версії</string>
<string name="repo_cache_cleared">Кеш репозиторію очищено</string>
<string name="safetyNet_hide_notice">Ця програма використовує SafetyNet.\nВже опрацьовано MagiskHide за замовчуванням</string>
<string name="zip_download_title">Завантаження</string>
<string name="zip_download_msg">Завантаження zip файлу (%1$d%%) …</string>
<string name="process_error">Помилка опрацювання</string>
<string name="internal_storage">Архів розташований:\n[Внутрішнє Сховище]%1$s</string>
<string name="zip_process_title">Опрацювання</string>
@@ -102,7 +105,19 @@
<string name="manager_download_install">Натисніть, щоб завантажити і встановити</string>
<string name="magisk_updates">Оновлення Magisk</string>
<string name="flashing">Прошивання</string>
<string name="hide_manager_toast">Приховування Magisk Manager…</string>
<string name="hide_manager_fail_toast">Не вдалося приховати Magisk Manager…</string>
<string name="download_zip_only">Тільки завантажити</string>
<string name="patch_boot_file">Пропатчити boot образ</string>
<string name="direct_install">Пряме встановлення (рекомендовано)</string>
<string name="select_method">Виберіть спосіб</string>
<string name="no_boot_file_patch_support">Цільова версія Magisk не підтримує пропатчування boot образу</string>
<string name="boot_file_patch_msg">Виберіть дамп заводського boot образу в форматі .img чи .img.tar</string>
<string name="complete_uninstall">Видалення виконано</string>
<string name="restore_stock_boot">Відновити заводський образ</string>
<string name="restore_done">Відновлення завершено!</string>
<string name="restore_fail">Немає резервної копії заводського boot образу</string>
<!--Settings Activity -->
<string name="settings_general_category">Основні</string>
<string name="settings_dark_theme_title">Темна тема</string>
@@ -111,8 +126,16 @@
<string name="settings_notification_summary">Показувати сповіщення про оновлення в Панелі сповіщень, коли доступна нова версія</string>
<string name="settings_clear_cache_title">Очищення кешу</string>
<string name="settings_clear_cache_summary">Очистити збережену інформацію про мережеві репозиторії, змушуючи програму примусово оновлюватися через Інтернет</string>
<string name="settings_hide_manager_title">Приховати Magisk Manager</string>
<string name="settings_hide_manager_summary">Тимчасово приховати Magisk Manager.\nЦя дія встановить новий додаток \"Unhide Magisk Manager\"</string>
<string name="language">Мова</string>
<string name="system_default">Стандартна (системна)</string>
<string name="settings_update">Оновити налаштування</string>
<string name="settings_update_channel_title">Канал оновлення</string>
<string name="settings_update_stable">Стабільний реліз</string>
<string name="settings_update_beta">Бета реліз</string>
<string name="settings_boot_format_title">Формат пропатченого образу</string>
<string name="settings_boot_format_summary">Виберіть формат вихідного пропатченого boot образу.\n.img - для прошивання через fastboot/download режим;\n.img.tar - для прошивання через ODIN.</string>
<string name="settings_core_only_title">Режим ядра Magisk</string>
<string name="settings_core_only_summary">Увімкнути тільки можливості ядра, всі модулі не будуть активними. MagiskSU, MagiskHide і позасистемні хости залишуться увімкненими</string>
@@ -153,12 +176,6 @@
<string name="requester_summary">Сеанси Суперкористувача наслідують простір імен запитувача</string>
<string name="isolate_summary">Кожнен сеанс Суперкористувача має власний ізольований простір імен</string>
<string name="settings_development_category">Розробники</string>
<string name="settings_developer_logging_title">Розширені логи</string>
<string name="settings_developer_logging_summary">Увімкнути детальне ведення логів налагодження</string>
<string name="settings_shell_logging_title">Логи команд оболонки</string>
<string name="settings_shell_logging_summary">Увімкнути детальний запис всіх команд оболонки і їх виведення</string>
<!--Superuser-->
<string name="su_request_title">Запит прав Суперкористувача</string>
<string name="deny_with_str">Відмовити %1$s</string>

View File

@@ -19,7 +19,6 @@
<string name="not_rooted">Chưa root</string>
<string name="safetyNet_check_text">Chạm để bắt đầu kiểm tra SafetyNet</string>
<string name="checking_safetyNet_status">Đang kiểm tra trạng thái SafetyNet…</string>
<string name="safetyNet_no_response">Không thể kiểm tra SafetyNet, không có Internet?</string>
<!--Install Fragment-->
<string name="auto_detect">(Tự động) %1$s</string>
@@ -32,7 +31,6 @@
<string name="current_magisk_title">Đã cài đặt Magisk phiên bản: %1$s</string>
<string name="install_magisk_title">Phiên bản Magisk mới nhất: %1$s</string>
<string name="uninstall">Gỡ bỏ</string>
<string name="reboot_countdown">Khởi động lại trong %1$d</string>
<string name="uninstall_magisk_title">Gỡ bỏ Magisk</string>
<!--Module Fragment-->
@@ -122,12 +120,6 @@
<string name="superuser_notification">Thông báo Superuser</string>
<string name="request_timeout_summary">%1$s giây</string>
<string name="settings_development_category">Phát triển ứng dụng</string>
<string name="settings_developer_logging_title">Kích hoạt ghi nhận gỡ rối nâng cao</string>
<string name="settings_developer_logging_summary">Chọn để kích hoạt ghi nhận chi tiết</string>
<string name="settings_shell_logging_title">Kích hoạt ghi nhận gỡ rối lệnh shell</string>
<string name="settings_shell_logging_summary">Chọn để kích hoạt ghi nhận tất cả các lệnh shell và kết quả xuất ra</string>
<!--Superuser-->
<string name="su_request_title">Yêu cầu Superuser</string>
<string name="deny_with_str">Từ chối%1$s</string>

View File

@@ -20,7 +20,6 @@
<string name="safetyNet_check_text">点击启动 SafetyNet 检查</string>
<string name="checking_safetyNet_status">正在检查 SafetyNet 状态…</string>
<string name="safetyNet_check_success">SafetyNet 检查成功</string>
<string name="safetyNet_no_response">无法验证 SafetyNet没有网络连接</string>
<string name="safetyNet_network_loss">网络连接不可用</string>
<string name="safetyNet_service_disconnected">服务已被取消</string>
<string name="safetyNet_res_invalid">回传值无效</string>
@@ -36,7 +35,6 @@
<string name="current_magisk_title">已安装 Magisk 版本:%1$s</string>
<string name="install_magisk_title">最新的 Magisk 版本:%1$s</string>
<string name="uninstall">卸载</string>
<string name="reboot_countdown">将在 %1$d 后重启</string>
<string name="uninstall_magisk_title">卸载 Magisk</string>
<string name="update">更新 %1$s</string>
@@ -165,12 +163,6 @@
<string name="requester_summary">ROOT 会话继承原程序的命名空间</string>
<string name="isolate_summary">每一个 ROOT 会话使用自己独立的命名空间</string>
<string name="settings_development_category">开发</string>
<string name="settings_developer_logging_title">启用高级日志记录</string>
<string name="settings_developer_logging_summary">勾选此项以启用更加详细的日志记录。</string>
<string name="settings_shell_logging_title">启用 shell 命令日志记录</string>
<string name="settings_shell_logging_summary">勾选此项以启用对所有 shell 命令及输出进行日志记录</string>
<!--Superuser-->
<string name="su_request_title">超级用户请求</string>
<string name="deny_with_str">拒绝 %1$s</string>

View File

@@ -19,7 +19,6 @@
<string name="not_rooted">未 ROOT</string>
<string name="safetyNet_check_text">點擊啟動 SafetyNet 檢查</string>
<string name="checking_safetyNet_status">正在檢查 SafetyNet 狀態…</string>
<string name="safetyNet_no_response">無法檢查 SafetyNet沒有網絡連線</string>
<!--Install Fragment-->
<string name="auto_detect">(自動) %1$s</string>
@@ -31,7 +30,6 @@
<string name="current_magisk_title">已安裝 Magisk 版本:%1$s</string>
<string name="install_magisk_title">最新的 Magisk 版本:%1$s</string>
<string name="uninstall">解除安裝</string>
<string name="reboot_countdown">將在 %1$d 後重啟</string>
<!--Module Fragment-->
<string name="no_info_provided">(未提供資訊)</string>
@@ -112,12 +110,6 @@
<string name="superuser_notification">超級用戶通知</string>
<string name="request_timeout_summary">%1$s 秒</string>
<string name="settings_development_category">開發</string>
<string name="settings_developer_logging_title">啟用進階日誌記錄</string>
<string name="settings_developer_logging_summary">勾選此項以啟用更加詳細的日誌記錄。</string>
<string name="settings_shell_logging_title">啟用 shell 命令日誌記錄</string>
<string name="settings_shell_logging_summary">勾選此項以啟用對所有 shell 命令及輸出進行日誌記錄</string>
<!--Superuser-->
<string name="su_request_title">超級用戶請求</string>
<string name="deny_with_str">拒絕 %1$s</string>
@@ -206,12 +198,17 @@
<string name="settings_update">更新設定</string>
<string name="settings_boot_format_title">已補丁 Boot 映像輸出格式</string>
<string name="zip_download_title">正在下載</string>
<string name="zip_download_msg">正在下載 Zip 文件 …</string>
<string name="zip_download_msg">正在下載 Zip 文件 (%1$d%%)</string>
<string name="settings_boot_format_summary">選擇已補丁 Boot 映像文件輸出格式\n若要透過 fastboot/download 模式刷入,請選擇 .img 格式;若要透過 ODIN 刷入,則選擇 .img.tar\n</string>
<string name="complete_uninstall">完全解除安裝</string>
<string name="restore_stock_boot">還原原廠 boot 映像</string>
<string name="restore_done">還原完成!</string>
<string name="restore_fail">原廠 boot 映像備份不存在!</string>
<string name="boot_file_patch_msg">選擇原廠 boot 映像備份;支援 .img 以及 .img.tar 格式</string>
<string name="install_second_slot">安裝到第二分區 (安裝完OTA後)</string>
<string name="uninstall_toast">將在 5 秒內解除安裝 Magisk Manager接下來請手動重新啟動</string>
<string name="safetyNet_api_error">SafetyNet API 錯誤</string>
<string name="proprietary_title">下載非開源程式</string>
<string name="proprietary_notice">Magisk Manager 是一個 100% 開源的程式,因此不會包含 Google 私有所有權的 SafetyNet API 程式碼。\n\n你允許 Magisk Manager 下載一個擴充包 (包含 GoogleApiClient) 以執行 SafetyNet 檢查嗎?</string>
</resources>

View File

@@ -22,8 +22,8 @@
<string name="not_rooted">Not rooted</string>
<string name="safetyNet_check_text">Tap to start SafetyNet check</string>
<string name="checking_safetyNet_status">Checking SafetyNet status…</string>
<string name="safetyNet_check_success">SafetyNet Check Was Successful</string>
<string name="safetyNet_no_response">Cannot verify SafetyNet, no internet?</string>
<string name="safetyNet_check_success">SafetyNet Check Success</string>
<string name="safetyNet_api_error">SafetyNet API Error</string>
<string name="safetyNet_network_loss">Network connection unavailable</string>
<string name="safetyNet_service_disconnected">Service has been killed</string>
<string name="safetyNet_res_invalid">The response is invalid</string>
@@ -39,7 +39,6 @@
<string name="current_magisk_title">Installed Magisk Version: %1$s</string>
<string name="install_magisk_title">Latest Magisk Version: %1$s</string>
<string name="uninstall">Uninstall</string>
<string name="reboot_countdown">Rebooting in %1$d</string>
<string name="uninstall_magisk_title">Uninstall Magisk</string>
<string name="uninstall_magisk_msg">All modules will be disabled/removed. Root will be removed, and potentially encrypt your data if your data is not currently encrypted</string>
<string name="update">Update %1$s</string>
@@ -101,7 +100,7 @@
<string name="process_error">Process error</string>
<string name="internal_storage">The zip is stored in:\n[Internal Storage]%1$s</string>
<string name="zip_download_title">Downloading</string>
<string name="zip_download_msg">Downloading zip file …</string>
<string name="zip_download_msg">Downloading zip file (%1$d%%)</string>
<string name="zip_process_title">Processing</string>
<string name="zip_process_msg">Processing zip file …</string>
<string name="manual_boot_image">Please manually select a boot image!</string>
@@ -114,6 +113,7 @@
<string name="download_zip_only">Download Zip Only</string>
<string name="patch_boot_file">Patch Boot Image File</string>
<string name="direct_install">Direct Install (Recommend)</string>
<string name="install_second_slot">Install to Second Slot (After OTA)</string>
<string name="select_method">Select Method</string>
<string name="no_boot_file_patch_support">Target Magisk version doesn\'t support boot image file patching</string>
<string name="boot_file_patch_msg">Select stock boot image dump in .img or .img.tar format</string>
@@ -121,6 +121,9 @@
<string name="restore_stock_boot">Restore Stock Boot</string>
<string name="restore_done">Restoration done!</string>
<string name="restore_fail">Stock backup does not exist!</string>
<string name="uninstall_toast">Uninstalling Magisk Manager in 5 seconds, please manually reboot afterwards</string>
<string name="proprietary_title">Download Proprietary Code</string>
<string name="proprietary_notice">Magisk Manager is FOSS so doesn\'t contain Google\'s proprietary SafetyNet API code.\n\nDo you allow Magisk Manager to download an extension (contains GoogleApiClient) for SafetyNet checks?</string>
<!--Settings Activity -->
<string name="settings_general_category">General</string>
@@ -180,12 +183,6 @@
<string name="requester_summary">Root sessions will inherit its requester\'s namespace</string>
<string name="isolate_summary">Each root session will have its own isolated namespace</string>
<string name="settings_development_category">App Development</string>
<string name="settings_developer_logging_title">Enable advanced debug logging</string>
<string name="settings_developer_logging_summary">Check this to enable verbose logging</string>
<string name="settings_shell_logging_title">Enable shell command debug logging</string>
<string name="settings_shell_logging_summary">Check this to enable logging all shell commands and its output</string>
<!--Superuser-->
<string name="su_request_title">Superuser Request</string>
<string name="deny_with_str">Deny%1$s</string>

View File

@@ -114,22 +114,4 @@
</PreferenceCategory>
<PreferenceCategory
android:key="developer"
android:title="@string/settings_development_category">
<SwitchPreference
android:key="developer_logging"
android:defaultValue="false"
android:title="@string/settings_developer_logging_title"
android:summary="@string/settings_developer_logging_summary" />
<SwitchPreference
android:key="shell_logging"
android:defaultValue="false"
android:title="@string/settings_shell_logging_title"
android:summary="@string/settings_shell_logging_summary" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -5,9 +5,10 @@ buildscript {
jcenter()
mavenCentral()
maven { url "https://maven.google.com" }
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-beta4'
classpath 'com.android.tools.build:gradle:3.0.0-rc1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -18,6 +19,7 @@ allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
google()
}
}

View File

@@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
buildToolsVersion "26.0.2"
defaultConfig {
minSdkVersion 21
targetSdkVersion 26

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

View File

@@ -1,6 +1,5 @@
#Sat Sep 02 18:20:31 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip

1
jarsigner/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

37
jarsigner/build.gradle Normal file
View File

@@ -0,0 +1,37 @@
apply plugin: 'java-library'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'java'
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
jar {
manifest {
attributes 'Main-Class': 'com.topjohnwu.jarsigner.CommandLine'
}
}
shadowJar {
classifier = 'fat'
version = null
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
}
}
repositories {
jcenter()
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'org.bouncycastle:bcprov-jdk15on:1.58'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.58'
}

View File

@@ -0,0 +1,34 @@
package com.topjohnwu.jarsigner;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class ByteArrayStream extends ByteArrayOutputStream {
public byte[] getBuf() {
return buf;
}
public synchronized void readFrom(InputStream is) {
readFrom(is, Integer.MAX_VALUE);
}
public synchronized void readFrom(InputStream is, int len) {
int read;
byte buffer[] = new byte[4096];
try {
while ((read = is.read(buffer, 0, len < buffer.length ? len : buffer.length)) > 0) {
write(buffer, 0, read);
len -= read;
}
} catch (IOException e) {
e.printStackTrace();
}
}
public synchronized void writeTo(OutputStream out, int off, int len) throws IOException {
out.write(buf, off, len);
}
public ByteArrayInputStream getInputStream() {
return new ByteArrayInputStream(buf, 0, count);
}
}

View File

@@ -0,0 +1,42 @@
package com.topjohnwu.jarsigner;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.Security;
public class CommandLine {
public static void main(String[] args) {
boolean minSign = false;
int argStart = 0;
if (args.length < 4) {
System.err.println("Usage: zipsigner [-m] publickey.x509[.pem] privatekey.pk8 input.jar output.jar");
System.exit(2);
}
if (args[0].equals("-m")) {
minSign = true;
argStart = 1;
}
SignAPK.sBouncyCastleProvider = new BouncyCastleProvider();
Security.insertProviderAt(SignAPK.sBouncyCastleProvider, 1);
File pubKey = new File(args[argStart]);
File privKey = new File(args[argStart + 1]);
File input = new File(args[argStart + 2]);
File output = new File(args[argStart + 3]);
try (InputStream pub = new FileInputStream(pubKey);
InputStream priv = new FileInputStream(privKey);
JarMap jar = new JarMap(input, false)) {
SignAPK.signZip(pub, priv, jar, output, minSign);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}

View File

@@ -0,0 +1,153 @@
package com.topjohnwu.jarsigner;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/*
* A universal random access interface for both JarFile and JarInputStream
*
* In the case when JarInputStream is provided to constructor, the whole stream
* will be loaded into memory for random access purposes.
* On the other hand, when a JarFile is provided, it simply works as a wrapper.
* */
public class JarMap implements Closeable, AutoCloseable {
private JarFile jarFile;
private JarInputStream jis;
private InputStream is;
private File file;
private boolean isInputStream = false, hasLoaded = false, verify;
private LinkedHashMap<String, JarEntry> bufMap = new LinkedHashMap<>();
public JarMap(File file) throws IOException {
this(file, true);
}
public JarMap(File file, boolean verify) throws IOException {
this(file, verify, ZipFile.OPEN_READ);
}
public JarMap(File file, boolean verify, int mode) throws IOException {
this.file = file;
jarFile = new JarFile(file, verify, mode);
}
public JarMap(String name) throws IOException {
this(new File(name));
}
public JarMap(String name, boolean verify) throws IOException {
this(new File(name), verify);
}
public JarMap(InputStream is) throws IOException {
this(is, true);
}
public JarMap(InputStream is, boolean verify) throws IOException {
isInputStream = true;
this.is = is;
this.verify = verify;
}
private void loadJarInputStream() {
if (!isInputStream || hasLoaded) return;
hasLoaded = true;
JarEntry entry;
try {
jis = new JarInputStream(is, verify);
while ((entry = jis.getNextJarEntry()) != null) {
bufMap.put(entry.getName(), new JarMapEntry(entry, jis));
}
} catch (IOException e) {
e.printStackTrace();
}
}
public InputStream getInputStream() {
try {
return isInputStream ? is : new FileInputStream(file);
} catch (FileNotFoundException e) {
return null;
}
}
public Manifest getManifest() throws IOException {
loadJarInputStream();
return isInputStream ? jis.getManifest() : jarFile.getManifest();
}
public InputStream getInputStream(ZipEntry ze) throws IOException {
loadJarInputStream();
return isInputStream ? ((JarMapEntry) bufMap.get(ze.getName())).getInputStream() :
jarFile.getInputStream(ze);
}
public OutputStream getOutputStream(ZipEntry ze) {
if (!isInputStream) // Only support inputstream mode
return null;
loadJarInputStream();
ByteArrayStream bs = ((JarMapEntry) bufMap.get(ze.getName())).data;
bs.reset();
return bs;
}
public byte[] getRawData(ZipEntry ze) throws IOException {
if (isInputStream) {
loadJarInputStream();
return ((JarMapEntry) bufMap.get(ze.getName())).data.toByteArray();
} else {
ByteArrayStream bytes = new ByteArrayStream();
bytes.readFrom(jarFile.getInputStream(ze));
return bytes.toByteArray();
}
}
public Enumeration<JarEntry> entries() {
loadJarInputStream();
return isInputStream ? Collections.enumeration(bufMap.values()) : jarFile.entries();
}
public ZipEntry getEntry(String name) {
return getJarEntry(name);
}
public JarEntry getJarEntry(String name) {
loadJarInputStream();
return isInputStream ? bufMap.get(name) : jarFile.getJarEntry(name);
}
@Override
public void close() throws IOException {
if (isInputStream)
is.close();
else
jarFile.close();
}
private static class JarMapEntry extends JarEntry {
ByteArrayStream data;
JarMapEntry(JarEntry je, InputStream is) {
super(je);
data = new ByteArrayStream();
data.readFrom(is);
}
InputStream getInputStream() {
return data.getInputStream();
}
}
}

View File

@@ -0,0 +1,529 @@
package com.topjohnwu.jarsigner;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.encoders.Base64;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
/*
* Modified from from AOSP(Marshmallow) SignAPK.java
* */
public class SignAPK {
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
private static final String CERT_SIG_NAME = "META-INF/CERT.%s";
public static Provider sBouncyCastleProvider;
// bitmasks for which hash algorithms we need the manifest to include.
private static final int USE_SHA1 = 1;
private static final int USE_SHA256 = 2;
static {
SignAPK.sBouncyCastleProvider = new BouncyCastleProvider();
Security.insertProviderAt(SignAPK.sBouncyCastleProvider, 1);
}
public static void signZip(InputStream publicIn, InputStream privateIn,
JarMap input, File output, boolean minSign) throws Exception {
int alignment = 4;
BufferedOutputStream outputFile;
int hashes = 0;
X509Certificate publicKey = readPublicKey(publicIn);
hashes |= getDigestAlgorithm(publicKey);
// Set the ZIP file timestamp to the starting valid time
// of the 0th certificate plus one hour (to match what
// we've historically done).
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
PrivateKey privateKey = readPrivateKey(privateIn);
outputFile = new BufferedOutputStream(new FileOutputStream(output));
if (minSign) {
signWholeFile(input.getInputStream(), publicKey, privateKey, outputFile);
} else {
JarOutputStream outputJar = new JarOutputStream(outputFile);
// For signing .apks, use the maximum compression to make
// them as small as possible (since they live forever on
// the system partition). For OTA packages, use the
// default compression level, which is much much faster
// and produces output that is only a tiny bit larger
// (~0.1% on full OTA packages I tested).
outputJar.setLevel(9);
Manifest manifest = addDigestsToManifest(input, hashes);
copyFiles(manifest, input, outputJar, timestamp, alignment);
signFile(manifest, input, publicKey, privateKey, outputJar);
outputJar.close();
}
input.close();
outputFile.close();
}
/**
* Return one of USE_SHA1 or USE_SHA256 according to the signature
* algorithm specified in the cert.
*/
private static int getDigestAlgorithm(X509Certificate cert) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
if ("SHA1WITHRSA".equals(sigAlg) ||
"MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above.
return USE_SHA1;
} else if (sigAlg.startsWith("SHA256WITH")) {
return USE_SHA256;
} else {
throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
"\" in cert [" + cert.getSubjectDN());
}
}
/** Returns the expected signature algorithm for this key type. */
private static String getSignatureAlgorithm(X509Certificate cert) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
if ("RSA".equalsIgnoreCase(keyType)) {
if (getDigestAlgorithm(cert) == USE_SHA256) {
return "SHA256withRSA";
} else {
return "SHA1withRSA";
}
} else if ("EC".equalsIgnoreCase(keyType)) {
return "SHA256withECDSA";
} else {
throw new IllegalArgumentException("unsupported key type: " + keyType);
}
}
// Files matching this pattern are not copied to the output.
private static Pattern stripPattern =
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
private static X509Certificate readPublicKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(input);
} finally {
input.close();
}
}
/** Read a PKCS#8 format private key. */
private static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
byte[] buffer = new byte[4096];
int size = input.read(buffer);
byte[] bytes = Arrays.copyOf(buffer, size);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
/**
* Add the hash(es) of every file to the manifest, creating it if
* necessary.
*/
private static Manifest addDigestsToManifest(JarMap jar, int hashes)
throws IOException, GeneralSecurityException {
Manifest input = jar.getManifest();
Manifest output = new Manifest();
Attributes main = output.getMainAttributes();
if (input != null) {
main.putAll(input.getMainAttributes());
} else {
main.putValue("Manifest-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
}
MessageDigest md_sha1 = null;
MessageDigest md_sha256 = null;
if ((hashes & USE_SHA1) != 0) {
md_sha1 = MessageDigest.getInstance("SHA1");
}
if ((hashes & USE_SHA256) != 0) {
md_sha256 = MessageDigest.getInstance("SHA256");
}
byte[] buffer = new byte[4096];
int num;
// We sort the input entries by name, and add them to the
// output manifest in sorted order. We expect that the output
// map will be deterministic.
TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
JarEntry entry = e.nextElement();
byName.put(entry.getName(), entry);
}
for (JarEntry entry: byName.values()) {
String name = entry.getName();
if (!entry.isDirectory() &&
(stripPattern == null || !stripPattern.matcher(name).matches())) {
InputStream data = jar.getInputStream(entry);
while ((num = data.read(buffer)) > 0) {
if (md_sha1 != null) md_sha1.update(buffer, 0, num);
if (md_sha256 != null) md_sha256.update(buffer, 0, num);
}
Attributes attr = null;
if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes();
if (md_sha1 != null) {
attr.putValue("SHA1-Digest",
new String(Base64.encode(md_sha1.digest()), "ASCII"));
}
if (md_sha256 != null) {
attr.putValue("SHA-256-Digest",
new String(Base64.encode(md_sha256.digest()), "ASCII"));
}
output.getEntries().put(name, attr);
}
}
return output;
}
/** Write to another stream and track how many bytes have been
* written.
*/
private static class CountOutputStream extends FilterOutputStream {
private int mCount;
public CountOutputStream(OutputStream out) {
super(out);
mCount = 0;
}
@Override
public void write(int b) throws IOException {
super.write(b);
mCount++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
mCount += len;
}
public int size() {
return mCount;
}
}
/** Write a .SF file with a digest of the specified manifest. */
private static void writeSignatureFile(Manifest manifest, OutputStream out,
int hash)
throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
Attributes main = sf.getMainAttributes();
main.putValue("Signature-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
MessageDigest md = MessageDigest.getInstance(
hash == USE_SHA256 ? "SHA256" : "SHA1");
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
true, "UTF-8");
// Digest of the entire manifest
manifest.write(print);
print.flush();
main.putValue(hash == USE_SHA256 ? "SHA-256-Digest-Manifest" : "SHA1-Digest-Manifest",
new String(Base64.encode(md.digest()), "ASCII"));
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
print.print("Name: " + entry.getKey() + "\r\n");
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
}
print.print("\r\n");
print.flush();
Attributes sfAttr = new Attributes();
sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest-Manifest",
new String(Base64.encode(md.digest()), "ASCII"));
sf.getEntries().put(entry.getKey(), sfAttr);
}
CountOutputStream cout = new CountOutputStream(out);
sf.write(cout);
// A bug in the java.util.jar implementation of Android platforms
// up to version 1.6 will cause a spurious IOException to be thrown
// if the length of the signature file is a multiple of 1024 bytes.
// As a workaround, add an extra CRLF in this case.
if ((cout.size() % 1024) == 0) {
cout.write('\r');
cout.write('\n');
}
}
/** Sign data and write the digital signature to 'out'. */
private static void writeSignatureBlock(
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
OutputStream out)
throws IOException,
CertificateEncodingException,
OperatorCreationException,
CMSException {
ArrayList<X509Certificate> certList = new ArrayList<>(1);
certList.add(publicKey);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))
.setProvider(sBouncyCastleProvider)
.build(privateKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder()
.setProvider(sBouncyCastleProvider)
.build())
.setDirectSignature(true)
.build(signer, publicKey));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(data, false);
ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
DEROutputStream dos = new DEROutputStream(out);
dos.writeObject(asn1.readObject());
}
/**
* Copy all the files in a manifest from input to output. We set
* the modification times in the output to a fixed time, so as to
* reduce variation in the output file and make incremental OTAs
* more efficient.
*/
private static void copyFiles(Manifest manifest, JarMap in, JarOutputStream out,
long timestamp, int alignment) throws IOException {
byte[] buffer = new byte[4096];
int num;
Map<String, Attributes> entries = manifest.getEntries();
ArrayList<String> names = new ArrayList<>(entries.keySet());
Collections.sort(names);
boolean firstEntry = true;
long offset = 0L;
// We do the copy in two passes -- first copying all the
// entries that are STORED, then copying all the entries that
// have any other compression flag (which in practice means
// DEFLATED). This groups all the stored entries together at
// the start of the file and makes it easier to do alignment
// on them (since only stored entries are aligned).
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
JarEntry outEntry = null;
if (inEntry.getMethod() != JarEntry.STORED) continue;
// Preserve the STORED method of the input entry.
outEntry = new JarEntry(inEntry);
outEntry.setTime(timestamp);
// 'offset' is the offset into the file at which we expect
// the file data to begin. This is the value we need to
// make a multiple of 'alignement'.
offset += JarFile.LOCHDR + outEntry.getName().length();
if (firstEntry) {
// The first entry in a jar file has an extra field of
// four bytes that you can't get rid of; any extra
// data you specify in the JarEntry is appended to
// these forced four bytes. This is JAR_MAGIC in
// JarOutputStream; the bytes are 0xfeca0000.
offset += 4;
firstEntry = false;
}
if (alignment > 0 && (offset % alignment != 0)) {
// Set the "extra data" of the entry to between 1 and
// alignment-1 bytes, to make the file data begin at
// an aligned offset.
int needed = alignment - (int)(offset % alignment);
outEntry.setExtra(new byte[needed]);
offset += needed;
}
out.putNextEntry(outEntry);
InputStream data = in.getInputStream(inEntry);
while ((num = data.read(buffer)) > 0) {
out.write(buffer, 0, num);
offset += num;
}
out.flush();
}
// Copy all the non-STORED entries. We don't attempt to
// maintain the 'offset' variable past this point; we don't do
// alignment on these entries.
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
JarEntry outEntry = null;
if (inEntry.getMethod() == JarEntry.STORED) continue;
// Create a new entry so that the compressed len is recomputed.
outEntry = new JarEntry(name);
outEntry.setTime(timestamp);
out.putNextEntry(outEntry);
InputStream data = in.getInputStream(inEntry);
while ((num = data.read(buffer)) > 0) {
out.write(buffer, 0, num);
}
out.flush();
}
}
// This class is to provide a file's content, but trimming out the last two bytes
// Used for signWholeFile
private static class CMSProcessableFile implements CMSTypedData {
private InputStream is;
private ASN1ObjectIdentifier type;
ByteArrayStream bos;
CMSProcessableFile(InputStream is) {
this.is = is;
type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
bos = new ByteArrayStream();
bos.readFrom(is);
}
@Override
public ASN1ObjectIdentifier getContentType() {
return type;
}
@Override
public void write(OutputStream out) throws IOException, CMSException {
bos.writeTo(out, 0, bos.size() - 2);
}
@Override
public Object getContent() {
return is;
}
byte[] getTail() {
return Arrays.copyOfRange(bos.getBuf(), bos.size() - 22, bos.size());
}
}
private static void signWholeFile(InputStream input, X509Certificate publicKey,
PrivateKey privateKey, OutputStream outputStream)
throws Exception {
ByteArrayOutputStream temp = new ByteArrayOutputStream();
// put a readable message and a null char at the start of the
// archive comment, so that tools that display the comment
// (hopefully) show something sensible.
// TODO: anything more useful we can put in this message?
byte[] message = "signed by SignApk".getBytes("UTF-8");
temp.write(message);
temp.write(0);
CMSProcessableFile cmsFile = new CMSProcessableFile(input);
writeSignatureBlock(cmsFile, publicKey, privateKey, temp);
// For a zip with no archive comment, the
// end-of-central-directory record will be 22 bytes long, so
// we expect to find the EOCD marker 22 bytes from the end.
byte[] zipData = cmsFile.getTail();
if (zipData[zipData.length-22] != 0x50 ||
zipData[zipData.length-21] != 0x4b ||
zipData[zipData.length-20] != 0x05 ||
zipData[zipData.length-19] != 0x06) {
throw new IllegalArgumentException("zip data already has an archive comment");
}
int total_size = temp.size() + 6;
if (total_size > 0xffff) {
throw new IllegalArgumentException("signature is too big for ZIP file comment");
}
// signature starts this many bytes from the end of the file
int signature_start = total_size - message.length - 1;
temp.write(signature_start & 0xff);
temp.write((signature_start >> 8) & 0xff);
// Why the 0xff bytes? In a zip file with no archive comment,
// bytes [-6:-2] of the file are the little-endian offset from
// the start of the file to the central directory. So for the
// two high bytes to be 0xff 0xff, the archive would have to
// be nearly 4GB in size. So it's unlikely that a real
// commentless archive would have 0xffs here, and lets us tell
// an old signed archive from a new one.
temp.write(0xff);
temp.write(0xff);
temp.write(total_size & 0xff);
temp.write((total_size >> 8) & 0xff);
temp.flush();
// Signature verification checks that the EOCD header is the
// last such sequence in the file (to avoid minzip finding a
// fake EOCD appended after the signature in its scan). The
// odds of producing this sequence by chance are very low, but
// let's catch it here if it does.
byte[] b = temp.toByteArray();
for (int i = 0; i < b.length-3; ++i) {
if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) {
throw new IllegalArgumentException("found spurious EOCD header at " + i);
}
}
cmsFile.write(outputStream);
outputStream.write(total_size & 0xff);
outputStream.write((total_size >> 8) & 0xff);
temp.writeTo(outputStream);
}
private static void signFile(Manifest manifest, JarMap inputJar,
X509Certificate publicKey, PrivateKey privateKey,
JarOutputStream outputJar)
throws Exception {
// Assume the certificate is valid for at least an hour.
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
// MANIFEST.MF
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey));
byte[] signedData = baos.toByteArray();
outputJar.write(signedData);
// CERT.{EC,RSA} / CERT#.{EC,RSA}
final String keyType = publicKey.getPublicKey().getAlgorithm();
je = new JarEntry(String.format(CERT_SIG_NAME, keyType));
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData),
publicKey, privateKey, outputJar);
}
}

View File

@@ -1 +1 @@
include ':app', ':unhide', ':resource'
include ':app', ':unhide', ':common', ':snet', ':jarsigner'

1
snet/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

27
snet/build.gradle Normal file
View File

@@ -0,0 +1,27 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "com.topjohnwu.sn"
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.android.gms:play-services-safetynet:11.4.2'
}

24
snet/proguard-rules.pro vendored Normal file
View File

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

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.topjohnwu.snet">
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</manifest>

View File

@@ -0,0 +1,5 @@
package com.topjohnwu.snet;
public interface SafetyNetCallback {
void onResponse(int responseCode);
}

View File

@@ -0,0 +1,118 @@
package com.topjohnwu.snet;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Base64;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.safetynet.SafetyNet;
import com.google.android.gms.safetynet.SafetyNetApi;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Method;
import java.security.SecureRandom;
public class SafetyNetHelper
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
public static final int CONNECTION_FAIL = -1;
public static final int CAUSE_SERVICE_DISCONNECTED = 0x00001;
public static final int CAUSE_NETWORK_LOST = 0x00010;
public static final int RESPONSE_ERR = 0x00100;
public static final int BASIC_PASS = 0x01000;
public static final int CTS_PASS = 0x10000;
private GoogleApiClient mGoogleApiClient;
private Context mActivity;
private int responseCode;
private SafetyNetCallback cb;
public SafetyNetHelper(Context context, SafetyNetCallback cb) {
mActivity = context;
this.cb = cb;
responseCode = 0;
}
// Entry point to start test
public void attest() {
// Connect Google Service
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(mActivity);
try {
// Use reflection to workaround FragmentActivity crap
Class<?> clazz = Class.forName("com.google.android.gms.common.api.GoogleApiClient$Builder");
for (Method m : clazz.getMethods()) {
if (m.getName().equals("enableAutoManage")) {
m.invoke(builder, mActivity, this);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
mGoogleApiClient = builder.addApi(SafetyNet.API).addConnectionCallbacks(this).build();
mGoogleApiClient.connect();
}
@Override
public void onConnectionSuspended(int i) {
cb.onResponse(i);
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
cb.onResponse(CONNECTION_FAIL);
}
@Override
public void onConnected(@Nullable Bundle bundle) {
// Create nonce
byte[] nonce = new byte[24];
new SecureRandom().nextBytes(nonce);
// Call SafetyNet
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
.setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {
@Override
public void onResult(@NonNull SafetyNetApi.AttestationResult result) {
Status status = result.getStatus();
try {
if (!status.isSuccess()) throw new JSONException("");
String json = new String(Base64.decode(
result.getJwsResult().split("\\.")[1], Base64.DEFAULT));
JSONObject decoded = new JSONObject(json);
responseCode |= decoded.getBoolean("ctsProfileMatch") ? CTS_PASS : 0;
responseCode |= decoded.getBoolean("basicIntegrity") ? BASIC_PASS : 0;
} catch (JSONException e) {
cb.onResponse(RESPONSE_ERR);
return;
}
// Disconnect
try {
// Use reflection to workaround FragmentActivity crap
Class<?> clazz = Class.forName("com.google.android.gms.common.api.GoogleApiClient");
for (Method m : clazz.getMethods()) {
if (m.getName().equals("stopAutoManage")) {
m.invoke(mGoogleApiClient, mActivity, this);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
mGoogleApiClient.disconnect();
// Return results
cb.onResponse(responseCode);
}
});
}
}

View File

@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "com.topjohnwu.unhide"
minSdkVersion 21
@@ -21,5 +21,5 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':resource')
implementation project(':common')
}