Intermediate
Last verified: February 2026

Kali NetHunter Installation Guide

Transform your Samsung Galaxy S10 (or other supported Android device) into a portable penetration testing platform with Kali NetHunter. This guide walks you through every step—from unlocking to first boot.

Before You Begin

  • This will wipe your device. Back up everything first.
  • You need a Linux machine (Kali preferred) for flashing.
  • Ensure your device has at least 60% battery.
  • Unlocking the bootloader may void your warranty and trip Knox.

Always Verify Latest Versions

Firmware, recoveries, and tools update frequently. Before flashing, confirm you have the latest compatible versions:

1. Prerequisites & Downloads

Supported Devices

NetHunter has three editions. Choose based on your device:

NetHunter (Full)

Requires a device with an officially supported kernel. Full HID attacks, BadUSB, WiFi injection.

Examples: OnePlus, Nexus, some Samsung models

NetHunter Rootless

No root required. Runs in Termux. Limited hardware access.

Works on almost any Android 5+

NetHunter Lite

Rooted device, no custom kernel. App + chroot, no HID attacks.

Works on most rooted devices

This Guide Covers Full NetHunter

We'll install the full NetHunter on a Samsung Galaxy S10 (Exynos: SM-G973F). The S10 has official kernel support for WiFi injection and HID attacks.

LineageOS Required for Samsung Devices

As of 2025.4, all Kali NetHunter images for Samsung Galaxy S10/S10+/S10e/S10 5G require LineageOS — not stock Samsung firmware. You must flash LineageOS on your device before installing NetHunter. The current build uses LineageOS 16 (Android 15). See LineageOS for beyond1lte for installation instructions.

What You'll Need

Hardware

  • ☑️ Samsung Galaxy S10 (SM-G973F/Exynos) — or your supported device
  • ☑️ USB-C cable (data-capable, not charge-only)
  • ☑️ Linux PC/laptop (Kali Linux recommended)
  • ☑️ microSD card (optional, for storing images/wordlists)
  • ☑️ USB OTG adapter (for external WiFi adapters later)

Software Downloads

Download all files to a folder on your Kali machine (e.g., ~/nethunter-install/).

File Source Notes
Odin (Linux: Heimdall) GitHub Flash tool for Samsung
TWRP Recovery twrp.me Search for your exact model
LineageOS ROM LineageOS Wiki Required — flash before NetHunter (match your codename)
NetHunter ZIP kali.org Pick your device codename (e.g., beyond1lte)
Magisk APK/ZIP GitHub For root access
no-verity-opt-encrypt GitHub Disables DM-verity (stock Samsung only — not needed for LineageOS)
Auto-Download Script for Samsung Galaxy Devices

Run this script on your Kali machine to automatically download all prerequisites. Supports multiple Samsung models — enter your model number or press Enter to use S10 defaults.

Note: If you see "apt update" errors about mirror sync or 404s, this is a temporary Kali mirror issue. The script will continue anyway. Run sudo apt update --fix-missing later if needed.

bash
#!/bin/bash
# ============================================================
# NetHunter Samsung Auto-Installer  (v3 — Feb 2026)
# Supports: S10, S10+, S10e, S10 5G, S7, S9, Note 10 series
# Run on: Kali Linux (with phone connected via USB)
# Downloads to: the directory you run this script from
#
# IMPORTANT: Current Kali NetHunter Samsung S10/S20/Note images
# require LineageOS — NOT stock Samsung firmware.
#
# Features:
#   • Auto-detects device via ADB (no manual model entry needed)
#   • Skips files that are already downloaded and valid
#   • Downloads LineageOS ROM automatically when required
#   • Verifies SHA256 checksums for NetHunter images
#   • Pushes files to device via ADB (if in TWRP)
#   • Flashes TWRP via Heimdall (if in Download Mode)
# ============================================================

set +e  # Don't exit on first error

# Colors
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'

# ============================================================
# Failure tracking — collected and reported at the end
# ============================================================
FAILED_DOWNLOADS=()   # "LABEL|FILENAME|URL|MANUAL_URL|MIN_SIZE|HINT"
PASSED_DOWNLOADS=()   # "LABEL|FILENAME|SIZE_HUMAN"
SKIPPED_DOWNLOADS=()  # "LABEL|REASON"

# Minimum expected file sizes (bytes) for validation
MIN_TWRP=1048576        # 1 MB   — real TWRP images are ~30-50 MB
MIN_MAGISK=1048576      # 1 MB   — real Magisk APK is ~12 MB
MIN_NETHUNTER=10485760  # 10 MB  — real NetHunter ZIPs are ~2.4 GB
MIN_NOVERITY=5120       # 5 KB   — real zip is ~10-20 KB
MIN_LINEAGEOS=209715200 # 200 MB — real LOS ZIPs are ~700 MB+

# ============================================================
# Helper: validate a file by minimum size
# ============================================================
validate_download() {
    local label="$1" file="$2" min_size="$3" url="$4" manual_url="$5" hint="${6:-}"
    if [ ! -f "$file" ]; then
        echo -e "${RED}    ✗ $file not found — download failed.${NC}"
        FAILED_DOWNLOADS+=("$label|$file|$url|$manual_url|$min_size|$hint")
        return 1
    fi
    local actual_size
    actual_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
    local human_size
    human_size=$(ls -lh "$file" | awk '{print $5}')
    if [ "$actual_size" -lt "$min_size" ]; then
        echo -e "${RED}    ✗ $file is only $human_size — expected at least $(numfmt --to=iec $min_size 2>/dev/null || echo "$min_size bytes").${NC}"
        echo -e "${RED}      File is likely an error page or empty. Removing it.${NC}"
        rm -f "$file"
        FAILED_DOWNLOADS+=("$label|$file|$url|$manual_url|$min_size|$hint")
        return 1
    fi
    echo -e "${GREEN}    ✓ $file$human_size — OK${NC}"
    PASSED_DOWNLOADS+=("$label|$file|$human_size")
    return 0
}

# ============================================================
# Helper: check if a valid file already exists → skip download
# ============================================================
already_have() {
    local label="$1" file="$2" min_size="$3"
    if [ -f "$file" ]; then
        local actual_size
        actual_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
        if [ "$actual_size" -ge "$min_size" ]; then
            local human_size
            human_size=$(ls -lh "$file" | awk '{print $5}')
            echo -e "${GREEN}    ✓ Already have $file ($human_size) — skipping download.${NC}"
            PASSED_DOWNLOADS+=("$label|$file|$human_size (cached)")
            return 0
        fi
    fi
    return 1
}

echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║  NetHunter Samsung Auto-Installer  (v3)                   ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"

# ============================================================
# Working directory = wherever the script is run from
# ============================================================
INSTALL_DIR="$(pwd)"
echo -e "\n${GREEN}[+] Working directory: $INSTALL_DIR${NC}"

# ============================================================
# Device Selection — auto-detect via ADB, fallback to manual
# ============================================================
echo -e "\n${CYAN}[Device Configuration]${NC}"

ADB_DETECTED=""
if command -v adb &>/dev/null; then
    # Start ADB server quietly
    adb start-server 2>/dev/null
    # Try to read model from connected device
    ADB_MODEL=$(adb shell getprop ro.product.model 2>/dev/null | tr -d '\r\n')
    if [ -n "$ADB_MODEL" ]; then
        ADB_DETECTED="$ADB_MODEL"
        echo -e "${GREEN}    📱 Detected device via ADB: $ADB_MODEL${NC}"
        # Try to get the full model number (SM-XXXXX)
        ADB_SKU=$(adb shell getprop ro.product.name 2>/dev/null | tr -d '\r\n')
        ADB_DEVICE=$(adb shell getprop ro.product.device 2>/dev/null | tr -d '\r\n')
        ADB_MODELNUM=$(adb shell getprop ro.boot.em.model 2>/dev/null | tr -d '\r\n')
        # Samsung stores full model in sku or modelnum
        [ -z "$ADB_MODELNUM" ] && ADB_MODELNUM=$(adb shell getprop ro.product.vendor.model 2>/dev/null | tr -d '\r\n')
        if [ -n "$ADB_MODELNUM" ]; then
            echo -e "${GREEN}    Model number: $ADB_MODELNUM${NC}"
            echo -e "\nUse detected model ${GREEN}$ADB_MODELNUM${NC}? [Y/n] "
            read -r USE_DETECTED
            if [[ ! "$USE_DETECTED" =~ ^[Nn] ]]; then
                MODEL_INPUT="$ADB_MODELNUM"
            fi
        else
            echo -e "${YELLOW}    Could not read model number. Device code: $ADB_DEVICE${NC}"
        fi
    else
        echo -e "${YELLOW}    No device detected via ADB (phone not connected or USB debugging off).${NC}"
    fi
fi

if [ -z "$MODEL_INPUT" ]; then
    echo -e "Enter your Samsung model number (e.g., SM-G973F, SM-G975F, SM-N975F)"
    echo -e "Or press Enter to use default: ${GREEN}SM-G973F (Galaxy S10 Exynos)${NC}"
    echo ""
    read -p "Model number [SM-G973F]: " MODEL_INPUT
fi

MODEL=${MODEL_INPUT:-SM-G973F}
MODEL_UPPER=$(echo "$MODEL" | tr '[:lower:]' '[:upper:]')

echo -e "\n${GREEN}[+] Selected model: $MODEL_UPPER${NC}"

# Model → codename mapping
# Format: CODENAME|FRIENDLY_NAME|TWRP_DEVICE
declare -A MODEL_MAP=(
    # Galaxy S10 series (Exynos)
    ["SM-G970F"]="beyond0lte|Galaxy S10e|samsunggalaxys10e"
    ["SM-G973F"]="beyond1lte|Galaxy S10|samsunggalaxys10"
    ["SM-G975F"]="beyond2lte|Galaxy S10+|samsunggalaxys10plus"
    ["SM-G977B"]="beyondx|Galaxy S10 5G|samsunggalaxys105g"
    # Galaxy S10 series (Snapdragon — bootloader often locked!)
    ["SM-G970U"]="beyond0lte|Galaxy S10e (SD)|samsunggalaxys10e"
    ["SM-G973U"]="beyond1lte|Galaxy S10 (SD)|samsunggalaxys10"
    ["SM-G975U"]="beyond2lte|Galaxy S10+ (SD)|samsunggalaxys10plus"
    # Galaxy S7 series (Samsung UI — OUI)
    ["SM-G935F"]="hero2lte|Galaxy S7 Edge|samsunggalaxys7"
    ["SM-G930F"]="herolte|Galaxy S7|samsunggalaxys7"
    # Galaxy S9 series
    ["SM-G960F"]="starlte|Galaxy S9|samsunggalaxys9"
    ["SM-G965F"]="star2lte|Galaxy S9+|samsunggalaxys9plus"
    # Galaxy Note series
    ["SM-N970F"]="d1|Galaxy Note 10|samsunggalaxynote10"
    ["SM-N975F"]="d2s|Galaxy Note 10+|samsunggalaxynote10plus"
)

# Parse model info
if [[ -v MODEL_MAP[$MODEL_UPPER] ]]; then
    IFS='|' read -r CODENAME DEVICE_FRIENDLY TWRP_DEVICE <<< "${MODEL_MAP[$MODEL_UPPER]}"
    echo -e "${GREEN}    Device:   $DEVICE_FRIENDLY${NC}"
    echo -e "${GREEN}    Codename: $CODENAME${NC}"
    echo -e "${GREEN}    TWRP key: $TWRP_DEVICE${NC}"
else
    echo -e "${YELLOW}[!] Model not in database. Using S10 defaults.${NC}"
    echo -e "${YELLOW}    You may need to manually find correct TWRP/NetHunter files.${NC}"
    CODENAME="beyond1lte"
    DEVICE_FRIENDLY="Galaxy S10"
    TWRP_DEVICE="samsunggalaxys10"
fi

# Detect chipset
if [[ "$MODEL_UPPER" =~ U[0-9]*$ ]]; then
    CHIPSET="snapdragon"
    echo -e "${YELLOW}    Chipset: Snapdragon (US/China variant)${NC}"
    echo -e "${RED}    ⚠ Warning: Many Snapdragon Samsung devices have locked bootloaders!${NC}"
else
    CHIPSET="exynos"
    echo -e "${GREEN}    Chipset: Exynos (International variant)${NC}"
fi

# Save device config
cat > "$INSTALL_DIR/device-config.sh" << EOF
# Device configuration — source this file in other scripts
export MODEL="$MODEL_UPPER"
export CODENAME="$CODENAME"
export DEVICE_FRIENDLY="$DEVICE_FRIENDLY"
export TWRP_DEVICE="$TWRP_DEVICE"
export CHIPSET="$CHIPSET"
EOF
echo -e "${GREEN}[+] Saved device config to: $INSTALL_DIR/device-config.sh${NC}"

# ============================================================
# 1. Install Heimdall & tools
# ============================================================
echo -e "\n${CYAN}[1/7] Installing Heimdall & tools...${NC}"

# Check which tools are already installed
MISSING_PKGS=()
for cmd in heimdall adb fastboot curl wget; do
    if ! command -v "$cmd" &>/dev/null; then
        MISSING_PKGS+=("$cmd")
    fi
done

if [ ${#MISSING_PKGS[@]} -eq 0 ]; then
    echo -e "${GREEN}    All tools already installed — skipping.${NC}"
else
    echo -e "    Missing: ${MISSING_PKGS[*]}. Installing..."
    sudo apt update --allow-releaseinfo-change 2>/dev/null || {
        echo -e "${YELLOW}[!] apt update had some errors (mirror sync issues are common).${NC}"
        echo -e "${YELLOW}    Continuing anyway — packages may still install fine.${NC}"
    }
    sudo apt install -y heimdall-flash heimdall-flash-frontend adb fastboot curl wget || {
        echo -e "${RED}[!] Package installation failed. Try:${NC}"
        echo -e "    sudo apt update --fix-missing"
        echo -e "    sudo apt install -y heimdall-flash adb fastboot curl wget"
        echo -e "${YELLOW}    Continuing with script...${NC}"
    }
fi

# ============================================================
# 2. Download TWRP Recovery
# ============================================================
echo -e "\n${CYAN}[2/7] TWRP Recovery for $CODENAME...${NC}"
TWRP_DL_URL="https://dl.twrp.me/$CODENAME/"
TWRP_MANUAL_URL="https://twrp.me/samsung/$TWRP_DEVICE.html"

if already_have "TWRP Recovery" "twrp.img" "$MIN_TWRP"; then
    : # already_have prints confirmation and adds to PASSED
else
    echo -e "    Checking twrp.me for latest $CODENAME image..."
    TWRP_PAGE=$(curl -sL "$TWRP_DL_URL")
    TWRP_FILE=$(echo "$TWRP_PAGE" | grep -oP 'twrp-[0-9.]+_[0-9]+-[0-9]+-'"$CODENAME"'\.img' | head -1)

    if [ -n "$TWRP_FILE" ]; then
        TWRP_FULL_URL="https://dl.twrp.me/$CODENAME/$TWRP_FILE"
        echo -e "${GREEN}    Found: $TWRP_FILE${NC}"
    else
        TWRP_FILE="twrp-3.7.0_12-0-$CODENAME.img"
        TWRP_FULL_URL="https://dl.twrp.me/$CODENAME/$TWRP_FILE"
        echo -e "${YELLOW}    Using fallback: $TWRP_FILE${NC}"
    fi

    echo -e "    Downloading twrp.img ..."
    wget --content-disposition --header="Referer: $TWRP_DL_URL" \
         -O twrp.img "$TWRP_FULL_URL" 2>&1 || true
    validate_download "TWRP Recovery" "twrp.img" "$MIN_TWRP" \
        "$TWRP_FULL_URL" "$TWRP_MANUAL_URL" \
        "twrp.me blocks direct wget. Open the Manual URL in a browser and download manually."
fi

# ============================================================
# 3. Download Magisk (latest stable)
# ============================================================
echo -e "\n${CYAN}[3/7] Magisk...${NC}"
MAGISK_MANUAL_URL="https://github.com/topjohnwu/Magisk/releases"

# Check for any existing Magisk APK
EXISTING_MAGISK=$(ls -1 Magisk-*.apk 2>/dev/null | head -1)
if [ -n "$EXISTING_MAGISK" ] && already_have "Magisk" "$EXISTING_MAGISK" "$MIN_MAGISK"; then
    MAGISK_FILE="$EXISTING_MAGISK"
else
    MAGISK_URL=$(curl -sL https://api.github.com/repos/topjohnwu/Magisk/releases/latest \
        | grep "browser_download_url.*Magisk-.*\.apk" | head -1 | cut -d '"' -f 4)

    if [ -n "$MAGISK_URL" ]; then
        MAGISK_FILE=$(basename "$MAGISK_URL")
        echo -e "    Downloading $MAGISK_FILE ..."
        wget -O "$MAGISK_FILE" "$MAGISK_URL" 2>&1 || true
        validate_download "Magisk" "$MAGISK_FILE" "$MIN_MAGISK" \
            "$MAGISK_URL" "$MAGISK_MANUAL_URL" \
            "Download the latest Magisk-vXX.X.apk from the GitHub releases page."
    else
        echo -e "${RED}    ✗ Could not resolve Magisk URL from GitHub API.${NC}"
        MAGISK_FILE="Magisk-latest.apk"
        FAILED_DOWNLOADS+=("Magisk|$MAGISK_FILE|<GitHub API failed>|$MAGISK_MANUAL_URL|$MIN_MAGISK|Download the latest Magisk-vXX.X.apk from GitHub releases.")
    fi
fi

# ============================================================
# 4. Download NetHunter (auto-detect from Kali mirror)
# ============================================================
echo -e "\n${CYAN}[4/7] NetHunter for $CODENAME...${NC}"
NH_MANUAL_URL="https://www.kali.org/get-kali/#kali-mobile"
NH_BASE="https://kali.download/nethunter-images/current/"

# Check for any existing NetHunter ZIP matching our codename
EXISTING_NH=$(ls -1 kali-nethunter-*"$CODENAME"*-full.zip 2>/dev/null | head -1)
if [ -n "$EXISTING_NH" ] && already_have "NetHunter" "$EXISTING_NH" "$MIN_NETHUNTER"; then
    NH_FILE="$EXISTING_NH"
else
    echo -e "    Fetching available images from Kali mirror..."
    NH_DIR_LISTING=$(curl -sL "$NH_BASE")

    echo -e "    Searching for codename: ${BOLD}$CODENAME${NC}"
    NH_MATCHES=$(echo "$NH_DIR_LISTING" | grep -oP 'kali-nethunter-[0-9.]+-'"$CODENAME"'-[^"]+?-full\.zip' | sort -u)

    if [ -n "$NH_MATCHES" ]; then
        NH_COUNT=$(echo "$NH_MATCHES" | wc -l)
        if [ "$NH_COUNT" -eq 1 ]; then
            NH_FILE="$NH_MATCHES"
            echo -e "${GREEN}    Found: $NH_FILE${NC}"
        else
            echo -e "${GREEN}    Found $NH_COUNT images for $CODENAME:${NC}"
            i=1
            while IFS= read -r f; do
                echo -e "      $i) $f"
                i=$((i + 1))
            done <<< "$NH_MATCHES"
            read -p "    Select image [1]: " NH_PICK
            NH_PICK=${NH_PICK:-1}
            NH_FILE=$(echo "$NH_MATCHES" | sed -n "${NH_PICK}p")
            [ -z "$NH_FILE" ] && NH_FILE=$(echo "$NH_MATCHES" | head -1)
            echo -e "${GREEN}    Selected: $NH_FILE${NC}"
        fi
        NH_URL="${NH_BASE}${NH_FILE}"
    else
        echo -e "${YELLOW}    No image found for codename '$CODENAME'.${NC}"
        echo -e "${YELLOW}    Searching for any 'beyond' (S10 family) image...${NC}"
        NH_FALLBACK=$(echo "$NH_DIR_LISTING" | grep -oP 'kali-nethunter-[0-9.]+-beyond1lte-[^"]+?-full\.zip' | head -1)
        if [ -n "$NH_FALLBACK" ]; then
            NH_FILE="$NH_FALLBACK"
            NH_URL="${NH_BASE}${NH_FILE}"
            echo -e "${GREEN}    Found fallback: $NH_FILE${NC}"
        else
            NH_FILE="kali-nethunter-$CODENAME-full.zip"
            NH_URL=""
            echo -e "${RED}    ✗ No matching image found on the Kali mirror.${NC}"
        fi
    fi

    # Detect LineageOS requirement
    if [[ "$NH_FILE" == *"-los-"* ]]; then
        LOS_VERSION=$(echo "$NH_FILE" | grep -oP '(?<=-los-)[a-z]+')
        echo -e ""
        echo -e "${YELLOW}    ╔══════════════════════════════════════════════════════╗${NC}"
        echo -e "${YELLOW}    ║  ⚠  LINEAGEOS REQUIRED                               ║${NC}"
        echo -e "${YELLOW}    ╚══════════════════════════════════════════════════════╝${NC}"
        echo -e "${YELLOW}    This NetHunter image requires LineageOS (not stock Samsung).${NC}"
        echo -e "${YELLOW}    LineageOS version in this build: $LOS_VERSION${NC}"
        echo -e "${YELLOW}    LineageOS will be downloaded automatically in step 5.${NC}"
        echo ""
        echo "export REQUIRES_LINEAGEOS=\"true\"" >> "$INSTALL_DIR/device-config.sh"
        echo "export LOS_VERSION=\"$LOS_VERSION\"" >> "$INSTALL_DIR/device-config.sh"
    fi

    if [ -n "$NH_URL" ]; then
        echo -e "    Downloading $NH_FILE (~2.4 GB — be patient)..."
        wget -O "$NH_FILE" "$NH_URL" 2>&1 || true
        validate_download "NetHunter" "$NH_FILE" "$MIN_NETHUNTER" \
            "$NH_URL" "$NH_MANUAL_URL" \
            "Go to kali.org/get-kali → Mobile → Samsung section. Look for $DEVICE_FRIENDLY."
    else
        FAILED_DOWNLOADS+=("NetHunter|$NH_FILE|<no URL found>|$NH_MANUAL_URL|$MIN_NETHUNTER|Go to kali.org/get-kali → Mobile → Samsung and download the image for $DEVICE_FRIENDLY.")
    fi
fi

# ============================================================
# 4b. Verify SHA256 checksum for NetHunter
# ============================================================
if [ -f "$NH_FILE" ]; then
    echo -e "\n${CYAN}    Verifying SHA256 checksum for $NH_FILE...${NC}"
    SHA_URL="${NH_BASE}SHA256SUMS"
    SHA_SUMS=$(curl -sL "$SHA_URL")

    if [ -n "$SHA_SUMS" ]; then
        EXPECTED_SHA=$(echo "$SHA_SUMS" | grep "$NH_FILE" | awk '{print $1}')
        if [ -n "$EXPECTED_SHA" ]; then
            ACTUAL_SHA=$(sha256sum "$NH_FILE" | awk '{print $1}')
            if [ "$EXPECTED_SHA" = "$ACTUAL_SHA" ]; then
                echo -e "${GREEN}    ✓ SHA256 checksum verified — file is authentic.${NC}"
            else
                echo -e "${RED}    ✗ SHA256 MISMATCH!${NC}"
                echo -e "${RED}      Expected: $EXPECTED_SHA${NC}"
                echo -e "${RED}      Got:      $ACTUAL_SHA${NC}"
                echo -e "${RED}      The file may be corrupted. Consider re-downloading.${NC}"
            fi
        else
            echo -e "${YELLOW}    No checksum found for $NH_FILE in SHA256SUMS.${NC}"
        fi
    else
        echo -e "${YELLOW}    Could not fetch SHA256SUMS from $SHA_URL${NC}"
    fi
fi

# ============================================================
# 5. Download LineageOS (if required)
# ============================================================
REQUIRES_LOS=false
[[ "$NH_FILE" == *"-los-"* ]] && REQUIRES_LOS=true

if [ "$REQUIRES_LOS" = true ]; then
    echo -e "\n${CYAN}[5/7] LineageOS ROM for $CODENAME...${NC}"
    LOS_MANUAL_URL="https://wiki.lineageos.org/devices/$CODENAME"

    # Check for existing LineageOS ZIP
    EXISTING_LOS=$(ls -1 lineage-*"$CODENAME"*.zip 2>/dev/null | head -1)
    if [ -n "$EXISTING_LOS" ] && already_have "LineageOS" "$EXISTING_LOS" "$MIN_LINEAGEOS"; then
        LOS_FILE="$EXISTING_LOS"
    else
        # LineageOS provides an API to find the latest build
        LOS_API="https://download.lineageos.org/api/v2/devices/$CODENAME"
        echo -e "    Querying LineageOS API for $CODENAME..."
        LOS_JSON=$(curl -sL "$LOS_API")

        # Try the download API for builds
        LOS_BUILDS_API="https://download.lineageos.org/api/v2/devices/$CODENAME/builds"
        LOS_BUILDS=$(curl -sL "$LOS_BUILDS_API")

        # Extract the latest build download URL
        LOS_DL_URL=$(echo "$LOS_BUILDS" | grep -oP '"url"\s*:\s*"\K[^"]+\.zip' | head -1)

        if [ -n "$LOS_DL_URL" ]; then
            LOS_FILE=$(basename "$LOS_DL_URL")
            echo -e "${GREEN}    Found: $LOS_FILE${NC}"
            echo -e "    Downloading LineageOS (~700 MB)..."
            wget -O "$LOS_FILE" "$LOS_DL_URL" 2>&1 || true
            validate_download "LineageOS" "$LOS_FILE" "$MIN_LINEAGEOS" \
                "$LOS_DL_URL" "$LOS_MANUAL_URL" \
                "Visit $LOS_MANUAL_URL and download the latest build."
        else
            # Fallback: try scraping the device page
            echo -e "${YELLOW}    API did not return a build. Checking download page...${NC}"
            LOS_PAGE=$(curl -sL "https://download.lineageos.org/$CODENAME")
            LOS_DL_URL=$(echo "$LOS_PAGE" | grep -oP 'https://[^"]+lineage-[0-9.]+-[0-9]+-nightly-'"$CODENAME"'-signed\.zip' | head -1)
            if [ -n "$LOS_DL_URL" ]; then
                LOS_FILE=$(basename "$LOS_DL_URL")
                echo -e "${GREEN}    Found: $LOS_FILE${NC}"
                echo -e "    Downloading LineageOS (~700 MB)..."
                wget -O "$LOS_FILE" "$LOS_DL_URL" 2>&1 || true
                validate_download "LineageOS" "$LOS_FILE" "$MIN_LINEAGEOS" \
                    "$LOS_DL_URL" "$LOS_MANUAL_URL" \
                    "Visit $LOS_MANUAL_URL and download the latest build."
            else
                LOS_FILE="lineage-$CODENAME.zip"
                echo -e "${RED}    ✗ Could not find LineageOS download for $CODENAME.${NC}"
                FAILED_DOWNLOADS+=("LineageOS|$LOS_FILE|<auto-detect failed>|$LOS_MANUAL_URL|$MIN_LINEAGEOS|Visit $LOS_MANUAL_URL, download the latest nightly ZIP, and save it here.")
            fi
        fi
    fi
else
    echo -e "\n${CYAN}[5/7] LineageOS — not required for this build.${NC}"
    SKIPPED_DOWNLOADS+=("LineageOS|Not required — NetHunter image uses stock firmware")
fi

# ============================================================
# 6. Download no-verity-opt-encrypt
# ============================================================
echo -e "\n${CYAN}[6/7] no-verity-opt-encrypt...${NC}"
NOVERITY_FILE="no-verity-opt-encrypt.zip"
NOVERITY_MANUAL_URL="https://github.com/AMP-1/no-verity-opt-encrypt"
NOVERITY_DOWNLOADED=false

if [ "$REQUIRES_LOS" = true ]; then
    echo -e "${YELLOW}    LineageOS build detected — no-verity is NOT needed.${NC}"
    echo -e "${YELLOW}    Skipping (only required for stock Samsung firmware).${NC}"
    SKIPPED_DOWNLOADS+=("no-verity-opt-encrypt|Not needed for LineageOS builds")
elif already_have "no-verity-opt-encrypt" "$NOVERITY_FILE" "$MIN_NOVERITY"; then
    NOVERITY_DOWNLOADED=true
else
    NOVERITY_URLS=(
        "https://artifacts.kali.org/images-nethunter/nethunter-installer/no-verity-opt-encrypt-6.1.zip"
        "https://github.com/AMP-1/no-verity-opt-encrypt/releases/download/noencrypt/no-verity-opt-encrypt-6.1.zip"
    )
    for NOVERITY_URL in "${NOVERITY_URLS[@]}"; do
        echo -e "    Trying: $NOVERITY_URL"
        wget -O "$NOVERITY_FILE" "$NOVERITY_URL" 2>&1 || true
        if validate_download "no-verity-opt-encrypt" "$NOVERITY_FILE" "$MIN_NOVERITY" \
            "$NOVERITY_URL" "$NOVERITY_MANUAL_URL" \
            "Search GitHub for 'no-verity-opt-encrypt' and download the latest ZIP."; then
            NOVERITY_DOWNLOADED=true
            break
        fi
    done
fi

# ============================================================
# 7. Device actions — push files / flash TWRP
# ============================================================
echo -e "\n${CYAN}[7/7] Device actions...${NC}"

# Detect device state via ADB / Heimdall
DEVICE_STATE="none"
if command -v adb &>/dev/null; then
    ADB_STATE=$(adb get-state 2>/dev/null | tr -d '\r\n')
    case "$ADB_STATE" in
        device)     DEVICE_STATE="adb-normal" ;;
        recovery)   DEVICE_STATE="adb-recovery" ;;
        sideload)   DEVICE_STATE="adb-sideload" ;;
    esac
fi

if [ "$DEVICE_STATE" = "none" ] && command -v heimdall &>/dev/null; then
    if heimdall detect &>/dev/null; then
        DEVICE_STATE="download-mode"
    fi
fi

case "$DEVICE_STATE" in

    adb-recovery)
        # Phone is in TWRP recovery — push all files automatically
        echo -e "${GREEN}    📱 Device detected in TWRP recovery mode!${NC}"
        echo -e "    Pushing files to /sdcard/NetHunter/ ..."
        adb shell mkdir -p /sdcard/NetHunter 2>/dev/null

        PUSH_FILES=()
        # LineageOS first (must be flashed first)
        if [ "$REQUIRES_LOS" = true ] && [ -f "${LOS_FILE:-}" ]; then
            PUSH_FILES+=("$LOS_FILE")
        fi
        # Magisk
        [ -f "${MAGISK_FILE:-}" ] && PUSH_FILES+=("$MAGISK_FILE")
        # NetHunter
        [ -f "${NH_FILE:-}" ] && PUSH_FILES+=("$NH_FILE")
        # no-verity (only for stock)
        if [ "$REQUIRES_LOS" = false ] && [ -f "$NOVERITY_FILE" ]; then
            PUSH_FILES+=("$NOVERITY_FILE")
        fi

        if [ ${#PUSH_FILES[@]} -gt 0 ]; then
            for pf in "${PUSH_FILES[@]}"; do
                echo -e "    Pushing $pf ..."
                adb push "$pf" /sdcard/NetHunter/ 2>&1
                if [ $? -eq 0 ]; then
                    echo -e "${GREEN}    ✓ $pf pushed${NC}"
                else
                    echo -e "${RED}    ✗ Failed to push $pf${NC}"
                fi
            done
            echo -e "\n${GREEN}    Files on device:${NC}"
            adb shell ls -la /sdcard/NetHunter/
        else
            echo -e "${YELLOW}    No valid files to push.${NC}"
        fi

        echo -e "\n${GREEN}    ✓ Files pushed! In TWRP:${NC}"
        if [ "$REQUIRES_LOS" = true ]; then
            echo -e "       1. Install → select LineageOS ZIP → Swipe to flash"
            echo -e "       2. Install → select Magisk APK → Swipe to flash"
            echo -e "       3. Install → select NetHunter ZIP → Swipe to flash"
            echo -e "       4. Wipe → Advanced Wipe → Dalvik/ART Cache → Swipe"
            echo -e "       5. Reboot → System"
        else
            echo -e "       1. Install → select Magisk APK → Swipe to flash"
            echo -e "       2. Install → select no-verity ZIP → Swipe to flash"
            echo -e "       3. Install → select NetHunter ZIP → Swipe to flash"
            echo -e "       4. Wipe → Advanced Wipe → Dalvik/ART Cache → Swipe"
            echo -e "       5. Reboot → System"
        fi
        ;;

    download-mode)
        # Phone is in Download Mode — offer to flash TWRP
        echo -e "${GREEN}    📱 Device detected in Download Mode!${NC}"

        if [ -f "twrp.img" ]; then
            echo -e ""
            echo -e "${YELLOW}    Flash TWRP recovery to this device now?${NC}"
            echo -e "${YELLOW}    This will write twrp.img to the RECOVERY partition.${NC}"
            echo -e ""
            read -p "    Flash TWRP? [y/N]: " FLASH_CONFIRM
            if [[ "$FLASH_CONFIRM" =~ ^[Yy] ]]; then
                echo -e "    Flashing TWRP..."
                heimdall flash --RECOVERY twrp.img --no-reboot
                if [ $? -eq 0 ]; then
                    echo -e "${GREEN}    ✓ TWRP flashed successfully!${NC}"
                    echo -e ""
                    echo -e "${CYAN}    Next: Boot into TWRP recovery:${NC}"
                    echo -e "      1. Disconnect USB"
                    echo -e "      2. Hold Volume Up + Bixby + Power"
                    echo -e "      3. Release when Samsung logo appears"
                    echo -e "      4. Re-run this script once in TWRP to auto-push files"
                else
                    echo -e "${RED}    ✗ Heimdall flash failed. Try manually:${NC}"
                    echo -e "      heimdall flash --RECOVERY twrp.img --no-reboot"
                fi
            else
                echo -e "    Skipped. Flash manually later with:"
                echo -e "      heimdall flash --RECOVERY twrp.img --no-reboot"
            fi
        else
            echo -e "${YELLOW}    twrp.img not found — cannot flash.${NC}"
            echo -e "    Resolve TWRP download above, then re-run."
        fi
        ;;

    adb-normal)
        echo -e "${YELLOW}    📱 Device detected in normal Android mode.${NC}"
        echo -e "    To continue installation:"
        echo -e "      1. Boot to Download Mode (Vol Up+Down, plug USB) → re-run to flash TWRP"
        echo -e "      2. Or boot to TWRP (Vol Up+Bixby+Power) → re-run to push files"
        ;;

    *)
        echo -e "${YELLOW}    No device detected via ADB or Heimdall.${NC}"
        echo -e "    Connect your phone and re-run this script to:"
        echo -e "      • Download Mode → auto-flash TWRP"
        echo -e "      • TWRP Recovery  → auto-push all ZIPs to device"
        ;;
esac

# ============================================================
# Final Report
# ============================================================
echo -e "\n${CYAN}════════════════════════════════════════════════════════════${NC}"
echo -e "\n${GREEN}Files in $INSTALL_DIR:${NC}"
ls -lh "$INSTALL_DIR"

echo ""
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║                    DOWNLOAD REPORT                         ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"

echo -e "\n${GREEN}Device:${NC}    $MODEL_UPPER ($DEVICE_FRIENDLY)"
echo -e "${GREEN}Codename:${NC}  $CODENAME"
echo -e "${GREEN}Chipset:${NC}   $CHIPSET"
echo -e "${GREEN}Directory:${NC} $INSTALL_DIR"

# --- Successful downloads ---
if [ ${#PASSED_DOWNLOADS[@]} -gt 0 ]; then
    echo -e "\n${GREEN}✓ Successful (${#PASSED_DOWNLOADS[@]}):${NC}"
    for entry in "${PASSED_DOWNLOADS[@]}"; do
        IFS='|' read -r label file size <<< "$entry"
        echo -e "    ${GREEN}✓${NC} $label$file  ($size)"
    done
fi

# --- Skipped downloads ---
if [ ${#SKIPPED_DOWNLOADS[@]} -gt 0 ]; then
    echo -e "\n${YELLOW}⊘ Skipped (${#SKIPPED_DOWNLOADS[@]}):${NC}"
    for entry in "${SKIPPED_DOWNLOADS[@]}"; do
        IFS='|' read -r label reason <<< "$entry"
        echo -e "    ${YELLOW}⊘${NC} $label$reason"
    done
fi

# --- Failed downloads ---
CLEAN_FAILED=()
for entry in "${FAILED_DOWNLOADS[@]}"; do
    [ -n "$entry" ] && CLEAN_FAILED+=("$entry")
done
FAILED_DOWNLOADS=("${CLEAN_FAILED[@]}")

if [ ${#FAILED_DOWNLOADS[@]} -gt 0 ]; then
    echo -e "\n${RED}╔════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${RED}║  ✗ FAILED (${#FAILED_DOWNLOADS[@]}) — Manual action required                 ║${NC}"
    echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
    echo ""
    for entry in "${FAILED_DOWNLOADS[@]}"; do
        IFS='|' read -r label file url manual min hint <<< "$entry"
        echo -e "  ${RED}✗ $label${NC}"
        echo -e "    File needed : ${BOLD}$file${NC}"
        echo -e "    Tried URL   : $url"
        echo -e "    Manual URL  : ${CYAN}$manual${NC}"
        echo -e "    Save to     : ${BOLD}$INSTALL_DIR/$file${NC}"
        echo -e "    Min size    : $(numfmt --to=iec $min 2>/dev/null || echo "$min bytes")"
        [ -n "$hint" ] && echo -e "    ${YELLOW}Hint: $hint${NC}"
        echo ""
    done
    echo -e "${YELLOW}TIP: Some sites block direct wget downloads.${NC}"
    echo -e "${YELLOW}     Open the Manual URL in a browser, download the file,${NC}"
    echo -e "${YELLOW}     then copy/move it into: $INSTALL_DIR/${NC}"
else
    echo -e "\n${GREEN}All downloads completed successfully! ✓${NC}"
fi

# Next steps (context-aware)
echo -e "\n${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║  WHAT TO DO NEXT                                          ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"

case "$DEVICE_STATE" in
    adb-recovery)
        echo -e "  ${GREEN}Files are on the device. Flash them in TWRP now!${NC}"
        ;;
    download-mode)
        echo -e "  TWRP is flashed (or ready to flash). Next:"
        echo -e "  1. Boot into TWRP recovery"
        echo -e "  2. Re-run: ${BOLD}./download-nethunter.sh${NC}  (will skip downloads, push files)"
        ;;
    *)
        echo -e "  1. Resolve any failed downloads above"
        echo -e "  2. Unlock bootloader (if not done already)"
        echo -e "  3. Boot to Download Mode → re-run to flash TWRP"
        echo -e "  4. Boot to TWRP → re-run to push files and see flash instructions"
        ;;
esac

echo -e "\n  Source device config in future scripts:"
echo -e "    ${BOLD}source $INSTALL_DIR/device-config.sh${NC}"
echo ""
echo -e "${GREEN}All files saved to: $INSTALL_DIR${NC}"
#!/bin/bash
# ============================================================
# NetHunter Samsung Auto-Installer  (v3 — Feb 2026)
# Supports: S10, S10+, S10e, S10 5G, S7, S9, Note 10 series
# Run on: Kali Linux (with phone connected via USB)
# Downloads to: the directory you run this script from
#
# IMPORTANT: Current Kali NetHunter Samsung S10/S20/Note images
# require LineageOS — NOT stock Samsung firmware.
#
# Features:
#   • Auto-detects device via ADB (no manual model entry needed)
#   • Skips files that are already downloaded and valid
#   • Downloads LineageOS ROM automatically when required
#   • Verifies SHA256 checksums for NetHunter images
#   • Pushes files to device via ADB (if in TWRP)
#   • Flashes TWRP via Heimdall (if in Download Mode)
# ============================================================

set +e  # Don't exit on first error

# Colors
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'

# ============================================================
# Failure tracking — collected and reported at the end
# ============================================================
FAILED_DOWNLOADS=()   # "LABEL|FILENAME|URL|MANUAL_URL|MIN_SIZE|HINT"
PASSED_DOWNLOADS=()   # "LABEL|FILENAME|SIZE_HUMAN"
SKIPPED_DOWNLOADS=()  # "LABEL|REASON"

# Minimum expected file sizes (bytes) for validation
MIN_TWRP=1048576        # 1 MB   — real TWRP images are ~30-50 MB
MIN_MAGISK=1048576      # 1 MB   — real Magisk APK is ~12 MB
MIN_NETHUNTER=10485760  # 10 MB  — real NetHunter ZIPs are ~2.4 GB
MIN_NOVERITY=5120       # 5 KB   — real zip is ~10-20 KB
MIN_LINEAGEOS=209715200 # 200 MB — real LOS ZIPs are ~700 MB+

# ============================================================
# Helper: validate a file by minimum size
# ============================================================
validate_download() {
    local label="$1" file="$2" min_size="$3" url="$4" manual_url="$5" hint="${6:-}"
    if [ ! -f "$file" ]; then
        echo -e "${RED}    ✗ $file not found — download failed.${NC}"
        FAILED_DOWNLOADS+=("$label|$file|$url|$manual_url|$min_size|$hint")
        return 1
    fi
    local actual_size
    actual_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
    local human_size
    human_size=$(ls -lh "$file" | awk '{print $5}')
    if [ "$actual_size" -lt "$min_size" ]; then
        echo -e "${RED}    ✗ $file is only $human_size — expected at least $(numfmt --to=iec $min_size 2>/dev/null || echo "$min_size bytes").${NC}"
        echo -e "${RED}      File is likely an error page or empty. Removing it.${NC}"
        rm -f "$file"
        FAILED_DOWNLOADS+=("$label|$file|$url|$manual_url|$min_size|$hint")
        return 1
    fi
    echo -e "${GREEN}    ✓ $file$human_size — OK${NC}"
    PASSED_DOWNLOADS+=("$label|$file|$human_size")
    return 0
}

# ============================================================
# Helper: check if a valid file already exists → skip download
# ============================================================
already_have() {
    local label="$1" file="$2" min_size="$3"
    if [ -f "$file" ]; then
        local actual_size
        actual_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
        if [ "$actual_size" -ge "$min_size" ]; then
            local human_size
            human_size=$(ls -lh "$file" | awk '{print $5}')
            echo -e "${GREEN}    ✓ Already have $file ($human_size) — skipping download.${NC}"
            PASSED_DOWNLOADS+=("$label|$file|$human_size (cached)")
            return 0
        fi
    fi
    return 1
}

echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║  NetHunter Samsung Auto-Installer  (v3)                   ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"

# ============================================================
# Working directory = wherever the script is run from
# ============================================================
INSTALL_DIR="$(pwd)"
echo -e "\n${GREEN}[+] Working directory: $INSTALL_DIR${NC}"

# ============================================================
# Device Selection — auto-detect via ADB, fallback to manual
# ============================================================
echo -e "\n${CYAN}[Device Configuration]${NC}"

ADB_DETECTED=""
if command -v adb &>/dev/null; then
    # Start ADB server quietly
    adb start-server 2>/dev/null
    # Try to read model from connected device
    ADB_MODEL=$(adb shell getprop ro.product.model 2>/dev/null | tr -d '\r\n')
    if [ -n "$ADB_MODEL" ]; then
        ADB_DETECTED="$ADB_MODEL"
        echo -e "${GREEN}    📱 Detected device via ADB: $ADB_MODEL${NC}"
        # Try to get the full model number (SM-XXXXX)
        ADB_SKU=$(adb shell getprop ro.product.name 2>/dev/null | tr -d '\r\n')
        ADB_DEVICE=$(adb shell getprop ro.product.device 2>/dev/null | tr -d '\r\n')
        ADB_MODELNUM=$(adb shell getprop ro.boot.em.model 2>/dev/null | tr -d '\r\n')
        # Samsung stores full model in sku or modelnum
        [ -z "$ADB_MODELNUM" ] && ADB_MODELNUM=$(adb shell getprop ro.product.vendor.model 2>/dev/null | tr -d '\r\n')
        if [ -n "$ADB_MODELNUM" ]; then
            echo -e "${GREEN}    Model number: $ADB_MODELNUM${NC}"
            echo -e "\nUse detected model ${GREEN}$ADB_MODELNUM${NC}? [Y/n] "
            read -r USE_DETECTED
            if [[ ! "$USE_DETECTED" =~ ^[Nn] ]]; then
                MODEL_INPUT="$ADB_MODELNUM"
            fi
        else
            echo -e "${YELLOW}    Could not read model number. Device code: $ADB_DEVICE${NC}"
        fi
    else
        echo -e "${YELLOW}    No device detected via ADB (phone not connected or USB debugging off).${NC}"
    fi
fi

if [ -z "$MODEL_INPUT" ]; then
    echo -e "Enter your Samsung model number (e.g., SM-G973F, SM-G975F, SM-N975F)"
    echo -e "Or press Enter to use default: ${GREEN}SM-G973F (Galaxy S10 Exynos)${NC}"
    echo ""
    read -p "Model number [SM-G973F]: " MODEL_INPUT
fi

MODEL=${MODEL_INPUT:-SM-G973F}
MODEL_UPPER=$(echo "$MODEL" | tr '[:lower:]' '[:upper:]')

echo -e "\n${GREEN}[+] Selected model: $MODEL_UPPER${NC}"

# Model → codename mapping
# Format: CODENAME|FRIENDLY_NAME|TWRP_DEVICE
declare -A MODEL_MAP=(
    # Galaxy S10 series (Exynos)
    ["SM-G970F"]="beyond0lte|Galaxy S10e|samsunggalaxys10e"
    ["SM-G973F"]="beyond1lte|Galaxy S10|samsunggalaxys10"
    ["SM-G975F"]="beyond2lte|Galaxy S10+|samsunggalaxys10plus"
    ["SM-G977B"]="beyondx|Galaxy S10 5G|samsunggalaxys105g"
    # Galaxy S10 series (Snapdragon — bootloader often locked!)
    ["SM-G970U"]="beyond0lte|Galaxy S10e (SD)|samsunggalaxys10e"
    ["SM-G973U"]="beyond1lte|Galaxy S10 (SD)|samsunggalaxys10"
    ["SM-G975U"]="beyond2lte|Galaxy S10+ (SD)|samsunggalaxys10plus"
    # Galaxy S7 series (Samsung UI — OUI)
    ["SM-G935F"]="hero2lte|Galaxy S7 Edge|samsunggalaxys7"
    ["SM-G930F"]="herolte|Galaxy S7|samsunggalaxys7"
    # Galaxy S9 series
    ["SM-G960F"]="starlte|Galaxy S9|samsunggalaxys9"
    ["SM-G965F"]="star2lte|Galaxy S9+|samsunggalaxys9plus"
    # Galaxy Note series
    ["SM-N970F"]="d1|Galaxy Note 10|samsunggalaxynote10"
    ["SM-N975F"]="d2s|Galaxy Note 10+|samsunggalaxynote10plus"
)

# Parse model info
if [[ -v MODEL_MAP[$MODEL_UPPER] ]]; then
    IFS='|' read -r CODENAME DEVICE_FRIENDLY TWRP_DEVICE <<< "${MODEL_MAP[$MODEL_UPPER]}"
    echo -e "${GREEN}    Device:   $DEVICE_FRIENDLY${NC}"
    echo -e "${GREEN}    Codename: $CODENAME${NC}"
    echo -e "${GREEN}    TWRP key: $TWRP_DEVICE${NC}"
else
    echo -e "${YELLOW}[!] Model not in database. Using S10 defaults.${NC}"
    echo -e "${YELLOW}    You may need to manually find correct TWRP/NetHunter files.${NC}"
    CODENAME="beyond1lte"
    DEVICE_FRIENDLY="Galaxy S10"
    TWRP_DEVICE="samsunggalaxys10"
fi

# Detect chipset
if [[ "$MODEL_UPPER" =~ U[0-9]*$ ]]; then
    CHIPSET="snapdragon"
    echo -e "${YELLOW}    Chipset: Snapdragon (US/China variant)${NC}"
    echo -e "${RED}    ⚠ Warning: Many Snapdragon Samsung devices have locked bootloaders!${NC}"
else
    CHIPSET="exynos"
    echo -e "${GREEN}    Chipset: Exynos (International variant)${NC}"
fi

# Save device config
cat > "$INSTALL_DIR/device-config.sh" << EOF
# Device configuration — source this file in other scripts
export MODEL="$MODEL_UPPER"
export CODENAME="$CODENAME"
export DEVICE_FRIENDLY="$DEVICE_FRIENDLY"
export TWRP_DEVICE="$TWRP_DEVICE"
export CHIPSET="$CHIPSET"
EOF
echo -e "${GREEN}[+] Saved device config to: $INSTALL_DIR/device-config.sh${NC}"

# ============================================================
# 1. Install Heimdall & tools
# ============================================================
echo -e "\n${CYAN}[1/7] Installing Heimdall & tools...${NC}"

# Check which tools are already installed
MISSING_PKGS=()
for cmd in heimdall adb fastboot curl wget; do
    if ! command -v "$cmd" &>/dev/null; then
        MISSING_PKGS+=("$cmd")
    fi
done

if [ ${#MISSING_PKGS[@]} -eq 0 ]; then
    echo -e "${GREEN}    All tools already installed — skipping.${NC}"
else
    echo -e "    Missing: ${MISSING_PKGS[*]}. Installing..."
    sudo apt update --allow-releaseinfo-change 2>/dev/null || {
        echo -e "${YELLOW}[!] apt update had some errors (mirror sync issues are common).${NC}"
        echo -e "${YELLOW}    Continuing anyway — packages may still install fine.${NC}"
    }
    sudo apt install -y heimdall-flash heimdall-flash-frontend adb fastboot curl wget || {
        echo -e "${RED}[!] Package installation failed. Try:${NC}"
        echo -e "    sudo apt update --fix-missing"
        echo -e "    sudo apt install -y heimdall-flash adb fastboot curl wget"
        echo -e "${YELLOW}    Continuing with script...${NC}"
    }
fi

# ============================================================
# 2. Download TWRP Recovery
# ============================================================
echo -e "\n${CYAN}[2/7] TWRP Recovery for $CODENAME...${NC}"
TWRP_DL_URL="https://dl.twrp.me/$CODENAME/"
TWRP_MANUAL_URL="https://twrp.me/samsung/$TWRP_DEVICE.html"

if already_have "TWRP Recovery" "twrp.img" "$MIN_TWRP"; then
    : # already_have prints confirmation and adds to PASSED
else
    echo -e "    Checking twrp.me for latest $CODENAME image..."
    TWRP_PAGE=$(curl -sL "$TWRP_DL_URL")
    TWRP_FILE=$(echo "$TWRP_PAGE" | grep -oP 'twrp-[0-9.]+_[0-9]+-[0-9]+-'"$CODENAME"'\.img' | head -1)

    if [ -n "$TWRP_FILE" ]; then
        TWRP_FULL_URL="https://dl.twrp.me/$CODENAME/$TWRP_FILE"
        echo -e "${GREEN}    Found: $TWRP_FILE${NC}"
    else
        TWRP_FILE="twrp-3.7.0_12-0-$CODENAME.img"
        TWRP_FULL_URL="https://dl.twrp.me/$CODENAME/$TWRP_FILE"
        echo -e "${YELLOW}    Using fallback: $TWRP_FILE${NC}"
    fi

    echo -e "    Downloading twrp.img ..."
    wget --content-disposition --header="Referer: $TWRP_DL_URL" \
         -O twrp.img "$TWRP_FULL_URL" 2>&1 || true
    validate_download "TWRP Recovery" "twrp.img" "$MIN_TWRP" \
        "$TWRP_FULL_URL" "$TWRP_MANUAL_URL" \
        "twrp.me blocks direct wget. Open the Manual URL in a browser and download manually."
fi

# ============================================================
# 3. Download Magisk (latest stable)
# ============================================================
echo -e "\n${CYAN}[3/7] Magisk...${NC}"
MAGISK_MANUAL_URL="https://github.com/topjohnwu/Magisk/releases"

# Check for any existing Magisk APK
EXISTING_MAGISK=$(ls -1 Magisk-*.apk 2>/dev/null | head -1)
if [ -n "$EXISTING_MAGISK" ] && already_have "Magisk" "$EXISTING_MAGISK" "$MIN_MAGISK"; then
    MAGISK_FILE="$EXISTING_MAGISK"
else
    MAGISK_URL=$(curl -sL https://api.github.com/repos/topjohnwu/Magisk/releases/latest \
        | grep "browser_download_url.*Magisk-.*\.apk" | head -1 | cut -d '"' -f 4)

    if [ -n "$MAGISK_URL" ]; then
        MAGISK_FILE=$(basename "$MAGISK_URL")
        echo -e "    Downloading $MAGISK_FILE ..."
        wget -O "$MAGISK_FILE" "$MAGISK_URL" 2>&1 || true
        validate_download "Magisk" "$MAGISK_FILE" "$MIN_MAGISK" \
            "$MAGISK_URL" "$MAGISK_MANUAL_URL" \
            "Download the latest Magisk-vXX.X.apk from the GitHub releases page."
    else
        echo -e "${RED}    ✗ Could not resolve Magisk URL from GitHub API.${NC}"
        MAGISK_FILE="Magisk-latest.apk"
        FAILED_DOWNLOADS+=("Magisk|$MAGISK_FILE|<GitHub API failed>|$MAGISK_MANUAL_URL|$MIN_MAGISK|Download the latest Magisk-vXX.X.apk from GitHub releases.")
    fi
fi

# ============================================================
# 4. Download NetHunter (auto-detect from Kali mirror)
# ============================================================
echo -e "\n${CYAN}[4/7] NetHunter for $CODENAME...${NC}"
NH_MANUAL_URL="https://www.kali.org/get-kali/#kali-mobile"
NH_BASE="https://kali.download/nethunter-images/current/"

# Check for any existing NetHunter ZIP matching our codename
EXISTING_NH=$(ls -1 kali-nethunter-*"$CODENAME"*-full.zip 2>/dev/null | head -1)
if [ -n "$EXISTING_NH" ] && already_have "NetHunter" "$EXISTING_NH" "$MIN_NETHUNTER"; then
    NH_FILE="$EXISTING_NH"
else
    echo -e "    Fetching available images from Kali mirror..."
    NH_DIR_LISTING=$(curl -sL "$NH_BASE")

    echo -e "    Searching for codename: ${BOLD}$CODENAME${NC}"
    NH_MATCHES=$(echo "$NH_DIR_LISTING" | grep -oP 'kali-nethunter-[0-9.]+-'"$CODENAME"'-[^"]+?-full\.zip' | sort -u)

    if [ -n "$NH_MATCHES" ]; then
        NH_COUNT=$(echo "$NH_MATCHES" | wc -l)
        if [ "$NH_COUNT" -eq 1 ]; then
            NH_FILE="$NH_MATCHES"
            echo -e "${GREEN}    Found: $NH_FILE${NC}"
        else
            echo -e "${GREEN}    Found $NH_COUNT images for $CODENAME:${NC}"
            i=1
            while IFS= read -r f; do
                echo -e "      $i) $f"
                i=$((i + 1))
            done <<< "$NH_MATCHES"
            read -p "    Select image [1]: " NH_PICK
            NH_PICK=${NH_PICK:-1}
            NH_FILE=$(echo "$NH_MATCHES" | sed -n "${NH_PICK}p")
            [ -z "$NH_FILE" ] && NH_FILE=$(echo "$NH_MATCHES" | head -1)
            echo -e "${GREEN}    Selected: $NH_FILE${NC}"
        fi
        NH_URL="${NH_BASE}${NH_FILE}"
    else
        echo -e "${YELLOW}    No image found for codename '$CODENAME'.${NC}"
        echo -e "${YELLOW}    Searching for any 'beyond' (S10 family) image...${NC}"
        NH_FALLBACK=$(echo "$NH_DIR_LISTING" | grep -oP 'kali-nethunter-[0-9.]+-beyond1lte-[^"]+?-full\.zip' | head -1)
        if [ -n "$NH_FALLBACK" ]; then
            NH_FILE="$NH_FALLBACK"
            NH_URL="${NH_BASE}${NH_FILE}"
            echo -e "${GREEN}    Found fallback: $NH_FILE${NC}"
        else
            NH_FILE="kali-nethunter-$CODENAME-full.zip"
            NH_URL=""
            echo -e "${RED}    ✗ No matching image found on the Kali mirror.${NC}"
        fi
    fi

    # Detect LineageOS requirement
    if [[ "$NH_FILE" == *"-los-"* ]]; then
        LOS_VERSION=$(echo "$NH_FILE" | grep -oP '(?<=-los-)[a-z]+')
        echo -e ""
        echo -e "${YELLOW}    ╔══════════════════════════════════════════════════════╗${NC}"
        echo -e "${YELLOW}    ║  ⚠  LINEAGEOS REQUIRED                               ║${NC}"
        echo -e "${YELLOW}    ╚══════════════════════════════════════════════════════╝${NC}"
        echo -e "${YELLOW}    This NetHunter image requires LineageOS (not stock Samsung).${NC}"
        echo -e "${YELLOW}    LineageOS version in this build: $LOS_VERSION${NC}"
        echo -e "${YELLOW}    LineageOS will be downloaded automatically in step 5.${NC}"
        echo ""
        echo "export REQUIRES_LINEAGEOS=\"true\"" >> "$INSTALL_DIR/device-config.sh"
        echo "export LOS_VERSION=\"$LOS_VERSION\"" >> "$INSTALL_DIR/device-config.sh"
    fi

    if [ -n "$NH_URL" ]; then
        echo -e "    Downloading $NH_FILE (~2.4 GB — be patient)..."
        wget -O "$NH_FILE" "$NH_URL" 2>&1 || true
        validate_download "NetHunter" "$NH_FILE" "$MIN_NETHUNTER" \
            "$NH_URL" "$NH_MANUAL_URL" \
            "Go to kali.org/get-kali → Mobile → Samsung section. Look for $DEVICE_FRIENDLY."
    else
        FAILED_DOWNLOADS+=("NetHunter|$NH_FILE|<no URL found>|$NH_MANUAL_URL|$MIN_NETHUNTER|Go to kali.org/get-kali → Mobile → Samsung and download the image for $DEVICE_FRIENDLY.")
    fi
fi

# ============================================================
# 4b. Verify SHA256 checksum for NetHunter
# ============================================================
if [ -f "$NH_FILE" ]; then
    echo -e "\n${CYAN}    Verifying SHA256 checksum for $NH_FILE...${NC}"
    SHA_URL="${NH_BASE}SHA256SUMS"
    SHA_SUMS=$(curl -sL "$SHA_URL")

    if [ -n "$SHA_SUMS" ]; then
        EXPECTED_SHA=$(echo "$SHA_SUMS" | grep "$NH_FILE" | awk '{print $1}')
        if [ -n "$EXPECTED_SHA" ]; then
            ACTUAL_SHA=$(sha256sum "$NH_FILE" | awk '{print $1}')
            if [ "$EXPECTED_SHA" = "$ACTUAL_SHA" ]; then
                echo -e "${GREEN}    ✓ SHA256 checksum verified — file is authentic.${NC}"
            else
                echo -e "${RED}    ✗ SHA256 MISMATCH!${NC}"
                echo -e "${RED}      Expected: $EXPECTED_SHA${NC}"
                echo -e "${RED}      Got:      $ACTUAL_SHA${NC}"
                echo -e "${RED}      The file may be corrupted. Consider re-downloading.${NC}"
            fi
        else
            echo -e "${YELLOW}    No checksum found for $NH_FILE in SHA256SUMS.${NC}"
        fi
    else
        echo -e "${YELLOW}    Could not fetch SHA256SUMS from $SHA_URL${NC}"
    fi
fi

# ============================================================
# 5. Download LineageOS (if required)
# ============================================================
REQUIRES_LOS=false
[[ "$NH_FILE" == *"-los-"* ]] && REQUIRES_LOS=true

if [ "$REQUIRES_LOS" = true ]; then
    echo -e "\n${CYAN}[5/7] LineageOS ROM for $CODENAME...${NC}"
    LOS_MANUAL_URL="https://wiki.lineageos.org/devices/$CODENAME"

    # Check for existing LineageOS ZIP
    EXISTING_LOS=$(ls -1 lineage-*"$CODENAME"*.zip 2>/dev/null | head -1)
    if [ -n "$EXISTING_LOS" ] && already_have "LineageOS" "$EXISTING_LOS" "$MIN_LINEAGEOS"; then
        LOS_FILE="$EXISTING_LOS"
    else
        # LineageOS provides an API to find the latest build
        LOS_API="https://download.lineageos.org/api/v2/devices/$CODENAME"
        echo -e "    Querying LineageOS API for $CODENAME..."
        LOS_JSON=$(curl -sL "$LOS_API")

        # Try the download API for builds
        LOS_BUILDS_API="https://download.lineageos.org/api/v2/devices/$CODENAME/builds"
        LOS_BUILDS=$(curl -sL "$LOS_BUILDS_API")

        # Extract the latest build download URL
        LOS_DL_URL=$(echo "$LOS_BUILDS" | grep -oP '"url"\s*:\s*"\K[^"]+\.zip' | head -1)

        if [ -n "$LOS_DL_URL" ]; then
            LOS_FILE=$(basename "$LOS_DL_URL")
            echo -e "${GREEN}    Found: $LOS_FILE${NC}"
            echo -e "    Downloading LineageOS (~700 MB)..."
            wget -O "$LOS_FILE" "$LOS_DL_URL" 2>&1 || true
            validate_download "LineageOS" "$LOS_FILE" "$MIN_LINEAGEOS" \
                "$LOS_DL_URL" "$LOS_MANUAL_URL" \
                "Visit $LOS_MANUAL_URL and download the latest build."
        else
            # Fallback: try scraping the device page
            echo -e "${YELLOW}    API did not return a build. Checking download page...${NC}"
            LOS_PAGE=$(curl -sL "https://download.lineageos.org/$CODENAME")
            LOS_DL_URL=$(echo "$LOS_PAGE" | grep -oP 'https://[^"]+lineage-[0-9.]+-[0-9]+-nightly-'"$CODENAME"'-signed\.zip' | head -1)
            if [ -n "$LOS_DL_URL" ]; then
                LOS_FILE=$(basename "$LOS_DL_URL")
                echo -e "${GREEN}    Found: $LOS_FILE${NC}"
                echo -e "    Downloading LineageOS (~700 MB)..."
                wget -O "$LOS_FILE" "$LOS_DL_URL" 2>&1 || true
                validate_download "LineageOS" "$LOS_FILE" "$MIN_LINEAGEOS" \
                    "$LOS_DL_URL" "$LOS_MANUAL_URL" \
                    "Visit $LOS_MANUAL_URL and download the latest build."
            else
                LOS_FILE="lineage-$CODENAME.zip"
                echo -e "${RED}    ✗ Could not find LineageOS download for $CODENAME.${NC}"
                FAILED_DOWNLOADS+=("LineageOS|$LOS_FILE|<auto-detect failed>|$LOS_MANUAL_URL|$MIN_LINEAGEOS|Visit $LOS_MANUAL_URL, download the latest nightly ZIP, and save it here.")
            fi
        fi
    fi
else
    echo -e "\n${CYAN}[5/7] LineageOS — not required for this build.${NC}"
    SKIPPED_DOWNLOADS+=("LineageOS|Not required — NetHunter image uses stock firmware")
fi

# ============================================================
# 6. Download no-verity-opt-encrypt
# ============================================================
echo -e "\n${CYAN}[6/7] no-verity-opt-encrypt...${NC}"
NOVERITY_FILE="no-verity-opt-encrypt.zip"
NOVERITY_MANUAL_URL="https://github.com/AMP-1/no-verity-opt-encrypt"
NOVERITY_DOWNLOADED=false

if [ "$REQUIRES_LOS" = true ]; then
    echo -e "${YELLOW}    LineageOS build detected — no-verity is NOT needed.${NC}"
    echo -e "${YELLOW}    Skipping (only required for stock Samsung firmware).${NC}"
    SKIPPED_DOWNLOADS+=("no-verity-opt-encrypt|Not needed for LineageOS builds")
elif already_have "no-verity-opt-encrypt" "$NOVERITY_FILE" "$MIN_NOVERITY"; then
    NOVERITY_DOWNLOADED=true
else
    NOVERITY_URLS=(
        "https://artifacts.kali.org/images-nethunter/nethunter-installer/no-verity-opt-encrypt-6.1.zip"
        "https://github.com/AMP-1/no-verity-opt-encrypt/releases/download/noencrypt/no-verity-opt-encrypt-6.1.zip"
    )
    for NOVERITY_URL in "${NOVERITY_URLS[@]}"; do
        echo -e "    Trying: $NOVERITY_URL"
        wget -O "$NOVERITY_FILE" "$NOVERITY_URL" 2>&1 || true
        if validate_download "no-verity-opt-encrypt" "$NOVERITY_FILE" "$MIN_NOVERITY" \
            "$NOVERITY_URL" "$NOVERITY_MANUAL_URL" \
            "Search GitHub for 'no-verity-opt-encrypt' and download the latest ZIP."; then
            NOVERITY_DOWNLOADED=true
            break
        fi
    done
fi

# ============================================================
# 7. Device actions — push files / flash TWRP
# ============================================================
echo -e "\n${CYAN}[7/7] Device actions...${NC}"

# Detect device state via ADB / Heimdall
DEVICE_STATE="none"
if command -v adb &>/dev/null; then
    ADB_STATE=$(adb get-state 2>/dev/null | tr -d '\r\n')
    case "$ADB_STATE" in
        device)     DEVICE_STATE="adb-normal" ;;
        recovery)   DEVICE_STATE="adb-recovery" ;;
        sideload)   DEVICE_STATE="adb-sideload" ;;
    esac
fi

if [ "$DEVICE_STATE" = "none" ] && command -v heimdall &>/dev/null; then
    if heimdall detect &>/dev/null; then
        DEVICE_STATE="download-mode"
    fi
fi

case "$DEVICE_STATE" in

    adb-recovery)
        # Phone is in TWRP recovery — push all files automatically
        echo -e "${GREEN}    📱 Device detected in TWRP recovery mode!${NC}"
        echo -e "    Pushing files to /sdcard/NetHunter/ ..."
        adb shell mkdir -p /sdcard/NetHunter 2>/dev/null

        PUSH_FILES=()
        # LineageOS first (must be flashed first)
        if [ "$REQUIRES_LOS" = true ] && [ -f "${LOS_FILE:-}" ]; then
            PUSH_FILES+=("$LOS_FILE")
        fi
        # Magisk
        [ -f "${MAGISK_FILE:-}" ] && PUSH_FILES+=("$MAGISK_FILE")
        # NetHunter
        [ -f "${NH_FILE:-}" ] && PUSH_FILES+=("$NH_FILE")
        # no-verity (only for stock)
        if [ "$REQUIRES_LOS" = false ] && [ -f "$NOVERITY_FILE" ]; then
            PUSH_FILES+=("$NOVERITY_FILE")
        fi

        if [ ${#PUSH_FILES[@]} -gt 0 ]; then
            for pf in "${PUSH_FILES[@]}"; do
                echo -e "    Pushing $pf ..."
                adb push "$pf" /sdcard/NetHunter/ 2>&1
                if [ $? -eq 0 ]; then
                    echo -e "${GREEN}    ✓ $pf pushed${NC}"
                else
                    echo -e "${RED}    ✗ Failed to push $pf${NC}"
                fi
            done
            echo -e "\n${GREEN}    Files on device:${NC}"
            adb shell ls -la /sdcard/NetHunter/
        else
            echo -e "${YELLOW}    No valid files to push.${NC}"
        fi

        echo -e "\n${GREEN}    ✓ Files pushed! In TWRP:${NC}"
        if [ "$REQUIRES_LOS" = true ]; then
            echo -e "       1. Install → select LineageOS ZIP → Swipe to flash"
            echo -e "       2. Install → select Magisk APK → Swipe to flash"
            echo -e "       3. Install → select NetHunter ZIP → Swipe to flash"
            echo -e "       4. Wipe → Advanced Wipe → Dalvik/ART Cache → Swipe"
            echo -e "       5. Reboot → System"
        else
            echo -e "       1. Install → select Magisk APK → Swipe to flash"
            echo -e "       2. Install → select no-verity ZIP → Swipe to flash"
            echo -e "       3. Install → select NetHunter ZIP → Swipe to flash"
            echo -e "       4. Wipe → Advanced Wipe → Dalvik/ART Cache → Swipe"
            echo -e "       5. Reboot → System"
        fi
        ;;

    download-mode)
        # Phone is in Download Mode — offer to flash TWRP
        echo -e "${GREEN}    📱 Device detected in Download Mode!${NC}"

        if [ -f "twrp.img" ]; then
            echo -e ""
            echo -e "${YELLOW}    Flash TWRP recovery to this device now?${NC}"
            echo -e "${YELLOW}    This will write twrp.img to the RECOVERY partition.${NC}"
            echo -e ""
            read -p "    Flash TWRP? [y/N]: " FLASH_CONFIRM
            if [[ "$FLASH_CONFIRM" =~ ^[Yy] ]]; then
                echo -e "    Flashing TWRP..."
                heimdall flash --RECOVERY twrp.img --no-reboot
                if [ $? -eq 0 ]; then
                    echo -e "${GREEN}    ✓ TWRP flashed successfully!${NC}"
                    echo -e ""
                    echo -e "${CYAN}    Next: Boot into TWRP recovery:${NC}"
                    echo -e "      1. Disconnect USB"
                    echo -e "      2. Hold Volume Up + Bixby + Power"
                    echo -e "      3. Release when Samsung logo appears"
                    echo -e "      4. Re-run this script once in TWRP to auto-push files"
                else
                    echo -e "${RED}    ✗ Heimdall flash failed. Try manually:${NC}"
                    echo -e "      heimdall flash --RECOVERY twrp.img --no-reboot"
                fi
            else
                echo -e "    Skipped. Flash manually later with:"
                echo -e "      heimdall flash --RECOVERY twrp.img --no-reboot"
            fi
        else
            echo -e "${YELLOW}    twrp.img not found — cannot flash.${NC}"
            echo -e "    Resolve TWRP download above, then re-run."
        fi
        ;;

    adb-normal)
        echo -e "${YELLOW}    📱 Device detected in normal Android mode.${NC}"
        echo -e "    To continue installation:"
        echo -e "      1. Boot to Download Mode (Vol Up+Down, plug USB) → re-run to flash TWRP"
        echo -e "      2. Or boot to TWRP (Vol Up+Bixby+Power) → re-run to push files"
        ;;

    *)
        echo -e "${YELLOW}    No device detected via ADB or Heimdall.${NC}"
        echo -e "    Connect your phone and re-run this script to:"
        echo -e "      • Download Mode → auto-flash TWRP"
        echo -e "      • TWRP Recovery  → auto-push all ZIPs to device"
        ;;
esac

# ============================================================
# Final Report
# ============================================================
echo -e "\n${CYAN}════════════════════════════════════════════════════════════${NC}"
echo -e "\n${GREEN}Files in $INSTALL_DIR:${NC}"
ls -lh "$INSTALL_DIR"

echo ""
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║                    DOWNLOAD REPORT                         ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"

echo -e "\n${GREEN}Device:${NC}    $MODEL_UPPER ($DEVICE_FRIENDLY)"
echo -e "${GREEN}Codename:${NC}  $CODENAME"
echo -e "${GREEN}Chipset:${NC}   $CHIPSET"
echo -e "${GREEN}Directory:${NC} $INSTALL_DIR"

# --- Successful downloads ---
if [ ${#PASSED_DOWNLOADS[@]} -gt 0 ]; then
    echo -e "\n${GREEN}✓ Successful (${#PASSED_DOWNLOADS[@]}):${NC}"
    for entry in "${PASSED_DOWNLOADS[@]}"; do
        IFS='|' read -r label file size <<< "$entry"
        echo -e "    ${GREEN}✓${NC} $label$file  ($size)"
    done
fi

# --- Skipped downloads ---
if [ ${#SKIPPED_DOWNLOADS[@]} -gt 0 ]; then
    echo -e "\n${YELLOW}⊘ Skipped (${#SKIPPED_DOWNLOADS[@]}):${NC}"
    for entry in "${SKIPPED_DOWNLOADS[@]}"; do
        IFS='|' read -r label reason <<< "$entry"
        echo -e "    ${YELLOW}⊘${NC} $label$reason"
    done
fi

# --- Failed downloads ---
CLEAN_FAILED=()
for entry in "${FAILED_DOWNLOADS[@]}"; do
    [ -n "$entry" ] && CLEAN_FAILED+=("$entry")
done
FAILED_DOWNLOADS=("${CLEAN_FAILED[@]}")

if [ ${#FAILED_DOWNLOADS[@]} -gt 0 ]; then
    echo -e "\n${RED}╔════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${RED}║  ✗ FAILED (${#FAILED_DOWNLOADS[@]}) — Manual action required                 ║${NC}"
    echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
    echo ""
    for entry in "${FAILED_DOWNLOADS[@]}"; do
        IFS='|' read -r label file url manual min hint <<< "$entry"
        echo -e "  ${RED}✗ $label${NC}"
        echo -e "    File needed : ${BOLD}$file${NC}"
        echo -e "    Tried URL   : $url"
        echo -e "    Manual URL  : ${CYAN}$manual${NC}"
        echo -e "    Save to     : ${BOLD}$INSTALL_DIR/$file${NC}"
        echo -e "    Min size    : $(numfmt --to=iec $min 2>/dev/null || echo "$min bytes")"
        [ -n "$hint" ] && echo -e "    ${YELLOW}Hint: $hint${NC}"
        echo ""
    done
    echo -e "${YELLOW}TIP: Some sites block direct wget downloads.${NC}"
    echo -e "${YELLOW}     Open the Manual URL in a browser, download the file,${NC}"
    echo -e "${YELLOW}     then copy/move it into: $INSTALL_DIR/${NC}"
else
    echo -e "\n${GREEN}All downloads completed successfully! ✓${NC}"
fi

# Next steps (context-aware)
echo -e "\n${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║  WHAT TO DO NEXT                                          ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"

case "$DEVICE_STATE" in
    adb-recovery)
        echo -e "  ${GREEN}Files are on the device. Flash them in TWRP now!${NC}"
        ;;
    download-mode)
        echo -e "  TWRP is flashed (or ready to flash). Next:"
        echo -e "  1. Boot into TWRP recovery"
        echo -e "  2. Re-run: ${BOLD}./download-nethunter.sh${NC}  (will skip downloads, push files)"
        ;;
    *)
        echo -e "  1. Resolve any failed downloads above"
        echo -e "  2. Unlock bootloader (if not done already)"
        echo -e "  3. Boot to Download Mode → re-run to flash TWRP"
        echo -e "  4. Boot to TWRP → re-run to push files and see flash instructions"
        ;;
esac

echo -e "\n  Source device config in future scripts:"
echo -e "    ${BOLD}source $INSTALL_DIR/device-config.sh${NC}"
echo ""
echo -e "${GREEN}All files saved to: $INSTALL_DIR${NC}"

Save as download-nethunter.sh, then run: chmod +x download-nethunter.sh && ./download-nethunter.sh

Re-run friendly: The script skips files already downloaded. Connect your phone in different modes to unlock new actions — Download Mode auto-flashes TWRP, TWRP recovery auto-pushes all ZIPs.

2. Unlock the Bootloader

Knox Will Be Tripped

Unlocking the bootloader on Samsung devices permanently trips Knox. Samsung Pay and some enterprise features will stop working. This cannot be reversed.

Step 2.1: Enable Developer Options

  1. Go to Settings → About Phone → Software Information
  2. Tap Build Number 7 times until you see "Developer mode enabled"
  3. Go back to Settings → Developer Options
  4. Enable OEM Unlocking (if grayed out, see troubleshooting)
  5. Enable USB Debugging

Step 2.2: Boot to Download Mode

  1. Power off the phone completely
  2. Connect USB cable to your Kali machine (leave phone end disconnected)
  3. Hold Volume Up + Volume Down simultaneously
  4. While holding both buttons, connect the USB cable to the phone
  5. Keep holding until you see the warning screen
  6. Press Volume Up to continue to Download Mode

Alternative Method

If the above doesn't work: Power off → Hold Bixby + Volume Down → Connect USB cable.

Step 2.3: Unlock via Heimdall

bash
# Verify device is detected
heimdall detect

# If detected, you'll see:
# Device detected

# Unlock bootloader (this triggers factory reset)
heimdall flash --no-reboot
# Verify device is detected
heimdall detect

# If detected, you'll see:
# Device detected

# Unlock bootloader (this triggers factory reset)
heimdall flash --no-reboot

After unlocking, the phone will factory reset. Complete initial Android setup (skip Google account for now), then re-enable Developer Options and USB Debugging.

3. Flash TWRP Recovery

Step 3.1: Download Correct TWRP

Go to twrp.me/Devices and search for your exact model:

Device Model Number Codename
Galaxy S10 (Exynos) SM-G973F beyond1lte
Galaxy S10+ (Exynos) SM-G975F beyond2lte
Galaxy S10e (Exynos) SM-G970F beyond0lte
Galaxy S10 (Snapdragon) SM-G973U beyond1q

Exynos vs Snapdragon

The Exynos (international) and Snapdragon (US/China) variants use different firmware and recoveries. Flashing the wrong one will brick your device. Check your model number in Settings → About Phone.

Step 3.2: Flash TWRP with Heimdall

bash
# Navigate to your download folder
cd ~/nethunter-install

# Rename TWRP file for convenience (adjust filename as needed)
mv twrp-*.img twrp.img

# Boot phone to Download Mode (Vol Up + Vol Down + USB)

# Verify device detected
heimdall detect

# Flash TWRP to recovery partition
heimdall flash --RECOVERY twrp.img --no-reboot
# Navigate to your download folder
cd ~/nethunter-install

# Rename TWRP file for convenience (adjust filename as needed)
mv twrp-*.img twrp.img

# Boot phone to Download Mode (Vol Up + Vol Down + USB)

# Verify device detected
heimdall detect

# Flash TWRP to recovery partition
heimdall flash --RECOVERY twrp.img --no-reboot

Step 3.3: Boot into TWRP

Important: You must boot directly into TWRP before booting Android, or Samsung will overwrite recovery.

  1. Disconnect USB cable
  2. Hold Volume Down + Bixby + Power for 10+ seconds (force restart)
  3. When screen goes black, immediately switch to Volume Up + Bixby + Power
  4. Keep holding until you see the TWRP logo
  5. Swipe to allow modifications when prompted

TWRP Password Prompt

If TWRP asks for a password or says "data encrypted", you'll need to format data. Go to Wipe → Format Data → type "yes". This erases everything but fixes encryption issues.

4. Flash NetHunter

Step 4.1: Copy Files to Device

LineageOS Must Be Flashed First

If you're using a LineageOS-based NetHunter image (all current Samsung S10 builds), you must flash LineageOS via TWRP before pushing and flashing Magisk/NetHunter. See LineageOS install guide for beyond1lte.

With TWRP running, your phone mounts as a storage device. Push the required ZIPs:

bash
# Connect phone (in TWRP) via USB

# Create folder on device
adb shell mkdir -p /sdcard/NetHunter

# Push all required files
adb push lineage-*.zip /sdcard/NetHunter/         # LineageOS ROM
adb push Magisk-*.apk /sdcard/NetHunter/
adb push kali-nethunter-*.zip /sdcard/NetHunter/
# adb push no-verity-*.zip /sdcard/NetHunter/      # Only if using stock Samsung (not LOS)

# Verify files are there
adb shell ls -la /sdcard/NetHunter/
# Connect phone (in TWRP) via USB

# Create folder on device
adb shell mkdir -p /sdcard/NetHunter

# Push all required files
adb push lineage-*.zip /sdcard/NetHunter/         # LineageOS ROM
adb push Magisk-*.apk /sdcard/NetHunter/
adb push kali-nethunter-*.zip /sdcard/NetHunter/
# adb push no-verity-*.zip /sdcard/NetHunter/      # Only if using stock Samsung (not LOS)

# Verify files are there
adb shell ls -la /sdcard/NetHunter/

Step 4.2: Install Magisk (Root)

  1. In TWRP, go to Install
  2. Navigate to /sdcard/NetHunter/
  3. Select Magisk-*.apk (yes, the APK — TWRP will handle it)
  4. Swipe to confirm flash
  5. Wait for completion (do NOT reboot yet)

Step 4.3: Install no-verity-opt-encrypt (Stock Samsung Only)

Skip This Step on LineageOS

If you flashed LineageOS, you can skip this step entirely. LineageOS does not use Samsung's dm-verity or forced encryption, so this module is unnecessary and may cause issues.

Only for stock Samsung firmware:

  1. In TWRP, tap the back arrow
  2. Go to Install again
  3. Select no-verity-opt-encrypt-*.zip
  4. Swipe to confirm flash
  5. Wait for completion (do NOT reboot yet)

Why This Module?

This disables dm-verity and forced encryption, which can cause boot loops on rooted stock Samsung devices. Not needed on LineageOS or other custom ROMs.

Step 4.4: Install NetHunter

  1. In TWRP, go to Install
  2. Select nethunter-*.zip
  3. Swipe to confirm flash
  4. This takes 5-10 minutes — be patient
  5. When complete, tap Reboot System

First Boot Takes Time

The first boot after flashing NetHunter can take 5-15 minutes. The screen may stay black or show the logo for a while. Do not force restart — let it complete.

5. First Boot & Configuration

Step 5.1: Initial Android Setup

  1. Complete Android setup wizard (WiFi, skip Google account for now)
  2. Open the app drawer — you should see NetHunter, NetHunter Store, NetHunter Terminal, and Magisk
  3. Open Magisk and verify it shows "Installed" with a version number

Step 5.2: Grant Root Access

  1. Open NetHunter app
  2. A Magisk superuser prompt will appear — tap Grant
  3. The app will initialize and download additional components
  4. Open NetHunter Terminal — grant root when prompted

Step 5.3: Verify Installation

bash
# Open NetHunter Terminal and run:

# Check root access
su -c id
# Should show: uid=0(root)

# Check NetHunter chroot
nethunter
# Should drop you into Kali shell

# Verify Kali
cat /etc/os-release
# Should show Kali Linux

# Check kernel features
nethunter -c "ls /lib/modules"
# Should list kernel modules

# Exit chroot
exit
# Open NetHunter Terminal and run:

# Check root access
su -c id
# Should show: uid=0(root)

# Check NetHunter chroot
nethunter
# Should drop you into Kali shell

# Verify Kali
cat /etc/os-release
# Should show Kali Linux

# Check kernel features
nethunter -c "ls /lib/modules"
# Should list kernel modules

# Exit chroot
exit

6. Post-Install Setup

Update Kali Chroot

bash
# Enter NetHunter chroot
nethunter

# Update package lists and upgrade
apt update && apt full-upgrade -y

# Install additional tools
apt install -y seclists wordlists nmap sqlmap

# Clean up
apt autoremove -y && apt autoclean

# Exit chroot
exit
# Enter NetHunter chroot
nethunter

# Update package lists and upgrade
apt update && apt full-upgrade -y

# Install additional tools
apt install -y seclists wordlists nmap sqlmap

# Clean up
apt autoremove -y && apt autoclean

# Exit chroot
exit

Configure Kali Services

NetHunter includes a service manager. Open the NetHunter app and navigate to:

SSH Server

Enable to SSH into your phone from another machine. Change default credentials!

Apache + MySQL

Host phishing pages or run local web tools.

VNC Server

Get a full Kali desktop on your phone.

Bluetooth Arsenal

Bluetooth scanning and attacks.

External WiFi Adapter Setup

For packet injection and monitor mode, connect a supported external USB WiFi adapter via OTG:

bash
# Recommended adapters with NetHunter kernel support:
# - Alfa AWUS036ACH (RTL8812AU)
# - Alfa AWUS036NHA (Atheros AR9271)
# - Panda PAU05 (RT5372)

# Connect adapter via OTG cable, then:
nethunter

# Check interface
ip a
# Should show wlan1 or similar

# Enable monitor mode (using NetHunter app is easier)
airmon-ng check kill
airmon-ng start wlan1

# Verify monitor mode
iwconfig
# Should show wlan1mon in Monitor mode
# Recommended adapters with NetHunter kernel support:
# - Alfa AWUS036ACH (RTL8812AU)
# - Alfa AWUS036NHA (Atheros AR9271)
# - Panda PAU05 (RT5372)

# Connect adapter via OTG cable, then:
nethunter

# Check interface
ip a
# Should show wlan1 or similar

# Enable monitor mode (using NetHunter app is easier)
airmon-ng check kill
airmon-ng start wlan1

# Verify monitor mode
iwconfig
# Should show wlan1mon in Monitor mode

HID Attacks (Keyboard Emulation)

One of NetHunter's killer features — your phone acts as a USB keyboard to type payloads:

  1. Open NetHunter → HID Attacks
  2. Choose a payload (e.g., PowerShell reverse shell, Windows backdoor)
  3. Connect phone to target Windows PC via USB
  4. Tap Execute — phone types the payload automatically

Authorized Use Only

HID attacks are extremely powerful. Only use on systems you own or have explicit written permission to test.

7. Troubleshooting

OEM Unlock is grayed out

Connect to WiFi and leave the phone on for 7 days (Samsung's waiting period for new devices). If it's a carrier-locked phone, you may need to unlock from carrier first.

Heimdall doesn't detect device

bash
# Add udev rules for Samsung
sudo tee /etc/udev/rules.d/51-android.rules << 'EOF'
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", MODE="0666", GROUP="plugdev"
EOF

# Reload udev
sudo udevadm control --reload-rules
sudo udevadm trigger

# Reconnect device and try again
# Add udev rules for Samsung
sudo tee /etc/udev/rules.d/51-android.rules << 'EOF'
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", MODE="0666", GROUP="plugdev"
EOF

# Reload udev
sudo udevadm control --reload-rules
sudo udevadm trigger

# Reconnect device and try again

Boot loop after flashing

Boot back into TWRP (Vol Up + Bixby + Power) and:

  1. Go to Wipe → Format Data → type "yes"
  2. Re-flash Magisk and NetHunter in that order (add no-verity only if on stock Samsung, not LineageOS)
  3. If still failing, try an older NetHunter version

NetHunter app shows "Chroot not found"

The chroot may not have extracted. Open NetHunter → Kali Chroot Manager and tap Install Kali Chroot. Choose "Full" for all tools or "Minimal" for faster install.

WiFi injection not working

Your device may not have a custom kernel with injection patches. Check the NetHunter supported devices page. If not listed, you'll need NetHunter Lite with an external adapter.

8. Notes for Other Devices

OnePlus Devices

Excellent NetHunter support. Bootloader unlock is simpler (Settings → Developer Options → OEM Unlock is instant).

  • • Use fastboot instead of Heimdall
  • fastboot flash recovery twrp.img
  • • OnePlus 7/8/9 series have full kernel support

Google Pixel

Great development support but limited NetHunter kernel features.

  • • Use fastboot
  • fastboot flashing unlock
  • • NetHunter Lite recommended

Xiaomi Devices

Bootloader unlock requires Mi Unlock tool and a waiting period (72h-30d).

  • • Apply via Mi Unlock
  • • Some models have excellent kernel support
  • • Check XDA forums for device-specific guides

Snapdragon Samsung

US carrier variants often have locked bootloaders that cannot be unlocked.

  • • Check model: SM-G973U (locked) vs SM-G973U1 (unlocked)
  • • U1 (unlocked) variants may work
  • • Consider NetHunter Rootless as fallback

Best Devices for NetHunter (2026)

  • OnePlus 7 Pro / 7T / 8T — Best overall support, easy unlock, full features
  • Samsung Galaxy S10/S20 (Exynos) — Great hardware, HID support
  • Nexus 6P / Nexus 5 — Classic, well-tested, cheap on eBay
  • Xiaomi Poco F1 — Budget option with good support

Quick Reference Card

S10 Boot Key Combos

Download Mode Vol Up + Vol Down + USB cable
Recovery (TWRP) Vol Up + Bixby + Power (hold until logo)
Force Restart Vol Down + Power (hold 10+ sec)
Safe Mode Hold Vol Down during boot logo
bash
# === NETHUNTER QUICK COMMANDS ===

# Enter Kali chroot
nethunter

# Start Kali chroot with command
nethunter -c "nmap -sV 192.168.1.1"

# Start KeX (VNC desktop)
nethunter kex &

# Check root
su -c id

# WiFi monitor mode (in chroot)
airmon-ng start wlan1

# Packet capture
airodump-ng wlan1mon

# === HEIMDALL COMMANDS ===
heimdall detect
heimdall flash --RECOVERY twrp.img --no-reboot
heimdall print-pit  # Print partition table
# === NETHUNTER QUICK COMMANDS ===

# Enter Kali chroot
nethunter

# Start Kali chroot with command
nethunter -c "nmap -sV 192.168.1.1"

# Start KeX (VNC desktop)
nethunter kex &

# Check root
su -c id

# WiFi monitor mode (in chroot)
airmon-ng start wlan1

# Packet capture
airodump-ng wlan1mon

# === HEIMDALL COMMANDS ===
heimdall detect
heimdall flash --RECOVERY twrp.img --no-reboot
heimdall print-pit  # Print partition table