#!/bin/bash

#
# Remove NMI vector from AVM image files
# Author: Alexander Kriegisch (http://scrum-master.de) and Freetz developer team
#
# NMI vector v1:
#  1. starts with byte sequence 3C1A8000375A038003400008
#  2. located at (fixed) offset 0xBE0000
#  3. has (fixed) length of 256 bytes
#  4. NMI vector size is saved in 4 bytes at (fixed) offset 0xBE0044,
#     the same offset could also be determined by locating the string "NMI Boot"
#     and subtracting 4 bytes from its offset
#  5. image size without the NMI vector and without the image checksum is saved
#     in 4 bytes at (fixed) offset 0xBE0040,
#     the same offset could also be determined by locating the string "NMI Boot"
#     and subtracting 8 bytes from its offset
#
# NMI vector v2:
#  1. starts with byte sequence 409AE8053C1A8000375A038003400008 (v1 sequence prefixed with 409AE805)
#  2. same as v1
#  3. has (fixed) length of 4096 bytes
#  4. same as v1
#  5. same as v1
#
# NMI vector v3:
#  1. starts with byte sequence 409AE805409BE0043C1A8000375A0380
#  2. same as v1
#  3. same as v2
#  4. same as v1
#  5. same as v1
#

source "$(dirname $(readlink -f ${0}))/freetz_bin_functions"

INPUT_FILE="$1"
OUTPUT_FILE="$2"

if [ $# -ne 2 -o ! -e "$INPUT_FILE" ]; then
	echo "Usage: ${0##*/} input.image output.image"
	exit 2
fi

# see linux-${KERNEL_VERSION}/arch/mips/fusiv/fusiv_mips32/fusiv_mtd.c (7390) for details
NMI_BOOT_STRING="NMI Boot Vector"
NMI_BOOT_PATTERN="4E4D4920426F6F7420566563746F7200" # the string above (zero-terminated) as sfk-bin-pattern
expectedNmiBootOffset=0xBE0048

nmiVectorOffset=0xBE0000
declare -A nmiVectorKnownStartSequence=(
		[v1]=3C1A8000375A038003400008
	[v2]=409AE8053C1A8000375A038003400008
	[v3]=409AE805409BE0043C1A8000375A0380
)

getInputFileSize() {
	stat -L -c "%s" "$INPUT_FILE"
}


declare -a nmiBootOffsets=($(getHexOffsetsOfAllMatches "$INPUT_FILE" bin "${NMI_BOOT_PATTERN}"))

if [ ${#nmiBootOffsets[@]} -eq 0 ]; then
	echo "No NMI vector found" >&2
	exit 1
fi

if [ ${#nmiBootOffsets[@]} -gt 1 ]; then
	echo "[Error] Multiple '${NMI_BOOT_STRING}'-matches found: ${nmiBootOffsets[@]}" >&2
	exit 2
fi

nmiBootOffset="${nmiBootOffsets[0]}"
if [ "${nmiBootOffset}" != "${expectedNmiBootOffset}" ]; then
	echo "[Error] '${NMI_BOOT_STRING}'-match found at unexpected offset ${nmiBootOffset}" >&2
	exit 2
fi

# NMI vector length is encoded within the NMI block 4 bytes before 'NMI Boot'
nmiVectorLength="0x$(getHexContentAtOffset "$INPUT_FILE" $((${nmiBootOffset} - 4)) 4)"
if [ $((${nmiVectorLength})) -ne 256 -a $((${nmiVectorLength})) -ne 4096 ]; then
	echo "[Error] Unexpected/unsupported NMI vector length: $((${nmiVectorLength}))" >&2
	exit 2
fi

# check if the image contains the TI-checksum at the end
TI_CHECKSUM_MAGIC_BE="23DE53C4"
if [ "$(getHexContentAtOffset "$INPUT_FILE" $(($(getInputFileSize) - 8)) 4)" == "${TI_CHECKSUM_MAGIC_BE}" ]; then
	# TI checksum found
	imageChecksumSize=8
else
	# no TI checksum found
	imageChecksumSize=0
fi

# real image size is encoded within the NMI block 8 bytes before 'NMI Boot'
imageSizeWithinNMI="0x$(getHexContentAtOffset "$INPUT_FILE" $((${nmiBootOffset} - 8)) 4)"

if [ "$((${imageSizeWithinNMI}))" -gt "$((${nmiVectorOffset}))" ]; then
	# NMI boot block resides within the (filesystem) image, typically Fritz!Box
	# kernel.image | filesystem.image part1 | NMI block | filesystem.image part2
	imageSizeWithinNMIPadded256=$(( ${imageSizeWithinNMI} + (256 - ${imageSizeWithinNMI} % 256) % 256 ))
	imageSizeExpected=$(printf "0x%08X" $(( $(getInputFileSize) - ${nmiVectorLength} - ${imageChecksumSize} )))
	if [ "$((${imageSizeWithinNMI}))" -ne "$((${imageSizeExpected}))" -a "$((${imageSizeWithinNMIPadded256}))" -ne "$((${imageSizeExpected}))" ]; then
		echo "[Error] Image size encoded in NMI block ($((${imageSizeWithinNMI}))) doesn't match calculated size ($((${imageSizeExpected})))" >&2
		exit 2
	fi
else
	# NMI boot block resides after the (filesystem) image, typically Fritz!WLAN Repeater / Fritz!Powerline Adapter
	# kernel.image | filesystem.image | gap padding bytes | NMI block
	fileSize=$(getInputFileSize)
	fileSizeExpected=$(printf "0x%08X" $(( ${nmiVectorOffset} + ${nmiVectorLength} + ${imageChecksumSize} )))
	if [ "$((${fileSize}))" -ne "$((${fileSizeExpected}))" ]; then
		echo "[Error] File size ($((${fileSize}))) doesn't match the expected one ($((${fileSizeExpected})))" >&2
		exit 2
	fi
fi

# get first 32 bytes of the NMI vector
nmiVectorStartSequence=$(getHexContentAtOffset "$INPUT_FILE" ${nmiVectorOffset} 32)
# and check if they contain one of the known sequences
for v in v1 v2 v3; do
	if [ "${nmiVectorStartSequence:0:${#nmiVectorKnownStartSequence[${v}]}}" != "${nmiVectorKnownStartSequence[${v}]}" ]; then
		continue
	fi

	echo -n "NMI vector ${v} found at offset ${nmiVectorOffset}, removing it ... "
	{
		head -c "$((${nmiVectorOffset}))" "$INPUT_FILE"
		tail -c "+$((${nmiVectorOffset} + ${nmiVectorLength} + 1))" "$INPUT_FILE"
	} | head -c "$((${imageSizeWithinNMI}))" > "$OUTPUT_FILE"
	echo "done."

	# debug: dump NMI vector to a separate file
	#tail -c "+$((${nmiVectorOffset} + 1))" "$INPUT_FILE" | head -c $((${nmiVectorLength})) > "$INPUT_FILE".nmi-vector

	exit 0
done

echo "[Error] NMI vector start sequence (${nmiVectorStartSequence}) doesn't match any of the known sequences" >&2
exit 2
