#!/usr/bin/env bash

. /opt/arpl/include/functions.sh
. /opt/arpl/include/addons.sh
. /opt/arpl/include/modules.sh

# Check partition 3 space, if < 2GiB is necessary clean cache folder
CLEARCACHE=0
LOADER_DISK="$(blkid | grep 'LABEL="ARPL3"' | cut -d3 -f1)"
LOADER_DEVICE_NAME=$(echo ${LOADER_DISK} | sed 's|/dev/||')
if [ $(cat /sys/block/${LOADER_DEVICE_NAME}/${LOADER_DEVICE_NAME}3/size) -lt 4194304 ]; then
  CLEARCACHE=1
fi

# Get actual IP
IP=$(ip route 2>/dev/null | sed -n 's/.* via .* dev \(.*\)  src \(.*\)  metric .*/\1: \2 /p' | head -1)

# Dirty flag
DIRTY=0
# Debug flag
# DEBUG=0

MODEL="$(readConfigKey "model" "${USER_CONFIG_FILE}")"
BUILD="$(readConfigKey "build" "${USER_CONFIG_FILE}")"
LAYOUT="$(readConfigKey "layout" "${USER_CONFIG_FILE}")"
KEYMAP="$(readConfigKey "keymap" "${USER_CONFIG_FILE}")"
LKM="$(readConfigKey "lkm" "${USER_CONFIG_FILE}")"
DIRECTBOOT="$(readConfigKey "directboot" "${USER_CONFIG_FILE}")"
NOTSETMACS="$(readConfigKey "notsetmacs" "${USER_CONFIG_FILE}")"
BOOTIPWAIT="$(readConfigKey "bootipwait" "${USER_CONFIG_FILE}")"
SN="$(readConfigKey "sn" "${USER_CONFIG_FILE}")"

###############################################################################
# Mounts backtitle dynamically
function backtitle() {
  BACKTITLE="${ARPL_TITLE}"
  if [ -n "${MODEL}" ]; then
    BACKTITLE+=" ${MODEL}"
  else
    BACKTITLE+=" (no model)"
  fi
  if [ -n "${BUILD}" ]; then
    BACKTITLE+=" ${BUILD}"
  else
    BACKTITLE+=" (no build)"
  fi
  if [ -n "${SN}" ]; then
    BACKTITLE+=" ${SN}"
  else
    BACKTITLE+=" (no SN)"
  fi
  if [ -n "${IP}" ]; then
    BACKTITLE+=" ${IP}"
  else
    BACKTITLE+=" (no IP)"
  fi
  if [ -n "${KEYMAP}" ]; then
    BACKTITLE+=" (${LAYOUT}/${KEYMAP})"
  else
    BACKTITLE+=" (qwerty/us)"
  fi
  if [ -d "/sys/firmware/efi" ]; then
    BACKTITLE+=" [UEFI]"
  else
    BACKTITLE+=" [BIOS]"
  fi
  echo ${BACKTITLE}
}

###############################################################################
# Shows available models to user choose one
function modelMenu() {
  if [ -z "${1}" ]; then
    RESTRICT=1
    FLGBETA=0
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Model")" \
      --infobox "$(TEXT "Reading models")" 0 0
    while true; do
      echo "" >"${TMP_PATH}/menu"
      FLGNEX=0
      while read M; do
        M="$(basename ${M})"
        M="${M::-4}"
        PLATFORM=$(readModelKey "${M}" "platform")
        DT="$(readModelKey "${M}" "dt")"
        BETA="$(readModelKey "${M}" "beta")"
        [ "${BETA}" = "true" -a ${FLGBETA} -eq 0 ] && continue
        # Check id model is compatible with CPU
        COMPATIBLE=1
        if [ ${RESTRICT} -eq 1 ]; then
          for F in $(readModelArray "${M}" "flags"); do
            if ! grep -q "^flags.*${F}.*" /proc/cpuinfo; then
              COMPATIBLE=0
              FLGNEX=1
              break
            fi
          done
        fi
        [ "${DT}" = "true" ] && DT="-DT" || DT=""
        [ ${COMPATIBLE} -eq 1 ] && echo "${M} \"\Zb${PLATFORM}${DT}\Zn\" " >>"${TMP_PATH}/menu"
      done < <(find "${MODEL_CONFIG_PATH}" -maxdepth 1 -name \*.yml | sort)
      [ ${FLGNEX} -eq 1 ] && echo "f \"\Z1$(TEXT "Disable flags restriction")\Zn\"" >>"${TMP_PATH}/menu"
      [ ${FLGBETA} -eq 0 ] && echo "b \"\Z1$(TEXT "Show beta models")\Zn\"" >>"${TMP_PATH}/menu"
      dialog --backtitle "$(backtitle)" --colors \
        --menu "$(TEXT "Choose the model")" 0 0 0 --file "${TMP_PATH}/menu" \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && return
      resp=$(<${TMP_PATH}/resp)
      [ -z "${resp}" ] && return
      if [ "${resp}" = "f" ]; then
        RESTRICT=0
        continue
      fi
      if [ "${resp}" = "b" ]; then
        FLGBETA=1
        continue
      fi
      break
    done
  else
    resp="${1}"
  fi
  # If user change model, clean buildnumber and S/N
  if [ "${MODEL}" != "${resp}" ]; then
    MODEL=${resp}
    writeConfigKey "model" "${MODEL}" "${USER_CONFIG_FILE}"
    BUILD=""
    writeConfigKey "build" "${BUILD}" "${USER_CONFIG_FILE}"
    SN=$(generateSerial "${MODEL}")
    writeConfigKey "sn" "${SN}" "${USER_CONFIG_FILE}"
    # Delete old files
    rm -f "${ORI_ZIMAGE_FILE}" "${ORI_RDGZ_FILE}" "${MOD_ZIMAGE_FILE}" "${MOD_RDGZ_FILE}"
    rm -f "${TMP_PATH}/patdownloadurl"
    DIRTY=1
  fi
}

###############################################################################
# Shows available buildnumbers from a model to user choose one
function buildMenu() {
  ITEMS="$(readConfigEntriesArray "builds" "${MODEL_CONFIG_PATH}/${MODEL}.yml" | sort -r)"
  if [ -z "${1}" ]; then
    dialog --backtitle "$(backtitle)" --colors \
      --no-items --menu "$(TEXT "Choose a build number")" 0 0 0 ${ITEMS} \
      2>${TMP_PATH}/resp
    [ $? -ne 0 ] && return
    resp=$(<${TMP_PATH}/resp)
    [ -z "${resp}" ] && return
  else
    if ! arrayExistItem "${1}" ${ITEMS}; then return; fi
    resp="${1}"
  fi
  if [ "${BUILD}" != "${resp}" ]; then
    local KVER=$(readModelKey "${MODEL}" "builds.${resp}.kver")
    if [ -d "/sys/firmware/efi" -a "${KVER:0:1}" = "3" ]; then
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Build Number")" \
        --msgbox "$(TEXT "This version does not support UEFI startup, Please select another version or switch the startup mode.")" 0 0
      buildMenu
    fi
    if [ ! "usb" = "$(udevadm info --query property --name ${LOADER_DISK} | grep BUS | cut -d= -f2)" -a "${KVER:0:1}" = "5" ]; then
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Build Number")" \
        --msgbox "$(TEXT "This version only support usb startup, Please select another version or switch the startup mode.")" 0 0
      buildMenu
    fi
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Build Number")" \
      --infobox "$(TEXT "Reconfiguring Synoinfo, Addons and Modules")" 0 0
    BUILD=${resp}
    writeConfigKey "build" "${BUILD}" "${USER_CONFIG_FILE}"
    # Delete synoinfo and reload model/build synoinfo
    writeConfigKey "synoinfo" "{}" "${USER_CONFIG_FILE}"
    while IFS=': ' read KEY VALUE; do
      writeConfigKey "synoinfo.${KEY}" "${VALUE}" "${USER_CONFIG_FILE}"
    done < <(readModelMap "${MODEL}" "builds.${BUILD}.synoinfo")
    # Check addons
    PLATFORM="$(readModelKey "${MODEL}" "platform")"
    KVER="$(readModelKey "${MODEL}" "builds.${BUILD}.kver")"
    while IFS=': ' read ADDON PARAM; do
      [ -z "${ADDON}" ] && continue
      if ! checkAddonExist "${ADDON}" "${PLATFORM}" "${KVER}"; then
        deleteConfigKey "addons.${ADDON}" "${USER_CONFIG_FILE}"
      fi
    done < <(readConfigMap "addons" "${USER_CONFIG_FILE}")
    # Rebuild modules
    writeConfigKey "modules" "{}" "${USER_CONFIG_FILE}"
    while read ID DESC; do
      writeConfigKey "modules.${ID}" "" "${USER_CONFIG_FILE}"
    done < <(getAllModules "${PLATFORM}" "${KVER}")
    # Remove old files
    rm -f "${ORI_ZIMAGE_FILE}" "${ORI_RDGZ_FILE}" "${MOD_ZIMAGE_FILE}" "${MOD_RDGZ_FILE}"
    rm -f "${TMP_PATH}/patdownloadurl"
    DIRTY=1
  fi
}

###############################################################################
# Manage addons
function addonMenu() {
  # Read 'platform' and kernel version to check if addon exists
  PLATFORM="$(readModelKey "${MODEL}" "platform")"
  KVER="$(readModelKey "${MODEL}" "builds.${BUILD}.kver")"
  # Read addons from user config
  unset ADDONS
  declare -A ADDONS
  while IFS=': ' read KEY VALUE; do
    [ -n "${KEY}" ] && ADDONS["${KEY}"]="${VALUE}"
  done < <(readConfigMap "addons" "${USER_CONFIG_FILE}")
  NEXT="a"
  # Loop menu
  while true; do
    dialog --backtitle "$(backtitle)" --colors \
      --default-item ${NEXT} --menu "$(TEXT "Choose a option")" 0 0 0 \
      a "$(TEXT "Add an addon")" \
      d "$(TEXT "Delete addon(s)")" \
      s "$(TEXT "Show user addons")" \
      m "$(TEXT "Show all available addons")" \
      o "$(TEXT "Upload a external addon")" \
      e "$(TEXT "Exit")" \
      2>${TMP_PATH}/resp
    [ $? -ne 0 ] && return
    case "$(<${TMP_PATH}/resp)" in
    a)
      NEXT='a'
      rm "${TMP_PATH}/menu"
      while read ADDON DESC; do
        arrayExistItem "${ADDON}" "${!ADDONS[@]}" && continue # Check if addon has already been added
        echo "${ADDON} \"${DESC}\"" >>"${TMP_PATH}/menu"
      done < <(availableAddons "${PLATFORM}" "${KVER}")
      if [ ! -f "${TMP_PATH}/menu" ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
          --msgbox "$(TEXT "No available addons to add")" 0 0
        NEXT="e"
        continue
      fi
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
        --menu "$(TEXT "Select an addon")" 0 0 0 --file "${TMP_PATH}/menu" \
        2>"${TMP_PATH}/resp"
      [ $? -ne 0 ] && continue
      ADDON="$(<"${TMP_PATH}/resp")"
      [ -z "${ADDON}" ] && continue
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
        --inputbox "$(TEXT "Type a opcional params to addon")" 0 0 \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && continue
      ADDONS[${ADDON}]="$(<"${TMP_PATH}/resp")"
      writeConfigKey "addons.${ADDON}" "${VALUE}" "${USER_CONFIG_FILE}"
      DIRTY=1
      ;;
    d)
      NEXT='d'
      if [ ${#ADDONS[@]} -eq 0 ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
          --msgbox "$(TEXT "No user addons to remove")" 0 0
        continue
      fi
      rm -f "${TMP_PATH}/opts"
      for I in "${!ADDONS[@]}"; do
        echo "\"${I}\" \"${I}\" \"off\"" >>"${TMP_PATH}/opts"
      done
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
        --no-tags --checklist "$(TEXT "Select addon to remove")" 0 0 0 --file "${TMP_PATH}/opts" \
        2>"${TMP_PATH}/resp"
      [ $? -ne 0 ] && continue
      ADDON="$(<"${TMP_PATH}/resp")"
      [ -z "${ADDON}" ] && continue
      for I in ${ADDON}; do
        unset ADDONS[${I}]
        deleteConfigKey "addons.${I}" "${USER_CONFIG_FILE}"
      done
      DIRTY=1
      ;;
    s)
      NEXT='s'
      ITEMS=""
      for KEY in ${!ADDONS[@]}; do
        ITEMS+="${KEY}: ${ADDONS[$KEY]}\n"
      done
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
        --msgbox "${ITEMS}" 0 0
      ;;
    m)
      NEXT='m'
      MSG=""
      while read MODULE DESC; do
        if arrayExistItem "${MODULE}" "${!ADDONS[@]}"; then
          MSG+="\Z4${MODULE}\Zn"
        else
          MSG+="${MODULE}"
        fi
        MSG+=": \Z5${DESC}\Zn\n"
      done < <(availableAddons "${PLATFORM}" "${KVER}")
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
        --msgbox "${MSG}" 0 0
      ;;
    o)
      if ! tty | grep -q "/dev/pts"; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
          --msgbox "$(TEXT "This feature is only available when accessed via web/ssh.")" 0 0
        return
      fi
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
        --msgbox "$(TEXT "Please upload the *.addons file.")" 0 0
      TMP_UP_PATH=/tmp/users
      USER_FILE=""
      rm -rf ${TMP_UP_PATH}
      mkdir -p ${TMP_UP_PATH}
      pushd ${TMP_UP_PATH}
      rz -be
      for F in $(ls -A); do
        USER_FILE=${F}
        break
      done
      popd
      if [ -z "${USER_FILE}" ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
          --msgbox "$(TEXT "Not a valid file, please try again!")" 0 0
      else
        if [ -d "${ADDONS_PATH}/$(basename ${USER_FILE} .addons)" ]; then
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
            --yesno "$(TEXT "The addon already exists. Do you want to overwrite it?")" 0 0
          RET=$?
          [ ${RET} -eq 0 ] && return
        fi
        ADDON="$(untarAddon "${TMP_UP_PATH}/${USER_FILE}")"
        if [ -n "${ADDON}" ]; then
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
            --msgbox "$(printf "$(TEXT "Addon '%s' added to loader, Please enable it in 'Add an addon' menu.")" "${ADDON}")" 0 0
        else
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Addons")" \
            --msgbox "$(TEXT "File format not recognized!")" 0 0
        fi
      fi
      ;;

    e) return ;;
    esac
  done
}
###############################################################################
function moduleMenu() {
  PLATFORM="$(readModelKey "${MODEL}" "platform")"
  KVER="$(readModelKey "${MODEL}" "builds.${BUILD}.kver")"
  dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
    --infobox "$(TEXT "Reading modules")" 0 0
  ALLMODULES=$(getAllModules "${PLATFORM}" "${KVER}")
  unset USERMODULES
  declare -A USERMODULES
  while IFS=': ' read KEY VALUE; do
    [ -n "${KEY}" ] && USERMODULES["${KEY}"]="${VALUE}"
  done < <(readConfigMap "modules" "${USER_CONFIG_FILE}")
  NEXT="s"
  # loop menu
  while true; do
    dialog --backtitle "$(backtitle)" --colors \
      --default-item ${NEXT} --menu "$(TEXT "Choose a option")" 0 0 0 \
      s "$(TEXT "Show selected modules")" \
      l "$(TEXT "Select loaded modules")" \
      a "$(TEXT "Select all modules")" \
      d "$(TEXT "Deselect all modules")" \
      c "$(TEXT "Choose modules to include")" \
      o "$(TEXT "Upload a external module")" \
      e "$(TEXT "Exit")" \
      2>${TMP_PATH}/resp
    [ $? -ne 0 ] && break
    case "$(<${TMP_PATH}/resp)" in
    s)
      ITEMS=""
      for KEY in ${!USERMODULES[@]}; do
        ITEMS+="${KEY}: ${USERMODULES[$KEY]}\n"
      done
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
        --msgbox "${ITEMS}" 0 0
      ;;
    l)
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
        --infobox "$(TEXT "Selecting loaded modules")" 0 0
      KOLIST=""
      for I in $(lsmod | awk -F' ' '{print $1}' | grep -v 'Module'); do
        KOLIST+="$(getdepends ${PLATFORM} ${KVER} ${I}) ${I} "
      done
      KOLIST=($(echo ${KOLIST} | tr ' ' '\n' | sort -u))
      unset USERMODULES
      declare -A USERMODULES
      writeConfigKey "modules" "{}" "${USER_CONFIG_FILE}"
      for ID in ${KOLIST[@]}; do
        USERMODULES["${ID}"]=""
        writeConfigKey "modules.${ID}" "" "${USER_CONFIG_FILE}"
      done
      DIRTY=1
      ;;
    a)
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
        --infobox "$(TEXT "Selecting all modules")" 0 0
      unset USERMODULES
      declare -A USERMODULES
      writeConfigKey "modules" "{}" "${USER_CONFIG_FILE}"
      while read ID DESC; do
        USERMODULES["${ID}"]=""
        writeConfigKey "modules.${ID}" "" "${USER_CONFIG_FILE}"
      done <<<${ALLMODULES}
      DIRTY=1
      ;;

    d)
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
        --infobox "$(TEXT "Deselecting all modules")" 0 0
      writeConfigKey "modules" "{}" "${USER_CONFIG_FILE}"
      unset USERMODULES
      declare -A USERMODULES
      DIRTY=1
      ;;

    c)
      rm -f "${TMP_PATH}/opts"
      while read ID DESC; do
        arrayExistItem "${ID}" "${!USERMODULES[@]}" && ACT="on" || ACT="off"
        echo "${ID} ${DESC} ${ACT}" >>"${TMP_PATH}/opts"
      done <<<${ALLMODULES}
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
        --checklist "$(TEXT "Select modules to include")" 0 0 0 --file "${TMP_PATH}/opts" \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && continue
      resp=$(<${TMP_PATH}/resp)
      [ -z "${resp}" ] && continue
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
        --infobox "$(TEXT "Writing to user config")" 0 0
      unset USERMODULES
      declare -A USERMODULES
      writeConfigKey "modules" "{}" "${USER_CONFIG_FILE}"
      for ID in ${resp}; do
        USERMODULES["${ID}"]=""
        writeConfigKey "modules.${ID}" "" "${USER_CONFIG_FILE}"
      done
      DIRTY=1
      ;;

    o)
      MSG=""
      MSG+="$(TEXT "This function is experimental and dangerous. If you don't know much, please exit.\n")"
      MSG+="$(TEXT "The imported .ko of this function will be implanted into the corresponding arch's modules package, which will affect all models of the arch.\n")"
      MSG+="$(TEXT "This program will not determine the availability of imported modules or even make type judgments, as please double check if it is correct.\n")"
      MSG+="$(TEXT "If you want to remove it, please go to the \"Update Menu\" -> \"Update modules\" to forcibly update the modules. All imports will be reset.\n")"
      MSG+="$(TEXT "Do you want to continue?")"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
        --yesno "${MSG}" 0 0
      [ $? -ne 0 ] && return
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
        --msgbox "$(TEXT "Please upload the *.ko file.")" 0 0
      TMP_UP_PATH=/tmp/users
      USER_FILE=""
      rm -rf ${TMP_UP_PATH}
      mkdir -p ${TMP_UP_PATH}
      pushd ${TMP_UP_PATH}
      rz -be
      for F in $(ls -A); do
        USER_FILE=${F}
        break
      done
      popd
      if [ -n "${USER_FILE}" -a "${USER_FILE##*.}" = "ko" ]; then
        addToModules ${PLATFORM} ${KVER} "${TMP_UP_PATH}/${USER_FILE}"
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
          --msgbox "$(printf "$(TEXT "Module '%s' added to %s-%s")" "${USER_FILE}" ${PLATFORM} ${KVER})" 0 0
        rm -f "${TMP_UP_PATH}/${USER_FILE}"
      else
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Modules")" \
          --msgbox "$(TEXT "Not a valid file, please try again!")" 0 0
      fi
      ;;
    e)
      break
      ;;
    esac
  done
}

###############################################################################
function cmdlineMenu() {
  unset CMDLINE
  declare -A CMDLINE
  while IFS=': ' read KEY VALUE; do
    [ -n "${KEY}" ] && CMDLINE["${KEY}"]="${VALUE}"
  done < <(readConfigMap "cmdline" "${USER_CONFIG_FILE}")
  echo "a \"$(TEXT "Add/edit a cmdline item")\"" >"${TMP_PATH}/menu"
  echo "d \"$(TEXT "Delete cmdline item(s)")\"" >>"${TMP_PATH}/menu"
  if [ -n "${MODEL}" ]; then
    echo "s \"$(TEXT "Define a serial number")\"" >>"${TMP_PATH}/menu"
  fi
  echo "c \"$(TEXT "Define a custom MAC")\"" >>"${TMP_PATH}/menu"
  echo "v \"$(TEXT "Show user added cmdline")\"" >>"${TMP_PATH}/menu"
  echo "m \"$(TEXT "Show model inherent cmdline")\"" >>"${TMP_PATH}/menu"
  echo "e \"$(TEXT "Exit")\"" >>"${TMP_PATH}/menu"
  # Loop menu
  while true; do
    dialog --backtitle "$(backtitle)" --colors \
      --menu "$(TEXT "Choose a option")" 0 0 0 --file "${TMP_PATH}/menu" \
      2>${TMP_PATH}/resp
    [ $? -ne 0 ] && return
    case "$(<${TMP_PATH}/resp)" in
    a)
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
        --inputbox "$(TEXT "Type a name of cmdline")" 0 0 \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && continue
      NAME="$(sed 's/://g' <"${TMP_PATH}/resp")"
      [ -z "${NAME}" ] && continue
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
        --inputbox "$(printf "$(TEXT "Type a value of '%s' cmdline")" "${NAME}")" 0 0 "${CMDLINE[${NAME}]}" \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && continue
      VALUE="$(<"${TMP_PATH}/resp")"
      CMDLINE[${NAME}]="${VALUE}"
      writeConfigKey "cmdline.${NAME}" "${VALUE}" "${USER_CONFIG_FILE}"
      ;;
    d)
      if [ ${#CMDLINE[@]} -eq 0 ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
          --msgbox "$(TEXT "No user cmdline to remove")" 0 0
        continue
      fi
      rm -f "${TMP_PATH}/opts"
      for I in "${!CMDLINE[@]}"; do
        echo "\"${I}\" \"${CMDLINE[${I}]}\" \"off\"" >>"${TMP_PATH}/opts"
      done
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
        --checklist "$(TEXT "Select cmdline to remove")" 0 0 0 --file "${TMP_PATH}/opts" \
        2>"${TMP_PATH}/resp"
      [ $? -ne 0 ] && continue
      RESP=$(<"${TMP_PATH}/resp")
      [ -z "${RESP}" ] && continue
      for I in ${RESP}; do
        unset CMDLINE[${I}]
        deleteConfigKey "cmdline.${I}" "${USER_CONFIG_FILE}"
      done
      ;;
    s)
      while true; do
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
          --inputbox "$(TEXT "Please enter a serial number ")" 0 0 "" \
          2>${TMP_PATH}/resp
        [ $? -ne 0 ] && break 2
        SERIAL=$(cat ${TMP_PATH}/resp)
        if [ -z "${SERIAL}" ]; then
          return
        elif [ $(validateSerial ${MODEL} ${SERIAL}) -eq 1 ]; then
          break
        fi
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
          --yesno "$(TEXT "Invalid serial, continue?")" 0 0
        [ $? -eq 0 ] && break
      done
      SN="${SERIAL}"
      writeConfigKey "sn" "${SN}" "${USER_CONFIG_FILE}"
      ;;
    c)
      ETHX=($(ls /sys/class/net/ | grep eth)) # real network cards list
      for N in $( # Currently, only up to 8 are supported.  (<==> boot.sh L96, <==> lkm: MAX_NET_IFACES)
        seq 1 8
      ); do
        MACR="$(cat /sys/class/net/${ETHX[$(expr ${N} - 1)]}/address | sed 's/://g')"
        MACF=${CMDLINE["mac${N}"]}
        [ -n "${MACF}" ] && MAC=${MACF} || MAC=${MACR}
        RET=1
        while true; do
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
            --inputbox "$(printf "$(TEXT "Type a custom MAC address of %s")" "mac${N}")" 0 0 "${MAC}" \
            2>${TMP_PATH}/resp
          RET=$?
          [ ${RET} -ne 0 ] && break 2
          MAC="$(<"${TMP_PATH}/resp")"
          [ -z "${MAC}" ] && MAC="$(readConfigKey "original-mac${i}" "${USER_CONFIG_FILE}")"
          [ -z "${MAC}" ] && MAC="${MACFS[$(expr ${i} - 1)]}"
          MACF="$(echo "${MAC}" | sed 's/://g')"
          [ ${#MACF} -eq 12 ] && break
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
            --msgbox "$(TEXT "Invalid MAC")" 0 0
        done
        if [ ${RET} -eq 0 ]; then
          CMDLINE["mac${N}"]="${MACF}"
          CMDLINE["netif_num"]=${N}
          writeConfigKey "cmdline.mac${N}" "${MACF}" "${USER_CONFIG_FILE}"
          writeConfigKey "cmdline.netif_num" "${N}" "${USER_CONFIG_FILE}"
          MAC="${MACF:0:2}:${MACF:2:2}:${MACF:4:2}:${MACF:6:2}:${MACF:8:2}:${MACF:10:2}"
          ip link set dev ${ETHX[$(expr ${N} - 1)]} address ${MAC} 2>&1 |
            dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
              --progressbox "$(TEXT "Changing MAC")" 20 70
          /etc/init.d/S41dhcpcd restart 2>&1 |
            dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
              --progressbox "$(TEXT "Renewing IP")" 20 70
          # IP=`ip route 2>/dev/null | sed -n 's/.* via .* dev \(.*\)  src \(.*\)  metric .*/\1: \2 /p' | head -1`
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
            --yesno "$(TEXT "Continue to custom MAC?")" 0 0
          [ $? -ne 0 ] && break
        fi
      done
      ;;
    v)
      ITEMS=""
      for KEY in ${!CMDLINE[@]}; do
        ITEMS+="${KEY}: ${CMDLINE[$KEY]}\n"
      done
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
        --msgbox "${ITEMS}" 0 0
      ;;
    m)
      ITEMS=""
      while IFS=': ' read KEY VALUE; do
        ITEMS+="${KEY}: ${VALUE}\n"
      done < <(readModelMap "${MODEL}" "builds.${BUILD}.cmdline")
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cmdline")" \
        --msgbox "${ITEMS}" 0 0
      ;;
    e) return ;;
    esac
  done
}

###############################################################################
function synoinfoMenu() {
  # Read synoinfo from user config
  unset SYNOINFO
  declare -A SYNOINFO
  while IFS=': ' read KEY VALUE; do
    [ -n "${KEY}" ] && SYNOINFO["${KEY}"]="${VALUE}"
  done < <(readConfigMap "synoinfo" "${USER_CONFIG_FILE}")

  echo "a \"$(TEXT "Add/edit a synoinfo item")\"" >"${TMP_PATH}/menu"
  echo "d \"$(TEXT "Delete synoinfo item(s)")\"" >>"${TMP_PATH}/menu"
  echo "s \"$(TEXT "Show synoinfo entries")\"" >>"${TMP_PATH}/menu"
  echo "e \"$(TEXT "Exit")\"" >>"${TMP_PATH}/menu"

  # menu loop
  while true; do
    dialog --backtitle "$(backtitle)" --colors \
      --menu "$(TEXT "Choose a option")" 0 0 0 --file "${TMP_PATH}/menu" \
      2>${TMP_PATH}/resp
    [ $? -ne 0 ] && return
    case "$(<${TMP_PATH}/resp)" in
    a)
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Synoinfo")" \
        --inputbox "$(TEXT "Type a name of synoinfo entry")" 0 0 \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && continue
      NAME="$(<"${TMP_PATH}/resp")"
      [ -z "${NAME}" ] && continue
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Synoinfo")" \
        --inputbox "$(printf "$(TEXT "Type a value of '%s' synoinfo entry")" "${NAME}")" 0 0 "${SYNOINFO[${NAME}]}" \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && continue
      VALUE="$(<"${TMP_PATH}/resp")"
      SYNOINFO[${NAME}]="${VALUE}"
      writeConfigKey "synoinfo.${NAME}" "${VALUE}" "${USER_CONFIG_FILE}"
      DIRTY=1
      ;;
    d)
      if [ ${#SYNOINFO[@]} -eq 0 ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Synoinfo")" \
          --msgbox "$(TEXT "No synoinfo entries to remove")" 0 0
        continue
      fi
      rm -f "${TMP_PATH}/opts"
      for I in ${!SYNOINFO[@]}; do
        echo "\"${I}\" \"${SYNOINFO[${I}]}\" \"off\"" >>"${TMP_PATH}/opts"
      done
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Synoinfo")" \
        --checklist "$(TEXT "Select synoinfo entry to remove")" 0 0 0 --file "${TMP_PATH}/opts" \
        2>"${TMP_PATH}/resp"
      [ $? -ne 0 ] && continue
      RESP=$(<"${TMP_PATH}/resp")
      [ -z "${RESP}" ] && continue
      for I in ${RESP}; do
        unset SYNOINFO[${I}]
        deleteConfigKey "synoinfo.${I}" "${USER_CONFIG_FILE}"
      done
      DIRTY=1
      ;;
    s)
      ITEMS=""
      for KEY in ${!SYNOINFO[@]}; do
        ITEMS+="${KEY}: ${SYNOINFO[$KEY]}\n"
      done
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Synoinfo")" \
        --msgbox "${ITEMS}" 0 0
      ;;
    e) return ;;
    esac
  done
}

###############################################################################
# Extract linux and ramdisk files from the DSM .pat
function extractDsmFiles() {
  PAT_URL="$(readModelKey "${MODEL}" "builds.${BUILD}.pat.url")"
  PAT_MD5="$(readModelKey "${MODEL}" "builds.${BUILD}.pat.md5")"

  SPACELEFT=$(df --block-size=1 | awk '/'${LOADER_DEVICE_NAME}'3/{print $4}') # Check disk space left

  PAT_FILE="${MODEL}-${BUILD}.pat"
  PAT_PATH="${CACHE_PATH}/dl/${PAT_FILE}"
  EXTRACTOR_PATH="${CACHE_PATH}/extractor"
  EXTRACTOR_BIN="syno_extract_system_patch"
  OLDPAT_URL="https://global.synologydownload.com/download/DSM/release/7.0.1/42218/DSM_DS3622xs%2B_42218.pat"

  if [ -f "${PAT_PATH}" ]; then
    echo "$(printf "$(TEXT "%s cached.")" "${PAT_FILE}")"
  else
    # If we have little disk space, clean cache folder
    if [ ${CLEARCACHE} -eq 1 ]; then
      echo "$(TEXT "Cleaning cache")"
      rm -rf "${CACHE_PATH}/dl"
    fi
    mkdir -p "${CACHE_PATH}/dl"

    speed_a=$(ping -c 1 -W 5 global.synologydownload.com | awk '/time=/ {print $7}' | cut -d '=' -f 2)
    speed_b=$(ping -c 1 -W 5 global.download.synology.com | awk '/time=/ {print $7}' | cut -d '=' -f 2)
    speed_c=$(ping -c 1 -W 5 cndl.synology.cn | awk '/time=/ {print $7}' | cut -d '=' -f 2)
    fastest="$(echo -e "global.synologydownload.com ${speed_a:-999}\nglobal.download.synology.com ${speed_b:-999}\ncndl.synology.cn ${speed_c:-999}" | sort -k2n | head -1 | awk '{print $1}')"

    mirror="$(echo ${PAT_URL} | sed 's|^http[s]*://\([^/]*\).*|\1|')"
    if [ "${mirror}" != "${fastest}" ]; then
      echo "$(printf "$(TEXT "Based on the current network situation, switch to %s mirror to downloading.")" "${fastest}")"
      PAT_URL="$(echo ${PAT_URL} | sed "s/${mirror}/${fastest}/")"
      OLDPAT_URL="https://${fastest}/download/DSM/release/7.0.1/42218/DSM_DS3622xs%2B_42218.pat"
    fi
    echo ${PAT_URL} >"${TMP_PATH}/patdownloadurl"
    echo "$(printf "$(TEXT "Downloading %s")" "${PAT_FILE}")"
    # Discover remote file size
    FILESIZE=$(curl -k -sLI "${PAT_URL}" | grep -i Content-Length | awk '{print$2}')
    if [ 0${FILESIZE} -ge 0${SPACELEFT} ]; then
      # No disk space to download, change it to RAMDISK
      PAT_PATH="${TMP_PATH}/${PAT_FILE}"
    fi
    STATUS=$(curl -k -w "%{http_code}" -L "${PAT_URL}" -o "${PAT_PATH}" --progress-bar)
    if [ $? -ne 0 -o ${STATUS} -ne 200 ]; then
      rm "${PAT_PATH}"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
        --msgbox "$(TEXT "Check internet or cache disk space")" 0 0
      return 1
    fi
  fi

  echo -n "$(printf "$(TEXT "Checking hash of %s: ")" "${PAT_FILE}")"
  if [ "$(md5sum ${PAT_PATH} | awk '{print $1}')" != "${PAT_MD5}" ]; then
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
      --msgbox "$(TEXT "md5 Hash of pat not match, try again!")" 0 0
    rm -f ${PAT_PATH}
    return 1
  fi
  echo "$(TEXT "OK")"

  rm -rf "${UNTAR_PAT_PATH}"
  mkdir "${UNTAR_PAT_PATH}"
  echo -n "$(printf "$(TEXT "Disassembling %s: ")" "${PAT_FILE}")"

  header="$(od -bcN2 ${PAT_PATH} | head -1 | awk '{print $3}')"
  case ${header} in
  105)
    echo "$(TEXT "Uncompressed tar")"
    isencrypted="no"
    ;;
  213)
    echo "$(TEXT "Compressed tar")"
    isencrypted="no"
    ;;
  255)
    echo "$(TEXT "Encrypted")"
    isencrypted="yes"
    ;;
  *)
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
      --msgbox "$(TEXT "Could not determine if pat file is encrypted or not, maybe corrupted, try again!")" 0 0
    return 1
    ;;
  esac

  SPACELEFT=$(df --block-size=1 | awk '/'${LOADER_DEVICE_NAME}'3/{print$4}') # Check disk space left

  if [ "${isencrypted}" = "yes" ]; then
    # Check existance of extractor
    if [ -f "${EXTRACTOR_PATH}/${EXTRACTOR_BIN}" ]; then
      echo "$(TEXT "Extractor cached.")"
    else
      # Extractor not exists, get it.
      mkdir -p "${EXTRACTOR_PATH}"
      # Check if old pat already downloaded
      OLDPAT_PATH="${CACHE_PATH}/dl/DS3622xs+-42218.pat"
      if [ ! -f "${OLDPAT_PATH}" ]; then
        echo "$(TEXT "Downloading old pat to extract synology .pat extractor...")"
        # Discover remote file size
        FILESIZE=$(curl -k -sLI "${OLDPAT_URL}" | grep -i Content-Length | awk '{print$2}')
        if [ 0${FILESIZE} -ge 0${SPACELEFT} ]; then
          # No disk space to download, change it to RAMDISK
          OLDPAT_PATH="${TMP_PATH}/DS3622xs+-42218.pat"
        fi
        STATUS=$(curl -k -w "%{http_code}" -L "${OLDPAT_URL}" -o "${OLDPAT_PATH}" --progress-bar)
        if [ $? -ne 0 -o ${STATUS} -ne 200 ]; then
          rm "${OLDPAT_PATH}"
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
            --msgbox "$(TEXT "Check internet or cache disk space")" 0 0
          return 1
        fi
      fi
      # Extract DSM ramdisk file from PAT
      rm -rf "${RAMDISK_PATH}"
      mkdir -p "${RAMDISK_PATH}"
      tar -xf "${OLDPAT_PATH}" -C "${RAMDISK_PATH}" rd.gz >"${LOG_FILE}" 2>&1
      if [ $? -ne 0 ]; then
        rm -f "${OLDPAT_PATH}"
        rm -rf "${RAMDISK_PATH}"
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
          --textbox "${LOG_FILE}" 0 0
        return 1
      fi
      [ ${CLEARCACHE} -eq 1 ] && rm -f "${OLDPAT_PATH}"
      # Extract all files from rd.gz
      (
        cd "${RAMDISK_PATH}"
        xz -dc <rd.gz | cpio -idm
      ) >/dev/null 2>&1 || true
      # Copy only necessary files
      for f in libcurl.so.4 libmbedcrypto.so.5 libmbedtls.so.13 libmbedx509.so.1 libmsgpackc.so.2 libsodium.so libsynocodesign-ng-virtual-junior-wins.so.7; do
        cp "${RAMDISK_PATH}/usr/lib/${f}" "${EXTRACTOR_PATH}"
      done
      cp "${RAMDISK_PATH}/usr/syno/bin/scemd" "${EXTRACTOR_PATH}/${EXTRACTOR_BIN}"
      rm -rf "${RAMDISK_PATH}"
    fi
    # Uses the extractor to untar pat file
    echo "$(TEXT "Extracting...")"
    LD_LIBRARY_PATH=${EXTRACTOR_PATH} "${EXTRACTOR_PATH}/${EXTRACTOR_BIN}" "${PAT_PATH}" "${UNTAR_PAT_PATH}" || true
  else
    echo "$(TEXT "Extracting...")"
    tar -xf "${PAT_PATH}" -C "${UNTAR_PAT_PATH}" >"${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
        --textbox "${LOG_FILE}" 0 0
    fi
  fi
  if [ ! -f ${UNTAR_PAT_PATH}/grub_cksum.syno ] ||
    [ ! -f ${UNTAR_PAT_PATH}/GRUB_VER ] ||
    [ ! -f ${UNTAR_PAT_PATH}/zImage ] ||
    [ ! -f ${UNTAR_PAT_PATH}/rd.gz ]; then
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
      --msgbox "$(TEXT "pat Invalid, try again!")" 0 0
    return 1
  fi
  echo -n "$(TEXT "Setting hash: ")"
  ZIMAGE_HASH="$(sha256sum ${UNTAR_PAT_PATH}/zImage | awk '{print $1}')"
  writeConfigKey "zimage-hash" "${ZIMAGE_HASH}" "${USER_CONFIG_FILE}"
  RAMDISK_HASH="$(sha256sum ${UNTAR_PAT_PATH}/rd.gz | awk '{print $1}')"
  writeConfigKey "ramdisk-hash" "${RAMDISK_HASH}" "${USER_CONFIG_FILE}"
  echo "$(TEXT "OK")"

  echo -n "$(TEXT "Copying files: ")"
  cp "${UNTAR_PAT_PATH}/grub_cksum.syno" "${BOOTLOADER_PATH}"
  cp "${UNTAR_PAT_PATH}/GRUB_VER" "${BOOTLOADER_PATH}"
  cp "${UNTAR_PAT_PATH}/grub_cksum.syno" "${SLPART_PATH}"
  cp "${UNTAR_PAT_PATH}/GRUB_VER" "${SLPART_PATH}"
  cp "${UNTAR_PAT_PATH}/zImage" "${ORI_ZIMAGE_FILE}"
  cp "${UNTAR_PAT_PATH}/rd.gz" "${ORI_RDGZ_FILE}"
  rm -rf "${UNTAR_PAT_PATH}"
  echo "$(TEXT "OK")"
}

###############################################################################
# Where the magic happens!
function make() {
  clear
  PLATFORM="$(readModelKey "${MODEL}" "platform")"
  KVER="$(readModelKey "${MODEL}" "builds.${BUILD}.kver")"

  # Check if all addon exists
  while IFS=': ' read ADDON PARAM; do
    [ -z "${ADDON}" ] && continue
    if ! checkAddonExist "${ADDON}" "${PLATFORM}" "${KVER}"; then
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
        --msgbox "$(printf "$(TEXT "Addon %s not found!")" "${ADDON}")" 0 0
      return 1
    fi
  done < <(readConfigMap "addons" "${USER_CONFIG_FILE}")

  if [ ! -f "${ORI_ZIMAGE_FILE}" -o ! -f "${ORI_RDGZ_FILE}" ]; then
    extractDsmFiles
    [ $? -ne 0 ] && return 1
  fi

  /opt/arpl/zimage-patch.sh
  if [ $? -ne 0 ]; then
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
      --msgbox "$(TEXT "zImage not patched:\n")$(<"${LOG_FILE}")" 0 0
    return 1
  fi

  /opt/arpl/ramdisk-patch.sh
  if [ $? -ne 0 ]; then
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Error")" \
      --msgbox "$(TEXT "Ramdisk not patched:\n")$(<"${LOG_FILE}")" 0 0
    return 1
  fi

  echo "$(TEXT "Cleaning")"
  rm -rf "${UNTAR_PAT_PATH}"

  echo "$(TEXT "Ready!")"
  sleep 3
  DIRTY=0
  return 0
}

###############################################################################
# Advanced menu
function advancedMenu() {
  NEXT="l"
  while true; do
    rm "${TMP_PATH}/menu"
    if [ -n "${BUILD}" ]; then
      echo "l \"$(TEXT "Switch LKM version:") \Z4${LKM}\Zn\"" >>"${TMP_PATH}/menu"
    fi
    if loaderIsConfigured; then
      echo "q \"$(TEXT "Switch direct boot:") \Z4${DIRECTBOOT}\Zn\"" >>"${TMP_PATH}/menu"
      if [ "${DIRECTBOOT}" = "false" ]; then
        echo "w \"$(TEXT "boot IPs wait time:") \Z4${BOOTIPWAIT}\Zn\"" >>"${TMP_PATH}/menu"
      fi
    fi
    echo "m \"$(TEXT "Switch 'not set MACs':") \Z4${NOTSETMACS}\Zn\"" >>"${TMP_PATH}/menu"
    echo "u \"$(TEXT "Edit user config file manually")\"" >>"${TMP_PATH}/menu"
    echo "t \"$(TEXT "Try to recovery a DSM installed system")\"" >>"${TMP_PATH}/menu"
    echo "s \"$(TEXT "Show SATA(s) # ports and drives")\"" >>"${TMP_PATH}/menu"
    if [ -n "${MODEL}" -a -n "${BUILD}" ]; then
      echo "k \"$(TEXT "show pat download link")\"" >>"${TMP_PATH}/menu"
    fi
    echo "a \"$(TEXT "Allow downgrade installation")\"" >>"${TMP_PATH}/menu"
    echo "f \"$(TEXT "Format disk(s) # Without loader disk")\"" >>"${TMP_PATH}/menu"
    echo "x \"$(TEXT "Reset syno system password")\"" >>"${TMP_PATH}/menu"
    echo "p \"$(TEXT "Persistence of arpl modifications")\"" >>"${TMP_PATH}/menu"
    if [ -n "${MODEL}" -a "true" = "$(readModelKey "${MODEL}" "dt")" ]; then
      echo "d \"$(TEXT "Custom dts file # Need rebuild")\"" >>"${TMP_PATH}/menu"
    fi
    if [ -n "${DEBUG}" ]; then
      echo "b \"$(TEXT "Backup bootloader disk # test")\"" >>"${TMP_PATH}/menu"
      echo "r \"$(TEXT "Restore bootloader disk # test")\"" >>"${TMP_PATH}/menu"
    fi
    echo "o \"$(TEXT "Development tools")\"" >>"${TMP_PATH}/menu"
    echo "e \"$(TEXT "Exit")\"" >>"${TMP_PATH}/menu"

    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
      --default-item "${NEXT}" --menu "$(TEXT "Choose the option")" 0 0 0 --file "${TMP_PATH}/menu" \
      2>${TMP_PATH}/resp
    [ $? -ne 0 ] && break
    case $(<"${TMP_PATH}/resp") in
    l)
      LKM=$([ "${LKM}" = "dev" ] && echo 'prod' || ([ "${LKM}" = "test" ] && echo 'dev' || echo 'test'))
      writeConfigKey "lkm" "${LKM}" "${USER_CONFIG_FILE}"
      DIRTY=1
      NEXT="l"
      ;;
    q)
      [ "${DIRECTBOOT}" = "false" ] && DIRECTBOOT='true' || DIRECTBOOT='false'
      writeConfigKey "directboot" "${DIRECTBOOT}" "${USER_CONFIG_FILE}"
      NEXT="e"
      ;;
    w)
      ITEMS="$(echo -e "1 \n5 \n10 \n30 \n60 \n")"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --default-item "${BOOTIPWAIT}" --no-items --menu "$(TEXT "Choose a waiting time(seconds)")" 0 0 0 ${ITEMS} \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && return
      resp=$(cat ${TMP_PATH}/resp 2>/dev/null)
      [ -z "${resp}" ] && return
      BOOTIPWAIT=${resp}
      writeConfigKey "bootipwait" "${BOOTIPWAIT}" "${USER_CONFIG_FILE}"
      NEXT="e"
      ;;
    m)
      [ "${NOTSETMACS}" = "false" ] && NOTSETMACS='true' || NOTSETMACS='false'
      writeConfigKey "notsetmacs" "${NOTSETMACS}" "${USER_CONFIG_FILE}"
      NEXT="e"
      ;;
    u)
      editUserConfig
      NEXT="e"
      ;;
    t) tryRecoveryDSM ;;
    s)
      MSG=""
      NUMPORTS=0
      ATTACHTNUM=0
      DiskIdxMap=""
      for PCI in $(echo -e "$(lspci -d ::106)\n$(lspci -d ::107)" | awk '{print $1}'); do
        NAME=$(lspci -s "${PCI}" | sed "s/\ .*://")
        MSG+="\Zb${NAME}\Zn\nPorts: "
        unset HOSTPORTS
        declare -A HOSTPORTS
        ATTACHTIDX=0
        while read LINE; do
          ATAPORT="$(echo ${LINE} | grep -o 'ata[0-9]*')"
          PORT=""
          if [ -n "${ATAPORT}" ]; then
            PORT=$(echo ${ATAPORT} | sed 's/ata//')
          else
            SASPORT="$(echo ${LINE} | grep -o "${PCI}/host[0-9]*")"
            PORT=$(echo ${SASPORT} | sed "s/${PCI}\/host//")
          fi
          HOSTPORTS[${PORT}]=$(echo ${LINE} | grep -o 'host[0-9]*$')
        done < <(ls -l /sys/class/scsi_host | fgrep "${PCI}")
        while read PORT; do
          ls -l /sys/block | fgrep -q "${PCI}/ata${PORT}" && ATTACH=1 ||
            ls -l /sys/block | fgrep -q "${PCI}/host${PORT}" && ATTACH=1 || ATTACH=0
          PCMD=$(cat /sys/class/scsi_host/${HOSTPORTS[${PORT}]}/ahci_port_cmd)
          [ "${PCMD}" = "0" ] && DUMMY=1 || DUMMY=0
          [ ${ATTACH} -eq 1 ] && MSG+="\Z2\Zb" && ATTACHTIDX=$((${ATTACHTIDX} + 1))
          [ ${DUMMY} -eq 1 ] && MSG+="\Z1"
          MSG+="${PORT}\Zn "
          NUMPORTS=$((${NUMPORTS} + 1))
        done < <(echo ${!HOSTPORTS[@]} | tr ' ' '\n' | sort -n)
        MSG+="\n"
        [ ${ATTACHTIDX} -gt 0 ] && DiskIdxMap+=$(printf '%02x' ${ATTACHTNUM}) || DiskIdxMap+="ff"
        ATTACHTNUM=$((${ATTACHTNUM} + ${ATTACHTIDX}))
      done
      MSG+="$(printf "$(TEXT "\nTotal of ports: %s\n")" "${NUMPORTS}")"
      MSG+="$(TEXT "\nPorts with color \Z1red\Zn as DUMMY, color \Z2\Zbgreen\Zn has drive connected.")"
      MSG+="$(TEXT "\nRecommended value:")"
      MSG+="$(TEXT "\nDiskIdxMap:") ${DiskIdxMap}"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --msgbox "${MSG}" 0 0
      ;;
    k)
      # output pat download link
      if [ ! -f "${TMP_PATH}/patdownloadurl" ]; then
        echo "$(readModelKey "${MODEL}" "builds.${BUILD}.pat.url")" >"${TMP_PATH}/patdownloadurl"
      fi
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --editbox "${TMP_PATH}/patdownloadurl" 10 100
      ;;
    a)
      MSG=""
      MSG+="$(TEXT "This feature will allow you to downgrade the installation by removing the VERSION file from the first partition of all disks.\n")"
      MSG+="$(TEXT "Therefore, please insert all disks before continuing.\n")"
      MSG+="$(TEXT "Warning:\nThis operation is irreversible. Please backup important data. Do you want to continue?")"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --yesno "${MSG}" 0 0
      [ $? -ne 0 ] && return
      (
        mkdir -p /tmp/sdX1
        for I in $(ls /dev/sd*1 2>/dev/null | grep -v ${LOADER_DISK}1); do
          mount ${I} /tmp/sdX1
          [ -f "/tmp/sdX1/etc/VERSION" ] && rm -f "/tmp/sdX1/etc/VERSION"
          [ -f "/tmp/sdX1/etc.defaults/VERSION" ] && rm -f "/tmp/sdX1/etc.defaults/VERSION"
          sync
          umount ${I}
        done
        rm -rf /tmp/sdX1
      ) | dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --progressbox "$(TEXT "Removing ...")" 20 70
      MSG="$(TEXT "Remove VERSION file for all disks completed.")"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --msgbox "${MSG}" 0 0
      ;;
    f)
      rm -f "${TMP_PATH}/opts"
      while read POSITION NAME; do
        [ -z "${POSITION}" -o -z "${NAME}" ] && continue
        echo "${POSITION}" | grep -q "${LOADER_DEVICE_NAME}" && continue
        echo "\"${POSITION}\" \"${NAME}\" \"off\"" >>"${TMP_PATH}/opts"
      done < <(ls -l /dev/disk/by-id/ | sed 's|../..|/dev|g' | grep -E "/dev/sd*" | awk -F' ' '{print $NF" "$(NF-2)}' | sort -uk 1,1)
      while read POSITION NAME; do
        [ -z "${POSITION}" -o -z "${NAME}" ] && continue
        echo "${POSITION}" | grep -q "${LOADER_DEVICE_NAME}" && continue
        echo "\"${POSITION}\" \"${NAME}\" \"off\"" >>"${TMP_PATH}/opts"
      done < <(ls -l /dev/disk/by-path/ | sed 's|../..|/dev|g' | grep -E "/dev/sd*" | awk -F' ' '{print $NF" "$(NF-2)}' | sort -uk 1,1)
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --checklist "$(TEXT "Advanced")" 0 0 0 --file "${TMP_PATH}/opts" \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && return
      RESP=$(<"${TMP_PATH}/resp")
      [ -z "${RESP}" ] && return
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --yesno "$(TEXT "Warning:\nThis operation is irreversible. Please backup important data. Do you want to continue?")" 0 0
      [ $? -ne 0 ] && return
      if [ $(ls /dev/md* | wc -l) -gt 0 ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --yesno "$(TEXT "Warning:\nThe current hds is in raid, do you still want to format them?")" 0 0
        [ $? -ne 0 ] && return
        for I in $(ls /dev/md*); do
          mdadm -S ${I}
        done
      fi
      (
        for I in ${RESP}; do
          mkfs.ext4 -F -O ^metadata_csum ${I}
        done
      ) | dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --progressbox "$(TEXT "Formatting ...")" 20 70
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --msgbox "$(TEXT "Formatting is complete.")" 0 0
      ;;
    x)
      SHADOW_FILE=""
      mkdir -p /tmp/sdX1
      for I in $(ls /dev/sd*1 2>/dev/null | grep -v ${LOADER_DISK}1); do
        mount ${I} /tmp/sdX1
        if [ -f "/tmp/sdX1/etc/shadow" ]; then
          cp "/tmp/sdX1/etc/shadow" "/tmp/shadow_bak"
          SHADOW_FILE="/tmp/shadow_bak"
        fi
        umount ${I}
        [ -n "${SHADOW_FILE}" ] && break
      done
      rm -rf /tmp/sdX1
      if [ -z "${SHADOW_FILE}" ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(TEXT "The installed Syno system not found in the currently inserted disks!")" 0 0
        return
      fi
      ITEMS="$(cat ${SHADOW_FILE} | awk -F ':' '{if ($2 != "*" && $2 != "!!") {print $1;}}')"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --no-items --menu "$(TEXT "Choose a user name")" 0 0 0 ${ITEMS} \
        2>${TMP_PATH}/resp
      [ $? -ne 0 ] && return
      USER=$(<${TMP_PATH}/resp)
      [ -z "${USER}" ] && return
      OLDPASSWD=$(cat ${SHADOW_FILE} | grep "^${USER}:" | awk -F ':' '{print $2}')
      while true; do
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --inputbox "$(printf "$(TEXT "Type a new password for user '%s'")" "${USER}")" 0 0 "${CMDLINE[${NAME}]}" \
          2>${TMP_PATH}/resp
        [ $? -ne 0 ] && break 2
        VALUE="$(<"${TMP_PATH}/resp")"
        [ -n "${VALUE}" ] && break
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(TEXT "Invalid password")" 0 0
      done
      NEWPASSWD=$(python -c "import crypt,getpass;pw=\"${VALUE}\";print(crypt.crypt(pw))")
      (
        mkdir -p /tmp/sdX1
        for I in $(ls /dev/sd*1 2>/dev/null | grep -v ${LOADER_DISK}1); do
          mount ${I} /tmp/sdX1
          sed -i "s|${OLDPASSWD}|${NEWPASSWD}|g" "/tmp/sdX1/etc/shadow"
          sync
          umount ${I}
        done
        rm -rf /tmp/sdX1
      ) | dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --progressbox "$(TEXT "Resetting ...")" 20 70
      [ -f "${SHADOW_FILE}" ] && rm -rf "${SHADOW_FILE}"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --msgbox "$(TEXT "Password reset completed.")" 0 0
      # dialog --backtitle "`backtitle`" --colors --title "$(TEXT "Advanced")" \
      #   --msgbox "$(TEXT "You came early, this function has not been implemented yet, hahaha!")" 0 0
      ;;
    p)
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --yesno "$(TEXT "Warning:\nDo not terminate midway, otherwise it may cause damage to the arpl. Do you want to continue?")" 0 0
      [ $? -ne 0 ] && return
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --infobox "$(TEXT "Persisting ...")" 0 0
      RDXZ_PATH=/tmp/rdxz_tmp
      mkdir -p "${RDXZ_PATH}"
      (
        cd "${RDXZ_PATH}"
        xz -dc <"/mnt/p3/initrd-arpl" | cpio -idm
      ) >/dev/null 2>&1 || true
      rm -rf "${RDXZ_PATH}/opt/arpl"
      cp -rf "/opt" "${RDXZ_PATH}/"
      (
        cd "${RDXZ_PATH}"
        find . 2>/dev/null | cpio -o -H newc -R root:root | xz --check=crc32 >"/mnt/p3/initrd-arpl"
      ) || true
      rm -rf "${RDXZ_PATH}"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --msgbox ""$(TEXT "Persisting is complete.")"" 0 0
      ;;
    d)
      if ! tty | grep -q "/dev/pts"; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(TEXT "This feature is only available when accessed via web/ssh.")" 0 0
        return
      fi
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --msgbox "$(TEXT "Currently, only dts format files are supported. Please prepare and click to confirm uploading.\n(saved in /mnt/p3/users/)")" 0 0
      TMP_UP_PATH=/tmp/users
      rm -rf ${TMP_UP_PATH}
      mkdir -p ${TMP_UP_PATH}
      pushd ${TMP_UP_PATH}
      rz -be
      for F in $(ls -A); do
        USER_FILE=${TMP_UP_PATH}/${F}
        dtc -q -I dts -O dtb ${F} >test.dtb
        RET=$?
        break
      done
      popd
      if [ ${RET} -ne 0 -o -z "${USER_FILE}" ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(TEXT "Not a valid dts file, please try again!")" 0 0
      else
        mkdir -p ${USER_UP_PATH}
        cp -f ${USER_FILE} ${USER_UP_PATH}/${MODEL}.dts
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(TEXT "A valid dts file, Automatically import at compile time.")" 0 0
      fi
      DIRTY=1
      ;;
    b)
      if ! tty | grep -q "/dev/pts"; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(TEXT "This feature is only available when accessed via web/ssh.")" 0 0
        return
      fi
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --yesno "$(TEXT "Warning:\nDo not terminate midway, otherwise it may cause damage to the arpl. Do you want to continue?")" 0 0
      [ $? -ne 0 ] && return
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --infobox "$(TEXT "Backuping...")" 0 0
      rm -f /var/www/data/backup.img.gz # thttpd root path
      dd if="${LOADER_DISK}" bs=1M conv=fsync | gzip >/var/www/data/backup.img.gz
      if [ $? -ne 0]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(TEXT "Failed to generate backup. There may be insufficient memory. Please clear the cache and try again!")" 0 0
        return
      fi
      if [ -z "${SSH_TTY}" ]; then # web
        IP_HEAD="$(ip route show 2>/dev/null | sed -n 's/.* via .* src \(.*\)  metric .*/\1/p' | head -1)"
        echo "http://${IP_HEAD}/backup.img.gz" >${TMP_PATH}/resp
        echo "            ↑                  " >>${TMP_PATH}/resp
        echo "$(TEXT "Click on the address above to download.")" >>${TMP_PATH}/resp
        echo "$(TEXT "Please confirm the completion of the download before closing this window.")" >>${TMP_PATH}/resp
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --editbox "${TMP_PATH}/resp" 10 100
      else # ssh
        sz -be /var/www/data/backup.img.gz
      fi
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --msgbox "$(TEXT "backup is complete.")" 0 0
      rm -f /var/www/data/backup.img.gz
      ;;
    r)
      if ! tty | grep -q "/dev/pts"; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(TEXT "This feature is only available when accessed via web/ssh.")" 0 0
        return
      fi
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --yesno "$(TEXT "Please upload the backup file.\nCurrently, zip(github) and img.gz(backup) compressed file formats are supported.")" 0 0
      [ $? -ne 0 ] && return
      IFTOOL=""
      TMP_UP_PATH=/tmp/users
      rm -rf ${TMP_UP_PATH}
      mkdir -p ${TMP_UP_PATH}
      pushd ${TMP_UP_PATH}
      rz -be
      for F in $(ls -A); do
        USER_FILE="${F}"
        [ "${F##*.}" = "zip" -a $(unzip -l "${TMP_UP_PATH}/${USER_FILE}" | grep -c "\.img$") -eq 1 ] && IFTOOL="zip"
        [ "${F##*.}" = "gz" -a "${F#*.}" = "img.gz" ] && IFTOOL="gzip"
        break
      done
      popd
      if [ -z "${IFTOOL}" -o -z "${TMP_UP_PATH}/${USER_FILE}" ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --msgbox "$(printf "$(TEXT "Not a valid .zip/.img.gz file, please try again!")" "${USER_FILE}")" 0 0
      else
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --yesno "$(TEXT "Warning:\nDo not terminate midway, otherwise it may cause damage to the arpl. Do you want to continue?")" 0 0
        [ $? -ne 0 ] && (
          rm -f ${LOADER_DISK}
          return
        )
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --infobox "$(TEXT "Writing...")" 0 0
        umount /mnt/p1 /mnt/p2 /mnt/p3
        if [ "${IFTOOL}" = "zip" ]; then
          unzip -p "${TMP_UP_PATH}/${USER_FILE}" | dd of="${LOADER_DISK}" bs=1M conv=fsync
        elif [ "${IFTOOL}" = "gzip" ]; then
          gzip -dc "${TMP_UP_PATH}/${USER_FILE}" | dd of="${LOADER_DISK}" bs=1M conv=fsync
        fi
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
          --yesno "$(printf "$(TEXT "Restore bootloader disk with success to %s!\nReboot?")" "${USER_FILE}")" 0 0
        [ $? -ne 0 ] && continue
        reboot
        exit
      fi
      ;;
    o)
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --yesno "$(TEXT "This option only installs opkg package management, allowing you to install more tools for use and debugging. Do you want to continue?")" 0 0
      [ $? -ne 0 ] && return
      (
        wget -O - http://bin.entware.net/x64-k3.2/installer/generic.sh | /bin/sh
        sed -i 's|:/opt/arpl|:/opt/bin:/opt/arpl|' ~/.bashrc
        source ~/.bashrc
        opkg update
        #opkg install python3 python3-pip
      ) | dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --progressbox "$(TEXT "opkg installing ...")" 20 70
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Advanced")" \
        --msgbox "$(TEXT "opkg install is complete. Please reconnect to SSH/web, or execute 'source ~/.bashrc'")" 0 0
      ;;
    e) break ;;
    esac
  done
}

###############################################################################
# Try to recovery a DSM already installed
function tryRecoveryDSM() {
  dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Try recovery DSM")" \
    --infobox "$(TEXT "Trying to recovery a DSM installed system")" 0 0
  if findAndMountDSMRoot; then
    MODEL=""
    BUILD=""
    if [ -f "${DSMROOT_PATH}/.syno/patch/VERSION" ]; then
      eval $(cat ${DSMROOT_PATH}/.syno/patch/VERSION | grep unique)
      eval $(cat ${DSMROOT_PATH}/.syno/patch/VERSION | grep base)
      if [ -n "${unique}" ]; then
        while read F; do
          M="$(basename ${F})"
          M="${M::-4}"
          UNIQUE=$(readModelKey "${M}" "unique")
          [ "${unique}" = "${UNIQUE}" ] || continue
          # Found
          modelMenu "${M}"
        done < <(find "${MODEL_CONFIG_PATH}" -maxdepth 1 -name \*.yml | sort)
        if [ -n "${MODEL}" ]; then
          buildMenu ${base}
          if [ -n "${BUILD}" ]; then
            cp "${DSMROOT_PATH}/.syno/patch/zImage" "${SLPART_PATH}"
            cp "${DSMROOT_PATH}/.syno/patch/rd.gz" "${SLPART_PATH}"
            MSG="$(printf "$(TEXT "Found a installation:\nModel: %s\nBuildnumber: %s")" "${MODEL}" "${BUILD}")"
            SN=$(_get_conf_kv SN "${DSMROOT_PATH}/etc/synoinfo.conf")
            if [ -n "${SN}" ]; then
              writeConfigKey "sn" "${SN}" "${USER_CONFIG_FILE}"
              MSG+="$(printf "$(TEXT "\nSerial: %s")" "${SN}")"
            fi
            dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Try recovery DSM")" \
              --msgbox "${MSG}" 0 0
          fi
        fi
      fi
    fi
  else
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Try recovery DSM")" \
      --msgbox "$(TEXT "Unfortunately I couldn't mount the DSM partition!")" 0 0
  fi
}

###############################################################################
# Permits user edit the user config
function editUserConfig() {
  while true; do
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Edit with caution")" \
      --editbox "${USER_CONFIG_FILE}" 0 0 2>"${TMP_PATH}/userconfig"
    [ $? -ne 0 ] && return
    mv "${TMP_PATH}/userconfig" "${USER_CONFIG_FILE}"
    ERRORS=$(yq eval "${USER_CONFIG_FILE}" 2>&1)
    [ $? -eq 0 ] && break
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Edit with caution")" \
      --msgbox "${ERRORS}" 0 0
  done
  OLDMODEL=${MODEL}
  OLDBUILD=${BUILD}
  MODEL="$(readConfigKey "model" "${USER_CONFIG_FILE}")"
  BUILD="$(readConfigKey "build" "${USER_CONFIG_FILE}")"
  SN="$(readConfigKey "sn" "${USER_CONFIG_FILE}")"

  if [ "${MODEL}" != "${OLDMODEL}" -o "${BUILD}" != "${OLDBUILD}" ]; then
    # Remove old files
    rm -f "${MOD_ZIMAGE_FILE}"
    rm -f "${MOD_RDGZ_FILE}"
  fi
  DIRTY=1
}

###############################################################################
# Calls boot.sh to boot into DSM kernel/ramdisk
function boot() {
  [ ${DIRTY} -eq 1 ] && dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Alert")" \
    --yesno "$(TEXT "Config changed, would you like to rebuild the loader?")" 0 0
  if [ $? -eq 0 ]; then
    make || return
  fi
  boot.sh
}

###############################################################################
# Shows language to user choose one
function languageMenu() {
  ITEMS="$(ls /usr/share/locale)"
  dialog --backtitle "$(backtitle)" --colors \
    --default-item "${LAYOUT}" --no-items --menu "$(TEXT "Choose a language")" 0 0 0 ${ITEMS} 2>${TMP_PATH}/resp
  [ $? -ne 0 ] && return
  resp=$(cat ${TMP_PATH}/resp 2>/dev/null)
  [ -z "${resp}" ] && return
  LANGUAGE=${resp}
  echo "${LANGUAGE}.UTF-8" >${BOOTLOADER_PATH}/.locale
  export LANG="${LANGUAGE}.UTF-8"
}

###############################################################################
# Shows available keymaps to user choose one
function keymapMenu() {
  OPTIONS="azerty bepo carpalx colemak dvorak fgGIod neo olpc qwerty qwertz"
  dialog --backtitle "$(backtitle)" --colors \
    --default-item "${LAYOUT}" --no-items --menu "$(TEXT "Choose a layout")" 0 0 0 ${OPTIONS} \
    2>${TMP_PATH}/resp
  [ $? -ne 0 ] && return
  LAYOUT="$(<${TMP_PATH}/resp)"
  OPTIONS=""
  while read KM; do
    OPTIONS+="${KM::-7} "
  done < <(
    cd /usr/share/keymaps/i386/${LAYOUT}
    ls *.map.gz
  )
  dialog --backtitle "$(backtitle)" --colors \
    --default-item "${KEYMAP}" --no-items --menu "$(TEXT "Choice a keymap")" 0 0 0 ${OPTIONS} \
    2>${TMP_PATH}/resp
  [ $? -ne 0 ] && return
  resp=$(cat ${TMP_PATH}/resp 2>/dev/null)
  [ -z "${resp}" ] && return
  KEYMAP=${resp}
  writeConfigKey "layout" "${LAYOUT}" "${USER_CONFIG_FILE}"
  writeConfigKey "keymap" "${KEYMAP}" "${USER_CONFIG_FILE}"
  loadkeys /usr/share/keymaps/i386/${LAYOUT}/${KEYMAP}.map.gz
}

# 1 - ext name
# 2 - current version
# 3 - repo url
# 4 - attachment name
function downloadExts() {
  PROXY="$(readConfigKey "proxy" "${USER_CONFIG_FILE}")"
  [ -n "${PROXY}" ] && [[ "${PROXY: -1}" != "/" ]] && PROXY="${PROXY}/"
  T="$(printf "$(TEXT "Update %s")" "${1}")"

  dialog --backtitle "$(backtitle)" --colors --title "${T}" \
    --infobox "$(TEXT "Checking last version")" 0 0
  # TAG=`curl -skL "${PROXY}https://api.github.com/repos/wjz304/arpl-addons/releases/latest" | grep "tag_name" | awk '{print substr($2, 2, length($2)-3)}'`
  # In the absence of authentication, the default API access count for GitHub is 60 per hour, so removing the use of api.github.com
  LATESTURL="$(curl -skL -w %{url_effective} -o /dev/null "${PROXY}${3}/releases/latest")"
  TAG="${LATESTURL##*/}"
  [ "${TAG:0:1}" = "v" ] && TAG="${TAG:1}"
  if [ -z "${TAG}" ]; then
    dialog --backtitle "$(backtitle)" --colors --title "${T}" \
      --msgbox "$(TEXT "Error checking new version")" 0 0
    return 1
  fi
  if [ "${2}" = "${TAG}" ]; then
    dialog --backtitle "$(backtitle)" --colors --title "${T}" \
      --yesno "$(printf "$(TEXT "No new version. Actual version is %s\nForce update?")" "${2}")" 0 0
    [ $? -ne 0 ] && return 1
  fi
  dialog --backtitle "$(backtitle)" --colors --title "${T}" \
    --infobox "$(TEXT "Downloading last version")" 0 0
  rm -f "/tmp/${4}.zip"
  STATUS=$(curl -kL -w "%{http_code}" "${PROXY}${3}/releases/download/${TAG}/${4}.zip" -o "/tmp/${4}.zip")
  if [ $? -ne 0 -o ${STATUS} -ne 200 ]; then
    dialog --backtitle "$(backtitle)" --colors --title "${T}" \
      --msgbox "$(TEXT "Error downloading new version")" 0 0
    return 1
  fi
  return 0
}

# 1 - ext name
function updateArpl() {
  T="$(printf "$(TEXT "Update %s")" "${1}")"
  dialog --backtitle "$(backtitle)" --colors --title "${T}" \
    --infobox "$(TEXT "Extracting last version")" 0 0
  unzip -oq /tmp/update.zip -d /tmp
  if [ $? -ne 0 ]; then
    dialog --backtitle "$(backtitle)" --colors --title "${T}" \
      --msgbox "$(TEXT "Error extracting update file")" 0 0
    continue
  fi
  # Check checksums
  (cd /tmp && sha256sum --status -c sha256sum)
  if [ $? -ne 0 ]; then
    dialog --backtitle "$(backtitle)" --colors --title "${T}" \
      --msgbox "$(TEXT "Checksum do not match!")" 0 0
    continue
  fi
  # Check conditions
  if [ -f "/tmp/update-check.sh" ]; then
    chmod +x /tmp/update-check.sh
    /tmp/update-check.sh
    if [ $? -ne 0 ]; then
      dialog --backtitle "$(backtitle)" --colors --title "${T}" \
        --msgbox "$(TEXT "The current version does not support upgrading to the latest update.zip. Please remake the bootloader disk!")" 0 0
      continue
    fi
  fi
  dialog --backtitle "$(backtitle)" --colors --title "${T}" \
    --infobox "$(TEXT "Installing new files")" 0 0
  # Process update-list.yml
  while read F; do
    [ -f "${F}" ] && rm -f "${F}"
    [ -d "${F}" ] && rm -Rf "${F}"
  done < <(readConfigArray "remove" "/tmp/update-list.yml")
  while IFS=': ' read KEY VALUE; do
    if [ "${KEY: -1}" = "/" ]; then
      rm -Rf "${VALUE}"
      mkdir -p "${VALUE}"
      tar -zxf "/tmp/$(basename "${KEY}").tgz" -C "${VALUE}"
    else
      mkdir -p "$(dirname "${VALUE}")"
      mv "/tmp/$(basename "${KEY}")" "${VALUE}"
    fi
  done < <(readConfigMap "replace" "/tmp/update-list.yml")
  dialog --backtitle "$(backtitle)" --colors --title "${T}" \
    --yesno "$(printf "$(TEXT "Arpl updated with success to %s!\nReboot?")" "${TAG}")" 0 0
  [ $? -ne 0 ] && continue
  arpl-reboot.sh config
}

# 1 - ext name
function updateExts() {
  T="$(printf "$(TEXT "Update %s")" "${1}")"
  dialog --backtitle "$(backtitle)" --colors --title "${T}" \
    --infobox "$(TEXT "Extracting last version")" 0 0
  if [ "${1}" = "addons" ]; then
    rm -rf /tmp/addons
    mkdir -p /tmp/addons
    unzip /tmp/addons.zip -d /tmp/addons >/dev/null 2>&1
    dialog --backtitle "$(backtitle)" --colors --title "${T}" \
      --infobox "$(printf "$(TEXT "Installing new %s")" "${1}")" 0 0
    rm -Rf "${ADDONS_PATH}/"*
    [ -f /tmp/addons/VERSION ] && cp -f /tmp/addons/VERSION ${ADDONS_PATH}/
    for PKG in $(ls /tmp/addons/*.addon); do
      ADDON=$(basename ${PKG} | sed 's|.addon||')
      rm -rf "${ADDONS_PATH}/${ADDON}"
      mkdir -p "${ADDONS_PATH}/${ADDON}"
      tar -xaf "${PKG}" -C "${ADDONS_PATH}/${ADDON}" >/dev/null 2>&1
    done
  elif [ "${1}" = "modules" ]; then
    rm "${MODULES_PATH}/"*
    unzip /tmp/modules.zip -d "${MODULES_PATH}" >/dev/null 2>&1
    # Rebuild modules if model/buildnumber is selected
    if [ -n "${PLATFORM}" -a -n "${KVER}" ]; then
      writeConfigKey "modules" "{}" "${USER_CONFIG_FILE}"
      while read ID DESC; do
        writeConfigKey "modules.${ID}" "" "${USER_CONFIG_FILE}"
      done < <(getAllModules "${PLATFORM}" "${KVER}")
    fi
  elif [ "${1}" = "LKMs" ]; then
    rm -rf "${LKM_PATH}/"*
    unzip /tmp/rp-lkms.zip -d "${LKM_PATH}" >/dev/null 2>&1
  fi
  DIRTY=1
  dialog --backtitle "$(backtitle)" --colors --title "${T}" \
    --msgbox "$(printf "$(TEXT "%s updated with success!")" "${1}")" 0 0
}

###############################################################################
function updateMenu() {
  PLATFORM="$(readModelKey "${MODEL}" "platform")"
  KVER="$(readModelKey "${MODEL}" "builds.${BUILD}.kver")"
  PROXY="$(readConfigKey "proxy" "${USER_CONFIG_FILE}")"
  [ -n "${PROXY}" ] && [[ "${PROXY: -1}" != "/" ]] && PROXY="${PROXY}/"
  while true; do
    dialog --backtitle "$(backtitle)" --colors \
      --menu "$(TEXT "Choose a option")" 0 0 0 \
      a "$(TEXT "Update arpl")" \
      d "$(TEXT "Update addons")" \
      m "$(TEXT "Update modules")" \
      l "$(TEXT "Update LKMs")" \
      p "$(TEXT "Set proxy server")" \
      u "$(TEXT "Local upload")" \
      e "$(TEXT "Exit")" \
      2>${TMP_PATH}/resp
    [ $? -ne 0 ] && return
    case "$(<${TMP_PATH}/resp)" in
    a)
      T="$(printf "$(TEXT "Update %s")" "$(TEXT "arpl")")"
      CURVER="${ARPL_VERSION:-0}"
      downloadExts "arpl" ${CURVER} "https://github.com/wjz304/arpl-i18n" "update"
      [ $? -ne 0 ] && continue
      updateArpl "arpl"
      ;;

    d)
      T="$(printf "$(TEXT "Update %s")" "$(TEXT "addons")")"
      CURVER="$(cat "/mnt/p3/addons/VERSION" 2>/dev/null)"
      downloadExts "addons" "${CURVER:-0}" "https://github.com/wjz304/arpl-addons" "addons"
      [ $? -ne 0 ] && continue
      updateExts "addons"
      ;;

    m)
      T="$(printf "$(TEXT "Update %s")" "$(TEXT "modules")")"
      CURVER="$(cat "/mnt/p3/modules/VERSION" 2>/dev/null)"
      downloadExts "modules" "${CURVER:-0}" "https://github.com/wjz304/arpl-modules" "modules"
      [ $? -ne 0 ] && continue
      updateExts "modules"
      ;;

    l)
      T="$(printf "$(TEXT "Update %s")" "$(TEXT "LKMs")")"
      CURVER="$(cat "/mnt/p3/lkms/VERSION" 2>/dev/null)"
      downloadExts "LKMs" "${CURVER:-0}" "https://github.com/wjz304/redpill-lkm" "rp-lkms"
      [ $? -ne 0 ] && continue
      updateExts "LKMs"
      ;;

    p)
      RET=1
      while true; do
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Update")" \
          --inputbox "$(TEXT "Please enter a proxy server url")" 0 0 "${PROXY}" \
          2>${TMP_PATH}/resp
        RET=$?
        [ ${RET} -ne 0 ] && break
        PROXY=$(cat ${TMP_PATH}/resp)
        if [ -z "${PROXYSERVER}" ]; then
          break
        elif [[ "${PROXYSERVER}" =~ "^(https?|ftp)://[^\s/$.?#].[^\s]*$" ]]; then
          break
        else
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Update")" \
            --yesno "$(TEXT "Invalid proxy server url, continue?")" 0 0
          RET=$?
          [ ${RET} -eq 0 ] && break
        fi
      done
      [ ${RET} -eq 0 ] && writeConfigKey "proxy" "${PROXY}" "${USER_CONFIG_FILE}"
      ;;

    u)
      if ! tty | grep -q "/dev/pts"; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Update")" \
          --msgbox "$(TEXT "This feature is only available when accessed via web/ssh.")" 0 0
        return
      fi
      MSG=""
      MSG+="$(TEXT "Please keep the attachment name consistent with the attachment name on Github.\n")"
      MSG+="$(TEXT "Upload update.zip will update arpl.\n")"
      MSG+="$(TEXT "Upload addons.zip will update Addons.\n")"
      MSG+="$(TEXT "Upload modules.zip will update Modules.\n")"
      MSG+="$(TEXT "Upload rp-lkms.zip will update LKMs.\n")"
      dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Update")" \
        --msgbox "${MSG}" 0 0
      EXTS=("update.zip" "addons.zip" "modules.zip" "rp-lkms.zip")
      TMP_UP_PATH=/tmp/users
      USER_FILE=""
      rm -rf ${TMP_UP_PATH}
      mkdir -p ${TMP_UP_PATH}
      pushd ${TMP_UP_PATH}
      rz -be
      for F in $(ls -A); do
        for I in ${EXTS[@]}; do
          [[ "${I}" == "${F}" ]] && USER_FILE=${F}
        done
        break
      done
      popd
      if [ -z "${USER_FILE}" ]; then
        dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Update")" \
          --msgbox "$(TEXT "Not a valid file, please try again!")" 0 0
      else
        rm /tmp/${USER_FILE}
        mv ${TMP_UP_PATH}/${USER_FILE} /tmp/${USER_FILE}
        if [ "${USER_FILE}" = "update.zip" ]; then
          updateArpl "arpl"
        elif [ "${USER_FILE}" = "addons.zip" ]; then
          updateExts "addons"
        elif [ "${USER_FILE}" = "modules.zip" ]; then
          updateExts "modules"
        elif [ "${USER_FILE}" = "rp-lkms.zip" ]; then
          updateExts "LKMs"
        else
          dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Update")" \
            --msgbox "$(TEXT "Not a valid file, please try again!")" 0 0
        fi
      fi
      ;;
    e) return ;;
    esac
  done
}

###############################################################################
###############################################################################

if [ "x$1" = "xb" -a -n "${MODEL}" -a -n "${BUILD}" -a loaderIsConfigured ]; then
  install-addons.sh
  make
  boot && exit 0 || sleep 5
fi
# Main loop
NEXT="m"
while true; do
  echo "m \"$(TEXT "Choose a model")\"" >"${TMP_PATH}/menu"
  if [ -n "${MODEL}" ]; then
    echo "n \"$(TEXT "Choose a Build Number")\"" >>"${TMP_PATH}/menu"
    if [ -n "${BUILD}" ]; then
      echo "a \"$(TEXT "Addons")\"" >>"${TMP_PATH}/menu"
      echo "o \"$(TEXT "Modules")\"" >>"${TMP_PATH}/menu"
      echo "x \"$(TEXT "Cmdline menu")\"" >>"${TMP_PATH}/menu"
      echo "i \"$(TEXT "Synoinfo menu")\"" >>"${TMP_PATH}/menu"
    fi
  fi
  echo "v \"$(TEXT "Advanced menu")\"" >>"${TMP_PATH}/menu"
  if [ -n "${MODEL}" ]; then
    if [ -n "${BUILD}" ]; then
      echo "d \"$(TEXT "Build the loader")\"" >>"${TMP_PATH}/menu"
    fi
  fi
  if loaderIsConfigured; then
    echo "b \"$(TEXT "Boot the loader")\"" >>"${TMP_PATH}/menu"
  fi
  echo "l \"$(TEXT "Choose a language")\"" >>"${TMP_PATH}/menu"
  echo "k \"$(TEXT "Choose a keymap")\"" >>"${TMP_PATH}/menu"
  if [ ${CLEARCACHE} -eq 1 -a -d "${CACHE_PATH}/dl" ]; then
    echo "c \"$(TEXT "Clean disk cache")\"" >>"${TMP_PATH}/menu"
  fi
  echo "p \"$(TEXT "Update menu")\"" >>"${TMP_PATH}/menu"
  echo "e \"$(TEXT "Exit")\"" >>"${TMP_PATH}/menu"

  dialog --backtitle "$(backtitle)" --colors \
    --default-item ${NEXT} --menu "$(TEXT "Choose the option")" 0 0 0 --file "${TMP_PATH}/menu" \
    2>${TMP_PATH}/resp
  [ $? -ne 0 ] && break
  case $(<"${TMP_PATH}/resp") in
  m)
    modelMenu
    NEXT="n"
    ;;
  n)
    buildMenu
    NEXT="a"
    ;;
  a)
    addonMenu
    NEXT="o"
    ;;
  o)
    moduleMenu
    NEXT="x"
    ;;
  x)
    cmdlineMenu
    NEXT="i"
    ;;
  i)
    synoinfoMenu
    NEXT="v"
    ;;
  v)
    advancedMenu
    NEXT="d"
    ;;
  d)
    make
    NEXT="b"
    ;;
  b)
    boot && exit 0 || sleep 5
    ;;
  l)
    languageMenu
    ;;
  k)
    keymapMenu
    ;;
  c)
    dialog --backtitle "$(backtitle)" --colors --title "$(TEXT "Cleaning")" \
      --prgbox "rm -rfv \"${CACHE_PATH}/dl\"" 0 0
    ;;
  p)
    updateMenu
    ;;
  e)
    break
    ;;
  esac
done
clear
echo -e "$(TEXT "Call \033[1;32mmenu.sh\033[0m to return to menu")"
