#!/bin/bash
#
# Christian's Boot Image Extractor
#
# Extracts the boot record volume descriptor, boot catalog and default boot
# image from the first session of a bootable "El Torito" CD-ROM or ISO 9660
# image.
#
# (c) 2005, 2009 Christian Schneider
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation, not 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, see <http://www.gnu.org/licenses/>.

########################################################################
# configuration part
########################################################################

# version
VERSION=20090415

# debugging
#set -x

# execute no command without full path
export PATH=""

# commands with full paths
RM="/bin/rm"
DD="/bin/dd"
OD="/usr/bin/od"
BC="/usr/bin/bc"

########################################################################
# functions
########################################################################

# function
exit_error () {
  echo "ERROR: ${1}" >&2
  exit 1
}

print_help () {
  echo "Usage: ${0##*/} [-c|--clean] <cdrom device>|<iso9660 image>" >&2
  echo >&2
  echo "  -c|--clean   delete the following files first (if they exist):" >&2
  echo "               boot.record, boot.catalog, bootimg.bin" >&2
  echo >&2
  echo "Note: Does not work for multi-session CD-ROMs! In case of \"El Torito\"" >&2
  echo "multi-boot images/CD-ROMs the extraction of the boot catalog is not" >&2
  echo "reliable and only the default boot image is extracted!" >&2
  exit 2
}

########################################################################
# parse command line parameters and do some tests
########################################################################

# iso9660 image file
ISOIMG=""

# parse options
while [ "${1}" != "" ]; do
  case "${1}" in
    -c|--clean)
      "${RM}" -f boot.record boot.catalog bootimg.bin
      ;;
    -h|--help)
      print_help
      ;;
    *)
      if [ "${ISOIMG}" = "" ]; then
        ISOIMG="${1}"
      else
        exit_error "Too many parameters: \"${1}\"!"
      fi
      ;;
  esac
  shift
done

# test iso9660 image
{ [ -b "${ISOIMG}" ] || [ -f "${ISOIMG}" ]; } || \
  exit_error "\"${ISOIMG}\" not a regular file or block device!"
[ -r "${ISOIMG}" ] || exit_error "\"${ISOIMG}\" not readable!"

# check if output files exist
for I in boot.record boot.catalog bootimg.bin; do
  [ -e "${I}" ] && exit_error "file \"${I}\" already exists!"
done

########################################################################
# main part
########################################################################

# boot record volume descriptor
echo "Extracting boot record volume descriptor (sector = 17)..."
"${DD}" if="${ISOIMG}" of=boot.record bs=2048 skip=17 count=1 2>/dev/null || \
  exit_error "extraction failed"

# boot system identifier
SYSTEM_ID="$("${DD}" if=boot.record bs=1 skip=7 count=23 2>/dev/null)"
[ "${SYSTEM_ID}" = "EL TORITO SPECIFICATION" ] || \
  exit_error "not an \"El Torito\" image/CD-ROM!"

# boot catalog
BOOTCAT_BEGIN="$("${OD}" -t o1 -N 4 -j 71 boot.record | \
  (read X D C B A Y; echo ibase=8; echo "$A*4000000+$B*40000+$C*400+$D") | "${BC}")"
echo "Extracting boot catalog (sector = ${BOOTCAT_BEGIN})..."
"${DD}" if="${ISOIMG}" of=boot.catalog bs=2048 skip="${BOOTCAT_BEGIN}" count=1 2>/dev/null || \
  exit_error "extraction failed"

# header indicator
HEADER_INDICATOR="$("${OD}" -t x1 -N 1 -j 64 boot.catalog | (read X A Y; echo "$A"))"
case "${HEADER_INDICATOR}" in
  90|91)
    echo "WARNING: multi-boot image detected!" >&2
    echo "The exctraction of the boot catalog is not reliable!" >&2
    ;;
  00)
    echo "Single boot image detected."
    ;;
  *)
    echo "WARNING: unknown header indicator (${HEADER_INDICATOR})!" >&2
    ;;
esac

# media type (size in multiples of 512 bytes = size of a "virtual sector")
MEDIA_TYPE="$("${OD}" -t x1 -N 1 -j 33 boot.catalog | (read X A Y; echo "$A"))"
case "${MEDIA_TYPE}" in
  00)
    BOOTSEC_SIZE="$("${OD}" -t o1 -N 2 -j 38 boot.catalog | \
      (read X B A Y; echo ibase=8; echo "$A*400+$B") | "${BC}")"
    echo "Media type is \"no emulation\" (image size = ${BOOTSEC_SIZE})."
    ;;
  01)
    BOOTSEC_SIZE="2400"
    echo "Media type is 1.2 MB diskette."
    ;;
  02)
    BOOTSEC_SIZE="2880"
    echo "Media type is 1.44 MB diskette."
    ;;
  03)
    BOOTSEC_SIZE="5760"
    echo "Media type is 2.88 MB diskette."
    ;;
  04)
    exit_error "unsupported media type (hard disk)!"
    ;;
  *)
    exit_error "unknown media type (${MEDIA_TYPE})!"
    ;;
esac

# default boot image
BOOTSEC_BEGIN="$("${OD}" -t o1 -N 4 -j 40 boot.catalog | \
  (read X D C B A Y; echo ibase=8; echo "$A*4000000+$B*40000+$C*400+$D") | "${BC}")"
echo "Extracting default boot image (start sector = ${BOOTSEC_BEGIN})..."
"${DD}" if="${ISOIMG}" of=bootimg.bin bs=512 skip="$((4*BOOTSEC_BEGIN))" \
  count="${BOOTSEC_SIZE}" 2>/dev/null || exit_error "extraction failed"

exit 0
