#!/bin/bash

# firmware header bytestring [bytes 04-32]
fw_bytes_header="feffffeafeffffeafeffffeafeffffeafeffffeafeffffeafeffffea"
fw_bytes_footer="00000000ffffffff"

# Known driver hashes
#
# NOTE: use sha256 checksums as they are more robust that md5 against collisions
hash_drv_wnd_105='6ec37d48c0764ed059dd49f472456a4f70150297d6397b7cc7965034cf78627e'
hash_drv_wnd_138='7044344593bfc08ab9b41ab691213bca568c8d924d0e05136b537f66b3c46f31'
hash_drv_osx_140='387097b5133e980196ac51504a60ae1ad8bab736eb0070a55774925ca0194892'
hash_drv_osx_143_1='4667e6828f6bfc690a39cf9d561369a525f44394f48d0a98d750931b2f3f278b'
hash_drv_osx_143_2='d4650346c940dafdc50e5fcbeeeffe074ec359726773e79c0cfa601cec6b1f08'
hash_drv_osx_143_3='dfac86799c6cf0aceb59bb4e732be8f030e7943eb1146830c7136f62621c9853'
hash_drv_osx_143_5='f56e68a880b65767335071531a1c75f3cfd4958adc6d871adf8dbf3b788e8ee1'

hash_fw_wnd_105='dabb8cf8e874451ebc85c51ef524bd83ddfa237c9ba2e191f8532b896594e50e'
hash_fw_wnd_138='ed75dc37b1a0e19949e9e046a629cb55deb6eec0f13ba8fd8dd49b5ccd5a800e'
hash_fw_osx_140='504fcf1565bf10d61b31a12511226ae51991fb55d480f82de202a2f7ee9c966e'
hash_fw_osx_143='e3e6034a67dfdaa27672dd547698bbc5b33f47f1fc7f5572a2fb68ea09d32d3d'

# Driver names
declare -A known_hashes=(
  ["$hash_drv_wnd_105"]='Windows Boot Camp 5.1.5722'
  ["$hash_drv_wnd_138"]='Windows Boot Camp Update Jul 29, 2015'
  ["$hash_drv_osx_140"]='OS X, El Capitan'
  ["$hash_drv_osx_143_1"]='OS X, El Capitan'
  ["$hash_drv_osx_143_2"]='OS X, El Capitan 10.11.2'
  ["$hash_drv_osx_143_3"]='OS X, El Capitan 10.11.3'
  ["$hash_drv_osx_143_5"]='OS X, El Capitan 10.11.5'
)

# Offset in bytes of the firmware inside the driver
declare -A firmw_offsets=(
  ["$hash_drv_wnd_105"]=78208
  ["$hash_drv_wnd_138"]=85296
  ["$hash_drv_osx_140"]=81920
  ["$hash_drv_osx_143_1"]=81920
  ["$hash_drv_osx_143_2"]=81920
  ["$hash_drv_osx_143_3"]=81920
  ["$hash_drv_osx_143_5"]=81920
)

# Size in bytes of the firmware inside the driver 
declare -A firmw_sizes=(
  ["$hash_drv_wnd_105"]=1523716
  ["$hash_drv_wnd_138"]=1421316
  ["$hash_drv_osx_140"]=603715
  ["$hash_drv_osx_143_1"]=603715
  ["$hash_drv_osx_143_2"]=603715
  ["$hash_drv_osx_143_3"]=603715
  ["$hash_drv_osx_143_5"]=603715
)

# Compression method used to store the firmware inside the driver
declare -A compression=(
  ["$hash_drv_wnd_105"]='cat'
  ["$hash_drv_wnd_138"]='cat'
  ["$hash_drv_osx_140"]='gzip'
  ["$hash_drv_osx_143_1"]='gzip'
  ["$hash_drv_osx_143_2"]='gzip'
  ["$hash_drv_osx_143_3"]='gzip'
  ["$hash_drv_osx_143_5"]='gzip'
)

declare -A firmw_hashes=(
  ["$hash_fw_wnd_105"]='1.05'
  ["$hash_fw_wnd_138"]='1.38'
  ["$hash_fw_osx_140"]='1.40.0'
  ["$hash_fw_osx_143"]='1.43.0'
)

printHelp()
{
  cat <<HELP_DOC
Usage: $0 [OPTIONS]

OPTION:

  -h  --help          Print this help message and exit.

  -i  --install       Download, extract and install binary firmware

  -u --uninstall      Remove binary firmware

NOTES:

  Only two drivers are currently available:

   - AppleCameraInterface: this is the OS X native driver, it can be found
     in a OS X installation under the following system directory
     /System/Library/Extensions/AppleCameraInterface.kext/Contents/MacOS/

   - AppleCamera.sys: this comes within the bootcamp windows driver package.
     You can download it from http://support.apple.com/downloads/DL1831/".

  However only the version 1.43.0 of the firmware from OS X actually works.

HELP_DOC
  exit 1
}

msg()
{
  echo "==> $*"
}

msg2()
{
  echo " --> $*"
}

err()
{
  echo "Error: $*"
}

hasProgram()
{
  if ! which "$1" &> /dev/null; then
    err "'$1' needed but not found!"
    exit 1
  fi
}

checkDmgPrerequisites()
{
  hasProgram "7z"
  hasProgram "cpio"
  hasProgram "head"
  hasProgram "mkdir"
  hasProgram "pbzx"
  hasProgram "sha256sum"
  hasProgram "tail"
  hasProgram "xar"
}

checkPrerequisites()
{
  hasProgram "awk"
  hasProgram "dd"
  hasProgram "sha256sum"
  hasProgram "zcat"
  hasProgram "curl"
}

getCheckSum()
{
  sha256sum $1 | awk '{ print $1 }'
}

checkDriverHash()
{
  # computing the hash for the input file
  driver_hash="$(getCheckSum $1)"

  # checking if it is among the known hashes
  for cur_hash in "${!known_hashes[@]}"; do
    if [[ "$driver_hash" == "$cur_hash" ]]; then
      echo "Found matching hash from ${known_hashes[$cur_hash]}"
      return
    fi
  done

  err "Mismatching driver hash for $1"
  err "The unknown hash is ${driver_hash}"
	err "No firmware extracted!"
  exit 1
}

checkFirmwareHash()
{
  # computing the hash for the input file
  fw_hash="$(getCheckSum $1)"

  # checking if it is among the known hashes
  for cur_hash in "${!firmw_hashes[@]}"; do
    if [[ "$fw_hash" == "$cur_hash" ]]; then
      msg2 "Extracted firmware version ${firmw_hashes[$cur_hash]}"
      return 0
    fi
  done

  err "Mismatching firmware hash ${firm_hash}"
  return 1
}

checkFirmwareHexdump()
{
  if ! which hexdump &> /dev/null; then
    err "You need to install 'hexdump' in order to check a firmware with unknown hash!"
    return 1
  fi

  header=$(hexdump -v -e '"" /1 "%02x"' "$1" -s 4 -n 28)
  footer=$(hexdump -v -e '"" /1 "%02x"' "$1" | tail -c 16)

  if [[ "${header}" != "${fw_bytes_header}" ]]; then
    err "The extracted firmware does not seem good (wrong header)"
    return 1
  elif [[ "${footer}" != "${fw_bytes_footer}" ]]; then
    err "The extracted firmware does not seem good (wrong footer)"
    return 1
  else
    msg2 "You're lucky, the firmware looks good, " \
         "but it could also not work... use it at your own risk!"
  fi
}

extractFirmware()
{
  msg "Extracting firmware..."
  dd bs=1 skip=$3 count=$4 if=$1 of="$2.tmp" &> /dev/null

  msg2 "Decompressing the firmware using $5..."
  case "$5" in
    "gzip")
      zcat "$2.tmp" > "$2"
      ;;
    "cat")
      cat "$2.tmp" > "$2"
      ;;
  esac

  msg2 "Deleting temporary files..."
  rm "$2.tmp"
}

decompress_dmg()
{
  msg "Extracting the driver from $1..."

  msg2 "Creating temporary directories..."
  mkdir -p "${_main_dir}/temp"
  cd "${_main_dir}/temp"

  msg2 "Decompressing the image..."
  7z e -y "${_main_dir}/$1" "5.hfs" > /dev/null

  msg2 "Extracting update package..."
  tail -c +189001729 "5.hfs" | head -c 661960661 > OSXUpd.xar
  rm -f "5.hfs"

  msg2 "Uncompressing XAR archive..."
  xar -x -f "OSXUpd.xar"
  rm -f "OSXUpd.xar"

  msg2 "Decoding Payload..."
  pbzx "OSXUpd"*.pkg"/Payload" > /dev/null
  rm "OSXUpdCombo10.11.3.pkg/Payload"

  msg2 "Decompressing archives..."
  cd "OSXUpdCombo10.11.3.pkg" 
  find . -name "Payload.part*.xz" -exec xz --decompress --verbose {} \;
  cat "Payload.part"* | cpio -id &> /dev/null
  cp "./System/Library/Extensions/AppleCameraInterface.kext/Contents/MacOS/AppleCameraInterface" \
     "${_main_dir}"
  msg2 "Cleaning up..."
  rm -rf "${_main_dir}/temp"
}

extract_from_osx()
{
 
  echo "" 
  checkDriverHash "$1"

  offset="${firmw_offsets[$driver_hash]}"
  size="${firmw_sizes[$driver_hash]}"
  comp_method="${compression[$driver_hash]}"

  extractFirmware "$1" "firmware.bin" "$offset" "$size" "$comp_method"

  if ! checkFirmwareHash "firmware.bin"; then
    if [[ -z "$skip_sums" ]]; then
	    err "No firmware extracted!"
      exit 1
    else
      msg2 "Ignoring hashes and check the firmware header..."
      checkFirmwareHexdump "firmware.bin" then
    fi
  fi
}

must_be_root()
{
  echo "must be root"
  exit 1
}

action=none

# Parsing arguments
while [[ $# > 0 ]]; do
  case $1 in
    -h|--help)
      printHelp
      exit 1
      ;;
    -i|--install)
      action='install'
      shift
      ;;
    -u|--uninstall)
      action='uninstall'
      shift
      ;;
  esac
  shift
done

[ "$action" != "none" -a $# -eq 0 ] || printHelp

#[ `whoami` = "root" ] || must_be_root

checkPrerequisites

DMG='osxupd10.11.5.dmg'
OSX_DRV='AppleCameraInterface'
OSX_DRV_DIR='System/Library/Extensions/AppleCameraInterface.kext/Contents/MacOS'

# https://github.com/patjak/bcwc_pcie/issues/14#issuecomment-167446787
RANGE='205261917-208085450'

URL="https://support.apple.com/downloads/DL1877/en_US/${DMG}"
FILE="${OSX_DRV_DIR}/${OSX_DRV}"

FW_DIR_BASE='/lib/firmware/facetimehd'

TMPDIR=$(mktemp -d)
OLD_PWD=$PWD
cd ${TMPDIR}

msg "Downloading & extracting OS X driver..."
/usr/bin/curl -s -L -r "${RANGE}" "${URL}" | xzcat --quiet --quiet | cpio -i -d --format odc --quiet --warning=none "./${FILE}" 2>&1> /dev/null || true
/bin/mv ${FILE} ${TMPDIR}/${OSX_DRV}

extract_from_osx "${OSX_DRV}"

msg "Installing firmware in ${FW_DIR_BASE}..."
msg2 "Creating ${FW_DIR_BASE} directory..."
/bin/mkdir -p ${FW_DIR_BASE}
msg2 "Moving firmware..."
/bin/mv ${TMPDIR}/firmware.bin ${FW_DIR_BASE}/
msg2 "Updating owner..."
chown -R root:root ${FW_DIR_BASE}
msg2 "Updating permissions..."
chmod 644 ${FW_DIR_BASE}/firmware.bin

cd ${OLD_PWD}
rm -rf ${TMPDIR}

exit 0
