From 97cd87c13e052b19d5c377186000026e37467a02 Mon Sep 17 00:00:00 2001 From: Fox2Code Date: Wed, 2 Mar 2022 22:20:41 +0100 Subject: [PATCH] Add compat flags for modules, fix Androidacy code typo. --- .../com/fox2code/mmm/AppUpdateManager.java | 92 +++++++++++++++++++ .../java/com/fox2code/mmm/MainActivity.java | 2 + .../fox2code/mmm/ModuleViewListBuilder.java | 9 +- .../mmm/androidacy/AndroidacyActivity.java | 4 +- .../mmm/installer/InstallerActivity.java | 53 ++++++++--- .../com/fox2code/mmm/utils/PropUtils.java | 23 ++++- 6 files changed, 165 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java index 1050811..9ce29e7 100644 --- a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java +++ b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java @@ -2,25 +2,42 @@ package com.fox2code.mmm; import android.util.Log; +import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Http; import org.json.JSONArray; import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.HashMap; // See https://docs.github.com/en/rest/reference/repos#releases public class AppUpdateManager { + public static int FLAG_COMPAT_LOW_QUALITY = 0x01; + public static int FLAG_COMPAT_NO_EXT = 0x02; + public static int FLAG_COMPAT_MAGISK_CMD = 0x04; + public static int FLAG_COMPAT_NEED_32BIT = 0x08; private static final String TAG = "AppUpdateManager"; private static final AppUpdateManager INSTANCE = new AppUpdateManager(); private static final String RELEASES_API_URL = "https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/releases"; + private static final String COMPAT_API_URL = + "https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/releases"; public static AppUpdateManager getAppUpdateManager() { return INSTANCE; } + private final HashMap compatDataId = new HashMap<>(); private final Object updateLock = new Object(); + private final File compatFile; private String latestRelease; private String latestPreRelease; private long lastChecked; @@ -28,12 +45,20 @@ public class AppUpdateManager { private boolean lastCheckSuccess; private AppUpdateManager() { + this.compatFile = new File(MainApplication.getINSTANCE().getFilesDir(), "compat.txt"); this.latestRelease = MainApplication.getBootSharedPreferences() .getString("updater_latest_release", BuildConfig.VERSION_NAME); this.latestPreRelease = MainApplication.getBootSharedPreferences() .getString("updater_latest_pre_release", BuildConfig.VERSION_NAME); this.lastChecked = 0; this.preReleaseNewer = true; + if (this.compatFile.isFile()) { + try { + this.parseCompatibilityFlags(new FileInputStream(this.compatFile)); + } catch (IOException e) { + e.printStackTrace(); + } + } } // Return true if should show a notification @@ -95,6 +120,31 @@ public class AppUpdateManager { return this.peekShouldUpdate(); } + public void checkUpdateCompat() { + if (this.compatFile.exists()) { + long lastUpdate = this.compatFile.lastModified(); + if (lastUpdate <= System.currentTimeMillis() && + lastUpdate + 600_000L > System.currentTimeMillis()) { + return; // Skip update + } + } + try { + JSONObject object = new JSONObject(new String(Http.doHttpGet( + COMPAT_API_URL, false), StandardCharsets.UTF_8)); + if (object.isNull("body")) { + compatDataId.clear(); + Files.write(compatFile, new byte[0]); + return; + } + byte[] rawData = object.getString("body") + .getBytes(StandardCharsets.UTF_8); + this.parseCompatibilityFlags(new ByteArrayInputStream(rawData)); + Files.write(compatFile, rawData); + } catch (Exception e) { + Log.e("AppUpdateManager", "Failed to update compat list", e); + } + } + public boolean peekShouldUpdate() { return !(BuildConfig.VERSION_NAME.equals(this.latestRelease) || (this.preReleaseNewer && @@ -109,4 +159,46 @@ public class AppUpdateManager { public boolean isLastCheckSuccess() { return lastCheckSuccess; } + + private void parseCompatibilityFlags(InputStream inputStream) throws IOException { + compatDataId.clear(); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line; + while ((line = bufferedReader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) continue; + int i = line.indexOf('/'); + if (i == -1) continue; + int value = 0; + for (String arg : line.substring(i + 1).split(",")) { + switch (arg) { + default: + break; + case "lowQuality": + value |= FLAG_COMPAT_LOW_QUALITY; + break; + case "noExt": + value |= FLAG_COMPAT_NO_EXT; + break; + case "magiskCmd": + value |= FLAG_COMPAT_MAGISK_CMD; + break; + case "need32bit": + value |= FLAG_COMPAT_NEED_32BIT; + break; + } + } + compatDataId.put(line.substring(0, i), value); + } + } + + public int getCompatibilityFlags(String moduleId) { + Integer compatFlags = compatDataId.get(moduleId); + return compatFlags == null ? 0 : compatFlags; + } + + public static int getFlagsForModule(String moduleId) { + return INSTANCE.getCompatibilityFlags(moduleId); + } } diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 2bd3ce3..45750b1 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -179,6 +179,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O } else { if (AppUpdateManager.getAppUpdateManager().checkUpdate(true)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); + if (AppUpdateManager.getAppUpdateManager().isLastCheckSuccess()) + AppUpdateManager.getAppUpdateManager().checkUpdateCompat(); if (max != 0) { int current = 0; for (LocalModuleInfo localModuleInfo : diff --git a/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java b/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java index ec074a2..02417ad 100644 --- a/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java +++ b/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java @@ -76,6 +76,7 @@ public class ModuleViewListBuilder { RepoManager repoManager = RepoManager.getINSTANCE(); repoManager.runAfterUpdate(() -> { Log.i(TAG, "A2: " + repoManager.getModules().size()); + boolean no32bitSupport = Build.SUPPORTED_32_BIT_ABIS.length == 0; for (RepoModule repoModule : repoManager.getModules().values()) { if (!repoModule.repoData.isEnabled()) continue; ModuleInfo moduleInfo = repoModule.moduleInfo; @@ -84,9 +85,11 @@ public class ModuleViewListBuilder { // Only check Magisk compatibility if root is present (InstallerInitializer.peekMagiskPath() != null && repoModule.moduleInfo.minMagisk > - InstallerInitializer.peekMagiskVersion() - ))) - continue; // Skip adding incompatible modules + InstallerInitializer.peekMagiskVersion())) || + // If 64bit only system, skip 32bit only modules + (no32bitSupport && (AppUpdateManager.getFlagsForModule(repoModule.id) + & AppUpdateManager.FLAG_COMPAT_NEED_32BIT) != 0) + ) continue; // Skip adding incompatible modules ModuleHolder moduleHolder = this.mappedModuleHolders.get(repoModule.id); if (moduleHolder == null) { this.mappedModuleHolders.put(repoModule.id, diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java index c7d905d..6fdb430 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -81,9 +81,9 @@ public class AndroidacyActivity extends CompatActivity { setActionBarBackground(null); this.setDisplayHomeAsUpEnabled(true); if (title == null || title.isEmpty()) { - this.setTitle(title); - } else { this.setTitle("Androidacy"); + } else { + this.setTitle(title); } if (allowInstall || title == null || title.isEmpty()) { this.hideActionBar(); diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java index 448c55f..cc21da1 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java @@ -16,6 +16,7 @@ import android.widget.Toast; import androidx.recyclerview.widget.RecyclerView; import com.fox2code.mmm.ActionButtonType; +import com.fox2code.mmm.AppUpdateManager; import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.Constants; import com.fox2code.mmm.MainApplication; @@ -26,6 +27,7 @@ import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Hashes; import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.IntentHelper; +import com.fox2code.mmm.utils.PropUtils; import com.google.android.material.progressindicator.LinearProgressIndicator; import com.topjohnwu.superuser.CallbackList; import com.topjohnwu.superuser.Shell; @@ -261,23 +263,39 @@ public class InstallerActivity extends CompatActivity { .to(installerController, installerMonitor); } else { String arch32 = "true"; // Do nothing by default + boolean needs32bit = false; + String moduleId = null; + try (ZipFile zipFile = new ZipFile(file)) { + if (zipFile.getEntry( // Check if module hard require 32bit support + "common/addon/Volume-Key-Selector/tools/arm64/keycheck") == null && + zipFile.getEntry( + "common/addon/Volume-Key-Selector/install.sh") != null) { + needs32bit = true; + } + moduleId = PropUtils.readModuleId(zipFile + .getInputStream(zipFile.getEntry("module.prop"))); + } catch (IOException ignored) {} + int compatFlags = AppUpdateManager.getFlagsForModule(moduleId); + if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NEED_32BIT) != 0) + needs32bit = true; + if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NO_EXT) != 0) + noExtensions = true; + if (moduleId != null && (moduleId.isEmpty() || + moduleId.contains("/") || moduleId.contains("\0") || + (moduleId.startsWith(".") && moduleId.endsWith(".")))) { + this.setInstallStateFinished(false, + "! This module contain a dangerous moduleId", + null); + return; + } if (Build.SUPPORTED_32_BIT_ABIS.length == 0) { - boolean needs32bit = false; - try (ZipFile zipFile = new ZipFile(file)) { - if (zipFile.getEntry( // Check if module hard require 32bit support - "common/addon/Volume-Key-Selector/tools/arm64/keycheck") == null && - zipFile.getEntry( - "common/addon/Volume-Key-Selector/install.sh") != null) { - needs32bit = true; - } - } catch (IOException ignored) {} if (needs32bit) { this.setInstallStateFinished(false, "! This module can't be installed on a 64bit only system", null); return; } - } else { + } else if (needs32bit || (compatFlags & AppUpdateManager.FLAG_COMPAT_NO_EXT) == 0) { // Restore Magisk legacy stuff for retro compatibility if (Build.SUPPORTED_32_BIT_ABIS[0].contains("arm")) arch32 = "export ARCH32=arm"; @@ -288,7 +306,8 @@ public class InstallerActivity extends CompatActivity { File installExecutable; if (InstallerInitializer.peekMagiskVersion() >= Constants.MAGISK_VER_CODE_INSTALL_COMMAND && - (noExtensions || MainApplication.isUsingMagiskCommand())) { + ((compatFlags & AppUpdateManager.FLAG_COMPAT_MAGISK_CMD) != 0 || + noExtensions || MainApplication.isUsingMagiskCommand())) { installCommand = "magisk --install-module \"" + file.getAbsolutePath() + "\""; installExecutable = new File(InstallerInitializer.peekMagiskPath() .equals("/sbin") ? "/sbin/magisk" : "/system/bin/magisk"); @@ -296,13 +315,14 @@ public class InstallerActivity extends CompatActivity { installExecutable = this.extractInstallScript("module_installer_compat.sh"); if (installExecutable == null) { this.setInstallStateFinished(false, - "! Failed to extract module install script", ""); + "! Failed to extract module install script", null); return; } installCommand = "sh \"" + installExecutable.getAbsolutePath() + "\"" + " /dev/null 1 \"" + file.getAbsolutePath() + "\""; } installerMonitor = new InstallerMonitor(installExecutable); + if (moduleId != null) installerMonitor.setForCleanUp(moduleId); if (noExtensions) { installJob = Shell.su(arch32, // No Extensions "cd \"" + this.moduleCache.getAbsolutePath() + "\"", @@ -462,6 +482,7 @@ public class InstallerActivity extends CompatActivity { private static final String DEFAULT_ERR = "! Install failed"; private final String installScriptErr; public String lastCommand = ""; + public String forCleanUp; public InstallerMonitor(File installScript) { super(Runnable::run); @@ -476,6 +497,10 @@ public class InstallerActivity extends CompatActivity { this.lastCommand = s; } + public void setForCleanUp(String forCleanUp) { + this.forCleanUp = forCleanUp; + } + private String doCleanUp() { String installScriptErr = this.installScriptErr; // This block is mainly to help fixing customize.sh syntax errors @@ -490,6 +515,10 @@ public class InstallerActivity extends CompatActivity { Log.e(TAG, "Failed to delete failed update"); return "Error: " + installScriptErr.substring(i + 1); } + } else if (this.forCleanUp != null) { + SuFile moduleUpdate = new SuFile("/data/adb/modules_update/" + this.forCleanUp); + if (moduleUpdate.exists() && !moduleUpdate.deleteRecursive()) + Log.e(TAG, "Failed to delete failed update"); } return DEFAULT_ERR; } diff --git a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java index 5a9deed..8b78628 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java +++ b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java @@ -1,7 +1,11 @@ package com.fox2code.mmm.utils; +import static com.fox2code.mmm.AppUpdateManager.FLAG_COMPAT_LOW_QUALITY; +import static com.fox2code.mmm.AppUpdateManager.getFlagsForModule; + import android.os.Build; import android.text.TextUtils; +import android.util.Log; import com.fox2code.mmm.manager.ModuleInfo; import com.topjohnwu.superuser.io.SuFileInputStream; @@ -274,6 +278,22 @@ public class PropUtils { } } + public static String readModuleId(InputStream inputStream) { + String moduleId = null; + try (BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + if (line.startsWith("id=")) { + moduleId = line.substring(3).trim(); + } + } + } catch (IOException e) { + Log.d("PropUtils", "Failed to get moduleId", e); + } + return moduleId; + } + public static void applyFallbacks(ModuleInfo moduleInfo) { if (moduleInfo.support == null || moduleInfo.support.isEmpty()) { moduleInfo.support = moduleSupportsFallbacks.get(moduleInfo.id); @@ -299,7 +319,8 @@ public class PropUtils { || moduleInfo.author == null || !TextUtils.isGraphic(moduleInfo.author) || (description = moduleInfo.description) == null || !TextUtils.isGraphic(description) || description.toLowerCase(Locale.ROOT).equals(moduleInfo.name.toLowerCase(Locale.ROOT)) - || description.length() < Math.min(Math.max(moduleInfo.name.length() + 4, 16), 24); + || description.length() < Math.min(Math.max(moduleInfo.name.length() + 4, 16), 24) + || (getFlagsForModule(moduleInfo.id) & FLAG_COMPAT_LOW_QUALITY) != 0; } private static boolean isInvalidValue(String name) {