From 64684185417c1f4fda754909c7546d81e6a67291 Mon Sep 17 00:00:00 2001 From: Alberto Ponces Date: Wed, 3 Jan 2024 22:56:19 +0000 Subject: [PATCH 1/3] feat(patch): add direct boot patch install method (only when updating) --- app/src/main/assets/boot_patch.sh | 18 +++-- app/src/main/assets/util_functions.sh | 60 ++++++++++++++ app/src/main/java/me/bmax/apatch/APatchApp.kt | 4 + .../java/me/bmax/apatch/ui/screen/Home.kt | 7 +- .../java/me/bmax/apatch/ui/screen/Patch.kt | 78 +++++++++++-------- 5 files changed, 129 insertions(+), 38 deletions(-) create mode 100644 app/src/main/assets/util_functions.sh diff --git a/app/src/main/assets/boot_patch.sh b/app/src/main/assets/boot_patch.sh index 765c52e76..8f3a2e9b1 100644 --- a/app/src/main/assets/boot_patch.sh +++ b/app/src/main/assets/boot_patch.sh @@ -10,7 +10,7 @@ # File name Type Description # # boot_patch.sh script A script to patch boot image for APatch. -# (this file) The script will use files in its same +# (this file) The script will use files in its same # directory to complete the patching process. # bootimg binary The target boot image # kpimg binary KernelPatch core Image @@ -38,23 +38,31 @@ getdir() { # Switch to the location of the script file cd "$(getdir "${BASH_SOURCE:-$0}")" +# Load utility functions +. ./util_functions.sh + echo "APatch Boot Image Patcher" # Check if 64-bit if [ $(uname -m) = "aarch64" ]; then echo "System is arm64" else - echo "Not is arm64" - exit + echo "System is not arm64" + exit 1 fi - SUPERKEY=$1 BOOTIMAGE=$2 +if [ -z "$BOOTIMAGE" ]; then + find_boot_image +fi + [ -z "$SUPERKEY" ] && { echo "SuperKey empty!"; exit 1; } [ -e "$BOOTIMAGE" ] || { echo "$BOOTIMAGE does not exist!"; exit 1; } +echo "- Target image: $BOOTIMAGE" + # Check for dependencies command -v ./magiskboot >/dev/null 2>&1 || { echo "magiskboot not found!"; exit 1; } command -v ./kptools >/dev/null 2>&1 || { echo "kptools not found!"; exit 1; } @@ -78,4 +86,4 @@ echo "- Repacking boot image" ls -l "new-boot.img" | echo # Reset any error code -true \ No newline at end of file +true diff --git a/app/src/main/assets/util_functions.sh b/app/src/main/assets/util_functions.sh new file mode 100644 index 000000000..baec6670d --- /dev/null +++ b/app/src/main/assets/util_functions.sh @@ -0,0 +1,60 @@ +#!/system/bin/sh +####################################################################################### +# Helper Functions (credits to topjohnwu) +####################################################################################### + +toupper() { + echo "$@" | tr '[:lower:]' '[:upper:]' +} + +grep_prop() { + local REGEX="s/^$1=//p" + shift + local FILES=$@ + [ -z "$FILES" ] && FILES='/system/build.prop' + cat $FILES 2>/dev/null | dos2unix | sed -n "$REGEX" | head -n 1 +} + +find_block() { + local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT + for BLOCK in "$@"; do + DEVICE=$(find /dev/block \( -type b -o -type c -o -type l \) -iname $BLOCK | head -n 1) 2>/dev/null + if [ ! -z $DEVICE ]; then + readlink -f $DEVICE + return 0 + fi + done + # Fallback by parsing sysfs uevents + for UEVENT in /sys/dev/block/*/uevent; do + DEVNAME=$(grep_prop DEVNAME $UEVENT) + PARTNAME=$(grep_prop PARTNAME $UEVENT) + for BLOCK in "$@"; do + if [ "$(toupper $BLOCK)" = "$(toupper $PARTNAME)" ]; then + echo /dev/block/$DEVNAME + return 0 + fi + done + done + # Look just in /dev in case we're dealing with MTD/NAND without /dev/block devices/links + for DEV in "$@"; do + DEVICE=$(find /dev \( -type b -o -type c -o -type l \) -maxdepth 1 -iname $DEV | head -n 1) 2>/dev/null + if [ ! -z $DEVICE ]; then + readlink -f $DEVICE + return 0 + fi + done + return 1 +} + +find_boot_image() { + SLOT=$(getprop ro.boot.slot_suffix) + if [ ! -z $SLOT ]; then + BOOTIMAGE=$(find_block "ramdisk$SLOT" "recovery_ramdisk$SLOT" "init_boot$SLOT" "boot$SLOT") + else + BOOTIMAGE=$(find_block ramdisk recovery_ramdisk kern-a android_boot kernel bootimg init_boot boot lnx boot_a) + fi + if [ -z $BOOTIMAGE ]; then + # Lets see what fstabs tells me + BOOTIMAGE=$(grep -v '#' /etc/*fstab* | grep -E '/boot(img)?[^a-zA-Z]' | grep -oE '/dev/[a-zA-Z0-9_./-]*' | head -n 1) + fi +} diff --git a/app/src/main/java/me/bmax/apatch/APatchApp.kt b/app/src/main/java/me/bmax/apatch/APatchApp.kt index 36a6288f2..169e44f3f 100644 --- a/app/src/main/java/me/bmax/apatch/APatchApp.kt +++ b/app/src/main/java/me/bmax/apatch/APatchApp.kt @@ -194,6 +194,10 @@ class APApplication : Application() { } } + fun getSuperKey(): String { + return superKey + } + fun updateSuperKey(password: String) { superKey = password } diff --git a/app/src/main/java/me/bmax/apatch/ui/screen/Home.kt b/app/src/main/java/me/bmax/apatch/ui/screen/Home.kt index a17b0187d..face1b870 100644 --- a/app/src/main/java/me/bmax/apatch/ui/screen/Home.kt +++ b/app/src/main/java/me/bmax/apatch/ui/screen/Home.kt @@ -81,7 +81,7 @@ fun HomeScreen(navigator: DestinationsNavigator) { ) { WarningCard() KStatusCard(state) - AStatusCard(state) + AStatusCard(state, navigator) InfoCard() LearnMoreCard() Spacer(Modifier) @@ -397,7 +397,7 @@ private fun KStatusCard(state: APApplication.State) { } @Composable -private fun AStatusCard(state: APApplication.State) { +private fun AStatusCard(state: APApplication.State, navigator: DestinationsNavigator) { ElevatedCard( colors = CardDefaults.elevatedCardColors(containerColor = run { MaterialTheme.colorScheme.secondaryContainer @@ -470,6 +470,9 @@ private fun AStatusCard(state: APApplication.State) { state.equals(APApplication.State.KERNELPATCH_READY) || state.equals(APApplication.State.ANDROIDPATCH_NEED_UPDATE) -> { APApplication.install() + if (state.equals(APApplication.State.ANDROIDPATCH_NEED_UPDATE)) { + navigator.navigate(PatchScreenDestination(null, apApp.getSuperKey())) + } } state.equals(APApplication.State.ANDROIDPATCH_INSTALLED) -> { APApplication.uninstall() diff --git a/app/src/main/java/me/bmax/apatch/ui/screen/Patch.kt b/app/src/main/java/me/bmax/apatch/ui/screen/Patch.kt index d7ce278b3..2dba55f4d 100644 --- a/app/src/main/java/me/bmax/apatch/ui/screen/Patch.kt +++ b/app/src/main/java/me/bmax/apatch/ui/screen/Patch.kt @@ -43,7 +43,7 @@ import java.util.* @Composable @Destination -fun PatchScreen(navigator: DestinationsNavigator, uri: Uri, superKey: String) { +fun PatchScreen(navigator: DestinationsNavigator, uri: Uri?, superKey: String) { var text by remember { mutableStateOf("") } var showFloatAction by remember { mutableStateOf(false) } val scrollState = rememberScrollState() @@ -153,23 +153,27 @@ private fun String.fsh() = ShellUtils.fastCmd(shell, this) private fun Array.fsh() = ShellUtils.fastCmd(shell, *this) -fun patchBootimg(uri: Uri, superKey: String, logs: MutableList): Boolean { +fun patchBootimg(uri: Uri?, superKey: String, logs: MutableList): Boolean { + var outPath: File? = null + var srcBoot: ExtendedFile? = null var patchDir: ExtendedFile = FileSystemManager.getLocal().getFile(apApp.filesDir.parent, "patch") patchDir.deleteRecursively() patchDir.mkdirs() - val srcBoot = patchDir.getChildFile("boot.img") + if (uri != null) { + srcBoot = patchDir.getChildFile("boot.img") - // Process input file - try { - uri.inputStream().buffered().use { src -> - srcBoot.also { - src.copyAndCloseOut(it.newOutputStream()) + // Process input file + try { + uri.inputStream().buffered().use { src -> + srcBoot.also { + src.copyAndCloseOut(it.newOutputStream()) + } } + } catch (e: IOException) { + logs.add("Copy boot image error: " + e) + return false } - } catch (e: IOException) { - logs.add("Copy boot image error: " + e) - return false } val execs = listOf("libkptools.so", "libmagiskboot.so", "libbusybox.so") @@ -185,7 +189,7 @@ fun patchBootimg(uri: Uri, superKey: String, logs: MutableList): Boolean } // Extract scripts - for (script in listOf("boot_patch.sh", "kpimg")) { + for (script in listOf("boot_patch.sh", "util_functions.sh", "kpimg")) { val dest = File(patchDir, script) apApp.assets.open(script).writeTo(dest) } @@ -193,39 +197,51 @@ fun patchBootimg(uri: Uri, superKey: String, logs: MutableList): Boolean val apVer = getManagerVersion().second val rand = (1..4).map { ('a'..'z').random() }.joinToString("") val outFilename = "apatch_${apVer}_${rand}_boot.img" + val patchCommand = if (uri != null) "sh boot_patch.sh $superKey ${srcBoot?.path}" else "sh boot_patch.sh $superKey" val cmds = arrayOf( "cd $patchDir", - "sh boot_patch.sh $superKey ${srcBoot.path}", + patchCommand, ) shell.newJob().add(*cmds).to(logs, logs).exec() logs.add("****************************") - val newBootFile = patchDir.getChildFile("new-boot.img") - if(!newBootFile.exists()) { - logs.add(" Patch failed, no new-boot.img generated") - return false - } + var succ = true + if (uri != null) { + val newBootFile = patchDir.getChildFile("new-boot.img") + if(!newBootFile.exists()) { + logs.add(" Patch failed, no new-boot.img generated") + return false + } - val outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - if(!outDir.exists()) outDir.mkdirs() - val outPath = File(outDir, outFilename) + val outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + if(!outDir.exists()) outDir.mkdirs() + outPath = File(outDir, outFilename) - val inputUri = UriUtils.getUriForFile(newBootFile) + val inputUri = UriUtils.getUriForFile(newBootFile) - var succ = true - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val outUri = MediaStoreUtils.createDownloadUri(outFilename) - succ = MediaStoreUtils.insertDownload(outUri, inputUri) - } else { - newBootFile.inputStream().copyAndClose(outPath.outputStream()) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val outUri = MediaStoreUtils.createDownloadUri(outFilename) + succ = MediaStoreUtils.insertDownload(outUri, inputUri) + } else { + newBootFile.inputStream().copyAndClose(outPath.outputStream()) + } } if(succ) { - logs.add(" Output file is written to ") - logs.add(" ${outPath.path}") + if (uri != null) { + logs.add(" Write patched boot.img was successful") + logs.add(" Output file is written to ") + logs.add(" ${outPath?.path}") + } else { + logs.add(" Boot patch was successful") + } } else { - logs.add(" Write patched boot.img failed ") + if (uri != null) { + logs.add(" Write patched boot.img failed") + } else { + logs.add(" Boot patch failed") + } } logs.add("****************************") From 479ab74407b4744e740462f3dd6819d61d837ed3 Mon Sep 17 00:00:00 2001 From: Alberto Ponces Date: Thu, 4 Jan 2024 09:49:21 +0000 Subject: [PATCH 2/3] feat(patch): use shell exit code to validate operation success --- app/src/main/assets/boot_patch.sh | 7 ++- .../java/me/bmax/apatch/ui/screen/Patch.kt | 63 +++++++++---------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/app/src/main/assets/boot_patch.sh b/app/src/main/assets/boot_patch.sh index 8f3a2e9b1..d0db51dc0 100644 --- a/app/src/main/assets/boot_patch.sh +++ b/app/src/main/assets/boot_patch.sh @@ -70,6 +70,11 @@ command -v ./kptools >/dev/null 2>&1 || { echo "kptools not found!"; exit 1; } echo "- Unpacking boot image" ./magiskboot unpack "$BOOTIMAGE" >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "Unpack error: $?" + exit $? +fi + mv kernel kernel.ori echo "- Patching kernel" @@ -83,7 +88,5 @@ fi echo "- Repacking boot image" ./magiskboot repack "$BOOTIMAGE" >/dev/null 2>&1 || exit $? -ls -l "new-boot.img" | echo - # Reset any error code true diff --git a/app/src/main/java/me/bmax/apatch/ui/screen/Patch.kt b/app/src/main/java/me/bmax/apatch/ui/screen/Patch.kt index 2dba55f4d..e9c39383b 100644 --- a/app/src/main/java/me/bmax/apatch/ui/screen/Patch.kt +++ b/app/src/main/java/me/bmax/apatch/ui/screen/Patch.kt @@ -203,47 +203,46 @@ fun patchBootimg(uri: Uri?, superKey: String, logs: MutableList): Boolea "cd $patchDir", patchCommand, ) - shell.newJob().add(*cmds).to(logs, logs).exec() + val isSuccess = shell.newJob().add(*cmds).to(logs, logs).exec().isSuccess logs.add("****************************") var succ = true - if (uri != null) { - val newBootFile = patchDir.getChildFile("new-boot.img") - if(!newBootFile.exists()) { - logs.add(" Patch failed, no new-boot.img generated") - return false - } - - val outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - if(!outDir.exists()) outDir.mkdirs() - outPath = File(outDir, outFilename) - - val inputUri = UriUtils.getUriForFile(newBootFile) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val outUri = MediaStoreUtils.createDownloadUri(outFilename) - succ = MediaStoreUtils.insertDownload(outUri, inputUri) - } else { - newBootFile.inputStream().copyAndClose(outPath.outputStream()) - } - } - - if(succ) { - if (uri != null) { - logs.add(" Write patched boot.img was successful") - logs.add(" Output file is written to ") - logs.add(" ${outPath?.path}") - } else { + if (uri == null) { + if (isSuccess) { logs.add(" Boot patch was successful") + } else { + succ = false + logs.add(" Boot patch failed") } } else { - if (uri != null) { - logs.add(" Write patched boot.img failed") + val newBootFile = patchDir.getChildFile("new-boot.img") + if (newBootFile.exists()) { + val outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + if(!outDir.exists()) outDir.mkdirs() + outPath = File(outDir, outFilename) + + val inputUri = UriUtils.getUriForFile(newBootFile) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val outUri = MediaStoreUtils.createDownloadUri(outFilename) + succ = MediaStoreUtils.insertDownload(outUri, inputUri) + } else { + newBootFile.inputStream().copyAndClose(outPath.outputStream()) + } + + if (succ) { + logs.add(" Write patched boot.img was successful") + logs.add(" Output file is written to ") + logs.add(" ${outPath?.path}") + } else { + logs.add(" Write patched boot.img failed") + } } else { - logs.add(" Boot patch failed") + succ = false + logs.add(" Patch failed, no new-boot.img generated") } } logs.add("****************************") - return true + return succ } From 814003b4cd471e10ca3b8ef7ea2d06ea52736ed1 Mon Sep 17 00:00:00 2001 From: Alberto Ponces Date: Thu, 4 Jan 2024 22:24:10 +0000 Subject: [PATCH 3/3] Add .gitattributes --- .gitattributes | 25 +++++++ gradlew.bat | 184 ++++++++++++++++++++++++------------------------- 2 files changed, 117 insertions(+), 92 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..e3f77570b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,25 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text eol=lf + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +# *.c text +# *.h text + +# Declare files that will always have CRLF line endings on checkout. +*.cmd text eol=crlf +*.bat text eol=crlf + +# Denote all files that are truly binary and should not be modified. +tools/** binary +*.jar binary +*.exe binary +*.apk binary +*.png binary +*.jpg binary +*.ttf binary +*.so binary + +# Help GitHub detect languages +native/jni/external/** linguist-vendored +native/jni/systemproperties/** linguist-language=C++ diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..93e3f59f1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega