#!/bin/bash

###############################################################################
# UExtract v4.14 by JakeSFR (a.k.a. SFR)'2013-2024                            #
# GNU GPL v2 applies                                                          #
###############################################################################

set -o pipefail

APPNAME="UExtract v4.14"

MYPATH="$(readlink -f "$0")"
MYDIRPATH="$(dirname "$MYPATH")"
MYNAME="$(basename "$MYPATH")"

PATH="${MYDIRPATH}/resources:${PATH}"
LD_LIBRARY_PATH="${MYDIRPATH}/resources:${LD_LIBRARY_PATH}"
OLDDIR="$(pwd)"
WORKDIR="/tmp/uextract.${USER}.${$}"

trap 'func_cleanup; exit' 0 INT HUP TERM	# Clean up things on exit

export TEXTDOMAINDIR="${MYDIRPATH}/locale"
export TEXTDOMAIN=uextract
export OUTPUT_CHARSET=UTF-8

################################ FUNCTIONS ####################################

func_die() {
	[ "$@" ] && echo -e "$@"
	exit 1
}

# -----------------------------------------------------------------------------

func_exists() {
	if [ "$1" ]; then
		type "$1" >/dev/null 2>&1 && return 0 || return 1
	else
		return 1
	fi
}

# -----------------------------------------------------------------------------

func_cleanup() {
	local cmnd
	
	cd "$OLDDIR" 2>/dev/null

	# Unmount work dir (if mounted)
	if [ -d "$WORKDIR" ]; then
		umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null
	fi

	# Disassociate loop
	if [ "$FILEPATH" ] && [ "$FREELOOP" ] && func_exists $LOSETUP; then
		$LOSETUP -d "$FREELOOP" 2>/dev/null
	fi

	# Unmount NBD device
	if [ "$FILEPATH" ] && [ "$NBD_DEV" ] && func_exists qemu-nbd; then
		qemu-nbd -d /dev/nbd${NBD_DEV} >/dev/null 2>&1 ||
		{ sleep 1; qemu-nbd -d /dev/nbd${NBD_DEV} >/dev/null 2>&1; }
	fi

	# Unmount Ghost/True/Veracrypt containers
	case "${FILENAME_LOWER##*.}" in
		gc|gst)	cmnd="$GOSTCRYPT"	;;
		hc|vc)	cmnd="$VERACRYPT"	;;
		tc)		cmnd="$TRUECRYPT"	;;
		*)		cmnd=''				;;
	esac
  
	if [ "$FILEPATH" ] && [ "$cmnd" ] && func_exists $cmnd && [ "$($cmnd -t -l 2>/dev/null | grep ".*${FILEPATH} ")" ]; then
		$cmnd -d "$FILEPATH" 2>/dev/null
	fi

	# Unmount cryptsetup container
	if [ "$FILEPATH" ] && [ "$MAPPER_NAME" ] && [ -e "/dev/mapper/${MAPPER_NAME}" ] && func_exists cryptsetup; then
		cryptsetup close "/dev/mapper/${MAPPER_NAME}"
	fi

	# Flush workdir
	#if [ -d "$WORKDIR" ] && ! mountpoint -q "$WORKDIR"; then	# mountpoint can be missing in very old Pups
	if [ -d "$WORKDIR" ] && ! grep -qw "$WORKDIR" /proc/mounts; then
		rm -rf "$WORKDIR" 2>/dev/null
	fi

	# Some applications' weird output can break the terminal
	# But 'stty sane' in turn breaks 'more', so screw it,
	# IIRC there was only one problematic archive anyway
	# Oh, it happens with .aes when instead of entering password you press ^C
	#stty sane

	echo -ne "\e[00m"

	unset FREELOOP NBD_DEV MAPPER_NAME
}

# -----------------------------------------------------------------------------

func_hr() {
	printf -- "${1}%.0s" $(eval echo {1..$( $TERM_WIDTH_CMD | cut -f2 -d ' ')})
}

# -----------------------------------------------------------------------------

func_precheck() {
	local i notfound=0

	for i in "$@"; do
		if ! func_exists $i; then
			echo -e "${REDCOL}$(gettext 'ERROR:') ${BLUECOL}${i}${OFFCOL} $(gettext 'is not installed or not executable!')"
			notfound=1
		fi
	done

	return $notfound
}

# -----------------------------------------------------------------------------

# $1 = what, $2 = where
func_mount() {
	local noload loop
	
	func_precheck mount || return 1

	# Use noload for ext{3,4}, because even if it's mounted with 'ro'
	# it can still modify the journal (switches to rw temporarily!)
	#blkid "$1" | grep -q -E -w 'ext3|ext4' && noload="noload" || noload=''
	file -bsL "$1" | grep -qEwi 'ext3|ext4' && noload="noload" || noload=''

	# If $1 is /dev/loop*, don't use 'loop' again
	echo "$1" | grep -q '^/dev/loop' && loop='' || loop='loop'

	# Mount
	mount -t auto -o ro,${noload},${loop} "$1" "$2" || return 1
}

# -----------------------------------------------------------------------------

# $1 = subdir (optional)

func_extract_mntpt() {
	local error=0

	if [ $(find "$WORKDIR" -maxdepth 0 -type d -empty) ]; then
		echo -e "${YELLOWCOL}$(gettext 'WARNING:') ${OFFCOL}$(gettext 'Image/partition does not contain any files, it is empty!')"
		return 0
	fi

	if [ "$MODE" = "list" ]; then
		find "$WORKDIR" -printf '%M %u:%g %s\r\t\t\t\t\t%P\n' || error=1
	else
		if ! func_exists stdbuf; then
			cp -ravi "${WORKDIR}/." "./${1}" || error=1
		else
			stdbuf -o0 -e0 cp -ravi "${WORKDIR}/." "./${1}" | sed -e "s|${WORKDIR}/./||" -e 's/ -> .*//' || error=1
		fi
	fi
	
	[ $error -eq 0 ] && return 0 || return 1
}

# -----------------------------------------------------------------------------

# void; sets KEY variable
func_get_key() {
	echo "$(gettext "Enter path to the keyfile or just press Enter to use a passphrase:")"
	IFS= read -r -p "> " KEY
	
	# If key doesn't point to any file, try also the image's dir+key for convenience
	[ "$KEY" ] && [ ! -e "$KEY" ] && [ -e "$(dirname "$FILEPATH")/$KEY" ] && KEY="$(dirname "$FILEPATH")/$KEY"
}

# -----------------------------------------------------------------------------

# $1 = path to image/loop; returns 0 if LUKS
func_is_luks() {
	[ "$(od -v -An -tc -j0 -w4 -N4 "$1" | tr -d ' \t\n')" = "LUKS" ]
	# newer (?) guess_fstype can detect LUKS as well
	#[ "$(guess_fstype "$1")" = 'crypto_LUKS' ]
}

# -----------------------------------------------------------------------------

# $1 = path to image/loop; returns 0 if successful and sets MAPPER_NAME
func_open_luks() {
	func_precheck cryptsetup || return 1

	MAPPER_NAME="${1##*/}"
	MAPPER_NAME="${MAPPER_NAME%.*}"
	MAPPER_NAME="$(echo "$MAPPER_NAME" | tr -cd '[:alnum:]')_${$}_${RANDOM}"

	func_get_key
	
	if [ "$KEY" ]; then
		cryptsetup -r -d "$KEY" open "$1" "$MAPPER_NAME" || return 1
	else
		cryptsetup -r open "$1" "$MAPPER_NAME" || return 1
	fi
}

# -----------------------------------------------------------------------------

# $1 = path to image/loop; $2 = cipher; returns 0 if successful and sets MAPPER_NAME
func_open_luks_cryptoloop() {
	local cipher
	
	func_precheck cryptsetup || return 1

	[ "$2" ] && cipher="$2" || cipher='aes'

	MAPPER_NAME="${1##*/}"
	MAPPER_NAME="${MAPPER_NAME%.*}"
	MAPPER_NAME="$(echo "$MAPPER_NAME" | tr -cd '[:alnum:]')_${$}_${RANDOM}"

	cryptsetup -r plainOpen -c "$cipher" -h plain "$1" "$MAPPER_NAME" || return 1
}

# -----------------------------------------------------------------------------

# $1 = mapper name
func_close_luks() {
	if [ "$1" ] && [ -e "/dev/mapper/${1}" ]; then
		cryptsetup close "$1" 2>/dev/null
	fi
}

# -----------------------------------------------------------------------------

# $1 = path to image, $2 = ISO (or any string; optional, to use fdisk instead of gdisk for ISO images)
func_mnt_disk_image() {
	local cnt fdisk_local info unit partitions offset error=0

	[ "$2" ] && fdisk_local='fdisk' || fdisk_local="$FDISK"

	func_precheck $fdisk_local $LOSETUP umount || return 1

	case "$fdisk_local" in
		fdisk)
			info="$(LANG=C $fdisk_local -lu "$1" 2>/dev/null)"
			unit="$(echo "$info" | sed -n 's/^Units.*=.*[^0-9]\([0-9]\+\).*bytes.*/\1/p')"
			partitions="$(echo "$info" | grep -E "${FILENAME}[^:]|nbd[0-9]+[^:]" | grep -viE 'Extended$| swap| unknown' | sed -r 's/( [0-9]+ ).*|./\1/g')"
		;;
		*)	# gdisk
			info="$(LANG=C $fdisk_local -l "$1" 2>/dev/null)"
			unit="$(echo "$info" l | sed -n 's/^Sector size.*logical.*[^0-9]\([0-9]\+\).*/\1/p')"
			partitions="$(echo "$info" 2>/dev/null | grep -A256 -i 'Start.*End.*Size' | grep -vE 'Extended$| swap' | sed -e 's/^[[:space:]]*//' | sed -r 's/( [0-9]+ ).*|./\1/g')"
		;;
	esac

	if [ "${partitions}" = "" ] ||
	   [[ "${partitions}" == *"?"* ]] ||
	   [[ "$info" == *"Exact type match not found"* ]] ||
	   [ ! "$(LANG=C file -L -b "$1" | grep -wE 'boot sector|block special')" ]
	then	# No partitions
		# LUKS
		if func_is_luks "$1"; then
			func_open_luks "$1" || return 1
			func_mount "/dev/mapper/${MAPPER_NAME}" "$WORKDIR" && { func_extract_mntpt || error=1; } || error=1
		else
			func_mount "$1" "$WORKDIR" && { func_extract_mntpt || error=1; } || error=1
		fi

		umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null
		func_close_luks "$MAPPER_NAME"
		MAPPER_NAME=''

	else	# Partitions

		# List/extract MBR
		echo -e "${BLUECOL}MBR:${OFFCOL}"
		if func_exists hexdump; then
			hexdump -C -n 512 "$1"
		else
			od -Ax -tx1 -N512 "$1"
		fi

		echo

		if [ "$MODE" != "list" ]; then
			head -c 512 "$1" > "./${FILENAME_NOEXT}.mbr" || error=1
		fi

		cnt=0

		for offset in $partitions; do
			echo -e "${BLUECOL}$(gettext 'Partition')_$((++cnt)):${OFFCOL}"

			FREELOOP="$($LOSETUP -f)"
			[ "$FREELOOP" = "" ] && return 1
			
			# Try a few times, because it may fail on heavy CPU and/or IO load
			for i in {1..3}; do
				sleep $i
				$LOSETUP $LOSETUP_R_OPT -o $((offset*unit)) "${FREELOOP}" "$1" && break
			done
			[ $? -ne 0 ] && { error=1; continue; }

			# LUKS
			if func_is_luks "${FREELOOP}"; then
				func_open_luks "$FREELOOP" && func_mount "/dev/mapper/${MAPPER_NAME}" "$WORKDIR"	# don't split it to if-fi !!
			else
				func_mount "$FREELOOP" "$WORKDIR"
			fi

			if [ $? -ne 0 ]; then
				echo -e "${REDCOL}$(gettext 'Failed!')${OFFCOL}\n"
				func_close_luks "$MAPPER_NAME"
				[ "$FREELOOP" ] && $LOSETUP -d "$FREELOOP" 2>/dev/null
				error=1
				continue
			fi

			[ "$MODE" != "list" ] && mkdir -p "$(gettext 'Partition')_${cnt}"

			if ! func_extract_mntpt "$(gettext 'Partition')_${cnt}"; then
				rmdir "$(gettext 'Partition')_${cnt}" 2>/dev/null
				error=1
			fi

			umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null
			func_close_luks "$MAPPER_NAME"
			[ "$FREELOOP" ] && $LOSETUP -d "$FREELOOP" 2>/dev/null

			echo
			FREELOOP=''
			MAPPER_NAME=''

		done

	fi

	[ $error -eq 0 ] && return 0 || return 1
}

# -----------------------------------------------------------------------------

# $1/$2 = MIME string and/or extension; returns decompression command
func_set_decompressor() {
	local cmnd=''
	
	while [ $# -gt 0 ]; do
		case "$(echo "$1" | tr '[:upper:]' '[:lower:]')" in
			#ext|mime
			bz2|'bzip2 '*)				cmnd="$BUNZIP2 -c -d"		;;
			bz3|'bzip3 '*)				cmnd="$BUNZIP3 -c -d"		;;
			z|'compress'\''d '*)		cmnd="$UNCOMPRESS -c -d"	;;	# compress'd data ...
			gz|'gzip '*)				cmnd="$GUNZIP -c -d" 		;;
			lz|'lzip '*)				cmnd="$LUNZIP -c -d"		;;
			lz4|'lz4 '*)				cmnd="$UNLZ4 -c -d"			;;
			lzma|'lzma '*)				cmnd="$UNLZMA -c -d"		;;
			lzo|'lzop '*)				cmnd="$UNLZOP -c -d"		;;
			xz|'xz '*)					cmnd="$UNXZ -c -d"			;;
			zst|'zstandard '*)			cmnd="$UNZSTD -c -d"		;;
			tar|'posix tar '*)			cmnd="cat"					;;	# plain tar, just pass through
			'ascii '*)					cmnd="base64 -d"			;;	# only for makeself installers
			'pgp'*'symmetric'*)			cmnd="$GPG -d"				;;	# only for makeself installers
			#'openssl'*)				cmnd="openssl enc -aes-256-cbc -d"	;;	# it can actually be compressed, unlike the 'pgp' one
			*)							cmnd=""						;;	# unknown compression
		esac
		
		[ "$cmnd" ] && break
		shift
	done
	
	echo "$cmnd"
}

# -----------------------------------------------------------------------------

# Sets PASSWORD global variable
func_askpass() {
	local oldifs
	
	oldifs="$IFS"; IFS=
	read -s -r -p "$(gettext 'Enter password (will not be echoed):')" PASSWORD
	IFS="$oldifs"
	echo; echo
}

# -----------------------------------------------------------------------------

# If 7z's '-so' switch was used, no password prompt is shown for encrypted archives
func_askpass_7z() {
	$SEVENZ -spd -p- t "$1" >/dev/null 2>&1 || echo -n "$(gettext 'Enter password (will not be echoed):')"
}

# -----------------------------------------------------------------------------

func_try_7z() {
	echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') '${SEVENZ}'...\n"
	func_precheck $SEVENZ || return 1
	$SEVENZ $SEVENZ_OPTS "$1" || return 1
}

# -----------------------------------------------------------------------------

func_try_tar() {
	echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying generic tar extraction command...')\n"
	func_precheck tar || return 1
	tar $TAR_OPTS "$1" && return 0 || return 1
}

# -----------------------------------------------------------------------------
# Heart of the script!
# -----------------------------------------------------------------------------

func_uextract() {
	# Yep, there's a lot of them, but at least everything's descriptive
	local add aformat b1 b2 b3 b4 b5 b6 b7 b8 boundary char cmnd cnt cntext
	local codec commit compression data_size end entry error ext f f1 f2 f3 f4
	local fileext files flags i i1 i2 i3 i4 id_string index infosector isboot
	local items line maincmnd meta_size method mime name namerev names null
	local o1 o2 o3 o4 offset offsets opts opts1 opts2 outdir outname padding
	local pages pattern repo result ret s1 s1 s2 s2 s3 s4 sigsize sigtype size
	local sizes skip_lines slashes startsector stream stream_info streamsfound
	local subarchive subdir sublang subnum substream substreamsfound tmpfile type

	error=0
	
	if [ "$MODE" != "list" ]; then
		if ! mkdir -p -- "$DESTDIR"; then
			echo -e "\n${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Cannot create destination directory!')"
			return 1
		fi

		if ! cd -- "$DESTDIR"; then
			echo -e "\n${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Cannot access destination directory!')"
			return 1
		fi
	fi

	fileext="${FILENAME_LOWER#*.}"
	[ "$fileext" != "${FILENAME_LOWER}" ] && fileext=".${fileext}"

	# Try to detect .run installers that have no .run extension
	if ( [ ! "$(echo "${fileext##*.}" | grep -E 'package|shar|shr')" ] &&
		 [[ "$(head -c256 "$FILEPATH" | LANG=C file -L -b - | cut -f1 -d ',')" == *"shell"* ]] &&
		 ( [ "$(LANG=C grep -o -m1 '' "$FILEPATH" 2>&1 | grep -i 'binary file.*matches')" ] || head -n2 "$FILEPATH" | grep 'Makeself') ) ||	# OR
	   ( [[ "$FILEINFO" == "ELF"* ]] &&
		 [ "$(od -v -An -tc -j32769 -w5 -N5 "$FILEPATH" 2>/dev/null | tr -d ' \t\n')" = "CD001" ] )
	then
		fileext=".run"
	fi

	# Detect UPX-ed files
	if [ "$(echo "$FILEINFO" | grep -iE '^ELF|^PE32|^DOS.*UPX')" ] &&
	   func_exists upx &&
	   [ "$(LANG=C upx -l "$FILEPATH" 2>&1 | grep ':.*Exception')" = "" ]
	then
		fileext=".upx"
	fi

	# Detect .snap packages that are DEB rather than Squashfs'ed
	if [ "${fileext##*.}" = 'snap' ] && [[ "$FILEINFO" == 'Debian'* ]]; then
		fileext=".deb"
	fi

	# Detect initrd/initram archives with misleading extensions or without one
	if [ "$(echo "$FILENAME_LOWER" | grep -iE '^initrd|^initram|^initrfs|^ramdisk\.lzma$|^ramdisk\.img$|^minirt|^hwe-initrd|^core\.gz$|^full\.cz$|^PARROTOS_initrd|^install\.img$')" ]; then
		type="$(echo "$FILEINFO" | grep -wioE 'bzip2|gzip|lz4|lzma|lzop|xz|zstandard|ext2|cpio' | tr '[:upper:]' '[:lower:]')"
		if [ "$type" ]; then
			case "$type" in
				ext2)	fileext='.ext2'			;;
				*)		fileext=".FAKE_initrd"	;;	# fake extension
			esac
		fi
	fi

	# Detect extensionless CPIO archives
	if [ "$fileext" != '.FAKE_initrd' ] && [ "$(echo "$FILEINFO" | grep -wio 'cpio')" ]; then
		fileext='.cpio'
	fi

	# Detect fusecompress(ed) files
	if [ "$(echo "$FILEINFO" | grep -i '^FuseCompress')" ]; then
		fileext=".FAKE_fusecompress"	# fake extension
	fi

	# Detect U-Boot uImage
	if [[ "$FILEINFO" == 'u-boot'* ]]; then
		fileext=".FAKE_uimage"	# fake extension
	fi

	# Detect Linux Kernel
	if [ "$(echo "$FILEINFO" | grep -wi "Linux kernel")" ]; then
		fileext=".FAKE_kernel"	# fake extension
	fi

	# Detect extensionless X11 cursor files
	if [ "$(echo "$FILEINFO" | grep -wiE 'Xcursor|X11 cursor')" ]; then
		fileext=".xmc"
	fi

	# Detect extensionless DICOM images
	if [ "$(echo "$FILEINFO" | grep -wi "^DICOM")" ]; then
		fileext=".dcm"
	fi

	# Ok, start extracting...
	case "$fileext" in
		# -----------
		*.tar.7z)
			func_precheck $SEVENZ tar || return 1
			func_askpass_7z "$FILEPATH"
			$SEVENZ -spd -so x "$FILEPATH" 2>/dev/null | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }	# hack for exit code 141 ('set -o pipefail' + usually piping through (but not only!) tail/head pair produces that!)
		;;
		# -----------
		*.tar.br|*.tar.bro)
			func_precheck brotli tar || return 1
			brotli -c -d "${FILEPATH}" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.bz|*.tar.bzip|*.tar.bz2|*.tar.bzip2|*.tar.gz2|*.tb2|*.tbz|*.tbz2|*.cbb|*.slp|*.xjtbz2)
			[ "$(head -c 3 "$FILEPATH")" = "BZ0" ] && cmnd=bunzip || cmnd="$BUNZIP2"
			func_precheck $cmnd tar || return 1
			$cmnd -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.bz3|.tar.bzip3|*.tbz3)
			func_precheck $BUNZIP3 tar || return 1
			$BUNZIP3 -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.f)
			func_precheck $UNFREEZE tar || return 1
			$UNFREEZE -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.gz|*.tar.gzip|*.tg|*.tgz|*.tar.jet|*.cbg|*.cxarchive|*.depot.gz|*.dsl|*.emerald|*.epk|*.gtp|*.iar|*.mbz|*.mtf|*.nif|*.oar|*.obt|*.qpk|*.qpr|*.rub|*.tce|*.tcel|*.tcem|*.unitypackage|*.vbox-extpack|*.wbm.gz|*.xjtgz)
			func_precheck $GUNZIP tar || return 1

			# Handle mistakenly double gzipped tarballs
			if [ "$(gunzip -c "$FILEPATH" 2>/dev/null | head -c 3)" = "$(echo -e "\x1f\x8b\x08")" ]; then
				cmnd="$GUNZIP -c -d"
			else
				cmnd="cat"
			fi

			$cmnd "$FILEPATH" | $GUNZIP -c -d | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.lrz|*.tlrz)
			func_precheck $LRUNZIP tar || return 1
			$LRUNZIP -d "$FILEPATH" -o - | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.lz4|*.tlz4)
			func_precheck $LZ4 tar || return 1
			$UNLZ4 -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }	    
		;;
		# -----------
		*.tar.lz|*.tlz)
			func_precheck $LUNZIP tar || return 1
			$LUNZIP -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }	    
		;;
		# -----------
		*.tar.lzma|*.tlzma|*.tzma)
			func_precheck $UNLZMA tar || return 1
			$UNLZMA -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.lzo|*.tzo)
			func_precheck $UNLZOP tar || return 1
			$UNLZOP -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.xz|*.txz|*.tpkg|*.tpxz)
			func_precheck $UNXZ tar || return 1
			$UNXZ -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.z|*.tarz|*.tz|*.taz)
			func_precheck $UNCOMPRESS tar || return 1
			$UNCOMPRESS -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.zip)
			func_precheck tar || return 1
			func_precheck unzip && { unzip -p "$FILEPATH" | tar $TAR_OPTS - && return 0; }
			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') '${SEVENZ}'...\n"
			func_precheck $SEVENZ || return 1
			$SEVENZ x -spd -so "$FILEPATH" 2>/dev/null | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.zst|*.tzst)
			func_precheck $UNZSTD tar || return 1
			$UNZSTD -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.tar.zz)
			func_precheck $GUNZIP || return 1
			result=$( { printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - "${FILEPATH}" | LANG=C $GUNZIP -c -d -v  | tar $TAR_OPTS - ; } 2>&1 )
			if [ $? -ne 0 ] && [ "$(echo "$result" | grep 'unexpected end of file')" = "" ];then
				error=1
			else
				error=0
			fi
			echo "$result"
		;;
		# -----------
		*.cpio.7z)
			func_precheck $SEVENZ cpio || return 1
			func_askpass_7z "$FILEPATH"
			$SEVENZ x -spd -so "$FILEPATH" 2>/dev/null | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.br|*.cpio.bro)
			func_precheck brotli cpio || return 1
			brotli -c -d "${FILEPATH}" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.bz|*.cpio.bzip|*.cpio.bz2|*.cpio.bzip2)
			[ "$(head -c 3 "$FILEPATH")" = "BZ0" ] && cmnd=bunzip || cmnd="$BUNZIP2"
			func_precheck $cmnd cpio || return 1
			$cmnd -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.bz3|*.cpio.bzip3)
			func_precheck $BUNZIP3 cpio || return 1
			$BUNZIP3 -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.f)
			func_precheck $UNFREEZE cpio || return 1
			$UNFREEZE -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.gz|*.cpio.gzip|*.cpgz|*.cgz)
			func_precheck $GUNZIP cpio || return 1
			$GUNZIP -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.lrz)
			func_precheck $LRUNZIP cpio || return 1
			$LRUNZIP -d "${FILEPATH}" -o - | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.lz4)
			func_precheck $LZ4 cpio || return 1
			$UNLZ4 -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.lz)
			func_precheck $LUNZIP cpio || return 1
			$LUNZIP -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.lzma)
			func_precheck $UNLZMA cpio || return 1
			$UNLZMA -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.lzo)
			func_precheck $UNLZOP cpio || return 1
			$UNLZOP -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.xz)
			func_precheck $UNXZ cpio || return 1
			$UNXZ -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.z)
			func_precheck $UNCOMPRESS cpio || return 1
			$UNCOMPRESS -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.zip)
			func_precheck cpio || return 1
			func_precheck unzip && { unzip -p "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT && return 0; }
			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') '${SEVENZ}'...\n"
			func_precheck $SEVENZ || return 1
			$SEVENZ x -spd -so "$FILEPATH" 2>/dev/null | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.zst)
			func_precheck $UNZSTD cpio || return 1
			$UNZSTD -c -d "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.cpio.zz)
			func_precheck cpio || return 1
			result=$( { printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - "${FILEPATH}" | LANG=C $GUNZIP -c -d -v  | cpio $CPIO_OPTS $CPIO_ABS_OPT ; } 2>&1 )
			if [ $? -ne 0 ] && [ "$(echo "$result" | grep 'unexpected end of file')" = "" ];then
				error=1
			else
				error=0
			fi
			echo "$result"
		;;
		# -----------
		*.adz|*.age|*.ai|*.baklz4|*.blend|*.cps|*.stm|*.cso|*.dcm|*.dds|*.gnumeric|*.graphmlz|*.jgz|*.jsonlz4|*.ktx|*.mozlz4|*.naf|*.psz|*.sifz|*.stpz|*.svgz|*.vgz|*.wrz|*.x3d|*.xcfgz|*.xmcdz|*.zabw|*.mo|*.dms|*.pack|*.wv|*.xcfbz2|*.aes|*.b32|*.b64|*.balz|*.bbb|*.bfe|*.bgz|*.br|*.bro|*.bz|*.bz2|*.bzip|*.bzip2|*.bz3|*.bzip3|*.cpt|*.dia|*.ecm|*.emz|*.f|*.flzp|*.gz|*.gz2|*.gzi|*.gzip|*.igz|*.jet|*.lep|*.liz|*.lpaq1|*.lpaq8|*.lz4|*.lz|*.lzma|*.lzo|*.lrz|*.lzfse|*.mpz|*.nc|*.ogz|*.quad|*.rz|*.sfe|*.snappy|*.snz|*.sz|*.tor|*.xz|*.pxz|*.xoj|*.xopp|*.z|*.zst|*.zx0)
			ext=''

			for i in 'adz|.adf' \
					 'ai|.svg' \
					 'blend|.blend' \
					 'cso|.iso' \
					 'dcm|.jpg' \
					 'dds|.png' \
					 'dms|.adf' \
					 'gnumeric|.gnumeric' \
					 'graphmlz|.graphml' \
					 'jgz|.js' \
					 'jsonlz4|.json' \
					 'ktx|.png' \
					 'lep|.jpg' \
					 'mo|.po' \
					 'naf|.fa' \
					 'pack|.jar' \
					 'psz|.ps' \
					 'sifz|.sif' \
					 'stpz|.stp' \
					 'svgz|.svg' \
					 'vgz|.vgm' \
					 'wrz|.wrl' \
					 'wv|.wav' \
					 'x3d|.x3d' \
					 'xcfbz2|.xcf' \
					 'xcfgz|.xcf' \
					 'xmcdz|.xmcd' \
					 'xoj|.xml' \
					 'xopp|.xml' \
					 'zabw|.abw'
			do
				if [ "${fileext##*.}" = "${i%%|*}" ]; then
					ext="${i##*|}"
					break
				fi
			done

			if [ "$MODE" = "list" ]; then
				echo "${FILENAME_NOEXT}${ext}" && return 0 || return 1
			fi

			echo "$(gettext 'Extracting...')"

			case "${fileext##*.}" in
				# -----------
				adz|gnumeric|graphmlz|jgz|psz|sifz|stpz|svgz|vgz|wrz|x3d|xcfgz|xmcdz|zabw)
					func_precheck $GUNZIP || return 1
					$GUNZIP -c -d -v "$FILEPATH" > "${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }
				;;
				# -----------
				ai)
					if [ "$(echo "$FILEINFO" | grep -wi 'postscript')" ]; then
						func_precheck gs || return 1
						gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile="${FILENAME_NOEXT}.pdf" "$FILEPATH" || { rm -f -- "${FILENAME_NOEXT}.pdf"; return 1; }
					fi

					func_precheck pdftocairo || return 1

					if [ -e "${FILENAME_NOEXT}.pdf" ]; then
						tmpfile="${FILENAME_NOEXT}.pdf"
					else	
						tmpfile="${FILEPATH}"
					fi

					pdftocairo -svg "$tmpfile" "${FILENAME_NOEXT}${ext}" || error=1
					rm -f "${FILENAME_NOEXT}.pdf"
				;;
				# -----------
				baklz4|jsonlz4|mozlz4)
					func_precheck lz4jsoncat || return 1
					lz4jsoncat "$FILEPATH" > "${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }
				;;
				# -----------
				blend)
					[ "$(echo "$FILEINFO" | grep -wi 'gzip')" ] && cmnd="$GUNZIP" || cmnd="$UNZSTD"
					func_precheck $cmnd || return 1
					$cmnd -c -d -v "$FILEPATH" > "${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }			
				;;
				# -----------
				cso)
					func_precheck maxcso || return 1
					maxcso --decompress --output-path=. "$FILEPATH" || { rm -f -- "${FILENAME_NOEXT}.iso"; return 1; }
				;;
				# -----------
				dcm)
					func_precheck dcmj2pnm && {
						echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'dcmj2pnm'...\n"
						dcmj2pnm "$FILEPATH" "${FILENAME_NOEXT}${ext}" && return 0
					}
					
					func_precheck dcml2pnm && {
						echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'dcml2pnm'...\n"
						dcml2pnm "$FILEPATH" "${FILENAME_NOEXT}${ext}" && return 0
					}
					
					return 1
				;;
				# -----------
				dds|ktx)
					func_precheck detex-convert || return 1
					detex-convert "$FILEPATH" "${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }
				;;
				# -----------
				dms)
					func_precheck undms || return 1
					undms "$FILEPATH" "${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }	      
				;;
				# -----------
				mo)
					func_precheck msgunfmt || return 1
					msgunfmt -v "$FILEPATH" -o "./${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }
				;;
				# -----------
				naf)
					func_precheck unnaf || return 1
					unnaf "$FILEPATH" -o "${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }
				;;
				# -----------
				pack)
					func_precheck unpack200 || return 1
					unpack200 -v "$FILEPATH" "${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }	      
				;;
				# -----------
				wv)
					func_precheck wvunpack || return 1
					wvunpack -o . "$FILEPATH" || return 1
				;;
				# -----------
				xcfbz2)
					func_precheck $BUNZIP2 || return 1
					$BUNZIP2 -c -d -v "$FILEPATH"  > "${FILENAME_NOEXT}${ext}" || { rm -f -- "${FILENAME_NOEXT}${ext}"; return 1; }
				;;
				# -----------
				xoj|xopp)
					func_precheck $GUNZIP || return 1
					$GUNZIP -c -d -v "$FILEPATH" > "${FILENAME_NOEXT}${ext}" || { [ $? -ne 2 ] && rm -f -- "${FILENAME_NOEXT}${ext}" && return 1; }
				;;
				# -----------
				aes)
					func_precheck aescrypt || return 1
					aescrypt -d -o ./"$FILENAME_NOEXT" "$FILEPATH" || return 1
				;;
				# -----------
				age)
					func_precheck age || return 1

					func_get_key
					
					if [ "$KEY" ]; then
						age -d -i "$KEY" -o "$FILENAME_NOEXT" "$FILEPATH" || return 1
					else
						age -d -o "$FILENAME_NOEXT" "$FILEPATH" || return 1
					fi				
				;;
				# -----------
				b32)
					func_precheck base32 || return 1
					base32 -d "$FILEPATH" > "$FILENAME_NOEXT" || return 1	        
				;;
				# -----------
				b64)
					func_precheck base64 || return 1
					base64 -d "$FILEPATH" > "$FILENAME_NOEXT" || return 1	        
				;;
				# -----------
				balz)
					func_precheck balz || return 1
					balz d "$FILEPATH" "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }	        
				;;
				# -----------
				bbb|flzp|lpaq1|lpaq8)
					cmnd="${fileext##*.}"	# will be bbb or flzp or lpaq1 or lpaq8
					func_precheck ${cmnd} || return 1
					$cmnd d "$FILEPATH" "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				bfe)
					func_precheck bcrypt || return 1
					bcrypt -o "$FILEPATH" > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }	        
				;;
				# -----------
				br|bro)
					func_precheck brotli || return 1
					brotli -d "$FILEPATH" -o "$FILENAME_NOEXT" || return 1
				;;
				# -----------
				bz|bz2|bzip|bzip2|gz2)
					[ "$(head -c 3 "$FILEPATH")" = "BZ0" ] && cmnd="bunzip" || cmnd="$BUNZIP2"
					func_precheck $cmnd || return 1
					$cmnd -c -d -v "$FILEPATH" > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }        
				;;
				# -----------
				bz3|bzip3)
					func_precheck $BUNZIP3 || return 1
					$BUNZIP3 -c -d -v "$FILEPATH" > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }        
				;;
				# -----------
				cps|stm)
					func_precheck $BUNZIP2 || return 1
					tail -c +13 "$FILEPATH" | $BUNZIP2 -c -d > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				cpt)
					func_precheck ccrypt || return 1
					ccrypt -d -c "$FILEPATH" > ./"$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				ecm)
					func_precheck $UNECM || return 1
					$UNECM d "$FILEPATH" "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				f)
					func_precheck $UNFREEZE || return 1
					$UNFREEZE -c -d "$FILEPATH" > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				gz|gzi|gzip|emz|bgz|dia|mpz|ogz|jet)
					func_precheck $GUNZIP || return 1
					$GUNZIP -c -d -v "$FILEPATH" > "$FILENAME_NOEXT" || { [ $? -ne 2 ] && rm -f -- "$FILENAME_NOEXT" && return 1; }
				;;
				# -----------
				igz)	# currently not in use for initram*/initrd* files, redirected to 'initrd'
					[ "$(echo "$FILEINFO" | grep -wi 'gzip')" ] && cmnd="$GUNZIP" || cmnd="$UNXZ"
					func_precheck $cmnd || return 1
					$cmnd -c -d -v "$FILEPATH" > "$FILENAME_NOEXT" || { [ $? -ne 2 ] && rm -f -- "$FILENAME_NOEXT" && return 1; }
				;;
				# -----------
				lep)
					func_precheck lepton || return 1
					lepton "$FILEPATH" "${FILENAME_NOEXT}.jpg" || { rm -f -- "${FILENAME_NOEXT}.jpg"; return 1; }
				;;
				# -----------
				liz)
					func_precheck lizard || return 1
					lizard -d "$FILEPATH" "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				lrz)
					func_precheck $LRUNZIP || return 1
					$LRUNZIP -d "$FILEPATH" -O . || return 1
				;;
				# -----------
				lz4)
					func_precheck $LZ4 || return 1
					$UNLZ4 -c -d "$FILEPATH" > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				lz)
					func_precheck $LUNZIP || return 1
					$LUNZIP -c -d -v "$FILEPATH"  > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				lzfse)
					func_precheck lzfse || return 1
					lzfse -decode -i "$FILEPATH"  > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				lzma)
					func_precheck $UNLZMA || return 1
					$UNLZMA -c -d -v "$FILEPATH"  > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				lzo)
					func_precheck $UNLZOP || return 1
					$UNLZOP -c -d -v "$FILEPATH"  > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				nc)
					func_precheck mcrypt || return 1
					cat "$FILEPATH" | mcrypt -d  > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				quad)
					func_precheck quad || return 1
					quad -d "$FILEPATH" "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				rz)
					func_precheck $RUNZIP || return 1
					$RUNZIP -k -d "$FILEPATH" -o "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				sfe)
					func_precheck scrypt || return 1
					scrypt dec "$FILEPATH" "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }	        
				;;
				# -----------
				snappy|snz|sz)
					func_precheck snzip || return 1
					snzip -c -d "$FILEPATH" > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				tor)
					func_precheck tor || return 1
					# It just randomly fails, even few times in a row...
					# "Can't open x/x for write"
					for i in {1..5}; do
						tor -d "$FILEPATH" -o"$FILENAME_NOEXT" && return 0
						sleep 0.2
					done
					rm -f -- "$FILENAME_NOEXT"; return 1
				;;
				# -----------
				xz|pxz)
					func_precheck $UNXZ || return 1
					$UNXZ -c -d -v "$FILEPATH"  > "$FILENAME_NOEXT" || { [ $? -ne 2 ] && rm -f -- "$FILENAME_NOEXT" && return 1; }
				;;
				# -----------
				z)
					if [ "$(echo "$FILEINFO" | grep -i '^compress')" ]; then
						func_precheck $UNCOMPRESS || return 1
						$UNCOMPRESS -c -d "$FILEPATH" > "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
					else
						func_precheck tosz || return 1
						result="$(LANG=C tosz -ascii "${FILEPATH}" "$FILENAME_NOEXT")"
						echo "$result"
						echo "$result" | grep -q '^Fail:' && { rm -f -- "$FILENAME_NOEXT"; return 1; }
					fi
				;;
				# -----------
				zst)
					func_precheck $UNZSTD || return 1
					$UNZSTD -d "$FILEPATH" -o "$FILENAME_NOEXT" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
				;;
				# -----------
				zx0)
					func_precheck salvador || return 1
					salvador -d "$FILEPATH" "$FILENAME_NOEXT" && return 0
					echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying backwards decompression...')\n"
					salvador -b -d "$FILEPATH" "$FILENAME_NOEXT" && return 0
					echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying classical (V1) format...')\n"
					salvador -classic -d "$FILEPATH" "$FILENAME_NOEXT" || return 1			
				;;
				# -----------
			esac
		;;
		# -----------
		*.2fs|*.3fs|*.4fs)
			func_precheck $LOSETUP umount || return 1

			# Cryptoloop has been removed in newer kernels, so try cryptsetup at first
			case "$FILENAME_LOWER" in
				*_crypta*)
					if func_open_luks_cryptoloop "$FILEPATH" aes &&
						func_mount "/dev/mapper/${MAPPER_NAME}" "$WORKDIR"
					then
						func_extract_mntpt	# ignore FS errors, if any - using legacy method won't help in this case
					else
						error=1
					fi

					umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null
					func_close_luks "$MAPPER_NAME"

					if [ $error -eq 0 ]; then
						return 0
					else
						echo -e "\n${REDCOL}ERROR:${OFFCOL} $(gettext 'cryptsetup failed, trying the legacy method...')\n"
					fi
				;;
			esac

			# The general (for unencrypted savefiles) and legacy method (for encrypted ones)
			FREELOOP="$($LOSETUP -f)"
			[ "$FREELOOP" = "" ] && return 1
			
			modprobe -a cryptoloop aes_generic aes crypto_blkcipher cbc 2>/dev/null	# for encrypted savefiles

			case "$FILENAME_LOWER" in
				*_cryptx*)	# doesn't work with newer kernels
					if func_exists losetup-klibc; then
						echo -n "$(gettext "Password: ")"
						losetup-klibc -p0 -e 1 "$FREELOOP" "$FILEPATH"
					else
						$LOSETUP $LOSETUP_R_OPT -E 1 "$FREELOOP" "$FILEPATH"
					fi
				;;
				*_crypta*)
					if func_exists losetup-klibc; then
						echo -n "$(gettext "Password: ")"
						losetup-klibc -p0 -e aes "$FREELOOP" "$FILEPATH"
					else
						$LOSETUP $LOSETUP_R_OPT -e aes "$FREELOOP" "$FILEPATH"
					fi
				;;
				*)
					$LOSETUP $LOSETUP_R_OPT "$FREELOOP" "$FILEPATH"
				;;
			esac

			if func_mount "$FREELOOP" "$WORKDIR"; then
				func_extract_mntpt || error=1
			else
				error=1
			fi

			umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null
			$LOSETUP -d "$FREELOOP" 2>/dev/null
			FREELOOP=''
		;;
		# -----------
		*.7z|*.7z.001|*.7zip|*.cb7|*.cl2arc|*.cpl|*.crx|*.ctx|*.ctz|*.deskthemepack|*.dll|*.dmg|*.dylib|*.efi|*.fwp|*.gmz|*.hme|*.icl|*.mct|*.msg|*.msi|*.msp|*.msu|*.mzz|*.nex|*.ndr|*.ngr|*.nlr|*.nsis|*.ocx|*.onepkg|*.safariextz|*.sbsar|*.scr|*.sdi|*.swm|*.sys|*.themepack|*.tsk|*.vhd|*.wa|*.wim|*.wsp|*.xsn|*.zipx|*.zw1|*.??_)
			func_precheck $SEVENZ || return 1
			$SEVENZ $SEVENZ_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.a|*.ar|*.rlib)
			if func_precheck ar; then
				ar  $AR_OPTS "$FILEPATH" && return 0 || return 1
			else
				func_try_7z "$FILEPATH" && return 0 || return 1
			fi
		;;
		# -----------
		*.ab)	# only compressed and unencrypted
			func_precheck $GUNZIP tar || return 1
			( set +o pipefail &&
				{ printf "\x1f\x8b\x08\x00\x00\x00\x00\x00"; tail -c +25 "${FILEPATH}"; } |
				$GUNZIP -c -d -v 2>/dev/null | tar $TAR_OPTS -
			) || return 1
		;;
		# -----------
		*.ace|*.cba)
			func_precheck unace || return 1
			unace $UNACE_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.adf)
			func_precheck unadf || return 1
			unadf $UNADF_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.alz)
			func_precheck unalz || return 1
			unalz $UNALZ_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.ani|*.arch00|*.sfx|*.u|*.uax|*.utf|*.vpk) # arch00/sfx/u/uax/vpk - only wav sounds

			if [ "${fileext##*.}" = "sfx" ] && echo "$FILEINFO" | grep -wiq '^ELF'; then
				func_precheck $SEVENZ || return 1
				$SEVENZ $SEVENZ_OPTS "$FILEPATH" && return 0 || return 1
			elif [ "${fileext##*.}" = "vpk" ] && echo "$FILEINFO" | grep -wiq 'Zip'; then
				func_precheck unzip && {
					unzip $UNZIP_OPTS "$FILEPATH" && return 0
				}
				func_precheck $SEVENZ || return 1
				$SEVENZ $SEVENZ_OPTS "$FILEPATH" && return 0 || return 1			
			elif [ "${fileext##*.}" = "ani" ]; then
				pattern='icon'
				ext='cur'
				add='9'
			else
				pattern='RIFF'
				ext='wav'
				add='1'
				[ "${fileext##*.}" = "utf" ] && ext='mp3'	# Freelancer
			fi

			#items=( $(od -v -An -tx1 -j0 -w1 "$FILEPATH" | tr -d '\n' | grep -bo '52 49 46 46 .. .. .. .. 57 41 56 45' | cut -f1 -d ':' | sed 's/.*/& 3 \/ p/' | dc) ${FILESIZE} )
			items=( $(grep -abo "${pattern}" "$FILEPATH" | cut -f1 -d ':') ${FILESIZE} )
			
			if [ ${#items[@]} -eq 1 ]; then
				echo -e "${REDCOL}ERROR: ${OFFCOL}$(gettext 'No items found!')"
				return 1
			fi

			echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext "Items found:") $(( ${#items[@]} - 1))"

			[ "$MODE" = "list" ] && return 0
			
			echo
			padding=$(( ${#items[@]} - 1 ))
			padding=${#padding}

			for ((cnt=0; cnt<$((${#items[@]}-1)); cnt++)); do

				echo "$(gettext 'Extracting item') $(printf "%0${padding}d\n" $((cnt+1)) )"

				if [ "${fileext##*.}" = "ani" ]; then
					end=$(echo $(( ${items[$(($cnt+1))]} - ${items[$cnt]} )) )
				else
					read b1 b2 b3 b4 <<< $(od -v -An -tu1 -j$(( ${items[$cnt]} + 4 )) -w4 -N4 "$FILEPATH")
					end=$(echo $(( ($b4*16777216)+($b3*65536)+($b2*256)+($b1) + 8)) )
					if [ ${end} -gt $(( ${items[$(($cnt+1))]} - ${items[$cnt]} )) ]; then
						end=$(echo $(( ${items[$(($cnt+1))]} - ${items[$cnt]} )) )
					fi
				fi

				tail -c +$((${items[$cnt]}+${add})) "$FILEPATH" | head -c ${end} > "$(printf "%0${padding}d\n" $((cnt+1)) ).${ext}" ||
					{ [ $? -ne 141 ] && { rm "$(printf "%0${padding}d\n" $((cnt+1)) ).${ext}"; error=1; }; }

			done
		;;
		# -----------
		*.arc|*.ark|*.sue)
			echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') '(un)arc'...\n"
			
			if func_precheck $UNARC; then
				$UNARC $UNARC_OPTS "$FILEPATH" && return 0 || { echo; echo; }
			fi
			
			# workaround - arc/unarc/nomarch are incompatibile with FreeArc's arc/unarc and nomarch doesn't support Unicode
			if [ "$UNARC" = "unarc" ] && func_exists arc; then
				arc $UNARC_OPTS "$FILEPATH" && return 0
			fi

			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'nomarch'...\n"
			func_precheck nomarch || return 1
			nomarch $NOMARCH_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.aplp)
			if [ "$(head -c 16 "$FILEPATH")" != "###_aplpinfo_###" ]; then
				echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'It is not an .aplp file!')"
				return 1
			fi

			names=( $(grep -a -o -m1 'filenames=.*' "$FILEPATH" | cut -f1 -d ';'| cut -f2 -d '=' | tr ',' '\t') )
			offsets=( $(grep -a -o -m1 'offsets=.*' "$FILEPATH" | cut -f1 -d ';'| cut -f2 -d '=' | tr ',' '\t') $(du -b "$FILEPATH" | cut -f1) )

			#for f in $(seq -w 0 $(( ${#names[@]}-1 ))); do
			for f in $(eval echo {0..$(( ${#names[@]}-1 ))}); do
				echo "${names[$f]}"
				[ "$MODE" = "list" ] && continue
				tail -c +$((0x${offsets[$f]} + 1)) "$FILEPATH" | head -c $(( 0x${offsets[$((f+1))]} - 0x${offsets[$f]} )) > "${names[$f]}" || { [ $? -ne 141 ] && error=1; }
			done
		;;
		# -----------
		*.appx|*.appxbundle|*.msix)	# only unzip can handle those, 7z fails
			func_precheck unzip || return 1
			unzip $UNZIP_OPTS "$FILEPATH" && return 0
		;;
		# -----------
		*.arj|*.a[0-9][0-9])
			if func_precheck arj; then
				arj $ARJ_OPTS "$FILEPATH" && return 0 || return 1
			else
				func_try_7z "$FILEPATH" && return 0 | return 1
			fi
		;;
		# -----------
		*.asar)
			func_precheck asar || return 1
			
			[ "$MODE" = "list" ] && outdir="$WORKDIR" || outdir="."
			asar --unpack -o "$outdir" "$FILEPATH"

			if [ $(find "$outdir" -maxdepth 0 -type d -empty) ]; then
				echo -e "${REDCOL}$(gettext "ERROR:")${OFFCOL} $(gettext "Nothing listed/extracted!")"
				return 1
			fi

			cd "$outdir"
			find . -printf "%P\n"
		;;
		# -----------
		*.asc|*.gpg|*.pgp)
			func_precheck $GPG || return 1
			if [ "$MODE" = "list" ]; then
				echo "$FILENAME_NOEXT"
				$GPG --list-only "$FILEPATH" && return 0 || return 1
			else
				# Note: even with the below option, gpg 2.x still invokes gpg-agent,
				# which caches the password. 'killall gpg-agent' "uncaches" it.
				opts='--pinentry-mode loopback'
				if ! $GPG ${opts} --version >/dev/null 2>&1; then
					opts=''
				fi
				$GPG ${opts} -o "$FILENAME_NOEXT" -d "$FILEPATH" || { rm -f -- "$FILENAME_NOEXT"; return 1; }
			fi
		;;
		# -----------
		*.asd)
			func_precheck $UNASD || return 1
			$UNASD $UNASD_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.bin|*.daa|*.mdf|*.mds|*.nrg)
			if [ "${fileext##*.}" = 'bin' ] && [[ "$FILEINFO" == 'ELF'* ]]; then
				if func_precheck unzip; then
					unzip $UNZIP_OPTS "$FILEPATH" || { [ $? -eq 1 ] && return 0 || return 1; }	# 1 means extra bytes at the beginning of the file; ignore
				elif func_precheck $SEVENZ; then
					$SEVENZ $SEVENZ_OPTS "$FILEPATH" || return 1
				else
					return 1
				fi
			else
				func_precheck poweriso || return 1
				poweriso $POWERISO_OPTS "$FILEPATH" / -r -od . || return 1
			fi
		;;
		# -----------
		*.btrfs|*.cramfs|*.disk|*.dsk|*.ext2|*.ext3|*.ext4|*.fat|*.flp|*.hfs|*.hfv|*.ima|*.image|*.img|*.luks|*.ntfs|*.ucimg|*.udf|*.vfd)
			if [ "${fileext##*.}" = "fat" ] && [ "$(echo "$FILEINFO" | grep -wi 'gzip')" ]; then
				func_precheck $GUNZIP tar || return 1
				$GUNZIP -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
			else
				func_mnt_disk_image "$FILEPATH" && return 0
				
				if [ "${fileext##*.}" = 'img' ]; then
					echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'poweriso'...\n"
					func_precheck poweriso || return 1
					poweriso $POWERISO_OPTS "$FILEPATH" / -r -od . && return 0 || return 1
				else
					return 1
				fi
			fi
		;;
		# -----------
		*.bxy|*.sdk|*.sh2|*.shk)
			func_precheck $NULIB || return 1
			[ "$NULIB" = "nulib2" ] && opts="$NULIB2_OPTS" || opts="$NULIB_OPTS"
			$NULIB ${opts} "$FILEPATH" || return 1
		;;
		# -----------
		*.cab)
			if func_precheck cabextract; then
				cabextract $CABEXTRACT_OPTS "$FILEPATH" && return 0 || return 1
			else
				func_try_7z "$FILEPATH" && return 0 || return 1
			fi
		;;
		# -----------
		*.chm)
			if [ "$MODE" = "list" ]; then
				cmnd=enum_chmLib
				outdir=''
			else
				cmnd=extract_chmLib
				outdir='.'
			fi

			if func_precheck $cmnd; then
				$cmnd "$FILEPATH" $outdir && return 0
			else
				func_try_7z "$FILEPATH" && return 0 || return 1
			fi
		;;
		# -----------
		*.cpio)
			func_precheck cpio || return 1
			cpio $CPIO_OPTS $CPIO_ABS_OPT < "$FILEPATH" || return 1
		;;
		# -----------
		*.cpk)
			func_precheck uncpk || return 1
			while read -r line; do
				echo "$line"
				[ "$line" = 'Wrong Version!' ] && return 1
			done < <(LANG=C uncpk $UNCPK_OPTS "$FILEPATH" 2>&1)
		;;
		# -----------
		*.cur|*.ico)
			func_precheck icotool || return 1
			[ "$MODE" != "list" ] && echo "$(gettext 'Extracting...')"
			result="$(LANG=C icotool $ICOTOOL_OPTS "$FILEPATH" 2>&1)"
			# icotool always returns 0, so have to scrap the stderr
			echo "$result"
			[[ "$result" == "${FILEPATH}: "* ]] &&
			[[ "$result" != *"--index"* ]] && 
			[[ "$result" != *"clr_important"* ]] && return 1
		;;
		# -----------
		*.cvd|*.cld)
			if [ "${fileext##*.}" = "cvd" ]; then
				cmnd="$GUNZIP -c -d"
				func_precheck $GUNZIP tar || return 1
			else
				cmnd="cat"
				func_precheck tar || return 1
			fi

			tail -c +513 "$FILEPATH" | ${cmnd} | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.d41|*.d64|*.d67|*.d71|*.d80|*.d81|*.d82|*.d90|*.g41|*.g64|*.g71|*.x64)
			func_precheck c1541 || return 1
			[ "$MODE" != 'list' ] && echo "$(gettext 'Extracting...')"
			c1541 "$FILEPATH" $C1541_OPTS || return 1
		;;
		# -----------
		*.dar)
			func_precheck dar || return 1
			dar $DAR_OPTS "${FILEPATH%.*.dar*}" $DAR_OPTS2 || return 1
		;;
		# -----------
		*.dat|*.tnef)
			func_precheck tnef || return 1
			tnef $TNEF_OPTS --unix-paths --save-body="$FILENAME_NOEXT" -v "$FILEPATH" || return 1
		;;
		# -----------
		*.db|*.db3|*.sqlite|*.sqlite3|*.sqlitedb|*.qch|*.ctb|*.xwcb)

			if [[ "$FILENAME" == Thumbs*.db ]]; then

				func_precheck $SEVENZ || return 1

				if ! IMAGES="$($SEVENZ l -spd "$FILEPATH" | grep -oE '[0-9]* files' | cut -f1 -d ' ')"; then
					echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext "No thumbnails found!")"
					return 1
				fi

				echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Thumbnails found:') $(($IMAGES-1))"

				[ "$MODE" = "list" ] && return 0

				$SEVENZ x -spd "$FILEPATH" || return 1

				echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Postprocessing, please wait...')"

				for name in *; do
					[ "$name" = "Catalog" ] && continue
					namerev=""; for ((i=0; i < ${#name}; i++)); do namerev="${name:i:1}${namerev}"; done
					tail -c +13 "$name" > "$(gettext 'Thumbnail')_${namerev}.jpg" || error=1
					rm -f "$name"
				done
			
			else

				func_precheck $SQLITE || return 1

				# Integrity check
				echo -ne "$(gettext "Integrity check:") "
				if [ "$SQLITE" = "fossil" ]; then
					opts='sqlite3 --no-repository'
					result="$(LANG=C $SQLITE ${opts} "$FILEPATH" "PRAGMA integrity_check" 2>&1 | head -n 1)"
					ret=$?
					echo "$result"
					[ $ret -ne 0 ] && [ $ret -ne 141 ] && ([ "$result" != "ok" ] || [ "$result" != "'ok'" ]) && return 1
				else
					opts=''
					$SQLITE ${opts} "$FILEPATH" "PRAGMA integrity_check" | head -n 1 || return 1
				fi

				echo
	
				if [ "$MODE" = "list" ]; then
					$SQLITE ${opts} "$FILEPATH" ".dump" || return 1
				else
					$SQLITE ${opts} "$FILEPATH" ".dump" > "${FILENAME_NOEXT}.sql" || { rm -f -- "${FILENAME_NOEXT}.sql"; return 1; }
				fi

			fi
		;;
		# -----------
		*.deb|*.ddeb|*.udeb|*.ipk)
			if [ "$(echo "$FILEINFO" | grep -wi 'gzip')" ]; then	# some .ipk pkgs are gzip compressed, not ar
				func_precheck $GUNZIP tar || return 1

				if [ "$MODE" != "list" ]; then
					mkdir -p control data
					opts1='-C control'
					opts2='-C data'
				else
					opts1=''
					opts2=''
				fi

				$GUNZIP -c -d "$FILEPATH" | tar $TAR_OPTS - --exclude=control.tar.gz --exclude=data.tar.gz || { [ $? -ne 141 ] && error=1; }
				echo
				$GUNZIP -c -d "$FILEPATH" | tar x --to-stdout -f - ./control.tar.gz | $GUNZIP -c -d | tar $TAR_OPTS - $opts1 || { [ $? -ne 141 ] && error=1; }
				echo
				$GUNZIP -c -d "$FILEPATH" | tar x --to-stdout -f - ./data.tar.gz	| $GUNZIP -c -d | tar $TAR_OPTS - $opts2 || { [ $? -ne 141 ] && error=1; }

				[ $error -eq 0 ] && return 0 || return 1
			fi

			# -----------

			for maincmnd in ar $([ "$SEVENZ" = '7z' ] && echo $SEVENZ); do

				echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') '$maincmnd'...\n"

				func_precheck ${maincmnd} tar && {

					for i in control data; do
						if [ "$maincmnd" = 'ar' ]; then
							entry="$(ar t "$FILEPATH" 2>/dev/null | grep -io "${i}.tar.*")"
							mime="$(LANG=C ar p "$FILEPATH" "$entry" | file -L -b -)"
							ext="$(ar t "$FILEPATH" 2>/dev/null | grep -io "${i}.tar.*")"
						else
							entry="$($SEVENZ l -tar -spd "$FILEPATH" 2>/dev/null | grep -io "${i}.tar.*")"
							mime="$(LANG=C $SEVENZ e -tar -spd -so "$FILEPATH" "$entry" | file -L -b -)"
							ext="$($SEVENZ l -tar -spd "$FILEPATH" 2>/dev/null | grep -io "${i}.tar.*")"
						fi
						ext="${ext##*.}"
						
						cmnd="$(func_set_decompressor "$mime" "$ext")"
						[ "$cmnd" ] || cmnd='tar'	# treat as uncompressed and see what happens
						
						func_precheck ${cmnd%% *} && {
							if [ "$maincmnd" = 'ar' ]; then
								ar p "$FILEPATH" "$entry" | $cmnd | tar $TAR_OPTS - || { [ $? -ne 141 ] && error=1; }
							else
								$SEVENZ -aos -tar -spd -so x "$FILEPATH" "$entry" | $cmnd | tar $TAR_OPTS - || { [ $? -ne 141 ] && error=1; }
							fi
						} || { error=1; break; }
					done
				
					[ $error -eq 0 ] && return 0 || error=0
				}
				echo
			done

			# -----------

			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'dpkg-deb'...\n"

			func_precheck dpkg-deb && {
				for cmnd in 'dpkg-deb' 'busybox dpkg-deb'; do
					if [ "$MODE" = "list" ]; then
						$cmnd -c "$FILEPATH" || error=1
					else
						$cmnd -e "$FILEPATH" . || error=1
						$cmnd -X "$FILEPATH" . || error=1
					fi
					( [ $error -eq 0 ] ||
					  LANG=C dpkg-deb --help 2>&1 | grep -qwi 'BusyBox' ||
					  [ ! "$(busybox | grep -wo 'dpkg-deb')" ]) &&
					  break
				done

				[ $error -eq 0 ] && return 0 || error=0
			}

			# -----------

			if [ "${fileext##*.}" = "deb" ]; then
				echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'exploderpm'...\n"
				func_precheck exploderpm || return 1
	
				exploderpm $EXPLODERPM_OPTS "$FILEPATH"
				ret=$?
	
				if [ $ret -eq 0 ] && [ $(exploderpm -l "$FILEPATH" | grep '^./$' | wc -l) -lt 2 ]; then
					echo -e "\n${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Archive has not been fully listed/extracted!')"
					return 1
				fi
	
				if [ $ret -ne 0 ]; then
					echo -e "$(gettext "Error...")"
					return 1
				fi
			else
				return 1
			fi
		;;
		# -----------
		*.djvu)
			func_precheck ddjvu || return 1

			if [ "$MODE" = "list" ]; then
				ddjvu -verbose -format=rle -scale=1% "$FILEPATH" > /dev/null && return 0 || return 1
			fi

			pages="$(ddjvu -verbose -format=ppm -scale=1% -page=999999999 "$FILEPATH" 2>&1 | grep -E '^-.*[0-9]+.*-' | tr -cd '[:digit:]')"
			if [ ! "$pages" ]; then
				echo -e "${REDCOL}$(gettext "Error...")${OFFCOL}"
				return 1
			fi

			for i in $(eval echo {1..${pages}}); do
				ddjvu -verbose -format=ppm -page=$i "$FILEPATH" "${i}.${FILENAME_NOEXT}.ppm" || error=1
			done
		;;
		# -----------
		*.dz)
			if [ "$(echo "$FILEINFO" | grep -wi '^gzip')" ]; then

				if [ "$MODE" = "list" ]; then
					echo "$FILENAME_NOEXT" && return 0 || return 1
				else
					func_precheck $GUNZIP || return 1
					$GUNZIP -c -d -v "$FILEPATH" > "$FILENAME_NOEXT" || { [ $? -ne 2 ] && rm -f -- "$FILENAME_NOEXT" && return 1; }
				fi
				
			else
			
				func_precheck dzip || return 1
				dzip $DZIP_OPTS "$FILEPATH" || return 1
			
			fi
		;;
		# -----------
		*.ecsbx|*.sbx)
			func_precheck blkar || return 1
			blkar $BLKAR_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.eds)
			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'cryptsetup'...\n"
			func_mnt_disk_image "$FILEPATH" && return 0

			echo -ne "\n${BLUECOL}(T)${OFFCOL}ruecrypt / ${BLUECOL}(V)${OFFCOL}eracrypt? "
			read -n1 REPLY
			echo

			case "$REPLY" in
				t|T)	cmnd="$TRUECRYPT"
						if [ "$cmnd" = 'veracrypt' ] || [ "$cmnd" = 'veracrypt_cli' ]; then
							opts='-tc'
						fi
				;;
				v|V)	cmnd="$VERACRYPT"
						opts=''
				;;
				*)		echo -e "${YELLOWCOL}$(gettext 'Aborted...')${OFFCOL}"
						return 1
				;;
			esac

			func_precheck $cmnd || return 1
			$cmnd --text ${opts} --fs-options=ro --mount-options=nokernelcrypto --mount "$FILEPATH" "$WORKDIR" || return 1 && echo
			func_extract_mntpt || error=1
			$cmnd -d "$FILEPATH"
		;;
		# -----------
		*.eml|*.mht|*.mhtml|*.mim|*.mime|*.mbox|*.mbx)
			func_precheck ripmime || return 1

			[ "$MODE" = 'list' ] && outdir="$WORKDIR" || outdir="."

			ripmime --mailbox -e -v -i "$FILEPATH" -d "$outdir" || return 1
			
			# GMail .mbox's extracted attachments are usually base64 encoded, so we need the below to decode them
			if [ "$MODE" != 'list' ]; then
				echo
				for i in .* *; do
					[ "$i" = '.*' ] || [ "$i" = '*' ] || [[ "$i" == 'textfile'[0-9]* ]] && continue
					echo "$(gettext 'Postprocessing:') ${i}"
					ripmime --overwrite -i "$i" 2>/dev/null
				done
			fi

			if [ $(find "$outdir" -maxdepth 0 -type d -empty) ]; then
				echo -e "${REDCOL}$(gettext "ERROR:")${OFFCOL} $(gettext "Nothing listed/extracted!")"
				return 1
			fi
		;;
		# -----------
		*.eopkg|*.pisi)
			func_precheck unzip $UNXZ tar || return 1
			unzip $UNZIP_OPTS "$FILEPATH" -x "install.tar.*" || return 1
			unzip -pv "$FILEPATH" "install.tar.*" | $UNXZ -c -d | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.exe)
			if func_precheck innoextract && innoextract -l "$FILEPATH" >/dev/null 2>&1; then
				innoextract $INNOEXTRACT_OPTS "$FILEPATH" && return 0 || return 1
			else
				func_try_7z "$FILEPATH" && return 0 || return 1
			fi
		;;
		# -----------
		*.fdi)
			FREELOOP="$($LOSETUP -f)"
			[ "$FREELOOP" = "" ] && return 1
			if $LOSETUP $LOSETUP_R_OPT -o 4096 "${FREELOOP}" "$FILEPATH" 2>/dev/null &&
				func_mount "$FREELOOP" "$WORKDIR"
			then
				func_extract_mntpt || error=1
			else
				error=1
			fi
			umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null
			$LOSETUP -d "$FREELOOP" 2>/dev/null
			FREELOOP=''
		;;
		# -----------
		*.flatpak)
			func_precheck ostree || return 1

			echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Processing, please wait...')\n"

			[ "$MODE" = "list" ] && repo="${WORKDIR}/.uextract_repo" || repo=".uextract_repo"
			outdir=".uextract_out"

			ostree init --repo="$repo" || return 1
			ostree static-delta apply-offline --repo="$repo" "$FILEPATH" || error=1
			
			slashes="${repo//[^\/]}"; slashes=${#slashes}
			commit="$(basename $(echo "${repo}"/objects/*/*.commit | cut -d '/' -f$((slashes+3))- | tr -d '/' ) .commit)"

			if [ $error -eq 0 ]; then
				if [ "$MODE" != "list" ]; then
					ostree checkout --repo="$repo" -U "$commit" "$outdir" || error=1
					mv -f "$outdir"/* "$outdir"/.[!.]* "$outdir"/..?* . 2>/dev/null
					rmdir "$outdir"
				fi
	
				ostree ls --repo="$repo" -R "$commit" || error=1
			fi

			[ "$MODE" != "list" ] && rm -r "$repo"
		;;
		# -----------
		*.fossil|*.fsl|*.efossil)
			func_precheck fossil || return 1

			# Hmm, newer Fossil modifies the repo file, even when using 'ls' alone,
			# so we need to copy the repo itself and work on a copy, to prevent that.
			tmpfile="${WORKDIR}/${FILEPATH##*/}"
			cp -a "$FILEPATH" "$tmpfile"

			if [ "$MODE" = "list" ]; then
				#cd "$WORKDIR"
				#{ fossil open --empty "${FILEPATH}" && fossil checkout --latest --keep && fossil ls; } || return 1
				fossil ls -v -r tip -R "${tmpfile}" || return 1
			else
				#{ fossil open --empty "${FILEPATH}" >/dev/null && fossil checkout --latest; } || return 1
				fossil open --nosync "${tmpfile}" || return 1
				rm .fslckout
			fi
		;;
		# -----------
		*.fp8)
			func_precheck fp8 || return 1
			fp8 $FP8_OPTS "$FILEPATH" . || return 1
		;;
		# -----------
		*.fsb)
			func_precheck fsbext || return 1
			fsbext $FSBEXT_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.gc|*.gst|*.tc|*.hc|*.vc)
			case "${fileext##*.}" in
				gc|gst)	cmnd="$GOSTCRYPT"	;;
				hc|vc)	cmnd="$VERACRYPT"	;;
				*)		cmnd="$TRUECRYPT"
						if [ "$cmnd" = 'veracrypt' ] || [ "$cmnd" = 'veracrypt_cli' ]; then
							opts='-tc'
						else
							opts=''
						fi
				;;
			esac

			func_precheck $cmnd || return 1
			$cmnd --text ${opts} --fs-options=ro --mount-options=nokernelcrypto --mount "$FILEPATH" "$WORKDIR" || return 1 && echo
			func_extract_mntpt || error=1
			$cmnd -d "$FILEPATH"
		;;
		# -----------
		*.gcf)
			func_precheck hlextract || return 1
			hlextract -p "$FILEPATH" $HLEXTRACT_OPTS || return 1
		;;
		# -----------
		*.gif)
			func_precheck gifsicle || return 1
			gifsicle $GIFSICLE_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.gifar)
			boundary=$(grep -aboU -m1 "$(echo -e "\x3b\x50\x4b\x03\x04")" "$FILEPATH" | cut -f1 -d ':')
			
			if [ "$boundary" = "" ]; then
				echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Cannot find appended .jar file!')"
				return 1
			else
				echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'This file contains appended .jar file!')"

				[ "$MODE" = "list" ] && return 0
	
				echo -e "$(gettext 'Extracting GIF')"
				head -c $(($boundary+1)) "$FILEPATH" > "${FILENAME_NOEXT}.gif" || error=1
				
				echo -e "$(gettext 'Extracting JAR')"
				tail -c +$(($boundary+2)) "$FILEPATH" > "${FILENAME_NOEXT}.jar" || error=1
			fi
		;;
		# -----------
		*.gig)
			if [ "$MODE" = "list" ]; then
				func_precheck gigdump || return 1
				gigdump "$FILEPATH" || return 1
			else
				func_precheck gigextract || return 1
				gigextract "$FILEPATH" . || return 1
			fi
		;;
		# -----------
		*.gpkg.tar)
			func_precheck tar || return 1
			
			if LANG=C tar --help 2>&1 | grep -qwi -- '--wildcards[^-]'; then
				opts='--wildcards'
			else
				opts=''
			fi
			
			tar --exclude=metadata.tar*[^.sig$] --exclude=image.tar*[^.sig$] $TAR_OPTS "$FILEPATH" || return 1
			
			[ "$MODE" != 'list' ] && cd *	# enter the subdir and extract the rest inside
			
			for i in metadata image; do
				mime="$(LANG=C tar ${opts} -xf "$FILEPATH" "*/${i}.tar.*" -O | file -L -b -)"
				ext="$(tar -tf "$FILEPATH" 2>/dev/null | grep -m1 -io "${i}.tar.*[^.sig$]")"
				ext="${ext##*.}"

				cmnd="$(func_set_decompressor "$mime" "$ext")"
				
				func_precheck ${cmnd%% *} || return 1
				tar ${opts} -xf "$FILEPATH" "*/${i}.tar.*" -O | ${cmnd} | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1 || return 0; }
			done
		;;
		# -----------
		*.grp)
			func_precheck grpar || return 1
			grpar $GRPAR_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.ha)
			func_precheck ha || return 1
			ha $HA_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.hqx)
			func_precheck hexbin || return 1
			hexbin $HEXBIN_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.icns)
			func_precheck icns2png || return 1
			icns2png $ICNS2PNG_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.iso|*.cfs)
		
			func_precheck mount umount || return 1
		
			for DUMMY in DUMMY; do	# a workaround to avoid a separate function or nested if-fi
				[ "$(od -v -An -tc -j$((17*2048+1)) -w29 -N29 "$FILEPATH" | tr -d ' \t\n')" != "CD001001ELTORITOSPECIFICATION" ] && break

				read b1 b2 b3 b4 <<< $(od -v -An -tu1 -j$((17*2048+71)) -w4 -N4 "$FILEPATH")
				infosector=$(echo $(( ($b4*16777216)+($b3*65536)+($b2*256)+($b1) )) )
				read b1 b2 b3 b4 <<< $(od -v -An -tu1 -j$(($infosector*2048+40)) -w4 -N4 "$FILEPATH")
				startsector=$(echo $(( ($b4*16777216)+($b3*65536)+($b2*256)+($b1) )) )
				
				read isboot type null null null null s1 s2 <<< $(od -v -An -tx1 -j$(($infosector*2048+32)) -w8 -N8 "$FILEPATH")
				
				[ $isboot -ne 88 ] && break
				
				case $type in
					01) cnt=$((1200*1024/512))
						size='1.2M'
					;;
					02) cnt=$((1440*1024/512))
						size='1.44M'
					;;
					03) cnt=$((2880*1024/512))
						size='2.88M'
					;;
					*)	break
					;;
				esac

				if [ "$MODE" != "list" ]; then
					echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext "Extracting embedded floppy boot image:") ${size}.\n"
					mkdir -p _.BOOTIMAGE
					tail -c +$((startsector*512*4+1)) "$FILEPATH" | head -c $((cnt*512)) > "./_.BOOTIMAGE/FLOPPY.img" || { [ $? -ne 141 ] && error=1; }	
				else
					echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext "Found embedded floppy boot image:") ${size}.\n"
				fi
			
			done
			
			# For hybrid ISOs
			if [ "$(LANG=C file -L -b "$FILEPATH" | grep -wE 'boot sector|block special')" ] &&
				func_exists fdisk
			then
				#func_mnt_disk_image "$FILEPATH" ISO && return 0

				echo -e "${YELLOWCOL}$(gettext 'WARNING:') ${OFFCOL}$(gettext "Failed to list/extract some partitions!")"
				echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext "Trying generic 'mount' command...")\n"

				if [ "$MODE" != "list" ]; then
					mkdir -p '_.ISO'
					cd '_.ISO'
				fi
			fi
			
			# HBCD gets mounted as *empty* if there is no 'norock' option (?!)
			mount -o utf8,loop,ro -t iso9660 "$FILEPATH" "$WORKDIR" 2>/dev/null
			ls "$WORKDIR" >/dev/null 2>&1 && opts='' || opts='norock'
			umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null

			mount -o utf8,loop,ro,${opts} -t iso9660 "$FILEPATH" "$WORKDIR" || return 1
			func_extract_mntpt || error=1
			umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null
		;;
		# -----------
		*.jpa|*.jps)
			func_precheck php kickstart.php || return 1
			f=$(type -p kickstart.php)

			if [ "${fileext##*.}" = 'jps' ]; then	# not actually tested (don't have an example .jps file)
				func_askpass
				opts="--password $PASSWORD"
			else
				opts=''
			fi
			
			php "$f" "$FILEPATH" ${KICKSTART_OPTS} ${opts} || return 1
		;;
		# -----------
		*.jpg|*.jpeg|*.png|*.bmp|*.wav|*.au)
			error=1	# error by default, if none of the below tools succeeded

			# APNG (Animated PNG)
			if [ "${fileext##*.}" = 'png' ] && head -c 256 "$FILEPATH" | grep -q -m1 'acTL'; then
				echo -ne "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying to extract animated PNG...')\n"
				[ "$MODE" = "list" ] && outdir="$WORKDIR" || outdir="."
				if func_precheck apngdis; then
					cp -a "$FILEPATH" "${outdir}/${FILENAME}"
					if apngdis "${outdir}/${FILENAME}" "${FILENAME_NOEXT}_";then 
						error=0
						echo -e "\n${GREENCOL}$(gettext 'Succeeded!')${OFFCOL}\n"
					else
						echo -e "${REDCOL}$(gettext 'Failed!')${OFFCOL}\n"
					fi
					rm -f "${outdir}/${FILENAME}"
				fi
			fi

			# Barcodes
			case "${fileext##*.}" in
				jpg|jpeg|png)
					if func_precheck zxing; then
						echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying to find a barcode...')\n"
						result="$(LANG=C zxing --try-harder --search-multi "$FILEPATH")"

						if [ "$result" ] && [ "$result" != "decoding failed" ]; then
							error=0
							echo "$result"; echo
							echo -e "${GREENCOL}$(gettext 'Succeeded!')${OFFCOL}\n"
							[ "$MODE" != "list" ] && echo "$result" > "${FILENAME_NOEXT}_$(gettext "barcode").txt"
						else
							echo -e "${REDCOL}$(gettext 'Failed!')${OFFCOL}\n"
						fi

					fi
				;;
			esac

			# Steganography
			case "${fileext##*.}" in
				jpg|jpeg|bmp|wav|au)
					if func_precheck steghide; then
						echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying to find an embedded stego file...')\n"
						if steghide $STEGHIDE_OPTS "$FILEPATH"; then
							error=0
							echo -e "\n${GREENCOL}$(gettext 'Succeeded!')${OFFCOL}"
						else
							echo -e "\n${REDCOL}$(gettext 'Failed!')${OFFCOL}"
						fi
					fi
				;;
			esac
		;;
		# -----------
		*.lbr)
			func_precheck lbrate || return 1
			lbrate $LBRATE_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.lha|*.lza|*.lzh)
			if func_precheck lha; then
				lha $LHA_OPTS "$FILEPATH" && return 0 || return 1
			else
				func_try_7z "$FILEPATH" && return 0 || return 1
			fi
		;;
		# -----------
		*.lnx|*.t64)
			func_precheck c1541 || return 1
			
			[ "$MODE" != 'list' ] && echo "$(gettext 'Extracting...')"
			
			case "${fileext##*.}" in
				t64)	cmnd='-tape'	;;
				lnx)	cmnd='-unlynx'	;;
			esac
			
			c1541 -format "$FILENAME_NOEXT",00 d64 "${WORKDIR}/out.d64" ${cmnd} "$FILEPATH" >/dev/null 2>&1 || return 1
			c1541 "${WORKDIR}/out.d64" $C1541_OPTS || return 1
		;;
		# -----------
		*.lzx)
			func_precheck unlzx || return 1
			unlzx $UNLZX_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.mar)
			func_precheck $BUNZIP2 || return 1
			error=1

			if [ "$(head -c4 "$FILEPATH")" != "MAR1" ]; then
				echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'It is not a .mar archive!')"
				return 1
			fi

			read i1 i2 i3 i4 <<< $(od -v -An -tu1 -j4 -w4 -N4 "$FILEPATH")
			index=$(echo $(( ($i1*16777216)+($i2*65536)+($i3*256)+($i4) +4 )) ) || return 1

			while [ ${index} -lt ${FILESIZE} ]; do
				read o1 o2 o3 o4 s1 s2 s3 s4 f1 f2 f3 f4 <<< $(od -v -An -tu1 -j${index} -w12 -N12 "$FILEPATH")
				offset=$(echo $(( ($o1*16777216)+($o2*65536)+($o3*256)+($o4) )) )		|| return 1
				size=$(echo $(( ($s1*16777216)+($s2*65536)+($s3*256)+($s4) )) )			|| return 1
				flags=$(printf '%o' $(( ($f1*16777216)+($f2*65536)+($f3*256)+($f4) )) )	|| return 1

				((index+=12))

				name=''
				while read -r char; do
					[[ "$char" == "\\0" ]] && break || name="${name}${char}"
				done < <(od -v -An -tc -j${index} -w1 "$FILEPATH")

				if [ "$name" = "" ]; then
					return 1
				else
					echo "${name}"
					error=0
				fi

				index=$(( ${index} + ${#name} + 1 ))

				[ "$MODE" = "list" ] && continue

				mkdir -p "$(dirname "$name")"

				case "$(tail -c +$((${offset}+1)) "$FILEPATH" | head -c 3)" in
					BZh)	cmnd="$BUNZIP2 -c -d"	;;
					?7z)	cmnd="$UNXZ -c -d"		;;
					*)		cmnd=''					;;	# unknown compression
				esac

				if [ "$cmnd" = '' ]; then
					echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Unknown compression!')"
					continue
				else
					tail -c +$((${offset}+1)) "$FILEPATH" | head -c ${size} | ${cmnd} > "${name}" || { [ $? -ne 141 ] && error=1; }
					chmod $flags "${name}"
				fi
			done
		;;
		# -----------
		*.mo3)
			func_precheck unmo3 || return 1

			if [ "$MODE" = "list" ]; then
				cd "$WORKDIR"
				unmo3 -s all "$FILEPATH" | sed 's/^saving //g' || { [ $? -ne 141 ] && return 1; }
			else
				unmo3 -s all "$FILEPATH" || return 1
			fi
		;;
		# -----------
		*.mpq|*.sc2assets|*.sc2archive|*.sc2data|*.sc2mod|*.sc2ma)
			func_precheck MPQExtractor || return 1
			MPQExtractor $MPQEXTRACTOR_OPTS "*" "$FILEPATH" || return 1
		;;
		# -----------
		*.nz)
			func_precheck nz || return 1
			nz $NZ_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.package)
			func_precheck $GUNZIP tar || return 1

			compression=$(grep -m1 -a -E 'compression=".*"' "$FILEPATH" | cut -f2 -d '"')
			skip_lines=$(grep -m1 -a -E 'skip_lines=".*"|skipLines=".*"' "$FILEPATH" | cut -f2 -d '"')
			meta_size=$(grep -m1 -a -E 'meta_size=".*"|metaSize=".*"' "$FILEPATH" | cut -f2 -d '"')
			data_size=$(grep -m1 -a -E 'data_size=".*"|dataSize=".*"' "$FILEPATH" | cut -f2 -d '"')

			if [ ! "$skip_lines" ] || [ ! "$meta_size" ] || [ ! "$data_size" ]; then
				echo -e "${REDCOL}$(gettext "Error...")${OFFCOL}"
				return 1
			fi

			if [ "$MODE" != "list" ]; then
				mkdir -p meta payload
				opts1='xvf - -C meta'
				opts2='xvf - -C payload'
			else  
				opts1='tvf -'
				opts2='tvf -'
			fi

			tail -n +${skip_lines} "$FILEPATH" | head -c ${meta_size} | $GUNZIP -c -d | tar ${opts1} || { [ $? -ne 141 ] && error=1; }
			
			cmnd="$(func_set_decompressor "$compression ")"	# we need that space!
			[ "$cmnd" ] || cmnd="${BUNZIP2} -c -d"	# default to bzip2
			
			if func_precheck ${cmnd%% *}; then
				tail -c ${data_size} "$FILEPATH" | ${cmnd} | tar ${opts2} || { [ $? -ne 141 ] && error=1 || error=0; }
			else
				error=1
			fi

			if [ "$MODE" != "list" ]; then
				find ./meta ./payload -maxdepth 0 -type d -empty -delete 2>/dev/null
			fi
		;;
		# -----------
		*.paq8l|*.paq8n|*.paq8o|*.kgb)
			cmnd="${fileext##*.}"	# becomes paq8l or paq8n or paq8o or kgb

			if [ "${cmnd}" = "kgb" ]; then
				id_string="KGB_arch"
				outdir=''
			else
				id_string="paq8"
				outdir='.'
			fi

			if [ "$MODE" = "list" ]; then
				cat "$FILEPATH" | while read -r line; do
					echo "$line" | grep -qE "^[0-9]+$(echo -e "\t")|^${id_string}" && echo "$line" || break
				done
			else
				func_precheck $cmnd || return 1
				$cmnd "$FILEPATH" $outdir || return 1
			fi
		;;
		# -----------
		*.pax)
			if [ "$(echo "$FILEINFO" | grep -wi 'cpio')" ]; then
				func_precheck cpio || return 1
				cpio $CPIO_OPTS $CPIO_ABS_OPT < "$FILEPATH" || return 1
			else
				func_precheck tar || return 1
				tar $TAR_OPTS "$FILEPATH" || return 1
			fi
		;;
		# -----------
		*.pbi)
			if [[ "$FILEINFO" == "ELF"* ]]; then
				func_precheck $SEVENZ tar || return 1
				$SEVENZ x -spd -so "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
			else
				func_precheck $BUNZIP2 tar || return 1
				$BUNZIP2 -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
			fi
		;;
		# -----------
		*.pbp)
			if [ "$(head -c 4 "$FILEPATH" | tail -c 3)" != "PBP" ]; then
				echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'It is not a .pbp file!')"
				return 1
			fi

			files=( PARAM.SFO ICON0.PNG ICON1.PMF PIC0.PNG PIC1.PNG SND0.AT3 DATA.PSP DATA.PSAR )

			for i in {0..7}; do
				read b1 b2 b3 b4 <<< $(od -v -An -tu1 -j$((8+(i*4))) -w4 -N4 "$FILEPATH")
				offset[$i]=$(echo $(( ($b4*16777216)+($b3*65536)+($b2*256)+($b1) )) ) || return 1
			done

			offset[8]=${FILESIZE}

			for i in {0..7}; do
				printf "%-32s%s\n" "${files[$i]}" "$(( ${offset[$(($i+1))]} - ${offset[$i]} )) $(gettext 'bytes')"
				[ "$MODE" = "list" ] && continue
				tail -c +$((${offset[$i]}+1)) "$FILEPATH" | head -c $(( ${offset[$(($i+1))]} - ${offset[$i]} )) > ${files[$i]} || { [ $? -ne 141 ] && error=1; }  
			done
		;;
		# -----------
		*.pdf)
			func_precheck pdfinfo || return 1

			unset PASSWORD

			if [ "$(LANG=C pdfinfo "$FILEPATH" 2>&1 | grep -i 'Incorrect password')" != "" ]; then
				func_askpass
			fi

			if [ "$MODE" = "list" ]; then

				if func_precheck pdftotext; then
					echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Listing text...')"
					pdftotext -opw "$PASSWORD" -upw "$PASSWORD" -layout -nopgbrk -enc UTF-8 "$FILEPATH" - 2>/dev/null | wc -l | tr -d '\n' || error=1
					echo -e " $(gettext 'lines')\n"
				else
					error=1
				fi
	
				if func_precheck pdfimages; then
					echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Listing images...')"
					pdfimages -opw "$PASSWORD" -upw "$PASSWORD" -list "$FILEPATH" 2>/dev/null || error=1
				else
					error=1
				fi
	
				if func_precheck pdfdetach; then
					echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Listing attachments...')"
					pdfdetach -opw "$PASSWORD" -upw "$PASSWORD" -list "$FILEPATH" 2>/dev/null || error=1
				else
					error=1
				fi

			else

				mkdir -p ./TEXT/{RAW,LAYOUT} ./IMAGES ./ATTACHMENTS

				if func_precheck pdftotext; then
					echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Extracting text...')"
					pdftotext -opw "$PASSWORD" -upw "$PASSWORD" -raw -nopgbrk -enc UTF-8 "$FILEPATH" "./TEXT/RAW/${FILENAME_NOEXT}.txt" || error=1
					pdftotext -opw "$PASSWORD" -upw "$PASSWORD" -layout -nopgbrk -enc UTF-8 "$FILEPATH" "./TEXT/LAYOUT/${FILENAME_NOEXT}.txt" || error=1
					for i in ./TEXT/*/*.txt; do [ $(stat -c %s "$i") -eq 0 ] && rm "$i"; done 2>/dev/null
					find ./TEXT -maxdepth 1 -type d -empty -delete 2>/dev/null
					[ ! -d ./TEXT ] && echo -e "${OFFCOL}$(gettext 'No text found!')\n" || echo;:
				else
					error=1
				fi

				if func_precheck pdfimages; then
					echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Extracting images...')"
					pdfimages --help 2>&1 | grep -qFw -- '-all' && PDFIMAGES_OPT='-all' || PDFIMAGES_OPT=''
					pdfimages $PDFIMAGES_OPT -opw "$PASSWORD" -upw "$PASSWORD" -p "$FILEPATH" "./IMAGES/${FILENAME_NOEXT}" || error=1
					find ./IMAGES -maxdepth 0 -type d -empty -delete 2>/dev/null
					[ ! -d ./IMAGES ] && echo -e "${OFFCOL}$(gettext 'No images found!')\n" || echo;:
				else
					error=1
				fi

				if func_precheck pdfdetach; then
					echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Extracting attachments...')"
					cnt=1
					#for ext in $(pdfdetach -opw "$PASSWORD" -upw "$PASSWORD" -list "$FILEPATH" | tail -n +2 | rev | cut -f1 -d '.' | rev); do
					for ext in $(pdfdetach -opw "$PASSWORD" -upw "$PASSWORD" -list "$FILEPATH" | tail -n +2 | sed 's#^.*\.##'); do
						pdfdetach -opw "$PASSWORD" -upw "$PASSWORD" -save $cnt "$FILEPATH" -o "./ATTACHMENTS/${FILENAME_NOEXT}_${cnt}.${ext}" || error=1
						((cnt++))
					done
					find ./ATTACHMENTS -maxdepth 0 -type d -empty -delete 2>/dev/null
					[ ! -d ./ATTACHMENTS ] && echo -e "${OFFCOL}$(gettext 'No attachments found!')\n";: 
				else
					error=1
				fi

			fi
		;;
		# -----------
		*.pdmod)
			# The password is: 0$45'5))66S2ixF51a<6}L2UK
			func_precheck $SEVENZ || return 1
			$SEVENZ $SEVENZ_OPTS "$FILEPATH" -p'0$45'"'"'5))66S2ixF51a<6}L2UK' || return 1
		;;
		# -----------
		*.pet)
			# For PETs downloaded via browser from e.g. nluug
			# when they get decompressed during the transfer
			if [ "$(echo "$FILEINFO" | grep -wi 'POSIX tar')" ]; then
				func_precheck tar || return 1
				tar $TAR_OPTS "$FILEPATH" && return 0 || return 1
			fi

			cmnd="$(func_set_decompressor "$FILEINFO")"
			
			func_precheck ${cmnd%% *} tar || return 1
			head -c -32 "$FILEPATH" | ${cmnd} | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.phar)
			func_precheck php || return 1

			if [ "$MODE" = "list" ]; then
				php -r '$phar = new Phar("'${FILEPATH}'"); foreach( new RecursiveIteratorIterator($phar) as $file) { echo $file->getSize() . "\t" . $file->getPathName() . "\n"; };' || return 1
			else
				echo "$(gettext 'Extracting...')"
				php -r '$phar = new Phar("'${FILEPATH}'"); $phar->extractTo(".");' || return 1
			fi
		;;
		# -----------
		*.pgn)
			func_precheck pgn-extract || return 1
			if [ "$MODE" = "list" ]; then
				pgn-extract "$FILEPATH" >/dev/null || return 1
			else
				pgn-extract -#1 "$FILEPATH" || return 1
			fi
		;;
		# -----------
		*.pkg|*.xbps|*.xpak)
			if [ "${fileext##*.}" = 'pkg' ] && echo "$FILEINFO" | grep -wiq "^xar"; then	# Mac OS X Installer Package

				func_precheck $SEVENZ $GUNZIP cpio || return 1
	
				if [ "$MODE" = "list" ]; then
					echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Listing the main archive...')\n"
	
					$SEVENZ l -txar -spd "$FILEPATH" || return 1
	
					#for subarchive in $($SEVENZ l -txar "$FILEPATH" | grep -E 'Payload|Scripts' | rev | cut -f1 -d ' ' | rev); do
					for subarchive in $($SEVENZ l -txar -spd "$FILEPATH" | sed -n 's#.* \(.*/Payload\|.*/Scripts\).*#\1#p'); do
						echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Listing subarchive:')'$subarchive'\n"
						$SEVENZ e -txar -so "$FILEPATH" "$subarchive" 2>/dev/null | $GUNZIP -c -d | cpio -itv $CPIO_ABS_OPT || { [ $? -ne 141 ] && error=1; }
					done
				else
					$SEVENZ x -txar -spd "$FILEPATH" || return 1
	
					while read -r subdir; do
						cd "$subdir"
						for i in Payload Scripts; do
							if [ -f "${i}" ]; then
								mv "${i}" "${i}.cpio.gz"
								$GUNZIP -c -d -v "${i}.cpio.gz" | cpio -idmv $CPIO_ABS_OPT || { [ $? -ne 141 ] && error=1; }
								rm "${i}.cpio.gz"
							fi
						done
						[ "$subdir" != "." ] && cd ..
					done < <(find . -maxdepth 1 -type d -iname "*.pkg"; echo . )
				fi
				
			else	# FreeBSD PKG or Void XBPS or Gentoo XPAK

				# *.xpak needs more output from 'file' (second result), specifically the '-k' option
				mime="$(LANG=C file -L -b -k "$FILEPATH" | cut -f1 -d ',' | grep -m1 -wioE 'bzip2|bzip3|gzip|lz4|lzip|lzma|lzop|xz|zstandard|posix tar' | head -n 1 | tr '[:upper:]' '[:lower:]')"
				cmnd="$(func_set_decompressor "$mime ")"	# we need that space!
				
				if [ "$cmnd" = '' ]; then
					echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Unknown compression!')"
					return 1
				fi
	
				func_precheck ${cmnd%% *} tar || return 1
				$cmnd "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
			fi
		;;
		# -----------
		*.prg)
			func_precheck unp64 || return 1

			result="$(LANG=C unp64 $UNP64_OPTS -o "$FILENAME" "$FILEPATH" 2>&1)"
			echo "$result"

			if [ "$MODE" = "list" ]; then
				[[ "$result" != *'(Unknown)'* ]] && return 0 || return 1
			else
				[ "$(ls -A)" ] && return 0 || return 1
			fi
		;;
		# -----------
		*.qcow|*.qcow2|*.qcow2c|*.vdi|*.vmdk)
			if func_precheck qemu-nbd; then
				modprobe nbd || return 1 && sleep 1
				NBD_DEV=0

				while true; do
					if [ ! -b /dev/nbd${NBD_DEV} ]; then
						NBD_DEV=''
						return 1
					fi

					qemu-nbd -r -c /dev/nbd${NBD_DEV} "$FILEPATH" 2>/dev/null && break
					((NBD_DEV++))
				done

				func_mnt_disk_image "/dev/nbd${NBD_DEV}" || error=1
				qemu-nbd -d /dev/nbd${NBD_DEV} >/dev/null 2>&1 
				NBD_DEV=''
			else
				func_try_7z "$FILEPATH" || return 1	    
			fi
		;;
		# -----------
		*.rar|*.rar5|*.r[0-9]|*.r[0-9][0-9]|*.cbr|*.fomod|*.lemon|*.rsn)
			if [ "${fileext##*.}" = 'rar' ] && echo "$FILEINFO" | grep -q 'JAR'; then
				if func_precheck unzip; then
					unzip $UNZIP_OPTS "$FILEPATH" && return 0 || return 1
				else
					func_try_7z "$FILEPATH" && return 0 || return 1
				fi
			fi

			if func_precheck $UNRAR; then
				$UNRAR $UNRAR_OPTS "$FILEPATH" && return 0
			fi

			func_try_7z "$FILEPATH" || return 1
		;;
		# -----------
		*.rpm|*.spm)
			echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'rpm2cpio' + 'cpio'...\n"
			if func_precheck rpm2cpio cpio; then
				rpm2cpio "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT && [ $? -eq 0 -o $? -eq 141 ] && return 0
			fi
			
			# Custom routine (based on Engrampa's rpm2cpio and amigo's exploderpm)
			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying internal method') + 'cpio'...\n"
			offset=104
			read b1 b2 b3 b4 b5 b6 b7 b8 <<< $(od -v -An -tu1 -j${offset} -w8 -N8 "$FILEPATH")
			sigsize=$(echo $((8 + 16 * (($b1*16777216)+($b2*65536)+($b3*256)+($b4)) + ($b5*16777216)+($b6*65536)+($b7*256)+($b8) )) )
			offset=$(echo $((offset + sigsize + (8 - (sigsize % 8)) % 8 + 8)) )
			read b1 b2 b3 b4 b5 b6 b7 b8 <<< $(od -v -An -tu1 -j${offset} -w8 -N8 "$FILEPATH")
			offset=$(echo $((offset + 8 + 16 * (($b1*16777216)+($b2*65536)+($b3*256)+($b4)) + ($b5*16777216)+($b6*65536)+($b7*256)+($b8) )) )
			sigtype="$(tail -c +$((offset+1)) "$FILEPATH" | head -c 4 | tr -d '\0')"
			case "$sigtype" in
				"BZh"*)								cmnd="$BUNZIP2 -c -d"	;;
				"$(echo -en '\x1f\x8b')"*)			cmnd="$GUNZIP -c -d"	;;
				"]"*|?"7z"*)						cmnd="$UNXZ -c -d"		;;
				"$(echo -en '\x28\xb5\x2f\xfd')"*)	cmnd="$UNZSTD -c -d"	;;
				*)									cmnd=''					;;
			esac
			if [ "$cmnd" = '' ]; then
				echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Unknown compression!')"
			else
				func_precheck ${cmnd%% *} &&
				tail -c +$((offset+1)) "$FILEPATH" | ${cmnd} | cpio $CPIO_OPTS $CPIO_ABS_OPT && [ $? -eq 0 -o $? -eq 141 ] && return 0
			fi
			
			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') 'exploderpm'...\n"
			if func_precheck exploderpm; then
				exploderpm $EXPLODERPM_OPTS "$FILEPATH" && return 0
			fi

			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying') '${SEVENZ}' + 'cpio'...\n"
			if func_precheck $SEVENZ cpio; then
				case "$($SEVENZ l -spd "$FILEPATH" | grep -o '\.cpio.*' | tail -n 1)" in
					*'zstd')	cmnd="$UNZSTD -c -d"	;;
					*)			cmnd="cat"				;;
				esac
				func_precheck ${cmnd%% *} || return 1
				$SEVENZ x -so -spd "$FILEPATH" | ${cmnd} | cpio $CPIO_OPTS $CPIO_ABS_OPT && [ $? -eq 0 -o $? -eq 141 ] && return 0
			fi

			return 1
		;;
		# -----------
		*.run|*.sh|*.app|*.appimage|*.orb)	# supports at least amd/nvidia/vbox/truecrypt/veracrypt/BOINC/Unity/Java_EE_SDK/EAGLE installers + makeself.sh + portablelinuxapps + AppImage + BurpSuite
			if [ "$(od -v -An -tc -j32769 -w5 -N5 "$FILEPATH" 2>/dev/null | tr -d ' \n')" = "CD001" ]; then

				func_mnt_disk_image "$FILEPATH" || return 1

			elif [ "${fileext##*.}" = "appimage" ]; then

				func_precheck $LOSETUP mount || return 1

				offset=$(grep -abo -m2 'hsqs' "$FILEPATH" | tail -n 1 | cut -f1 -d ':')	# 2nd 'hsqs' is "the one"

				# Note: BB's mount doesn't support 'offset'! Use losetup.
				if [ "$offset" ]; then
					#mount -o loop,ro,offset=${offset} "$FILEPATH" "$WORKDIR"
					FREELOOP="$($LOSETUP -f)"
					[ "$FREELOOP" = "" ] && return 1

					if $LOSETUP $LOSETUP_R_OPT -o ${offset} "${FREELOOP}" "$FILEPATH" 2>/dev/null &&
						func_mount "$FREELOOP" "$WORKDIR"
					then
						func_extract_mntpt || error=1
					else
						error=1
					fi
					umount "$WORKDIR" 2>/dev/null || umount -f -l "$WORKDIR" 2>/dev/null
					$LOSETUP -d "$FREELOOP" 2>/dev/null
					FREELOOP=''
				else
					return 1
				fi

			else

				for DUMMY in DUMMY; do
					offset=$(head -n 1024 "$FILEPATH" | grep -a -m1 -E '^PACKAGE_START=|#.*LINES:' | tr -c -d '[:digit:]')	# Veracrypt
					[ "$offset" ] && { method="-n +$offset"; break; }

					offset=$(head -n 1024 "$FILEPATH" | grep -a -m1 -n -E '^__ARCHIVE_BEGINS_HERE__|^__DATA__|^__TARFILE_FOLLOWS__' | cut -f1 -d ':')	# Unity-editor, EAGLE, genymotion
					[ "$offset" ] && { method="-n +$((offset+1))"; break; }

					offset=$(head -n 1024 "$FILEPATH" | grep -a -m1 '^filesizes=' | tr -c -d '[:digit:]')	# MakeSelf
					[ "$offset" ] && { method="-c $offset"; break; }

					offset=$(head -n 1024 "$FILEPATH" | grep -a -m1 '^skip=' | tr -c -d '[:digit:]')	# NVidia (has to be after MakeSelf!)
					[ "$offset" ] && { method="-n +$offset"; break; }
					
					[ "$(head -n 3 "$FILEPATH" | grep 'BOINC.*binstall.sh')" ] && { method="-n +4"; break; }	# BOINC

					offset=$(head -n 1024 "$FILEPATH" | grep -a -o -m1 -E 'tail \+[0-9]+l' | cut -f2 -d ' ' | head -n 1)	# java_ee_sdk
					[ "$offset" ] && { method="$offset"; break; }

					offset=$(head -n 1024 "$FILEPATH" | grep -a -o -m1 -E 'tail \+[0-9]+' | cut -f2 -d ' ' | head -n 1)	# j2sdk
					[ "$offset" ] && { method="$offset"; break; }
					
					offset=$(head -n 1024 "$FILEPATH" | grep -a -m1 -E 'tail ' | grep -oE '[0-9]+' | head -n 1)
					[ "$offset" ] && { method="-c $offset"; break; }

					echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Unknown method!')"
					return 1
				done
				
				mime="$(tail $method "$FILEPATH" | LANG=C file -L -b -)"

				# in this case extract directly, no need to tail the actual ZIP to a tmpfile first
				#if [[ "$mime" =~ ^(Java*|ELF*) ]]; then	# doesn't work with Bash 3
				if [[ "$mime" == 'Java'* ]] || [[ "$mime" == 'ELF'* ]]; then
					if func_precheck unzip; then
						unzip $UNZIP_OPTS "$FILEPATH" && return 0 || { [ $? -eq 1 ] && return 0 || return 1; }	# 1 means extra bytes at the beginning of the file; ignore
					else
						func_precheck $SEVENZ || return 1
						$SEVENZ $SEVENZ_OPTS "$FILEPATH" && return 0 || return 1
					fi
				fi
				
				cmnd="$(func_set_decompressor "$mime")"
				[ "$cmnd" ] || { echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Unknown compression!')"; return 1; }
				
				func_precheck "${cmnd%% *}" || return 1
				tail $method "$FILEPATH" | ${cmnd} | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1 || return 0; }
				
			fi
		;;
		# -----------
		*.sar|*.sqlar)
			func_precheck $SQLITE || return 1
			[ "$SQLITE" = "fossil" ] && opts='sqlite3 --no-repository' || opts=''
			$SQLITE ${opts} "$FILEPATH" "$SQLITE_OPTS"  || return 1
		;;
		# -----------
		*.sf2)
			if [ "$MODE" = "list" ]; then
				#shdr="$(grep -abo -m1 'shdr' "$FILEPATH" | cut -f1 -d ':')"
				#[ "$shdr" ] || return 1
				#shdr=$((shdr+8))
				#cnt=1
				#while true; do
				#	name=''
				#	while read -r char; do
				#		[[ "$char" == "\\0" ]] && break || name="${name}${char}"
				#	done < <(od -v -An -tc -j${shdr} -N20 -w1 "$FILEPATH")
				#
				#	([ "$name" = '' ] || [ "$name" = 'EOS' ]) && break
				#	echo "${cnt}. ${name}"
				#	shdr=$((shdr+46))
				#	cnt=$((cnt+1))
				#done
				#[ $cnt -gt 1 ] && return 0 || return 1
				func_precheck sf2dump || return 1
				sf2dump "$FILEPATH" || return 1
			else
				func_precheck sf2extract || return 1
				sf2extract "$FILEPATH" . || return 1
			fi
		;;
		# -----------
		*.sfs|*.slm|*.srm|*.usfs|*.pfs|*.sb|*.scm|*.snap|*.sqf|*.sqfs|*.sqsh|*.squashfs|*.sxz|*.opk|*.tcz|*.lzm|*.xzm)
			if [[ "${fileext##*.}" == "tcz" && "$FILEINFO" == *" ROM "* ]]; then

				func_mnt_disk_image "$FILEPATH" || return 1

			else

				func_precheck $UNSQUASHFS || return 1

				if ! $UNSQUASHFS $UNSQUASHFS_OPTS "$FILEPATH"; then
					# Slax's old SFS packages and LZM modules need a patch for squashfs-tools from:
					# https://gitlab.howett.net/polaris/openwrt/commit/90673a048cffc62eaf624e7ca54b1bd1c27b67bd
					if [ $(od -v -An -td4 -w4 -N4 -j0 "$FILEPATH") -eq 1903388787 ]; then
						if func_exists unsquashfs_lzm; then
							unsquashfs_lzm $UNSQUASHFS_OPTS "$FILEPATH" || return 1
						else
							func_try_7z "$FILEPATH" || return 1
						fi
					else
						return 1
					fi
				fi

				# Unsquashfs exits with 0 if 'No space left on device' !!!
				[ "$MODE" != "list" ] && [ "$($DF . | tail -n1 | tr -s ' ' | cut -f4 -d' ')" = "0" ] && return 1
			fi
		;;
		# -----------
		*.shar|*.shr)
			if [ "$MODE" = "list" ]; then

				while read -r line; do
					[ "$line" != "#" ] && echo "$line" || break 
				done < <(grep -A999999999999 "# This shar contains:" "$FILEPATH")

			else

				echo -e "${REDCOL}$(gettext 'WARNING! This is an executable script!')${OFFCOL}"
				read -p "$(gettext 'Are you sure you want to launch it? If so, type uppercase YES:') " -t11 KEY
				
				if [ "$KEY" != "$(gettext "YES")" ]; then
					echo -e "${YELLOWCOL}$(gettext 'Aborted...')${OFFCOL}"
					return 1
				fi

				echo
				sh "$FILEPATH" || return 1

			fi
		;;
		# -----------
		*.spk)
			func_precheck $GUNZIP tar || return 1
			
			if [ "$MODE" != "list" ]; then
				mkdir -p package
				opts="-C package"
			else
				opts=''
			fi

			tar $TAR_OPTS "$FILEPATH" --exclude=package.tgz || return 1
			echo
			tar x --to-stdout -f "$FILEPATH" package.tgz | $GUNZIP -c -d | tar $TAR_OPTS - ${opts} || { [ $? -ne 141 ] && return 1; }
		;;
		# -----------
		*.swf)
			func_precheck swfextract || return 1
			swfextract $SWFEXTRACT_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.tar|*.tar.md5|*.gem|*.gnutar|*.gtar|*.cbt|*.hid|*.ova|*.pat|*.tardist|*.thm|*.tmf|*.ustar|*.wbm|*.xjt)
			func_precheck tar || return 1
			tar $TAR_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.tazpkg|*.spack)
			if [ "${fileext##*.}" = "tazpkg" ]; then
				cmnd="${UNLZMA}"
				tmpfile="fs.cpio.lzma"
			else
				cmnd="${UNXZ}"
				tmpfile="files.xz"
			fi

			func_precheck $cmnd cpio || return 1

			cpio $CPIO_OPTS $CPIO_ABS_OPT < "$FILEPATH" || return 1

			if [ "$MODE" = "list" ]; then
				echo
				cpio --to-stdout -iv -E <(echo "${tmpfile}") < "$FILEPATH" 2>/dev/null | ${cmnd} -c | cpio -itv || { [ $? -ne 141 ] && return 1; }
			else
				$cmnd -c "${tmpfile}" | cpio -idmv $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }
				rm -f "${tmpfile}"
			fi
		;;
		# -----------
		*.ttc)
			func_precheck stripttc || return 1
			[ "$MODE" = "list" ] && outdir="$WORKDIR" || outdir="."
			cp -a "$FILEPATH" "$outdir" || return 1
			cd "$outdir"
			stripttc "$FILENAME" || error=1
			rm "$FILENAME"
		;;
		# -----------
		*.uc2)
			func_precheck unuc2 || return 1
			unuc2 $UNUC2_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.upx)
			func_precheck upx || return 1
			upx $UPX_OPTS "$FILEPATH" -o "./$FILENAME" || return 1
		;;
		# -----------
		*.uu|*.uue|*.xx|*.xxe)
			outname="$(sed -n 's/^begin [0-9]\+ \(.*\)/\1/p' "$FILEPATH")"
			if [ ! "$outname" ]; then
				echo -e "${REDCOL}$(gettext "Error...")${OFFCOL}"
				return 1
			fi
			
			if [ "$MODE" = "list" ]; then
				echo "${outname##*/}" && return 0 || return 1
			fi

			echo "$(gettext 'Extracting...')"

			case "${fileext##*.}" in
				uu|uue)
					func_precheck uudecode || return 1
					uudecode "$FILEPATH" -o "${outname##*/}" || return 1
				;;
				xx|xxe)
					func_precheck $XXDECODE || return 1
					sed "s#${outname}#${outname##*/}#" "$FILEPATH" | $XXDECODE -d || { [ $? -ne 141 ] && return 1; }
				;;
			esac
		;;
		# -----------
		*.vlt)
			if echo "$FILEINFO" | grep -wi 'gzip'; then
				func_precheck $GUNZIP tar || return 1
				$GUNZIP -c -d "$FILEPATH" | tar $TAR_OPTS - || { [ $? -ne 141 ] && return 1; }
			else
				if func_precheck unzip; then
					unzip $UNZIP_OPTS "$FILEPATH" || { [ $? -eq 1 ] && return 0 || return 1; }	# 1 means extra bytes at the beginning of the file; ignore
				else
					func_try_7z "$FILEPATH" || return 1
				fi
			fi
		;;
		# -----------
		*.vmg)
			if [ "$(tr < "$FILEPATH" -d '\0' | head -c 10)" != 'BEGIN:VMSG' ]; then
				echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'It is not a .vmg file!')"
				return 1
			fi
			
			if [ "$MODE" = "list" ]; then
				tr < "$FILEPATH" -d '\0' || return 1
			else
				echo "$(gettext 'Extracting...')"
				tr < "$FILEPATH" -d '\0' > "${FILENAME_NOEXT}.txt" || return 1
			fi
		;;
		# -----------
		*.wad)
			func_precheck wadext || return 1
			if [ "$MODE" = 'list' ]; then
				cd "$WORKDIR"
				wadext "$FILEPATH" -nogfxconvert -nosndconvert | cut -f2- -d ' ' || return 1
			else
				wadext "$FILEPATH" || return 1
				#mv "$FILENAME_NOEXT" "${FILENAME_NOEXT}.$$" || return 1
				#mv "${FILENAME_NOEXT}.$$"/* . || return 1
				#rmdir "${FILENAME_NOEXT}.$$" || return 1
			fi
		;;
		# -----------
		*.webp)
			func_precheck anim_dump || return 1
			
			[ "$MODE" = "list" ] && outdir="$WORKDIR" || outdir="."

			anim_dump -folder "$outdir" -prefix "${FILENAME_NOEXT}_" "$FILEPATH" || return 1

			find "$outdir" -printf "%P\n" | sort -V
		;;
		# -----------
		*.wot)
			func_precheck $BUNZIP2 || return 1
			$BUNZIP2 -c -d -v "$FILEPATH" > "${WORKDIR}/${FILENAME_NOEXT}.ar" || return 1

			if func_precheck ar; then
				ar $AR_OPTS "${WORKDIR}/${FILENAME_NOEXT}.ar" && return 0 || return 1
			else
				func_try_7z "${WORKDIR}/${FILENAME_NOEXT}.ar" && return 0 || return 1
			fi
		;;
		# -----------
		*.xar|*.mpkg)
			if func_precheck xar; then
				xar $XAR_OPTS "$FILEPATH" && return 0 || return 1		
			else
				func_try_7z "$FILEPATH" && return 0 || return 1
			fi
		;;
		# -----------
		*.xmc)
			func_precheck xcur2png || return 1
			xcur2png $XCUR2PNG_OPTS "$FILEPATH"

			if [ $? -eq 255 ]; then
				rm -f *.conf
				return 1
			fi
		;;
		# -----------
		*.zim)
			func_precheck zimdump || return 1

			[ "$MODE" != 'list' ] && echo "$(gettext 'Extracting...')"

			zimdump $ZIMDUMP_OPTS "$FILEPATH" || return 1
			
			if [ "$MODE" != "list" ] && [ "$ZIMDUMP_OPTS" = '-D .' ]; then
				echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Postprocessing, please wait...')"
				for i in A/*; do mv "$i" "${i}.html"; done || return 1
			fi
		;;
		# -----------
		*.zip|*.aab|*.aar|*.a2theme|*.actc|*.amf|*.acp|*.afzplug|*.apk|*.apkm|*.apks|*.apz|*.aqz|*.arduboy|*.atz|*.avastsounds|*.azw2|*.bau|*.bdoc|*.bmz|*.bootskin|*.capx|*.catrobat|*.cbz|*.cdmtz|*.cdmz|*.chrt|*.cptl|*.cptx|*.crf|*.curxptheme|*.curtainsstyle|*.dbk|*.dazip|*.docm|*.docx|*.dotx|*.dxpack|*.ear|*.ecs|*.egg|*.eaz|*.eftx|*.epub|*.esriaddin|*.f3d|*.f3z|*.fb2k-component|*.fcstd|*.flf|*.fzbz|*.fzpz|*.fzz|*.g3x|*.gadget|*.gfar|*.gg|*.goomod|*.gps|*.gszip|*.hmxz|*.honmod|*.htmlz|*.htz|*.hwp|*.i5z|*.icmod|*.imz|*.ip|*.ipa|*.ipcc|*.ipg|*.ipsw|*.ita|*.itz|*.iwd|*.ja|*.jar|*.jic|*.jwpub|*.key|*.kfo|*.kmz|*.kpr|*.ksf|*.ksp|*.kwd|*.lca|*.libzip|*.little|*.love|*.lpk|*.lpkg|*.lxf|*.m2s|*.maff|*.mcaddon|*.mcgame|*.mcpack|*.mcworld|*.mepx|*.mscz|*.mdz|*.mdzip|*.mga|*.mmip|*.mpk|*.mpzip|*.msz|*.mtz|*.mxl|*.mxskin|*.mzp|*.nbf|*.nbm|*.nfl|*.nth|*.numbers|*.nupkg|*.odb|*.odc|*.odf|*.odg|*.odi|*.odm|*.odp|*.ods|*.odt|*.oex|*.oiv|*.otg|*.oth|*.osf|*.osk|*.otp|*.ots|*.ott|*.ovp|*.oxps|*.oxt|*.p2s|*.p2s.backup|*.pages|*.pak|*.par|*.pcv|*.pigm|*.pigs|*.piz|*.pk3|*.pk4|*.pkz|*.pmlz|*.potx|*.ppsm|*.ppsx|*.pptm|*.pptx|*.pup|*.pvga|*.qtz|*.rbk|*.reaperthemezip|*.rp9|*.rxdbak|*.quiz|*.rjs|*.rmskin|*.s3z|*.sam|*.sb2|*.sb3|*.scdoc|*.sdt|*.sdz|*.sfg|*.sh3d|*.sh3f|*.sh3t|*.sjr|*.skz|*.sle|*.smskin|*.smt|*.smzip|*.snb|*.sob|*.soundpack|*.sox|*.sprite2|*.sprite3|*.stc|*.std|*.sti|*.studyarch|*.stw|*.styx|*.sublime-package|*.swc|*.sxc|*.sxd|*.sxg|*.sxi|*.sxm|*.sxw|*.tcx|*.thmx|*.tpz|*.ts4script|*.tsz|*.twz|*.twzip|*.u3p|*.usdz|*.utz|*.wal|*.war|*.wba|*.webz|*.wgt|*.wgz|*.whl|*.widget|*.wlz|*.wmd|*.wmga|*.wmz|*.wotmod|*.wsz|*.xap|*.xapk|*.xflac|*.xlam|*.xlsm|*.xlsx|*.xltx|*.xmz|*.xo|*.xpi|*.xps|*.xrns|*.xwp|*.z[0-9][0-9]|*.zab|*.zad|*.zds|*.zfsendtotarget|*.zi|*.zi_|*.zpi|*.zm9|*.ztd|*.zxp)

			if echo "$FILEINFO" | grep -wi 'gzip'; then
				case "${fileext##*.}" in
					apk)
						func_precheck $GUNZIP tar || return 1
						$GUNZIP -c -d "$FILEPATH" | tar $TAR_OPTS - && return 0 || { [ $? -ne 141 ] && return 1 || return 0; }
						;;
					sdz)	ext='.sdf'	;;
					imz)	ext='.ima'	;;
				esac
				[ "$MODE" = "list" ] && { echo "${FILENAME_NOEXT}${ext}"  && return 0 || return 1; }
				func_precheck $GUNZIP || return 1
				$GUNZIP -c -d -v "$FILEPATH" > "${FILENAME_NOEXT}${ext}" || { [ $? -ne 2 ] && rm -f -- "${FILENAME_NOEXT}${ext}" && return 1; }
				return 0
			fi

			if [ "${fileext##*.}" = "egg" ] && [ ! "$(echo "$FILEINFO" | grep -wi 'Zip')" ]; then
				func_precheck unegg || return 1
				unegg $UNEGG_OPTS "$FILEPATH" && return 0 || return 1	
			fi
			
			if [ "${fileext##*.}" = "pcv" ] && [ ! "$(echo "$FILEINFO" | grep -wi 'Zip')" ]; then
				func_precheck picocrypt || return 1
				if [ "$MODE" = 'list' ]; then
					echo "$FILENAME_NOEXT" && return 0
				else
					cp -a "$FILEPATH" . || return 1
					func_askpass
					picocrypt -p "$PASSWORD" "$FILENAME"
					ret=$?
					rm -- "$FILENAME"
					[ $ret -eq 0 ] && return 0 || return 1
				fi
			fi

			if [ "${fileext##*.}" = "pak" ] && [ ! "$(echo "$FILEINFO" | grep -wi 'Zip')" ] &&
				head -c 1 "$FILEPATH" | grep -qE "$(echo -e "\x04")|$(echo -e "\x05")"	# version: 0x04 or 0x05
			then
				func_precheck pak || return 1
				[ "$MODE" = "list" ] && outdir="$WORKDIR" || outdir="."

				pak -u "$FILEPATH" "$outdir" && error=0 || error=1
				if [ $error -eq 0 ]; then
					cd "$outdir"
					find . -printf "%P\n"
				fi
				
				return $error
			fi

			func_precheck unzip && {
				unzip $UNZIP_OPTS "$FILEPATH" && return 0
				# In case of some extensions ignore errors <= 2 and don't continue with 7z, because unzip handles backslashes properly (but warns) and 7z does conversely
				#[ $? -eq 1 ] && [ "$(echo ${fileext##*.} | grep -E 'capx|eaz|esriaddin|lpk|mpk|xrns')" ] && return 0
				# Actually, always ignore them, because of archives with backslashes as a path separator.
				# According to docs, exit code 1 results in successfully extracted archive anyway and 2 most likely successful.
				[ $? -le 2 ] && return 0
			}

			func_try_7z "$FILEPATH" && return 0 || return 1
		;;
		# -----------
		*.zoo)
			func_precheck zoo || return 1
			zoo $ZOO_OPTS "$FILEPATH" || return 1
		;;
		# -----------
		*.zpaq)
			func_precheck zpaq || return 1
			zpaq $ZPAQ_OPTS "$FILEPATH" $ZPAQ_OPTS2 && return 0
			echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Archive might be encrypted, trying with '-k' option...')\n"
			zpaq $ZPAQ_OPTS "$FILEPATH" $ZPAQ_OPTS2 -k || return 1
		;;
		# -----------
		*.zl|*.zlib|*.zz)
			if [ "${fileext##*.}" = "zz" ] && [ ! "$(echo "$FILEINFO" | grep -wi 'zlib compressed')" ]; then
				func_precheck zzip || return 1
				zzip $ZZIP_OPTS "$FILEPATH" || return 1
			else
				if [ "$MODE" = "list" ]; then
					echo "${FILENAME_NOEXT}" && return 0 || return 1
				fi
				func_precheck $GUNZIP || return 1
				echo "$(gettext 'Extracting...')"
				result=$( { printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - "${FILEPATH}" | LANG=C $GUNZIP -c -d -v > "$FILENAME_NOEXT"; } 2>&1)
				if [ $? -ne 0 ] && [ "$(echo "$result" | grep -v 'unexpected end of file')" != "" ];then
					echo "$result"
					rm -f -- "${FILENAME_NOEXT}"
					return 1
				fi
			fi
		;;
		# -----------
		*.flac|*.mp3|*.ogg|*.wma)
			func_precheck $FFMPEG || return 1

			if [ "$MODE" = "list" ]; then

				echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Listing album art...')\n"
				result="$(LANG=C $FFMPEG -i "$FILEPATH" 2>&1 | grep 'Video: ')"

				if [ "$result" != "" ]; then
					echo "$result"
				else
					echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'No album art found!')${OFFCOL}"
					return 1
				fi

			else

				echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Extracting album art...')\n"

				cnt=0
				result="$(LANG=C $FFMPEG -i "$FILEPATH" 2>&1 | grep 'Video: ')"
				for stream in $(echo "$result" | cut -f2 -d '#' | cut -b1-3); do
					case "$(echo "$result" | grep -wo 'Video: .*' | cut -f2 -d ' ')" in
						'png'*)			ext=png; opts='-acodec copy'	;;
						'mjpeg'*)		ext=jpg; opts='-acodec copy'	;;
						'theora'*|*)	ext=jpg; opts=''				;;
					esac
					[ $cnt -eq 0 ] && cntext= || cntext="(${cnt})"
					LANG=C $FFMPEG -i "$FILEPATH" -map ${stream//./:} -an ${opts} "${FILENAME_NOEXT}${cntext}.${ext}" || error=1
					((cnt++))
				done

				[ $cnt -eq 0 ] && { echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'No album art found!')${OFFCOL}"; return 1; }

			fi
		;;
		# -----------
		*.3g2|*.3ga|*.3gp|*.3gpp|*.asf|*.avi|*.divx|*.f4v|*.flv|*.h264|*.hevc|*.k3g|*.lrv|*.m2ts|*.m2v|*.m4b|*.m4v|*.mk3d|*.mkv|*.mov|*.mp4|*.mpeg|*.mpg|*.mts|*.mxf|*.nsv|*.nut|*.ogv|*.qt|*.skm|*.ts|*.vb|*.vob|*.vp6|*.webm|*.wmv|*.wtv|*.xmv)
			func_precheck $FFMPEG || return 1

			streamsfound=''

			if [ "$MODE" = "list" ]; then

				for stream in "$(gettext 'Listing audio streams...')|Audio" \
						"$(gettext 'Listing video streams...')|Video" \
						"$(gettext 'Listing subtitle streams...')|Subtitle" \
						"$(gettext 'Listing attachment streams...')|Attachment"; do
					echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}${stream%|*}"
					result="$(LANG=C $FFMPEG -i "$FILEPATH" 2>&1 | grep " ${stream##*|}: ")"
					if [ "$result" != "" ]; then
						echo "$result"
						streamsfound=true
					else
						echo -e "${YELLOWCOL}$(gettext 'WARNING:') ${OFFCOL}$(gettext 'No streams found.')"
					fi
				done

			else

				for stream in "$(gettext 'Extracting audio streams...')|Audio" "$(gettext 'Extracting video streams...')|Video" "$(gettext 'Extracting subtitle streams...')|Subtitle" "$(gettext 'Extracting attachment streams...')|Attachment"; do
					echo -e "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}${stream%|*}"

					substreamsfound=''

					stream_info="$(LANG=C $FFMPEG -i "$FILEPATH" 2>&1 | grep "Stream #.*${STREAM##*|}:")"

					for substream in $(echo "$stream_info" | sed -n 's/.*#\(.*\): '${stream##*|}:'.*/\1/p'); do
						case "$substream" in
							*'['*']'*)	substream="${substream%%[*}"	# remove crap like [0x1c0] in .mpg vids
						esac
						sublang="${substream##*[0-9]}"
						[ "$sublang" ] || sublang="(und)"	# if the above crap [0x1c0] instead of (lang), use (und)
						subnum="${substream//./:}"; subnum="${subnum##*:}"; subnum="[${subnum%%(*}]"
						substream="${substream%(*}"	# remove (lang) that might have sliped in

						codec=$(echo "$stream_info" | grep -w "#${substream}" | grep -o " ${stream##*|}: .*" | cut -f3 -d ' '  | tr '[:upper:]' '[:lower:]')

						case "${stream##*|}" in
							Audio)
								ext=$(echo "$codec" | cut -b1-3)
								aformat='-acodec copy'

								for i in adp:wav pcm:wav vor:ogg opu:opus fla:flac eac:eac3; do
									[ "$ext" = "${i%:*}" ] && { ext="${i##*:}"; break; }
								done
								
								[ "$codec" = "adpcm_ima_qt" ] && ext='caf'

								case "${fileext##*.}" in
									'vp6')			aformat='-format wav'	;;
									'k3g'|'skm')	ext='amr'; aformat=''	;;	# used to work, now have to actually convert to amr
								esac
							
								LANG=C $FFMPEG -i "$FILEPATH" -map ${substream//./:} ${aformat} -vn "${FILENAME_NOEXT}${sublang}${subnum}.${ext}" || error=1
							;;
							Video)
								cnt=''
								ext="$(echo "${FILEPATH##*.}" | tr '[:upper:]' '[:lower:]')"
								case "$ext" in
									'divx'|'h264'|'lrv'|'mk3d'|'nsv'|'nut'|'vb'|'vp6'|'xmv')	ext=mkv	;;
									'3gpp'|'k3g'|'skm')		ext=3gp	;;
									'm4b')					ext=tif; cnt='_%03d'	;;
									'qt')					ext=mov	;;
								esac								
								[ "$codec" = 'av1' ] && ext=mkv
								LANG=C $FFMPEG -i "$FILEPATH" -map ${substream//./:} -vcodec copy -an "${FILENAME_NOEXT}${sublang}${cnt}${subnum}.${ext}" || error=1
							;;
							Subtitle)
								[ "$codec" = 'non' ] && continue
								ext=$(echo "$stream_info" | grep -w "#${substream}" | grep -o " Subtitle: .*" | cut -f3 -d ' ' | cut -b1-3)
								case "$ext" in
									mov)	ext=srt	;;	# mov_text
									sub)	ext=srt	;;	# subrip
									web)	ext=vtt	;;	# webvtt
									hdm)	ext=sup	;;	# hdmv_pgs_subtitle
								esac
								if ! LANG=C $FFMPEG -i "$FILEPATH" -map ${substream//./:} -scodec copy -an -vn "${FILENAME_NOEXT}${sublang}${subnum}.${ext}"; then
									echo -e "\n${YELLOWCOL}$(gettext 'WARNING:') ${OFFCOL}$(gettext 'Failed to extract subtitles to their original format!')"
									echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Trying to convert to SRT format...')\n"
									rm "${FILENAME_NOEXT}${sublang}${subnum}.${ext}"
									#LANG=C $FFMPEG -i "$FILEPATH" -map ${substream//./:} -scodec copy -an -vn "${FILENAME_NOEXT}${sublang}${subnum}.sup" || error=1
									LANG=C $FFMPEG -i "$FILEPATH" -map ${substream//./:} -scodec srt -an -vn "${FILENAME_NOEXT}${sublang}${subnum}.srt" || error=1
								fi
							;;
							Attachment)
								LANG=C $FFMPEG -dump_attachment:${substream:2} "" -i "$FILEPATH"	# never returns 0, even if extraction went well
							;;
						esac

						substreamsfound='true'
					done

					[ ! "$substreamsfound" ] && echo -e "${YELLOWCOL}$(gettext 'WARNING:') ${OFFCOL}$(gettext 'No streams found.')" || streamsfound='true'
				done

			fi

			[ "$streamsfound" ] || error=1
		;;
		# -----------
		.FAKE_fusecompress)	# fake extension
			if [ "$MODE" = "list" ]; then
				echo "$FILENAME" && return 0 || return 1
			fi

			func_precheck fusecompress_offline || return 1
			cp -a "$FILEPATH" . || return 1
			fusecompress_offline -v -r null -- "$FILENAME" || { rm -- "$FILENAME"; return 1; }
		;;
		# -----------
		.FAKE_initrd)	# fake extension
			if [[ "$FILEINFO" == *' cpio '* ]]; then
				cmnd='cat'
			else
				cmnd="$(func_set_decompressor "$FILEINFO")"
				if [ "$cmnd" = '' ]; then
					echo -e "${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Unknown compression!')"
					return 1
				fi
			fi

			if [[ "$(LANG=C $cmnd "$FILEPATH" | head -c 16 | file -b -)" != *" cpio "* ]]; then
				echo "$FILENAME_NOEXT"
				[ "$MODE" = "list" ] && return 0
				func_precheck ${cmnd%% *} || return 1
				$cmnd "$FILEPATH" > "$FILENAME_NOEXT" || { [ $? -ne 2 ] && rm -f -- "${FILENAME_NOEXT}" && return 1; }
			else
				func_precheck cpio ${cmnd%% *} || return 1

				$cmnd "$FILEPATH" | cpio $CPIO_OPTS $CPIO_ABS_OPT || { [ $? -ne 141 ] && return 1; }

				# Some initrds can have another, compressed initrd appended
				offset=$(grep -aboE '07070[0-9A-Za-z]+TRAILER!!!' "$FILEPATH" | tail -n 1 | cut -f1 -d ':')
				[ "$offset" ] && offset=$((offset+120)) || return 0

				echo -en "\n${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Scanning for secondary archive...')"

				while [ $offset -lt $FILESIZE ]; do
					if [ $(od -An -td -j${offset} -N1 "$FILEPATH") -eq 0 ]; then
						offset=$((offset+1))
					else
						break
					fi
				done

				if [ ${offset} -ge ${FILESIZE} ]; then
					echo -e "${YELLOWCOL}$(gettext 'not found.')${OFFCOL}"
					return 0
				else
					echo -e "${GREENCOL}$(gettext 'found!')${OFFCOL}"
					[ "$MODE" = "list" ] && return 0
					tail -c +$((offset+1)) "$FILEPATH" > "${FILENAME_NOEXT}.2" || return 1
				fi
			fi
		;;
		# -----------
		.FAKE_kernel)	# fake extension
			# DON'T USE: doesn't work with SystemRescueCD's altker32
			#if [ "$MODE" != 'list' ]; then 
			#	func_precheck extract-vmlinux && {
			#		echo -e "${BLUECOL}$(gettext 'INFO:') ${OFFCOL}$(gettext 'Extracting vmlinux...')"
			#		result="$(extract-vmlinux "$FILEPATH" 2>&1 > vmlinux)"
			#		echo "$result"
			#		case "$result" in
			#			*"Cannot find vmlinux."*)
			#				rm vmlinux
			#				error=1
			#				;;
			#			*)
			#				error=0
			#				;;
			#		esac
			#	}
			#fi

			func_precheck extract-ikconfig || return 1

			if [ "$MODE" = "list" ]; then
				extract-ikconfig "$FILEPATH" || return 1
			else
				echo "$(gettext 'Extracting...')"
				extract-ikconfig "$FILEPATH" > DOTconfig || { rm DOTconfig; return 1; }
			fi
		;;
		# -----------
		.FAKE_uimage)	# fake extension
			# Type=4 is Multi-File image
			# Compression: 0 - No Compression, 1 - gzip, 2 - bzip2, 3 - lzma, 4 -lzo
			type=$(od -v -An -td1 -w1 -N1 -j 30 "$FILEPATH")
			compression=$(od -v -An -td1 -w1 -N1 -j 31 "$FILEPATH")

			case $(echo ${compression}) in	# echo to get rid of leading spaces
				1)	cmnd="$GUNZIP -c -d"	;;
				2)	cmnd="$BUNZIP2 -c -d"	;;
				3)	cmnd="$UNLZMA -c -d"	;;
				4)	cmnd="$UNLZOP -c -d"	;;
				*)	cmnd='cat'				;;	# no compression
			esac
			
			func_precheck ${cmnd%% *} || return 1
			
			if [ ${type} -eq 4 ]; then
				size=-1
				sizes=()
				offset=64
				while [ ${size} -ne 0 ]; do
					#size=$(echo $((0x$(od -v -An -tx4 -w4 -N4 -j $offset "$FILEPATH"  | tr -d ' \t\n' | tac -rs ..))))
					size=$(od -v -An -tx4 -w4 -N4 -j $offset "$FILEPATH" | tr -d ' \t\n')
					size=$(echo $((0x${size:6:2}${size:4:2}${size:2:2}${size:0:2})))
					sizes=( ${sizes[@]} ${size} )
					offset=$((offset+4))
				done
			
				cnt=0
				while [ ${sizes[cnt]} -ne 0 ]; do
					name=$(tail -c +$((offset+32+1)) "$FILEPATH" | head -c 32 | tr -d '\0')
					[ "$name" ] || return 1
					echo "$name"
					if [ "$MODE" != "list" ]; then
						tail -c +$((offset+1)) "$FILEPATH" | head -c ${sizes[cnt]} | ${cmnd} > "$name" || { [ $? -ne 141 ] && error=1; }
						[ $(stat -c %s "$name") -ne ${sizes[cnt]} ] && { rm "$name"; return 1; }
					fi
					offset=$((offset+sizes[cnt]))
					cnt=$((cnt+1))
				done
			
			else
			
				name=$(tail -c +33 "$FILEPATH" | head -c 32 | tr -d '\0')
				echo "$name"
				if [ "$MODE" != "list" ]; then
					tail -c +65 "$FILEPATH" | ${cmnd} > "$name" || { [ $? -ne 141 ] && error=1; }
				fi
			
			fi
		;;
		# -----------
		*)	# Unsupported extensions
			echo -e "${YELLOWCOL}$(gettext 'WARNING:') ${OFFCOL}$(gettext 'Format not supported!')${OFFCOL}"

			func_try_tar "$FILEPATH" && return 0
			func_try_7z "$FILEPATH" && return 0

			return 1
		;;
		# -----------
	esac

	[ $error -eq 0 ] && return 0 || return 1
}

#################################### START ####################################

# If gettext is missing, use 'echo' wrapper
if ! func_exists gettext; then
	gettext() { echo "$@"; };
fi

# Check if tput is available and fallback to stty if not
if func_exists tput; then
	TERM_WIDTH_CMD='tput cols'
else
	TERM_WIDTH_CMD='stty size'
fi

unset CURSTOMDIR FILESSTACK EXTRAOPT

CUSTOMDIR_FLAG=false
SKIP_SYMLINKS=false
MODE=extract
RECURSIVE=false
BADOPT=false

# Colors
OFFCOL="\e[00m"
REDCOL="\e[0;31m"
BLUECOL="\e[0;34m"
GREENCOL="\e[0;32m"
YELLOWCOL="\e[0;33m"
UNDERLINE="\e[0;32;4m"

# No args, show help
[ "$*" ] || EXTRAOPT=help

# Parse args
while [ $# -gt 0 ]; do
	case "$1" in
		-o|-output|--output)
			CUSTOMDIR="$2"
			CUSTOMDIR_FLAG=true
			shift; shift	# 'shift 2' hangs if only one arg left
		;;
		-output=*|--output=*)
			CUSTOMDIR="${1#*=}"
			CUSTOMDIR_FLAG=true
			shift
		;;
		-l|-list|--list)
			MODE=list
			shift
		;;
		-r|-recursive|--recursive)
			RECURSIVE=true
			shift
		;;
		-s|-skip-symlinks|--skip-symlinks)
			SKIP_SYMLINKS=true
			shift
		;;
		-n|-no-colors|--no-colors)
			unset REDCOL BLUECOL GREENCOL YELLOWCOL UNDERLINE
			shift
		;;
		-b|-backends|--backends)
			EXTRAOPT=backends
			shift
		;;
		-d|-dependencies|--dependencies)
			EXTRAOPT=dependencies
			shift
		;;
		-f|-formats|--formats)
			EXTRAOPT=formats
			shift
		;;
		-h|-help|--help|"")
			EXTRAOPT=help
			shift
		;;
		--)
			shift
			while (($#)); do
				FILESSTACK=( "${FILESSTACK[@]}" "$1" )
				shift
			done
			break
		;;
		-*)
			BADOPT=true
			break
		;;
		*)
			FILESSTACK=( "${FILESSTACK[@]}" "$1" )
			shift
		;;
	esac
done

echo -e "\n${UNDERLINE}${APPNAME} by JakeSFR'$(head -n 6 "$MYPATH" | grep -Eo "[0-9]+-[0-9]+"); GNU GPL v2 applies${OFFCOL}"

# Bad option
if [ "$BADOPT" = "true" ]; then
	func_die "\n${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Unknown option:') ${1}\n"
fi

# Pick the best tools for the job
for i in 'SEVENZ|7z|7za|7zr|7zz' \
		 'BUNZIP2|bunzip2|bzip2' \
		 'BUNZIP3|bunzip3|bzip3' \
		 'DF|df-FULL|df' \
		 'FDISK|gdisk|fdisk' \
		 'FFMPEG|ffmpeg|avconv' \
		 'GOSTCRYPT|gostcrypt|gostcrypt_cli' \
		 'GPG|gpg|gpg2' \
		 'GUNZIP|gunzip|gzip' \
		 'LRUNZIP|lrunzip|lrzip' \
		 'LUNZIP|lunzip|lzip' \
		 'NULIB|nulib2|nulib' \
		 'RUNZIP|runzip|rzip' \
		 'SQLITE|sqlite3|fossil' \
		 'TRUECRYPT|truecrypt|truecrypt_cli' \
		 'UNARC|unarc|arc' \
		 'UNASD|unasd|asd' \
		 'UNCOMPRESS|uncompress|compress' \
		 'UNECM|unecm|ecm' \
		 'UNFREEZE|unfreeze|freeze' \
		 'UNLZ4|unlz4|lz4|lz4c' \
		 'UNLZMA|unlzma|lzma' \
		 'UNLZOP|unlzop|lzop' \
		 'UNRAR|unrar|rar' \
		 'UNSQUASHFS|unsquashfs4|unsquashfs|unsquashfs3' \
		 'UNXZ|unxz|xz' \
		 'UNZSTD|unzstd|zstd' \
		 'VERACRYPT|veracrypt|veracrypt_cli' \
		 'XXDECODE|xxdecode|xxencode'
do
	read -r CMND IN1 IN2 IN3 IN4 <<< "${i//|/ }"

	eval "$CMND=$IN1"	# fallback to 1st, if nothing found

	# exceptions
	[ "$CMND" = "DF" ] && DF='df'
	[ "$CMND" = "UNSQUASHFS" ] && UNSQUASHFS='unsquashfs'

	for i in $IN1 $IN2 $IN3 $IN4; do
		func_exists $i && eval "$CMND=$i" && break
	done

done

# (un)xz can handle lzma, too
if func_exists $UNLZMA; then
	# BB's (un)lzma can only decompress
	if (LANG=C ${UNLZMA} --help 2>&1 | grep -qwi 'BusyBox' &&
		func_exists "$UNXZ")
	then
		UNLZMA="$UNXZ"
	fi
else
	func_exists "$UNXZ" && UNLZMA="$UNXZ"
fi

# as well as lzip
func_exists $LUNZIP || LUNZIP="$UNXZ"

# veracrypt can handle truecrypt; UPDATE: only up to version 1.25.9!
if ! func_exists "$TRUECRYPT" && 
	 func_exists "$VERACRYPT" &&
	 [ $(printf "%03d%03d%03d%03d%03d" $($VERACRYPT --text --version 2>&1 | tr -cd '[0-9].' | tr '.' ' ')) -le $(printf "%03d%03d%03d%03d%03d" 1 25 9) ]
then
	TRUECRYPT="$VERACRYPT"
fi

# Pick the best losetup
if (LANG=C losetup --help 2>&1 | grep -qwi 'BusyBox' &&
	func_exists losetup-FULL)
then
	LOSETUP='losetup-FULL'
else
	LOSETUP='losetup'
fi

# Check if losetup supports '-r' (read-only) option
if $LOSETUP --help 2>&1 | grep -qwF -- '-r'; then
	LOSETUP_R_OPT='-r'
else
	LOSETUP_R_OPT=''
fi

# For full cpio, use also --no-absolute-filenames opt
if LANG=C cpio --help 2>&1 | grep -qwi 'BusyBox'; then
	CPIO_ABS_OPT=''
else
	CPIO_ABS_OPT="--no-absolute-filenames"
fi

# Check if tar supports --xattrs and --xattrs-include options
if LANG=C tar --help 2>&1 | grep -qwi -- '--xattrs[^-]'; then
	TAR_XATTRS_OPT='--xattrs'
	if LANG=C tar --help 2>&1 | grep -qwi -- '--xattrs-include'; then
		TAR_XATTRS_OPT=''${TAR_XATTRS_OPT}' --xattrs-include=*'
	fi
else
	TAR_XATTRS_OPT=''
fi

# zimdump 1.4 doesn't seem to work with the latest [2018] Wikipedia's ZIM
if zimdump --version >/dev/null 2>&1; then	# no error, assume newer version with new syntax
	ZIMDUMP_OPTS_LIST='list'
	ZIMDUMP_OPTS_EXTR='dump --dir .'
else										# old syntax
	ZIMDUMP_OPTS_LIST='-l'
	ZIMDUMP_OPTS_EXTR='-D .'
fi

# Common options
if [ "$MODE" = "list" ]; then
	AR_OPTS='tv'
	ARJ_OPTS='l -y -v'
	BLKAR_OPTS='show'
	C1541_OPTS='-dir'
	CABEXTRACT_OPTS='-l'
	CPIO_OPTS='-ivt'
	DAR_OPTS='-l'
	DAR_OPTS2=''
	DZIP_OPTS='-e -l'
	EXPLODERPM_OPTS='-l'
	FP8_OPTS='-l'
	FSBEXT_OPTS='-l'
	GIFSICLE_OPTS='-I'
	GRPAR_OPTS='-t -v -f'
	HA_OPTS='l'
	HEXBIN_OPTS='-i'
	HLEXTRACT_OPTS='-l'
	ICNS2PNG_OPTS='-l'
	ICOTOOL_OPTS='-l'
	INNOEXTRACT_OPTS='-c 0 -l -g'
	KICKSTART_OPTS='. --dry-run'
	LBRATE_OPTS='-l'
	LHA_OPTS='-l'
	MPQEXTRACTOR_OPTS='-s'
	NOMARCH_OPTS='-l'
	NULIB_OPTS='tv'
	NULIB2_OPTS='-v'
	NZ_OPTS='l -v'
	POWERISO_OPTS='list'
	SQLITE_OPTS='.ar -tv'
	SEVENZ_OPTS='l -spd'
	STEGHIDE_OPTS='info'
	SWFEXTRACT_OPTS=''
	TAR_OPTS=''${TAR_XATTRS_OPT}' -tvf'
	TNEF_OPTS='-t'
	UNACE_OPTS='l -y'
	UNADF_OPTS='-l'
	UNALZ_OPTS='-l'
	UNARC_OPTS='l'
	UNASD_OPTS='l'
	UNCPK_OPTS='l'
	UNEGG_OPTS='-l'
	UNLZX_OPTS='-v'
	UNP64_OPTS='-i'
	UNRAR_OPTS='l'
	UNSQUASHFS_OPTS='-l'
	UNUC2_OPTS='-l'
	UNZIP_OPTS='-lv'
	UPX_OPTS='-l'
	XAR_OPTS='-t -f'
	XCUR2PNG_OPTS='-n'
	ZIMDUMP_OPTS="$ZIMDUMP_OPTS_LIST"
	ZOO_OPTS='l'
	ZPAQ_OPTS='l'
	ZPAQ_OPTS2=''
	ZZIP_OPTS='l'
else	# extract
	AR_OPTS='xv'
	ARJ_OPTS='x -y -v'
	BLKAR_OPTS='decode'
	C1541_OPTS='-extract'
	CABEXTRACT_OPTS=''
	CPIO_OPTS='-idmv'
	DAR_OPTS='-x'
	DAR_OPTS2='-v'
	DZIP_OPTS='-e -x'
	EXPLODERPM_OPTS='-x'
	FP8_OPTS=''
	FSBEXT_OPTS='-M -d .'
	GIFSICLE_OPTS='--no-background -eVU'
	GRPAR_OPTS='-x -v -C . -f'
	HA_OPTS='e'
	HEXBIN_OPTS='-fl'
	HLEXTRACT_OPTS='-d . -e .'
	ICNS2PNG_OPTS='-x'
	ICOTOOL_OPTS='-x'
	INNOEXTRACT_OPTS='-c 0 -e -g'
	KICKSTART_OPTS=''
	LBRATE_OPTS=''
	LHA_OPTS='-x'
	MPQEXTRACTOR_OPTS='-f -e'
	NOMARCH_OPTS=''
	NULIB_OPTS='x'
	NULIB2_OPTS='-x'
	NZ_OPTS='x -v'
	POWERISO_OPTS='extract'
	SEVENZ_OPTS='x -spd'
	SQLITE_OPTS='.ar -xv'
	STEGHIDE_OPTS='extract -sf'
	SWFEXTRACT_OPTS='--outputformat '"$(gettext 'Item')_%06d.%s"' -a 1-'
	TAR_OPTS=''${TAR_XATTRS_OPT}' -xvf'
	TNEF_OPTS=''
	UNACE_OPTS='x'
	UNADF_OPTS='-r'
	UNALZ_OPTS=''
	UNARC_OPTS='x'
	UNASD_OPTS='x'
	UNCPK_OPTS='xv'
	UNEGG_OPTS='-x'
	UNLZX_OPTS='-x'
	UNP64_OPTS='-v'
	UNRAR_OPTS='-ola x'
	UNSQUASHFS_OPTS='-f -d .'
	UNUC2_OPTS=''
	UNZIP_OPTS=''
	UPX_OPTS='-d'
	XAR_OPTS='-x -v -f'
	XCUR2PNG_OPTS=''
	ZIMDUMP_OPTS="$ZIMDUMP_OPTS_EXTR"
	ZOO_OPTS='x'
	ZPAQ_OPTS='x'
	ZPAQ_OPTS2='-to ./'
	ZZIP_OPTS='x'
fi

# Handle extra options
case "$EXTRAOPT" in
	help)
		printf "\n%s\n"		"$(gettext 'Usage:')"
		printf "  %s\n" 	"${MYNAME} [$(gettext "options")] $(gettext "files")"
		printf "  %s\n"		"${MYNAME} [-n] <$(gettext "extra_option")>"
		printf "\n%s\n"		"$(gettext 'Options:')"
		printf "  %-22s%s\n" "-o, --output <dir>"	"$(gettext 'set output directory (ignored if -l was also specified)')"
		printf "  %-22s%s\n" "-l, --list"			"$(gettext 'list contents instead of extracting')"
		printf "  %-22s%s\n" "-r, --recursive"		"$(gettext 'recurse (sub)directories')"
		printf "  %-22s%s\n" "-s, --skip-symlinks"	"$(gettext 'do not follow symlinks')"
		printf "  %-22s%s\n" "-n, --no-colors"	"$(gettext 'disable ANSI colors in output')"
		printf "\n%s\n"		"$(gettext 'Extra options:')"
		printf "  %-22s%s\n" "-f, --formats"		"$(gettext 'show supported formats/extensions')"
		printf "  %-22s%s\n" "-b, --backends"		"$(gettext "check availability of backends")"
		printf "  %-22s%s\n" "-d, --dependencies"	"$(gettext "check availability of common dependencies")"
		printf "  %-22s%s\n" "-h, --help"			"$(gettext 'this very help')"
		echo -e "${OFFCOL}"
		# Non-root warning
		if [ $(id -u) -ne 0 ]; then
			echo -e "\n${YELLOWCOL}$(gettext 'NOTE'): ${OFFCOL}$(gettext 'Some actions (like mounting ISO/disk images) require root privileges.')\n"
		fi
		exit
	;;
	backends)
		echo -e "\n${YELLOWCOL}$(gettext 'Backends:')"
		for i in $(echo 7z $SEVENZ aescrypt age anim_dump apngdis ar arj asar balz base32 base64 bbb bcrypt blkar brotli bunzip $BUNZIP2 $BUNZIP3 c1541 cabextract ccrypt cpio cryptsetup dar dcmj2pnm dcml2pnm ddjvu dpkg-deb detex-convert dzip enum_chmLib exploderpm extract_chmLib extract-ikconfig $FDISK fsbext gdisk $FFMPEG flzp fossil fp8 fusecompress_offline gifsicle gigdump gigextract $GOSTCRYPT $GPG grpar gs $GUNZIP ha hexbin hlextract icns2png icotool innoextract kickstart.php kgb lbrate lepton lha lizard $LOSETUP lpaq1 lpaq8 $LRUNZIP lz4jsoncat $LUNZIP lzfse maxcso mcrypt mount MPQExtractor msgunfmt nomarch $NULIB nz ostree pak paq8l paq8n paq8o pdfdetach pdfimages pdfinfo pdftocairo pdftotext pgn-extract php picocrypt poweriso qemu-nbd quad ripmime rpm2cpio $RUNZIP salvador scrypt sf2dump sf2extract snzip $SQLITE steghide stripttc swfextract tar tnef $TRUECRYPT tor tosz umount unace unadf unalz $UNARC $UNASD $UNCOMPRESS uncpk undms $UNECM unegg $UNFREEZE $UNLZ4 $UNLZMA $UNLZOP unlzx unmo3 unnaf unp64 unpack200 $UNRAR $UNSQUASHFS unuc2 $UNXZ unzip $UNZSTD upx uudecode $VERACRYPT wadext wvunpack xar xcur2png $XXDECODE zimdump zoo zpaq zxing zzip | tr ' ' '\n' | sort -u); do
			printf "${BLUECOL}%-32s" "> $i"
			if func_exists $i; then
				printf "${GREENCOL}%s${OFFCOL}\n" "[$(gettext 'OK')]"
			else
				printf "${REDCOL}%s${OFFCOL}\n" "[$(gettext 'MISSING')]"
			fi
		done

		echo -e "${OFFCOL}"
		exit
	;;
	dependencies)
		OPTIONAL='gettext|hexdump|stdbuf'
		echo -e "\n${YELLOWCOL}$(gettext 'Dependencies:')"
		for i in $(echo basename bash cat cd chmod cp cut $DF dirname du file find gettext grep head hexdump id ls mkdir modprobe mv od pwd readlink rm sed sleep sort stat stdbuf tail ${TERM_WIDTH_CMD%% *} tr uniq | tr ' ' '\n' | sort -u); do
			echo "$i" | grep -qwE "$OPTIONAL" && OPTNL="$(gettext '(optional)')" || OPTNL=''
			printf "${BLUECOL}%-32s" "> $i $OPTNL"
			if func_exists $i; then
				printf "${GREENCOL}%s${OFFCOL}\n" "[$(gettext 'OK')]"
			else
				printf "${REDCOL}%s${OFFCOL}\n" "[$(gettext 'MISSING')]"
			fi
		done

		echo -e "${OFFCOL}"
		exit
	;;
	formats)
		echo -e "\n${YELLOWCOL}$(gettext 'Supported formats/extensions:')${BLUECOL}"
		grep -woE "^[[:space:]]*\*\..*\)" "$MYPATH" | tr -d '\t \*)' | tr '|' '\n' | sort | uniq
		echo -e "${OFFCOL}"
		exit
	;;
esac

set -- "${FILESSTACK[@]}"

if [ ! "$1" ]; then
	func_die "\n${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Nothing to process, exiting...')\n"
fi

if [ -f "$CUSTOMDIR" ]; then
	func_die "\n${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Cannot create output directory!')\n$(gettext 'A file with the same name already exists:')\n${BLUECOL}> ${CUSTOMDIR}${OFFCOL}\n"
fi

if [ "$MODE" = "list" ]; then
	MSG1="$(gettext "Listing:")"
	MSG2="$(gettext "Listing failed!")"
	MSG3="$(gettext "Listing successful!")"
else
	MSG1="$(gettext "Extracting:")"
	MSG2="$(gettext "Extraction failed!")"
	MSG3="$(gettext "Extraction successful!")"
fi

# -----------------------------------------------------------------------------

SUCCESSCNT=0
FAILCNT=0
SKIPCNT=0

while [ $# -gt 0 ]; do
	DESTDIR=''
	FREELOOP=''
	NBD_DEV=''
	MAPPER_NAME=''

	FILEPATH_ORG="${1%/}"
	FILEPATH="$(readlink -f -- "$FILEPATH_ORG" 2>/dev/null)"
	FILESIZE=$(stat -c %s "$FILEPATH" 2>/dev/null)
	FILEINFO="$(LANG=C file -L -b "$FILEPATH" | cut -f1 -d ',')"
	FILENAME_ORG="$(basename "$FILEPATH_ORG")"
	FILENAME="$(basename "$FILEPATH")"
	FILENAME_NOEXT="${FILENAME%.*}"
	FILENAME_LOWER="$(echo "$FILENAME" | tr '[:upper:]' '[:lower:]')"

	# File summary and custom directory
	if [ -L "$FILEPATH_ORG" ]; then
		FILENAME_SUMMARY="$FILEPATH_ORG -> $(file -h -b -- "$FILEPATH_ORG")"
		if [ "${FILEPATH_ORG//\//}" = "$FILEPATH_ORG" ]; then
			[ "$CUSTOMDIR_FLAG" != "true" ] && CUSTOMDIR="$(dirname "$(pwd)/${FILEPATH_ORG}")"
		else
			[ "$CUSTOMDIR_FLAG" != "true" ] && CUSTOMDIR="$(dirname "$FILEPATH_ORG")"
		fi
	else
		FILENAME_SUMMARY="$FILEPATH_ORG"
		[ "$CUSTOMDIR_FLAG" != "true" ] && CUSTOMDIR="$(dirname "$FILEPATH")"
	fi

	# Set destination directory
	if [ "$MODE" != "list" ]; then
		CNT=1
		DESTDIR="${CUSTOMDIR%*/}/${FILENAME_ORG}.$(gettext 'extracted')"
		TMP="$DESTDIR"
		while [ -e "$TMP" ]; do
			TMP="${DESTDIR}($((CNT++)))"
		done
		DESTDIR="$TMP"
	fi

	# Print header
	echo -e "\n${BLUECOL}$(func_hr '=')${OFFCOL}\n${BLUECOL}${MSG1}${OFFCOL} ${FILEPATH_ORG}\n${BLUECOL}$(func_hr '-')${OFFCOL}"
	if [ -L "$FILEPATH_ORG" ]; then
		echo "$FILENAME_SUMMARY"
	fi
	(file -L -b -- "${FILEPATH:-${FILEPATH_ORG}}" 2>/dev/null; file -L -bi -- "${FILEPATH:-${FILEPATH_ORG}}" 2>/dev/null) | uniq
	echo -e "${BLUECOL}$(func_hr '=')${OFFCOL}\n"

	SKIPMSG=''
	if [ -d "$FILEPATH" ]; then
		if [ "$RECURSIVE" != 'true' ]; then
			SKIPMSG="$(gettext "It is a directory; skipping...")"
		else
			echo -e "${BLUECOL}$(gettext "INFO:")${OFFCOL} $(gettext "It's a directory, adding contents...")"
			shift
			set -- ${FILEPATH%/}/* "$@"
			if [ "${1%*/}" = ''${FILEPATH%*/}'/*' ]; then
				SKIPMSG="$(gettext "Directory is empty, skipping...")"
			else
				continue
			fi
		fi
	elif [ "$SKIP_SYMLINKS" = 'true' ] && [ -L "$FILEPATH_ORG" ]; then
		SKIPMSG="$(gettext "It is a symlink; skipping...")"
	elif [ ! -f "$FILEPATH" ]; then
		SKIPMSG="$(gettext "File does not exist or it's a special file; skipping...")"
	elif [ ${FILESIZE} -eq 0 ]; then
		SKIPMSG="$(gettext "Zero-byte file; skipping...")"
	fi

	if [ "$SKIPMSG" ]; then
		echo -e "${YELLOWCOL}$(gettext 'WARNING:') ${OFFCOL}${SKIPMSG}"
		((SKIPCNT++))
		SKIPFILES[$SKIPCNT]="$FILENAME_SUMMARY"
		shift
		continue
	fi

	if [ -d "$WORKDIR" ]; then
		func_die "\n${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'The temporary directory already exists! Aborting...')\n"
	fi

	if ! mkdir -p "$WORKDIR"; then
		func_die "\n${REDCOL}$(gettext 'ERROR:') ${OFFCOL}$(gettext 'Cannot create temporary directory! Aborting...')\n"
	fi

	#############
	func_uextract
	#############

	RETCODE=$?

	# Check for free space on destination device and issue a warning if it's 0
	if [ $RETCODE -ne 0 ] && [ "$MODE" != "list" ] && [ "$($DF . | tail -n1 | tr -s ' ' | cut -f4 -d' ')" = "0" ]; then
		echo -e "\n${YELLOWCOL}$(gettext 'WARNING:') ${OFFCOL}$(gettext "the destination device is out of space!")"
	fi

	func_cleanup

	echo -e "\n${BLUECOL}$(func_hr '-')${OFFCOL}"

	if [ $RETCODE -ne 0 ]; then
		if [ "$MODE" != "list" ] && [ "$DESTDIR" ]; then
			find "$(readlink -f "$DESTDIR" 2>/dev/null)" -maxdepth 0 -type d -empty -delete 2>/dev/null
		fi
		echo -e "${REDCOL}${MSG2}${OFFCOL}"
		FAILFILES[FAILCNT++]="$FILENAME_SUMMARY"
	else
		echo -e "${GREENCOL}${MSG3}${OFFCOL}"
		SUCCESSFILES[SUCCESSCNT++]="$FILENAME_SUMMARY"
	fi

	shift

done

# -----------------------------------------------------------------------------

echo -e "\n${BLUECOL}$(func_hr '*')${OFFCOL}"

if [ $SUCCESSCNT -ne 0 ]; then
	echo -e "\n${GREENCOL}$(gettext 'Successful files'):${OFFCOL}"
	printf "${GREENCOL}>${OFFCOL} %s\n" "${SUCCESSFILES[@]}"
	TMPCOL="${GREENCOL}"
fi

if [ $SKIPCNT -ne 0 ]; then
	echo -e "\n${YELLOWCOL}$(gettext 'Skipped files'):${OFFCOL}"
	printf "${YELLOWCOL}>${OFFCOL} %s\n" "${SKIPFILES[@]}"
	TMPCOL="${YELLOWCOL}"
fi

if [ $FAILCNT -ne 0 ]; then
	echo -e "\n${REDCOL}$(gettext 'Failed files'):${OFFCOL}"
	printf "${REDCOL}>${OFFCOL} %s\n" "${FAILFILES[@]}"
	TMPCOL="${REDCOL}"
fi

echo -e "
${BLUECOL}$(func_hr '*')${OFFCOL}\n
${BLUECOL}$((SUCCESSCNT+FAILCNT+SKIPCNT))${OFFCOL} \
$(gettext 'file(s) processed'): \
${GREENCOL}${SUCCESSCNT}${OFFCOL} $(gettext 'successfully'), \
${YELLOWCOL}${SKIPCNT}${OFFCOL} $(gettext 'skipped'), \
${REDCOL}${FAILCNT}${OFFCOL} $(gettext 'failed').\n
${TMPCOL}$(gettext 'Finished!')${OFFCOL}
"

[ $FAILCNT -ne 0 ] && exit 2
[ $SKIPCNT -ne 0 ] && exit 1
exit 0

################################ (_)3><+.-@<+ #################################
