Intermediate
Last verified: March 2026

Kali NetHunter Installation Guide

Install full Kali NetHunter on an unlockable Android device with a Samsung Galaxy S10 Exynos path as the primary example. This refresh prioritizes version checks, device fit, and recovery checkpoints so you can stop early if your hardware or firmware path is wrong.

Read This First

  • This will wipe your device. Back up everything first.
  • Samsung Galaxy S10-family NetHunter builds currently require a matching LineageOS-based path, not stock Samsung firmware.
  • Exynos Samsung models are the intended target. Many Snapdragon carrier variants still cannot be unlocked reliably.
  • Use a Linux machine for flashing, a known-good data cable, and a direct motherboard USB port instead of a hub or dock.
  • Set aside 2-4 hours, 4+ GB of downloads, and at least 60% battery on both the phone and laptop.
  • Unlocking the bootloader permanently trips Knox and may disable Samsung Pay, Secure Folder, and some enterprise controls.

Always Verify Latest Versions

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

Freshness Rule for This Page

Re-check the official LineageOS device page, the official NetHunter mobile downloads page, and the TWRP device page every time you reuse this guide. If any filename, Android generation, or recovery instruction differs from what is shown here, trust upstream and adjust the flow before flashing.

Use This Guide

You have an unlockable device, you are comfortable wiping it, and you want full NetHunter with root and recovery-based flashing.

Choose Lite Instead

Your phone can be rooted but does not have a practical custom-kernel path. You still want the app, Kali container, and post-exploitation tooling.

Choose Rootless Instead

Your bootloader is locked, your model is a carrier Snapdragon Samsung, or you only need portable Kali tooling without kernel features.

Pre-Flight Checks

  1. Confirm your exact model number in Settings → About Phone, then map it to the correct codename before downloading anything.
  2. Confirm your NetHunter filename and your LineageOS build target the same Android generation.
  3. Test adb devices and heimdall detect before you start flashing so cable and driver problems show up early.
  4. Download everything to local disk first. Do not depend on live mirror scraping while the phone is already in recovery mode.
  5. Keep a manual recovery path ready: official LineageOS install page, official NetHunter download page, and a fallback flashing machine if Heimdall misbehaves.

1. Prerequisites & Downloads

Supported Devices

NetHunter has three practical installation paths. Pick the one that matches your hardware reality before you collect files:

NetHunter (Full)

Requires an unlockable device and an officially supported kernel path. This is the only route that gives you the full HID, BadUSB, and wireless feature set.

Use this page only if this is your target state.

NetHunter Rootless

No root required. Runs in Termux and avoids bootloader, recovery, and firmware changes.

Best fallback when your bootloader path is blocked.

NetHunter Lite

Rooted device with no custom-kernel dependency. App + Kali container, but not the full hardware feature set.

Useful when full NetHunter support is incomplete for your model.

This Guide Covers Full NetHunter

This walkthrough stays focused on full NetHunter for a Samsung Galaxy S10 Exynos (SM-G973F / beyond1lte). If your exact model differs, keep the flow but replace every download with the files for your codename.

LineageOS Required for Samsung Devices

Current Samsung S10-family NetHunter downloads are published against a LineageOS-based Android 16 path, not stock Samsung firmware. Do not rely on old screenshots, older ROM names, or a generic "latest" assumption. Verify the exact Android generation encoded in the NetHunter filename and match it to the LineageOS device page for your codename before flashing.

Expected Time and Failure Points

Most failed installs happen before flashing starts: wrong model, bad cable, unstable USB hub, or mismatched ROM/NetHunter version. Plan for 30-60 minutes of downloads and preparation, 15-30 minutes of flashing, and up to 15 minutes for the first boot.

What You'll Need

Hardware

  • ☑️ Samsung Galaxy S10 (SM-G973F/Exynos) — or your supported device
  • ☑️ Short, known-good USB-C data cable connected directly to the computer
  • ☑️ Linux PC/laptop (Kali Linux recommended)
  • ☑️ microSD card (optional, for storing images/wordlists)
  • ☑️ USB OTG adapter (for external WiFi adapters later)

Cable Quality Matters

Samsung flashing issues are often just transport issues. If ADB drops, Heimdall stalls, or the phone disconnects when entering Download Mode, swap the cable first and move to a direct USB port before troubleshooting software.

Software Downloads

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

File Source Notes
Odin (Linux: Heimdall) GitHub Use Heimdall on Linux first; keep a fallback flashing option ready if USB detection stays unreliable
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)
Optional Helper Script for Samsung Galaxy Devices

Use this only as a convenience layer after you understand the manual path. It can save time, but vendor mirrors, API responses, and file naming conventions change regularly, so you still need to verify every file yourself.

Best use: download files to disk before the phone is in recovery. If any URL lookup fails, fall back to the official download links above instead of forcing the script to work.

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 on the Device

  1. From the Download Mode warning screen, long-press Volume Up to open the bootloader unlock prompt.
  2. Read the warning carefully, then confirm the unlock on the phone itself.
  3. Wait for the phone to wipe and reboot automatically.
  4. Complete basic Android setup again, then return to Developer Options and re-enable USB Debugging.

Do Not Use Heimdall to Unlock

Heimdall is for flashing partitions, not for initiating Samsung bootloader unlock. The unlock confirmation happens on the device in Download Mode and usually triggers a factory reset immediately.

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 can leave you with an unbootable device or a recovery loop. Check your model number in Settings → About Phone and stop if your download filenames do not match it.

If Heimdall Is Unstable

Test heimdall detect before you attempt a flash. If the device disconnects repeatedly, change cable, change USB port, reboot both devices, and only then troubleshoot packages or drivers. Do not keep retrying a flaky flash path with the same hardware setup.

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, fails to mount /data, or shows internal storage as 0 MB, Android file-based encryption is still in the way. Go to Wipe → Format Data and type yes. This is different from Advanced Wipe: it fully recreates the data partition metadata so recovery can mount storage correctly.

4. Flash NetHunter

Step 4.1: Copy Files to Device

LineageOS Must Be Flashed First

If you're using a LineageOS-based NetHunter image, flash LineageOS in TWRP before Magisk and NetHunter. On current Samsung S10-family downloads, assume this is the required order unless the official NetHunter filename and notes say otherwise. See LineageOS install guide for beyond1lte.

Recommended Flash Order

  1. Flash LineageOS if your NetHunter build requires it.
  2. If recovery cannot mount storage afterward, format data and reboot back into recovery once.
  3. Flash Magisk.
  4. Flash NetHunter.
  5. Flash no-verity only when you are intentionally staying on stock Samsung firmware.
  6. Wipe Dalvik/ART cache, then reboot system and wait through the long first boot.

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.

Healthy First Boot

Boot animation runs for several minutes, then Android setup appears and Magisk plus NetHunter apps are present.

Recovery Loop

Usually points to a bad recovery handoff, storage mount issue, or an incomplete ROM flash.

Samsung Logo Loop

Usually means version mismatch, encryption not cleared, or the wrong flash order. Go back to recovery instead of repeatedly hard rebooting.

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

First eliminate transport issues: swap the cable, move to a direct USB port, disconnect hubs, and boot the phone fully back into Download Mode before changing packages.

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

Samsung boots back to stock recovery

That usually means Android booted once before you entered TWRP. Re-flash recovery, then boot directly into TWRP using the hardware key combo without allowing a normal Android boot in between.

Boot loop after flashing

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

  1. Go to Wipe → Format Data → type "yes"
  2. Verify your ROM and NetHunter filenames target the same Android generation, then re-flash in the correct order
  3. Re-flash Magisk and NetHunter in that order (add no-verity only if on stock Samsung, not LineageOS)
  4. If still failing, return to the official LineageOS install guide for a clean ROM flash before retrying NetHunter

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.

Magisk opens as a stub or root prompts never appear

Launch Magisk once after first boot and allow it to complete any in-app environment setup it requests. If root is still missing, return to recovery and re-flash the current Magisk APK before troubleshooting NetHunter itself.

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

Use Vendor-Specific Unlock Paths

The overall logic stays the same on other devices, but bootloader unlock, recovery flashing, and anti-rollback behavior vary by vendor. Treat the sections below as direction, not as a substitute for the official recovery and ROM instructions for your exact model.

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