3535import com .topjohnwu .superuser .internal .UiThreadHandler ;
3636import com .topjohnwu .superuser .io .SuFile ;
3737
38+ import java .io .BufferedReader ;
3839import java .io .ByteArrayInputStream ;
3940import java .io .File ;
4041import java .io .FileOutputStream ;
4142import java .io .IOException ;
43+ import java .io .InputStreamReader ;
4244import java .io .OutputStream ;
45+ import java .io .PrintStream ;
4346import java .util .zip .ZipEntry ;
4447import java .util .zip .ZipFile ;
4548import 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 );
0 commit comments