Skip to content
This repository was archived by the owner on May 4, 2023. It is now read-only.

Commit 0026388

Browse files
committed
Add system-less AnyKernel module install support. (Experimental)
1 parent 1c3894e commit 0026388

File tree

2 files changed

+161
-109
lines changed

2 files changed

+161
-109
lines changed

app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java

Lines changed: 160 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@
3535
import com.topjohnwu.superuser.internal.UiThreadHandler;
3636
import com.topjohnwu.superuser.io.SuFile;
3737

38+
import java.io.BufferedReader;
3839
import java.io.ByteArrayInputStream;
3940
import java.io.File;
4041
import java.io.FileOutputStream;
4142
import java.io.IOException;
43+
import java.io.InputStreamReader;
4244
import java.io.OutputStream;
45+
import java.io.PrintStream;
4346
import java.util.zip.ZipEntry;
4447
import java.util.zip.ZipFile;
4548
import java.util.zip.ZipInputStream;
@@ -62,7 +65,8 @@ protected void onCreate(Bundle savedInstanceState) {
6265
this.setDisplayHomeAsUpEnabled(true);
6366
setActionBarBackground(null);
6467
this.setOnBackPressedCallback(a -> {
65-
this.canceled = true; return false;
68+
this.canceled = true;
69+
return false;
6670
});
6771
final Intent intent = this.getIntent();
6872
final String target;
@@ -95,7 +99,7 @@ protected void onCreate(Bundle savedInstanceState) {
9599
setTitle(name);
96100
this.textWrap = MainApplication.isTextWrapEnabled();
97101
setContentView(this.textWrap ?
98-
R.layout.installer_wrap :R.layout.installer);
102+
R.layout.installer_wrap : R.layout.installer);
99103
int background;
100104
int foreground;
101105
if (MainApplication.getINSTANCE().isLightTheme() &&
@@ -118,127 +122,107 @@ protected void onCreate(Bundle savedInstanceState) {
118122
this.getWindow().setFlags( // Note: Doesn't require WAKELOCK permission
119123
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
120124
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
121-
if (urlMode) {
122-
this.progressIndicator.setVisibility(View.VISIBLE);
123-
this.installerTerminal.addLine("- Downloading " + name);
124-
new Thread(() -> {
125-
File moduleCache = this.toDelete =
126-
new File(this.moduleCache, "module.zip");
127-
if (moduleCache.exists() && !moduleCache.delete() &&
128-
!new SuFile(moduleCache.getAbsolutePath()).delete())
129-
Log.e(TAG, "Failed to delete module cache");
130-
String errMessage = "Failed to download module zip";
131-
try {
132-
Log.i(TAG, "Downloading: " + target);
133-
byte[] rawModule = Http.doHttpGet(target,(progress, max, done) -> {
134-
if (max <= 0 && this.progressIndicator.isIndeterminate())
135-
return;
136-
this.runOnUiThread(() -> {
137-
this.progressIndicator.setIndeterminate(false);
138-
this.progressIndicator.setMax(max);
139-
this.progressIndicator.setProgressCompat(progress, true);
140-
});
125+
final File moduleFile = urlMode ? null : new File(target);
126+
this.progressIndicator.setVisibility(View.VISIBLE);
127+
this.installerTerminal.addLine("- Downloading " + name);
128+
new Thread(() -> {
129+
File moduleCache = this.toDelete = urlMode ?
130+
new File(this.moduleCache, "module.zip") : moduleFile;
131+
if (moduleCache.exists() && !moduleCache.delete() &&
132+
!new SuFile(moduleCache.getAbsolutePath()).delete())
133+
Log.e(TAG, "Failed to delete module cache");
134+
String errMessage = "Failed to download module zip";
135+
try {
136+
Log.i(TAG, "Downloading: " + target);
137+
byte[] rawModule = urlMode ? Http.doHttpGet(target, (progress, max, done) -> {
138+
if (max <= 0 && this.progressIndicator.isIndeterminate())
139+
return;
140+
this.runOnUiThread(() -> {
141+
this.progressIndicator.setIndeterminate(false);
142+
this.progressIndicator.setMax(max);
143+
this.progressIndicator.setProgressCompat(progress, true);
141144
});
145+
}) : Files.readSU(moduleFile);
146+
this.runOnUiThread(() -> {
147+
this.progressIndicator.setVisibility(View.GONE);
148+
this.progressIndicator.setIndeterminate(true);
149+
});
150+
if (this.canceled) return;
151+
if (checksum != null && !checksum.isEmpty()) {
152+
Log.d(TAG, "Checking for checksum: " + checksum);
142153
this.runOnUiThread(() -> {
143-
this.progressIndicator.setVisibility(View.GONE);
144-
this.progressIndicator.setIndeterminate(true);
154+
this.installerTerminal.addLine("- Checking file integrity");
145155
});
146-
if (this.canceled) return;
147-
if (checksum != null && !checksum.isEmpty()) {
148-
Log.d(TAG, "Checking for checksum: " + checksum);
149-
this.runOnUiThread(() -> {
150-
this.installerTerminal.addLine("- Checking file integrity");
151-
});
152-
if (!Hashes.checkSumMatch(rawModule, checksum)) {
153-
this.setInstallStateFinished(false,
154-
"! File integrity check failed", "");
155-
return;
156-
}
156+
if (!Hashes.checkSumMatch(rawModule, checksum)) {
157+
this.setInstallStateFinished(false,
158+
"! File integrity check failed", "");
159+
return;
157160
}
158-
if (this.canceled) return;
159-
Files.fixJavaZipHax(rawModule);
160-
boolean noPatch = false;
161-
boolean isModule = false;
162-
boolean isAnyKernel = false;
163-
errMessage = "File is not a valid zip file";
164-
try (ZipInputStream zipInputStream = new ZipInputStream(
165-
new ByteArrayInputStream(rawModule))) {
166-
ZipEntry zipEntry;
167-
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
168-
String entryName = zipEntry.getName();
169-
if (entryName.equals("anykernel.sh")) {
170-
isAnyKernel = true;
171-
break;
172-
} else if (entryName.equals("module.prop")) {
173-
noPatch = true;
174-
isModule = true;
175-
break;
176-
} else if (entryName.endsWith("/module.prop")) {
177-
isModule = true;
178-
}
161+
}
162+
if (this.canceled) return;
163+
Files.fixJavaZipHax(rawModule);
164+
boolean noPatch = false;
165+
boolean isModule = false;
166+
boolean isAnyKernel = false;
167+
errMessage = "File is not a valid zip file";
168+
try (ZipInputStream zipInputStream = new ZipInputStream(
169+
new ByteArrayInputStream(rawModule))) {
170+
ZipEntry zipEntry;
171+
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
172+
String entryName = zipEntry.getName();
173+
if (entryName.equals("anykernel.sh")) {
174+
noPatch = true;
175+
isAnyKernel = true;
176+
break;
177+
} else if (entryName.equals("module.prop")) {
178+
noPatch = true;
179+
isModule = true;
180+
break;
181+
} else if (entryName.endsWith("/anykernel.sh")) {
182+
isAnyKernel = true;
183+
} else if (entryName.endsWith("/module.prop")) {
184+
isModule = true;
179185
}
180186
}
181-
if (!isModule) {
182-
this.setInstallStateFinished(false, isAnyKernel ?
183-
"! AnyKernel modules can only be installed on recovery" :
184-
"! File is not a valid magisk module", "");
185-
return;
186-
}
187-
if (noPatch) {
187+
}
188+
if (!isModule && !isAnyKernel) {
189+
this.setInstallStateFinished(false,
190+
"! File is not a valid magisk module", "");
191+
return;
192+
}
193+
if (noPatch) {
194+
if (urlMode) {
188195
errMessage = "Failed to save module zip";
189196
try (OutputStream outputStream = new FileOutputStream(moduleCache)) {
190197
outputStream.write(rawModule);
191198
outputStream.flush();
192199
}
193-
} else {
194-
errMessage = "Failed to patch module zip";
195-
this.runOnUiThread(() -> {
196-
this.installerTerminal.addLine("- Patching " + name);
197-
});
198-
Log.i(TAG, "Patching: " + moduleCache.getName());
199-
try (OutputStream outputStream = new FileOutputStream(moduleCache)) {
200-
Files.patchModuleSimple(rawModule, outputStream);
201-
outputStream.flush();
202-
}
203200
}
204-
if (this.canceled) return;
205-
//noinspection UnusedAssignment (Important to avoid OutOfMemoryError)
206-
rawModule = null; // Because reference is kept when calling doInstall
201+
} else {
202+
errMessage = "Failed to patch module zip";
207203
this.runOnUiThread(() -> {
208-
this.installerTerminal.addLine("- Installing " + name);
204+
this.installerTerminal.addLine("- Patching " + name);
209205
});
210-
errMessage = "Failed to install module zip";
211-
this.doInstall(moduleCache, noExtensions, rootless);
212-
} catch (IOException e) {
213-
Log.e(TAG, errMessage, e);
214-
this.setInstallStateFinished(false,
215-
"! " + errMessage, "");
216-
}
217-
}, "Module download Thread").start();
218-
} else {
219-
final File moduleFile = new File(target);
220-
if (checksum != null && !checksum.isEmpty()) {
221-
Log.d(TAG, "Checking for checksum: " + checksum);
222-
this.installerTerminal.addLine("- Checking file integrity");
223-
try {
224-
if (!Hashes.checkSumMatch(Files.readSU(moduleFile), checksum)) {
225-
this.setInstallStateFinished(false,
226-
"! File integrity check failed", "");
227-
return;
206+
Log.i(TAG, "Patching: " + moduleCache.getName());
207+
try (OutputStream outputStream = new FileOutputStream(moduleCache)) {
208+
Files.patchModuleSimple(rawModule, outputStream);
209+
outputStream.flush();
228210
}
229-
} catch (IOException e) {
230-
Log.e(TAG, "Failed to read file for checksum check", e);
231-
this.setInstallStateFinished(false,
232-
"! File integrity check failed", "");
233-
return;
234211
}
212+
//noinspection UnusedAssignment (Important to avoid OutOfMemoryError)
213+
rawModule = null; // Because reference is kept when calling doInstall
235214
if (this.canceled) return;
215+
this.runOnUiThread(() -> {
216+
this.installerTerminal.addLine("- Installing " + name);
217+
});
218+
errMessage = "Failed to install module zip";
219+
this.doInstall(moduleCache, noExtensions, rootless);
220+
} catch (IOException e) {
221+
Log.e(TAG, errMessage, e);
222+
this.setInstallStateFinished(false,
223+
"! " + errMessage, "");
236224
}
237-
this.installerTerminal.addLine("- Installing " + name);
238-
new Thread(() -> this.doInstall(
239-
this.toDelete = moduleFile, noExtensions, rootless),
240-
"Install Thread").start();
241-
}
225+
}, "Module install Thread").start();
242226
}
243227

244228

@@ -271,15 +255,63 @@ private void doInstall(File file,boolean noExtensions,boolean rootless) {
271255
String arch32 = "true"; // Do nothing by default
272256
boolean needs32bit = false;
273257
String moduleId = null;
258+
boolean anyKernel = false;
259+
boolean magiskModule = false;
260+
boolean anyKernelSystemLess = false;
261+
File anyKernelInstallScript = new File(this.moduleCache, "update-binary");
274262
try (ZipFile zipFile = new ZipFile(file)) {
263+
ZipEntry anyKernelSh = zipFile.getEntry("anykernel.sh");
264+
if (anyKernelSh != null) { // Check if module is AnyKernel module
265+
BufferedReader bufferedReader = new BufferedReader(
266+
new InputStreamReader(zipFile.getInputStream(anyKernelSh)));
267+
String line;
268+
// Check if AnyKernel module support system-less
269+
while ((line = bufferedReader.readLine()) != null) {
270+
String trimmedLine = line.trim();
271+
if (trimmedLine.equals("do.modules=1"))
272+
anyKernel = true;
273+
if (trimmedLine.equals("do.systemless=1"))
274+
anyKernelSystemLess = true;
275+
}
276+
bufferedReader.close();
277+
if (anyKernelSystemLess && anyKernel) {
278+
anyKernelSystemLess = false;
279+
ZipEntry updateBinary = zipFile.getEntry(
280+
"META-INF/com/google/android/update-binary");
281+
if (updateBinary != null) {
282+
bufferedReader = new BufferedReader(
283+
new InputStreamReader(zipFile.getInputStream(updateBinary)));
284+
PrintStream printStream = new PrintStream(
285+
new FileOutputStream(anyKernelInstallScript));
286+
while ((line = bufferedReader.readLine()) != null) {
287+
String trimmedLine = line.trim();
288+
if (trimmedLine.equals("mount_all;") ||
289+
trimmedLine.equals("umount_all;"))
290+
continue; // Do not mount anything
291+
line = line.replace("/sbin/sh", "/system/bin/sh");
292+
int prePatch = line.length();
293+
line = line.replace("/data/adb/modules/ak3-helper",
294+
"/data/adb/modules-update/ak3-helper");
295+
if (prePatch != line.length()) anyKernelSystemLess = true;
296+
printStream.println(line);
297+
}
298+
printStream.close();
299+
bufferedReader.close();
300+
if (!anyKernelSystemLess) anyKernelInstallScript.delete();
301+
}
302+
}
303+
anyKernel = true;
304+
}
275305
if (zipFile.getEntry( // Check if module hard require 32bit support
276306
"common/addon/Volume-Key-Selector/tools/arm64/keycheck") == null &&
277307
zipFile.getEntry(
278308
"common/addon/Volume-Key-Selector/install.sh") != null) {
279309
needs32bit = true;
280310
}
311+
ZipEntry moduleProp = zipFile.getEntry("module.prop");
312+
magiskModule = moduleProp != null;
281313
moduleId = PropUtils.readModuleId(zipFile
282-
.getInputStream(zipFile.getEntry("module.prop")));
314+
.getInputStream(moduleProp));
283315
} catch (IOException ignored) {}
284316
int compatFlags = AppUpdateManager.getFlagsForModule(moduleId);
285317
if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NEED_32BIT) != 0)
@@ -294,6 +326,12 @@ private void doInstall(File file,boolean noExtensions,boolean rootless) {
294326
null);
295327
return;
296328
}
329+
if (magiskModule && moduleId == null && !anyKernel) {
330+
// Modules without module Ids are module installed by 3rd party software
331+
this.setInstallStateFinished(false,
332+
"! Magisk modules require a moduleId", null);
333+
return;
334+
}
297335
if (Build.SUPPORTED_32_BIT_ABIS.length == 0) {
298336
if (needs32bit) {
299337
this.setInstallStateFinished(false,
@@ -310,22 +348,35 @@ private void doInstall(File file,boolean noExtensions,boolean rootless) {
310348
}
311349
String installCommand;
312350
File installExecutable;
313-
if (InstallerInitializer.peekMagiskVersion() >=
351+
if (anyKernel) {
352+
if (!anyKernelSystemLess) {
353+
this.setInstallStateFinished(false,
354+
"! This AnyKernel module only support recovery install", null);
355+
return;
356+
}
357+
installExecutable = anyKernelInstallScript;
358+
installCommand = "sh \"" + installExecutable.getAbsolutePath() + "\"" +
359+
" /dev/null 0 \"" + file.getAbsolutePath() + "\"";
360+
} else if (InstallerInitializer.peekMagiskVersion() >=
314361
Constants.MAGISK_VER_CODE_INSTALL_COMMAND &&
315362
((compatFlags & AppUpdateManager.FLAG_COMPAT_MAGISK_CMD) != 0 ||
316363
noExtensions || MainApplication.isUsingMagiskCommand())) {
317364
installCommand = "magisk --install-module \"" + file.getAbsolutePath() + "\"";
318365
installExecutable = new File(InstallerInitializer.peekMagiskPath()
319366
.equals("/sbin") ? "/sbin/magisk" : "/system/bin/magisk");
320-
} else {
367+
} else if (moduleId != null) {
321368
installExecutable = this.extractInstallScript("module_installer_compat.sh");
322369
if (installExecutable == null) {
323370
this.setInstallStateFinished(false,
324371
"! Failed to extract module install script", null);
325372
return;
326373
}
327374
installCommand = "sh \"" + installExecutable.getAbsolutePath() + "\"" +
328-
" /dev/null 1 \"" + file.getAbsolutePath() + "\"";
375+
" /dev/null 0 \"" + file.getAbsolutePath() + "\"";
376+
} else {
377+
this.setInstallStateFinished(false,
378+
"! Zip file is not a valid Magisk or a AnyKernel module!", null);
379+
return;
329380
}
330381
installerMonitor = new InstallerMonitor(installExecutable);
331382
if (moduleId != null) installerMonitor.setForCleanUp(moduleId);

app/src/main/java/com/fox2code/mmm/utils/PropUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ else if (moduleInfo.id.startsWith("riru_")
303303
}
304304

305305
public static String readModuleId(InputStream inputStream) {
306+
if (inputStream == null) return null;
306307
String moduleId = null;
307308
try (BufferedReader bufferedReader = new BufferedReader(
308309
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {

0 commit comments

Comments
 (0)