#!/bin/bash

# Create a TRX image from up to three source files (kernel, initramfs, rootfs),
# and output it to stdout.  (This is the format you flash linksys routers with.)

# The TRX format is a simple (28 byte) header followed by the concatenation of
# the files with each file zero padded to a multiple of 4 bytes, and then the
# file as a whole padded up to 4k.  Padding is done with zero bytes.

# The tricky part is calculating the lengths and CRC for the header before
# outputting the header, without screwing up the ability to pipe the output
# somewhere.

if [ ! -f "$1" ] ||
   ( [ ! -z "$2" ] && [ ! -f "$2" ] ) ||
   ( [ ! -z "$3" ] && [ ! -f "$3" ] )
then
  echo "Usage: trximg.sh file1 [file2 [file3]]" >&2
  exit 1
fi

# Output $1 bytes of decimal number $2 as little endian binary data

function leout()
{
  X=0
  DATA=$2

  # Loop through bytes, smallest first

  while [ $X -lt $1 ]
  do
    # Grab next byte

    BYTE=$[$DATA%256]
    DATA=$[$DATA/256]

    # Convert to octal (because that's what echo needs)

    OCTAL=""
    for i in 1 2 3
    do
      OCTAL=$[$BYTE%8]"$OCTAL"
      BYTE=$[$BYTE/8]
    done

    # Emit byte and loop

    echo -ne "\0$OCTAL"

    X=$[$X+1]
    BYTE=$x
  done
}

# Print number of bytes required to round $2 up to a multiple of $1

function padlen()
{
  echo $[($1-($2%$1))%$1]
}

# Print number $2 rounded up to $1

function roundlen()
{
  echo $[$2+$(padlen $1 $2)]
}

# Return length of file $1 in bytes

function filelen()
{
  wc -c "$1" | awk '{print $1}'
}

# Output $1 zero bytes

function zpad()
{
  [ $1 -ne 0 ] && dd if=/dev/zero bs=$1 count=1 2>/dev/null
}

# Output file $2, followed by enough zero bytes to pad length up to $1 bytes

function zpad_file()
{
  [ -z "$2" ] && return
  cat $2
  zpad $(padlen $1 $(filelen "$2"))
}

# Output header.  (Optionally just the part included in the CRC32).

function emit_header
{
  if [ -z "$1" ]
  then
    echo -n "HDR0"       # File ID magic
    leout 4 $LENGTH      # Length of file (including this header)
    leout 4 $CRC32       # crc32 of all file data after this crc field
  fi

  leout 2 0            # flags
  leout 2 1            # version
  leout 4 28           # Start of first file
  leout 4 $OFFSET2     # Start of second file
  leout 4 $OFFSET3     # Start of third file
}

# Calculate file offsets for the three arguments

TOTAL=$[28+$(roundlen 4 $(filelen "$1"))]
if [ -z "$2" ]
then
  OFFSET2=0
else
  OFFSET2=$TOTAL
  TOTAL=$[$TOTAL+$(roundlen 4 $(filelen "$2"))]
fi
if [ -z "$3" ]
then
  OFFSET3=0
else
  OFFSET3=$TOTAL
  TOTAL=$[$TOTAL+$(roundlen 4 $(filelen "$3"))]
fi
LENGTH=$(roundlen 4096 $TOTAL)

# Calculate the CRC value for the header

CRC32=$(
  (
   emit_header skip
   zpad_file 4 "$1"
   zpad_file 4 "$2"
   zpad_file 4 "$3"
   zpad $(padlen 4096 $TOTAL)
  ) | cksum -NILP
)

# Output the image to stdout

emit_header
zpad_file 4 "$1"
zpad_file 4 "$2"
zpad_file 4 "$3"
zpad $(padlen 4096 $TOTAL)
