#!/usr/bin/env sh
# SPDX-License-Identifier: GPL-3.0-only
# SPDX-FileCopyrightText: 2022 Caleb La Grange <thonkpeasant@protonmail.com>
# SPDX-FileCopyrightText: 2022 Ferass El Hafidi <vitali64pmemail@protonmail.com>
# SPDX-FileCopyrightText: 2023 Leah Rowe <leah@libreboot.org>

. "include/err.sh"
. "include/defconfig.sh"
. "include/blobutil.sh"

main()
{
	[ $# -gt 0 ] || \
		err "No argument given"

	board="${1}"
	boarddir="${cbcfgsdir}/${board}"

	check_defconfig "${boarddir}" || exit 0
	detect_firmware && exit 0
	scan_sources_config

	build_dependencies
	download_blobs
}

detect_firmware()
{
	set -- "${boarddir}/config/"*
	. "${1}" 2>/dev/null

	for c in CONFIG_HAVE_MRC CONFIG_HAVE_ME_BIN CONFIG_KBC1126_FIRMWARE \
	    CONFIG_VGA_BIOS_FILE CONFIG_INCLUDE_SMSC_SCH5545_EC_FW; do
		eval "[ -z \"\${${c}}\" ] || return 1"
	done
	printf "Blobs not needed for: %s\n" "${board}" 1>&2
}

scan_sources_config()
{
	_b=${board%%_*mb} # shorthand to avoid duplicating config per rom size
	awkstr=" /\{.*${_b}.*}{/ {flag=1;next} /\}/{flag=0} flag { print }"
	while read -r line ; do
		set ${line} 1>/dev/null 2>/dev/null
		eval "${1}=\"${2}\""
	done << EOF
	$(eval "awk '${awkstr}' config/blobs/sources")
EOF
}

build_dependencies()
{
	[ -d ${cbdir} ] || \
		./update project trees coreboot ${cbdir##*/} || \
		    err "build_dependencies: can't fetch ${cbdir}"
	for d in uefitool biosutilities bios_extract me_cleaner; do
		[ -d "${d}" ] && continue
		./update project repo "${d}" || \
		    err "build_dependencies: can't fetch ${d}"
	done
	[ -f uefitool/uefiextract ] || \
		./handle make file -b uefitool || \
		    err "build_dependencies: can't build uefitool"
	if [ ! -f "${cbdir}/util/kbc1126/kbc1126_ec_dump" ]; then
		make -C "${cbdir}/util/kbc1126" || \
		    err "build_dependencies: can't build kbc1126_ec_dump"
	fi
}

download_blobs()
{
	[ -z "${CONFIG_HAVE_ME_BIN}" ] || \
		download_blob_intel_me || err "download_blobs ${board}: !me"
	[ -z "${CONFIG_INCLUDE_SMSC_SCH5545_EC_FW}" ] || \
		download_sch5545ec || err "download_blobs ${board}: !sch5545"
	[ -z "${CONFIG_KBC1126_FIRMWARE}" ] || \
		download_ec || err "download_blobs ${board}: kbc1126"
	[ -z "${CONFIG_VGA_BIOS_FILE}" ] || \
		download_e6400vga || err "download_blobs ${board}: !e6400vga"
	if [ ! -z "${CONFIG_HAVE_MRC}" ]; then
		./update blobs mrc || err "download_blobs ${board}: !mrc"
	fi
}

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

	fetch_update "${DL_url}" "${DL_url_bkup}" "${DL_hash}" || return 1
	extract_blob_intel_me || return 1
}

extract_blob_intel_me()
{
	printf "Extracting neutered ME for %s\n" "${board}"

	_me_destination=${CONFIG_ME_BIN_PATH#../../}
	mkdirs "${_me_destination}" "extract_blob_intel_me" || return 0
	bruteforce_extract_blob_intel_me "$(pwd)/${_me_destination}" \
	    "$(pwd)/${appdir}" || \
	    err "extract_blob_intel_me: could not extract Intel ME firmware"

	[ -f "${_me_destination}" ] || \
		err "extract_blob_intel_me, ${board}: me.bin missing"

	printf "Truncated and cleaned me output to: %s\n" "${_me_destination}"
}

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

	[ -f "${_me_destination}" ] && return 0

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

	(
	printf "Entering %s\n" "${cdir}"
	cd "${cdir}" || \
	    err "bruteforce_extract_blob_intel_me: can't cd \"${cdir}\""
	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 # (we found me.bin)
			_7ztest="${_7ztest}a"
			extract_archive "${i}" "${_7ztest}" || continue
			bruteforce_extract_blob_intel_me "${_me_destination}" \
			    "${cdir}/${_7ztest}"
		elif [ -d "$i" ]; then
			bruteforce_extract_blob_intel_me "${_me_destination}" \
			    "${cdir}/${i}"
		else
			printf "SKIPPING: %s\n" "${i}"
			continue
		fi
		cdir="${1}"
		cd "${cdir}" # audit note: we already checked this (see above)
	done
	)

	rm -Rf "${sdir}" || \
	    err "bruteforce_extract_blob_intel_me: can't rm -Rf \"${sdir}\""
}

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

	fetch_update "${EC_url}" "${EC_url_bkup}" "${EC_hash}" || return 1
	extract_blob_kbc1126_ec || return 1
}

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

	_ec_destination=${CONFIG_KBC1126_FW1#../../}
	mkdirs "${_ec_destination}" "extract_blob_kbc1126_ec" || return 0
	(
	cd "${appdir}/" || \
	    err "extract_blob_kbc1126_ec: !cd \"${appdir}/\""

	mv Rompaq/68*.BIN ec.bin || :
	if [ ! -f ec.bin ]; then
		unar -D ROM.CAB Rom.bin || \
		    unar -D Rom.CAB Rom.bin || \
		    unar -D 68*.CAB Rom.bin || \
		    err "extract_blob_kbc1126_ec: can't extract ec.bin"
		mv Rom.bin ec.bin || \
		    err "extract_blob_kbc1126_ec: *didn't* extract ec.bin"
	fi
	[ -f ec.bin ] || \
	    err "extract_blob_kbc1126_ec: ${board}: can't extract ec.bin"

	"${kbc1126_ec_dump}" ec.bin || \
	    err "extract_blob_kbc1126_ec: ${board}: can't extract ecfw1/2.bin"
	)

	ec_ex="y"
	for i in 1 2; do
		[ -f "${appdir}/ec.bin.fw${i}" ] || ec_ex="n"
	done
	[ "${ec_ex}" = "y" ] || \
	    err "extract_blob_kbc1126_ec: ${board}: didn't extract ecfw1/2.bin"

	cp "${appdir}/"ec.bin.fw* "${_ec_destination%/*}/" || \
	    err "extract_blob_kbc1126_ec: cant mv ecfw1/2 ${_ec_destination%/*}"
}

download_e6400vga()
{
	printf "Downloading Nvidia VGA ROM for Dell Latitude E6400\n"

	fetch_update "${E6400_VGA_DL_url}" "${E6400_VGA_DL_url_bkup}" \
	    "${E6400_VGA_DL_hash}" || return 1
	extract_e6400vga || return 1
}

extract_e6400vga()
{
	printf "Extracting Nvidia VGA ROM for ${board}\n"

	_vga_destination=${CONFIG_VGA_BIOS_FILE#../../}
	mkdirs "${_vga_destination}" "extract_e6400vga" || return 0

	[ "${E6400_VGA_offset}" = "" ] && \
		err "extract_e6400vga: E6400 VGA offset not defined"
	[ "${E6400_VGA_romname}" = "" ] && \
		err "extract_e6400vga: E6400 VGA ROM name not defined"

	tail -c +${E6400_VGA_offset} "${dl_path}" | \
	    gunzip >"${appdir}/bios.bin" || :
	(
	cd "${appdir}" || err "extract_e6400vga: can't cd ${appdir}"
	[ -f "bios.bin" ] || err "extract_e6400vga: can't extract bios.bin"
	"${e6400_unpack}" bios.bin || printf "TODO: fix dell extract util\n"
	[ -f "${E6400_VGA_romname}" ] || \
		err "extract_e6400vga: can't extract vga rom from bios.bin"
	)

	cp "${appdir}"/"${E6400_VGA_romname}" "${_vga_destination}" || \
	    err "extract_e6400vga: can't copy vga rom to ${_vga_destination}"

	printf "E6400 Nvidia ROM saved to: %s\n" "${_vga_destination}"
}

download_sch5545ec()
{
	printf "Downloading SMSC SCH5545 Environment Controller firmware\n"

	fetch_update "${SCH5545EC_DL_url}" "${SCH5545EC_DL_url_bkup}" \
	    "${SCH5545EC_DL_hash}" || return 1
	extract_sch5545ec || return 1
}

# TODO: this code is cancer. hardcoded is bad, and stupid.
# TODO: make it *scan* (based on signature, in each file)
extract_sch5545ec()
{
	printf "Extracting SCH5545 Environment Controller firmware for '%s'\n" \
	    ${board}

	rm -Rf "${dl_path}_extracted" || err "!rm ${dl_path}_extracted"

	_sch5545ec_destination=${CONFIG_SMSC_SCH5545_EC_FW_FILE#../../}
	mkdirs "${_sch5545ec_destination}" "extract_sch5545ec" || return 0

	# full system ROM (UEFI), to extract with UEFIExtract:
	_bios="${dl_path}_extracted/Firmware"
	_bios="${_bios}/1 ${dlsum} -- 1 System BIOS vA.28.bin"
	# this is the SCH5545 firmware, inside of the extracted UEFI ROM:
	_sch5545ec_fw="${_bios}.dump/4 7A9354D9-0468-444A-81CE-0BF617D890DF"
	_sch5545ec_fw="${_sch5545ec_fw}/54 D386BEB8-4B54-4E69-94F5-06091F67E0D3"
	_sch5545ec_fw="${_sch5545ec_fw}/0 Raw section/body.bin" # <-- this!

	# this makes the file defined by _sch5545ec_fw available to copy
	"${uefiextract}" "${_bios}" || \
	    err "extract_sch5545ec: cannot extract from uefi image"

	cp "${_sch5545ec_fw}" "${_sch5545ec_destination}" || \
	    err "extract_sch5545ec: cannot copy sch5545ec firmware file"
}

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

	dl="${1}"
	dl_bkup="${2}"
	dlsum="${3}"

	dl_path="${blobdir}/cache/${dlsum}"
	mkdir -p "${blobdir}/cache" || err "fetch_update: !mkdir ${blobdir}/cache"

	dl_fail="y"
	vendor_checksum "${dlsum}" && dl_fail="n"
	for x in "${dl}" "${dl_bkup}"; do
		[ "${dl_fail}" = "n" ] && break
		[ -z "${x}" ] && continue
		rm -f "${dl_path}" || err "fetch_update: !rm -f ${dl_path}"
		wget --tries 3 -U "${agent}" "${x}" -O "${dl_path}" || continue
		vendor_checksum "${dlsum}" && dl_fail="n"
	done
	if [ "${dl_fail}" = "y" ]; then
		printf "ERROR: invalid vendor updates for: %s\n" "${board}" 1>&2
		err "fetch_update ${dlsum}: matched vendor update unavailable"
	fi
}

vendor_checksum()
{
	if [ ! -f "${dl_path}" ]; then
		printf "Vendor update not found for: %s\n" "${board}" 1>&2
		return 1
	elif [ "$(sha512sum ${dl_path} | awk '{print $1}')" != "${1}" ]; then
		printf "Bad checksum on vendor update for: %s\n" "${board}" 1>&2
		return 1
	fi
}

mkdirs()
{
	[ -f "${1}" ] && \
		printf "mkdirs ${1} ${2}: already downloaded\n" 1>&2 && return 1
	mkdir -p "${1%/*}" || err "mkdirs ${1} ${2}: !mkdir ${1%/*}"
	rm -Rf "${appdir}" || err "mkdirs ${1} ${2}: can't remove ${appdir}"
	mkdir -p "${appdir}/" || err "mkdirs ${1} ${2}: !mkdir ${appdir}"
	extract_archive "${dl_path}" "${appdir}" || \
	    [ "${2}" = "extract_e6400vga" ] || err "mkdirs ${1} ${2}: !extract"
}

extract_archive()
{
	innoextract "${1}" -d "${2}" || python "${pfs_extract}" "${1}" -e || \
	     7z x "${1}" -o"${2}" || unar "${1}" -o "${2}" || return 1
}

main $@