#!/sbin/sh

# Minimal MicroG installer
# By FriendlyNeighborhoodShane
# Based on work by osm0sis @ xda-developers (Thanks!)
#
# Copyright 2018-2020 FriendlyNeighborhoodShane
# Distributed under the terms of the GNU GPL v3

exec 3>&1;
exec 1>&2;

outfd="/proc/self/fd/$2";
zipfile="$3";

ps | grep zygote | grep -v grep >/dev/null && bootmode=true || bootmode=false;
$bootmode || ps -A 2>/dev/null | grep zygote | grep -v grep >/dev/null && bootmode=true;

if $bootmode; then
  ui_print() {
    echo "$1" >&3;
  };
else
  ui_print() {
    echo "ui_print $1" >> "$outfd";
    echo "ui_print" >> "$outfd";
  };
fi;

log() { echo "$1"; }

$bootmode || mount -o bind /dev/urandom /dev/random;

select_word() {
  select_term="$1";
  while read -r select_line; do
    select_current=0;
    select_found="";
    for select_each in $select_line; do
      select_current="$(( select_current + 1 ))";
      [ "$select_current" = "$select_term" ] && { select_found="yes"; break; }
    done;
    [ "$select_found" = "yes" ] && echo "$select_each";
  done;
}

file_getprop() {
  grep "^$2=" "$1" | head -n1 | select_word 1 | cut -d= -f2;
}

umountparts="";
cleanup() {
  rm -rf "$filedir" "$bbdir";
  $bootmode || {
    for part in $umountparts; do
      umount "$part";
    done;
  }
  sync;
}

abort() {
  ui_print " ";
  ui_print "!!! FATAL ERROR: $1";
  ui_print " ";
  ui_print "Stopping installation and Uninstalling...";
  uninstall_pack;
  cleanup;
  ui_print " ";
  ui_print "Installation failed!";
  ui_print " ";
  exit 1;
}

ismntpoint() {
  mount | grep -q " on $1 ";
}

fstab_getmount() {
  grep -v "^#" /etc/recovery.fstab | grep "[[:blank:]]$1[[:blank:]]" | tail -n1 | tr -s "[:blank:]" " " | cut -d" " -f1;
}

find_block() {
  dynamicpart="$(getprop ro.boot.dynamic_partitions)";
  [ "$dynamicpart" = "true" ] || dynamicpart="false";
  [ "$dynamicpart" = "true" ] && blkpath="/dev/block/mapper" || {
    blkpath="/dev/block/by-name";
    [ -d "$blkpath" ] || blkpath="/dev/block/bootdevice/by-name";
  }
  blkabslot="$(getprop ro.boot.slot_suffix)";
  for name in "$@"; do
    blkmount="$(fstab_getmount "$name")";
    [ "$blkmount" ] && blkmountexists="true" || blkmountexists="false";
    case "$dynamicpart-$blkmountexists" in
      true-true)
        blkdev="${blkpath}/${blkmount}${blkabslot}";
      ;;
      false-true)
        blkdev="${blkmount}${blkabslot}";
      ;;
      true-false|false-false)
        blkdev="${blkpath}/${name}${blkabslot}";
      ;;
    esac;
    [ -b "$blkdev" ] && {
      echo "$blkdev";
      return;
    }
  done;
}

ui_print " ";
ui_print "--         Minimal MicroG Installer         --";
ui_print "--     The Essentials only MicroG pack      --";
modname="MinMicroG";

log " ";
log "Zip File is $zipfile";
log "Bootmode is $bootmode";

$bootmode || {
  log " ";
  log "Mounting early";
  for part in "/system" "/system_root"; do
    [ -e "$part" ] || continue;
    mount -o ro "$part";
    log "Mountpoint $part mounted (auto)";
    umountparts="$umountparts $part";
  done;
  sysblk="$(find_block /system_root /system /)";
  [ "$sysblk" ] && {
    for part in "/mnt/system"; do
      mkdir -p "$part";
      mount -o ro "$sysblk" "$part";
      umountparts="$umountparts $part";
      log "Mountpoint $part mounted (manual $sysblk)";
    done;
  }
  mount /data;
  umountparts="$umountparts /data";
}
[ -e "/system/build.prop" ] && { sysroot="/"; sysrootpart="/system"; };
[ -e "/system/system/build.prop" ] && { sysroot="/system"; sysrootpart="/system"; };
[ -e "/system_root/system/build.prop" ] && { sysroot="/system_root"; sysrootpart="/system_root"; };
[ -e "/mnt/system/system/build.prop" ] && { sysroot="/mnt/system"; sysrootpart="/mnt/system"; };
[ -f "$sysroot/system/build.prop" ] || abort "Could not find a ROM!";

bbdir="/tmp/busybox";
$bootmode || {
  for bb in /data/adb/magisk/busybox; do
    [ -f "$bb" ] && magiskbb="$bb";
  done;
  [ "$magiskbb" ] && {
    ui_print " ";
    ui_print "Setting up busybox...";
    mkdir -p "$bbdir";
    "$magiskbb" --install -s "$bbdir/";
    export PATH="$bbdir:$PATH";
    log "Shell path set to $PATH";
  }
}

for bin in chcon chmod chown cp cut df du echo find grep head mkdir mount ps rm sed tail touch tr umount unzip; do
  command -v "$bin" >/dev/null || abort "No $bin available";
done;

$bootmode && filedir="/dev/tmp/$modname" || filedir="/tmp/$modname";
tmplibdir="$filedir/tmplibdir";

$bootmode && forcesys=no;
case "$(basename "$zipfile" | tr 'A-Z' 'a-z')" in
  *system*)
    forcesys=yes;
    ui_print " ";
    ui_print "WARNING: Forcing a system action!";
  ;;
esac;
case "$(basename "$zipfile" | tr 'A-Z' 'a-z')" in
  *uninstall*)
    action=uninstallation;
  ;;
  *)
    action=installation;
  ;;
esac;

abi="$(file_getprop $sysroot/system/build.prop ro.product.cpu.abi)";
case "$abi" in
  arm64*)
    arch=arm64;
    libarches="arm64-v8a armeabi-v7a armeabi";
  ;;
  arm*)
    arch=arm;
    libarches="armeabi-v7a armeabi";
  ;;
  x86_64*)
    arch=x86_64;
    libarches="x86_64 x86 armeabi-v7a armeabi";
  ;;
  x86*)
    arch=x86;
    libarches="x86 armeabi-v7a armeabi";
  ;;
  mips64*)
    arch=mips64;
    libarches="mips64 mips";
  ;;
  mips*)
    arch=mips;
    libarches="mips";
  ;;
  *)
    abort "Could not recognise architecture: $abi";
  ;;
esac;

sdk="$(file_getprop $sysroot/system/build.prop ro.build.version.sdk)";
[ "$sdk" ] || abort "Could not find SDK";
[ "$sdk" -gt 0 ] || abort "Could not recognise SDK: $sdk";

sizecheck() {
  for realsizeobject in $1; do
    sizeobject="$realsizeobject";
    break;
  done;
  [ -e "$sizeobject" ] || { echo 0; return 0; }
  objectsize="$(du -s "$sizeobject" | select_word 1)";
  libsize=0;
  case "$sizeobject" in
    *.apk)
      apkunzip="$(unzip -l "$sizeobject" "lib/*/lib*.so")";
      if echo "$apkunzip" | grep -q "lib/.*/lib.*.so"; then
        for archlib in $libarches; do
          if echo "$apkunzip" | grep -q "lib/$archlib/lib.*.so"; then
            libsizeb=0;
            for entry in $(unzip -l "$sizeobject" | grep "lib/$archlib/lib.*.so" | select_word 1); do
              libsizeb="$(( libsizeb + entry ))";
            done;
            libsize="$(( libsizeb / 1024 + 1 ))";
            break;
          fi;
        done;
      fi;
    ;;
  esac;
  echo "$(( objectsize + libsize ))";
}

remove() {
  removalobject="$1";
  if [ "$sdk" -lt 21 ]; then
    [ "$(basename "$(dirname "$removalobject")").apk" = "$(basename "$removalobject")" ] && {
      removalobject="$(dirname "$(dirname "$removalobject")")/$(basename "$removalobject")";
    }
  fi;
  [ "$(basename "$(dirname "$removalobject")").apk" = "$(basename "$removalobject")" ] && {
    removalobject="$(dirname "$removalobject")";
  }
  [ -e "$removalobject" ] || return 0;
  rm -rf "$removalobject" || { log "ERROR: Could not remove ($removalobject)"; return 1; }
  if [ -e "$removalobject" ]; then
    log "ERROR: Could not remove ($removalobject)";
    return 1;
  else
    log "REMOVER: Object removed ($removalobject)";
  fi;
}

debloat() {
  debloatobject="$1";
  debloatingobject="$2";
  if [ "$sdk" -lt 21 ]; then
    [ "$(basename "$(dirname "$debloatobject")").apk" = "$(basename "$debloatobject")" ] && {
      debloatobject="$(dirname "$(dirname "$debloatobject")")/$(basename "$debloatobject")";
      debloatingobject="$(dirname "$(dirname "$debloatingobject")")/$(basename "$debloatingobject")";
    }
  fi;
  [ "$(basename "$(dirname "$debloatobject")").apk" = "$(basename "$debloatobject")" ] && debloatobject="$(dirname "$debloatobject")";
  [ -e "$debloatobject" ] || return 0;
  mkdir -p "$(dirname "$debloatingobject")";
  if [ "$(basename "$(dirname "$debloatingobject")").apk" = "$(basename "$debloatingobject")" ]; then
    if touch "$(dirname "$debloatingobject")/.replace"; then
      log "DEBLOATER: Object directory debloated ($debloatobject)";
    else
      log "ERROR: Could not create replace file for object $debloatobject";
      return 1;
    fi;
  elif [ -d "$debloatobject" ]; then
    mkdir -p "$debloatingobject";
    if touch "$debloatingobject/.replace"; then
      log "DEBLOATER: directory debloated ($debloatobject)";
    else
      log "ERROR: Could not create replace file for directory $debloatobject";
      return 1;
    fi;
  else
    if echo "# This is a dummy for debloating" > "$debloatingobject"; then
      log "DEBLOATER: Object dummy debloated ($debloatobject)";
    else
      log "ERROR: Could not create dummy file for $debloatobject";
      return 1;
    fi;
  fi;
}

uninstall() {
  uninstallobject="$1";
  if [ "$sdk" -lt 21 ]; then
    [ "$(basename "$(dirname "$uninstallobject")").apk" = "$(basename "$uninstallobject")" ] && uninstallobject="$(dirname "$(dirname "$uninstallobject")")/$(basename "$uninstallobject")";
  fi;
  [ "$(basename "$(dirname "$uninstallobject")").apk" = "$(basename "$uninstallobject")" ] && uninstallobject="$(dirname "$uninstallobject")";
  [ -e "$uninstallobject" ] || return 0;
  rm -rf "$uninstallobject" || {
    log "ERROR: Object not uninstalled ($uninstallobject)";
    return 1;
  }
  if [ -e "$uninstallobject" ]; then
    log "ERROR: Object not uninstalled ($uninstallobject)";
    return 1;
  else
    log "UNINSTALLER: Object uninstalled ($uninstallobject)";
  fi;
}

install_dest() {
  for realobject in $1; do
    object="$realobject";
    break;
  done;
  destobject="$2";
  [ -e "$object" ] || { log "ERROR: Object not found ($object)"; return 1; }
  if [ "$sdk" -lt 21 ]; then
    [ "$(basename "$(dirname "$destobject")").apk" = "$(basename "$destobject")" ] && destobject="$(dirname "$(dirname "$destobject")")/$(basename "$destobject")";
  fi;
  mkdir -p "$(dirname "$destobject")";
  cp -rf "$object" "$destobject" || abort "Could not install $destobject";
  if [ -e "$destobject" ]; then
    log "INSTALLER: Object installed ($object to $destobject)";
  else
    abort "Could not install $destobject";
  fi;
  case "$destobject" in
    *.apk)
      install_lib "$destobject";
    ;;
  esac;
}

install_lib() {
  libobject="$1";
  mkdir -p "$tmplibdir";
  unzipout="$(unzip -l "$libobject" "lib/*/lib*.so")";
  echo "$unzipout" | grep -q "lib/.*/lib.*.so" || return 0;
  for archlib in $libarches; do
    if echo "$unzipout" | grep -q "lib/$archlib/lib.*.so"; then
      case "$archlib" in
        *arm64*)
          log "INSTALLER: Installing arm64 libs ($libobject)";
          libdir=lib64;
          libarch=arm64;
        ;;
        *arm*)
          log "INSTALLER: Installing arm libs ($libobject)";
          libdir=lib;
          libarch=arm;
        ;;
        *x86_64*)
          log "INSTALLER: Installing x86_64 libs ($libobject)";
          libdir=lib64;
          libarch=x86_64;
        ;;
        *x86*)
          log "INSTALLER: Installing x86 libs ($libobject)";
          libdir=lib;
          libarch=x86;
        ;;
        *mips64*)
          log "INSTALLER: Installing mips64 libs ($libobject)";
          libdir=lib64;
          libarch=mips64;
        ;;
        *mips*)
          log "INSTALLER: Installing mips libs ($libobject)";
          libdir=lib;
          libarch=mips;
        ;;
      esac;
      if [ "$sdk" -lt 21 ]; then
        libdest="$(dirname "$(dirname "$libobject")")/$libdir";
      else
        libdest="$(dirname "$libobject")/lib/$libarch";
      fi;
      unzip -oq "$libobject" "lib/$archlib/lib*.so" -d "$tmplibdir";
      mkdir -p "$libdest";
      for lib in "$tmplibdir/lib/$archlib"/lib*.so; do
        cp -rf "$lib" "$libdest/$(basename "$lib")" || abort "Could not Install $lib for $libobject";
        if [ -f "$libdest/$(basename "$lib")" ]; then
          log "INSTALLER: Installed library ($lib to $libdest)";
        else
          abort "Could not Install $lib for $libobject";
        fi;
      done;
      break;
    fi;
  done;
  rm -rf "$tmplibdir";
}

sizecheck_pack() {
  packsize=0;
  for thing in defconf $stuff; do
    [ "$thing" ] && packsize="$(( packsize + $(sizecheck "$filedir/$thing") ))";
  done;
  for thing in $stuff_arch; do
    [ "$thing" ] && packsize="$(( packsize + $(sizecheck "$filedir/$(dirname "$thing")/*-$arch-*/$(basename "$thing")") ))";
  done;
  for thing in $stuff_sdk; do
    [ "$thing" ] && packsize="$(( packsize + $(sizecheck "$filedir/$(dirname "$thing")/*-$sdk-*/$(basename "$thing")") ))";
  done;
  for thing in $stuff_arch_sdk; do
    [ "$thing" ] && packsize="$(( packsize + $(sizecheck "$filedir/$(dirname "$thing")/*-$arch-*-$sdk-*/$(basename "$thing")") ))";
  done;
  echo "$packsize";
}

uninstall_pack() {
  if [ "$magisk" = "yes" ]; then
    rm -rf "$root" || { log " "; log "Could not delete Magisk root ($root)"; }
  elif [ "$magisk" = "no" ]; then
    for thing in $stuff_uninstall; do
      [ "$thing" ] && uninstall "$root/$thing";
    done;
  fi;
}

perm() {
  uid="$1";
  gid="$2";
  dmod="$3";
  fmod="$4";
  permobject="$5";
  [ -e "$permobject" ] || return 0;
  chown -R "$uid:$gid" "$permobject" || chown -R "$uid.$gid" "$permobject";
  find "$permobject" -type d -exec chmod "$dmod" {} +;
  find "$permobject" -type f -exec chmod "$fmod" {} +;
}

rm -rf "$filedir";
mkdir -p "$filedir";
unzip -o "$zipfile" "defconf" -d "$filedir/";
[ -f "$filedir/defconf" ] || abort "Could not find a default config";
. "$filedir/defconf" || abort "Could not execute default config";

moddir="/data/media/0/$modname";

ui_print " ";
ui_print "Package: $variant";
ui_print "Version: $ver";
ui_print "Release date: $date";
ui_print " ";
ui_print "Using architecture: $arch";
ui_print "Using SDK level: $sdk";
ui_print "Sysroot is on $sysroot";
if [ "$sdk" -lt "$minsdk" ]; then
  ui_print " ";
  ui_print "WARNING: Using an old Android";
  ui_print "Full compatibility not guaranteed";
fi;

ui_print " ";
ui_print "Mounting...";
if [ -e "/data/adb/magisk" ] && [ "$forcesys" != "yes" ]; then
  rootpart="/data";
  $bootmode && modulesdir="$rootpart/adb/modules_update" || modulesdir="$rootpart/adb/modules";
  root="$modulesdir/$modname";
  magisk=yes;
  log "Using $modulesdir";
  [ -d "$modulesdir" ] || {
    mkdir -p "$modulesdir";
    perm 0 0 0755 0644 "$modulesdir";
  }
else
  rootpart="$sysrootpart";
  [ "$dynamicpart" = "true" ] && [ "$sysblk" ] && blockdev --setrw "$sysblk";
  mount -o rw,remount "$rootpart";
  mount -o rw,remount "$rootpart" "$rootpart";
  root="$sysroot";
  magisk=no;
  log "Mounted $rootpart RW";
fi;

if [ "$action" = "installation" ]; then

  ui_print " ";
  ui_print "Extracting files...";
  mkdir -p "$filedir";
  unzip -o "$zipfile" -d "$filedir" || abort "Could not unzip $zipfile";

  pre_install_actions;

  ui_print " ";
  ui_print "Cleaning up...";
  log "Removing duplicates";
  uninstall_pack;
  log "Debloating";
  if [ "$magisk" = "yes" ]; then
    for thing in $stuff_debloat $stuff_uninstall; do
      [ "$thing" ] && debloat "$sysroot/$thing" "$root/$thing";
    done;
  elif [ "$magisk" = "no" ]; then
    for thing in $stuff_debloat; do
      [ "$thing" ] && remove "$sysroot/$thing";
    done;
  fi;

  ui_print " ";
  ui_print "Doing size checks...";
  packsizem="$(( $(sizecheck_pack) / 1024 + 1 ))";
  log "Pack size is $packsizem";
  diskfreem="$(( $(df -Pk "$rootpart" | tail -n 1 | select_word 4) / 1024 ))";
  log "Free space in $rootpart is $diskfreem";
  [ "$diskfreem" -gt "$packsizem" ] || abort "Not enough free space in your $rootpart!";

  ui_print " ";
  ui_print "Installing $modname to $root...";
  mkdir -p "$root";

  log " ";
  log "Installing generic stuff";
  for thing in $stuff; do
    [ "$thing" ] && install_dest "$filedir/$thing" "$root/$thing";
  done;

  log " ";
  log "Installing Arch dependant stuff for $arch";
  for thing in $stuff_arch; do
    [ "$thing" ] && install_dest "$filedir/$(dirname "$thing")/*-$arch-*/$(basename "$thing")" "$root/$thing";
  done;

  log " ";
  log "Installing SDK dependant stuff for SDK $sdk";
  for thing in $stuff_sdk; do
    [ "$thing" ] && install_dest "$filedir/$(dirname "$thing")/*-$sdk-*/$(basename "$thing")" "$root/$thing";
  done;

  log " ";
  log "Installing Arch and SDK dependant stuff for $arch and SDK $sdk";
  for thing in $stuff_arch_sdk; do
    [ "$thing" ] && install_dest "$filedir/$(dirname "$thing")/*-$arch-*-$sdk-*/$(basename "$thing")" "$root/$thing";
  done;

  log " ";
  log "Executing other actions";
  if [ "$magisk" = "yes" ]; then
    [ "$modprop" ] && {
      echo "$modprop" > "$root/module.prop" || abort "Could not create module.prop in $root";
    }
    touch "$root/auto_mount" || abort "Could not create auto_mount in $root";
    if $bootmode && [ "$modulesdir" = "$rootpart/adb/modules_update" ]; then
      modmnt="$rootpart/adb/modules";
      mkdir -p "$modmnt/$modname";
      touch "$modmnt/$modname/update" || abort "Could not create update in $modmnt/$modname";
      [ "$modprop" ] && {
        echo "$modprop" > "$modmnt/$modname/module.prop" || abort "Could not create module.prop in $modmnt/$modname";
      }
    fi;
  fi;

  ui_print " ";
  ui_print "Setting permissions...";
  if [ "$magisk" = "yes" ]; then
    find "$root" -maxdepth 1 -exec chmod 0755 {} +;
  fi;
  for thing in $stuff_perm; do
    case "$thing" in
      */bin*|*/xbin*)
        perm 0 2000 0755 0777 "$root/$thing";
      ;;
      *)
        perm 0 0 0755 0644 "$root/$thing";
      ;;
    esac;
    chcon -hR 'u:object_r:system_file:s0' "$root/$thing";
  done;

  post_install_actions;

fi;

if [ "$action" = "uninstallation" ]; then

  pre_uninstall_actions;

  ui_print " ";
  ui_print "Uninstalling $modname from $root...";
  uninstall_pack;

  post_uninstall_actions;

fi;

ui_print " ";
ui_print "Unmounting...";
cleanup;

ui_print " ";
ui_print "Done!";
ui_print " ";
exit 0;
