#!/bin/bash
#
# w11arm_esd2iso - download and convert Microsoft ESD files for Windows 11 ARM to ISO
#
# Copyright (C) 2023 Paul Rockwell
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA#
#
# Credit: Location and methods of obtaining Microsoft ESD distributions and
# Microsoft Product catalog from b0gdanw "ESD to ISO on macOS.txt" https://gist.github.com/b0gdanw/e36ea84828dbd19e03eff6158f1fc77c
#

versionID="4.0.3 (17-September-2023)"
version="w11arm_esd2iso ${versionID}\n"
verbosityLevel=0

awk="/usr/bin/awk"
genisoimage="$(command -v mkisofs)"

declare -a lTags
declare -a lDesc

usage() {
	echo "Usage:\n"
	echo "$0 [-v]"
	echo "$0 [-Vh]"
	echo "\nOptions:"
	echo "\t-h\tPrint usage and exit"
	echo "\t-v\tEnable verbose output"
	echo "\t-V\tPrint program version and exit"
}
printVersion() {
	echo $version
}
verboseOn() {
	if (( verbosityLevel == 0 )); then
		return 1
	else
		return 0
	fi
}

extractEsd(){	
	
	local eFile
	local eDir
	local retVal
	local esdImageCount
	local bootWimFile
	local installWimFile
	local images
	local imageIndex
	local imageEdition
	local beQuiet
	
	eFile=$1
	eDir=$2
	beQuiet="--quiet"
	bootWimFile=$eDir/sources/boot.wim
	installWimFile=$eDir/sources/install.wim
	images=("4" "5")
	
	verboseOn && beQuiet=""
	
	esdImageCount=$(wimlib-imagex info $eFile | $awk '/Image Count:/ {print $3}')
	verboseOn && echo "[DEBUG] image count in ESD: $esdImageCount"
	for (( i = 6; i <= esdImageCount; i++ )); do
		images+=("$i")
	done

	#---------------
	# Extract image 1 in the ESD to create the boot environment
	#---------------

	echo "\nApplying boot files to the image"
	wimlib-imagex apply $eFile 1 $eDir $beQuiet 2>/dev/null
	retVal=$?
	if (( retVal != 0 )); then
		echo "[ERROR] Extract of boot files failed"
		return $retVal
	fi

	echo "Boot files successfully applied to image"

	#---------------
	# Create the boot.wim file that contains WinPE and Windows Setup
	# Images 2 and 3 in the ESD contain these components
	#
	# Important: image 3 in the ESD must be marked as bootable when
	# transferred to boot.wim or else the installer will fail
	#---------------

	echo "\nAdding WinPE and Windows Setup to the image"
	wimlib-imagex export $eFile 2 $bootWimFile --compress=LZX --chunk-size 32K $beQuiet
	retVal=$?
	if (( retVal != 0 )); then
		echo "[ERROR] Add of WinPE failed"
		return $retVal
	fi

	wimlib-imagex export $eFile 3 $bootWimFile --compress=LZX --chunk-size 32K --boot $beQuiet
	retVal=$?
	if (( retVal != 0 )); then
		echo "[ERROR] Add of Windows Setup failed"
		return $retVal
	fi
	echo "WinPE and Windows Setup added successfully to image\n"
	
	verboseOn && {
		echo "[DEBUG] contents of $bootWimFile"
		wimlib-imagex info  $bootWimFile
	}


	#---------------
	# Create the install.wim file that contains the files that Setup will install
	# Images 4, 5, (and 6 if it exists) in the ESD contain these components
	#---------------
	
	for imageIndex in ${images[*]}; do
		imageEdition="$(wimlib-imagex info $eFile $imageIndex | grep '^Description:' | sed 's/Description:[ \t]*//')"
		echo "\nAdding $imageEdition to the image"
		wimlib-imagex export $eFile $imageIndex $installWimFile --compress=LZMS --chunk-size 128K $beQuiet
		retVal=$?
		if (( retVal != 0 )); then
			echo "[ERROR] Addition of $imageIndex to the image failed"
			return $retVal
		fi
		echo "$imageEdition added successfully to the image"

	done

	echo "\nAll Windows editions added to image"	
	
	verboseOn && {
		echo "[DEBUG] contents of $installWimFile"
		wimlib-imagex info $installWimFile
	}
	
	return 0
}

buildIso(){
	local iDir=$1
	local iFile=$2
	local iLabel=$3
	local elToritoBootFile
	
	iDir=$1
	iFile=$2
	
	if [ -e $iFile ]; then
		echo "\t[INFO] File $iFile exists, removing it"
		rm -rf $iFile
	fi

	elToritoBootFile=$iDir/efi/microsoft/boot/efisys.bin
	
	#
	# Create the ISO file
	#

	#$hdiutil makehybrid -o $iFile -iso -udf -hard-disk-boot -eltorito-boot $elToritoBootFile $iDir
	"$genisoimage" -b "efi/microsoft/boot/efisys.bin" --no-emul-boot \
		--udf -iso-level 3 --hide "*" -V "$iLabel" -o "$iFile" $iDir
	return $?
}

#-------------------
#
# Start of program
#
#-------------------

#-------------------
# Bug fix - 16-Sept-2023 PER
#
# aria2c seems to have an issue with Sonoma: https://github.com/aria2/aria2/issues/2083
# This results in an error 134 that was displayed when trying to download the
# Windows 11 ARM catalog from Microsoft.
#
# suggested workaround is to set LC_MESSAGES environment variable
#-------------------

export LC_MESSAGES="C"

#-------------------
# 
# Process arguments
# 
#-------------------

while getopts ":hr:vV" opt; do
	case $opt in
		h)
			usage
			exit 1
			;;
		v)
			let verbosityLevel+=1
			;;
		V)
			printVersion
			exit 1
			;;
		:)
			echo "[ERROR] Option -$OPTARG requires an argument"
			usage
			exit 1
			;;
		\?)
			echo "[ERROR] Invalid option: -$OPTARG\n"
			usage
			exit 1
			;;
		esac
done
shift "$((OPTIND-1))"


printVersion

esdFile="$1"
isoFile="$2"
isoLabel="$3"

#-------------------
# Check number of arguments
# One argument is allowed when using the -r option for restart
# No arguments are allowed otherwise
#-------------------

if (( $# > 3 )); then
	echo "[ERROR] Too many arguments"
	usage
	exit 1
fi

workingDir="$(mktemp -q -d ./esd2iso_temp.XXXXXX)"
if (( $? != 0 )); then
	echo "[ERROR] Unable to create work directory, exiting"
	exit 1
fi

#---------------
#
# extDir is the "extract directory" where we're going to extract the ESD 
# and evenutally build the ISO from. It's a subdirectory of the working/temp directory
#
##---------------

extDir=$workingDir/ESD_ISO
mkdir $extDir

echo "\nStep 3: Building installation image from ESD distribution"
extractEsd $esdFile $extDir
retVal=$?
if (( retVal != 0 )); then
	echo "[ERROR] Installation image build failed with error code $retVal"
	echo "Work directory $workingDir was not deleted, use for debugging"
	exit 1
fi

#---------------
# At this point we no longer need the ESD file as it's already extracted
# In order to reduce disk space reauirements, delete the ESD file unless we
# have set the environment variable keepDownloads
#---------------

if [[ "x${keepDownloads}" == "x" ]]; then
	echo \n"ESD added successfully to installation image and is no longer needed.\nDeleting it to save disk space."
	verboseOn && echo "Deleting ESD file ${esdFile}"
	rm -rf ${esdFile} 
	retVal=$?
	if (( retVal != 0 )); then
		echo "[WARNING] Deletion of ESD file encountered a problem."
		echo "          The ISO build can continue, but will consume an addtional 5 GB of disk space."
	else
		echo "ESD file deleted successfully\n"
	fi
else
	verboseOn && echo "[DEBUG] keepDownloads is set - keeping ESD download"
fi

echo "\nStep 3 complete - installation image built"
	
echo "\nStep 4: Creating ISO $isoFile from the installation image\n"

buildIso $extDir $isoFile $isoLabel
retVal=$?
if (( retVal != 0 )); then
	echo "[ERROR] ISO was NOT created"
	echo "Working directory $workingDir was not deleted, use for debugging"
	exit 1
fi

echo "Step 4 complete - ISO created"
echo "\nCleaning up work directory"
rm -rf $workingDir
echo "Done!"
exit 0
