#!/usr/bin/env bash set -Eeuo pipefail ETFS="boot/etfsboot.com" FB="falling back to manual installation!" EFISYS="efi/microsoft/boot/efisys_noprompt.bin" backup () { local iso="$1" local count=1 local name="unknown" local root="$STORAGE/backups" local file previous find_pid failed="" previous=$(readState "base") || return 1 [ -n "$previous" ] && name="${previous%.*}" if ! makeDir "$root"; then error "Failed to create directory \"$root\" !" return 1 fi local folder="$name" local dir="$root/$folder" while [ -d "$dir" ]; do (( count++ )) folder="${name}.${count}" dir="$root/$folder" done if ! makeDir "$dir"; then error "Failed to create directory \"$dir\" !" return 1 fi if [ -f "$iso" ]; then if ! mv -f -- "$iso" "$dir/"; then error "Failed to move \"$iso\" to \"$dir\"." failed="Y" fi fi while IFS= read -r -d '' file; do if ! mv -n -- "$file" "$dir/"; then error "Failed to move \"$file\" to \"$dir\"." failed="Y" fi done < <( find "$STORAGE" -maxdepth 1 -type f \ \( -iname 'data.*' -or -iname 'windows.*' -or -iname '*.rom' -or -iname '*.vars' \) \ -not -iname '*.iso' -print0 ) find_pid=$! if ! wait "$find_pid"; then error "Failed to enumerate files in \"$STORAGE\"." failed="Y" fi [ -z "$(ls -A "$dir")" ] && rm -rf "$dir" [ -z "$(ls -A "$root")" ] && rm -rf "$root" [ -n "$failed" ] && return 1 return 0 } findFile() { local dir file base local fname="$1" local boot="$STORAGE/windows.boot" dir=$(find / -maxdepth 1 -type d -iname "$fname" -print -quit) [ ! -d "$dir" ] && dir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$fname" -print -quit) if [ -d "$dir" ]; then if ! hasData || [ ! -f "$boot" ]; then error "The bind $dir maps to a file that does not exist!" && return 1 fi fi file=$(find / -maxdepth 1 -type f -iname "$fname" -print -quit) [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" -print -quit) if [ ! -s "$file" ] && [[ "${VERSION,,}" != "http"* ]]; then base=$(basename "$VERSION") file="$STORAGE/$base" fi if [ ! -f "$file" ] || [ ! -s "$file" ]; then return 0 fi local size size="$(stat -c%s "$file")" [ -z "$size" ] || [[ "$size" == "0" ]] && return 0 ISO="$file" CUSTOM="$file" BOOT="$STORAGE/windows.$size.iso" return 0 } detectCustom() { CUSTOM="" ! findFile "custom.iso" && return 1 [ -n "$CUSTOM" ] && return 0 ! findFile "boot.iso" && return 1 [ -n "$CUSTOM" ] && return 0 return 0 } skipInstall() { local iso="$1" local method="" local magic byte local boot="$STORAGE/windows.boot" local previous previous=$(readState "base") || return 1 if [ -n "$previous" ]; then if [[ "${STORAGE,,}/${previous,,}" != "${iso,,}" ]]; then if ! hasDisk; then if ! rm -f -- "$STORAGE/$previous"; then error "Failed to remove ISO file \"$STORAGE/$previous\" !" exit 50 fi return 1 fi if [[ "${iso,,}" == "${STORAGE,,}/windows."* ]]; then method="your custom .iso file was changed" else if [[ "${previous,,}" != "windows."* ]]; then method="the VERSION variable was changed" else method="your custom .iso file was removed" if [ -f "$boot" ] && hasData; then info "Detected that $method, will be ignored." return 0 fi fi fi info "Detected that $method, a backup of your previous installation will be saved..." if ! backup "$STORAGE/$previous"; then warn "the backup was incomplete, continuing with installation..." fi return 1 fi fi [ -f "$boot" ] && hasData && return 0 [ ! -f "$iso" ] && return 1 [ ! -s "$iso" ] && return 1 # Check if the ISO was already processed by our script magic=$(dd if="$iso" bs=1 count=1 status=none | tr -d '\000') magic="$(printf '%s' "$magic" | od -A n -t x1 -v | tr -d ' \n')" byte="16" && enabled "$MANUAL" && byte="17" if [[ "$magic" != "$byte" ]]; then info "The ISO will be processed again because the configuration was changed..." return 1 fi return 0 } startInstall() { html "Starting $APP..." if [ -z "$CUSTOM" ]; then local file="${VERSION//\//}.iso" if [[ "${VERSION,,}" == "http"* ]]; then file=$(basename "${VERSION%%\?*}") printf -v file '%b' "${file//%/\\x}" file="${file//[!A-Za-z0-9._-]/_}" else local language language=$(getLanguage "$LANGUAGE" "culture") language="${language%%-*}" if [ -n "$language" ] && [[ "${language,,}" != "en" ]]; then file="${VERSION//\//}_${language,,}.iso" fi fi BOOT="$STORAGE/$file" REUSED_ISO="" [ -s "$BOOT" ] && REUSED_ISO="Y" # Use the suggested answer file for a new automatic download. When an # existing ISO is reused, leave DETECTED empty so its actual image can # be inspected instead. if [ -n "$DETECTED" ]; then DETECTED_ORG="Y" elif [ -z "$REUSED_ISO" ]; then DETECTED="$SUGGEST" fi fi TMP="$STORAGE/tmp" if ! rm -rf -- "$TMP"; then error "Failed to remove directory \"$TMP\" !" exit 50 fi skipInstall "$BOOT" && return 1 if hasDisk; then if ! backup ""; then warn "the backup was incomplete, continuing with installation..." fi fi if ! makeDir "$TMP"; then error "Failed to create directory \"$TMP\" !" exit 50 fi if [ -z "$CUSTOM" ]; then ISO=$(basename "$BOOT") ISO="$TMP/$ISO" if [ -f "$BOOT" ] && [ -s "$BOOT" ]; then if ! mv -f -- "$BOOT" "$ISO"; then error "Failed to move ISO file from \"$BOOT\" to \"$ISO\" !" exit 50 fi fi fi if ! rm -f -- "$BOOT"; then error "Failed to remove ISO file \"$BOOT\" !" exit 50 fi if ! find "$STORAGE" -maxdepth 1 -type f -iname 'data.*' -not -iname '*.iso' -delete; then error "Failed to remove obsolete disk files from \"$STORAGE\" !" exit 50 fi if ! find "$STORAGE" -maxdepth 1 -type f -iname 'windows.*' -not -iname '*.iso' -delete; then error "Failed to remove obsolete Windows files from \"$STORAGE\" !" exit 50 fi if ! find "$STORAGE" -maxdepth 1 -type f \( -iname '*.rom' -or -iname '*.vars' \) -delete; then error "Failed to remove obsolete firmware files from \"$STORAGE\" !" exit 50 fi return 0 } finishInstall() { local iso="$1" local aborted="$2" local base byte if [ ! -s "$iso" ] || [ ! -f "$iso" ]; then error "Failed to find ISO file: $iso" && return 1 fi if [[ "$iso" == "$STORAGE/"* ]]; then if ! setOwner "$iso"; then warn "failed to set the owner for \"$iso\" !" fi fi if [[ "$aborted" != [Yy1]* ]]; then # Mark ISO as prepared via magic byte byte="16" && enabled "$MANUAL" && byte="17" if ! printf '%b' "\x$byte" | dd of="$iso" bs=1 seek=0 count=1 conv=notrunc status=none; then warn "failed to set magic byte in ISO file: $iso" fi fi local file="$STORAGE/windows.ver" cp -f /etc/version "$file" || return 1 if ! setOwner "$file"; then warn "Failed to set the owner for \"$file\" !" fi if [[ "$iso" == "$STORAGE/"* ]]; then if [[ "$aborted" != [Yy1]* ]] || [ -z "$CUSTOM" ]; then base=$(basename "$iso") writeState "base" "$base" || return 1 fi fi if [[ "${PLATFORM,,}" == "x64" ]]; then if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then writeState "mode" "$BOOT_MODE" || return 1 if [[ "${MACHINE,,}" != "q35" ]]; then writeState "old" "$MACHINE" || return 1 fi else # Enable secure boot + TPM on manual installs as Win11 requires if enabled "$MANUAL" || [[ "$aborted" == [Yy1]* ]]; then if [[ "${DETECTED,,}" == "win11"* ]]; then BOOT_MODE="windows_secure" writeState "mode" "$BOOT_MODE" || return 1 fi fi # Enable secure boot on multi-socket systems to workaround freeze if [ -n "$SOCKETS" ] && [[ "$SOCKETS" != "1" ]]; then BOOT_MODE="windows_secure" writeState "mode" "$BOOT_MODE" || return 1 fi fi fi if [ -n "${ARGS:-}" ]; then ARGUMENTS="$ARGS ${ARGUMENTS:-}" writeState "args" "$ARGS" || return 1 fi if [ -n "${VGA:-}" ] && [[ "${VGA:-}" != "virtio"* ]]; then writeState "vga" "$VGA" || return 1 fi if [ -n "${USB:-}" ] && [[ "${USB:-}" != "qemu-xhci"* ]]; then writeState "usb" "$USB" || return 1 fi if [ -n "${DISK_TYPE:-}" ] && [[ "${DISK_TYPE:-}" != "scsi" ]]; then writeState "type" "$DISK_TYPE" || return 1 fi if [ -n "${ADAPTER:-}" ] && [[ "${ADAPTER:-}" != "virtio-net-pci" ]]; then writeState "net" "$ADAPTER" || return 1 fi if [ -n "${SOUND:-}" ] && [[ "${SOUND:-}" != "intel-hda" ]]; then writeState "sound" "$SOUND" || return 1 fi rm -rf "$TMP" return 0 } abortInstall() { local dir="$1" local iso="$2" local efi [[ "${iso,,}" == *".esd" ]] && exit 60 enabled "${UNPACK:-}" && exit 60 efi=$(find "$dir" -maxdepth 1 -type d -iname efi -print -quit) if [ -z "$efi" ]; then [[ "${PLATFORM,,}" == "x64" ]] && BOOT_MODE="windows_legacy" fi if [ -n "$CUSTOM" ]; then BOOT="$iso" REMOVE="N" else if [[ "$iso" != "$BOOT" ]]; then if ! mv -f "$iso" "$BOOT"; then error "Failed to move ISO file: $iso" && return 1 fi fi fi finishInstall "$BOOT" "Y" && return 0 return 1 } checkFreeSpace() { local dir="$1" local size="$2" local size_gb space space_gb size_gb=$(formatBytes "$size") space=$(df --output=avail -B 1 "$dir" | tail -n 1) space_gb=$(formatBytes "$space") if (( size > space )); then error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." return 1 fi return 0 } getEsdField() { local list="$1" local index="$2" sed -n "${index}p" <<< "$list" | tr -cd '0-9' return 0 } extractESD() { local iso="$1" local dir="$2" local version="$3" local desc="$4" local msg ret index size local minSize freeSpace bootPad local info count totals links local bootTotal bootLinks bootSize local wimTotal wimLinks wimSize local installSize installPad local bootWim installWim local edition imgEdition minSize=100000000 freeSpace=9606127360 bootPad=60000000 installPad=3000000 msg="Extracting $desc bootdisk" info "$msg..." && html "$msg..." if ! size=$(stat -c%s -- "$iso"); then error "Failed to determine size of ISO file \"$iso\" !" return 1 fi if (( size < minSize )); then error "The downloaded ISO file is too small!" return 1 fi if ! rm -rf -- "$dir"; then error "Failed to remove directory \"$dir\" !" return 1 fi if ! makeDir "$dir"; then error "Failed to create directory \"$dir\" !" return 1 fi checkFreeSpace "$dir" "$freeSpace" || return 1 info=$(wimlib-imagex info "$iso") || { error "Cannot read ESD file information!" return 1 } count=$(awk '/Image Count:/ {print $3}' <<< "$info") if [[ ! "$count" =~ ^[0-9]+$ ]]; then error "Cannot read the image count in ESD file!" return 1 fi if (( count < 3 )); then error "Invalid ESD file: expected at least 3 images, found $count." return 1 fi totals=$(grep "Total Bytes:" <<< "$info" || true) links=$(grep "Hard Link Bytes:" <<< "$info" || true) bootTotal=$(getEsdField "$totals" 1) bootLinks=$(getEsdField "$links" 1) if [[ ! "$bootTotal" =~ ^[0-9]+$ ]] || [[ ! "$bootLinks" =~ ^[0-9]+$ ]]; then error "Cannot read bootdisk size from ESD file!" return 1 fi bootSize=$(( bootTotal - bootLinks )) wimTotal=$(getEsdField "$totals" 3) wimLinks=$(getEsdField "$links" 3) if [[ ! "$wimTotal" =~ ^[0-9]+$ ]] || [[ ! "$wimLinks" =~ ^[0-9]+$ ]]; then error "Cannot read boot.wim size from ESD file!" return 1 fi wimSize=$(( wimTotal - wimLinks + bootPad )) /run/progress.sh "$dir" "$bootSize" "$msg ([P])..." & index="1" wimlib-imagex apply "$iso" "$index" "$dir" --quiet 2>/dev/null || { ret=$? fKill "progress.sh" error "Extracting $desc bootdisk failed ($ret)" return 1 } fKill "progress.sh" bootWim="$dir/sources/boot.wim" installWim="$dir/sources/install.wim" msg="Extracting $desc environment" info "$msg..." && html "$msg..." index="2" /run/progress.sh "$bootWim" "$wimSize" "$msg ([P])..." & wimlib-imagex export "$iso" "$index" "$bootWim" --compress=none --quiet || { ret=$? fKill "progress.sh" error "Adding WinPE failed ($ret)" return 1 } fKill "progress.sh" msg="Extracting $desc setup" info "$msg..." index="3" /run/progress.sh "$bootWim" "$wimSize" "$msg ([P])..." & wimlib-imagex export "$iso" "$index" "$bootWim" --compress=none --boot --quiet || { ret=$? fKill "progress.sh" error "Adding Windows Setup failed ($ret)" return 1 } fKill "progress.sh" if [[ "${PLATFORM,,}" == "x64" ]]; then LABEL="CCCOMA_X64FRE_EN-US_DV9" else LABEL="CPBA_A64FRE_EN-US_DV9" fi msg="Extracting $desc image" info "$msg..." && html "$msg..." edition=$(getCatalog "$version" "name") if [ -z "$edition" ]; then error "Invalid VERSION specified, value \"$version\" is not recognized!" return 1 fi for (( index=4; index<=count; index++ )); do imgEdition=$(wimlib-imagex info "$iso" "$index" | grep '^Description:' | sed 's/Description:[ \t]*//') [[ "${imgEdition,,}" != "${edition,,}" ]] && continue installSize=$(stat -c%s "$iso") installSize=$(( installSize + installPad )) /run/progress.sh "$installWim" "$installSize" "$msg ([P])..." & wimlib-imagex export "$iso" "$index" "$installWim" --compress=LZMS --chunk-size 128K --quiet || { ret=$? fKill "progress.sh" error "Addition of $index to the $desc image failed ($ret)" return 1 } fKill "progress.sh" return 0 done fKill "progress.sh" error "Failed to find product '$edition' in install.wim!" return 1 } extractImage() { local iso="$1" local dir="$2" local version="$3" local desc="local ISO" local file size if [ -z "$CUSTOM" ]; then desc="downloaded ISO" if [[ "$version" != "http"* ]]; then desc=$(printVariant "$version" "$desc") fi fi if [[ "${iso,,}" == *".esd" ]]; then extractESD "$iso" "$dir" "$version" "$desc" && return 0 return 1 fi local msg="Extracting $desc image" info "$msg..." && html "$msg..." if ! rm -rf -- "$dir"; then error "Failed to remove directory \"$dir\" !" return 1 fi if ! makeDir "$dir"; then error "Failed to create directory \"$dir\" !" return 1 fi size=$(stat -c%s "$iso") if (( size < 100000000 )); then error "Invalid ISO file: Size is smaller than 100 MB" && return 1 fi checkFreeSpace "$dir" "$size" || return 1 if ! rm -rf -- "$dir"; then error "Failed to remove directory \"$dir\" !" return 1 fi /run/progress.sh "$dir" "$size" "$msg ([P])..." & if ! 7z x "$iso" -o"$dir" > /dev/null; then fKill "progress.sh" error "Failed to extract ISO file: $iso" && return 1 fi fKill "progress.sh" if ! enabled "${UNPACK:-}"; then LABEL=$(isoinfo -d -i "$iso" | sed -n 's/Volume id: //p') || LABEL="" else file=$(find "$dir" -maxdepth 1 -type f -iname "*.iso" -print -quit) if [ -z "$file" ]; then error "Failed to find any .iso file in archive!" && return 1 fi if ! 7z x "$file" -o"$dir" > /dev/null; then error "Failed to extract archive!" && return 1 fi LABEL=$(isoinfo -d -i "$file" | sed -n 's/Volume id: //p') || LABEL="" rm -f "$file" || warn "Failed to remove temporary ISO file: $file" fi return 0 } setMachine() { local id="$1" local iso="$2" local dir="$3" local desc="$4" case "${id,,}" in "win9"* ) ETFS="[BOOT]/Boot-1.44M.img" ;; "win2k"* ) if ! legacyInstall "$iso" "$dir" "$desc" "2k"; then error "Failed to prepare $desc ISO!" && return 1 fi ;; "winxp"* ) if ! legacyInstall "$iso" "$dir" "$desc" "xp"; then error "Failed to prepare $desc ISO!" && return 1 fi ;; "win2003"* ) if ! legacyInstall "$iso" "$dir" "$desc" "2k3"; then error "Failed to prepare $desc ISO!" && return 1 fi ;; esac case "${id,,}" in "win9"* ) USB="no" VGA="cirrus" DISK_TYPE="auto" MACHINE="pc-i440fx-2.4" BOOT_MODE="windows_legacy" [ -z "${ADAPTER:-}" ] && ADAPTER="pcnet" ;; "win2k"* ) VGA="cirrus" MACHINE="pc" USB="pci-ohci" DISK_TYPE="auto" BOOT_MODE="windows_legacy" [ -z "${ADAPTER:-}" ] && ADAPTER="rtl8139" ;; "winxp"* | "win2003"* ) DISK_TYPE="blk" BOOT_MODE="windows_legacy" [ -z "${SOUND:-}" ] && SOUND="usb-audio" ;; "winvista"* | "win7"* | "win2008"* ) BOOT_MODE="windows_legacy" ;; esac case "${id,,}" in "winxp"* | "win2003"* | "winvistax86"* | "win7x86"* | "win2008r2x86"* ) if [ -z "${MACHINE:-}" ] || [[ "${MACHINE:-}" == "q35" ]]; then # Prevent bluescreen if 64 bit PCI hole size is >2G. ARGS="-global q35-pcihost.x-pci-hole64-fix=false" fi ;; esac return 0 } prepareImage() { local iso="$1" local dir="$2" local desc missing desc=$(printVariant "$DETECTED" "$DETECTED") setMachine "$DETECTED" "$iso" "$dir" "$desc" || return 1 skipVersion "$DETECTED" && return 0 if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then [ -f "$dir/$ETFS" ] && [ -s "$dir/$ETFS" ] && [ -f "$dir/$EFISYS" ] && [ -s "$dir/$EFISYS" ] && return 0 missing=$(basename "$dir/$EFISYS") if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then missing=$(basename "$dir/$ETFS") fi error "Failed to locate file \"${missing,,}\" in ISO image!" return 1 fi legacyPrepare "$iso" "$dir" "$desc" && return 0 error "Failed to extract boot image from ISO image!" return 1 } addFolder() { local src="$1" local folder="/oem" file="" local dest="$src/\$OEM\$/\$1/OEM" [ ! -d "$folder" ] && folder="/OEM" [ ! -d "$folder" ] && folder="$STORAGE/oem" [ ! -d "$folder" ] && folder="$STORAGE/OEM" [ ! -d "$folder" ] && folder="" [ -z "$folder" ] && [ -z "$COMMAND" ] && return 0 local msg="Adding OEM files to image..." info "$msg" && html "$msg" mkdir -p "$dest" || return 1 if [ -n "$folder" ]; then cp -Lr "$folder/." "$dest" || return 1 fi file=$(find "$dest" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1 if [ -n "$COMMAND" ]; then [ -z "$file" ] && file="$dest/install.bat" if [ -s "$file" ]; then printf '\n' >> "$file" || return 1 fi printf '%s\n' "$COMMAND" >> "$file" || return 1 fi if [ -f "$file" ]; then if ! unix2dos -q "$file"; then error "Failed to convert $file to DOS format!" return 1 fi fi return 0 } addDriver() { local id="$1" local path="$2" local target="$3" local driver="$4" local desc="" local folder="" if [ -z "$id" ]; then warn "no Windows version specified for \"$driver\" driver!" && return 0 fi case "${id,,}" in "win7x86"* ) folder="w7/x86" ;; "win7x64"* ) folder="w7/amd64" ;; "win81x64"* ) folder="w8.1/amd64" ;; "win10x64"* ) folder="w10/amd64" ;; "win11x64"* ) folder="w11/amd64" ;; "win2025"* ) folder="2k25/amd64" ;; "win2022"* ) folder="2k22/amd64" ;; "win2019"* ) folder="2k19/amd64" ;; "win2016"* ) folder="2k16/amd64" ;; "win2012"* ) folder="2k12R2/amd64" ;; "win2008"* ) folder="2k8R2/amd64" ;; "win10arm64"* ) folder="w10/ARM64" ;; "win11arm64"* ) folder="w11/ARM64" ;; "winvistax86"* ) folder="2k8/x86" ;; "winvistax64"* ) folder="2k8/amd64" ;; esac if [ -z "$folder" ]; then desc=$(printVersion "$id" "$id") if [[ "${id,,}" != *"x86"* ]]; then warn "no \"$driver\" driver available for \"$desc\" !" && return 0 else warn "no \"$driver\" driver available for the 32-bit version of \"$desc\" !" && return 0 fi fi [ ! -d "$path/$driver/$folder" ] && return 0 case "${id,,}" in "winvista"* ) [[ "${driver,,}" == "viorng" ]] && return 0 ;; esac local dest="$path/$target/$driver" mkdir -p "$dest" || return 1 cp -Lr "$path/$driver/$folder/." "$dest" || return 1 return 0 } addDrivers() { local src="$1" local tmp="$2" local file="$3" local index="$4" local version="$5" local drivers="$tmp/drivers" rm -rf "$drivers" mkdir -p "$drivers" local msg="Adding drivers to image..." info "$msg" && html "$msg" if [ -z "$version" ]; then version="win11x64" warn "Windows version unknown, falling back to Windows 11 drivers..." fi if ! bsdtar -xf /var/drivers.txz -C "$drivers"; then error "Failed to extract drivers from archive!" && return 1 fi local target="\$WinPEDriver\$" local dest="$drivers/$target" mkdir -p "$dest" || return 1 wimlib-imagex update "$file" "$index" --command "delete --force --recursive /$target" >/dev/null || true local driver local driver_list=( qxl viofs sriov smbus qxldod viorng viostor viomem NetKVM Balloon vioscsi pvpanic vioinput viogpudo vioserial qemupciserial ) for driver in "${driver_list[@]}"; do addDriver "$version" "$drivers" "$target" "$driver" || return 1 done local dst="$src/\$OEM\$/\$\$/Drivers" mkdir -p "$dst" || return 1 cp -Lr "$dest/." "$dst" || return 1 case "${version,,}" in "win11x64"* | "win2025"* ) # Workaround Virtio GPU driver bug rm -rf "$dest/viogpudo" ;; esac if ! wimlib-imagex update "$file" "$index" --command "add $dest /$target" >/dev/null; then return 1 fi rm -rf "$drivers" return 0 } updateImage() { local dir="$1" local asset="$2" local language="$3" local tmp="/tmp/install" local xml="autounattend.xml" local bak="${xml//.xml/.org}" local dat="${xml//.xml/.dat}" local desc path src wim name info idx skipVersion "${DETECTED,,}" && return 0 if [ ! -s "$asset" ] || [ ! -f "$asset" ]; then asset="" if ! enabled "$MANUAL"; then MANUAL="Y" warn "no answer file provided, $FB." fi fi rm -rf "$tmp" || return 1 mkdir -p "$tmp" || return 1 src=$(find "$dir" -maxdepth 1 -type d -iname sources -print -quit) || return 1 if [ ! -d "$src" ]; then error "failed to locate 'sources' folder in ISO image, $FB" return 1 fi wim=$(find "$src" -maxdepth 1 -type f \( -iname boot.wim -or -iname boot.esd \) -print -quit) || return 1 if [ ! -f "$wim" ]; then error "failed to locate 'boot.wim' or 'boot.esd' in ISO image, $FB" return 1 fi idx="1" if ! info=$(wimlib-imagex info -xml "$wim" | iconv -f UTF-16LE -t UTF-8); then warn "failed to read boot image information, $FB" MANUAL="Y" info="" fi if [[ "${info^^}" == *""* ]]; then idx="2" fi if ! addDrivers "$src" "$tmp" "$wim" "$idx" "$DETECTED"; then error "Failed to add drivers to image!" fi if ! addFolder "$src"; then error "Failed to add OEM folder to image!" fi if wimlib-imagex extract "$wim" "$idx" "/$xml" "--dest-dir=$tmp" >/dev/null 2>&1; then if ! wimlib-imagex extract "$wim" "$idx" "/$dat" "--dest-dir=$tmp" >/dev/null 2>&1; then if ! wimlib-imagex extract "$wim" "$idx" "/$bak" "--dest-dir=$tmp" >/dev/null 2>&1; then if ! wimlib-imagex update "$wim" "$idx" --command "rename /$xml /$bak" > /dev/null; then warn "failed to backup original answer file ($xml)." fi fi fi fi if ! enabled "$MANUAL"; then name=$(basename "$asset") || return 1 local answer="$tmp/$name" info "Adding $name for automatic installation..." if ! cp "$asset" "$answer"; then error "Failed to copy answer file to $answer." return 1 fi removeGeneratedXML "$asset" || return 1 if [ -n "${CUSTOM_XML:-}" ]; then if ! xmllint --nonet --noout "$answer"; then error "The custom answer file is not valid XML!" return 1 fi else if ! updateXML "$answer" "$language"; then error "Failed to update answer file: $answer" return 1 fi fi if ! wimlib-imagex update "$wim" "$idx" --command "add $answer /$xml" > /dev/null; then MANUAL="Y" warn "failed to add answer file ($name) to ISO image, $FB" else wimlib-imagex update "$wim" "$idx" --command "add $answer /$dat" > /dev/null || true fi fi if enabled "$MANUAL"; then removeGeneratedXML "$asset" || return 1 wimlib-imagex update "$wim" "$idx" --command "delete --force /$xml" > /dev/null || true if wimlib-imagex extract "$wim" "$idx" "/$bak" "--dest-dir=$tmp" >/dev/null 2>&1; then if ! wimlib-imagex update "$wim" "$idx" --command "add $tmp/$bak /$xml" > /dev/null; then warn "failed to restore original answer file ($bak)." fi fi fi name="$xml" enabled "$MANUAL" && name="$bak" path=$(find "$dir" -maxdepth 1 -type f -iname "$name" -print -quit) || return 1 if [ -f "$path" ]; then if ! enabled "$MANUAL"; then if ! mv -f "$path" "${path%.*}.org"; then error "Failed to rename answer file: $path" return 1 fi else if ! mv -f "$path" "${path%.*}.xml"; then error "Failed to rename answer file: $path" return 1 fi fi fi rm -rf "$tmp" || return 1 return 0 } removeImage() { local iso="$1" [ ! -f "$iso" ] && return 0 [ -n "$CUSTOM" ] && return 0 rm -f "$iso" 2> /dev/null || warn "failed to remove $iso !" return 0 } buildImage() { local dir="$1" local failed="" local cat="BOOT.CAT" local log="/run/shm/iso.log" local base size desc if [ -f "$BOOT" ]; then error "File $BOOT does already exist?!" && return 1 fi base=$(basename "$BOOT") local out="$TMP/${base%.*}.tmp" rm -f "$out" desc=$(printVariant "$DETECTED" "ISO") local msg="Building $desc image" info "$msg..." && html "$msg..." [ -z "$LABEL" ] && LABEL="Windows" if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then error "Failed to locate file \"$ETFS\" in ISO image!" && return 1 fi size=$(du -b --max-depth=0 "$dir" | cut -f1) checkFreeSpace "$TMP" "$size" || return 1 /run/progress.sh "$out" "$size" "$msg ([P])..." & if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then genisoimage -o "$out" -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 4 -J -l -D -N -joliet-long -relaxed-filenames -V "${LABEL::30}" \ -udf -boot-info-table -eltorito-alt-boot -eltorito-boot "$EFISYS" -no-emul-boot -allow-limited-size -quiet "$dir" 2> "$log" || failed="y" else case "${DETECTED,,}" in "win2k"* | "winxp"* | "win2003"* ) genisoimage -o "$out" -b "$ETFS" -no-emul-boot -boot-load-seg 1984 -boot-load-size 4 -c "$cat" -iso-level 2 -J -l -D -N -joliet-long \ -relaxed-filenames -V "${LABEL::30}" -quiet "$dir" 2> "$log" || failed="y" ;; "win9"* ) genisoimage -o "$out" -b "$ETFS" -J -r -V "${LABEL::30}" -quiet "$dir" 2> "$log" || failed="y" ;; * ) genisoimage -o "$out" -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 2 -J -l -D -N -joliet-long -relaxed-filenames -V "${LABEL::30}" \ -udf -allow-limited-size -quiet "$dir" 2> "$log" || failed="y" ;; esac fi fKill "progress.sh" if [ -n "$failed" ]; then [ -s "$log" ] && echo "$(<"$log")" error "Failed to build image!" && return 1 fi local err="" local hide="Warning: creating filesystem that does not conform to ISO-9660." [ -s "$log" ] && err="$(<"$log")" [[ "$err" != "$hide" ]] && echo "$err" mv -f "$out" "$BOOT" || return 1 if ! setOwner "$BOOT"; then warn "Failed to set the owner for \"$BOOT\" !" fi return 0 } bootWindows() { ARGS=$(readState "$STORAGE/windows.args") || return 1 if [ -n "$ARGS" ]; then ARGUMENTS="$ARGS ${ARGUMENTS:-}" fi restoreState "VGA" "$STORAGE/windows.vga" || return 1 restoreState "USB" "$STORAGE/windows.usb" || return 1 restoreState "SOUND" "$STORAGE/windows.sound" || return 1 restoreState "ADAPTER" "$STORAGE/windows.net" || return 1 restoreState "DISK_TYPE" "$STORAGE/windows.type" || return 1 restoreState "BOOT_MODE" "$STORAGE/windows.mode" "Y" || return 1 if [[ "${PLATFORM,,}" == "x64" ]]; then restoreState "MACHINE" "$STORAGE/windows.old" "Y" || return 1 fi return 0 } ###################################### ! parseVersion && exit 58 ! parseLanguage && exit 56 ! detectCustom && exit 59 if ! startInstall; then bootWindows && return 0 exit 68 fi if [ ! -s "$ISO" ] || [ ! -f "$ISO" ]; then if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then rm -f "$ISO" 2> /dev/null || true exit 61 fi fi DIR="$TMP/unpack" if ! extractImage "$ISO" "$DIR" "$VERSION"; then rm -f "$ISO" 2> /dev/null || true exit 62 fi if ! detectImage "$DIR" "$VERSION"; then abortInstall "$DIR" "$ISO" && return 0 exit 60 fi if ! prepareImage "$ISO" "$DIR"; then abortInstall "$DIR" "$ISO" && return 0 exit 66 fi if ! updateImage "$DIR" "$XML" "$LANGUAGE"; then abortInstall "$DIR" "$ISO" && return 0 exit 63 fi if ! removeImage "$ISO"; then exit 64 fi if ! buildImage "$DIR"; then exit 65 fi if ! finishInstall "$BOOT" "N"; then exit 69 fi return 0