#!/usr/bin/env sh

# SPDX-FileCopyrightText: 2022 Caleb La Grange <thonkpeasant@protonmail.com>
# SPDX-FileCopyrightText: 2022 Ferass El Hafidi <vitali64pmemail@protonmail.com>
# SPDX-FileCopyrightText: 2023 Leah Rowe <info@minifree.org>
# SPDX-License-Identifier: GPL-3.0-only

ec_url=""
ec_url_bkup=""
ec_hash=""

blobdir="blobs"
dl_path="${blobdir}/vendorupdate"
appdir="${blobdir}/app"
_7ztest="a"
mecleaner="$(pwd)/me_cleaner/me_cleaner.py"
me7updateparser="$(pwd)/resources/blobs/me7_update_parser.py"
kbc1126_ec_dump="$(pwd)/coreboot/default/util/kbc1126/kbc1126_ec_dump"
board="${1}"
# A shorthand for each board, to avoid duplicating configs per flash size
board_short=${board%%_*mb}

set -- "resources/coreboot/${board}/config/*"
. ${1} 2>/dev/null
. "resources/coreboot/${board}/board.cfg"

if [ "${CONFIG_HAVE_MRC}" = "y" ]; then
	printf 'haswell board detected, downloading mrc\n'
	needs="${needs} MRC"
fi

if [ "${CONFIG_HAVE_IFD_BIN}" = "y" ]; then
	printf 'board needs intel firmware descriptor\n'
	needs="${needs} IFD"
fi

if [ "${CONFIG_HAVE_ME_BIN}" = "y" ]; then
	printf 'board needs intel management engine\n'
	needs="${needs} ME"
fi

if [ "${CONFIG_HAVE_GBE_BIN}" = "y" ]; then
	printf 'board needs gigabit ethernet firmware\n'
	needs="${needs} GBE"
fi

if [ "${CONFIG_KBC1126_FIRMWARE}" = "y" ]; then
	printf "HP board with KBC1126 EC detected, downloading ec\n"
	needs="${needs} EC"
fi

# Quickly exit without wasting more time if there are no blobs needed (GM45)
if [ -z ${needs+x} ]; then
	printf 'No binary blobs needed for this board\n'
	exit 0
fi

while read -r line ; do
	case ${line} in
		EC_url*)
		set ${line}
		ec_url=${2}
		;;
		EC_url_bkup*)
		set ${line}
		ec_url_bkup=${2}
		;;
		EC_hash*)
		set ${line}
		ec_hash=${2}
		;;
		DL_hash*)
		set ${line}
		dl_hash=${2}
		;;
		DL_url*)
		set ${line}
		dl_url=${2}
		;;
		DL_url_bkup*)
		set ${line}
		dl_url_bkup=${2}
		;;
	esac
done << EOF
$(eval "awk ' /\{.*${board_short}.*}{/ {flag=1;next} /\}/{flag=0} flag { print }' resources/blobs/sources")
EOF

Main() {
	Build_deps
	Download_needed
}

Fail(){
	printf "\nERROR: $@\n"
	exit 1
}

Build_deps(){
	if [ ! -d me_cleaner ]; then
		printf "downloading me_cleaner\n"
		./download me_cleaner || Fail 'could not download me_cleaner'
	fi

	if [ ! -d coreboot/default ]; then
		printf "downloading coreboot\n"
		./download coreboot default || Fail 'could not download coreboot'
	fi

	if [ ! -f coreboot/default/util/kbc1126/kbc1126_ec_dump ]; then
		printf "Building kbc1126_ec_dump from coreboot\n"
		make -BC coreboot/default/util/kbc1126 || Fail \
			"could not build kbc1126_ec_dump"
	fi
	
	if [ ! -f "coreboot/default/util/ifdtool/ifdtool" ]; then
		printf "building ifdtool from coreboot\n"
		make -C coreboot/default/util/ifdtool || Fail 'could not build ifdtool'
	fi
}

Download_needed(){
	for need in ${needs}; do
		case ${need} in
			*ME*)
				Download_me || _failed="${_failed} me"
				;;
			*MRC*)
				./download mrc || _failed="${_failed} mrc"
				;;
			*EC*)
				Download_ec || _failed="${_failed} ec"
				;;
	esac
	done
	
	if [ ! -z ${_failed+x} ]; then
	Fail "failed to obtain ${_failed}\nYou may try manually extracting blobs with './blobutil extract'"
	fi
}

Download_me() {
	printf "Downloading neutered ME for board: %s\n" ${board}

	Fetch_update me || return 1
	Extract_me || return 1

	return 0
}

Extract_me(){
	printf "Extracting neutered ME for ${board}\n"

	_me_destination=${CONFIG_ME_BIN_PATH#../../}

	if [ ! -d "${_me_destination%/*}" ]; then
		mkdir -p ${_me_destination%/*}
	fi
	
	if [ -d "${appdir}" ]; then
		rm -r ${appdir}
	fi

	if [ -f "${_me_destination}" ]; then
		printf 'me already downloaded\n'
		return 0
	fi

	printf 'extracting and stripping intel management engine\n'
	innoextract ${dl_path} -d ${blobdir} \
		|| 7z x ${dl_path} -o${appdir} \
		|| Fail 'could not extract me executable with innoextract' 

	Bruteforce_extract_me "$(pwd)/${_me_destination}" "$(pwd)/${appdir}" \
		|| return 1

	printf "Truncated and cleaned me output to ${_me_destination}\n"
	return 0
}

# cursed, carcinogenic code. TODO rewrite it better
Bruteforce_extract_me() {
	_me_destination="${1}"
	cdir="${2}" # must be an absolute path, not relative

	if [ -f "${_me_destination}" ]; then
		return 0
	fi

	sdir="$(mktemp -d)"
	mkdir -p "${sdir}" || return 1

	(
	printf "Entering %s\n" "${cdir}"
	cd "${cdir}" || exit 1
	for i in *; do
		if [ -f "${_me_destination}" ]; then
			# me.bin found, so avoid needless further traversal
			break
		elif [ -L "${i}" ]; then
			# symlinks are a security risk, in this context
			continue
		elif [ -f "${i}" ]; then
			"${mecleaner}" -r -t -O "${sdir}/vendorfile" -M "${_me_destination}" "${i}" \
				&& break # (we found me.bin)	
			"${mecleaner}" -r -t -O "${_me_destination}" "${i}" \
				&& break # (we found me.bin)
			"${me7updateparser}" -O ${_me_destination} "${i}" \
				&& break
			_7ztest="${_7ztest}a"
			7z x "${i}" -o${_7ztest} || continue
			Bruteforce_extract_me "${_me_destination}" "${cdir}/${_7ztest}"
			cdir="${1}"
			cd "${cdir}"
		elif [ -d "$i" ]; then
			Bruteforce_extract_me "${_me_destination}" "${cdir}/${i}"
			cdir="${1}"
			cd "${cdir}"
		else
			printf "SKIPPING: %s\n" "${i}"
		fi
	done
	)

	rm -Rf "${sdir}"

	if [ ! -f "${_me_destination}" ]; then
		printf "me.bin not found in vendor update for board: %s\n" ${board}
		return 1
	else
		return 0
	fi
}

Download_ec() {
	printf "Downloading KBC1126 EC firmware for HP laptop\n"

	Fetch_update ec || return 1
	Extract_ec || return 1

	return 0
}

Extract_ec() {
	printf "Extracting KBC1126 EC firmware for board: %s\n" ${board}

	_ec_destination=${CONFIG_KBC1126_FW1#../../}

	if [ ! -d "${_ec_destination%/*}" ]; then
		mkdir -p "${_ec_destination%/*}"
	fi

	if [ -d "${appdir}" ]; then
		rm -Rf "${appdir}"
	fi

	if [ -f "${_ec_destination}" ]; then
		printf "ec already downloaded\n"
		return 0
	fi

	unar "${dl_path}" -o "${appdir}"

	(
	cd "${appdir}/${dl_path##*/}"

	mv Rompaq/68*.BIN ec.bin
	if [ ! -f ec.bin ]; then
		unar -D ROM.CAB Rom.bin
		mv Rom.bin ec.bin
	fi

	"${kbc1126_ec_dump}" ec.bin
	)

	for i in 1 2; do
		if [ ! -f "${appdir}/${dl_path##*/}/ec.bin.fw${i}" ]; then
			printf "Not found: %s/%s/ec.bin.fw%s\n" \
				${appdir} ${dl_path##*/} ${i}
			printf "Could not extract EC firmware for board: %s\n" \
				${board}
			return 1
		fi
	done

	cp "${appdir}/${dl_path##*/}"/ec.bin.fw* "${_ec_destination%/*}/"
}

Fetch_update() {
	printf "Fetching vendor update for board: %s\n" ${board}

	fw_type="${1}"
	dl=""
	dl_bkup=""
	dlsum=""
	if [ "${fw_type}" = "me" ]; then
		dl=${dl_url}
		dl_bkup=${dl_url_bkup}
		dlsum=${dl_hash}
	elif [ "${fw_type}" = "ec" ]; then
		dl=${ec_url}
		dl_bkup=${ec_url_bkup}
		dlsum=${ec_hash}
	else
		printf "Unsupported download type: %s\n" ${fw_type}
		return 1
	fi

	if [ -z "${dl_url+x}" ]; then
		printf "No vendor update specified for board: %s\n" ${board}
		return 1
	fi

	Vendor_checksum ${dlsum} || \
		curl ${dl} > ${dl_path} || curl ${dl_bkup} > ${dl_path}

	Vendor_checksum ${dlsum} || Fail \
		"Cannot guarantee intergity of vendor update for board: ${board}"

	return 0
}

Vendor_checksum() {
	sha1=$1

	if [ ! -f "${dl_path}" ]; then
		printf "Vendor update not found on disk for board: %s\n" ${board}
		return 1
	fi
	if [ "$(sha1sum ${dl_path} | awk '{print $1}')" != "${sha1}" ]; then
		printf "Bad checksum on vendor update for board: %s\n" ${board}
		rm ${dl_path}
		return 1
	fi
	return 0
}

Main