diff --git a/META-INF/com/google/android/update-binary b/META-INF/com/google/android/update-binary new file mode 100644 index 0000000..dd5e824 --- /dev/null +++ b/META-INF/com/google/android/update-binary @@ -0,0 +1,119 @@ +#!/sbin/sh +#Dynamic Installer by @BlassGO --- Also uses code from @osm0sis and @topjohnwu + +#Basic functions +true() { return 0; } +false() { return 1; } +echo2() { >&2 echo "$@"; } +ui_print() { for __ in "$@"; do [ "$BOOTMODE" == false -a -n "$OUTFD" ] && echo -e "ui_print $__\nui_print" >> $OUTFD || echo "$__"; done; } +abort() { ui_print " " "$@" " "; exit 1; } +testrw() { for __ in "$@"; do { [ -d "$__" ] && echo > "$__/.rw$$" && rm -f "$__/.rw$$"; } || { echo2 '!'"Read-Only: $__"; return 1; }; done; } +ensure_dir() { for __ in "$@"; do rm -rf "$__" 2>/dev/null; mkdir -p "$__" || abort "ERROR: Cant create folder $__"; done; } +is_substring() { case $2 in *"$1"*) return 0 ;; *) return 1 ;; esac; } +is64bit() { __=$(od -An -t x1 -j 4 -N 1 "$1"); case $__ in *"02"*) return 0 ;; *"01"*) return 1 ;; *) echo2 "is64bit: Unknown $__: $1"; return 2 ;; esac; } + +#Advanced functions +bb_support() { + $bb_set || { bb_set=true; [ -n "$bb" ] && bb_list="$n$("$bb" --list)$n"; } + case $bb_list in *"$n$1$n"*) return 0 ;; *) return 1 ;; esac; +} +ensure_bin() { + _r_=0 + for __ in "$@"; do + command -v $__ >/dev/null || { + bb_support $__ && eval "$__() { \"$bb\" $__ \"\$@\"; }" || { $needed && abort "ERROR: Could not define \"$__\" binary" || _r_=1; } + } + done + return $_r_ +} +bbForArch() { + for ARCH in "arch/${abi:-none}" arch/*; do + [ -d "$ARCH" ] && bbpath="$ARCH/busybox" && chmod 755 "$bbpath" || continue + [ -x "$bbpath" ] && "$bbpath" >/dev/null 2>&1 && { mv -f "$bbpath" "$bb"; break; } + done + [ -f "$bb" ] && ARCH=${ARCH##*/} || abort "ERROR: Unsupported device architecture!" +} +ensureArch() { + _f_=$(readlink -f /system/bin/sh 2>/dev/null || readlink /proc/$PPID/exe 2>/dev/null || readlink /proc/self/exe 2>/dev/null || ps | awk '/sh|busybox/{print $NF; exit}' 2>/dev/null) + [ -e "$_f_" ] && { is64bit "$_f_" || ARCH="armeabi-v7a" ; echo2 "$ARCH: $_f_"; } +} +setup_bb() { + needed=false; ensure_bin umount ln + [ ! -f /system/bin/sh ] && { umount -l /system 2>/dev/null; mkdir -p /system/bin; ln -sf "$(command -v sh)" /system/bin/sh; } + "$bb" true || abort "ERROR:1: BusyBox cannot load on this device!" + "$bb" --install -s "$l" || { + for i in $("$bb" --list); do + ln -sf "$bb" "$l/$i" || "$bb" ln -sf "$bb" "$l/$i" || "$bb" ln -f "$bb" "$l/$i" || { + echo "#!$bb" > "$l/$i" && chmod 755 "$l/$i" || abort "ERROR:2: Failed to setup BusyBox" + } + done + } + [ -f "$l/sh" ] || abort "ERROR:3: Failed to setup BusyBox" +} +setup() { + #Ensure work DIRs + ensure_dir "$l" + + #Extract contents + unzip -qo "$installzip" "META-INF/zbin/*" -d $TMP || abort "ERROR: Failed to extract contents" + ROOT="$TMP/META-INF/zbin" + + #Check contents + [ -f "$ROOT/core" ] && cd "$ROOT" || abort "ERROR: Failed to extract contents" + + #Ensure BusyBox for ARCH + bb="$TMP/zbin/busybox"; bbForArch + + #Ensure BusyBox Environment + setup_bb; export PATH="$l:$PATH" + + #Rectify ARCH for 32bit devices + ensureArch + + #Loading bin & static + for bin in "arch/$ARCH/bin" static; do + unzip -qo "$bin" -d configs || abort "ERROR: Cant get $bin" + done + + #Getting version + di_version=$(grep -Eo "[0-9]+\.[0-9]+[-[:alnum:]]*" version.txt 2>/dev/null) + + #Getting configs & bins + chmod -R 755 configs + mv -f configs/* -t "$l" + mv -f core "$TMP/zbin/core"; cd $TMP + rm -rf "$TMP/META-INF" "$TMP/extra.zip" "$l/info.txt" 2>/dev/null + + #Start Installation + bash "$TMP/zbin/core" + [ $? == 130 ] && exit 1 || exit 0 +} + +#General vars +[ -n "$2" -a -e "/proc/self/fd/$2" ] && OUTFD="/proc/self/fd/$2" || OUTFD= +[ -f "$3" ] && installzip="$3" ZIPFILE="$3" PERSISTDIR=/sbin/.magisk/mirror/persist || installzip="$ZIPFILE" CUSTOM_SETUP=2 +BOOTMODE=$( [ "$(getprop sys.boot_completed 2>/dev/null)" == 1 ] && echo true || { ps | grep -q "[z]ygote" || ps -A 2>/dev/null | grep -q "[z]ygote" && echo true || echo false ; } ) + +#Start +umask 022 +export PATH="/sbin:/system/bin:/sbin/su:/su/bin:/su/xbin:/system/xbin:/data/adb/magisk:/data/adb/ksu/bin:/data/adb/ap/bin:$PATH" +bb_set=false; bb=$(command -v busybox); n=" +" +needed=true; ensure_bin unzip mkdir chmod mv +needed=false; ensure_bin rm +abi=$(getprop ro.product.cpu.abi 2>/dev/null) + +#Ensure Temp directory +for TMP in /dev/tmp$$ /cache/tmp$$ /mnt/tmp$$ /data/tmp$$ /data/local/tmp$$; do + mkdir -p $TMP 2>/dev/null && break +done +[ -d "$TMP" ] || abort "ERROR: Failed to create temporary directory" + +#Global vars +export TMPDIR="$TMP" \ +DNM="META-INF/com/google/android/magisk" \ +addons="$TMP/zbin/addons" \ +l="$TMP/zbin/ugu" \ +TMP BOOTMODE OUTFD ZIPFILE PERSISTDIR CUSTOM_SETUP ARCH installzip di_version + +setup \ No newline at end of file diff --git a/META-INF/com/google/android/updater-script b/META-INF/com/google/android/updater-script index dd0e431..d89e77c 100644 --- a/META-INF/com/google/android/updater-script +++ b/META-INF/com/google/android/updater-script @@ -13,6 +13,7 @@ setdefault devices_alert on setdefault dalvik_memory 800M setdefault extraction_speed 4M setdefault off_readonly "" +setdefault framework_res "/system/framework/framework-res.apk" setdefault permissions "0:0:0755:0644" #-----------------------------------------------# #Your script starts here: diff --git a/META-INF/zbin/arch/arm64-v8a/SOURCE.txt b/META-INF/zbin/arch/arm64-v8a/SOURCE.txt new file mode 100644 index 0000000..d22a5ee --- /dev/null +++ b/META-INF/zbin/arch/arm64-v8a/SOURCE.txt @@ -0,0 +1,32 @@ +BY topjohnwu { + URL: https://github.com/topjohnwu/Magisk + CONTENTS: busybox +} + +BY Zackptg5 { + URL: https://github.com/Zackptg5/Cross-Compiled-Binaries-Android + CONTENTS: ' + bash + keycheck + xxd + zip + ' +} + +BY Maximoff { + URL: https://github.com/Maximoff/binaries + CONTENTS: ' + aapt + zipalign + ' +} + +BY DualJoe { + URL: https://xdaforums.com/t/exe-static-linux-binaries-for-arm-android-cryptsetup-encfs-f2fs-tools-testdisk-photorec.3709380/post-82757251 + CONTENTS: fxz +} + +BY munjeni { + URL: https://github.com/munjeni/super_image_dumper + CONTENTS: losetup2 +} diff --git a/META-INF/zbin/arch/arm64-v8a/bin b/META-INF/zbin/arch/arm64-v8a/bin new file mode 100644 index 0000000..f8eed8a Binary files /dev/null and b/META-INF/zbin/arch/arm64-v8a/bin differ diff --git a/META-INF/zbin/arch/armeabi-v7a/SOURCE.txt b/META-INF/zbin/arch/armeabi-v7a/SOURCE.txt new file mode 100644 index 0000000..abda593 --- /dev/null +++ b/META-INF/zbin/arch/armeabi-v7a/SOURCE.txt @@ -0,0 +1,27 @@ +BY topjohnwu { + URL: https://github.com/topjohnwu/Magisk + CONTENTS: busybox +} + +BY Zackptg5 { + URL: https://github.com/Zackptg5/Cross-Compiled-Binaries-Android + CONTENTS: ' + bash + keycheck + xxd + zip + ' +} + +BY Maximoff { + URL: https://github.com/Maximoff/binaries + CONTENTS: ' + aapt + zipalign + ' +} + +BY DualJoe { + URL: https://xdaforums.com/t/exe-static-linux-binaries-for-arm-android-cryptsetup-encfs-f2fs-tools-testdisk-photorec.3709380/post-82757251 + CONTENTS: fxz +} diff --git a/META-INF/zbin/arch/armeabi-v7a/bin b/META-INF/zbin/arch/armeabi-v7a/bin new file mode 100644 index 0000000..c030c8f Binary files /dev/null and b/META-INF/zbin/arch/armeabi-v7a/bin differ diff --git a/META-INF/zbin/core b/META-INF/zbin/core new file mode 100644 index 0000000..0e6b40a --- /dev/null +++ b/META-INF/zbin/core @@ -0,0 +1,6718 @@ +#!/sbin/sh +#Dynamic Installer by @BlassGO --- Also uses code from @osm0sis and @topjohnwu + +unmount() { { umount "$1" || umount -l "$1" ; } 2>/dev/null; } + +write_raw_image() { local bs; is_number "${extraction_speed/M}" && bs=$extraction_speed || bs=4M; dd if="$1" of="$2" bs=$bs; } + +is_substring() { [[ "$2" == *"$1"* ]]; } + +is_mounted() { grep -q " $(readlink -f "$1") " /proc/mounts 2>/dev/null; } + +is_less() { compare "$1 < $2";} + +is_greater() { compare "$1 > $2";} + +is_less_equal() { compare "$1 <= $2"; } + +is_greater_equal() { compare "$1 >= $2"; } + +is_equal() { [[ "$1" == "$2" ]]; } + +is_zip() { [ "$(xxd -p -l 4 "$1" 2>/dev/null)" == 504b0304 ] ; } + +is_gzip() { [ "$(xxd -p -l 3 "$1" 2>/dev/null)" == 1f8b08 ] ; } + +is_bzip() { [ "$(xxd -p -l 2 "$1" 2>/dev/null)" == 425a ] ; } + +is_xz() { [ "$(xxd -p -l 6 "$1" 2>/dev/null)" == fd377a585a00 ] ; } + +is_tar() { [ "$(xxd -p -s 257 -l 5 "$1" 2>/dev/null)" == 7573746172 ] ; } + +is_number() { printf %f "${1:-a}" &>/dev/null ; } + +is_hex() { [[ "$1" =~ ^[[:xdigit:]]+$ ]] ; } + +to_hex() { printf "%x\n" "$1" ; } + +is_abc() { [[ "${1//[[:space:]]/}" =~ ^[[:alpha:]]+$ ]] ; } + +is_text() { [[ "${1//[[:space:]]/}" =~ ^[[:print:]]+$ ]] ; } + +is_exec() { [[ "$(stat -c '%A' "$1" 2>/dev/null)" == *"x"* ]] ; } + +is64bit() { local __=$(od -An -t x1 -j 4 -N 1 "$1"); case $__ in *"02"*) return 0 ;; *"01"*) return 1 ;; *) echo2 "is64bit: Unknown $__: $1"; return 2 ;; esac; } + +show_progress() { [ -e "$OUTFD" ] && echo "progress $1 $2" >> $OUTFD; } + +apply_patch() { LD_LIBRARY_PATH=$($is64bit && echo /system/lib64 || echo /system/lib) applypatch "$@"; } + +apply_patch_check() { LD_LIBRARY_PATH=$($is64bit && echo /system/lib64 || echo /system/lib) applypatch -c "$@"; } + +apply_patch_space() { LD_LIBRARY_PATH=$($is64bit && echo /system/lib64 || echo /system/lib) applypatch -s $1; } + +rename() { local path dir; path=$(fullpath "$1") || { echo2 "CANT FIND: $1"; return 1; }; dir=$(dirname "$path"); echo2 "rename: \"$path\" with \"$2\""; mv -f "$path" "$dir/$2" || return 1 ; } + +repeat() { awk -v n="$1" -v str="$2" 'BEGIN { for (i=0; i&2 echo "$@"; } + +contains_array() { local e match="$1"; shift; for e; do [[ "$e" == "$match" ]] && return 0; done; return 1; } + +get_array() { local e match="$1"; shift; for e; do [[ "$e" == *"$match"* ]] && echo "$e" && return 0; done; return 1; } + +binarch() { local __=$(od -An -t x1 -j 4 -N 1 "$1"); case $__ in *"02"*) echo 64bits ;; *"01"*) echo 32bits ;; *) echo2 "binarch: Unknown $__: $1"; return 2 ;; esac; } + +reboot() { [ -f "$(readlink -f /system/bin/reboot)" ] && /system/bin/reboot "$@" || "$(which reboot)" "$@";} + +create_dir() { local dir return=0; for dir; do if [ ! -d "$dir" ]; then mkdir -p "$dir" 2>/dev/null; if [ -d "$dir" ]; then set_perm2 $___uid $___gid $___mod "$dir" ; elif [ -e "$dir" ]; then echo2 "Already exist some reference called: $dir" && return=1; else testrw "$(dirname "$dir")" || return=1; echo2 "Cant create dir: $dir" && return=1; fi ; else testrw "$dir" || return=1; fi; done; return $return; } + +create_file() { local file return=0; for file; do create_dir "$(dirname "$file")" || return 1; rm -f "$file"; touch "$file" 2>/dev/null; if [ -f "$file" ]; then set_perm2 $___uid $___gid $___mof "$file" ; elif [ -e "$file" ]; then echo2 "Already exist some reference called: $file" && return=1; else echo2 "Cant create file: $file" && return=1; fi; done; return $return; } + +testrw() { local __; for __; do [ -d "$__" ] || { echo2 "Cant find folder: $__"; return 1 ; }; touch "$__/.rw$$" 2>/dev/null && rm -f "$__/.rw$$" || { echo2 '!'"Read-Only: $__"; return 1; }; done; } + +testvarname() { [[ "$1" =~ ^[a-zA-Z_][a-zA-Z_0-9]*$ ]] ; } + +encode_b64() { echo "$@" | base64; } + +decode_b64() { echo "$@" | base64 -d; } + +run_b64() { eval "$(echo "$@" | base64 -d 2>/dev/null)"; } + +int() { awk "BEGIN{print int($*)}"; } + +float() { is_number "$float_length" || local float_length=9; awk "BEGIN{printf(\"%.${float_length}f\\n\", ${*:-0})}"; } + +round() { awk "BEGIN{printf(\"%.0f\\n\", ${*:-0})}"; } + +is_static() { local file="$1" result; [ -n "$file" -a ! -e "$file" ] && file=$(which "$1"); [ ! -e "$file" ] && echo2 "is_static: Cant find: $1" && return 1; result=$(strings "$file" | grep -E "^/(bin|etc|system|system_ext|vendor|product|odm|apex)(/[^/]+)*/bin/[^/]+" | while read need; do echo "$file needs \"$need\""; done); [ -n "$result" ] && echo2 "$result" && return 1 || return 0; } + +mount_file() { if [ ! -e "$1" ]; then echo2 "Cant find: $1" && return 1; elif [ ! -e "$2" ]; then create_file "$2" || return 1; fi; mount -o bind "$1" "$2";} + +support_option() { can_run "$2" && ("$2" --help 2>&1) | grep -Eq -m1 "[[:space:]]+$1[[:space:]]+";} + +getfree() { if [ -d "$1" ]; then { calc "$(stat -f -c %S "$1") * $(stat -f -c %a "$1")" ; } 2>/dev/null && return 0; else echo2 "getfree: Cant find folder: $1"; return 1; fi; } + +getsize() { stat -c%s "$1" ; } + +################################### +#Common EDIFY extra references (For new users) + +ifelse() { eval "{ $1 ; } || { $2 ; }"; } + +run_program() { local program; program=$(fullpath "$1") || { echo2 "CANT FIND: $1"; return 1; }; { is_exec "$1" || chmod +x "$program" ; } || { echo2 "CANNOT SET PERMISSIONS: $program"; return 1; }; "$program" "${@:2}" ; } + +stdout() { "$@" 2>&1; } + +stderr() { "$@" 1>&2; } + +stdprint() { "$@" | while read print; do ui_print "$print"; done; } + +################################### +#Specific POSIX functions + +grep_o() ( + # POSIX grep -o matching find by @BlassGO + awk ' + function start(str) { + matches="" + return receiver(str) + } + function receiver(str2) { + find=match(str2,/'$1'/) + if (find!=0) { + if (matches!="") matches=matches "\n" + matches=matches substr(str2,RSTART,RLENGTH) + receiver(substr(str2,RSTART+RLENGTH,length(str2))) + } + return matches + } + { + result=start($0) + if (result!="") print result + }' "${2:--}" +) + +################################### + +exist() { + local ___ t + case $1 in + any) t="-e" + shift + ;; + file) t="-f" + shift + ;; + folder) t="-d" + shift + ;; + symlink) t="-L" + shift + ;; + block) t="-b" + shift + ;; + *) t="-e" + ;; + esac + [ $# == 0 ] && return 1 + for ___; do + [ $t "$___" ] || return 1 + done + return 0 +} + +check_content() { + local list file content + [ $# -lt 2 ] && return 1 + file=${!#} + if [ ! -e "$file" ]; then + echo2 "CANT FIND: $file" + return 1 + fi + list=$(zip_list "$file") + for content in "${@:1:$#-1}"; do + echo "$list" | grep -Fxq "$content" || return 1 + done + return 0 +} + +find_content() { + local list regex="-F" nf="[^/]$" limit pass=false + local file content + while [ $# -gt 0 ]; do + case $1 in + -regex|-r) + regex="-E" + shift; + ;; + -include-dirs|-id) + nf= + shift; + ;; + -dirs|-d) + nf="[/]" + shift; + ;; + -maxdepth|-max) + if is_number "$2"; then limit="$2"; else echo2 "find_content: Invalid option: $2" && return 1; fi + shift 2; + ;; + *) + break + ;; + esac + done + [ $# -lt 2 ] && return 1 + file=${!#} + if [ ! -e "$file" ]; then echo2 "CANT FIND: $file" && return 1; fi + list=$(zip_list "$file" | grep -Eo "^([^/]+/*){0,$limit}${nf}" | sort -bu) + for content in "${@:1:$#-1}"; do + if ! echo "$list" | grep $regex "$content"; then + echo2 "Cant find contents with: \"$content\" in \"$file\"" + else pass=true + fi + done + $pass && return 0 || return 1 +} + +is_valid() { + local ___ + [ $# == 0 ] && return 1 + for ___; do + [ -e "$___" ] && grep -q '[^[:space:]]' "$___" || return 1 + done + return 0 +} + +defined() { + local ___ + [ $# == 0 ] && return 1 + for ___; do + [ -z "${!___}" ] && return 1 + done + return 0 +} + +undefined() { + local ___ + [ $# == 0 ] && return 1 + for ___; do + [ -n "${!___}" ] && return 1 + done + return 0 +} + +copy() { + if [ -d "$2" ] || create_dir "$(dirname "$2")"; then + cp -prf "$1" "$2" + else + echo2 "Cant copy: \"$1\" in \"$2\"" + return 1 + fi +} + +move() { + if [ -d "$2" ] || create_dir "$(dirname "$2")"; then + if mv -f "$1" "$2"; then + return 0 + elif cp -prf "$1" "$2" 2>/dev/null; then + if rm -rf "$1"; then + return 0 + else + echo2 "Copied to \"$2\", but can't remove \"$1\"" + return 0 + fi + fi + fi + echo2 "Can't move: \"$1\" to \"$2\"" + return 1 +} + +fullpath() { + [ -e "$1" ] || return 1 + [ "${1:0:1}" = "/" ] && echo "$1" || { local d=$(readlink -f "$(dirname "$1")"); echo "${d%/}/$(basename "$1")" ; } +} + +random() { + [ -n "$1" -o -z "$RANDOM" ] && { awk "BEGIN { srand(); print int(rand() * ${1:-100000}) }" ; return $? ; } + echo "$RANDOM" +} + +regex_or() { + local regex fix='s/[]\/.^$*+?{}()|[]/\\&/g' + regex=$(printf "%s\001" "$@" | sed -e $fix) + regex=${regex:0:-1} + echo "${regex//$'\001'/|}" +} + +str_join() { + local str c="$1" + shift + printf -v str "%s$c" "$@" + [ -n "$c" ] && echo "${str:0:-${#c}}" || echo "$str" +} + +contains_one() { + local file regex + [ $# -lt 2 ] && return 1 + file=${!#} + if [ ! -e "$file" ]; then + echo2 "CANT FIND: $file" + return 1 + fi + grep -Eq "$(regex_or "${@:1:$#-1}")" "$file" +} + +contains() { + local file content + [ $# -lt 2 ] && return 1 + file=${!#} + if [ ! -e "$file" ]; then + echo2 "CANT FIND: $file" + return 1 + fi + for content in "${@:1:$#-1}"; do + grep -qF "$content" "$file" || return 1 + done + return 0 +} + +magic_file() { + local file fname type show bytes offset magic result config m return=0 + while [ $# -gt 0 ]; do + case $1 in + -o|-offset) offset=$2; shift 2 ;; + -b|-bytes) bytes=$2; shift 2 ;; + -t|-type) type=$2; shift 2 ;; + -s|-show) show=true; shift ;; + *) break ;; + esac + done + if [ -n "$type" ]; then + file="$1" + config=$(get_file_prop "$l/file_types.config" "$type " 2>/dev/null) + if [ -z "$config" ]; then echo2 "magic_file: Cant find Magic Config for: $type"; return 1; fi + IFS=',' read -r magic offset bytes line <<<"$(echo "$config" | tr -d ' ')" + else + magic="$1"; file="$2" + fi + [ -z "$magic" ] && { echo2 "magic_file: Null magic"; return 1; } + [ ! -e "$file" ] && { echo2 "CANT FIND: $file"; return 1; } + bytes=${bytes:-20} + offset=${offset:-0} + is_number "$offset" || { echo2 "magic_file: Invalid offset: $offset"; return 1; } + is_number "$bytes" || { echo2 "magic_file: Invalid bytes: $bytes"; return 1; } + echo2 '>> Magic File v1.0.1' + result=$(xxd -p -l $bytes -s $offset "$file") + fname=$(basename "$file") + [ "$show" ] && echo2 "RESULT: ${result:-NULL}" + for m in ${magic//:/ }; do + echo2 "MAGIC: $m | FILE: $fname | OFFSET: $offset | BYTES: $bytes" + if [[ "$result" == *"${m,,}"* ]]; then + echo2 "PASSED: YES" + return 0 + else + echo2 "PASSED: NO" + return=1 + fi + done + return $return +} + +convert() { + local num from to decimals=16 show + while [ $# -gt 0 ]; do + case $1 in + -d|-decimals) decimals=$2; shift 2 ;; + -s|-show) show=true; shift ;; + *) break ;; + esac + done + num=${1//[^0-9.]/} + from=${1//[0-9.]/} + to=${2//[0-9.]/} + [ -z "$from" -a -n "$convert_from" ] && from=$convert_from + [ -z "$to" -a -n "$convert_to" ] && to=$convert_to + if [ -z "$from" -o -z "$to" ]; then + echo2 "convert: Units not specified or invalid" + return 1 + fi + awk -v from="$from" -v to="$to" -v num="${num:-1}" -v decimals="$decimals" -v show="$show" ' + BEGIN { + foundGroup = 0 + result = num + quote = "\"" + fromq = quote from quote + toq = quote to quote + } + (substr($0, 1, 1) == "#") { next } + index($0, "[") { + if (foundGroup) { + exit + } else if (index($0, fromq) && index($0, toq)) { + foundGroup = 1 + } + } + foundGroup && index($0, "=") { + line = $0 + gsub(/[ \t]*=[ \t]*/, "=", line) + split(line, parts, "=") + + if (length(parts) == 2) { + left = parts[1] + right = parts[2] + leftValue = left + 0 + leftUnit = substr(left, length(leftValue) + 1) + rightValue = right + 0 + rightUnit = substr(right, length(rightValue) + 1) + + if (leftValue && leftUnit && rightValue && rightUnit) { + conversion[leftUnit "|" rightUnit] = rightValue / leftValue + conversion[rightUnit "|" leftUnit] = leftValue / rightValue + + if (!(leftUnit in graph)) { + graph[leftUnit] = "" + } + if (!(rightUnit in graph)) { + graph[rightUnit] = "" + } + graph[leftUnit] = graph[leftUnit] " " rightUnit + graph[rightUnit] = graph[rightUnit] " " leftUnit + } + } + } + END { + if (!(from in graph) || !(to in graph)) { + print "convert: Units not connected in the graph: " from " -> " to > "/dev/stderr" + exit 1 + } + + queue_from = 1 + queue_to = 1 + queue[queue_to] = from + visited[from] = 1 + path[from] = "" + + while (queue_from <= queue_to) { + current = queue[queue_from] + queue_from++ + + if (current == to) { + conversion_factor = 1 + node = to + conversion_path = num from + + while (node != from) { + prev = path[node] + conversion_factor *= conversion[prev "|" node] + node = prev + if (show && node!=from) conversion_path = conversion_path " | " (num * conversion_factor) node + } + + result = num * conversion_factor + + if (show) { + print "Conversion sequence:" + if (result == int(result)) { + print conversion_path " -> " int(result) to + } else { + printf "%s%." decimals "f%s\n", conversion_path " -> ", result, to + } + } else if (result == int(result)) { + print int(result) + } else { + printf "%." decimals "f\n", result + } + exit + } + + split(graph[current], neighbors, " ") + for (i in neighbors) { + neighbor = neighbors[i] + + if (!(neighbor in visited)) { + visited[neighbor] = 1 + path[neighbor] = current + queue_to++ + queue[queue_to] = neighbor + } + } + } + + print "convert: No path found between " from " and " to > "/dev/stderr" + exit 1 + } + ' "$l/units.config" +} + +convert_edify() { + # By @BlassGO + local tmp="$TMP/edify001.tmp" support func check + cat "$1" | dos2unix > "$tmp" + + # Get all supported functions (Regex) + support=$(compgen -A function | xargs printf '\\<%s\\>|') + support+="\\|\\|\\|\\|\\|\\" + + # Checking supported actions + check=$(grep -Eo "[[:alnum:]_]+[(]" "$tmp" | uniq | cut -d'(' -f1 | grep -Ewv "$support" | while read action; do + [ -z "$action" ] && break + echo "Unsupported action: $action" + done) + [ -n "$check" ] && echo2 "$check" && return 1 + + # Standardizing ifelse(action, elsethis) to action || elsethis + sed -i -Ee 's/ifelse\(([^,]*),[ ]*([^)]*)\)/\1 || \2/g' "$tmp" + + # Edify: action("arg1", "arg2"); to Shell: action "arg1" "arg2" + sed -i -Ee 's/^([[:alnum:]_]+)\((.*)\);$/\1 \2/' "$tmp" + + # Remove any comma that is not inside double quotes + sed -i -Ee ':a; s/^(([^",]|"[^"]*")*),/\1 /; ta' "$tmp" + + # Standardizing op1 comparator op2 to [[ op1 comparator op2 ]] + sed -i -Ee 's/([[:alnum:]_]+[ \t]*\([^)]+\)|[^ ]+)[ \t]*([!=]=|<[=]?|>[=]?)[ \t]*([[:alnum:]_]+[ \t]*\([^)]+\)|[^ ]+)[ \t]*(then)?/[[ \1 \2 \3 ]] \4/g' "$tmp" + sed -i -Ee 's/\[\[ ([^]]+) \]\][ \t]*then/\[\[ \1 \]\]; then/g' "$tmp" + + # Justifying if else indents + sed -i -r '/^[ \t]*if/,/^[ \t]*fi/{ /^[ \t]*if/{h;s/[^[:blank:]].*//;x;b;}; /^[ \t]*if|^[ \t]*fi|^[ \t]*else/! {;G;s/[[:blank:]]*(.*)\n(.*)/\2 \1/}}' "$tmp" + + # Formatting ui_print " " to ui_print ' ' + sed -i -r 's/^([ \t]*ui_print)[^"]*"([^"]*)"/\1 '\''\2'\''/g' "$tmp" + + # Standardizing residual functions to "$(func p1 p2 ...)" + sed -i -r 's/([[:alnum:]_]+)\(([^()]*("[^"]*"|[^()])*[^()]*)\)/"\$(\1 \2)"/g' "$tmp" + + # End + if is_valid "$tmp"; then + [ -z "$2" ] && copy "$tmp" "$1" || copy "$tmp" "$2" + rm -f "$tmp" + else + echo2 "Oops! Empty result" && return 1 + fi +} + +get_var_refs() { + echo "$@" | grep -Eo '%[a-zA-Z_][a-zA-Z0-9_]*' | cut -c 2- +} + +multi_option() { + local ___i=0 ___var="$1" ___end="$2" ___loop=false ___varname ___vars ___vars2 ___ref + local ___selected="${n}Selected %i" ___skipped="Skipped: %i" + [ -z "$___var" ] && return 0 + is_number $___end || ___end=1000 + shift + while [ $# -gt 0 ]; do + case $1 in + loop) + ___loop=true + shift + ;; + *=*) + ___varname="${1%%=*}" + if testvarname "___$___varname"; then + eval "___$___varname='${1#*=}'" + else + echo2 "Invalid variable name: $___varname" + return 0 + fi + shift + ;; + *) + shift + ;; + esac + done + ___vars=$(get_var_refs "$___selected") + ___vars2=$(get_var_refs "$___skipped") + while true; do + ((___i++)) + ___selectedout="$___selected" + ___skippedout="$___skipped" + for ___ref in $___vars; do + ___varname="___$___ref" + ___selectedout="${___selectedout//%$___ref/${!___varname}}" + done + for ___ref in $___vars2; do + ___varname="___$___ref" + ___skippedout="${___skippedout//%$___ref/${!___varname}}" + done + $yes && { + ui_print "$___selectedout" + eval "$___var=$___i" + return $___i + } || { + ui_print "$___skippedout" + } + [ $___i -ge $___end ] && { + $___loop && { + ___i=0 + continue + } || { + eval "$___var=" + return 0 + } + } + done +} + +obfuscate() { + local _obf_ _header_ _key_ _random_ _keys_ _orig_ _prev_ _native_ + local b64=false + while [ $# -gt 0 ]; do + case $1 in + -base64|-b64) + b64=true + shift; + ;; + *) + break + ;; + esac + done + [ ! -z "$1" ] && _native_=$(echo "$1" | sed -E '/^[[:space:]]*#/d') || _native_=$(cat | sed -E '/^[[:space:]]*#/d') + if [ ! -z "$_native_" ]; then + if $b64; then + b64=$(echo "$_native_" | base64) + _native_="eval \"\$(echo \"$b64\" | base64 -d)\"" + fi + _orig_="$_native_"; _prev_="$_native_" + else return 1 + fi + while IFS= read _obf_; do + _random_=$(random | tr '[0-9]' '[a-z]' | tr -d "$_obf_") + _orig_=$(echo "$_orig_" | sed -Ee :1 -e 's/^(([^{]|\{[^{]*\})*)['"$_obf_"']/\1\$\{'"$_random_"'\}/;t1') + if [[ "$_orig_" != "$_prev_" ]]; then [ "$_obf_" != "\"" ] && _keys_+="$_random_=\"$_obf_\";" || _keys_+="$_random_=\\$_obf_;"; fi + _prev_="$_orig_" + done < <(echo "${_orig_}" | tr -d '${}' | grep -Eo '[[:print:]]|[[:space:]]' | sort -uf) + _orig_="eval \"$_orig_\"" + for _obf_ in " " a e l v; do + _random_=$(echo "$_keys_" | tr ';' '\n' | grep -F "=\"$_obf_\"" | cut -d= -f1) + if [ -z "$_random_" ]; then _random_=$(random | tr '[0-9]' '[a-z]' | tr -d "$_obf_"); _keys_+="$_random_=\"$_obf_\";"; fi + _orig_=$(echo "$_orig_" | sed -Ee :1 -e 's/^(([^{]|\{[^{]*\})*)['"$_obf_"']/\1\$\{'"$_random_"'\}/;t1') + done + case $_orig_ in + *$'\n'*) + _random_=$(random | tr '[0-9]' '[a-z]') + _keys_+="$_random_=\$'\n';" + _orig_=$(echo "$_orig_" | sed -E '/^[[:space:]]*$/d') + _orig_=${_orig_//$'\n'/\$\{$_random_\}} + ;; + *);; + esac + if [[ "$_orig_" != "$_native_" ]]; then + _orig_=${_orig_//"\$\$"/\\\$\$} + echo "${_keys_}$_orig_" + else return 1 + fi +} + +replace_name() { + #regex="([^//]*/)*(${1}[^//]*)$" + local dir=false file=false find any name newname r regex + while [ $# -gt 0 ]; do + case $1 in + -dirs|-d) + dir=true + shift; + ;; + -files|-f) + file=true + shift; + ;; + -recursive|-r) + r=true + shift; + ;; + -regex) + regex=true + shift; + ;; + *) + break + ;; + esac + done + if $file && $dir; then if [ -z "$r" ]; then if [ ! -f "$3" -o ! -d "$3" ]; then echo2 "CANT FIND: $3" && return 1; fi; else find=" ( -type f -o -type d ) "; fi + elif $file; then if [ -z "$r" ]; then if [ ! -f "$3" ]; then echo2 "CANT FIND FILE: $3" && return 1; fi; else find=" -type f "; fi + elif $dir; then if [ -z "$r" ]; then if [ ! -d "$3" ]; then echo2 "CANT FIND DIR: $3" && return 1; fi; else find=" -type d "; fi + else if [ -z "$r" ]; then if [ ! -e "$3" ]; then echo2 "CANT FIND: $3" && return 1; fi; fi + fi + if [ -n "$r" -a ! -d "$3" ]; then echo2 "CANT FIND DIR: $3" && return 1; fi + while read any; do + name=$(basename "$any") + if [ -n "$regex" ]; then newname=$(echo "$name" | sed -E "s;$1;$2;g"); else newname=${name//"$1"/"$2"}; fi + [ "$newname" == "$name" ] && continue + rename "$any" "$newname" + done < <(if [ -n "$r" ]; then find "$3" -mindepth 1 $find; else echo "$3"; fi) +} + +abort() { + ui_print "$@" + umount_all + [ -n "$MODPATH" ] && rm -rf "$MODPATH" + remove_tmp + restore_env + exit 130 +} + +end() { + ui_print "$@" + umount_all + [ -n "$MODPATH" ] && rm -rf "$MODPATH" + remove_tmp + restore_env + exit 0 +} + +wipe() { + local wipe return=0 + [ $# == 0 ] && return 1 + for wipe in "$@"; do + case $wipe in + data) + if ! try_mount -e -rw /data; then echo2 "CANT WIPE: /data" && return=1 && continue; fi + echo2 " -- Wiping /data" + find /data -mindepth 1 ! -regex "/data/media.*" -delete + ;; + userdata) + if ! try_mount -e -rw /data; then echo2 "CANT WIPE: /data and /data/media" && return=1 && continue; fi + echo2 " -- Wiping /data and /data/media" + rm -rf /data/* + ;; + dalvik) + if ! try_mount -e -rw /data; then echo2 "CANT WIPE: /data/dalvik-cache" && return=1 && continue; fi + echo2 " -- Wiping /data/dalvik-cache" + rm -rf /data/dalvik-cache + ;; + *[[:space:]]*) + echo2 "wipe: Partition names cannot contain spaces: \"$wipe\"" + return 1 + ;; + *) + if ! try_mount -rw "/$wipe"; then echo2 "CANT WIPE: /$wipe" && return=1 && continue; fi + echo2 " -- Wiping /$wipe" + rm -rf /$wipe/* + unmount "/$wipe" + ;; + esac + done + return $return +} + +set_metadata() { + local file i; + file="$1"; + shift; + testrw "$(dirname "$file")" || return 1 + while [ "$2" ]; do + case $1 in + uid) chown $2 "$file";; + gid) chown :$2 "$file";; + mode) chmod $2 "$file";; + capabilities) if command -v twrp; then twrp setcap "$file" $2; else echo2 'Oops! You can't apply "capabilities" without the "twrp" binary, don't mount /system! to use it'; fi ;; + selabel) ch_con $2 "$file";; + *) ;; + esac; + shift 2; + done; +} + +set_metadata_recursive() { + local dir uid gid fmode dmode capabilities selabel twrp=false huh; + dir="$1"; + shift; + testrw "$dir" || return 1 + while [ "$2" ]; do + case $1 in + uid) uid=$2;; + gid) gid=$2;; + fmode) fmode=$2;; + dmode) dmode=$2;; + capabilities) capabilities=$2;; + selabel) selabel=$2;; + *) ;; + esac; + shift 2; + done; + if [ -n "$capabilities" ]; then command -v twrp && twrp=true || echo2 'Oops! You can't apply "capabilities" without the "twrp" binary, don't mount /system! to use it'; fi + [ -n "$uid" ] && chown -R $uid "$dir" + [ -n "$gid" ] && chown -R :$gid "$dir" + if [ -n "$fmode" ]; then + chmod -R $fmode "$dir" + find "$dir" -type l -exec chmod $fmode {} +; + fi + [ -n "$dmode" ] && find "$dir" -type d -exec chmod $dmode {} +; + find "$dir" -mindepth 1 | while read huh; do + $twrp && [ -n "$capabilities" ] && twrp setcap "$huh" $capabilities + [ -n "$selabel" ] && ch_con $selabel "$huh" + done +} + +update_file() { + local TMP2 file fname tmp props result=0 force + [ $# -lt 2 ] && return 1 + while [ $# -gt 0 ]; do + case $1 in + -force) + force=true + shift + ;; + *) + break + ;; + esac; + done; + file=${!#} + if [ ! -e "$file" ]; then echo "CANT FIND: $file" && return 1; fi + testrw "$(dirname "$file")" || return 1 + start_tmp + fname=$(basename "$file") + tmp="$TMP2/$fname" + props=$(awk ' + BEGIN { props = ""; delim = "\001"} + index($0, "=") { + props = props $0 delim; + } + END { if (props != "") print substr(props, 1, length(props) - 1) } + ' "${@:1:$#-1}") && awk -v force="$force" -v fname="$fname" -v props="$props" -F= ' + BEGIN { + exitcode = 1; + n = split(props, kv_pairs, "\001"); + for (i = 1; i <= n; i++) { + at = index(kv_pairs[i], "=") + if (at) { + keys[i] = substr(kv_pairs[i], 1, at - 1); + values[i] = substr(kv_pairs[i], at + 1); + } + } + } + { + for (i = 1; i <= n; i++) { + if ($1 == keys[i]) { + check[i] = 1 + if ($2 != values[i]) { + $0 = substr($0, 1, index($0, "=") - 1) "=" values[i]; + print "Updated prop: " keys[i] > "/dev/stderr" + exitcode = 0; + } + break; + } + } + print; + } + END { + if (force) { + for (i = 1; i <= n; i++) { + if (!check[i]) { + print keys[i] "=" values[i]; + print "Added prop: " keys[i] > "/dev/stderr" + exitcode = 0; + } + } + } + exit exitcode + } + ' "$file" > "$tmp" && { + inject "$tmp" "$file" 1 + result=0 + } || { + echo2 "update_file: No changes: $file" + result=1 + } + end_tmp + return $result +} + +update_file_string() { + local TMP2 file fname tmp props result=0 force + [ $# -lt 2 ] && return 1 + while [ $# -gt 0 ]; do + case $1 in + -force) + force=true + shift + ;; + *) + break + ;; + esac; + done; + file=${!#} + if [ ! -e "$file" ]; then echo "CANT FIND: $file" && return 1; fi + testrw "$(dirname "$file")" || return 1 + start_tmp + fname=$(basename "$file") + tmp="$TMP2/$fname" + printf -v props "%s\001" "${@:1:$#-1}" + awk -v force="$force" -v fname="$fname" -v props="${props:0:-1}" -F= ' + BEGIN { + exitcode = 1; + n = split(props, kv_pairs, "\001"); + for (i = 1; i <= n; i++) { + at = index(kv_pairs[i], "=") + if (at) { + keys[i] = substr(kv_pairs[i], 1, at - 1); + values[i] = substr(kv_pairs[i], at + 1); + } + } + } + { + for (i = 1; i <= n; i++) { + if ($1 == keys[i]) { + check[i] = 1 + if ($2 != values[i]) { + $0 = substr($0, 1, index($0, "=") - 1) "=" values[i]; + print "Updated prop: " keys[i] > "/dev/stderr" + exitcode = 0; + } + break; + } + } + print; + } + END { + if (force) { + for (i = 1; i <= n; i++) { + if (!check[i]) { + print keys[i] "=" values[i]; + print "Added prop: " keys[i] > "/dev/stderr" + exitcode = 0; + } + } + } + exit exitcode + } + ' "$file" > "$tmp" && { + inject "$tmp" "$file" 1 + result=0 + } || { + echo2 "update_file_string: No changes: $file" + result=1 + } + end_tmp + return $result +} + +update_file_addon() { + [ $# -lt 2 ] && return 1 + local ___ params + for ___ in "${@:1:$#-1}"; do + case $___ in + -force) + params+=("$___") + ;; + *) + params+=("$addons/$___") + ;; + esac; + done + update_file "${params[@]}" "${!#}" +} + +update_file_zip() { + [ $# -lt 2 ] && return 1 + local ___ params content + for ___ in "${@:1:$#-1}"; do + case $___ in + -force) + params+=("$___") + ;; + *) + mapfile -t content <<< $(package_extract_file "$___") + params+=("${content[@]}") + ;; + esac; + done + update_file_string "${params[@]}" "${!#}" +} + +flash() { + local fd + [ "$3" == "print" ] && fd=${OUTFD##*/} || fd=3 + if ! is_zip "$2"; then echo2 "flash: $(basename "$2") isnt a ZIP!" && return 1; fi + unzip -qp "$2" "META-INF/com/google/android/update-binary" 2>/dev/null > "$TMP/update-binary-test" + if ! is_valid "$TMP/update-binary-test"; then echo2 "flash: Empty binary: $(basename "$2") unsupported!" && return 1; fi + chmod +x "$TMP/update-binary-test" + setdefault "$1" "$("$TMP/update-binary-test" "$fd" "$fd" "$2" 2>&1)" + if checkvar "$1" | grep -q "bad interpreter:"; then + setdefault "$1" "$(sh "$TMP/update-binary-test" "$fd" "$fd" "$2" 2>&1)" + fi +} + +flash_addon() { + flash "$1" "$addons/$2" "$3" +} + +flash_zip() { + package_extract_file "$2" "$TMP/$(basename "$2")" || return 1 + flash "$1" "$TMP/$(basename "$2")" "$3" + rm -f "$TMP/$(basename "$2")" +} + +add_lines() { + [ $# -lt 2 ] && return 1 + local file tmp TMP2 empty=false opt + file=${!#} + if [ ! -f "$file" ]; then + create_file "$file" || return 1 + empty=true + else + testrw "$(dirname "$file")" || return 1 + is_valid "$file" || empty=true + fi + start_tmp + tmp="$TMP2/$(basename "$file")" + cp -f "$file" "$tmp" || return 1 + while [ $# -gt 1 ]; do + case $1 in + -al|-after-line) + string force $opt -al "$2" "$(<"$3")" "$(<"$tmp")" > "$tmp" + shift 3 + ;; + -bl|-before-line) + string force $opt -bl "$2" "$(<"$3")" "$(<"$tmp")" > "$tmp" + shift 3 + ;; + -r|-recursive) + opt="-r" + shift + ;; + *) + [ -f "$1" ] && cat "$1" >> "$tmp" || { echo2 "CANT FIND: $1" ; return 1 ; } + shift + ;; + esac + done + if ! is_valid "$tmp" && ! $empty; then + echo2 " FATAL ERROR: Invalid $tmp" + return 1 + fi + inject "$tmp" "$file" 1 + end_tmp + return 0 +} + +add_lines_string() { + [ $# -lt 2 ] && return 1 + local file tmp TMP2 empty=false opt + file=${!#} + if [ ! -f "$file" ]; then + create_file "$file" || return 1 + empty=true + else + testrw "$(dirname "$file")" || return 1 + is_valid "$file" || empty=true + fi + start_tmp + tmp="$TMP2/$(basename "$file")" + cp -f "$file" "$tmp" || return 1 + while [ $# -gt 1 ]; do + case $1 in + -al|-after-line) + string force $opt -al "$2" "$3" "$(<"$tmp")" > "$tmp" + shift 3 + ;; + -bl|-before-line) + string force $opt -bl "$2" "$3" "$(<"$tmp")" > "$tmp" + shift 3 + ;; + -r|-recursive) + opt="-r" + shift + ;; + *) + echo "$1" >> "$tmp" + shift + ;; + esac + done + if ! is_valid "$tmp" && ! $empty; then + echo2 " FATAL ERROR: Invalid $tmp" + return 1 + fi + inject "$tmp" "$file" 1 + end_tmp + return 0 +} + +add_lines_addon() { + local args + while [ $# -gt 1 ]; do + case $1 in + -al|-after-line) + args+=("$1" "$2" "$addons/$3") + shift 3 + ;; + -bl|-before-line) + args+=("$1" "$2" "$addons/$3") + shift 3 + ;; + -r|-recursive) + args+=("$1") + shift + ;; + *) + args+=("$addons/$1") + shift + ;; + esac + done + add_lines "${args[@]}" "$1" +} + +add_lines_zip() { + [ $# -lt 2 ] && return 1 + local file tmp TMP2 empty=false opt + file=${!#} + if [ ! -f "$file" ]; then + create_file "$file" || return 1 + empty=true + else + testrw "$(dirname "$file")" || return 1 + is_valid "$file" || empty=true + fi + start_tmp + tmp="$TMP2/$(basename "$file")" + cp -f "$file" "$tmp" || return 1 + while [ $# -gt 1 ]; do + case $1 in + -al|-after-line) + string force $opt -al "$2" "$(package_extract_file "$3")" "$(<"$tmp")" > "$tmp" + shift 3 + ;; + -bl|-before-line) + string force $opt -bl "$2" "$(package_extract_file "$3")" "$(<"$tmp")" > "$tmp" + shift 3 + ;; + -r|-recursive) + opt="-r" + shift + ;; + *) + package_extract_file "$1" >> "$tmp" + shift + ;; + esac + done + if ! is_valid "$tmp" && ! $empty; then + echo2 " FATAL ERROR: Invalid $tmp" + return 1 + fi + inject "$tmp" "$file" 1 + end_tmp + return 0 +} + +find_bin() { local IFS=":"; find $PATH -name "$1" -executable 2>/dev/null; } + +ensure_bin() { + local _r_ __ bb_set=false bb_list + local bb=$(command -v busybox) + [ -z "$needed" ] && local needed=false + _r_=0 + for __ in "$@"; do + if ! command -v $__ >/dev/null; then + $bb_set || { bb_set=true; [ -n "$bb" ] && bb_list=" $(echo $("$bb" --list)) "; } + { [ -n "$bb" ] && is_substring " $__ " "$bb_list" ; } && eval "$__() { \"$bb\" $__ \"\$@\"; }" || { $needed && abort "ERROR: Could not define \"$__\" binary" || _r_=1; } + fi + done + return $_r_ +} + +change_bin() { + local while + local path current pass name return=0 + create_dir "$TMP/zbin/change_bin" || return 1 + while [ $# -gt 0 ]; do + case $1 in + -while|-w) + while="$2" + shift 2 + ;; + *) + break + ;; + esac + done + [[ "$PATH" =~ ^$TMP/zbin/change_bin ]] || export PATH="$TMP/zbin/change_bin:$PATH" + while [ "$1" ]; do + pass=false + name=$( { basename "$1" || echo "$1" ; } 2>/dev/null) + [ -n "$(declare -f "$name" 2>/dev/null)" ] && echo2 "Unsetting function: \"$name\"" && unset -f "$name" + [ -e "$TMP/zbin/change_bin/$name" ] && current=$(readlink "$TMP/zbin/change_bin/$name") || current=$(command -v "$name") + echo2 " " + if [ -e "$current" ]; then echo2 "Currently using: \"$current\" binary" + else echo2 "Cant find any reference for: \"$name\" binary" && return 1 + fi + while read bin; do + if [ -e "$bin" -a "$bin" != "$current" ] && { [ -z "$while" ] || support_option "$while" "$bin"; }; then echo2 "Changing to: \"$bin\" binary"; ln -sf "$bin" "$TMP/zbin/change_bin/$name" && pass=true; break; fi + done < <([ -f "$1" ] && echo "$1" || find_bin "$1" | sort -bu | grep -Ev "^$TMP/zbin/change_bin" | shuf) + if ! $pass; then echo2 "No possible changes found for: \"$name\" binary"; return=1; fi + echo2 " " + shift; + done + return $return +} + +set_context() { + local context contextback huh path dir ext try backdir + if [ -e "$1" ]; then + context=$(eval_context "$1") + else + echo2 "set_context: Invalid line" + return 1 + fi + if [ -n "$context" ]; then + contextback="$context" + else + echo2 "set_context: FATAL ERROR: Can't get context from $1" + return 1 + fi + if [ -d "$(readlink -f "$2")" ]; then + testrw "$2" || return 1 + find -L "$2" \( -type f -o -type d -o -type l \) 2>/dev/null | while read -r huh; do + context="$contextback" + relative_path=${huh#"$2"/} + if [ -d "$1" -a -e "$1/$relative_path" ]; then + context=$(eval_context "$1/$relative_path") + else + ext=${huh##*.} + dir=$(dirname "$1/$relative_path") + while [ ! -d "$dir" ]; do + dir=$(dirname "$dir") + done + [ "$dir" == "/" -o "$dir" == "." ] && break + if [ -f "$huh" -a -n "$ext" ]; then + try=$(find -L "$dir" -mindepth 1 -type f -name "*.$ext" -print -quit) + [ -e "$try" ] && context=$(eval_context "$try") + fi + [ -z "$context" ] && context=$(eval_context "$dir") + fi + [ -z "$context" ] && context="$contextback" + ch_con "$context" "$huh" + done + elif [ -f "$2" ]; then + ch_con "$context" "$2" + else + echo2 "set_context: Invalid line" + return 1 + fi +} + +eval_context() { ls -Z "$1" | awk '{print $1}' | sort | uniq -c | sort -rn | awk 'NR==1{print $2}'; } + +get_context() { [ -d "$1" ] && ls -Zd "$1" 2>/dev/null | awk '{print $1}' || ls -Z "$1" 2>/dev/null | awk '{print $1}'; } + +eval_perm() { { stat -c "%a" "$1"/* 2>/dev/null || stat -c "%a" "$1" ; } | awk '{count[$1]++} END {for (i in count) print count[i], i}' | sort -rn | awk 'NR==1{print $2}'; } + +eval_user() { { stat -c "%u" "$1"/* 2>/dev/null || stat -c "%u" "$1" ; } | awk '{count[$1]++} END {for (i in count) print count[i], i}' | sort -rn | awk 'NR==1{print $2}'; } + +eval_group() { { stat -c "%g" "$1"/* 2>/dev/null || stat -c "%g" "$1" ; } | awk '{count[$1]++} END {for (i in count) print count[i], i}' | sort -rn | awk 'NR==1{print $2}'; } + +get_all_perm() { stat -c "%u %g %a" "$1" 2>/dev/null; } + +eval_all_perm() { echo "$(eval_user "$1") $(eval_group "$1") $(eval_perm "$1")"; } + +patch_fstab() { + #patch_fstab userdata "fileencryption" "encryptable" "FILE" + local in path attr orig part fstab match return=1 backup=false + local header restore fix='s/[]\/.^$*+?{}()|[]/\\&/g' count total + restore=() + while [ $# -gt 0 ]; do + case $1 in + -h|-header) + header=true + shift + ;; + -b|-backup) + backup=true + shift + ;; + *) + restore+=("$1") + shift + ;; + esac + done + in=${restore[0]} + unset "restore[0]" + restore=("${restore[@]}") + path=${restore[${#restore[@]} - 1]} + unset "restore[${#restore[@]}-1]" + total=${#restore[@]} + if [ ! -e "$path" ]; then echo2 "patch_fstab: Cant find $path" && return 1; fi + while IFS=$'\n' read -r fstab; do + if [ -z "$fstab" ]; then echo2 "patch_fstab: Cant get \"$in\" line in $path" && return 1; fi + attr=$(echo "$fstab" | grep -Eo '[^ ]*([,].*|$)') + part=${fstab/"$attr"/} + if [ -n "$header" ]; then + part=${part//" "/$'\n'} + orig="$part" + count=0 + while [ $count -lt $total ]; do + while IFS=$'\n' read -r match; do + [ -z "$match" ] && break + echo2 "patch_fstab: \"$match\" to \"${restore[$count+1]}\"" + part=${part/"$match"/"${restore[$count+1]}"} + done <<< $(echo "$part" | grep -F "${restore[$count]}") + count=$((count+2)) + done + if [ "$orig" != "$part" ]; then + part=$(echo "$part" | grep "." | tr '\n' ' ' | sed -r 's/[ ]+$//') + $backup && part="#$fstab${n}$part" + if replace "$fstab" "$part $attr" "$path"; then + return=0 + else return 1 + fi + fi + elif [ -n "$attr" ]; then + attr=${attr//","/$'\n'} + orig="$attr" + count=0 + while [ $count -lt $total ]; do + while IFS=$'\n' read -r match; do + [ -z "$match" ] && break + echo2 "patch_fstab: \"$match\" to \"${restore[$count+1]}\"" + attr=${attr/"$match"/"${restore[$count+1]}"} + done <<< $(echo "$attr" | grep -F "${restore[$count]}") + count=$((count+2)) + done + if [ "$orig" != "$attr" ]; then + attr=$(echo "$attr" | grep "." | tr '\n' ',' | sed 's/,$//') + $backup && part="#$fstab${n}$part" + if replace "$fstab" "$part$attr" "$path"; then + return=0 + else return 1 + fi + fi + else + echo2 "patch_fstab: No attributes found: $path" + fi + done <<< $(grep -E "^[ \t]*[^#].*$(echo "$in" | sed -e $fix)" "$path") + [ $return = 0 ] && echo2 "patch_fstab: Patched $path" + return $return +} + +import_config() { + local ___ + while IFS='' read -r ___; do + IFS=$'\n' read -r -d '' "$(echo ${___%%=*})" <<< "${___#*=}" + done <<< $(grep -E '^[ \t]*[a-zA-Z_][a-zA-Z_0-9]*[ \t]*\=' "$1") +} + +import_config_addon() { + import_config "$addons/$1" +} + +import_config_zip() { + package_extract_file "$1" "$TMP/$(basename "$1")" || return 1 + import_config "$TMP/$(basename "$1")" + rm -f "$TMP/$(basename "$1")" +} + +getdefault() { + awk -v key="^[ \t]*setdefault $2" '$0 ~ key { sub(/^[ \t]*setdefault[ \t]+[^ \t]+[ \t]*/, "", $0); gsub(/"/, "", $0); print; exit }' "$1" +} + +construct_default() { + awk -v terms="$2" ' + BEGIN { + p = "setdefault" + n = split(terms, search_terms, " "); + for (i = 1; i <= n; i++) search_keys[search_terms[i]] = 0; + found = 0; + quote = "\""; + } + { + if ($1 == p && $2 in search_keys) { + if (NF >= 3) value = (substr($NF, length($NF)) == quote) ? substr($0, index($0,quote)) : $NF; + else value = ""; + print $2 "=" value; + search_keys[$2] = 1; + found++; + if (found == n) exit; + } else if (found > 0) { + exit + } + } + END { + for (key in search_keys) { + if (!search_keys[key]) { + print key "=" + } + } + } + ' "$1" +} + +savestate() { + setdefault "$1" "$(md5sum "$2" | awk '{ print $1; }')" +} + +startlog() { + ___logpath="$1" + create_file "$1" || endlog +} + +endlog() { ___logpath=; } + +savelog() { + if [ -n "$___logpath" ]; then + while [ "$1" ]; do + echo "$1" >> "$___logpath" + shift; + done + fi + return 0 +} + +echolog() { + if [ -n "$___logpath" ]; then + while [ "$1" ]; do + echo "$1" >> "$___logpath" + echo2 "$1" + shift; + done + fi + return 0 +} + +printlog() { + if [ -n "$___logpath" ]; then + while [ "$1" ]; do + echo "$1" >> "$___logpath" + ui_print "$1" + shift; + done + fi + return 0 +} + +symlink() { + local file=$(fullpath "$1"); + [ ! -e "$file" ] && return 1 + while [ "$2" ]; do + testrw "$(dirname "$2")" || return 1 + ln -sf "$file" "$2"; + shift; + done; +} + +restore_con() { + restorecon -R "$@"; +} + +backup_files() { + while [ "$1" ]; do + testrw "$(dirname "$1")" || return 1 + test ! -e "$1.bak" && cp -pf "$1" "$1.bak"; + shift; + done; +} + +restore_files() { + while [ "$1" ]; do + testrw "$(dirname "$1")" || return 1 + mv -f "${1}.bak" "$1"; + shift; + done; +} + +sha3_check() { + local sum=$(sha3sum $1 | cut -c-40); + if [ ! "$2" -o $(is_substring $sum "$*") == 1 ]; then + echo $sum; + fi; +} + +assert() { + while [ "$1" ]; do + $1 || abort "assert failed -> \"$1\""; + shift; + done; +} + +split_string() { + echo "${2//"$1"/$'\n'}" +} + +split_cut() { + echo "$3" | awk -v delim="$1" -v max="$2" 'BEGIN { count = 0 } + { + while (match($0, delim)) { + print substr($0, 1, RSTART - 1) + count++ + if (count == max) exit + $0 = substr($0, RSTART + RLENGTH) + } + if (count < max) print $0 + }' +} + +split_extract() { + echo "$3" | awk -v delim="$1" -v target="$2" 'BEGIN { count = 0 } + { + while (match($0, delim)) { + count++ + if (count == target) { + print substr($0, 1, RSTART - 1) + exit + } + $0 = substr($0, RSTART + RLENGTH) + } + if (count + 1 == target) print $0 + }' +} + +checkvar() { + local ___ + for ___; do + [ -n "${!___}" ] && echo "${!___}" + done +} + +filtervar() { + [ $# -lt 2 ] && return 1 + local ___ + for ___ in "${@:1:$#-1}"; do + [[ "${!___}" == *"${!#}"* ]] && echo "${!___}" + done +} + +import_bin() { + cp -pf "$1" "$l" && chmod +x "$l/$(basename "$1")" || { echo2 " Cant import: $1"; return 1 ; } +} + +import_bin_addon() { + import_bin "$addons/$1" +} + +import_bin_zip() { + local result dest="$TMP/$(basename "$1")" + package_extract_file "$1" "$dest" || return 1 + import_bin "$dest" + result=$? + rm -f "$dest" + return $result +} + +import_module() { + . <(unzip -qp "$1" module.sh) "$@" +} + +import_module_addon() { + import_module "$addons/$1" "${@:2}" +} + +import_module_zip() { + local result dest="$TMP/$(basename "$1")" + package_extract_file "$1" "$dest" || return 1 + import_module "$dest" "${@:2}" + result=$? + rm -f "$dest" + return $result +} + +add_switch() { + local runtype count=0 source dest package zip_path + while [ $# -gt 0 ]; do + case $1 in + -import) + runtype=import + shift + ;; + -execute|-exec) + runtype=exec + shift + ;; + *) + break + ;; + esac + done + [ $# != 2 ] && { echo2 "add_switch: Invalid arguments: $1"; return 1 ; } + package=$(echo "${1%%:*}" | xargs); zip_path=$(echo "${1##*:}" | xargs) + [ "$zip_path" != "$package" ] && ___SWITCHZIPS["$package"]="$zip_path" || ___SWITCHZIPS["$package"]= + [ -n "$runtype" ] && ___SWITCHSH["$package"]=$runtype || ___SWITCHSH["$package"]= + while IFS== read -r version data; do + version=$(echo "$version" | xargs) + data=$(echo "$data" | xargs) + if [ -n "$version" -a -n "$data" ]; then + source=$(echo "${data%%,*}" | xargs) + dest=$(echo "${data##*,}" | xargs) + [ "$dest" == "$source" ] && { [ -n "$runtype" ] && dest="$TMP/zbin/switch/" || dest="$l/" ; } + ___SWITCHS["$package|$version"]="$source|$dest" && ((count++)) + fi + done <<< "$2" + echo2 "Attached $count versions for package \"$package\"" +} + +switch() { + local key data source dest zip_file sh fromdir=false todir=false + local package="$1" version="$2" + if [ -z "$package" -o -z "$version" ]; then + echo2 "switch: Insufficient values: $1" + return 1 + fi + key="$package|$version"; data="${___SWITCHS[$key]}" + if [ -z "$data" ]; then + echo2 "switch: Version not found or not defined: \"$version\" in package \"$package\"" + return 1 + fi + source="${data%%|*}"; dest="${data##*|}" + zip_file="${___SWITCHZIPS[$package]}" + sh="${___SWITCHSH[$package]}" + [ "${source: -1: 1}" == "/" ] && fromdir=true + [ "${dest: -1: 1}" == "/" -o -d "$dest" ] && todir=true + if [ -n "$zip_file" ]; then + [ -f "$zip_file" ] || { echo2 "switch: Cant find: $zip_file"; return 1 ; } + echo2 "switch: Extracting \"$source\" to \"$dest\"" + if $fromdir; then + package_extract_dir "$source" "$dest" "$zip_file" || return 1 + else + if $todir; then + dest="${dest%%/}/$(basename "$source")" + package_extract_file "$source" "$dest" "$zip_file" || return 1 + else + package_extract_file "$source" "$dest" "$zip_file" || return 1 + fi + fi + source="$dest" + elif [ -z "$sh" ]; then + [ -e "$source" ] || { echo2 "switch: Cant find: $source"; return 1 ; } + echo2 "switch: Copying \"$source\" to \"$dest\"" + if $todir; then create_dir "$dest" || return 1 ; else create_dir "$(dirname "$dest")" || return 1 ; fi + cp -prf "$source" "$dest" && return 0 || return 1 + fi + [ -f "$source" ] || { echo2 "switch: Cant find file: $source"; return 1 ; } + shift 2 + case $sh in + import) + echo2 "switch: Importing \"$source\" with $# args" + . "$source" "$@" + ;; + exec) + is_exec "$source" || chmod +x "$source" + is_exec "$source" && dest="$source" || { + [ -n "$zip_file" ] && { echo2 "switch: The specified dest path is not executable: $dest"; return 1 ; } + echo2 "switch: Copying \"$source\" to \"$dest\"" + if $todir; then create_dir "$dest" || return 1 ; else create_dir "$(dirname "$dest")" || return 1 ; fi + cp -prf "$source" "$dest" || return 1 + $todir && dest="${dest%%/}/$(basename "$source")" + chmod +x "$dest" + } + echo2 "switch: Executing \"$dest\" with $# args" + "$dest" "$@" + ;; + esac +} + +setdefault() { + [ -z "$1" ] && return 1 + [ "$1" = "permissions" ] && { + IFS=':' read -r ___uid ___gid ___mod ___mof <<< "$2" + ___uid=${___uid//[!0-9]/} + ___gid=${___gid//[!0-9]/} + ___mod=${___mod//[!0-7]/} + ___mof=${___mof//[!0-7]/} + ___uid=${___uid:-0} + ___gid=${___gid:-0} + ___mod=${___mod:-0755} + ___mof=${___mof:-0644} + } + IFS=$'\n' read -r -d '' "$1" <<< "$2" +} + +can_run() { + local check try + [ -z "$2" ] && try="--help" || try="$2" + if [ -f "$1" ]; then + check=$("$1" $try 2>&1) + [[ -x "$1" && "$check" != *"CANNOT LINK"* ]] && return 0 || return 1 + elif [ -f "$(which "$1")" ]; then + check=$("$(which "$1")" $try 2>&1) + [[ -x "$(which "$1")" && "$check" != *"CANNOT LINK"* ]] && return 0 || return 1 + else return 1 + fi +} + +make_overlay() { + #make_overlay 1 com.android FOLDER result.apk + local package content result tmp current random priority TMP2 + priority="$1" + package="$2" + content="$3" + result="$4" + start_tmp + tmp="$TMP2/overlay" + echo ">>Overlay Maker 1.0.0" + if [[ -z "$package" || -z "$content" || -z "$result" || -z "$priority" || ! -d "$content" ]]; then echo " make_overlay: Invalid line " && return; fi + echo " L: Detected $package..." + echo " L: With $content..." + echo " L: Building $(basename "$result")..." + random=$(random) + apktool -f d "$l/overlay.apk" -o "$tmp" >&2 + if ! exist "$tmp/AndroidManifest.xml"; then echo " CANT setup: make_overlay " && return 1; fi + { replace "overlay.template" "com.$random.dynamic.installer" "$tmp/AndroidManifest.xml" + replace 'android:targetPackage="android"' 'android:targetPackage="'$package'"' "$tmp/AndroidManifest.xml" + replace 'android:priority="1"' 'android:priority="'$priority'"' "$tmp/AndroidManifest.xml" ; } 2>/dev/null + rm -rf "$tmp/res" + current=${PWD} + cd "$content" + cp -prf * "$tmp" + cd "$current" + rm -f "$tmp/res/values/public.xml" + dynamic_apktool -sign -r "$tmp" -o "$result" >/dev/null + if exist "$result"; then echo " L: Success $(basename "$result")"; else echo " make_overlay: Cant compile $(basename "$result")" && return 1; fi + end_tmp +} + +make_overlay_addon() { + make_overlay "$1" "$2" "$addons/$3" "$4" +} + +make_overlay_zip() { + local result + rm -rf "$TMP/00patch" + package_extract_dir "$3" "$TMP/00patch" || return 1 + make_overlay "$1" "$2" "$TMP/00patch" "$4" + result=$? + rm -rf "$TMP/00patch" + return $result +} + +make_zip() { + local make="$TMP/NewZIP" current=${PWD} + local script include include2 type pa pm output i base head + local base_r="META-INF/com/google/android" base_m="META-INF/com/google/android/magisk" + local recovery="$make/$base_r" + local magisk="$make/$base_m" + local addons="META-INF/addons" + while [ $# -gt 0 ]; do + case $1 in + -script|-s) + script="$2" + shift 2; + ;; + -preserve-addons|-pa) + pa=true + shift + ;; + -preserve-magisk|-pm) + pm=true + shift + ;; + -head|-h) + head="$2" + shift 2; + ;; + -type|-t) + type="$2" + shift 2; + ;; + -output|-o) + output="$2" + shift 2; + ;; + -include|-i) + include+=("$2") + shift 2; + ;; + -magisk-include|-mi) + include2+=("$2") + shift 2; + ;; + *) + echo2 "make_zip: Unknown option: $1" + return 1 + ;; + esac + done + set -- "${restore[@]}" + for base in script type output; do + if undefined $base; then echo2 "make_zip: You need to set the parameter: -$base" && return 1; fi + done + echo2 '>> Make ZIP 1.0.0' + echo2 "-- Making space..." + rm -rf "$make" + rm -f "$output" + package_extract_dir META-INF/zbin "$make/META-INF/zbin" || return 1 + for base in update-binary updater-script; do + package_extract_file "$base_r/$base" "$recovery/$base" || return 1 + done + mkdir -p "$make/$addons" + mkdir -p "$magisk" + if defined pa; then package_extract_dir "$addons" "$make/$addons"; fi + if defined pm; then package_extract_dir "$base_m" "$magisk"; fi + for i in "${!include[@]}"; do + if exist "${include[i]}"; then + echo2 "Adding: ${include[i]}" + cp -pfr "${include[i]}" "$make" + fi + done + for i in "${!include2[@]}"; do + if exist "${include2[i]}"; then + echo2 "Adding:magisk: ${include2[i]}" + cp -pfr "${include2[i]}" "$magisk" + fi + done + echo2 "-- Making scripts..." + if [[ "$type" == "recovery" ]]; then + rm -f "$recovery/updater-script" + if ! is_substring "#MAGISK" "$script"; then echo -e "#MAGISK\n" > "$recovery/updater-script"; fi + echo "$script" >> "$recovery/updater-script" + elif [[ "$type" == "magisk" ]]; then + [ -z "$head" ] && head=$(string complete_extract 'Dynamic Installer Configs' '#-----------------------' -f "$recovery/updater-script") + if [ -n "$head" ] && is_substring setdefault "$head"; then + rm -f "$recovery/updater-script" + is_substring "#MAGISK" "$head" || echo -e "#MAGISK\n" > "$recovery/updater-script" + echo "$head" >> "$recovery/updater-script" + else echo2 "make_zip: Cant get: Dynamic Installer Configs: updater-script" && return 1 + fi + echo "$script" > "$magisk/customize.sh" + else + echo2 "FATAL ERROR: Invalid type" && return 1 + fi + echo2 "-- Making ZIP..." + create_dir "$(dirname "$output")" || return 1 + { cd "$make" && zip -r "$output" * ; } > /dev/null + cd "$current" || cd / + rm -rf "$make" + if is_valid "$output"; then echo2 "-- Done" && return 0; else echo2 "-- ERROR" && return 1; fi +} + +dynamic_install_apk() { + local userlist package f try inst_dir package2 count=0 + local temp_dir temp_dest temp_pack check_split check_package out nr na path1 path2 + local ro ra include i file check_overlay params overlays=false splits=false + if ! aapt version >/dev/null; then echo2 " CANT LOAD AAPT: U cant use dynamic_install_apk " && return 1; fi + while [ $# -gt 0 ]; do + case $1 in + -o|-output) + out="$2" + shift 2 + ;; + -nr|-no-replace) + nr=true + shift + ;; + -na|-no-add) + na=true + shift + ;; + -fs|-follow-symlinks) + params=-L + shift + ;; + -ro|-remove-oat) + ro=true + shift + ;; + -io|-include-overlays) + overlays=true + shift + ;; + -is|-include-splits) + splits=true + shift + ;; + -i|-include) + include+=("$2") + shift 2 + ;; + *) + break + ;; + esac + done + rm -f "$TMP/userlist.txt" "$TMP/packages.txt" + path1="$1"; path2="$2" + [ -L "$path1" ] && path1=$(readlink -f "$path1") + [ -L "$path2" ] && path2=$(readlink -f "$path2") + if [ -z "$out" ]; then testrw "$path2" || return 1; fi + while read userlist; do + $overlays || check_overlay=$(aapt dump xmlstrings "$userlist" AndroidManifest.xml 2>/dev/null | grep -w " overlay") + [ -z "$check_overlay" ] && package=$(apk_package "$userlist") || continue + [ -n "$package" ] && { + ((count++)) + echo "$count=$userlist" >> "$TMP/userlist.txt" + echo "$count=$package" >> "$TMP/packages.txt" + } + done < <(find $params "$path2" -type f -name "*.apk" 2>/dev/null) + [ $count == 0 ] && { echo2 "dynamic_install_apk: Cant find any apk in $path2 "; } + if ! exist "$TMP/packages.txt" "$TMP/userlist.txt"; then + #Make false files + echo > "$TMP/packages.txt" + echo > "$TMP/userlist.txt" + fi + count=0 + while read f; do + $splits || check_split=$(aapt dump badging "$f" 2>/dev/null | sed -n "s/.* split='\([^']*\).*$/\1/p") + if [ -n "$check_split" -a -n "$temp_pack" -a -n "$temp_dir" ] && [ "$temp_dir" = "$(dirname "$f")" ]; then + check_package=$(apk_package "$f") + if [ "$check_package" = "$temp_pack" ]; then + echo2 "add:split: $(basename "$f") in $temp_dest" + inject "$f" "$temp_dest" && continue + fi + fi + inst_dir="$(dirname "$f")" + inst_dir="${inst_dir#$path1}" + package=$(apk_package "$f") + try=$(grep -m1 "$package" "$TMP/packages.txt" | cut -d '=' -f1) + package2=$(get_file_prop "$TMP/packages.txt" "$try") + [ "$package" == "$package2" ] && try=$(get_file_prop "$TMP/userlist.txt" "$try") || try= + if [ -n "$try" -a -z "$check_split" ]; then + [ -n "$nr" ] && continue + [ -n "$out" ] && try="$out$try" + temp_pack="$package" + temp_dir=$(dirname "$f") + temp_dest=$(dirname "$try") + [ -n "$ro" ] && rm -rf "$temp_dest/oat" 2>/dev/null + [ ! -d "$temp_dest" ] && mkdir -p "$temp_dest" + echo2 "replace: $package in $try" + cp -pf "$f" "$try" + [ -d "$temp_dir/lib" ] && cp -pfr "$temp_dir/lib" "$temp_dest" + for i in "${!include[@]}"; do + if [ -e "$temp_dir/${include[i]}" ]; then + echo2 "add:extra: ${include[i]}" + cp -pfr "$temp_dir/${include[i]}" "$temp_dest" + fi + done + set_perm_recursive2 $___uid $___gid $___mod $___mof "$temp_dest" + count=1 + elif [ -z "$check_split" ]; then + [ -n "$na" ] && continue + temp_pack="$package" + temp_dir=$(dirname "$f") + temp_dest="$path2${inst_dir}" + [ -n "$out" ] && temp_dest="$out$temp_dest" + [ -n "$ro" ] && rm -rf "$temp_dest/oat" 2>/dev/null + echo2 "add: $package in $temp_dest" + inject "$f" "$temp_dest" + [ -d "$temp_dir/lib" ] && cp -pfr "$temp_dir/lib" "$temp_dest" + for i in "${!include[@]}"; do + if [ -e "$temp_dir/${include[i]}" ]; then + echo2 "add:extra: ${include[i]}" + cp -pfr "$temp_dir/${include[i]}" "$temp_dest" + fi + done + set_perm_recursive2 $___uid $___gid $___mod $___mof "$temp_dest" + count=1 + fi + done < <(find $params "$path1" -mindepth 1 -type f -name "*.apk") + [ $count == 0 ] && { echo2 "dynamic_install_apk: Cant find any apk in $path1 "; return 1 ; } || return 0 +} + +find_xxd() { + local xxd + if command -v xxd >/dev/null 2>&1; then + echo "xxd" + elif /system/bin/xxd --help >/dev/null 2>&1; then + echo "/system/bin/xxd" + elif /system/bin/toybox xxd --help >/dev/null 2>&1; then + echo "/system/bin/toybox xxd" + else + ensure_bin xxd && echo "xxd" || return 1 + fi +} + +hex_patch() { + local xxd magiskboot=$(find_bin magiskboot) + xxd=$(find_xxd) || { echo "CANT LOAD: xxd or magiskboot" && return 1; } + [ ! -f "$3" ] && { echo "CANT FIND: $3" && return 1; } + testrw "$(dirname "$3")" || return 1 + if [ -n "$magiskboot" ]; then + if "$magiskboot" hexpatch "$3" "$1" "$2"; then + set_perm2 $___uid $___gid $___mof "$3" + echo2 "hex_patch: Patched: $3" + return 0 + fi + fi + if $xxd -p "$3" | tr -d '\n ' | sed "s/$1/$2/" | $xxd -r -p > "$3.tmp" 2>/dev/null && is_valid "$3.tmp"; then + mv -f "$3.tmp" "$3" + set_perm2 $___uid $___gid $___mof "$3" + echo2 "hex_patch: Patched: $3" + return 0 + elif [ -f "$3.tmp" ]; then + rm -f "$3.tmp" + fi + echo2 "hex_patch: Failed: $3" + return 1 +} + +hex_search() { + local xxd after=0 before=0 + xxd=$(find_xxd) || { echo "CANT LOAD: xxd" && return 1; } + while [ $# -gt 0 ]; do + case "$1" in + -include) + after=$(echo "$2" | grep -Eio "after:[ \t]*[0-9]+" | cut -f2 -d: | tr -d ' \t') + before=$(echo "$2" | grep -Eio "before:[ \t]*[0-9]+" | cut -f2 -d: | tr -d ' \t') + [ -z "$after" ] && after=0 + [ -z "$before" ] && before=0 + shift 2 + ;; + *) break ;; + esac + done + [ ! -f "$2" ] && { echo "CANT FIND: $2" && return 1; } + $xxd -p "$2" | tr -d '\n ' | grep -E -o ".{0,${before}}$1.{0,${after}}" +} + +hex_check() { + local xxd + xxd=$(find_xxd) || { echo "CANT LOAD: xxd" && return 1; } + [ ! -f "$2" ] && { echo "CANT FIND: $2" && return 1; } + $xxd -p "$2" | tr -d '\n ' | grep -q "$1" +} + +getvalue() { + local try + try=$1 + shift 1 + TEMP=`getopt --long -o "$try:" "$@"` + eval set -- "$TEMP" + while true ; do + case "$1" in + -$try ) + echo "$2" + shift 2 + ;; + *) + break + ;; + esac + done +} + +get_equal_value() { + local equal i + equal="$1" + shift 1 + for i in "$@" + do + case $i in + $equal=*) + echo "${i#*=}" + shift + ;; + *) + ;; + esac + done +} + +get_custom_value() { + local value key + value="$1" + shift 1 + while [[ $# -gt 0 ]] + do + key="$1" + case $key in + $value) + echo "$2" + shift 2 + ;; + *) + shift + ;; + esac + done +} + +start_tmp() { + TMP2="${1:-$TMP}/$(random)" + ___TMP2+=("$TMP2") + rm -rf "$TMP2" + mkdir -p "$TMP2" +} + +end_tmp() { + local d i=$(( ${#___TMP2[@]} - 1 )) + [ $i -ge 0 ] && { + TMP2= + d="${___TMP2[i]}" + unset "___TMP2[i]" + ___TMP2=("${___TMP2[@]}") + rm -rf "$d" && return 0 + } + return 1 +} + +is_readable() { + [ ! -f "$1" -o ! -r "$1" ] && return 1 + awk -v m="${3:-4}" ' + BEGIN { + v = 0; ma = "20"; mx = "7e"; + ws["09"] = 1; # \t + ws["0a"] = 1; # \n + ws["0d"] = 1; # \r + } + { + l = length($0); + if (l % 2 != 0) { + exit 1; + } + for (i = 1; i <= l; i += 2) { + b = substr($0, i, 2); + if (b in ws || (b >= ma && b <= mx)) { + v++ + } + } + } + END { + exit (v >= m)?0:1 + } + ' <<< $(xxd -p -l "${2:-32}" "$1") +} + +find_readable() { + local r=() p="-type f" n m + while [ $# -gt 0 ]; do + case $1 in + -bytes|-b) + n="$2" + shift 2 + ;; + -min-bytes|-mb) + m="$2" + shift 2 + ;; + -no-params) + p= + shift + ;; + -type) + r+=("$1" "$2") + p= + shift 2 + ;; + *) + r+=("$1") + shift + ;; + esac + done + find "${r[@]}" $p | awk -v n="${n:-32}" -v m="${m:-4}" ' + BEGIN { + ma = "20"; mx = "7e"; + ws["09"] = 1; # \t + ws["0a"] = 1; # \n + ws["0d"] = 1; # \r + c = "xxd -p -l " n " \""; + e = "\" 2>/dev/null"; + ec = 1; + } + { + f = $0; + v = 0; + cmd = c f e + while ((cmd | getline b) > 0) { + l = length(b); + if (l % 2 != 0) { + next; + } + for (i = 1; i <= l; i += 2) { + by = substr(b, i, 2); + if (by in ws || (by >= ma && by <= mx)) { + v++ + } + } + if (v >= m) { + print f; + ec = 0; + break + } + } + close(cmd); + } + END { + exit ec + } + ' +} + +replace() { + local r a=false file dir load return=1 i=1 j limit=0 total p params perm num_bytes minimum opt=" -r " as_find check=true + while [ $# -gt 0 ]; do + case $1 in + -r|-recursive) + r=true + shift + ;; + -a|-all-line) + a=true + shift + ;; + -fs|-follow-symlinks) + params+="-L" + shift + ;; + -bytes|-b) + [ $2 -le 0 ] && check=false || { num_bytes="$2"; params+=" -bytes $2 " ; } + shift 2 + ;; + -min-bytes|-mb) + [ $2 -le 0 ] && check=false || { minimum="$2"; params+=" -mb $2 " ; } + shift 2 + ;; + -only-first) + opt= + shift + ;; + -no-check) + check=false + shift + ;; + *) + break + ;; + esac + done + total=$(($#-1)) + if (( total % 2 != 0 )); then echo2 "replace: Each search must have its respective replacement" && return 1; fi + file=${!#} + if [ ! -e "$file" ]; then echo2 "CANT FIND: $file" && return 1; fi + [ -L "$file" ] && file=$(readlink -f "$file") + while [ $i -lt $total ]; do + p+=(-e "${!i}") + ((i+=2)) + done + $check && as_find=find_readable || as_find=find + while read dir; do + grep -Fq "${p[@]}" "$dir" || continue + testrw "$(dirname "$dir")" || return 1 + i=1; j=2; perm=false + while [ $i -lt $total ]; do + if $a; then + load=$(string -f "$dir" $opt replace_line "${!i}" "${!j}") + else + load=$(string -f "$dir" $opt replace "${!i}" "${!j}") + fi + if [ $? = 0 ]; then + echo "$load" > "$dir" + perm=true + return=0 + else echo2 "replace: Nothing to do: $dir" + fi + ((i+=2)) + ((j+=2)) + done + $perm && set_perm2 $___uid $___gid $___mof "$dir" + done < <(if [ -n "$r" ]; then $as_find $params "$file" || echo2 "replace: No readable files found: $file"; else { ! $check || is_readable "$file" $num_bytes $minimum ; } && echo "$file" || echo2 "replace: $file isnt readable"; fi ; ) + return $return +} + +remove() { + local r a=false file dir load return=1 i=1 limit=0 total p params perm num_bytes minimum opt=" -r " as_find check=true + while [ $# -gt 0 ]; do + case $1 in + -r|-recursive) + r=true + shift + ;; + -a|-all-line) + a=true + shift + ;; + -fs|-follow-symlinks) + params+="-L" + shift + ;; + -bytes|-b) + [ $2 -le 0 ] && check=false || { num_bytes="$2"; params+=" -bytes $2 " ; } + shift 2 + ;; + -min-bytes|-mb) + [ $2 -le 0 ] && check=false || { minimum="$2"; params+=" -mb $2 " ; } + shift 2 + ;; + -only-first) + opt= + shift + ;; + -no-check) + check=false + shift + ;; + *) + break + ;; + esac + done + total=$(($#-1)) + if [ $total -lt 1 ]; then echo2 "remove: At least one search was expected" && return 1; fi + file=${!#} + if [ ! -e "$file" ]; then echo2 "CANT FIND: $file" && return 1; fi + [ -L "$file" ] && file=$(readlink -f "$file") + while [ $i -le $total ]; do + p+=(-e "${!i}") + ((i++)) + done + $check && as_find=find_readable || as_find=find + while read dir; do + grep -Fq "${p[@]}" "$dir" || continue + testrw "$(dirname "$dir")" || return 1 + i=1; perm=false + while [ $i -le $total ]; do + if $a; then + load=$(string -f "$dir" $opt remove_line "${!i}") + else + load=$(string -f "$dir" $opt remove "${!i}") + fi + if [ $? = 0 ]; then + echo "$load" > "$dir" + perm=true + return=0 + else echo2 "remove: Nothing to do: $dir" + fi + ((i++)) + done + $perm && set_perm2 $___uid $___gid $___mof "$dir" + done < <(if [ -n "$r" ]; then $as_find $params "$file" || echo2 "remove: No readable files found: $file"; else { ! $check || is_readable "$file" $num_bytes $minimum ; } && echo "$file" || echo2 "remove: $file isnt readable"; fi ; ) + return $return +} + +string() { + #Multi-tool for Strings by @BlassGO + local str str2 i to file r p md5 upper=false lower=false escape=false escapereg=false c=false force=false + [ $# -lt 2 ] && return 1 + str=${!#} + to=$(($#-1)) + for ((i=1; i<=to; i++)); do + case ${!i} in + -f|-file) ((i++)); file=${!i}; [ -f "$file" ] && { str=$(<"$file") ; } || { echo2 "string: Cant find file: $file"; return 1 ; } + ;; + -r|-recursive) r=true; + ;; + -p|-pattern) ((i++)); p=${!i} + ;; + esac + done + [ ${#str} -gt 1000 ] && md5=$(md5sum <<< "$str") || str2="$str" + [ -n "$file" ] && to=0 || to=1 + while [ $# -gt $to ]; do + case $1 in + replace) + [ -n "$r" ] && str=${str//"$2"/"$3"} || str=${str/"$2"/"$3"} + shift 3 + ;; + replace_line) + str=$(awk -v try="$2" -v repl="$3" -v r="$r" ' + BEGIN { replaced = 0; empty = (try == "") } + { + if (!r && replaced) { + print $0 + } else if (empty) { + if ($0 == "") { + print repl + replaced = 1 + } else { + print $0 + } + } else if (index($0, try)) { + print repl + replaced = 1 + } else { + print $0 + } + }' <<< "$str") + shift 3 + ;; + remove) + [ -n "$r" ] && str=${str//"$2"} || str=${str/"$2"} + shift 2 + ;; + remove_line) + str=$(awk -v try="$2" -v r="$r" ' + BEGIN { removed = 0; empty = (try == "") } + { + if (!r && removed) { + print $0 + } else if (empty) { + if ($0 == "") { + removed = 1 + } else { + print $0 + } + } else if (index($0, try)) { + removed = 1 + } else { + print $0 + } + }' <<< "$str") + shift 2 + ;; + inside_symbol) + str=$(awk -F "$2|$3" '{print $2}' <<< "$str") + shift 3 + ;; + inside) + str=${str#*"$2"} + str=${str%"$3"*} + shift 3 + ;; + extract) + str=$(awk -v start="$2" -v end="$3" -v r="$r" -v p="$p" ' + index($0, start) { + found=1; + section=""; + next; + } + found { + if (index($0, end)) { + found=0; + if (!p || index(section, p)) { print section } + if (!r) { exit } + } else { + section = (section ? section ORS : "") $0; + } + } + ' <<< "$str") + shift 3 + ;; + complete_extract) + str=$(awk -v start="$2" -v end="$3" -v r="$r" -v p="$p" ' + index($0, start) { + found=1; + section=""; + } + found { + section = (section ? section ORS : "") $0; + if (index($0, end)) { + found=0; + if (!p || index(section, p)) { print section } + if (!r) { exit } + } + } + ' <<< "$str") + shift 3 + ;; + -al|-after-line) + str=$(awk -v pattern="$2" -v after="$3" -v r="$r" ' + found == 1 { print $0; next } + { + if (index($0, pattern)) { + print $0; + print after; + found = r ? 0 : 1; + } else { + print $0; + } + } + ' <<< "$str") + shift 3 + ;; + -bl|-before-line) + str=$(awk -v pattern="$2" -v before="$3" -v r="$r" ' + found == 1 { print $0; next } + { + if (index($0, pattern)) { + print before; + print $0; + found = r ? 0 : 1; + } else { + print $0; + } + } + ' <<< "$str") + shift 3 + ;; + -aln|-after-line-number) + str=$(awk -v linenum="$2" -v after="$3" -v r="$r" ' + found == 1 { print $0; next } + { + if (NR == linenum) { + print $0; + print after; + found = r ? 0 : 1; + } else { + print $0; + } + } + ' <<< "$str") + shift 3 + ;; + -bln|-before-line-number) + str=$(awk -v linenum="$2" -v before="$3" -v r="$r" ' + found == 1 { print $0; next } + { + if (NR == linenum) { + print before; + print $0; + found = r ? 0 : 1; + } else { + print $0; + } + } + ' <<< "$str") + shift 3 + ;; + -ga|-get-after) + str=${str#*"$2"} + shift 2 + ;; + -gb|-get-before) + str=${str%"$2"*} + shift 2 + ;; + -r|-recursive) shift + ;; + -p|-pattern) shift 2 + ;; + -f|-file) shift 2 + ;; + upper) upper=true; shift + ;; + lower) lower=true; shift + ;; + escape) escape=true; shift + ;; + escape_regex) escapereg=true; shift + ;; + count) c=true; shift + ;; + force) force=true; shift + ;; + *) + echo2 "string: Invalid argument: $1" + return 1 + ;; + esac + done + $upper && str=${str^^} + $lower && str=${str,,} + $escape && str=$(sed -e 's/[]\/$*.^[]/\\&/g' <<< "$str") + $escapereg && str=$(sed -e 's/[]\/.^$*+?{}()|[]/\\&/g' <<< "$str") + $c && printf "%d\n" ${#str} || { $force || { if [ -n "$md5" ]; then [ "$md5" == "$(md5sum <<< "$str")" ] && return 1; elif [ "$str" == "$str2" ]; then return 1; fi ; } ; printf "%s\n" "$str" ; } +} + +chooseport() { + while true; do + sleep 0.2 + keycheck + case $? in + 42) return 0 ;; + 41) return 1 ;; + *) abort "ERROR: keycheck terminated abnormally" ;; + esac + done +} + +iterate() { + [ -z "$INDEX" ] && { + case $# in + 1) + INDEX=0 + is_number "$1" && ___END=$1 || { echo2 "loop: $1 isnt a number";INDEX=;return 1 ; } + ;; + 2) + is_number "$1" && INDEX=$(($1-1)) || { echo2 "loop: $1 isnt a number";INDEX=;return 1 ; } + is_number "$2" && ___END=$2 || { echo2 "loop: $2 isnt a number";INDEX=;return 1 ; } + ;; + *) + echo2 "Usage: loop OR " + return 1 + ;; + esac + } + [ $INDEX -lt $___END ] && { ((INDEX++));return 0 ; } || { INDEX=;___END=;return 1 ; } +} + +iterate_file() { + [ -z "$INDEX" ] && { + if [ -f "$1" ]; then + exec 3< "$1" + elif [ -z "$1" ]; then + echo2 "Usage: loop_file " + return 1 + else + echo2 "loop_file: Cant find file: $1" + return 1 + fi + INDEX=0 + } + read -u 3 LINE && { ((INDEX++));return 0 ; } || { INDEX=;return 1 ; } +} + +iterate_array() { + [ -z "$INDEX" ] && { + if [ -z "$1" ]; then + echo2 "Usage: loop_array " + return 1 + else + mapfile -t ___KEYS < <(eval "printf '%s\n' \"\${!$1[@]}\" | sort") + ___MAX=${#___KEYS[@]} + INDEX=0 + fi + } + [ $INDEX -lt $___MAX ] && { + KEY=${___KEYS[$INDEX]} + eval "VALUE=\${$1[$KEY]}" + ((INDEX++)) + return 0 + } || { + INDEX=;KEY=;VALUE= + ___KEYS=;___MAX= + return 1 + } +} + +iterate_string() { + [ -z "$INDEX" ] && { + if [ -z "$1" ]; then + ___MAX=${#2} + ___ISCHAR=true + else + IFS="$1" read -ra ___SECTIONS <<< "$2" + ___MAX=${#___SECTIONS[@]} + ___ISCHAR=false + fi + INDEX=0 + } + [ $INDEX -lt $___MAX ] && { + $___ISCHAR && SECTION=${2:INDEX:1} || SECTION=${___SECTIONS[$INDEX]} + ((INDEX++)) + return 0 + } || { + INDEX=;SECTION= + ___MAX=;___SECTIONS=;___ISCHAR= + return 1 + } +} + +find_block() { + local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT express=false t=5 + while [ $# -gt 0 ]; do + case $1 in + -e|-express) + express=true + shift + ;; + -t|-time) + is_number "$2" && t=$2 || { echo2 "find_block: Invalid time: $2"; return 1 ; } + shift 2 + ;; + *) + break + ;; + esac + done + for BLOCK in "$@"; do + DEVICE=$(timeout -k $t $t find /dev/block \( -type b -o -type c -o -type l \) \( -iname $BLOCK -o -iname $BLOCK$slot \) -print -quit) 2>/dev/null + [ -n "$DEVICE" ] && readlink -f $DEVICE && return 0 + done + $express || { + for UEVENT in /sys/dev/block/*/uevent; do + DEVNAME=$(grep_prop DEVNAME $UEVENT) + PARTNAME=$(grep_prop PARTNAME $UEVENT) + PARTNAME=${PARTNAME^^} + for BLOCK in "$@"; do + [ "${BLOCK^^}" == "$PARTNAME" -o "${BLOCK^^}${slot^^}" == "$PARTNAME" ] && echo /dev/block/$DEVNAME && return 0 + done + done + } + for DEV in "$@"; do + DEVICE=$(timeout -k $t $t find /dev -maxdepth 1 \( -type b -o -type c -o -type l \) \( -iname $DEV -o -iname $DEV$slot \) -print -quit) 2>/dev/null + [ -n "$DEVICE" ] && readlink -f $DEVICE && return 0 + done + return 1 +} + +grep_cmdline() { + local ___ + for ___ in $(< /proc/cmdline); do + case $___ in + "$1="*) + echo "${___#*=}" + return 0 + ;; + esac + done + return 1 +} + +grep_prop() { + local REGEX="s/^$1=//p" + shift + local FILES=$@ + [ -z "$FILES" ] && FILES='/system/build.prop' + cat $FILES | dos2unix | sed -n "$REGEX" 2>/dev/null | head -n 1 +} + +try_mount() { + local opt try basetry part result + local rw ro re name file LOOP express looped premounted + while [ $# -gt 0 ]; do + case $1 in + -rw|-read-write) rw=true; shift ;; + -ro|-read-only) ro=true; shift ;; + -re|-remount) re=true; shift ;; + -e|-express) express=true; shift ;; + -n|-name) name="$2"; shift 2 ;; + -f|-file) file="$2"; shift 2 ;; + *) break ;; + esac + done + # Ensure root space + mount -o rw,remount -t auto / &>/dev/null + [ -z "$ro" ] && [ -z "$rw" ] && rw=true && ro=true + for try in "$@"; do + result=1 + looped=false + premounted=false + part= + basetry=${name:-$(split_extract / 1 "$try")} + if [[ -z "$file" && -z "$express" ]]; then + [ -n "$basetry" ] && part=$(find_block "$basetry") + elif [[ -z "$file" && -n "$express" ]]; then + [ -n "$basetry" ] && part=$(find_block -e "$basetry") + else + basetry="No needed" + if [ ! -e "$file" ]; then + echo2 " CANT FIND: $file" && return 1 + elif [[ -b "$file" && -z "$name" ]]; then + part="$file" + elif can_run losetup2; then + if [ -n "$name" ]; then + start_loop "$name" "$file" &>/dev/null + part="$LOOP" + else + part=$(losetup2 -Pf --show "$file" 2>/dev/null) + fi + [ -z "$part" ] && echo2 " Cant loop: $file" && return 1 + looped=true + else + echo2 "Unsupported: losetup2" && return 1 + fi + fi + + [ -n "$part" ] && blockdev --setrw "$part" &>/dev/null + + setup_mountpoint "$try" + [ -n "$re" ] && { [[ -n "$name" && -d "/$name" ]] && unmount "/$name"; unmount "$try"; } + + if ! is_mounted "$try" || [ -n "$file" ]; then + if [ -n "$rw" ]; then + for opt in \ + "-w" \ + "-w $part" \ + "-w -t auto $part" \ + "-w -t ext4 $part" \ + "-o rw,remount -t auto" \ + "-o rw,remount -t auto $part"; do + mount $opt "$try" &>/dev/null && result=0 && break + done + [ $result -eq 0 ] && echo2 " Mounting RW: $part -> $try" + fi + if [ $result -ne 0 ] && [ -n "$ro" ]; then + for opt in \ + "-r" \ + "-r $part"; do + mount $opt "$try" &>/dev/null && result=0 && break + done + [ $result -eq 0 ] && echo2 " Mounting RO: $part -> $try" + fi + else + premounted=true + if [ -n "$rw" ]; then + for opt in \ + "-o rw,remount -t auto" \ + "-o rw,remount -t auto $part"; do + mount $opt "$try" &>/dev/null && result=0 && break + done + [ $result -eq 0 ] && echo2 " Remounting RW: $part -> $try" + fi + if [ $result -ne 0 ] && [ -n "$ro" ]; then + for opt in \ + "-o ro,remount -t auto" \ + "-o ro,remount -t auto $part"; do + mount $opt "$try" &>/dev/null && result=0 && break + done + [ $result -eq 0 ] && echo2 " Remounting RO: $part -> $try" + fi + fi + + if [ -n "$part" ]; then + contains_array "$part" "${___all_mounted_part[@]}" || ___all_mounted_part+=("$part") + fi + + if $looped || ! $premounted; then + contains_array "$try" "${___all_looped[@]}" || ___all_looped+=("$try") + else + contains_array "$try" "${___all_umount[@]}" || ___all_umount+=("$try") + fi + + if [ $result -ne 0 ]; then + is_mounted "$try" && ! testrw "$try" &>/dev/null && { echo2 " LOCKED: \"$try\" is not writable"; result=2; } || { echo2 " CANT MOUNT: $try" ; } + $looped && losetup -d "$part" + fi + done + + return $result +} + +run_wait() { + local pid count time="$1" + if ! is_number "$time"; then echo "run_wait: Invalid Line" && return 1; fi + if exist file "$2"; then is_exec "$source" || chmod +x "$2"; fi + shift >/dev/null 2>&1 + $@ & pid=$! count=0 + while kill -0 $pid >/dev/null 2>&1 ; do + read -t 1 >/dev/null 2>&1 + count=$(( $count + 1 )) + if [ $count -ge $time ]; then + kill $pid >/dev/null 2>&1 && break + fi + done + wait $pid + return $? +} + +run_jar() { + local dalvikvm file main + #Inspired in the osm0sis method + [ -z "$dalvik_logging" ] && local dalvik_logging=false + [ -z "$dalvik_memory" ] && local dalvik_memory=800m + if /system/bin/dalvikvm -showversion >/dev/null 2>&1; then + dalvikvm=/system/bin/dalvikvm + elif dalvikvm -showversion >/dev/null 2>&1; then + dalvikvm=dalvikvm + else + [ -z "$ANDROID_ART_ROOT" ] && ANDROID_ART_ROOT=$(find /apex -type d -name "com.android.art*" 2>/dev/null | head -n1) + if [ -n "$ANDROID_ART_ROOT" ]; then + dalvikvm=$(readlink -f "$(find "$ANDROID_ART_ROOT" \( -type f -o -type l \) -name "dalvikvm")") + if [ -z "$dalvikvm" ]; then if $is64bit; then dalvikvm=$(find "$ANDROID_ART_ROOT" \( -type f -o -type l \) -name "dalvikvm64"); else dalvikvm=$(find "$ANDROID_ART_ROOT" \( -type f -o -type l \) -name "dalvikvm32"); fi; fi + fi + if ! $dalvikvm -showversion >/dev/null 2>&1; then + echo2 "--------DALVIKVM LOGGING--------" + if [ -f "$(readlink -f "$dalvikvm")" ]; then + echo2 "$($dalvikvm -Xuse-stderr-logger -verbose:class,collector,compiler,deopt,gc,heap,interpreter,jdwp,jit,jni,monitor,oat,profiler,signals,simulator,startup,threads,verifier,verifier-debug,image,systrace-locks,plugin,agents,dex -showversion 2>&1)" + else + echo2 "Unable to find dalvikvm!" + [ -d /apex ] && echo2 "$(find /apex -type f -name "dalvikvm*")" + fi + echo2 "--------------------------------" + echo2 "CANT LOAD DALVIKVM " && return 1 + fi + fi + file="$1" + if [ ! -f "$file" ]; then echo2 "CANT FIND: $file" && return 1; fi + main=$(unzip -qp "$file" "META-INF/MANIFEST.MF" 2>/dev/null | grep -m1 "^Main-Class:" | cut -f2 -d: | tr -d " " | dos2unix) + if [ -z "$main" ]; then + echo2 "Cant get main: $file " && return 1 + fi + shift 1; + if ! $dalvikvm -Xmx${dalvik_memory,,} -Djava.io.tmpdir=. -Xnodex2oat -cp "$file" $main "$@" 2>/dev/null; then if $dalvik_logging; then $dalvikvm -Xmx${dalvik_memory,,} -Xuse-stderr-logger -Djava.io.tmpdir=. -Xnoimage-dex2oat -cp "$file" $main "$@"; else $dalvikvm -Xmx${dalvik_memory,,} -Djava.io.tmpdir=. -Xnoimage-dex2oat -cp "$file" $main "$@"; fi; fi +} + +run_jar_addon() { + local file + file="$1" + shift 1 + run_jar "$addons/$file" "$@" +} + +run_jar_zip() { + local file result + file="$1" + shift 1 + package_extract_file "$file" "$TMP/$(basename "$file")" || return 1 + run_jar "$TMP/$(basename "$file")" "$@" + result=$? + rm -f "$TMP/$(basename "$file")" + return $result +} + +run_jar_class() { + local dalvikvm file main + #Inspired in the osm0sis method + [ -z "$dalvik_logging" ] && local dalvik_logging=false + [ -z "$dalvik_memory" ] && local dalvik_memory=800m + if /system/bin/dalvikvm -showversion >/dev/null 2>&1; then + dalvikvm=/system/bin/dalvikvm + elif dalvikvm -showversion >/dev/null 2>&1; then + dalvikvm=dalvikvm + else + [ -z "$ANDROID_ART_ROOT" ] && ANDROID_ART_ROOT=$(find /apex -type d -name "com.android.art*" 2>/dev/null | head -n1) + if [ -n "$ANDROID_ART_ROOT" ]; then + dalvikvm=$(readlink -f "$(find "$ANDROID_ART_ROOT" \( -type f -o -type l \) -name "dalvikvm")") + if [ -z "$dalvikvm" ]; then if $is64bit; then dalvikvm=$(find "$ANDROID_ART_ROOT" \( -type f -o -type l \) -name "dalvikvm64"); else dalvikvm=$(find "$ANDROID_ART_ROOT" \( -type f -o -type l \) -name "dalvikvm32"); fi; fi + fi + if ! $dalvikvm -showversion >/dev/null 2>&1; then + echo2 "--------DALVIKVM LOGGING--------" + if [ -f "$(readlink -f "$dalvikvm")" ]; then + echo2 "$($dalvikvm -Xuse-stderr-logger -verbose:class,collector,compiler,deopt,gc,heap,interpreter,jdwp,jit,jni,monitor,oat,profiler,signals,simulator,startup,threads,verifier,verifier-debug,image,systrace-locks,plugin,agents,dex -showversion 2>&1)" + else + echo2 "Unable to find dalvikvm!" + [ -d /apex ] && echo2 "$(find /apex -type f -name "dalvikvm*")" + fi + echo2 "--------------------------------" + echo2 "CANT LOAD DALVIKVM " && return 1 + fi + fi + file="$1" + main="$2" + if [ ! -f "$file" ]; then echo2 "CANT FIND: $file" && return 1; fi + if [ -z "$main" ]; then + echo2 "Class undefined: $file " && return 1 + fi + shift 2; + if ! $dalvikvm -Xmx${dalvik_memory,,} -Djava.io.tmpdir=. -Xnodex2oat -cp "$file" $main "$@" 2>/dev/null; then if $dalvik_logging; then $dalvikvm -Xmx${dalvik_memory,,} -Xuse-stderr-logger -Djava.io.tmpdir=. -Xnoimage-dex2oat -cp "$file" $main "$@"; else $dalvikvm -Xmx${dalvik_memory,,} -Djava.io.tmpdir=. -Xnoimage-dex2oat -cp "$file" $main "$@"; fi; fi +} + +run_jar_class_addon() { + local file + file="$1" + shift 1 + run_jar_class "$addons/$file" "$@" +} + +run_jar_class_zip() { + local file result + file="$1" + shift 1 + package_extract_file "$file" "$TMP/$(basename "$file")" || return 1 + run_jar_class "$TMP/$(basename "$file")" "$@" + result=$? + rm -f "$TMP/$(basename "$file")" + return $result +} + +apktool() { + local tmp="$TMP/zbin/apktool" + [ -z "$apktool_aapt" ] && local apktool_aapt=off + [ ! -e "$l/apktool.jar" ] && { echo2 "apktool: Cant find apktool.jar"; return 1 ; } + [ ! -f "$tmp/1.apk" -a -n "$framework_res" ] && { + create_dir "$tmp" || return 1 + split_string : "$framework_res" | while read fw; do + if [ -f "$fw" ]; then + run_jar "$l/apktool.jar" if -p "$tmp" "$fw" 1>&2 + else + echo2 "apktool: Cant find framework: $fw" + fi + done + } + if [ "$apktool_aapt" == "off" ]; then + run_jar "$l/apktool.jar" --aapt "$l/aapt" -p "$tmp" "$@" + else + run_jar "$l/apktool.jar" -p "$tmp" "$@" + fi +} + +sign() { + [ ! -e "$l/zipsigner.jar" ] && { echo2 "sign: Cant find zipsigner.jar"; return 1 ; } + run_jar "$l/zipsigner.jar" "$@" +} + +decode_xml() { + local result="$TMP/$(random).xml" return=0 + [ ! -e "$l/axml.jar" ] && { echo2 "decode_xml: Cant find axml.jar"; return 1 ; } + if ! is_substring "ArrayIndexOutOfBoundsException" "$(run_jar "$l/axml.jar" -d "$1" "$result")" && is_valid "$result"; then + if [ -n "$2" ]; then copy "$result" "$2" || return=1 + else copy "$result" "$1" || return=1 + fi + else return=1 + fi + rm -f "$result" + return $return +} + +encode_xml() { + local result="$TMP/$(random).xml" return=0 + [ ! -e "$l/axml.jar" ] && { echo2 "encode_xml: Cant find axml.jar"; return 1 ; } + if ! is_substring "ArrayIndexOutOfBoundsException" "$(run_jar "$l/axml.jar" -e "$1" "$result")" && is_valid "$result"; then + if [ -n "$2" ]; then copy "$result" "$2" || return=1 + else copy "$result" "$1" || return=1 + fi + else return=1 + fi + rm -f "$result" + return $return +} + +xml_kit() { + [ $# -lt 2 ] && return 1 + local TMP2 xml="${!#}" opts tmp result + [ ! -f "$xml" ] && { echo2 "xml_kit: Cant find: $xml"; return 1 ; } + opts=$(printf "%s\001" "${@:1:$#-1}") + start_tmp + tmp="$TMP2/$(basename "$xml")" + + awk -v opts="$opts" ' + BEGIN { + FS = "\001" + ORS = "" + sq = sprintf("%c", 39) + nopts = split(opts, par, FS) + num_open = 0 + changed = 0 + blockFound = 0 + get_value_flag = 0 + print_flag = 0 + print_result_flag = 0 + indent_string = " " + previous_pos = -1 + no_auto_spaces_flag = 0 + tag_buffer = "" + in_tag = 0 + + for(i = 1; i <= nopts; ) { + if(par[i] == "-open" || par[i] == "-o") { + if(i+2 > nopts) { + print "xml_kit: Error: Invalid -open parameters" > "/dev/stderr" + exit 1 + } + num_open++ + open_pat = par[i+1] + gsub(/^[[:space:]]+|[[:space:]]+$/, "", open_pat) + if(open_pat ~ /^<.*>$/) { + tag = substr(open_pat, 2, length(open_pat)-2) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", tag) + open_regex[num_open] = "<" tag "([[:space:]]+[^>]+)?[[:space:]]*" + } else { + if (match(open_pat, /^[^[:space:]]+/)) { + tag = substr(open_pat, RSTART, RLENGTH) + open_regex[num_open] = "<" tag "([[:space:]]+[^>]+|[[:space:]]*\n[[:space:]]*[[:alnum:]][^>]+)?[[:space:]]*" + attr_str = substr(open_pat, RSTART + RLENGTH) + attr_count[num_open] = 0 + while (match(attr_str, /[[:space:]]*\n?[[:space:]]*[^[:space:]]+=?/)) { + attr = substr(attr_str, RSTART + 1, RLENGTH - 1) + gsub(/^\n?[[:space:]]+|\n?[[:space:]]+$/, "", attr) + attr_str = substr(attr_str, RSTART + RLENGTH) + attr_count[num_open]++ + idx = attr_count[num_open] + if (attr ~ /=$/) { + attr_name = substr(attr, 1, length(attr)-1) + if (match(attr_str, /^"/)) { + if (match(attr_str, /^"[^"]*"/)) { + attr_value = substr(attr_str, 2, RLENGTH-2) + attr_str = substr(attr_str, RSTART + RLENGTH) + attr_regex[num_open, idx] = "[[:space:]]*\n?[[:space:]]*" attr_name "=\"" attr_value "\"" + } else { + print "xml_kit: Invalid double-quoted attribute value in -open: " attr_str > "/dev/stderr" + exit 1 + } + } else if (match(attr_str, /^\047/)) { + if (match(attr_str, /^\047[^\047]*\047/)) { + attr_value = substr(attr_str, 2, RLENGTH-2) + attr_str = substr(attr_str, RSTART + RLENGTH) + attr_regex[num_open, idx] = "[[:space:]]*\n?[[:space:]]*" attr_name "=\047" attr_value "\047" + } else { + print "xml_kit: Invalid single-quoted attribute value in -open: " attr_str > "/dev/stderr" + exit 1 + } + } else if (match(attr_str, /^[^[:space:]]+/)) { + attr_value = substr(attr_str, RSTART, RLENGTH) + attr_str = substr(attr_str, RSTART + RLENGTH) + attr_regex[num_open, idx] = "[[:space:]]*\n?[[:space:]]*" attr_name "=" attr_value + } else { + print "xml_kit: Invalid attribute value in -open: " attr_str > "/dev/stderr" + exit 1 + } + } else { + attr_regex[num_open, idx] = "[[:space:]]*\n?[[:space:]]*" attr "([[:space:]]+|=(\"|\047)[^\"\047]*\\2)?" + } + } + } else { + open_regex[num_open] = "<" open_pat "([[:space:]]+[^>]+|[[:space:]]*\n[[:space:]]*[[:alnum:]][^>]+)?[[:space:]]*" + } + } + close_pat = par[i+2] + gsub(/^[[:space:]]+|[[:space:]]+$/, "", close_pat) + if (close_pat == "/>") { + self_closing_tag[num_open] = 1 + explicit_close_tag[num_open] = 0 + open_tag_only[num_open] = 0 + close_regex[num_open] = "[[:space:]]*/>" + } else if (close_pat == ">") { + self_closing_tag[num_open] = 0 + explicit_close_tag[num_open] = 0 + open_tag_only[num_open] = 1 + close_regex[num_open] = "[[:space:]]*>" + } else if(close_pat ~ /^<\/.*>$/) { + explicit_close_tag[num_open] = 1 + self_closing_tag[num_open] = 0 + open_tag_only[num_open] = 0 + tag = substr(close_pat, 3, length(close_pat)-3) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", tag) + close_regex[num_open] = "[[:space:]]*<\/" tag "[[:space:]]*>" + } else { + explicit_close_tag[num_open] = 1 + self_closing_tag[num_open] = 0 + open_tag_only[num_open] = 0 + close_regex[num_open] = "[[:space:]]*<\/" close_pat "[[:space:]]*>" + } + i += 3 + } else if(par[i] == "-in") { + if(i+1 > nopts) { + print "xml_kit: Error: Invalid -in parameter" > "/dev/stderr" + exit 1 + } + in_pattern = par[i+1] + i += 2 + } else if(par[i] == "-change-value") { + if(i+2 > nopts) { + print "xml_kit: Error: Invalid -change-value parameters" > "/dev/stderr" + exit 1 + } + change_value_flag = 1; value_old = par[i+1]; value_new = par[i+2] + i += 3 + } else if(par[i] == "-change-content") { + if(i+2 > nopts) { + print "xml_kit: Error: Invalid -change-content parameters" > "/dev/stderr" + exit 1 + } + change_content_flag = 1; content_old = par[i+1]; content_new = par[i+2] + i += 3 + } else if(par[i] == "-change-tag") { + if(i+2 > nopts) { + print "xml_kit: Error: Invalid -change-tag parameters" > "/dev/stderr" + exit 1 + } + change_tag_flag = 1; tag_old = par[i+1]; tag_new = par[i+2] + i += 3 + } else if(par[i] == "-add") { + add_text = par[i+1]; i += 2 + } else if(par[i] == "-add-inside") { + add_inside_text = par[i+1]; i += 2 + } else if(par[i] == "-after-line" || par[i] == "-al") { + if(i+2 > nopts) { + print "xml_kit: Error: Invalid -after-line parameters" > "/dev/stderr" + exit 1 + } + after_line_pattern = par[i+1]; after_line_text = par[i+2] + i += 3 + } else if(par[i] == "-before-line" || par[i] == "-bl") { + if(i+2 > nopts) { + print "xml_kit: Error: Invalid -before-line parameters" > "/dev/stderr" + exit 1 + } + before_line_pattern = par[i+1]; before_line_text = par[i+2] + i += 3 + } else if(par[i] == "-remove" || par[i] == "-rm") { + remove_flag = 1; i++ + } else if(par[i] == "-print") { + print_flag = 1; i++ + } else if(par[i] == "-print-result") { + print_result_flag = 1; i++ + } else if(par[i] == "-no-auto-spaces" || par[i] == "-nas") { + no_auto_spaces_flag = 1; i++ + } else if(par[i] == "-get-value") { + get_value_flag = 1; i++ + } else if (par[i] == "-indent" || par[i] == "-ind") { + if(i+1 > nopts) { + print "xml_kit: Error: Invalid -indent parameter" > "/dev/stderr" + exit 1 + } + indent_string = par[i+1] + i += 2 + } else { + i++ + } + } + target_depth = num_open + mod_actions = (change_value_flag == 1 || change_content_flag == 1 || change_tag_flag == 1 || + add_text != "" || add_inside_text != "" || remove_flag == 1 || + after_line_pattern != "" || before_line_pattern != "") + if(!print_result_flag && mod_actions) + extract_mode = 0 + else + extract_mode = 1 + + current_depth = 0 + stack[0] = "" + } + + function get_indent(block_content, default_indent) { + n = split(block_content, lines, "\n") + for (i = 1; i < n; i++) { + line = lines[i] + if (match(line, /^[[:space:]]*[^[:space:]]/) && (RLENGTH - 1 > 0)) { + return substr(line, RSTART, RLENGTH - 1) + } + } + return default_indent + } + + function normalize_spaces(tag) { + result = "" + in_quotes = 0 + quote_char = "" + for (i = 1; i <= length(tag); i++) { + c = substr(tag, i, 1) + if (in_quotes) { + if (c == quote_char) { + in_quotes = 0 + quote_char = "" + } + result = result c + } else { + if (c == "\"" || c == sq) { + in_quotes = 1 + quote_char = c + result = result c + } else if (c ~ /[[:space:]]/) { + if (result != "" && substr(result, length(result)) != " ") { + result = result " " + } + } else { + result = result c + } + } + } + return result + } + + function process_buffer(buf, result, orig, n, j, lines_arr, indentation, indented_text_arr, indented_text, current_indent, matched_close_tag, start_of_close_tag, content_before_close_tag) { + orig = buf + result = buf + + if(in_pattern) { + if(index(buf, in_pattern) == 0) { + if(extract_mode) + return "" + else + return buf + } + } + if(change_value_flag == 1 && value_old && value_new) { + result = gensub(value_old "=(\"[^\"]*\")", value_old "=\"" value_new "\"", "g", result) + result = gensub(value_old "=" sq "[^" sq "]*" sq, value_old "=" sq value_new sq, "g", result) + } + if(change_content_flag == 1 && content_old && content_new) { + result = gensub(content_old, content_new, "g", result) + } + if(change_tag_flag == 1 && tag_old && tag_new) { + result = gensub("(<" tag_old ">)[^<]*()", "\\1" tag_new "\\2", "g", result) + } + if(after_line_pattern && after_line_text) { + n = split(result, lines_arr, "\n") + result = "" + for(j = 1; j <= n; j++) { + result = result lines_arr[j] "\n" + if(lines_arr[j] ~ after_line_pattern) { + if (no_auto_spaces_flag == 0) { + current_indent = get_indent(lines_arr[j], indent_string) + nlines = split(after_line_text, indented_text_arr, "\n") + indented_text = "" + for (k = 1; k <= nlines; k++) { + indented_text = indented_text current_indent indented_text_arr[k] + if (k < nlines) indented_text = indented_text "\n" + } + result = result indented_text "\n" + } else { + result = result after_line_text "\n" + } + } + } + } + if(before_line_pattern && before_line_text) { + n = split(result, lines_arr, "\n") + result = "" + for(j = 1; j <= n; j++) { + if(lines_arr[j] ~ before_line_pattern) { + if (no_auto_spaces_flag == 0) { + current_indent = get_indent(lines_arr[j], indent_string) + nlines = split(before_line_text, indented_text_arr, "\n") + indented_text = "" + for (k = 1; k <= nlines; k++) { + indented_text = indented_text current_indent indented_text_arr[k] + if (k < nlines) indented_text = indented_text "\n" + } + result = result indented_text "\n" + } else { + result = result before_line_text "\n" + } + } + result = result lines_arr[j] "\n" + } + } + if(add_inside_text && !open_tag_only[current_depth]) { + if (no_auto_spaces_flag == 0) { + current_indent = get_indent(buf, indent_string) + nlines = split(add_inside_text, indented_text_arr, "\n") + indented_text = "" + for (k = 1; k <= nlines; k++) { + indented_text = indented_text current_indent indented_text_arr[k] + if (k < nlines) indented_text = indented_text "\n" + } + if(match(buf, close_regex[current_depth])) { + matched_close_tag = substr(buf, RSTART, RLENGTH) + start_of_close_tag = RSTART + content_before_close_tag = substr(buf, 1, start_of_close_tag - 1) + result = content_before_close_tag "\n" indented_text matched_close_tag + } else { + restored_close_tag = "" + result = buf "\n" indented_text restored_close_tag + } + } else { + indented_text = add_inside_text + result = gensub(close_regex[current_depth], indented_text "&", 1, buf) + } + } + if(add_text) { + if (no_auto_spaces_flag == 0) { + current_indent = get_indent(buf, indent_string) + nlines = split(add_text, indented_text_arr, "\n") + indented_text = "" + for (k = 1; k <= nlines; k++) { + indented_text = indented_text current_indent indented_text_arr[k] + if (k < nlines) indented_text = indented_text "\n" + } + result = orig indented_text + } else { + result = orig add_text + } + } + + if(remove_flag) { + result = "" + } + if(result != orig) + changed = 1 + blockFound = 1 + if(get_value_flag && extract_mode==1 && !open_tag_only[current_depth]) { + result = gensub(/^[[:space:]]*<[^>]+>[[:space:]]*/, "", 1, result) + result = gensub(/[[:space:]]*<\/[^>]+>[[:space:]]*/, "", 1, result) + } + return result + } + + { + line = $0 + pos = 1 + processed_line = "" + previous_pos = -1 + len = length(line) + + if (in_tag) { + tag_buffer = tag_buffer "\n" + } + + while(pos <= len) { + if (pos == previous_pos) { + print "xml_kit: Loop is not advancing pos variable. Possible infinite loop. Exiting." > "/dev/stderr" + printf "DEBUG: current_depth=%s, pos=%s, line=[%s]\n", current_depth, pos, line > "/dev/stderr" + exit 1 + } + previous_pos = pos + remaining = substr(line, pos) + + if (!in_tag && current_depth < target_depth && match(remaining, open_regex[current_depth+1])) { + in_tag = 1 + tag_buffer = substr(remaining, RSTART, RLENGTH) + pos += RSTART - 1 + RLENGTH + } else if (in_tag) { + if (match(remaining, />|\/>/)) { + tag_buffer = tag_buffer substr(remaining, 1, RSTART + RLENGTH - 1) + pos += RSTART - 1 + RLENGTH + in_tag = 0 + if (match(tag_buffer, open_regex[current_depth+1])) { + attr_match = 1 + if (attr_count[current_depth+1] > 0) { + for (j = 1; j <= attr_count[current_depth+1]; j++) { + if (!match(tag_buffer, attr_regex[current_depth+1, j])) { + attr_match = 0 + break + } + } + } + is_self_closing = (match(tag_buffer, /\/>[[:space:]]*$/)) + if (!attr_match || + (is_self_closing && !self_closing_tag[current_depth+1]) || + (!is_self_closing && self_closing_tag[current_depth+1]) || + (open_tag_only[current_depth+1] && !match(tag_buffer, close_regex[current_depth+1]))) { + stack[current_depth] = stack[current_depth] tag_buffer + tag_buffer = "" + next + } + tag_buffer = normalize_spaces(tag_buffer) + stack[current_depth] = stack[current_depth] substr(remaining, 1, RSTART-1) + current_depth++ + stack[current_depth] = tag_buffer + if (open_tag_only[current_depth] || (self_closing_tag[current_depth] && is_self_closing)) { + finished = stack[current_depth] + len_before_process = length(finished) + processed = process_buffer(finished) + len_after_process = length(processed) + len_diff = len_after_process - len_before_process + if (extract_mode) { + if (processed != "") + print processed "\n" + stack[current_depth] = "" + } else { + stack[current_depth-1] = stack[current_depth-1] processed + } + current_depth-- + pos += RLENGTH + len_diff + } + } else { + stack[current_depth] = stack[current_depth] tag_buffer + } + tag_buffer = "" + } else { + tag_buffer = tag_buffer remaining + pos += length(remaining) + } + } else if (current_depth > 0 && explicit_close_tag[current_depth] && match(remaining, close_regex[current_depth])) { + stack[current_depth] = stack[current_depth] substr(remaining, 1, RSTART-1) substr(remaining, RSTART, RLENGTH) + pos += RSTART - 1 + RLENGTH + finished = stack[current_depth] + if (current_depth == target_depth) { + len_before_process = length(finished) + processed = process_buffer(finished) + len_after_process = length(processed) + if (remove_flag && len_after_process == 0) { + len_diff = RSTART - 1 + } else { + len_diff = len_after_process - len_before_process + } + if (extract_mode) { + if (processed != "") + print processed "\n" + stack[current_depth] = "" + } else { + stack[current_depth-1] = stack[current_depth-1] processed + } + pos += RLENGTH + len_diff + } else { + stack[current_depth-1] = stack[current_depth-1] finished + } + current_depth-- + } else if (current_depth > 0 && self_closing_tag[current_depth] && match(remaining, close_regex[current_depth])) { + pos += RSTART + RLENGTH - 1 + } else { + processed_line = processed_line remaining + if (current_depth > 0) + stack[current_depth] = stack[current_depth] remaining + else + stack[0] = stack[0] remaining + break + } + } + if (current_depth == 0 && !in_tag) { + if (!extract_mode) + printf "%s\n", stack[0] + stack[0] = "" + } else if (!in_tag) { + stack[current_depth] = stack[current_depth] "\n" + } + } + + END { + if (in_tag) { + print "xml_kit: Unclosed tag detected in input" > "/dev/stderr" + exit 1 + } + while (current_depth > 0) { + finished = stack[current_depth] + if (current_depth == target_depth) { + processed = process_buffer(finished) + if (extract_mode && processed != "") + print processed "\n" + } else { + processed = finished + } + current_depth-- + stack[current_depth] = stack[current_depth] processed + } + if (!extract_mode) + printf "%s", stack[0] + else + print "" + if (blockFound == 0) + exit 3 + else if (print_flag || extract_mode) + exit 2 + else + exit (changed ? 0 : 1) + } + ' "$xml" > "$tmp" + result=$? + case $result in + 0) + inject "$tmp" "$xml" 1 || return 1 + echo2 "xml_kit: Modified $xml" + ;; + 1) + echo2 "xml_kit: No changes: $xml" + ;; + 2) + cat "$tmp" + result=$? + ;; + 3) + echo2 "xml_kit: No matches found: $xml" + ;; + esac + end_tmp + return $result +} + +dynamic_apktool() { + #Dynamic Apktool for Dynamic Installer by BlassGO + local file fullname filename filedir outdir folder outfile sign zipa add alladd move fw check out + local current=${PWD} tmp="$TMP/zbin/apktool" ext ps try bk count result command extra extralist noextra sm allowres bc sc + command=("-f") + bc=() + sc=() + alladd=() + while [ $# -gt 0 ]; do + case $1 in + -d|-decompile) + file="$2" + fullname=$(basename "$2") + filename=${fullname%.*} + filedir=$(dirname "$2") + outdir="$filedir/$filename" + shift 2; + ;; + -r|-recompile) + folder=${2%%/} + outfile=$(grep -m1 "FileName:" "$folder/apktool.yml" | tr -d " " | cut -f2 -d:) + ext=${outfile##*.} + shift 2; + ;; + -s|-sign) + sign=true + shift + ;; + -z|-zipalign) + zipa=true + shift + ;; + -a|-add) + alladd+=("$2") + shift 2; + ;; + -o|-output) + move=${2%%/} + shift 2; + ;; + -c|-command) + #Legacy + echo2 "dynamic_apktool: -command is deprecated, simply include the apktool flags directly" + shift 2; + ;; + -ps|-preserve-signature) + ps=true + command+=("--copy-original") + shift + ;; + -f|-framework) + #Legacy + echo2 "dynamic_apktool: -framework is deprecated, use the setdefault framework_res instead" + shift 2; + ;; + -use-baksmali) + bk="$2" + [ -e "$l/baksmali_${bk}.jar" ] && bk="$l/baksmali_${bk}.jar" + if [ ! -e "$bk" ]; then echo2 "dynamic_apktool: Cant find: $bk" && return 1; fi + command+=("--no-src") + shift 2; + ;; + -use-smali) + sm="$2" + [ -e "$l/smali_${sm}.jar" ] && sm="$l/smali_${sm}.jar" + if [ ! -e "$sm" ]; then echo2 "dynamic_apktool: Cant find: $sm" && return 1; fi + shift 2; + ;; + -bc|-baksmali-command) + mapfile -t bc < <(split_string "|" "$2") + shift 2; + ;; + -sc|-smali-command) + mapfile -t sc < <(split_string "|" "$2") + shift 2; + ;; + -no-api) + #Legacy + #noapi=true + echo2 "dynamic_apktool: -no-api is deprecated, now it uses the default apktool api level detection" + shift + ;; + -no-extras) + noextra=true + shift + ;; + -allow-res) + allowres=true + shift + ;; + *) + command+=("$1") + shift + ;; + esac + done + if [ ! -e "$l/apktool.jar" ]; then echo2 "dynamic_apktool: Cant find apktool.jar"; return 1; fi + if [ ! -e "$l/zipsigner.jar" ]; then echo2 "dynamic_apktool: Cant find zipsigner.jar"; return 1; fi + echo ">> Dynamic Apktool 1.5.1" + create_dir "$tmp" || return 1 + [ -n "$allowres" ] || command+=("--no-res") + if [[ -n "$file" && -z "$folder" && -z "$zipa" && -z "$sign" ]]; then + [ -z "$move" ] && move="$outdir" + if [ -n "$move" ]; then + echo " L: Decompiling $fullname... " + echo " L: Please wait..." + if ! apktool "${command[@]}" d "$file" -o "$move" >&2; then echo " ERROR: Cant decompile $fullname" && return 1; fi + if [ -n "$bk" ]; then + count=1 + echo " L: Executing $(basename "$bk")..." + while read dex; do + if package_extract_file "$dex" "$tmp/dex.tmp" "$file"; then + if [ -n "$result" ]; then ((count++)); result="${move}/smali_classes$count"; rm -f "${move}/classes${count}.dex"; else result="${move}/smali"; rm -f "${move}/classes.dex"; fi + echo " L: Decompiling $dex..." + if ! run_jar "$bk" d "$tmp/dex.tmp" "${bc[@]}" -o "$result"; then echo "${n} ERROR: Cant decode: \"$dex\" with \"$bk\"" && return 1; fi + fi + done < <(find_content -r "^classes[^[:space:]]*\.dex$" "$file" | sort) + fi + if [ -z "$noextra" ]; then + echo " L: Saving extras..." + while read extra; do + if [ ! -e "$move/original/$extra" ] && [ ! -e "$move/unknown/$extra" ] && [ ! -e "$move/$extra" ]; then + package_extract_file "$extra" "$move/unknown/$extra" "$file" && printf -v extralist "%s\n%s" "$extralist" " $extra: '8'" + fi + done < <( + r="kotlin/.*|assets/.*|resources\.arsc|AndroidManifest.xml|.*\.dex" + [ -d "$move/res/drawable" -o -d "$move/res/values" ] && r+="|res/.*" + find_content -r ".*" "$file" | grep -Ev "^($r)$" + ) + if [ -n "$extralist" ]; then + if ! grep -q "unknownFiles:" "$move/apktool.yml"; then + add_lines_string -after-line "sparseResources:" "unknownFiles:" "$move/apktool.yml" + elif grep -m1 "unknownFiles:" "$move/apktool.yml" | grep -q '{}'; then + replace -a "unknownFiles:" "unknownFiles:" "$move/apktool.yml" + fi + add_lines_string -after-line "unknownFiles:" "$extralist" "$move/apktool.yml" + fi + fi + echo " L: Checking results..." + if [ ! -d "$move" ]; then echo "ERROR: Decompiling $fullname" && return 1; fi + if [ ${#alladd[@]} -gt 0 ]; then + for add in "${alladd[@]}"; do + echo " L: Adding $(basename "$add")" + cp -rf "$add" "$move" + done + fi + else + echo " ERROR: Invalid dest dir" && return 1 + fi + echo " L: Success " + elif [[ -z "$file" && -n "$folder" ]]; then + rm -rf "$folder/try.apk" "$folder/dist" 2>/dev/null + echo " L: Recompiling $(basename "$folder")... " + [ -z "$ps" ] && echo " L: Using all changes..." || echo " L: Saving signature..." + echo " L: Please wait..." + if [ -n "$sm" ]; then + count=1 + echo " L: Executing $(basename "$sm")..." + while read dex; do + if [ -n "$result" ]; then ((count++)); result="${folder}/classes${count}.dex"; else result="${folder}/classes.dex"; fi + echo " L: Recompiling $(basename "$dex")..." + if ! run_jar "$sm" a "$dex" "${sc[@]}" -o "$result" || ! is_valid "$result"; then echo "${n} ERROR: Cant compile: \"$dex\" with \"$sm\""; return 1; fi + done < <(find "$folder" -mindepth 1 -maxdepth 1 -type d -regex '.*/smali\(_classes[0-9]*\)*$' | sort) + fi + apktool "${command[@]}" b "$folder" -o "$folder/try.apk" >&2 + if [ ! -f "$folder/try.apk" ]; then echo "ERROR: Compiling $outfile" && return 1; fi + if [ ${#alladd[@]} -gt 0 ]; then + for add in "${alladd[@]}"; do + echo " L: Adding $(basename "$add")" + cd "$(dirname "$add")" + zip -ur "$folder/try.apk" "$(basename "$add")" >/dev/null + done + cd "$current" + fi + if [ -n "$sign" ]; then + echo " L: Signing $outfile" + run_jar "$l/zipsigner.jar" "$folder/try.apk" "$folder/try.zp" >/dev/null + if [ ! -f "$folder/try.zp" ]; then echo "ERROR: sign $outfile" && return 1; fi + mv -f "$folder/try.zp" "$folder/try.apk" + fi + if [[ -n "$zipa" || "$ext" == "apk" || -n "$ps" ]]; then + echo " L: Zipaligning $(basename "$outfile")" + zipalign -f -v 4 "$folder/try.apk" "$folder/try.zp" >/dev/null + if [ ! -f "$folder/try.zp" ]; then echo "ERROR: zipalign $outfile" && return 1; fi + mv -f "$folder/try.zp" "$folder/try.apk" + fi + if [ -n "$move" ]; then + copy "$folder/try.apk" "$move" + if is_valid "$move"; then + rm -f "$folder/try.apk" + echo " L: Success $(basename "$move")" && return 0 + else + echo " L: ERROR $(basename "$move")" && return 1 + fi + else + mkdir -p "$folder/dist" + mv -f "$folder/try.apk" "$folder/dist/$outfile" + if is_valid "$folder/dist/$outfile"; then + echo " L: Success $outfile" && return 0 + else + echo " L: ERROR $outfile" && return 1 + fi + fi + else + echo "dynamic_apktool: Invalid line" && return 1 + fi +} + +find_installed_apk() { + local overlays pkg list hasover enabled re=0 params isover debug=false + while [ $# -gt 0 ]; do + case $1 in + -r|-recursive) + re=1 + shift + ;; + -no|-no-overlays) + overlays=0 + shift + ;; + -oo|-only-overlays) + overlays=1 + shift + ;; + -e|-enabled) + params=-e + shift + ;; + -d|-disabled) + params=-d + shift + ;; + -eo|-enabled-overlays) + enabled=1 + shift + ;; + -do|-disabled-overlays) + enabled=0 + shift + ;; + -debug) + debug=true + shift + ;; + -cmd) + params="$params $2" + shift 2 + ;; + *) + break + ;; + esac + done + if [ $# = 0 ]; then + set -- ".*" + re=-1 + fi + if [ "$overlays" != "" ]; then cmd overlay list 2>/dev/null | tee "$TMP/overlays.txt" >/dev/null; else rm -f "$TMP/overlays.txt"; fi + pm list packages -f $params | tee "$TMP/packages.txt" >/dev/null + for pkg in "$@"; do + [ -z "$pkg" ] && continue + grep "$pkg" "$TMP/packages.txt" | awk -F '=' -v pkg="$pkg" -v re="$re" ' + { + after=$NF + before=substr($0,1,length($0)-length(after)-1) + if (re==-1) { + if (index(before,"package:")==1) { + before=substr(before,9) + } + print before,after + } else if (re==0) { + if (after==pkg) { + if (index(before,"package:")==1) { + before=substr(before,9) + } + print before,after + } + } else { + if (index(after,pkg)!=0) { + if (index(before,"package:")==1) { + before=substr(before, 9) + } + print before,after + } + } + }' | while read -r before after; do + isover= + if [ "$overlays" != "" ]; then + isover=$(grep -m1 " ${after}$" "$TMP/overlays.txt") + if [ $overlays = 0 ]; then + if [ -n "$isover" ]; then + $debug && echo2 "$after is an overlay, skipping..." + continue + fi + elif [ $overlays = 1 ]; then + if [ -z "$isover" ]; then + $debug && echo2 "$after isn't an overlay, skipping..." + continue + fi + fi + fi + if [ "$overlays" != "0" ]; then + if [ "$enabled" != "" ]; then + [ "$overlays" = "" ] && isover=$(grep -m1 " ${after}$" "$TMP/overlays.txt") + if [ -n "$isover" ]; then + if [ $enabled = 1 ]; then + if ! echo "$isover" | grep -q "^\[x\]"; then + $debug && echo2 "$after overlay disabled, skipping..." + continue + fi + elif [ $enabled = 0 ]; then + if echo "$isover" | grep -q "^\[x\]"; then + $debug && echo2 "$after overlay enabled, skipping..." + continue + fi + fi + fi + fi + fi + echo "$before" + done + done +} + +find_apk() { + local userlist package try flag re result + local path try2 limit count=0 + local check_split splits=false overlays=false check params os oo + if ! aapt version >/dev/null; then echo2 " CANT LOAD AAPT: U cant use find_apk " && return 1; fi + while [ $# -gt 0 ]; do + case $1 in + -r|-recursive) + re=true + shift + ;; + -l|-limit) + limit="$2" + shift 2; + ;; + -is|-include-splits) + splits=true + shift + ;; + -io|-include-overlays) + overlays=true + shift + ;; + -os|-only-splits) + os=true + shift + ;; + -oo|-only-overlays) + oo=true + shift + ;; + -fs|-follow-symlinks) + params=-L + shift + ;; + *) + break + ;; + esac + done + [ $# -lt 1 ] && return 1 + if [ $# = 1 ]; then + set -- ".*" "$@" + re=-1 + fi + rm -f "$TMP/userlist.txt" "$TMP/packages.txt" + if [ -n "$limit" ] && ! is_number $limit; then echo2 " find_apk: Invalid line" && return 1; fi + path=${!#} + [ -L "$path" ] && path=$(readlink -f "$path") + if [ ! -d "$path" ]; then echo2 "find_apk: Cant find folder: $path"; return 1; fi + while read userlist; do + package=$(apk_package "$userlist") + [ -n "$package" ] && { + ((count++)) + echo "$count=$userlist" >> "$TMP/userlist.txt" + echo "$count=$package" >> "$TMP/packages.txt" + } + done < <(find $params "$path" -type f -name "*.apk") + [ $count == 0 ] && { echo2 "find_apk: Cant find any apk in $path "; return 1 ; } + count=0 + for try in "${@:1:$#-1}"; do + while IFS== read try2 package; do + result=$(get_file_prop "$TMP/userlist.txt" "$try2") + $splits || check_split=$(aapt dump badging "$result" 2>/dev/null | sed -n "s/.* split='\([^']*\).*$/\1/p") + $overlays || check_overlay=$(aapt dump xmlstrings "$result" AndroidManifest.xml 2>/dev/null | grep -w " overlay") + [ -n "$result" ] && { + if [ -n "$os" -a -n "$oo" ]; then + if [ -n "$check_split" -o -n "$check_overlay" ]; then echo "$result"; ((count++)); fi + elif [ -n "$os" ]; then + if [ -n "$check_split" ]; then echo "$result"; ((count++)); fi + elif [ -n "$oo" ]; then + if [ -n "$check_overlay" ]; then echo "$result"; ((count++)); fi + elif [ -z "$check_split" -a -z "$check_overlay" ]; then + echo "$result";((count++)) + elif $splits && [ -z "$check_overlay" ]; then + echo "$result";((count++)) + elif $overlays && [ -z "$check_split" ]; then + echo "$result";((count++)) + fi + [ -n "$limit" -a "$limit" = "$count" ] && break + } + done < <(if [ -z "$re" ]; then grep "=$try$" "$TMP/packages.txt"; else grep "$try" "$TMP/packages.txt"; fi) + done + [ $count != 0 ] +} + +apk_package() { + aapt dump badging "$1" 2>/dev/null | sed -n "s/.*package: name='\([^']*\).*$/\1/p" +} + +apk_main() { + aapt dump badging "$1" 2>/dev/null | sed -n "s/.*launchable-activity: name='\([^']*\).*$/\1/p" +} + +apk_icon() { + aapt dump badging "$1" 2>/dev/null | sed -n "s/.*application:.*icon='\([^']*\).*$/\1/p" +} + +apk_launch() { + local pkg main + if [ ! -f "$(which am)" ]; then echo " CANT LOAD AM: U cant use apk_launch, try mounting /system" && return 1; fi + [ -f "$1" ] && pkg=$(apk_package "$1") || pkg="$1" + [ -z "$2" ] && main=$(apk_main "$1") || main="$2" + if [ -z "$pkg" ]; then echo2 "apk_launch: Cant get the package of $1" && return 1; fi + if [ -z "$main" ]; then echo2 "apk_launch: Cant get the main launchable activity of $1" && return 1; fi + am start -n "$pkg/$main" +} + +get_density_key() { + awk -v value="$1" ' + BEGIN { + if (value >= 120 && value < 160) { + print "ldpi" + } else if (value >= 160 && value < 240) { + print "mdpi" + } else if (value >= 240 && value < 320) { + print "hdpi" + } else if (value >= 320 && value < 480) { + print "xhdpi" + } else if (value >= 480 && value < 640) { + print "xxhdpi" + } else if (value >= 640) { + print "xxxhdpi" + } else if (value == 0) { + print "nodpi" + } + }' +} + +get_density_int() { + case "$1" in + "ldpi") echo 120 ;; + "mdpi") echo 160 ;; + "tvdpi") echo 213 ;; + "hdpi") echo 240 ;; + "xhdpi") echo 320 ;; + "xxhdpi") echo 480 ;; + "xxxhdpi") echo 640 ;; + "nodpi") echo 0 ;; + *) is_number "$1" && echo "$1" ;; + esac +} + +apk_install() { + local list split apk count success=true try + local size total id TMP2 lang density support dp dp_support install archi_support archi get new + #if ! pm help >/dev/null; then echo " CANT LOAD PM: U cant use apk_install " && return 1; fi + TMP2="/data/local/tmp/apk_install" + create_dir "$TMP2" || return 1 + while [ "$1" ]; do + total=0 + count=0 + try="$1" + apk="${try%%:*}" + list=$(unzip -l "$apk" | tail -n+4 | cut -c4-) + if echo "$list" | grep -q "META-INF/APKMIRRO.SF" && echo "$list" | grep -q info.json; then + new= + archi= + dp= + lang=$(getprop persist.sys.locale | cut -d- -f1) + density=$(cmd window density | cut -d: -f2 | tr -d " ") + if [ -z "$lang" ]; then echo2 "apk_install: Unable to get current device language" && success=false && break; fi + if ! is_number "$density"; then echo2 "apk_install: Unable to get device density" && success=false && break; fi + [[ "$lang" != "en" ]] && lang+=" en" + dp_support=$(string extract '"dpis": [' ']' "$(package_extract_file info.json "" "$apk")" | tr -d ' ,"' | tr '\n' ' ') + if [[ "$(echo "$dp_support" | tr -d " ")" != "nodpi" ]]; then + for support in $dp_support; do + if [[ "$(get_density_int "$support")" == "$density" ]]; then dp=$density && break; fi + done + if [ -z "$dp" ]; then + density=$(printf "%.0f" "$(calc "$density/160")") + density=$(($density*160)) + for support in $dp_support; do + if [[ "$(get_density_int "$support")" == "$density" ]]; then dp=$density && break; fi + done + fi + [ -z "$dp" ] && dp=$(echo "$dp_support" | tr ' ' '\n' | grep -E "." | tail -n1 | tr -d " ") + if ! is_number "$dp"; then echo2 "apk_install: Could not get a valid dpi: $apk " && success=false && break; fi + dp=$(get_density_key "$dp") + fi + archi_support=$(string extract '"arches": [' ']' "$(package_extract_file info.json "" "$apk")" | tr -d ' ,"' | tr '\n' ' ') + for support in $archi_support; do + if [[ "$support" == "$ABILONG" ]]; then archi=$support && break; fi + done + if [ -z "$archi" ]; then + for support in $archi_support; do + if is_substring "$arch" "$support"; then archi=$support && break; fi + done + fi + if [ -z "$archi" ]; then + for support in $archi_support; do + if is_substring "$arch32" "$support"; then archi=$support && break; fi + done + fi + if [ -z "$archi" ]; then echo2 "apk_install: Unsupported architecture: $apk " && success=false && break; fi + archi=${archi/"-"/"_"} + for install in base $lang $dp $archi; do + get=$(echo "$list" | grep -m1 -F ".${install}.") + [ -z "$get" ] && get=$(echo "$list" | tail -n+4 | cut -c4- | grep -m1 -F "${install}.") + [ -z "$get" ] && continue + [ -n "$new" ] && new+=":" + get=${get##* } + unzip -qo "$apk" "$get" -d "$TMP2" + if [ -f "$TMP2/$get" ]; then + new+="$TMP2/$get" + else echo2 "apk_install: Cant get \"$get\"" && success=false && break + fi + done + if $success; then + try=${try/"${apk}"/"${new}"} + while read split; do + [ -z "$split" ] && break + if [ ! -f "$split" ]; then + unzip -qo "$apk" "$split" -d "$TMP2" + if is_valid "$TMP2/$(basename "$split")"; then + try=${try/"${split}"/"$TMP2/$(basename "$split")"} + else echo2 "Cant find: $split" && success=false && break + fi + fi + done <<< $(split_string : "$try") + fi + fi + $success || break + if is_substring : "$try"; then + while read split; do + [ -z "$split" ] && break + if [ ! -f "$split" ]; then echo2 "Cant find: $split" && success=false && break; fi + size=$(getsize "$split") + total=$((total+size)) + done <<< $(split_string : "$try") + $success || break + id=$(pm install-create -S "$total" | grep -oE "[0-9]*") + if is_number "$id"; then + echo2 "Created session: $id: $(basename "$apk")" + while read split; do + [ -z "$split" ] && break + size=$(getsize "$split") + [ "$(dirname "$split")" != "$TMP2" ] && copy "$split" "$TMP2" + if pm install-write -S $size $id $count "$TMP2/$(basename "$split")" >/dev/null; then + echo2 "Added: $(basename "$split")" + else + echo2 "apk_install: Could not add: $split to $id installation" && success=false && break + fi + count=$(($count+1)) + done <<< $(split_string : "$try") + if $success; then + if pm install-commit $id >/dev/null; then + echo2 "Installed successfully: $id session" + else + echo2 "apk_install: CANT INSTALL SESSION: $id" && success=false + fi + fi + else + echo2 "apk_install: Cannot create a session: $try" && success=false + fi + else + if [ ! -f "$try" ]; then echo2 "Cant find: $try" && success=false; fi + $success || break + copy "$try" "$TMP2" + if pm install -r "$TMP2/$(basename "$try")" >/dev/null; then + echo2 "Installed successfully: $(basename "$try")" + else + echo2 "apk_install: CANT INSTALL: $try" && success=false + fi + fi + $success || break + shift; + [ "$1" ] && echo2 " " + done; + rm -rf "$TMP2" + $success && return 0 || return 1 +} + +apk_install_recursive() { + local dir any apk ext list + while [ "$1" ]; do + dir="$1" + while read any; do + ext=$(basename "$any") + ext=${ext##*.} + if [ -f "$any" ] && [[ "$ext" == "apk" || "$ext" == "apkm" ]]; then + apk_install "$any" || return 1 + echo2 " " + elif [ -d "$any" ]; then + echo2 "From: $any" + list=$(find "$any" -type f -name "*.apkm" | head -n1) + while read apk; do + [ -n "$list" ] && list+=":" + list+="$apk" + done < <(find "$any" -type f -name "*.apk") + if [ -n "$list" ]; then apk_install "$list" || return 1; echo2 " " + else echo2 "No .apk/.apkm found on: $any" + fi + fi + done < <(find "$dir" -mindepth 1 -maxdepth 1) + shift; + done + return 0 +} + +patch_apk() { + local TMP2 c="${PWD}" s n tmp zp r=0 + if ! exist "$1" "$2"; then echo2 "patch_apk: Invalid line"; return 1; fi + testrw "$(dirname "$2")" || return 1 + start_tmp + tmp="$TMP2/$(basename "$2")"; zp="$tmp.zp" + cp -pf "$2" "$tmp" + savestate s "$tmp" + cd "$1" && zip -r "$tmp" * >/dev/null + cd "$c" + savestate n "$tmp" + [[ "$s" != "$n" ]] && echo "Patched: $2" || { echo "No changes: $2"; r=1 ; } + [ $r = 0 ] && { + case "$3" in + zipalign) + zipalign -f -v 4 "$tmp" "$zp" >/dev/null + [ -e "$zp" ] && { echo2 "Zipaligned: $2"; mv -f "$zp" "$tmp" ; } || { echo2 "ERROR: zipalign $2"; r=1 ; } + ;; + sign) + [ ! -e "$l/zipsigner.jar" ] && { echo2 "patch_apk: Cant find zipsigner.jar"; r=1 ; } + [ $r = 0 ] && run_jar "$l/zipsigner.jar" "$tmp" "$zp" >/dev/null + [ -e "$zp" ] && { echo2 "Signed: $2"; mv -f "$zp" "$tmp" ; } || { echo2 "ERROR: sign $2"; r=1 ; } + ;; + esac + [ $r = 0 ] && is_valid "$tmp" && inject "$tmp" "$2" 1 || { echo2 "FATAL ERROR: $tmp"; r=1 ; } + } + end_tmp + return $r +} + +patch_apk_addon() { + patch_apk "$addons/$1" "$2" "$3" +} + +patch_apk_zip() { + local result + rm -rf "$TMP/patch_apk" + package_extract_dir "$1" "$TMP/patch_apk" || return 1 + patch_apk "$TMP/patch_apk" "$2" "$3" + result=$? + rm -rf "$TMP/patch_apk" + return $result +} + +progress() { + # command & progress + local pid=$! huh delay=0.1 limit=10 count=0 anim='|/-\' finish=10 mark="==========================================" + while kill -0 "$pid" 2> /dev/null; do + sleep $delay + local temp=${anim#?} + local pd=$(( $count * 73 / $finish )) + if [[ "$count" == "2" ]]; then sleep 0.3 && count=$(( $count + 1 )); fi + if [[ "$count" == "3" ]]; then sleep 0.3 && count=$(( $count + 1 )); fi + if [[ "$count" -le "4" ]]; then count=$(( $count + 1 )); fi + printf "\r %c %3d.%1d%% %.${pd}s" $anim $(( $count * 100 / $finish )) $(( ($count * 1000 / $finish) % 10 )) $mark + local anim=$temp${anim%"$temp"} + done + while [[ $count -lt $limit ]]; do + sleep $delay + local temp=${anim#?} + local pd=$(( $count * 73 / $finish )) + count=$(( $count + 1 )) + printf "\r %c %3d.%1d%% %.${pd}s" $anim $(( $count * 100 / $finish )) $(( ($count * 1000 / $finish) % 10 )) $mark + local anim=$temp${anim%"$temp"} + done + echo +} + +progress_print() { + # command & progress_print " file to print" + local pid=$! check check2 idk=0 file="$1" delay=0 count=0 + local huh limit uwu + limit=$(cat "$file" | wc -l) + huh=$(( $limit / 4 )) + while read uwu; do + while kill -0 "$pid" 2> /dev/null; do + count=$(( $count + 1 )) + sleep $delay + if [[ "$uwu" != "$check" ]]; then + if [[ "$count" -lt "$huh" ]]; then + ui_print "$uwu" + fi + if [[ "$count" -lt "$(( $count * 2 ))" ]]; then + ui_print "$uwu" + fi + fi + check="$uwu" + done + while [[ "$idk" -lt "$limit" ]]; do + if [[ "$uwu" != "$check2" && "$uwu" != "$check" ]]; then + idk=$(( $idk + 1 )) + sleep $delay + ui_print "$uwu" + check2="$uwu" + else + break + fi + done + done < "$file" + echo +} + + +progress_script() { + local huh + #progress_script script/commands + huh="$@" + if [ -z "$huh" ]; then return; fi + $("$@" >/dev/null 2>&1) & progress +} + +start_loading() { + #start_loading -s 10 -f 100 -a '|/-\' -l [..................................] + #start_loading -s(tart) 10 -f(inish) 100 -d(elay) 0.5 -a(nimation) '|/-\' -l(inear) [..................................] + local complete=$(getvalue s $@ 2>/dev/null) + export mark=$(getvalue l $@ 2>/dev/null) + export loafinish=$(getvalue f $@ 2>/dev/null) + export delay=$(getvalue d $@ 2>/dev/null) + export loacount=0 + export anim=$(getvalue a $@ 2>/dev/null) + for huh in complete loafinish; do + uwu=$(checkvar $huh) + if [ -z "$uwu" ]; then + echo "start loading: Fatal line" && return + fi + done + if [ -z "$delay" ]; then export delay="0.3" ; fi; + while [[ $loacount -lt $loafinish && $loacount -lt $complete ]]; do + sleep $delay + local temp=${anim#?} + export loacount=$(( $loacount + 1 )) + export pd=$(( $loacount * 73 / $loafinish )) + if [[ -n "$mark" && -n "$anim" ]]; then + printf "\r %c %3d.%1d%% %.${pd}s" $anim $(( $loacount * 100 / $loafinish )) $(( ($loacount * 1000 / $loafinish) % 10 )) $mark + local anim=$temp${anim%"$temp"} + elif [[ -z "$mark" && -n "$anim" ]]; then + printf "\r %c %3d.%1d%%" $anim $(( $loacount * 100 / $loafinish )) $(( ($loacount * 1000 / $loafinish) % 10 )) + local anim=$temp${anim%"$temp"} + elif [[ -n "$mark" && -z "$anim" ]]; then + printf "\r%3d.%1d%% %.${pd}s" $(( $loacount * 100 / $loafinish )) $(( ($loacount * 1000 / $loafinish) % 10 )) $mark + else + printf "\r%3d.%1d%%" $(( $loacount * 100 / $loafinish )) $(( ($loacount * 1000 / $loafinish) % 10 )) + fi + done + if [[ "$loacount" -ge "$loafinish" ]]; then echo ; fi; +} + +add_loading() { + local complete=$1 + local add=0 + for huh in complete loafinish; do + uwu=$(checkvar $huh) + if [ -z "$uwu" ]; then + echo "add loading: Fatal line or undefined start_loading" && return + fi + done + while [[ $loacount -lt $loafinish && $add -lt $complete ]]; do + sleep $delay + local temp=${anim#?} + export loacount=$(( $loacount + 1 )) + add=$(( $add + 1 )) + export pd=$(( $loacount * 73 / $loafinish )) + if [[ -n "$mark" && -n "$anim" ]]; then + printf "\r %c %3d.%1d%% %.${pd}s" $anim $(( $loacount * 100 / $loafinish )) $(( ($loacount * 1000 / $loafinish) % 10 )) $mark + local anim=$temp${anim%"$temp"} + elif [[ -z "$mark" && -n "$anim" ]]; then + printf "\r %c %3d.%1d%%" $anim $(( $loacount * 100 / $loafinish )) $(( ($loacount * 1000 / $loafinish) % 10 )) + local anim=$temp${anim%"$temp"} + elif [[ -n "$mark" && -z "$anim" ]]; then + printf "\r%3d.%1d%% %.${pd}s" $(( $loacount * 100 / $loafinish )) $(( ($loacount * 1000 / $loafinish) % 10 )) $mark + else + printf "\r%3d.%1d%%" $(( $loacount * 100 / $loafinish )) $(( ($loacount * 1000 / $loafinish) % 10 )) + fi + done + if [[ "$loacount" -ge "$loafinish" ]]; then echo ; fi; +} + +smali_kit() { + #Smali Tool kit for Dynamic Installer by BlassGO + local dir num line original count=0 result=1 load try nothing + local file path method replace limit rim newline oldline check stock edit names=() remake + local get al al_add bl bl_add dim dim_oldline printpath prev dm all + while [ $# -gt 0 ]; do + case $1 in + -f|-file) + file="$2"; path= + shift 2 + ;; + -d|-dir) + path="$2"; file= + shift 2 + ;; + -m|-method) + method="$2" + shift 2 + ;; + -r|-replace) + replace="$2" + shift 2 + ;; + -rim|-replace-in-method) + rim=true + oldline="$2" + newline="$3" + shift 3 + ;; + -dim|-delete-in-method) + dim=true + dim_oldline="$2" + shift 2 + ;; + -re|-remake) + remake="$2" + shift 2 + ;; + -al|-after-line) + al="$2" + al_add="$3" + shift 3 + ;; + -bl|-before-line) + bl="$2" + bl_add="$3" + shift 3 + ;; + -c|-check) + check=true + shift + ;; + -n|-name) + names+=( $([ ${#names[@]} -gt 0 ] && echo -o) "-name" "$2") + shift 2 + ;; + -in|-iname) + names+=( $([ ${#names[@]} -gt 0 ] && echo -o) "-iname" "$2") + shift 2 + ;; + -l|-limit) + limit="$2" + shift 2 + ;; + -pp|-print-path) + printpath=true + shift + ;; + -dm|-delete-method) + dm=true + shift + ;; + -recursive) + all="-r" + shift + ;; + *) + echo2 "smali_kit: Invalid option: $1" + return 1 + ;; + esac + done + [ -d "$path" -o -f "$file" ] || { echo2 "CANT FIND: $path$file" && return 1 ; } + [ -n "$method" ] || { echo2 "smali_kit: Undefined method to find" && return 1 ; } + [ -z "$replace" -a -z "$rim" -a -z "$remake" -a -z "$al" -a -z "$bl" -a -z "$dim" -a -z "$printpath" -a -z "$dm" ] && nothing=true + while IFS=: read dir line; do + [ "${line:0:7}" == ".method" ] || continue + [[ "$line" != *" abstract "* ]] || continue + if [[ -n "$printpath" && "$dir" != "$prev" ]]; then + echo "$dir" + result=0 + ((count++)) + [[ -n "$limit" && "$limit" == "$count" ]] && break + prev="$dir" + continue + fi + if [ -n "$nothing" ]; then + echo "path=$dir" + string force -f "$dir" complete_extract "$line" ".end method" + result=0 + ((count++)) + [[ -n "$limit" && "$limit" == "$count" ]] && break + continue + fi + testrw "$(dirname "$dir")" || return 1 + load=$(<"$dir") + original=$(string force complete_extract "$line" ".end method" "$load") + [ -n "$check" ] && savestate stock "$dir" + if [ -n "$replace" ]; then + echo "${load/"$original"/"$replace"}" > "$dir" + fi + if [[ -n "$rim" && -n "$oldline" && -n "$newline" ]]; then + [ -z "$all" ] && try=${original/"$oldline"/"$newline"} || try=${original//"$oldline"/"$newline"} + echo "${load/"$original"/"$try"}" > "$dir" + fi + if [[ -n "$dim" && -n "$dim_oldline" ]]; then + [ -z "$all" ] && try=${original/"$dim_oldline"} || try=${original//"$dim_oldline"} + echo "${load/"$original"/"$try"}" > "$dir" + fi + if [ -n "$remake" ]; then + echo "${load/"$original"/"$line${n}$remake${n}.end method"}" > "$dir" + fi + if [[ -n "$bl" && -n "$bl_add" ]]; then + try=$(string force $all -before-line "$bl" "$bl_add" "$original") + echo "${load/"$original"/"$try"}" > "$dir" + fi + if [[ -n "$al" && -n "$al_add" ]]; then + try=$(string force $all -after-line "$al" "$al_add" "$original") + echo "${load/"$original"/"$try"}" > "$dir" + fi + if [ -n "$dm" ]; then + echo "${load/"$original"}" > "$dir" + fi + ((count++)) + if [ -n "$check" ]; then savestate edit "$dir"; [[ "$edit" != "$stock" ]] && { ui_print "Edited: \"$dir\"" ; result=0 ; } || { ui_print "Nothing: \"$dir\"" ; }; fi + [[ -n "$limit" && "$limit" == "$count" ]] && break + done < <(if [ -n "$path" ]; then if [ ${#names[@]} -gt 0 ]; then find "$path" -type f \( "${names[@]}" \) -exec grep -FH "$method" {} + ; else grep -FHr "$method" "$path"; fi ; else grep -FH "$method" "$file"; fi) + return $result +} + +apex_pkg() { + #Based on Magisk method + local apex="$1" dest pattern='s/.*"name":[^"]*"\([^"]*\).*/\1/p' pattern2='s/.*package="\([^"]*\).*$/\1/p' + [ -f "$apex/apex_manifest.json" ] && dest=$(sed -n $pattern "$apex/apex_manifest.json" | tr -d " ") || + [ -f "$apex/apex_manifest.pb" ] && dest=$(strings "$apex/apex_manifest.pb" | head -n 1 | tr -cd '[:alnum:]_.') + [ -z "$dest" ] && { + dest=$(unzip -qp "$apex" apex_manifest.pb 2>/dev/null | strings | head -n 1 | tr -cd '[:alnum:]_.') + [ -z "$dest" ] && dest=$(unzip -qp "$apex" apex_manifest.json 2>/dev/null | sed -n $pattern | tr -d " ") + [ -z "$dest" ] && dest=$(unzip -qp "$apex" apex_build_info.pb 2>/dev/null | sed -n $pattern2 | tr -d " ") + } + [ -n "$dest" ] && echo "$dest" || return 1 +} + +getarch() { + local info system_prop vendor_prop + ! $BOOTMODE && { + try_mount -e -n system /systemtmp 2>/dev/null + try_mount -e -n vendor /vendortmp 2>/dev/null + is_mounted /systemtmp && { + [ -f /systemtmp/build.prop ] && system_prop=/systemtmp/build.prop || [ -f /systemtmp/system/build.prop ] && system_prop=/systemtmp/system/build.prop + free_system=$(getfree /systemtmp) + } + is_mounted /vendortmp && { + vendor_prop=/vendortmp/build.prop + free_vendor=$(getfree /vendortmp) + } || { + [ -f /systemtmp/vendor/build.prop ] && { + vendor_prop=/systemtmp/vendor/build.prop + free_vendor=$free_system + } || [ -f /systemtmp/system/vendor/build.prop ] && { + vendor_prop=/systemtmp/system/vendor/build.prop + free_vendor=$free_system + } + } + } || { + # Get props from the environment + free_system=$(getfree /system) + free_vendor=$(getfree /vendor) + } + [ -f "$system_prop" ] && { + map_file_props info "$system_prop" \ + ro.build.version.sdk \ + ro.product.cpu.abi \ + ro.hardware.chipname + API=${info[0]} + ABILONG=${info[1]} + PROC=${info[2]} + } || { + API=$(getprop ro.build.version.sdk) + ABILONG=$(getprop ro.product.cpu.abi) + PROC=$(getprop ro.product.board) + } + ABI=${ABILONG:0:3} + + [ -f "$vendor_prop" ] && { + map_file_props info "$vendor_prop" \ + ro.virtual_ab.enabled \ + ro.boot.dynamic_partitions + virtual_partitions=${info[0]} + dynamic_partitions=${info[1]} + } || { + virtual_partitions=$(getprop ro.virtual_ab.enabled) + dynamic_partitions=$(getprop ro.boot.dynamic_partitions) + } + dynamic_partitions=${dynamic_partitions:-false} + virtual_partitions=${virtual_partitions:-false} + + free_root=$(getfree /) + status=$(getenforce) + + slot=$(grep_cmdline androidboot.slot_suffix) || slot=$(grep_cmdline androidboot.slot) || slot=$(getprop ro.boot.slot_suffix) + [ -n "$slot" ] && { is_substring _ "$slot" && export slot="$slot" || export slot="_${slot}"; } + + PROC=${PROC:-$(getprop ro.hardware)} + PROC=${PROC:-$(grep_cmdline androidboot.hardware)} + + encrypted=true + touch /data/.rw && rm /data/.rw && encrypted=false + grep ' /data ' /proc/mounts | grep -q 'dm-' && encrypted=true + [ "$(getprop ro.crypto.state)" = "encrypted" ] && encrypted=true + + [ -e /proc/cpuinfo ] && { + case "$(grep -m1 "Hardware" /proc/cpuinfo)" in + *Qualcomm*) chipname=snapdragon ;; + *Kirin*) chipname=kirin ;; + *Unisoc*) chipname=unisoc ;; + *MT*) chipname=mediatek ;; + *) is_substring "exynos" "${PROC,,}" && chipname=exynos ;; + esac + } + + arch=arm + arch32=arm + is64bit=false + case "$ABILONG" in + "arm64-v8a") arch=arm64; arch32=arm; is64bit=true ;; + "x86_64") arch=x64; arch32=x86; is64bit=true ;; + "x86") arch=x86; arch32=x86 ;; + esac + + echo2 "------------Device INFO------------" + for info in BOOTMODE ARCH API ABI ABILONG PROC arch arch32 is64bit chipname status encrypted slot dynamic_partitions virtual_partitions free_root free_system free_vendor; do + [ -n "${!info}" ] && echo2 "$info=${!info}" + done + echo2 "------------Setup INFO------------" + for info in CUSTOM_SETUP TMP $SHARED_VARS installzip di_version main_version; do + [ -n "${!info}" ] && echo2 "$info=${!info}" + done + echo2 "----------------------------------" + $BOOTMODE || { + unmount /systemtmp + unmount /vendortmp + is_mounted /systemtmp || rm -rf /systemtmp + is_mounted /vendortmp || rm -rf /vendortmp + } +} + +getbins() { + if [ $# == 0 ]; then local IFS=: ; find $PATH -maxdepth 1 -type f -executable 2>/dev/null + else find -L "$@" -type f -executable 2>/dev/null + fi +} + +run() { + local file var + file=$(fullpath "$2") + var="$1" + [ ! -f "$file" ] && return 1 + [ -z "$var" ] && return 1 + shift 2 + is_exec "$file" || chmod +x "$file" + setdefault "$var" "$("$file" "$@" 2>&1)" +} + +run_addon() { + local file var + file="$2" + var="$1" + shift 2 + run "$var" "$addons/$file" "$@" +} + +run_zip() { + local file var + file="$2" + var="$1" + shift 2 + package_extract_file "$file" "$TMP/$(basename "$file")" || return 1 + run "$var" "$TMP/$(basename "$file")" "$@" + rm -f "$TMP/$(basename "$file")" +} + +zip_list() { + local f="$1" p="$2" + [ "${p: -1: 1}" == "/" ] && p+="*" + if [ -n "$p" ]; then unzip -l "$f" "$p" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "") print path}' + elif [ -n "$f" ]; then unzip -l "$f" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "") print path}' + elif [ -n "$installzip" ]; then unzip -l "$installzip" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "") print path}' + fi +} + +zip_list_file() { + local f="$1" p="$2" + [ "${p: -1: 1}" == "/" ] && p+="*" + if [ -n "$p" ]; then unzip -l "$f" "$p" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "" && substr(path, length(path)) != "/") print path}' + elif [ -n "$f" ]; then unzip -l "$f" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "" && substr(path, length(path)) != "/") print path}' + elif [ -n "$installzip" ]; then unzip -l "$installzip" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "" && substr(path, length(path)) != "/") print path}' + fi +} + +zip_list_dir() { + local f="$1" p="$2" + [ "${p: -1: 1}" == "/" ] && p+="*" + if [ -n "$p" ]; then unzip -l "$f" "$p" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "" && substr(path, length(path)) == "/") print path}' + elif [ -n "$f" ]; then unzip -l "$f" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "" && substr(path, length(path)) == "/") print path}' + elif [ -n "$installzip" ]; then unzip -l "$installzip" | tail -n+4 | awk '{$1=$2=$3=""; path=substr($0, 4); if(path != "" && substr(path, length(path)) == "/") print path}' + fi +} + +package_extract_file() { + local check zip="$installzip" + [ -n "$3" ] && zip="$3" + if [ ! -f "$zip" ]; then echo2 "CANT FIND: $zip" && return 1; fi + check=$(zip_list_file "$zip" "$1" | wc -l) + if [ $check = 1 ]; then + if [ -n "$1" -a -n "$2" ]; then + [ -L "$2" ] && { [ "$overwrite_symlinks" == "on" ] && { [ -f "$2" ] && { echo2 "ZIP: Overwriting symlink [$2]"; rm -f "$2" ; } || { echo2 "ZIP: Symlink does not reference a file, skipping [$2]"; return 1 ; } ; } || { echo2 "ZIP: Skipping symlink [$2]"; return 0 ; } ; } + create_dir "$(dirname "$2")" || return 1 + unzip -qp "$zip" "$1" > "$2" + if [ -f "$2" ]; then set_perm2 $___uid $___gid $___mof "$2" + else return 1 + fi + elif [ -n "$1" ]; then + unzip -qp "$zip" "$1" || return 1 + else + return 1 + fi + elif [ $check -gt 1 ]; then + echo2 "ZIP: You are trying to extract a folder [$1], use package_extract_dir" && return 1 + else + echo2 "ZIP: Cant find file [$1]" && return 1 + fi +} + +package_extract_dir() { + local from="${1%%/}" list outfile outdir zip="$installzip" return=0 a + [ -n "$3" ] && zip="$3" + if [ ! -f "$zip" ]; then echo2 "CANT FIND: $zip" && return 1; fi + list=$(zip_list "$zip" "$from/") + if [ -z "$list" ]; then + list=$(zip_list_file "$zip" "$1") + [ -n "$list" ] && echo2 "ZIP: You are trying to extract a file [$1], please use package_extract_file" || echo2 "ZIP: Cant find folder [${1%%/}]" + return 1 + fi + while read a; do + if [ -n "$1" -a -n "$2" ]; then + outfile=${a/"$from"/"$2"} + outdir=$(dirname "$outfile") + [ -L "$outfile" ] && { [ "$overwrite_symlinks" == "on" ] && { [ -f "$outfile" ] && { echo2 "ZIP: Overwriting symlink [$outfile]"; rm -f "$outfile" ; } || { echo2 "ZIP: Symlink does not reference a file, skipping [$outfile]"; return=1; continue ; } ; } || { echo2 "ZIP: Skipping symlink [$outfile]"; continue ; } ; } + if [ "${a: -1: 1}" == "/" ]; then + create_dir "$outfile" || return 1 + continue + else create_dir "$outdir" || return 1 + fi + unzip -qoj "$zip" "$a" -d "$outdir" + if [ -f "$outfile" ]; then set_perm2 $___uid $___gid $___mof "$outfile" + else return=1 && continue + fi + elif [ -n "$1" ]; then + [ "${a: -1: 1}" == "/" ] && continue + unzip -qp "$zip" "$a" || return=1 + else return=1 && break + fi + done <<< "$list" + return $return +} + +getblocks() { + local seen_blocks + declare -A seen_blocks + find /dev/block/platform/*/by-name /dev/block/by-name /dev/block/bootdevice /dev/block/mapper \ + -mindepth 1 -maxdepth 1 \( -type b -o -type c -o -type l \) 2>/dev/null | sort | while read -r block; do + result=$(readlink -f "$block") + if [[ -n "$result" && -z "${seen_blocks[$result]}" ]]; then + seen_blocks["$result"]=1 + echo "$(basename "$block")=$result" + fi + done +} + +fprint() { + local line + while IFS='' read -r line || [[ -n "$line" ]]; do + ui_print "$line"; + done < "$1"; +} + +fprint_zip() { + package_extract_file "$1" "$TMP/$1" || return 1 + fprint "$TMP/$1" + rm -f "$TMP/$1" +} + +fprint_addon() { + fprint "$addons/$1" +} + +update() { + local bs restore xz gz ro sparse orig dest tmp="$TMP" + restore=() + while [ $# -gt 0 ]; do + case $1 in + -xz) + xz=true + shift; + ;; + -gz) + gz=true + shift; + ;; + -sparse) + sparse=true + shift; + ;; + -tmp) + create_dir "$2" || return 1 + tmp="$2" + shift 2; + ;; + *) + restore+=("$1") + shift; + ;; + esac + done + set -- "${restore[@]}" + orig="$1"; dest="$2" + is_number "${extraction_speed/M}" && bs=$extraction_speed || bs=4M + if exist "$dest"; then + blockdev --setrw "$dest" 2>/dev/null + ro=$(blockdev --getro "$dest" 2>/dev/null) + if [[ "$ro" == "1" ]]; then + echo2 "FATAL ERROR: Read/Only: $dest" + if [[ "$3" == "1" ]]; then abort; else return 1; fi + elif [[ "$ro" != "0" ]]; then + echo2 "Cant get state: $dest" + echo2 "Skipping: Writing in: $dest" + fi + else create_dir "$(dirname "$dest")" + fi + if defined sparse; then + if [ -f "$(which simg2img)" ]; then + dest="$tmp/$(basename "$orig").tmp" + else + echo2 "update: Please import simg2img to perform Sparse IMG installation" && return 1 + fi + fi + if defined xz; then + if fxz --threads=0 -dc "$orig" > "$dest"; then + if undefined sparse; then + echo2 " " + echo2 "-- Updated:xz: $(basename "$orig")" + echo2 " " + return 0 + else orig="$dest" + fi + else + echo2 " " + echo2 "-- Cant update:xz: $(basename "$orig")" + echo2 " " + if [[ "$3" == "1" ]]; then abort; else return 1; fi + fi + elif defined gz; then + if gunzip -c "$orig" > "$dest"; then + if undefined sparse; then + echo2 " " + echo2 "-- Updated:gz: $(basename "$orig")" + echo2 " " + return 0 + else orig="$dest" + fi + else + echo2 " " + echo2 "-- Cant update:gz: $(basename "$orig")" + echo2 " " + if [[ "$3" == "1" ]]; then abort; else return 1; fi + fi + fi + if defined sparse || magic_file -t sparse "$orig" 2>/dev/null; then + if [ -f "$(which simg2img)" ]; then + if simg2img "$orig" "$2"; then + echo2 " " + echo2 "-- Updated:sparse: $(basename "$orig")" + echo2 " " + [[ "$orig" != "$1" ]] && rm -f "$orig" + return 0 + else + echo2 " " + echo2 "-- Cant update:sparse: $(basename "$orig")" + echo2 " " + if [[ "$3" == "1" ]]; then abort; else return 1; fi + fi + else + echo2 "update: Please import simg2img to perform Sparse IMG installation" && return 1 + fi + fi + if dd if="$orig" of="$dest" bs=$bs; then + echo2 " " + echo2 "-- Updated:1: $(basename "$orig")" + echo2 " " + else + echo2 " " + echo2 "-- Cant update: $(basename "$orig")" + echo2 " " + if [[ "$3" == "1" ]]; then abort; else return 1; fi + fi +} + +update_addon() { + local restore=() flags=() + while [ $# -gt 0 ]; do + case $1 in + -xz|-gz|-sparse) + flags+=("$1") + shift + ;; + -tmp) + flags+=("$1" "$2") + shift 2; + ;; + *) + restore+=("$1") + shift + ;; + esac + done + set -- "${restore[@]}" + update "${flags[@]}" "$addons/$1" "$2" "$3" +} + +update_zip() { + local restore=() flags=() orig dest extract=false tmp="$TMP" result + while [ $# -gt 0 ]; do + case $1 in + -xz|-gz|-sparse) + flags+=("$1") + extract=true + shift + ;; + -tmp) + flags+=("$1" "$2") + create_dir "$2" || return 1 + tmp="$2" + shift 2; + ;; + *) + restore+=("$1") + shift + ;; + esac + done + set -- "${restore[@]}" + orig="$1"; dest="$2" + check=$(zip_list_file "$installzip" "$1" | wc -l) + if [ $check != 1 ]; then echo2 "ZIP: Cant find file [$1]" && return 1; fi + if $extract; then + start_tmp "$tmp" + dest="$TMP2/$(basename "$orig").tmp" + if unzip -qp "$installzip" "$orig" > "$dest"; then + update "${flags[@]}" "$dest" "$2" "$3" + result=$? + else + echo2 " " + echo2 "-- Cant update: $(basename "$orig")" + echo2 " " + result=1 + fi + end_tmp + [[ $result != 0 && "$3" == "1" ]] && abort + return $result + else + if exist "$dest"; then + blockdev --setrw "$dest" 2>/dev/null + ro=$(blockdev --getro "$dest" 2>/dev/null) + if [[ "$ro" == "1" ]]; then + echo2 "FATAL ERROR: Read/Only: $dest" + if [[ "$3" == "1" ]]; then abort; else return 1; fi + elif [[ "$ro" != "0" ]]; then + echo2 "Cant get state: $dest" + echo2 "Skipping: Writing in: $dest" + fi + else create_dir "$(dirname "$dest")" + fi + if unzip -qp "$installzip" "$orig" > "$dest"; then + echo2 " " + echo2 "-- Updated:1: $(basename "$orig")" + echo2 " " + return 0 + else + echo2 " " + echo2 "-- Cant update: $(basename "$orig")" + echo2 " " + if [[ "$3" == "1" ]]; then abort; else return 1; fi + fi + fi +} + +get_file_prop() { + [ $# -lt 2 ] && return 1 + awk -F= -v p="${!#}" ' + BEGIN { f=0 } + { + if ($1 == p) { + print substr($0, length($1) + 2); f=1 + exit + } + } + END { exit (f)?0:1 } + ' "${@:1:$#-1}" +} + +map_file_props() { + [ $# -lt 3 ] && return 1 + local ___v + while [ $# -gt 0 ]; do + case $1 in + -f|-files) + shift + while [ $# -gt 0 -a "$1" != -p -a "$1" != -props ]; do + files+=("$1") + shift + done + ;; + -p|-props) + props=("${@:2}") + break + ;; + -v|-var) + ___v="$2" + shift 2 + ;; + *) + ___v="$1" + files=("$2") + props=("${@:3}") + break + ;; + esac + done + mapfile -t $___v < <(awk -F= -v p="$(str_join "|" "${props[@]}")" ' + BEGIN { + f=0 + len = split(p, a, "|") + for (i=1; i<=len; i++) { + r[i]="" + m[a[i]]=i + seen[a[i]]=0 + } + } + { + k = $1 + if (k in m && seen[k] == 0) { + r[m[k]]=substr($0, length($1) + 2) + f++ + seen[k] = 1 + if (f==len) exit + } + } + END { + for (i=1; i<=len; i++) { + print r[i] + } + exit (f==len)?0:1 + } + ' "${files[@]}" + ) +} + +set_progress() { [ -e "$OUTFD" ] && echo "set_progress $1" >> $OUTFD; } + +ui_print() { + local ___ line + for ___; do + $___PRINT && { + while IFS=$'\n' read -r line; do + printf "ui_print %s\nui_print\n" "$line" >> $OUTFD + done <<< "$___" + } || echo "$___" + done +} + +ch_con() { + local ___ con return=0 + if [ -n "$1" ]; then is_substring u:object "$1" && con=$1 || con=u:object_r:$1:s0; else return 1; fi + shift + for ___; do + [[ "$con" == "$(get_context "$___")" ]] && continue + testrw "$(dirname "$___")" || return 1 + echo2 "set_context: $con in $___" + chcon -h $con "$___" 2>/dev/null || chcon $con "$___" + done + return $return +} + +ch_con_recursive() { + local ___ dcon fcon file folder s=0 f d + while [ $# -gt 0 ]; do + case $1 in + -f|-file) + f=true + shift + ;; + -d|-dir) + d=true + shift + ;; + *) + break + ;; + esac + done + if [ -n "$d" ]; then is_substring u:object "$1" && dcon=$1 || dcon=u:object_r:$1:s0; s=1; fi + if [ -n "$f" ]; then is_substring u:object "$1" && fcon=$1 || fcon=u:object_r:$1:s0; [ "$s" == 0 ] && s=1; fi + if [ -z "$f" -a -z "$d" ]; then + is_substring u:object "$1" && dcon=$1 || dcon=u:object_r:$1:s0; s=$((s+1)) + is_substring u:object "$2" && fcon=$2 || fcon=u:object_r:$2:s0; s=$((s+1)) + fi + [ $s == 0 ] && return 1 + shift $s + for ___; do + if [ ! -d "$___" ]; then echo2 "CANT FIND: $___" && continue; fi + testrw "$___" || return 1 + [ -n "$dcon" ] && find "$___" -type d | while read folder; do ch_con $dcon "$folder"; done + [ -n "$fcon" ] && find "$___" -type f -o -type l | while read file; do ch_con $fcon "$file"; done + done; +} + +set_perm() { + local ___ uid gid mod + [ $# -lt 4 ] && return 1 + uid=$1; gid=$2; mod=$3 + shift 3 + for ___; do + testrw "$(dirname "$___")" || return 1 + chown "$uid:$gid" "$___" 2>/dev/null || chown "$uid.$gid" "$___" + chmod "$mod" "$___" + done; +} + +set_perm2() { + #Ensure this format + local ___ uid gid mod + [ $# -lt 4 ] && return 1 + uid=$1; gid=$2; mod=$3 + shift 3 + for ___; do + testrw "$(dirname "$___")" || return 1 + chown "$uid:$gid" "$___" 2>/dev/null || chown "$uid.$gid" "$___" + chmod "$mod" "$___" + done; +} + +set_perm_recursive() { + local ___ uid gid dmod fmod + [ $# -lt 5 ] && return 1 + uid=$1; gid=$2; dmod=$3; fmod=$4 + shift 4 + for ___; do + if [ ! -d "$___" ]; then echo2 "CANT FIND: $___" && continue; fi + testrw "$___" || return 1 + chown -R $uid:$gid "$___" 2>/dev/null || chown -R $uid.$gid "$___" + chmod -R $fmod "$___" + find "$___" -type l -exec chmod $fmod {} +; + find "$___" -type d -exec chmod $dmod {} +; + done +} + +set_perm_recursive2() { + #Ensure this format + local ___ uid gid dmod fmod + [ $# -lt 5 ] && return 1 + uid=$1; gid=$2; dmod=$3; fmod=$4 + shift 4 + for ___; do + if [ ! -d "$___" ]; then echo2 "CANT FIND: $___" && continue; fi + testrw "$___" || return 1 + chown -R $uid:$gid "$___" 2>/dev/null || chown -R $uid.$gid "$___" + chmod -R $fmod "$___" + find "$___" -type l -exec chmod $fmod {} +; + find "$___" -type d -exec chmod $dmod {} +; + done +} + +saveperm() { + local huh perm list="$TMP/perm_list_001.txt" + [ ! -f "$list" ] && echo > "$list" + while [ "$1" ]; do + if [ -d "$1" ]; then + echo2 "saving permissions recursively: $1" + find -L "$1" -mindepth 1 | while read huh; do + if ! grep -Fq "$huh=" "$list"; then + perm=$(get_all_perm "$huh") + if [ -n "$perm" ]; then + echo "$huh=$perm" >> "$list" + else echo2 "saveperm: CANT get perm of $huh" + fi + fi + done + elif [ -f "$1" ]; then + echo2 "saving permissions: $1" + if ! grep -Fq "$1=" "$list"; then + perm=$(get_all_perm "$1") + if [ -n "$perm" ]; then + echo "$1=$perm" >> "$list" + else echo2 "saveperm: CANT get perm of $1" + fi + fi + fi + shift + done +} + +restoreperm() { + local huh perm list="$TMP/perm_list_001.txt" i=0 flag + while [ $((i+=1)) -lt $# ]; do + flag="$1" + case $flag in + -file|-f) + list="$2" + shift 2; + ;; + *) + set -- "$@" "$1" + shift + ;; + esac + done + while [ "$1" ]; do + if [ -d "$1" ]; then + find -L "$1" -mindepth 1 | while read huh; do + perm=$(get_file_prop "$list" "$huh" 2>/dev/null) + if [ -n "$perm" ]; then + [[ "$perm" == "$(get_all_perm "$huh")" ]] && continue + echo2 "set_perm: $perm in $huh" + set_perm2 $perm "$huh" || return 1 + else echo2 "set_perm: There is no saved permission for $huh" + fi + done + elif [ -f "$1" ]; then + perm=$(get_file_prop "$list" "$1" 2>/dev/null) + if [ -n "$perm" ]; then + [[ "$perm" == "$(get_all_perm "$1")" ]] && shift && continue + echo2 "set_perm: $perm in $1" + set_perm2 $perm "$1" || return 1 + else echo2 "set_perm: There is no saved permission for $1" + fi + fi + shift + done +} + +copy_perm_list() { + list="$TMP/perm_list_001.txt" + [ -z "$1" ] && return 1 + [ -f "$list" ] && copy "$list" "$1" || return 1 +} + +savecontext() { + local huh context list="$TMP/context_list_001.txt" + [ ! -f "$list" ] && echo > "$list" + while [ "$1" ]; do + if [ -d "$1" ]; then + echo2 "saving contexts recursively: $1" + find -L "$1" -mindepth 1 | while read huh; do + if ! grep -Fq "$huh=" "$list"; then + context=$(get_context "$huh") + if [ -n "$context" ]; then + echo "$huh=$context" >> "$list" + else echo2 "savecontext: CANT get context of $huh" + fi + fi + done + elif [ -f "$1" ]; then + echo2 "saving context: $1" + if ! grep -Fq "$1=" "$list"; then + context=$(get_context "$1") + if [ -n "$context" ]; then + echo "$1=$context" >> "$list" + else echo2 "savecontext: CANT get context of $1" + fi + fi + fi + shift + done +} + +restorecontext() { + local huh context list="$TMP/context_list_001.txt" i=0 flag + while [ $((i+=1)) -lt $# ]; do + flag="$1" + case $flag in + -file|-f) + list="$2" + shift 2; + ;; + *) + set -- "$@" "$1" + shift + ;; + esac + done + while [ "$1" ]; do + if [ -d "$1" ]; then + find -L "$1" -mindepth 1 | while read huh; do + context=$(get_file_prop "$list" "$huh" 2>/dev/null) + if [ -n "$context" ]; then + ch_con $context "$huh" || return 1 + else echo2 "set_context: There is no saved context for $huh" + fi + done + elif [ -f "$1" ]; then + context=$(get_file_prop "$list" "$1" 2>/dev/null) + if [ -n "$context" ]; then + ch_con $context "$1" || return 1 + else echo2 "set_context: There is no saved context for $1" + fi + fi + shift + done +} + +copy_context_list() { + list="$TMP/context_list_001.txt" + [ -z "$1" ] && return 1 + [ -f "$list" ] && copy "$list" "$1" || return 1 +} + +setup_mountpoint() { + test -L "$1" && mv -f "$1" "${1}_link" + if [ ! -d "$1" ]; then + rm -f "$1" + mkdir -p "$1" + fi +} + +is_same_mount() { + local try try2 + is_mounted "$1" && try=$(grep " $(readlink -f "$1") " /proc/mounts | awk '{print $1}' 2>/dev/null) + is_mounted "$2" && try2=$(grep " $(readlink -f "$2") " /proc/mounts | awk '{print $1}' 2>/dev/null) + [ -z "$try" ] && try=$(df -Pk "$1" | sed "1d" | awk '{print $1}' 2>/dev/null) + [ -z "$try2" ] && try2=$(df -Pk "$2" | sed "1d" | awk '{print $1}' 2>/dev/null) + defined try try2 || return 1 + if [[ "$try" == "$try2" ]]; then echo2 "[$1:$try] == [$2:$try2]" && return 0; else echo2 "[$1:$try] != [$2:$try2]" && return 1; fi +} + +remove_tmp() { + find "$TMP" -mindepth 1 ! -name "*.log" -delete > /dev/null +} + +losetup_a() { + local dev loop_device backing_file + for dev in /sys/block/loop*; do + if [ -d "$dev" ]; then + loop_device=$(basename "$dev") + if [ -e "/dev/block/$loop_device" -a -f "/sys/block/$loop_device/loop/backing_file" ]; then + backing_file=$(cat "/sys/block/$loop_device/loop/backing_file") + echo "/dev/block/$loop_device:$backing_file" + fi + fi + done +} + +loop_setup() { + loopdev= + local loop minorx=1 num=0 loops=$(losetup_a) + [ -e /dev/block/loop1 ] && minorx=$(stat -Lc '%T' /dev/block/loop1) + while [ $num -lt 64 ]; do + loop=/dev/block/loop$num + if { [ ! -e $loop ] && mknod $loop b 7 $((num * minorx)) ; } || ! echo "$loops" | grep -q $loop; then + if losetup $loop "$1" 2>/dev/null; then + loopdev=$loop + break + fi + fi + ((num++)) + done +} + +mount_apex() { + $BOOTMODE || [ ! -d /system/apex ] && return + local apex dest linkerconfig pkg post_apex imported + local list="$___apex_list" + rm -f "$list" + setup_mountpoint /apex + mount -t tmpfs tmpfs /apex -o mode=755 + for apex in /system/apex/* /system_ext/apex/*; do + [ ! -e "$apex" ] && continue + pkg=$(apex_pkg "$apex") + if is_substring ".vndk." "$pkg"; then post_apex+="${apex}:" && continue; fi + if ! grep -Eq "^$pkg=" "$list" 2>/dev/null; then apex_mount "$apex"; fi + done + export ANDROID_RUNTIME_ROOT=$(find /apex -type d -name "com.android.runtime*") + export ANDROID_ART_ROOT=$(find /apex -type d -name "com.android.art*") + export ANDROID_TZDATA_ROOT=$(find /apex -type d -name "com.android.tzdata*") + export ANDROID_I18N_ROOT=$(find /apex -type d -name "com.android.i18n*") + export ANDROID_DATA="$TMP/dalvik-cache" + local APEXJARS=$(find /apex -name '*.jar' | sort | tr '\n' ':') + local FWK=/system/framework + rm -rf "$ANDROID_DATA" + mkdir -p "$ANDROID_DATA" + export DEX2OATBOOTCLASSPATH=${APEXJARS} + export BOOTCLASSPATH=${APEXJARS}\ +$FWK/framework.jar:$FWK/ext.jar:$FWK/telephony-common.jar:\ +$FWK/voip-common.jar:$FWK/ims-common.jar:$FWK/telephony-ext.jar + #Try mount/generate linkerconfig + if can_run linkerconfig; then linkerconfig=linkerconfig + elif can_run $ANDROID_RUNTIME_ROOT/bin/linkerconfig; then linkerconfig=$ANDROID_RUNTIME_ROOT/bin/linkerconfig + elif can_run /system/bin/bootstrap/linkerconfig; then linkerconfig=/system/bin/bootstrap/linkerconfig + else echo2 "DI: No linkerconfig" + fi + if defined linkerconfig; then + genre_apex_list || echo2 "CANT GENERATE: apex-info-list.xml" + echo2 " Generating: /linkerconfig" + setup_mountpoint /linkerconfig + mount -t tmpfs tmpfs /linkerconfig -o mode=755 + $linkerconfig --target /linkerconfig + linkerconfig=$(find /linkerconfig -name '*.txt' | sort | tr '\n' ':') + fi + #Importing APEX binaries + for bin in /apex/*/bin/*; do + [ ! -e "$bin" ] && continue + if [ ! -e "$l/$(basename "$bin")" ]; then ln -sf "$bin" "$l/$(basename "$bin")"; [ -n "$imported" ] && imported+=":"; imported+="$bin"; fi + done + #Mount post-apex + split_string : "$post_apex" | while read post; do + [ -z "$post" ] && break + apex_mount "$post" + done + export ___APEX_MOUNT_PASS=true + echo2 "------------APEX INFO------------" + for dest in ANDROID_RUNTIME_ROOT ANDROID_ART_ROOT \ + ANDROID_TZDATA_ROOT ANDROID_I18N_ROOT ANDROID_DATA \ + BOOTCLASSPATH DEX2OATBOOTCLASSPATH linkerconfig imported + do + if defined $dest; then echo2 "$dest=$(checkvar $dest)"; fi + done + echo2 "-----------------------------------" +} + +apex_mount(){ + local a="$1" d loopdev; + if [ -f "$a" ]; then + d=$(apex_pkg "$a"); + [ -z "$d" ] && return 1; + d=/apex/$d; + [ -d "$d" ] || mkdir -p "$d"; + { unzip -l "$a" original_apex | grep -q original_apex && unzip -p "$a" original_apex | unzip -qo - apex_payload.img -d /apex || unzip -qo "$a" apex_payload.img -d /apex ; } 2>/dev/null; + if [ ! -f /apex/apex_payload.img ]; then + is_mounted "$d" || rm -rf "$d"; + return 1; + fi; + loop_setup /apex/apex_payload.img; + [ -n "$loopdev" ]&&{ + echo "$(basename $d)=$a" >> "$___apex_list"; + echo2 " Mounting: $loopdev -> $d"; + mount -t ext4 -o ro,noatime $loopdev $d; + }; + rm -f /apex/apex_payload.img; + elif [ -d "$a" ]; then + d=$(apex_pkg "$a"); + [ -z "$d" ] && return 1; + d=/apex/$d; + [ -d "$d" ] || mkdir -p "$d"; + echo "$(basename $d)=$a" >> "$___apex_list"; + echo2 " Mounting: $d"; + mount -o bind "$a" $d; + fi; +} + +umount_apex() { + test -d /apex || return + local dest loop + local list="$___apex_list" + for dest in $(cat "$list" | cut -d'=' -f1); do + dest=/apex/$dest + if [ -d $dest ]; then + loop=$(mount | grep $dest | cut -d" " -f1) + echo2 " Unmounting $dest " + umount -ld $dest + losetup -d $loop 2>/dev/null + fi + done + echo2 " Unmounting /apex " + umount -l /apex + echo2 " Unmounting /linkerconfig " + umount -l /linkerconfig 2>/dev/null + for dest in ANDROID_RUNTIME_ROOT ANDROID_ART_ROOT \ + ANDROID_TZDATA_ROOT ANDROID_I18N_ROOT ANDROID_DATA \ + BOOTCLASSPATH DEX2OATBOOTCLASSPATH + do + unset $dest + done + export ___APEX_MOUNT_PASS=false +} + +genre_apex_list() { + local module_path name apex list count=0 + local list2="$___apex_list" + [ -z "$1" ] && list=/apex/apex-info-list.xml || list="$1" + echo '' > "$list" + echo '' >> "$list" + for apex in $(cat "$list2" | cut -d'=' -f1); do + apex=/apex/$apex + if [ -d $apex ]; then + name=$(basename $apex) + module_path=$(get_file_prop "$list2" $name) + if [ -e "$module_path" ]; then + count=$(( $count + 1)) + echo " " >> "$list" + fi + fi + done + echo '' >> "$list" + if [[ -f "$list" && $count != 0 ]]; then return 0; else rm -f "$list" && return 1; fi +} + +unlock_all() { + #superrepack by @munjeni + #unlock_all for Dynamic Installer by @BlassGO + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + local super try=5 count=0 return=0 space current result + local log=/data/unlock_all.log + local log2=/data/local/tmp/script.log + space="/data/superrepack" + current=${PWD} + [ ! -e "$1" ] && super=$(find_block -e super) || super="$1" + [ -z "$super" ] && echo2 "unlock_all: CANT find super partition " && return 1 + rm -rf "$space" + rm -f "$log" + mkdir -p "$space" + if ! exist folder "$space"; then echo2 "unlock_all: CANT make $space" && return 1; fi + cd "$space" + startlog "$log" + echolog '>> Unlock_ALL On-Fly 1.0.0' + echo2 " " + echolog " -- Converting $super to RW..." + echolog " " + while true; do + result= + sleep 1 + [[ "$count" == "$try" ]] && echolog "FATAL ERROR: CANT ENSURE $super as RW with $count attemps" && return=1 && break + echolog "unlock_all:Attempt: $count" + count=$(($count + 1)) + superrepack "$super" >> "$log" 2>&1 + result=$? + savelog "----------------script.log----------------" + if exist "$log2"; then cat "$log2" >> "$log"; fi + if [[ "$result" == "0" ]]; then + if grep -q "Could not allocate block" "$log2"; then continue; else break; fi + fi + done + cd "$current" + rm -rf "$space" + return $return +} + +unlock() { + #superrepack by @munjeni + #unlock for Dynamic Installer by @BlassGO + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + local super try=5 count=0 return=0 space current result + local log=/data/unlock.log + local log2=/data/local/tmp/script.log + space="/data/superrepack" + current=${PWD} + [ ! -e "$2" ] && super=$(find_block -e super) || super="$2" + [ -z "$1" ] && echo2 "unlock: Undefined subpartition" && return 1 + [ -z "$super" ] && echo2 "unlock: CANT find super partition " && return 1 + rm -rf "$space" + rm -f "$log" + mkdir -p "$space" + if ! exist folder "$space"; then echo2 "super_rw: CANT make $space" && return 1; fi + cd "$space" + startlog "$log" + echolog '>> Unlock On-Fly 1.0.0' + echo2 " " + echolog " -- Converting:$1 in $super to RW..." + echolog " " + while true; do + result= + sleep 1 + [[ "$count" == "$try" ]] && echolog "FATAL ERROR: CANT ENSURE $1 as RW with $count attemps" && return=1 && break + echolog "unlock:Attempt: $count" + count=$(($count + 1)) + superrepack "$super" "$1" >> "$log" 2>&1 + result=$? + savelog "----------------script.log----------------" + if exist "$log2"; then cat "$log2" >> "$log"; fi + if [[ "$result" == "0" ]]; then + if grep -q "Could not allocate block" "$log2"; then continue; else break; fi + fi + done + cd "$current" + rm -rf "$space" + return $return +} + +superrepack() { + local return=0 + ( rm -f "$TMP/vbmeta.img" + write_raw_image "$(find_block -e vbmeta)" "$TMP/vbmeta.img") > /dev/null 2>&1 + if ! can_run superrepack; then echo2 "FATAL ERROR: CANT LOAD superrepack" && return=100; fi + if [ "$return" != 100 ]; then "$l/superrepack" "$@"; return=$?; fi + echo " -- Restoring vbmeta.img" + is_valid "$TMP/vbmeta.img" && write_raw_image "$TMP/vbmeta.img" "$(find_block vbmeta)" > /dev/null 2>&1 + return $return +} + +get_virtual_points() { + local LOOP point try super + super=$(find_block -e super) + if undefined super && [ -z "$1" ]; then echo2 "CANT FIND SUPER PARTITION" && return 1 + elif [ -n "$1" ] && ! checksuper "$1"; then echo2 "FATAL ERROR: Invalid SUPER: $1" && return 1 + fi + [ -n "$1" ] && super="$1" + echo2 '-- Loading virtual points... ' + for point in system vendor product odm system_ext; do + try="$point$slot" + start_loop "$try" "$super" 2>/dev/null + if defined LOOP; then ___all_looped_loop+=("$LOOP") && echo2 "$point=$LOOP" && setdefault "$point" "$LOOP"; else echo2 "NO POINT: $point "; fi + done +} + +get_size_ext4() { string inside ',' 'bytes' "$(fdisk -l "$1" | grep -m1 "Disk")" | tr -d " "; } + +get_offset() { + #get_offset "partition name inside SUPER" "SUPER partition/image" + local head offset + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + [[ -z "$1" || -z "$2" ]] && return 1 + head=$(string -r -p "$1" complete_extract "partition_" "Partition:" "$(superrepack "$2" noneeded 2>/dev/null)") + offset=$(string inside "offset =" ')' "$(echo "$head" | grep "dumping offset")" | tr -d " ") + offset=$(printf "%d\n" "$offset" 2>/dev/null || echo FAILED) + if ! is_substring "FAILED" "$offset"; then echo "$offset" && return 0; else return 1; fi +} + +get_size() { + #get_size "partition name inside SUPER" "SUPER partition/image" + local head size + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + [[ -z "$1" || -z "$2" ]] && return 1 + head=$(string -r -p "$1" complete_extract "partition_" "Partition:" "$(superrepack "$2" noneeded 2>/dev/null)") + size=$(string inside '(' 'bytes total' "$(echo "$head" | grep "bytes total")" | tr -d " ") + size=$(printf "%d\n" "$size" 2>/dev/null || echo FAILED) + if ! is_substring "FAILED" "$size"; then echo "$size" && return 0; else return 1; fi +} + +get_total_size() { + #get_total_size "SUPER partition/image" + local head size bytes total=0 error=0 + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + [ ! -e "$1" ] && return 1 + while read bytes; do + undefined bytes && break + size=$(string inside '(' 'bytes total' "$bytes" | tr -d " ") + size=$(printf "%d\n" "$size" 2>/dev/null || echo FAILED) + if ! is_substring "FAILED" "$size"; then total=$(calc $total + $size); else error=$(($error + 1)); fi + done <<< $(superrepack "$1" noneeded 2>/dev/null | grep "bytes total") + if [[ "$error" == "0" ]] && is_number "$total" && is_greater "$total" 0; then echo "$total" && return 0; else return 1; fi +} + +get_all_subparts() { + local parts + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + [ ! -e "$1" ] && return 1 + superrepack "$1" noneeded 2>/dev/null | grep -E "partition_[0-9]*_name" | cut -d= -f2 | tr -d " " +} + +get_group() { + #get_group "partition name inside SUPER" "SUPER partition" + local head group + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + [[ -z "$1" || -z "$2" ]] && return 1 + head=$(string -r -p "$1" complete_extract "partition_" "Partition:" "$(superrepack "$2" noneeded 2>/dev/null)") + group=$(echo "$head" | grep "partition_group" | cut -d'=' -f2 | tr -d " ") + if defined group; then echo "$group" && return 0; else return 1; fi +} + +checksuper() { + #checksuper "SUPER partition/image" + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + [ -z "$1" ] && return 1 + if ! is_substring "This is not super image" "$(superrepack "$1" noneeded 2>/dev/null)"; then true; else false; fi +} + +start_loop() { + local loop=$(losetup2 -f) size offset + [ "$(superrepack teSt >/dev/null; echo $?)" == "100" ] && return 1 + if ! can_run losetup2; then echo2 "FATAL ERROR: CANT LOAD losetup2" && return 1; fi + if undefined loop; then echo2 "Cant make loop" && return 1; fi + if ! checksuper "$2"; then echo2 "start_loop: Invalid SUPER: $2" && return 1; fi + size=$(get_size "$1" "$2") + offset=$(get_offset "$1" "$2") + if ! defined size offset || ! is_number "$size" || ! is_number "$offset"; then echo2 "CANT GET INFO FROM: $2" && return 1; fi + losetup2 --offset=$offset --sizelimit=$size $loop "$2" + if losetup2 -a | grep -q "$loop"; then all_loopkskqiiq+=("$loop") && echo2 "$loop" && LOOP="$loop"; else return 1; fi +} + +end_loop() { + local i refresh + LOOP= + [[ -z "${all_loopkskqiiq[${#all_loopkskqiiq[@]} - 1]}" && -z "$1" ]] && return 1 + if [ -z "$1" ]; then + losetup -d "${all_loopkskqiiq[${#all_loopkskqiiq[@]} - 1]}" + unset "all_loopkskqiiq[${#all_loopkskqiiq[@]}-1]" + for i in "${!all_loopkskqiiq[@]}"; do + refresh+=("${all_loopkskqiiq[i]}") + done + all_loopkskqiiq=("${refresh[@]}") + else + losetup -d "$1" + fi +} + +unify_path() { + local check eq1 eq2 only return=0 + [ -d "$1" ] || return 1 + testrw "$2" || return 1 + diff -qr "$1" "$2" | while IFS= read -r check; do + case "$check" in + *" differ"*) + eq1=$(string inside "Files " " and" "$check") + eq2=$(string inside " and " " differ" "$check") + if [ -n "$eq1" -a -n "$eq2" -a -e "$eq1" -a -e "$eq2" ]; then + echo2 "Replacing: $eq1 in $eq2" + if ! copy "$eq1" "$eq2"; then echo2 "FATAL ERROR: Cant unify $eq1" && return=1; fi + fi + ;; + *"Only in $1"*) + only=$(string remove "Only in $1: " "$check") + if [ -n "$only" -a -e "$1/$only" ]; then + echo2 "Adding: $1/$only in $2/$only" + if ! copy "$1/$only" "$2/$only"; then echo2 "FATAL ERROR: Cant unify $1/$only" && return=1; fi + fi + ;; + esac + done + return $return +} + +super_rw() { + #super_rw for Dynamic Installer by @BlassGO + #superunpack by @munjeni + #Inspired on @lebigmac systemrw project + ui_print " " + ui_print " Oops! " + ui_print " super_rw function was discontinued, its effectiveness is not good on all devices" + ui_print " " + return +} + +force_dir() { + local dir free from + for dir in "$@"; do + if [ ! -d "$dir" ]; then + mkdir -p "$dir" 2>/dev/null; + if [ -d "$dir" ]; then set_perm2 $___uid $___gid $___mod "$dir" + else + from="$(dirname "$dir")" + free=$(getfree "$from") + if [ -e "$dir" ]; then + ui_print " " + ui_print "DI: Oops\! Already exist some reference called: $dir" + elif is_number "$free" && ! is_greater "$free" "6144"; then + ui_print " " + ui_print "DI: Oops\! Your free space in \"$from\" is not enough to perform any basic operation" + else + ui_print " " + ui_print "DI: Oops\! Creation of folders is not allowed in \"$from\"" + fi + abort " " " " + fi + fi + done + return 0 +} + +ensure_root() { + local result pass=false opt + [ "$mount_all" = "ro" ] && opt=" -ro " + mount -o rw,remount -t auto / 2>/dev/null + force_dir /system_root + if try_mount $opt -n system /system_root || try_mount $opt -e -n system-verity /system_root; then + testrw /system_root; result=$? + else + ui_print " " + ui_print "DI: Failed to mount /system_root" + ui_print "DI: Aborting..." + ui_print " " + abort " " + fi + if [ $result == 0 ]; then + free_system=$(getfree /system_root) + if is_number "$free_system" && ! is_greater "$free_system" "10485760"; then + echo2 " " + echo2 'DI: The free space in /system_root is very low (Less than or equal to 10MB), I recommend you check the changes that required more free space' + echo2 " " + fi + return 0 + elif [ "$ensure_rw" != "off" -a $result == 2 ]; then + ui_print " " + ui_print 'DI: Read/Only partitions...' + ui_print "DI: Your current SYSTEM are locked..." + ui_print 'DI: You need to use a ROM/GSI that supports mounting as Read/Write' + ui_print " " + abort " " + else + echo2 " " + echo2 'DI: Read/Only partitions...' + echo2 "DI: Your current SYSTEM are locked..." + echo2 'DI: You need to use a ROM/GSI that supports mounting as Read/Write' + echo2 " " + return 1 + fi +} + +truncate_text() { + [ ${#1} -gt $2 ] && printf "%s...\n" "${1:0:$(($2 - 3))}" || printf "%s\n" "$1" +} + +get_terminal_width() { + printf "%d\n" $(stty size 2>/dev/null | { IFS=' '; read -r _ w; [ -n "$w" ] && printf "%d" "$((w-2))" || printf 80; }) +} + +center_line() { + local width="$2" pad + pad=$(( (width - ${#1}) / 2 )) + printf "%*s%s\n" "$pad" "" "$1" +} + +wrap_text() { IFS= fold -s -w "$2" <<< "$1"; } + +__generate_border__() { printf "+%*s+\n" "$1" | tr ' ' '-'; } + +__generate_linebox__() { printf '|%*s|\n' "$1" | tr ' ' '-'; } + +__center_text_in_box__() { + local inner_width padding line + inner_width=$(($2 - 2)) + wrap_text "$1" "$inner_width" | while read -r line; do + padding=$(( (inner_width - ${#line}) / 2 )) + printf "| %*s%s%*s |\n" "$padding" "" "$line" $((inner_width - ${#line} - padding)) "" + done +} + +__generate_table_border__() { + local width + printf "%s" "+" + for width in "$@"; do + printf "%-*s+" "$((width + 2))" | tr ' ' '-' + done +} + +__generate_table_row__() { + local row="$1" widths=("${@:2}") cells cell i=0 + IFS='|' read -ra cells <<< "$row" + printf "%s" "|" + for cell in "${cells[@]}"; do + cell=$(truncate_text "$(echo "$cell" | xargs)" "${widths[i]}") + printf " %-*s |" "${widths[i]}" "$cell" + ((i++)) + done + printf "\n" +} + +__process_table__() { + local width="$2" col_widths=() + local rows row border i len average_width + mapfile -t rows < <(printf "%s\n" "$1" | sed '/^[[:space:]]*$/d') + for row in "${rows[@]}"; do + IFS='|' read -ra cells <<< "$row" + for i in "${!cells[@]}"; do + len=${#cells[i]} + col_widths[i]=$(( col_widths[i] > len ? col_widths[i] : len )) + done + done + [ -n "$width" ] && { + average_width=$(( (width - 3 * ${#col_widths[@]} + 1) / ${#col_widths[@]} )) + for i in "${!col_widths[@]}"; do col_widths[i]="$average_width"; done + } + border=$(__generate_table_border__ "${col_widths[@]}") + printf "%s\n" "$border" + for row in "${rows[@]}"; do + __generate_table_row__ "$row" "${col_widths[@]}" + printf "%s\n" "$border" + done +} + +__print_buffer__() { + local line width="$2" is_box="$3" IFS="$IFS" + [ -n "$4" ] && IFS= + while IFS="$IFS" read -r line; do + [ -z "$line" ] && continue + [ "$is_box" -eq 1 ] && printf "| %-*s |\n" $((width - 2)) "$line" || printf "%s\n" "$line" + done < <(wrap_text "$1" $((width - is_box * 2))) +} + +print() { + local line width="$2" current_width="$2" + local tag stack=() buffer="" in_center=0 in_pre=0 is_box=0 in_table=0 in_ol=0 in_ul=0 index=0 + local closing_tag centered_line + [ -z "$n" ] && local n=$'\n' + [ -z "$width" ] && { width=$(get_terminal_width); current_width="$width"; } + { while IFS= read -r line; do + [ $in_pre -eq 1 ] && { + [[ "$line" == *""* ]] && { + __print_buffer__ "$buffer" "$current_width" "$is_box" 1 + in_pre=0 + buffer="" + } || { + buffer+="$line$n" + } + continue + } + case "$line" in + *""*|*"
"*) + [ -n "$buffer" ] && { + __print_buffer__ "$buffer" "$current_width" "$is_box" + buffer="" + } + tag=$(echo "$line" | sed -n 's/.*<\([^>]*\)>.*/\1/p') + stack+=("$tag") + if [ "$tag" == "box" ]; then + is_box=1 + __generate_border__ "$current_width" + else + is_box=0 + fi + ;; + + *""*|*"
"*) + [ -n "$buffer" ] && { + __print_buffer__ "$buffer" "$current_width" "$is_box" + buffer="" + } + closing_tag=$(echo "$line" | sed -n 's/.*<\/\([^>]*\)>.*/\1/p') + if [ "${stack[-1]}" == "$closing_tag" ]; then + if [ "$closing_tag" == "box" ]; then + __generate_border__ "$current_width" + fi + unset "stack[-1]" + else + printf "ERROR: Mismatched closing tag \n" "$closing_tag" >&2 + return 1 + fi + is_box=0 + ;; + + *""*) + [ -n "$buffer" ] && { + __print_buffer__ "$buffer" "$current_width" "$is_box" + buffer="" + } + [ $is_box -eq 1 ] && __generate_linebox__ "$current_width" + ;; + + *"
"*) + [ -n "$buffer" ] && { + __print_buffer__ "$buffer" "$current_width" "$is_box" + buffer="" + } + in_center=1 + ;; + + *"
"*) + if [ $in_center -eq 1 ]; then + if [ $is_box -eq 1 ]; then + __center_text_in_box__ "$buffer" "$current_width" + else + while read -r centered_line; do + [ -z "$centered_line" ] && continue + center_line "$centered_line" "$width" + done <<< $(wrap_text "$buffer" "$width") + fi + buffer="" + in_center=0 + fi + ;; + + *""*) + [ -n "$buffer" ] && { + __print_buffer__ "$buffer" "$current_width" "$is_box" + buffer="" + } + in_table=1 + ;; + + *""*) + if [ $in_table -eq 1 ]; then + __process_table__ "$buffer" + buffer="" + in_table=0 + fi + ;; + + *""*) + [ -n "$buffer" ] && { + __print_buffer__ "$buffer" "$current_width" "$is_box" + buffer="" + } + in_table=1 + ;; + + *"
"*) + if [ $in_table -eq 1 ]; then + __process_table__ "$buffer" "$current_width" + buffer="" + in_table=0 + fi + ;; + + *"
"*)
+        [ -n "$buffer" ] && {
+          __print_buffer__ "$buffer" "$current_width" "$is_box"
+        }
+        buffer=""
+        in_pre=1
+      ;;
+
+      *"
    "*) + in_ol=1 + index=1 + ;; + + *"
"*) + in_ol=0 + ;; + + *"
    "*) + in_ul=1 + ;; + + *"
"*) + in_ul=0 + ;; + + *"
"*) + buffer+="$n" + ;; + + *) + if [ $in_ol -eq 1 ]; then + buffer+=" $index. ${line#"${line%%[! ]*}"}$n" + index=$((index + 1)) + elif [ $in_ul -eq 1 ]; then + buffer+=" - ${line#"${line%%[! ]*}"}$n" + elif [ -n "$line" ]; then + buffer+="$line$n" + fi + ;; + esac + done <<< "$1" + [ -n "$buffer" ] && __print_buffer__ "$buffer" "$current_width" "$is_box" + } | while IFS= read out; do + $___PRINT && printf "ui_print %s\nui_print\n" "$out" >> $OUTFD || echo "$out" + done +} + +__print_box__() { + # print_box 40 "! Unsupported Device" " » " "$(echo -e "First\nSecond")" + local box_width="$1" message="$2" list_prefix="$3" devices="$4" + local max=$((box_width - ${#list_prefix} - 1)) + local device padding_left padding_right + ui_print "$( + echo "+$(printf '%*s' $box_width | tr ' ' '-')+" + if [ -n "$message" ]; then + message=$(truncate_text "$message" $box_width) + padding_left=$(((box_width - ${#message}) / 2)) + padding_right=$((box_width - ${#message} - padding_left)) + printf "|%*s%-${#message}s%*s|\n" $padding_left "" "$message" $padding_right "" + echo "+$(printf '%*s' $box_width | tr ' ' '-')+" + printf "|%-${box_width}s|\n" "" + fi + + if [ -n "$devices" ]; then + while IFS= read -r device; do + device=$(truncate_text "$device" "$max") + printf "|%s%-${max}s |\n" "$list_prefix" "$device" + done <<< "$devices" + + printf "|%-${box_width}s|\n" "" + fi + printf "|%-${box_width}s|\n" "" + echo "+$(printf '%*s' $box_width | tr ' ' '-')+" + )" +} + +___debug_command() { + local k="$BASH_COMMAND" + k=${k%% *} + k=${k//./_} + [[ "$k" =~ ^[[:alnum:]_]+$ ]] || return 0 + echo "debug: \"$k\"" + [ "${___supported[$k]}" == 1 ] && echo2 owo || echo2 "debug: $k is an external command" +} + +mount_all_old() { + local runtime dest part block bind + if ! is_mounted /data; then + mount /data + ___UMOUNT_DATA=1 + fi + #Ensure root space + mount -o rw,remount -t auto / 2>/dev/null + (mount /cache + mount -o ro -t auto /persist + mount -o ro -t auto /product + mount -o ro -t auto /vendor + mount -o ro -t auto /odm + mount -o ro -t auto /system_ext) 2>/dev/null + setup_mountpoint $ANDROID_ROOT + if ! is_mounted $ANDROID_ROOT; then + mount -o ro -t auto $ANDROID_ROOT 2>/dev/null + fi + case $ANDROID_ROOT in + /system_root) setup_mountpoint /system;; + /system) + if ! is_mounted /system && ! is_mounted /system_root; then + setup_mountpoint /system_root + mount -o ro -t auto /system_root + elif [ -f /system/system/build.prop ]; then + setup_mountpoint /system_root + mount --move /system /system_root + fi + if [ $? != 0 ]; then + umount /system + umount -l /system + if [ "$dynamic_partitions" = "true" ]; then + mount -o ro -t auto /dev/block/mapper/system$slot /system_root + for part in system_ext vendor product odm; do + block=/dev/block/mapper/$part$slot + if [ -e $block ]; then + setup_mountpoint /$part + mount -o ro -t auto $block /$part + fi + done + else + mount -o ro -t auto /dev/block/bootdevice/by-name/system$slot /system_root + fi + fi + ;; + esac + if is_mounted /system_root; then + contains_array "/system_root" "${___all_looped[@]}" || ___all_looped+=("/system_root") + if [ -f /system_root/build.prop ]; then + echo2 " Mounting Bind: /system_root -> /system" + mount -o bind /system_root /system + contains_array "/system" "${___all_binded[@]}" || ___all_binded+=("/system") + else + echo2 " Mounting Bind: /system_root/system -> /system" + mount -o bind /system_root/system /system + contains_array "/system" "${___all_binded[@]}" || ___all_binded+=("/system") + fi + for bind in /system/system_ext /system/product /system/vendor; do + if [ ! -L "$bind" ]; then + echo2 " Mounting Bind: $bind -> /$(basename "$bind")" + setup_mountpoint "/$(basename "$bind")" + mount -o bind "$bind" "/$(basename "$bind")" + contains_array "/$(basename "$bind")" "${___all_binded[@]}" || ___all_binded+=("/$(basename "$bind")") + fi + done + fi + if is_mounted /vendor; then + for bind in /vendor/odm; do + if [ ! -L "$bind" ]; then + echo2 " Mounting Bind: $bind -> /$(basename "$bind")" + setup_mountpoint "/$(basename "$bind")" + mount -o bind "$bind" "/$(basename "$bind")" + contains_array "/$(basename "$bind")" "${___all_binded[@]}" || ___all_binded+=("/$(basename "$bind")") + fi + done + fi +} + +umount_all() { + local part umount post extra result=0 loop restore simple_umt + mount -o rw,remount -t auto / 2>/dev/null + if $BOOTMODE; then + for umount in /system_ext /vendor /product /odm; do + if ! contains_array "$umount" "${___all_umount[@]}"; then ___all_umount+=("$umount"); fi + done + for simple_umt in "${___all_binded[@]}"; do + if is_mounted "$simple_umt"; then + echo2 " Unmounting $simple_umt " + umount -l "$simple_umt" 2>/dev/null + fi + done + for loop in "${___all_looped[@]}"; do + if is_mounted "$loop"; then + echo2 " Unmounting $loop " + umount -ld "$loop" 2>/dev/null + if is_mounted "$loop"; then echo2 " CANT UNMOUNT: $loop"; fi + fi + done + for loop in "${___all_looped_loop[@]}"; do + losetup -d "$loop" 2>/dev/null + done + for restore in "${___all_umount[@]}" "${___all_looped[@]}"; do + if [ -L "${restore}_link" ]; then + rmdir "$restore" + mv -f "${restore}_link" "$restore" + fi + done + for part in "${___all_umount[@]}"; do + if is_mounted "$part"; then + if ! try_mount -ro "$part"; then + for restore in "${___all_mounted_part[@]}"; do + blockdev --setro "$restore" 2>/dev/null + done + if ! try_mount -ro "$part"; then + echo2 " " + echo2 " CANT RESTORE: $part as Read/Only " + echo2 " Oops! It is dangerous! please save your information, you might have a brick after reboot" + echo2 " " + fi + fi + fi + done + else + for umount in /system_ext /system /system_root /cache /persist /vendor /product /odm; do + if ! contains_array "$umount" "${___all_umount[@]}"; then ___all_umount+=("$umount"); fi + done + $___APEX_MOUNT_PASS && umount_apex + for simple_umt in "${___all_binded[@]}"; do + if is_mounted "$simple_umt"; then + echo2 " Unmounting $simple_umt " + umount -l "$simple_umt" 2>/dev/null + fi + done + for part in "${___all_umount[@]}"; do + if is_mounted "$part"; then + echo2 " Unmounting $part " + unmount "$part" || post+=("$part") + fi + done + for loop in "${___all_looped[@]}"; do + if is_mounted "$loop"; then + echo2 " Unmounting $loop " + umount -ld "$loop" 2>/dev/null || post+=("$loop") + fi + done + for loop in "${___all_looped_loop[@]}"; do + losetup -d "$loop" 2>/dev/null + done + for extra in "${post[@]}"; do + unmount "$extra" + if is_mounted "$extra"; then echo2 " CANT UNMOUNT: $extra"; fi + done + for restore in /apex /linkerconfig "${___all_umount[@]}" "${___all_looped[@]}"; do + if [ -L "${restore}_link" ]; then + rmdir "$restore" + mv -f "${restore}_link" "$restore" + fi + done + fi + $BOOTMODE && mount -o ro,remount -t auto / 2>/dev/null + if [ "$___UMOUNT_DATA" ]; then + umount /data + umount -l /data + fi + $BOOTMODE || umount -l /dev/random 2>/dev/null + unset ___all_binded ___all_umount ___all_looped ___all_looped_loop ___all_mounted_part + return $result +} + +destroy_env() { + [ -z "$___LD_PATH" ] && ___LD_PATH="$LD_LIBRARY_PATH" + [ -z "$___LD_PRELOAD" ] && ___LD_PRELOAD="$LD_PRELOAD" + [ -z "$___LD_CONFIG" ] && ___LD_CONFIG="$LD_CONFIG_FILE" + unset LD_LIBRARY_PATH LD_PRELOAD LD_CONFIG_FILE +} + +restore_env() { + [ -n "$___LD_PATH" ] && export LD_LIBRARY_PATH="$___LD_PATH" + [ -n "$___LD_PRELOAD" ] && export LD_PRELOAD="$___LD_PRELOAD" + [ -n "$___LD_CONFIG" ] && export LD_CONFIG_FILE="$___LD_CONFIG" +} + +dynamic_install() { + local f d o + o=$(readlink -f "$1") + [ ! -d "$o" ] && return 1 + while read f; do + d=${f/"$o"/"$2"} + [ -d "$f" ] && { + create_dir "$d" || return 1 + continue + } + inject "$f" "$d" 1 || return 1 + done < <(find -L "$o" -mindepth 1) +} + +inject() { + local f r=0 + [ ! -f "$1" ] && return 1 + [ -n "$3" ] && f="$2" || { + create_dir "$2" || return 1 + f="$2/$(basename "$1")" + } + if install -D "$1" "$f" >/dev/null 2>&1 || cp -prf "$1" "$f"; then + set_perm2 $___uid $___gid $___mof "$f" || { echo2 "inject: Cant set permissions in: $f file"; r=1 ; } + else + echo2 "Cant inject: $f"; r=1 + fi + return $r +} + +auto_mount_partitions() { + local main block extra noexist bind opt + [ "$mount_all" = "ro" ] && opt=" -ro " + if ! $BOOTMODE; then + mount -o bind /dev/urandom /dev/random + umount_all + mount_all_old + fi + if [ "$dynamic_partitions" = "true" ]; then + find /dev/block/mapper -mindepth 1 -maxdepth 1 \( -type b -o -type c -o -type l \) | while read block; do + [ -z "$block" ] && { echo2 "WARNING: CANT FIND /dev/block/mapper partitions" && break ; } + blockdev --setrw "$block" + done + fi + #Ensure root space + ensure_root + if $BOOTMODE; then + if is_mounted /system_root; then + if [ -f /system_root/build.prop ]; then + echo2 " Mounting Bind: /system_root -> /system" + mount -o bind /system_root /system + contains_array "/system" "${___all_binded[@]}" || ___all_binded+=("/system") + else + echo2 " Mounting Bind: /system_root/system -> /system" + mount -o bind /system_root/system /system + contains_array "/system" "${___all_binded[@]}" || ___all_binded+=("/system") + fi + for bind in /system/system_ext /system/product /system/vendor; do + if [ ! -L "$bind" ]; then + echo2 " Mounting Bind: $bind -> /$(basename "$bind")" + mount -o bind "$bind" "/$(basename "$bind")" + contains_array "/$(basename "$bind")" "${___all_binded[@]}" || ___all_binded+=("/$(basename "$bind")") + fi + done + else + echo2 " CANT ENSURE: /system_root " + fi + for extra in system_ext vendor product odm; do + if ! contains_array "/$extra" "${___all_binded[@]}" && exist $(find_block -e $extra); then + try_mount $opt "/$extra" + fi + done + if is_mounted /vendor; then + for bind in /vendor/odm; do + if [ ! -L "$bind" ]; then + echo2 " Mounting Bind: $bind -> /$(basename "$bind")" + setup_mountpoint "/$(basename "$bind")" + mount -o bind "$bind" "/$(basename "$bind")" + contains_array "/$(basename "$bind")" "${___all_binded[@]}" || ___all_binded+=("/$(basename "$bind")") + fi + done + fi + else + for main in system system_ext vendor product odm; do + if ! is_mounted "/$main"; then + if [[ "$main" == "system" ]]; then + ensure_root + if is_mounted /system_root; then + setup_mountpoint /system + if [ -f /system_root/build.prop ]; then + echo2 " Mounting Bind: /system_root -> /system" + mount -o bind /system_root /system + contains_array "/system" "${___all_binded[@]}" || ___all_binded+=("/system") + else + echo2 " Mounting Bind: /system_root/system -> /system" + mount -o bind /system_root/system /system + contains_array "/system" "${___all_binded[@]}" || ___all_binded+=("/system") + fi + for bind in /system/system_ext /system/product /system/vendor; do + if [ ! -L "$bind" ]; then + echo2 " Mounting Bind: $bind -> /$(basename "$bind")" + setup_mountpoint "/$(basename "$bind")" + mount -o bind "$bind" "/$(basename "$bind")" + contains_array "/$(basename "$bind")" "${___all_binded[@]}" || ___all_binded+=("/$(basename "$bind")") + fi + done + else + echo2 " CANT ENSURE: /system_root " + fi + else + if exist $(find_block -e $main); then + contains_array "/$main" "${___all_binded[@]}" || try_mount -remount "/$main" + else + noexist+=("$main") + fi + fi + if contains_array "$main" "${noexist[@]}"; then + echo2 " No block: $main " + elif ! is_mounted "/$main"; then + echo2 " CANT MOUNT: /$main " + fi + else + echo2 " Mounted:main: /$main" + fi + done + if is_mounted /vendor; then + for bind in /vendor/odm; do + if [ ! -L "$bind" ] && ! contains_array "/$(basename "$bind")" "${___all_binded[@]}"; then + echo2 " Mounting Bind: $bind -> /$(basename "$bind")" + setup_mountpoint "/$(basename "$bind")" + mount -o bind "$bind" "/$(basename "$bind")" + ___all_binded+=("/$(basename "$bind")") + fi + done + fi + fi + if [[ -n "$apex_mount" && "$apex_mount" != "off" ]]; then + $BOOTMODE || mount_apex + elif ! $BOOTMODE; then + runtime=$(find /system/apex -name "com.android.runtime*" -print | head -n 1) + [[ -z "$runtime" && -d /system_ext/apex ]] && runtime=$(find /system_ext/apex -name "com.android.runtime*" -print | head -n 1) + if [ -z "$runtime" ]; then echo2 "Warning: Cant find RUNTIME" + else apex_mount "$runtime" + fi + fi + [ -z "$opt" ] && { mount -o rw,remount -t auto /system || mount -o rw,remount -t auto / + mount -o rw,remount -t auto /vendor + mount -o rw,remount -t auto /product + mount -o rw,remount -t auto /odm + mount -o rw,remount -t auto /system_ext + mount -o remount,rw /system_root ; } 2>/dev/null +} +[ -n "$ANDROID_ROOT" ] || ANDROID_ROOT=/system +[ -n "$CUSTOM_SETUP" ] || { [ -z "$installzip" ] && export CUSTOM_SETUP=1 || export CUSTOM_SETUP=0 ; } +[ "$BOOTMODE" == false -a -n "$OUTFD" ] && ___PRINT=true || ___PRINT=false + +main_version=${di_version%%-*} +PS1="\[\e[38;5;110m\]di-\[\e[38;5;222m\]$main_version\[\e[1;31m\]> \[\e[0m\] " +shopt -s expand_aliases +declare -A ___SWITCHS ___SWITCHZIPS ___SWITCHSH + +export ___apex_list="$TMP/apex_list_0001.txt" \ +___APEX_MOUNT_PASS=false \ +yes=chooseport \ +n=' +' +alias not=! \ +loop="while iterate " \ +loop_file="while iterate_file " \ +loop_array="while iterate_array " \ +loop_string="while iterate_string " \ +delete="rm -f " \ +delete_recursive="rm -rf " \ +apk_pkg=apk_package \ +mount_all=auto_mount_partitions \ +file_getprop=get_file_prop \ +read_file=cat \ +greater_than_int=is_greater \ +less_than_int=is_less \ +concat="printf \"%s\" " \ +force_update_file="update_file -force " \ +force_update_file_string="update_file_string -force " \ +force_update_file_addon="update_file_addon -force " \ +force_update_file_zip="update_file_zip -force " \ +tolog=echo2 \ +import=. + +___uid=0; ___gid=0; ___mod=0755; ___mof=0777 +defaultvars="magisk_support ensure_rw import_addons apex_mount overwrite_symlinks devices devices_alert extraction_speed dalvik_memory framework_res off_readonly permissions" + +#Declare native variables about the device +getarch + +#Checking TMP2 (For Dual functions operations) +start_tmp || abort "CANT SETUP: TMP2" +end_tmp || abort "CANT SETUP:2: TMP2" + +#Try using external unzip binary +change_bin -while -p unzip + +#Checking possible exceptions (Read/Only Functions) +[ -n "$installzip" ] && { + #Ensure updater-script + package_extract_file META-INF/com/google/android/updater-script "$TMP/updater-script" || abort "FATAL ERROR: Cant get updater-script" + defaults=$(construct_default "$TMP/updater-script" "$defaultvars") + eval "$defaults" + + #Exceptions + off_readonly=${off_readonly// } + [ -n "$off_readonly" ] && exception="^(${off_readonly//:/|})$" || exception="^$" +} + +[ $CUSTOM_SETUP != 1 ] && { + #Ensuring Dynamic Installer functions + readonly -f $(compgen -A function | grep -Ev "$exception") 2>/dev/null + + #Ensuring Dynamic Installer vars + readonly $(echo -e "TMP\nTMPDIR\nOUTFD\nDNM\naddons\ninstallzip\nl\nn\nyes\n___apex_list" | grep -Ev "$exception") 2>/dev/null +} +unset exception + +[ "$debug" == true ] && { + while IFS= read -r func; do + ___supported["${func//./_}"]=1 + done < <(compgen -A function; find $l -maxdepth 1 -executable -print0 | xargs -0 basename -a | grep -v "\[") + unset func + trap '___debug_command' DEBUG +} + +#Destroy Recovery env +$BOOTMODE || destroy_env + +#Importing APEX binaries +[ "$BOOTMODE" == true -a -d /apex ] && { + for dir in /apex/*/bin; do + [ -d "$dir" ] && PATH="$PATH:$dir" + done + export PATH +} +unset dir + +#Check Custom Mode +[ $CUSTOM_SETUP = 1 ] && { ___uid=0; ___gid=0; ___mod=0755; ___mof=0644; return ; } + +#Pre-setup +echo2 "---------Installer Configs---------- +$defaults +-----------------------------------" +unset defaultvars defaults DEVICE +[[ -n "$devices" && "$devices" != "off" ]] && { + declare -A dmap + dsorted=$(split_string : "$devices" | grep .) + while read device; do + dmap[$device]=1 + done <<< "$dsorted" + mapfile -t dprops < <( + { + getprop ro.product.device + getprop ro.build.product + getprop ro.product.vendor.device + getprop ro.vendor.product.device + get_file_prop /default.prop ro.product.device + getprop ro.product.model + grep_cmdline androidboot.em.model + } 2>/dev/null | sort -bu | grep . + ) + for device in "${dprops[@]}"; do + [[ -v "dmap[$device]" ]] && { + DEVICE="$device" + echo2 "Device checking: [$device] detected!" + break + } + done + [ -z "$DEVICE" ] && { + echo2 "Device checking: [$(str_join : "${dprops[@]}")] not in [$devices]" + [ "$devices_alert" == "on" ] && { + __print_box__ 40 "! Unsupported Device" " » " "$dsorted" + abort + } || { + echo2 "Device checking: Unsupported Device!" + } + } +} +unset device dsorted dmap dprops + +#Ensure/Check new default permissions +setdefault permissions "$permissions" + +#Extracting addons after the device checking pass +package_extract_dir META-INF/addons "$addons" + +#Loading extra.zip (Optional) +[ -f "$addons/extra.zip" ] && { + is_substring $ARCH "$(unzip -l "$addons/extra.zip" "$ARCH/")" && { + mkdir -p "$TMP/extra" + unzip -qoj "$addons/extra.zip" "$ARCH/*" -d "$TMP/extra" + chmod -R 755 "$TMP/extra" + mv -f "$TMP/extra"/* -t "$l" + rm -rf "$TMP/extra" "$l/info.txt" + } || { + echo2 "addons: extra.zip does not include additions to your architecture" + } +} + +#Importing addons(.sh) if needed +[[ "$import_addons" == "on" ]] && { + while read addon; do + echo2 "- Importing \"$(basename "$addon")\"" + . "$addon" + done < <(find "$addons" -mindepth 1 -maxdepth 1 -type f -name "*.sh" | sort) + unset addon +} + +if [[ "$magisk_support" == "off" ]] || [[ "$BOOTMODE" == false && "$magisk_support" != "force" ]]; then +echo2 "----------------Running SCRIPTs------------" +. "$TMP/updater-script" +cd / +remove_tmp +restore_env +echo2 "-------------------------------------------" + +else + +require_new_magisk() { + if ! $BOOTMODE && $encrypted; then + ui_print "*******************************" + ui_print " Please decrypt the device! " + ui_print "*******************************" + else + ui_print "*******************************" + ui_print " Please install Magisk v19.0+! " + ui_print "*******************************" + fi + abort +} + +mount /data 2>/dev/null +if [ $CUSTOM_SETUP != 2 ]; then + [ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk + . /data/adb/magisk/util_functions.sh + [ $MAGISK_VER_CODE -lt 19000 ] && require_new_magisk +fi + +___magisk_print() { +ui_print "---------------------------------------- + >>> Powered by Magisk $MAGISK_VER_CODE +---------------------------------------- + " +} + +is_legacy_script() { + unzip -l "$ZIPFILE" $DNM/install.sh 2>/dev/null | grep -q install.sh + return $? +} + +setup_flashable() { + $BOOTMODE && return + if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then + # We will have to manually find out OUTFD + for FD in `ls /proc/$$/fd`; do + if readlink /proc/$$/fd/$FD | grep -q pipe; then + if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then + OUTFD=$FD + break + fi + fi + done + fi + recovery_actions +} + +ensure_nvbase() { + [ -z "$NVBASE" ] && { + for NVBASE in "$(dirname "$MAGISKBIN")" /data/adb /cache/data_adb; do + [ -d "$NVBASE/magisk" ] && { + MAGISKBIN="$NVBASE/magisk" + break + } + NVBASE= + done + [ -z "$NVBASE" ] && abort " - Invalid Magisk environment!" + } +} + +install_module() { + + cd $TMPDIR + + ensure_nvbase + setup_flashable + mount_partitions + api_level_arch_detect + + $BOOTMODE && boot_actions || recovery_actions + + package_extract_file $DNM/module.prop $TMPDIR/module.prop + [ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!" + + local MODDIRNAME=modules + $BOOTMODE && MODDIRNAME=modules_update + local MODULEROOT=$NVBASE/$MODDIRNAME + MODID=`grep_prop id $TMPDIR/module.prop` + MODNAME=`grep_prop name $TMPDIR/module.prop` + MODAUTH=`grep_prop author $TMPDIR/module.prop` + MODPATH=$MODULEROOT/$MODID + + rm -rf $MODPATH + mkdir -p $MODPATH + + if is_legacy_script; then + package_extract_dir $DNM $TMPDIR + ___magisk_print + . $TMPDIR/install.sh + + # Callbacks + on_install + + [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh + $SKIPMOUNT && touch $MODPATH/skip_mount + $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop + cp -af $TMPDIR/module.prop $MODPATH/module.prop + $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh + $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh + + echo2 "- Setting permissions" + set_permissions + else + ___magisk_print + + package_extract_file $DNM/customize.sh $MODPATH/customize.sh + + ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null && { + package_extract_dir $DNM $MODPATH + + echo2 "- Setting common permissions/contexts" + echo2 " " + set_perm_recursive2 $___uid $___gid $___mod $___mof $MODPATH + ch_con_recursive system_file system_file $MODPATH + for perm in /system/bin /system/xbin /system/system_ext/bin /system/vendor/bin; do + [ -d "$MODPATH$perm" ] && set_perm_recursive2 0 2000 0755 0755 "$MODPATH$perm" + done + [ -d "$MODPATH/system/vendor" ] && ch_con_recursive vendor_file vendor_file "$MODPATH/system/vendor" + unset perm + } + [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh + fi + + for TARGET in $REPLACE; do + echo2 "- Replace target: $TARGET" + mktouch $MODPATH$TARGET/.replace + done + + $BOOTMODE && { + mktouch $NVBASE/modules/$MODID/update + rm -rf $NVBASE/modules/$MODID/{remove,disable} 2>/dev/null + cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop + } + + [ -f $MODPATH/sepolicy.rule ] && { + echo2 "- Installing custom sepolicy rules" + copy_sepolicy_rules + } + + rm -rf \ + $MODPATH/system/placeholder $MODPATH/customize.sh \ + $MODPATH/README.md $MODPATH/.git* + rmdir -p $MODPATH + + cd / + + $BOOTMODE || recovery_cleanup + rm -rf $TMPDIR +} + +install_custom_module() { + [ -z "$MODPATH" ] && abort "! Invalid implementation: MODPATH is not defined" + + if is_legacy_script; then + package_extract_dir $DNM $TMPDIR + . $TMPDIR/install.sh + + # Callbacks + on_install + + [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh + $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop + $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh + $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh + + echo2 "- Setting permissions" && set_permissions + else + package_extract_file $DNM/customize.sh $MODPATH/customize.sh + + { [ -n "$SKIPUNZIP" -a "$SKIPUNZIP" != 1 ] || ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null ; } && { + package_extract_dir $DNM $MODPATH + + echo2 "- Setting common permissions/contexts" + echo2 " " + set_perm_recursive2 $___uid $___gid $___mod $___mof $MODPATH + ch_con_recursive system_file system_file $MODPATH + for perm in /system/bin /system/xbin /system/system_ext/bin /system/vendor/bin; do + [ -d "$MODPATH$perm" ] && set_perm_recursive2 0 2000 0755 0755 "$MODPATH$perm" + done + unset perm + [ -d "$MODPATH/system/vendor" ] && ch_con_recursive vendor_file vendor_file "$MODPATH/system/vendor" + } + [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh + fi + cd / + rm -rf $TMPDIR +} + +if [ $CUSTOM_SETUP = 2 ]; then + echo2 "----------------Running SCRIPTs------------" + install_custom_module + echo2 "-------------------------------------------" +elif [ $MAGISK_VER_CODE -ge 20400 ]; then + # New Magisk have complete installation logic within util_functions.sh + echo2 "----------------Running SCRIPTs------------" + install_module + echo2 "-------------------------------------------" +fi + +restore_env +exit 0 + +fi + diff --git a/META-INF/zbin/setup b/META-INF/zbin/setup index 8525d69..0855d79 100644 --- a/META-INF/zbin/setup +++ b/META-INF/zbin/setup @@ -129,6 +129,7 @@ if [ -f core -a -f static ]; then export apex_mount=on echo " >> Recovery Mode 1.2.0 " } + export framework_res="/system/framework/framework-res.apk" apktool_aapt=off echo " @BlassGO " echo " " @@ -177,4 +178,4 @@ if [ -f core -a -f static ]; then bash --rcfile "$rc" else abort "setup: CANNOT FIND A VALID ENVIRONMENT " -fi +fi \ No newline at end of file diff --git a/META-INF/zbin/version.txt b/META-INF/zbin/version.txt new file mode 100644 index 0000000..dfae4cb --- /dev/null +++ b/META-INF/zbin/version.txt @@ -0,0 +1,4 @@ +Project: Dynamic Installer +Docs: https://blassgo.github.io/DynamicInstaller_Doc +Version: Stable - 5.5 +Author: @BlassGO \ No newline at end of file diff --git a/customize.sh b/customize.sh new file mode 100644 index 0000000..f11631b --- /dev/null +++ b/customize.sh @@ -0,0 +1,40 @@ +#!/sbin/sh +#Require DI v4.8+ + +#--------------READ ME-------------# +# Should I write my module script here? +# - NO, do it in META-INF/com/google/android/magisk/customize.sh +#----------------------------------# + +#-----------SPECIAL VARS-----------# +# To avoid compatibility issues with future implementations +SKIPUNZIP=0 +#----------------------------------# + +# Which variables should be shared with the DI +SHARED_VARS=" + +MODPATH +SKIPUNZIP +SKIPMOUNT +PROPFILE +POSTFSDATA +LATESTARTSERVICE +KSU +KSU_VER +KSU_VER_CODE +KSU_KERNEL_VER_CODE + +" + +#Exporting the SPECIAL VARIABLES ensures that they are also shared with the DI +export SHARED_VARS $SHARED_VARS +#Get update-binary +binary="META-INF/com/google/android/update-binary" +binaryout="$TMPDIR/$binary" +unzip -qo "$ZIPFILE" "$binary" -d "$TMPDIR" +if [ -f "$binaryout" ]; then + . "$binaryout" +else + abort "SETUP: Can't get update-binary" +fi diff --git a/module.prop b/module.prop new file mode 100644 index 0000000..3028c44 --- /dev/null +++ b/module.prop @@ -0,0 +1,7 @@ +id=test-module +name=Test Module +version=v1.0 +versionCode=1 +author=random +description=Module info for Magisk v28+ or KSU +support=none