@echo off
setlocal EnableExtensions DisableDelayedExpansion
cd /d "%~dp0"

:MENU
cls
echo ============================================================
echo  NETWORK SCANNER (PING + ARP MAC LOOKUP)
echo ============================================================
echo.

set /p "CIDR=Enter CIDR (e.g. 192.168.1.0/24): "
if "%CIDR%"=="" goto MENU

set /p "TOUT=Timeout ms [100]: "
if "%TOUT%"=="" set "TOUT=100"

set /p "CNT=Pings [1]: "
if "%CNT%"=="" set "CNT=1"

REM ============================================================
REM PARSE & VALIDATE
REM ============================================================
for /f "tokens=1,2 delims=/" %%A in ("%CIDR%") do (
    set "IP_ADDR=%%A"
    set "PREFIX=%%B"
)

if "%PREFIX%"=="" set "PREFIX=24"

REM Trim spaces/CR from prefix
for /f "delims= " %%T in ("%PREFIX%") do set "PREFIX=%%T"

set /a PNUM=%PREFIX% 2>nul
if errorlevel 1 (
    echo [Error] Prefix must be numeric.
    pause
    goto MENU
)

if %PNUM% LSS 1 goto ERR_RANGE
if %PNUM% GTR 30 goto ERR_RANGE

for /f "tokens=1-4 delims=." %%A in ("%IP_ADDR%") do (
    set "O1=%%A"
    set "O2=%%B"
    set "O3=%%C"
    set "O4=%%D"
)

if "%O4%"=="" (
    echo [Error] Invalid IP format.
    pause
    goto MENU
)

for %%V in (%O1% %O2% %O3% %O4%) do (
    if %%V LSS 0 goto ERR_RANGE
    if %%V GTR 255 goto ERR_RANGE
)

REM ============================================================
REM CALCULATE MASK
REM ============================================================
set "M1=0"
set "M2=0"
set "M3=0"
set "M4=0"

if %PNUM% LEQ 8 (
    set /a "M1=256-(1<<(8-%PNUM%))"
    goto MASK_DONE
)

set "M1=255"

if %PNUM% LEQ 16 (
    set /a "M2=256-(1<<(16-%PNUM%))"
    goto MASK_DONE
)

set "M2=255"

if %PNUM% LEQ 24 (
    set /a "M3=256-(1<<(24-%PNUM%))"
    goto MASK_DONE
)

set "M3=255"
set /a "M4=256-(1<<(32-%PNUM%))"

:MASK_DONE

REM ============================================================
REM CALCULATE HOST COUNT
REM ============================================================
set /a "HOSTS=(1<<(32-%PNUM%))-2"

if "%HOSTS%"=="0" (
    echo [Error] No usable hosts in this range.
    pause
    goto MENU
)

REM ============================================================
REM CALCULATE NETWORK ADDRESS
REM ============================================================
set /a "N1=O1&M1"
set /a "N2=O2&M2"
set /a "N3=O3&M3"
set /a "N4=O4&M4"

set "NETWORK_ADDR=%N1%.%N2%.%N3%.%N4%/%PNUM%"

REM ============================================================
REM CALCULATE BROADCAST ADDRESS
REM Broadcast = Network + wildcard mask
REM Wildcard mask = 255 - subnet mask octet
REM ============================================================
set /a "B1=N1+(255-M1)"
set /a "B2=N2+(255-M2)"
set /a "B3=N3+(255-M3)"
set /a "B4=N4+(255-M4)"

set "BROADCAST_ADDR=%B1%.%B2%.%B3%.%B4%"

REM ============================================================
REM CALCULATE START IP
REM Start IP = Network + 1
REM ============================================================
set /a S1=N1
set /a S2=N2
set /a S3=N3
set /a S4=N4+1

if %S4% GTR 255 (
    set /a S4=0
    set /a S3+=1
)

if %S3% GTR 255 (
    set /a S3=0
    set /a S2+=1
)

if %S2% GTR 255 (
    set /a S2=0
    set /a S1+=1
)

set "START_IP=%S1%.%S2%.%S3%.%S4%"

REM ============================================================
REM CALCULATE END IP
REM End IP = Broadcast - 1
REM ============================================================
set /a E1=B1
set /a E2=B2
set /a E3=B3
set /a E4=B4-1

if %E4% LSS 0 (
    set /a E4=255
    set /a E3-=1
)

if %E3% LSS 0 (
    set /a E3=255
    set /a E2-=1
)

if %E2% LSS 0 (
    set /a E2=255
    set /a E1-=1
)

set "END_IP=%E1%.%E2%.%E3%.%E4%"

REM ============================================================
REM PREPARE OUTPUT FILE
REM ============================================================
set "SAFE_IP=%IP_ADDR:.=_%"
set "SAFE_IP=%SAFE_IP:/=_%"
set "OUT_FILE=ping_%SAFE_IP%_p%PNUM%.txt"

call :GET_TIME_CS START_CS
set "START_STAMP=%DATE% %TIME%"

echo.
echo Network:    %NETWORK_ADDR%
echo Start IP:   %START_IP%
echo End IP:     %END_IP%
echo Broadcast:  %BROADCAST_ADDR%
echo Hosts:      %HOSTS%
echo Timeout:    %TOUT% ms
echo Pings:      %CNT%
echo File:       %OUT_FILE%
echo.
echo Scanning... Please wait.
echo.

REM ============================================================
REM CREATE OUTPUT FILE ONLY IF IT DOES NOT EXIST
REM Existing file is used as an accumulated inventory.
REM ============================================================
if not exist "%OUT_FILE%" (
    > "%OUT_FILE%" (
        echo ============================================================
        echo NETWORK SCANNER ACCUMULATED REPORT
        echo ============================================================
        echo.
        echo File created;%START_STAMP%
        echo Initial CIDR;%CIDR%
        echo Initial network;%NETWORK_ADDR%
        echo Initial start IP;%START_IP%
        echo Initial end IP;%END_IP%
        echo Initial broadcast IP;%BROADCAST_ADDR%
        echo.
        echo ------------------------------------------------------------
        echo UNIQUE HOST PAIRS
        echo ------------------------------------------------------------
        echo IP;MAC
    )
)

REM ============================================================
REM INIT COUNTERS
REM Start scan exactly from calculated Start IP
REM ============================================================
set /a IP1=S1
set /a IP2=S2
set /a IP3=S3
set /a IP4=S4

set /a CUR=0
set /a ALIVE=0
set /a NEW_PAIRS=0
set /a SKIPPED_PAIRS=0
set /a PE=HOSTS/50

if %PE% LSS 1 set PE=1

REM ============================================================
REM SCAN LOOP
REM The loop scans HOSTS addresses only:
REM from Start IP to End IP.
REM Broadcast IP is not included.
REM ============================================================
setlocal EnableDelayedExpansion

for /L %%I in (1,1,%HOSTS%) do (
    set /a CUR+=1
    set "CIP=!IP1!.!IP2!.!IP3!.!IP4!"

    ping -n %CNT% -w %TOUT% !CIP! >nul 2>&1

    if !errorlevel! EQU 0 (
        set /a ALIVE+=1

        REM Clear MAC before every ARP lookup
        set "MAC="

        REM Search MAC address in local ARP table
        for /f "tokens=1,2" %%A in ('arp -a') do (
            if "%%A"=="!CIP!" set "MAC=%%B"
        )

        if not defined MAC set "MAC=UNKNOWN"

        set "PAIR=!CIP!;!MAC!"

        REM ====================================================
        REM CHECK IF EXACT IP;MAC PAIR ALREADY EXISTS
        REM If the pair exists, do not add it again.
        REM If the pair is new, append it to the accumulated file.
        REM ====================================================
        findstr /x /c:"!PAIR!" "%OUT_FILE%" >nul 2>&1

        if errorlevel 1 (
            echo(!PAIR!>> "%OUT_FILE%"
            set /a NEW_PAIRS+=1
            echo New:      !CIP! MAC: !MAC!
        ) else (
            set /a SKIPPED_PAIRS+=1
            echo Existing: !CIP! MAC: !MAC!
        )
    )

    set /a MOD=CUR%%PE

    if !MOD! EQU 0 (
        echo Progress: !CUR!/%HOSTS% Alive: !ALIVE! New: !NEW_PAIRS! Existing: !SKIPPED_PAIRS!
    )

    set /a IP4+=1

    if !IP4! GTR 255 (
        set /a IP4=0
        set /a IP3+=1
    )

    if !IP3! GTR 255 (
        set /a IP3=0
        set /a IP2+=1
    )

    if !IP2! GTR 255 (
        set /a IP2=0
        set /a IP1+=1
    )
)

REM Save counter values before leaving local delayed expansion scope
endlocal & (
    set "ALIVE=%ALIVE%"
    set "NEW_PAIRS=%NEW_PAIRS%"
    set "SKIPPED_PAIRS=%SKIPPED_PAIRS%"
)

REM ============================================================
REM CALCULATE SCAN TIME
REM ============================================================
call :GET_TIME_CS END_CS
set "END_STAMP=%DATE% %TIME%"

set /a ELAPSED_CS=END_CS-START_CS

REM Midnight rollover protection
if %ELAPSED_CS% LSS 0 set /a ELAPSED_CS+=24*60*60*100

call :FORMAT_ELAPSED %ELAPSED_CS% ELAPSED_TEXT

REM ============================================================
REM WRITE SCAN HISTORY ENTRY
REM ============================================================
>> "%OUT_FILE%" (
    echo.
    echo ------------------------------------------------------------
    echo SCAN HISTORY ENTRY
    echo ------------------------------------------------------------
    echo Started;%START_STAMP%
    echo Finished;%END_STAMP%
    echo Elapsed;%ELAPSED_TEXT%
    echo Input CIDR;%CIDR%
    echo Network;%NETWORK_ADDR%
    echo Start IP;%START_IP%
    echo End IP;%END_IP%
    echo Broadcast IP;%BROADCAST_ADDR%
    echo Broadcast excluded;Yes
    echo Timeout ms;%TOUT%
    echo Pings;%CNT%
    echo Hosts scanned;%HOSTS%
    echo Alive hosts in this scan;%ALIVE%
    echo New IP-MAC pairs added;%NEW_PAIRS%
    echo Existing IP-MAC pairs skipped;%SKIPPED_PAIRS%
    echo ============================================================
)

echo.
echo ============================================================
echo Complete!
echo Network:                 %NETWORK_ADDR%
echo Start IP:                %START_IP%
echo End IP:                  %END_IP%
echo Broadcast:               %BROADCAST_ADDR%
echo Alive hosts this scan:   %ALIVE%
echo New IP-MAC pairs added:  %NEW_PAIRS%
echo Existing pairs skipped:  %SKIPPED_PAIRS%
echo Time:                    %ELAPSED_TEXT%
echo Results:                 %OUT_FILE%
echo ============================================================
pause
goto MENU

REM ============================================================
REM ERROR HANDLER
REM ============================================================
:ERR_RANGE
echo [Error] Value out of range. Prefix must be 1-30, octets must be 0-255.
pause
goto MENU

REM ============================================================
REM SUBROUTINE: GET CURRENT TIME IN CENTISECONDS
REM ============================================================
:GET_TIME_CS
set "T=%TIME: =0%"

for /f "tokens=1-4 delims=:., " %%A in ("%T%") do (
    set /a "%~1=((1%%A-100)*360000)+((1%%B-100)*6000)+((1%%C-100)*100)+(1%%D-100)"
)

exit /b

REM ============================================================
REM SUBROUTINE: FORMAT CENTISECONDS AS HH:MM:SS.CC
REM ============================================================
:FORMAT_ELAPSED
set /a _CS=%~1
set /a _H=_CS/360000
set /a _CS=_CS%%360000
set /a _M=_CS/6000
set /a _CS=_CS%%6000
set /a _S=_CS/100
set /a _C=_CS%%100

if %_H% LSS 10 set "_H=0%_H%"
if %_M% LSS 10 set "_M=0%_M%"
if %_S% LSS 10 set "_S=0%_S%"
if %_C% LSS 10 set "_C=0%_C%"

set "%~2=%_H%:%_M%:%_S%.%_C%"

exit /b