#!/bin/bash

# Written by Steve Palm - n9yty@n9yty.com
# If this breaks your V-Accordion, I am not responsible. I have used
# it only on my FR-4xb and have not had a chance to test it with any
# other accordion. I think the basic principles here could apply to
# the FR-8x as well, but I have no way to test that. 
#
# Use with care
#
# Pipe the file into this script to process:
#
# bash ./FR_extract_upgs_from_upg_all.sh
# The file it processes is defined as "ALL_UPG" below


DATE=`date +"%Y%m%d%H%M%S"`
OUTDIR="FR4X_UPGS_$DATE"
mkdir -p $OUTDIR

ALL_UPG=UPG_ALL.UPA
ALL_UP_DATA=UPG_ALL.DATA

UPGS_PER_BANK=14

UPG_BINARY_HEADER="\\xEF\\xBB\\xBF\\x00\\x00\\x03\\x28"

# echo "Binary header for single set: $UPG_BINARY_HEADER"

# This is the XML that has to get inserted at the top of each user program file
#
# NOTE: TABS ARE EMBEDDED HERE!
#
read -r -d '' UPG_XML_HEADER <<'END_OF_XML'
<?xml version="1.0" encoding="utf-8"?>
<Roland_Fr_UPS>
	<SET size="26038" number="1" offset="0">
		<SC size="128" number="1" offset="0"/>
		<O_R size="86" number="26" offset="128"/>
		<OB_R size="66" number="7" offset="2364"/>
		<OBC_R size="72" number="8" offset="2826"/>
		<OFB_R size="72" number="8" offset="3402"/>
		<TR size="304" number="16" offset="3978"/>
		<BR size="84" number="16" offset="8842"/>
		<BCR size="96" number="16" offset="10186"/>
		<ORR size="64" number="26" offset="11722"/>
		<OBR size="42" number="7" offset="13386"/>
		<OBCR size="124" number="8" offset="13680"/>
		<OFBR size="118" number="8" offset="14672"/>
		<ORCH1 size="210" number="28" offset="15616"/>
		<ORCH2 size="152" number="28" offset="21496"/>
		<SC2 size="286" number="1" offset="25752"/>
	</SET>
</Roland_Fr_UPS>
END_OF_XML

UPB_BINARY_HEADER="\\xEF\\xBB\\xBF\\x00\\x00\\x03\\x29"

# This is the XML that has to get inserted at the top of each user bank file
#
# NOTE: TABS ARE EMBEDDED HERE!
#
read -r -d '' UPB_XML_HEADER <<'END_OF_XML'
<?xml version="1.0" encoding="utf-8"?>
<Roland_Fr_UPB>
	<SET size="26038" number="14" offset="0">
		<SC size="128" number="1" offset="0"/>
		<O_R size="86" number="26" offset="128"/>
		<OB_R size="66" number="7" offset="2364"/>
		<OBC_R size="72" number="8" offset="2826"/>
		<OFB_R size="72" number="8" offset="3402"/>
		<TR size="304" number="16" offset="3978"/>
		<BR size="84" number="16" offset="8842"/>
		<BCR size="96" number="16" offset="10186"/>
		<ORR size="64" number="26" offset="11722"/>
		<OBR size="42" number="7" offset="13386"/>
		<OBCR size="124" number="8" offset="13680"/>
		<OFBR size="118" number="8" offset="14672"/>
		<ORCH1 size="210" number="28" offset="15616"/>
		<ORCH2 size="152" number="28" offset="21496"/>
		<SC2 size="286" number="1" offset="25752"/>
	</SET>
</Roland_Fr_UPB>
END_OF_XML

# echo "$UPB_XML_HEADER" | hexdump -C
# echo "$UPG_XML_HEADER" | hexdump -C

XML_MATCH_POS=`strings -a -t d $ALL_UPG | grep '<?xml' | awk '{print $1}'`
XML_MATCH_STR=`strings -a -t d $ALL_UPG | grep '<?xml' | awk '{print $2}'`
# Since there may be a printable character in front of the <?xml, we have to handle that
XML_SUBPOS=`awk -v a="$XML_MATCH_STR" -v b="<?xml" 'BEGIN{print index(a,b)}'`
let XML_START="$XML_MATCH_POS + $XML_SUBPOS - 1"

XML_END=`strings -a -t d $ALL_UPG | grep '</Roland_Fr_UPA>' | awk '{print $1}'`
# Length of search string plus 0x0a
let XML_END+=17
let XML_SIZE="$XML_END - $XML_START"

# echo "$XML_START to $XML_END is $XML_SIZE"

XML=`dd skip=$XML_START bs=1 count=$XML_SIZE if=$ALL_UPG 2>/dev/null`

# echo -e "XML:\n$XML\n"

# Get size, count and offset for each section from the XML

UPG_SIZE=`echo $XML | xmllint --xpath 'string(/Roland_Fr_UPA/SET/@size)' -`
UPG_COUNT=`echo $XML | xmllint --xpath 'string(/Roland_Fr_UPA/SET/@number)' -`
UPG_SKIP=`echo $XML | xmllint --xpath 'string(/Roland_Fr_UPA/SET/@offset)' -`

UPLIST_SIZE=`echo $XML | xmllint --xpath 'string(/Roland_Fr_UPA/UPLIST/@size)' -`
UPLIST_COUNT=`echo $XML | xmllint --xpath 'string(/Roland_Fr_UPA/UPLIST/@number)' -`
UPLIST_SKIP=`echo $XML | xmllint --xpath 'string(/Roland_Fr_UPA/UPLIST/@offset)' -`

echo "$UPG_COUNT UPG pieces, each $UPG_SIZE bytes, starting at position $UPG_SKIP"
echo "$UPLIST_COUNT UPLIST pieces, each $UPLIST_SIZE bytes, starting at position $UPLIST_SKIP"
echo

function dump_section() {
  COUNT=$1
  SIZE=$2
  SKIP=$3
  FNSUFF=$4
  let size="$COUNT * $SIZE"
  let offset="$XML_END + $SKIP"
  echo "Dumping out $FNSUFF data ($size bytes)"
  dd bs=1 skip=$offset count=$size if=$ALL_UPG of=$OUTDIR/UPG_ALL_$FNSUFF.DATA 2>/dev/null
} 

dump_section $UPG_COUNT $UPG_SIZE $UPG_SKIP UPG 
dump_section $UPLIST_COUNT $UPLIST_SIZE $UPLIST_SKIP UPLIST


let NUM_BANKS="$UPG_COUNT / $UPGS_PER_BANK"

# Starting offset
pos=0

# Do it by bank, and individual user program files
for (( bank=1; bank<=$NUM_BANKS; bank++ ))
do
  BANK_NUM=$(printf "%03d" $bank)
  # New bank file, put out header
  BANK_FN=$OUTDIR/${BANK_NUM}_UPG_BANK.UPG

  # No newline
  echo -n -e "$UPB_BINARY_HEADER" >$BANK_FN
  # Keep the newline
  echo -e "$UPB_XML_HEADER" >>$BANK_FN

  # Now do the user program data pieces, creating individual files as we go
  for (( set=1; set<=$UPGS_PER_BANK; set++ ))
  do
    UPG_NUM=$(printf "%03d" $set)
    OUT_TMP=$OUTDIR/UPG_${UPG_NUM}.DATA
    echo "Working on program $UPG_NUM at pos $pos, bank $BANK_NUM"

    dd bs=1 skip=$pos count=$UPG_SIZE if=$OUTDIR/UPG_ALL_UPG.DATA of=$OUT_TMP 2>/dev/null

    # New individual user program file, put out header
    UPG_FN=$OUTDIR/${BANK_NUM}_${UPG_NUM}_UPG.UPG

    # echo "   BANK FILE: ${BANK_FN}"
    # echo "   UPG FILE:  ${UPG_FN}"

    # No newline
    echo -n -e "$UPG_BINARY_HEADER" >$UPG_FN

    # Keep the newline
    echo -e "$UPG_XML_HEADER" >>$UPG_FN

    # Copy the data to the bank file and the individual upg file
    cat $OUT_TMP >>$BANK_FN
    cat $OUT_TMP >>$UPG_FN

    # Remove our working data file
    rm $OUT_TMP
    
    # Update our offset value
    let pos+=$UPG_SIZE
  done
done

# rm UPG_ALL_UPG.DATA


