squashDevice(){
  echo "$(mount | grep -F squashfs | head -n1 | awk '{print $1;}')"
}

umountSquash(){
  mountpoint -q "$squashfs" && umount "$squashfs"
}

mountSquash(){
  [ -d "$(dirname "$squashfs")" ] || return 1
  umountSquash
  mkdir -p "$squashfs"
  local sdev="$(squashDevice)"
  [ -z "$sdev" ] && return 1
  mount -o ro,noatime "$sdev" "$squashfs"
}

mount_base(){
  mount -t tmpfs -o mode=0755,nosuid,nodev tmpfs "$mountpoint/var"
  mkdir -p "$mountpoint/var/lib"
  mount -o defaults,nosuid,nodev,noatime /dev/nandc "$mountpoint/var/lib"
  mountSquash
}

umount_base(){
  sync
  umountSquash
  umount "$mountpoint/var/lib"
  umount "$mountpoint/var"
}

remove_old(){
  [ -d "$1" ] || return 1
  [ -f "$modpath/script/removed" ] || return 0
  local basepath="$1"
  source "$modpath/script/removed"
}

mod_repair_etc(){
  [ -d "$rootfs" ] || return 0
  remove_old "$rootfs"

  local etc="$temppath/etc"
  rm -rf "$etc"
  mkdir -p "$etc"
  copy "$mountpoint/etc/" "$etc/"
  if [ "$1" == "full" ]; then
    rm -f "$modpath/rootfs/etc/preinit.d/p0000_config"
    copy "$modpath/rootfs/etc/" "$etc/"
  else
    copy "$rootfs/etc/inittab" "$etc/inittab"
  fi
  copy "$etc/" "$rootfs/etc/"
  rm -rf "$etc"
}

mod_repair_modules(){
  [ -d "$mountpoint/lib/modules/$(uname -r)" ] && restore "/lib/modules/$(uname -r)/"
}

mount_move(){
  local tmpmount="/$2"
  if [ "$1" == "l" ]; then
    mountpoint -q "$mountpoint$tmpmount" || return 1
    mkdir -p "$tmpmount"
    mount --move "$mountpoint$tmpmount" "$tmpmount"
  else
    mountpoint -q "$tmpmount" || return 1
    mount --move "$tmpmount" "$mountpoint$tmpmount"
    rmdir --ignore-fail-on-non-empty "$tmpmount"
  fi
}

remount_root(){
  mount_move l media
  mount_move l var

  stopTask decodepng
  stopTask aplay
  umount "$mountpoint" || (mkdir -p "/var/fuckup" && mount --move "$mountpoint" "/var/fuckup")

  local loopfile="/var${1##$mountpoint/var}"
  [ -f "$loopfile" ] || loopfile="/media${1##$mountpoint/media}"
  if ! mount -o loop,ro,noatime "$loopfile" "$mountpoint"; then
    local preinitf="/${preinit##$mountpoint/}"
    local preinit="$preinitf"
    cfg_firmware="_nand_"
    save_config
    shutdown
  fi

  mount_move r var
  mount_move r media
}

checkFirmware(){
  [ -f "$1" ] || return 1
  [ "$(hexdump -e '1/4 "%u"' -s 0 -n 4 "$1")" == "1936814952" ] || return 1
  return 0
}

currentFirmware(){
  local firmware="$(losetup -a | awk '{print $3'})"
  if ! [ -z "$firmware" ]; then
    echo "$firmware"
    return 0
  fi
  if [ -b "/dev/mapper/root-crypt" ]; then
    echo "_nand_"
    return 0
  fi
  return 1
}

overmountModules(){
  if [ ! -d "$mountpoint/lib/modules/$(uname -r)" ]; then
    if [ -d "$rootfs/lib/modules/$(uname -r)" ]; then
      overmount "/lib/modules"
    else
      echo "no modules for loaded kernel $(uname -r)"
    fi
  fi
}

loadFirmware(){
  [ -z "$(mount | grep -F loop0)" ] || return 1

  local firmware="$mountpoint$cfg_firmware"
  if ! checkFirmware "$firmware"; then
    [ "$cfg_firmware" == "auto" ] || return 0
    [ -d "$extfirmwarepath" ] && \
    firmware="$(find "$extfirmwarepath" -type f -name "*.hsqs" | sort | head -n1)"
    if ! checkFirmware "$firmware"; then
      [ -d "$firmwarepath" ] && \
      firmware="$(find "$firmwarepath" -type f -name "*.hsqs" | sort | head -n1)"
    fi
  fi
  checkFirmware "$firmware" || return 0

  mod_repair_modules
  umountSquash
  remount_root "$firmware"
  cryptsetup close root-crypt
  mountSquash
  overmountModules
}

shutdown(){
  echo "The system is going down NOW!"
  sync
  umount -a -r 2>/dev/null
  poweroff -f
  while :;do sleep 1;:;done
}

reboot(){
  echo "Restarting system."
  sync
  umount -a -r 2>/dev/null
  /bin/reboot -f
  while :;do sleep 1;:;done
}

early_getty(){
  cd "$modpath/transfer"
  getty -ni 115200 ttyS0 -l /bin/sh
  cd /
}

copy(){
  # we must create target directory
  local dirname="$(dirname "$2")"
  mkdir -p "$dirname"
  rsync -ac "$1" "$2"
}

copy_mask(){
  # this function is unsafe, avoid spaces in filenames!
  local dirname="$(dirname "$2")"
  mkdir -p "$dirname"
  rsync -ac $1 "$2"
}

restore(){
  if mountpoint -q "$squashfs" && [ -e "$squashfs$1" ]; then
    copy "$squashfs$1" "$rootfs$1"
  else
    copy "$mountpoint$1" "$rootfs$1"
  fi
}

mount_bind(){
  if mountpoint -q "$2"; then
    umount "$2" || umount -f "$2"
  else
    umount "$2" 2>/dev/null
  fi
  mount -o bind "$1" "$2"
}

overmount(){
  if [ "$#" == "1" ]; then
    echo "overmounting $1"
    mount_bind "$rootfs$1" "$mountpoint$1" && return 0
  fi
  if [ "$#" == "2" ]; then
    echo "overmounting $1 on $2"
    mount_bind "$rootfs$1" "$mountpoint$2" && return 0
  fi
  echo "overmounting failed"
  return 1
}

containsGames(){
  [ -d "$1" ] || return 1
  [ -z "$(cd "$1";ls CLV-* 2>/dev/null)" ] && return 1
  return 0
}

linkGames(){
  local games="$mountpoint/media/$modname/games${1##$installpath/games}"
  [ -d "$games" ] || games="$installpath/games${1##$rootfs$gamepath}"
  [ -d "$games" ] || games="$1"
  games="${games##$mountpoint}"
  rm -f "$mountpoint/var/games"
  ln -s "$games" "$mountpoint/var/games"
  return 0
}

softwareCheck(){
  sftype="nes"
  sfregion="usa"
  [ "$(cat "$mountpoint/etc/clover/boardtype")" == "dp-shvc" ] && sftype="snes"
  [ "$(cat "$mountpoint/etc/clover/REGION")" == "JPN" ] && sfregion="jpn"
  [ "$(cat "$mountpoint/etc/clover/REGION")" == "EUR" ] && sfregion="eur"

  local feck="$mountpoint$profilepath/home-menu/save/system-save.json"
  if [ "$sftype" == "snes" ]; then
    gamepath="/usr/share/games"
    [ -f "$feck" ] && mv -f "$feck" "$feck.nes"
    [ -d "$feck.snes" ] && rm -rf "$feck" && mv "$feck.snes" "$feck"
  else
    gamepath="/usr/share/games/nes/kachikachi"
    [ -d "$feck" ] && rm -rf "$feck.snes" && mv "$feck" "$feck.snes"
    [ -f "$feck.nes" ] && mv -f "$feck.nes" "$feck"
  fi

  mkdir -p "$mountpoint$profilepath/$modname"

  rm -f "$mountpoint/var/saves"
  ln -s "$profilepath" "$mountpoint/var/saves"
}

repair_fonts(){
  if [ "$sftype" == "nes" ]; then
    mkdir -p "$1" || return 1    
    [ -w "$1" ] || return 1
    ( [ $cfg_fontfix_enabled == "y" ] && copy "$rootfs/usr/share/fonts/title.fnt" "$1/" ) || copy "$rootfs$gamepath/title.fnt" "$1/" || copy "$squashfs$gamepath/title.fnt" "$1/"
    copy "$squashfs$gamepath/copyright.fnt" "$1/"
  fi
}

repair_games(){
  [ -f "$1/.repair.flag" ] || return 0
  [ -w "$1" ] || return 1
  cat "$rootfs/etc/pleasewait.fb" | gunzip -c - > "/dev/fb0"
  echo repairing games...
  local usesymlink=''
  ln -s / "$1/symlinktest" 2>/dev/null && rm -f "$1/symlinktest" && usesymlink='y'

  ls -1 "$squashfs$gamepath" | grep CLV- | while read code
  do
    local gamedir=$(find "$1" -name $code -type d)
    local squashgamedir="$squashfs$gamepath/$code"

    [ -d "$gamedir" ] || continue

    if [ ! -f "$gamedir/$code.desktop" ]; then
      cp "$squashgamedir/$code.desktop" "$gamedir/"
      [ "$sftype" == "nes" ] && \
        sed -i -e 's#/usr/bin/clover-kachikachi#/bin/clover-kachikachi-wr#g' "$gamedir/$code.desktop"
      [ "$sftype" == "snes" ] && \
        sed -i -e 's#/usr/bin/clover-canoe-shvc#/bin/clover-canoe-shvc-wr#g' "$gamedir/$code.desktop"
    fi

    [ -d "$gamedir/autoplay/" ] && ([ "$(ls -A "$gamedir/autoplay/")" ] || rm -rf "$gamedir/autoplay/")
    [ -d "$gamedir/pixelart/" ] && ([ "$(ls -A "$gamedir/pixelart/")" ] || rm -rf "$gamedir/pixelart/")

    find "$squashgamedir" -maxdepth 1 | sed -n '1!p' | while read squashfile
    do
      if [ "$usesymlink" == "y" ]; then
        ln -s "${squashfile##$mountpoint}" "$gamedir/" > /dev/null 2>&1
      else
        rsync -a --ignore-existing "$squashfile" "$gamedir/" > /dev/null 2>&1
      fi
    done
  done

  ls -1 "$1" | while read f; do repair_fonts "$1/$f"; done

  rm -f "$1/.repair.flag" && sync && reboot
}

checkGamepath(){
  containsGames "$1" && echo "$1" && return 0
  containsGames "$1/000" && echo "$1" && return 0
  return 1
}

checkPath(){
  [ -d "$1" ] && echo "$1" && return 0
  return 1
}

findGameSyncStorage(){
  checkPath "$mountpoint/media/hakchi/games" && return 0
  mkdir -p "$installpath/games"
  checkPath "$installpath/games" && return 0
  return 1
}

findGameStorage(){
  checkGamepath "$mountpoint/media/hakchi/games/$sftype-$sfregion" && return 0
  checkGamepath "$mountpoint/media/hakchi/games/$sftype" && return 0
  checkGamepath "$mountpoint/media/hakchi/games" && return 0

  checkGamepath "$installpath/games/$sftype-$sfregion" && return 0
  checkGamepath "$installpath/games/$sftype" && return 0
  checkGamepath "$installpath/games" && return 0
  return 1
}

overmount_games(){
  local gameStorage="$(findGameStorage)" || return 0
  local state_path="$mountpoint$profilepath/$modname"
  local state_file="$state_path/menu"
  local menu_code="$1"
  [ -z "$1" ] && menu_code="000"
  [ -z "$1" ] && [ -f "$state_file" ] && menu_code="$(cat "$state_file")"
  if ! containsGames "$gameStorage/$menu_code"; then
    menu_code="000"
    containsGames "$gameStorage/$menu_code" || menu_code=""
  fi
  echo "menu code: $menu_code"
  if containsGames "$gameStorage/$menu_code"; then
    mount_bind "$gameStorage/$menu_code" "$mountpoint/$gamepath" && \
    repair_games "$gameStorage"
    repair_fonts "$gameStorage/$menu_code" && \
    linkGames "$gameStorage/$menu_code" && \
    return 0
  else
    echo "no romz found at: $gameStorage/$menu_code"
  fi
  linkGames "$gamepath"
  return 1
}

uilist(){
  lsof -n | grep -F "/dev/fb0" | awk '{print $1}' | sort -u
}

uikill(){
  [ -z "$1" ] && return 1
  uilist | xargs -r kill -s "$1"
}

uistop(){
  killall -s KILL clover-mcp 1>/dev/null 2>&1
  uikill TERM
}

uistart(){
  uistop
  sh "/etc/init.d/S81clover-mcp" start
}

uipause(){
  uikill STOP
}

uiresume(){
  uikill CONT
}

gameover(){
  poweroff
}

printSoftwareInfo(){
  echo "software=$sftype"
  echo "region=$sfregion"
}

stopTask(){
  local tokill="false"
  if [ "$1" = "-f" ]; then
    tokill="true"
    shift
  fi
  local pid="$(pidof "$1")"
  [ -z "$pid" ] && return 0
  "$tokill" && kill "$pid"
  wait "$pid"
}

waitTask(){
  local A="stopTask ${1+"$@"}"
  trap "$A" EXIT
}

showImage(){
  local image="$(eval echo "$1")"
  [ -f "$image" ] || image="$rootfs$1"
  [ -f "$image" ] || image="$squashfs$1"
  [ -f "$image" ] || return 1
  decodepng "$image" > "/dev/fb0" &
  waitTask decodepng
  return 0
}

playSound(){
  local wavfile="$(eval echo "$1")"
  [ -f "$wavfile" ] || wavfile="$rootfs$1"
  [ -f "$wavfile" ] || wavfile="$squashfs$1"
  [ -f "$wavfile" ] || return 1
  aplay -q "$wavfile" &
  waitTask aplay
  return 0
}

hwmon(){
  cat "/sys/devices/virtual/hwmon/hwmon1/temp1_input"
}

usedBlockSpace(){
  local path="$1"
  [ -e "$path" ] || path="$rootfs"
  local line="$(df -k "$path" | tail -n1)"
  local used="$(echo "$line" | awk '{print $3}')"
  local available="$(echo "$line" | awk '{print $4}')"
  local total="$(($used+$available))"
  echo "$used $total"
}

freeBlockSpace(){
  local path="$1"
  [ -e "$path" ] || path="$rootfs"
  df -k "$path" | tail -n1 | awk '{print $4}'
}

usedSpace(){
# Warning: SLOW
  local path="$1"
  [ -e "$path" ] || path="$rootfs"
  du -ksx "$path" | awk '{print $1}'
}

usedBy(){
  case "$1" in
    games)
      usedSpace "$(findGameSyncStorage)"
      ;;
    saves)
      usedSpace "$profilepath"
      ;;
    mods)
      usedSpace "$rootfs"
      ;;
    firmware)
      usedSpace "$firmwarepath"
      ;;
    *)
      echo "Usage: usedBy {games|saves|mods|firmware}"
      return 1
  esac
}
