[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bach/build.bach/tools/Makefile.SDK b/src/bach/build.bach/tools/Makefile.SDK
new file mode 100644
index 0000000..603bc13
--- /dev/null
+++ b/src/bach/build.bach/tools/Makefile.SDK
@@ -0,0 +1,10 @@
+all:
+ifneq ($(filter mt6297_mifi mt6297_32_mifi gen98_aarch64_general, $(strip $(PROJECT))),)
+	rm -rf qemu
+	rm -fr backtrace
+	rm -fr project
+	rm -fr compress
+	rm -fr mkbootimg_dts/mkbootimg.zip
+	rm -fr mklfs/src
+	rm -fr mkfatftl/src
+endif
diff --git a/src/bach/build.bach/tools/MemoryDeviceList/MemoryDeviceList_MT6291_Internal.xls b/src/bach/build.bach/tools/MemoryDeviceList/MemoryDeviceList_MT6291_Internal.xls
new file mode 100755
index 0000000..d49162e
--- /dev/null
+++ b/src/bach/build.bach/tools/MemoryDeviceList/MemoryDeviceList_MT6291_Internal.xls
Binary files differ
diff --git a/src/bach/build.bach/tools/MemoryDeviceList/MemoryDeviceList_MT6297_Internal.xls b/src/bach/build.bach/tools/MemoryDeviceList/MemoryDeviceList_MT6297_Internal.xls
new file mode 100755
index 0000000..cb7c244
--- /dev/null
+++ b/src/bach/build.bach/tools/MemoryDeviceList/MemoryDeviceList_MT6297_Internal.xls
Binary files differ
diff --git a/src/bach/build.bach/tools/backtrace/lk_backtrace.sh b/src/bach/build.bach/tools/backtrace/lk_backtrace.sh
new file mode 100755
index 0000000..4181e4a
--- /dev/null
+++ b/src/bach/build.bach/tools/backtrace/lk_backtrace.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# usage: ./tools/backtrace/lk_backtrace.sh "/mtkeda/Bach/mt6297/20180310/arm/buildroot-2018.02/output/host/bin/arm-linux" "out/tmp/lk.elf" 0x42009D60 0x420111A1 0x4200E617 0x4200055F 0x4200AD3F 0x4200A74B 0x4200A731 0x4204A228
+CROSS=$1
+ADDR2LINE=$CROSS-addr2line
+LK_ELF=$2
+
+if [ "$#" -le "2" ]; then
+    echo "Usage: $0 <cross prefix> <lk elf> <address list>"
+    exit
+fi
+
+# $3 is address list
+shift 2
+
+RED_PAT="\033[37;41m"
+BLUE_PAT="\033[37;44m"
+RESET_PAT="\033[0m"
+
+echo ""
+echo "LK ELF: $LK_ELF"
+echo "Find Address: $*"
+
+echo "---------------- backtrace -------------"
+out=`$ADDR2LINE -e $LK_ELF -f -i $*`
+while read -r fname; do
+    addr=$1
+    read file
+    shift
+    echo -e "[$RED_PAT$addr$RESET_PAT]: $BLUE_PAT$fname$RESET_PAT, $file"
+    if [ "$#" -eq 0 ]; then
+        break
+    fi
+done <<< "$out"
+echo "-------------------------- -------------"
+echo ""
diff --git a/src/bach/build.bach/tools/cfgGen.pl b/src/bach/build.bach/tools/cfgGen.pl
new file mode 100644
index 0000000..18bf724
--- /dev/null
+++ b/src/bach/build.bach/tools/cfgGen.pl
@@ -0,0 +1,58 @@
+#!/usr/bin/perl -w
+
+#use strict;
+use lib "tools/perl";
+use Spreadsheet::ParseExcel;
+
+my $CUSTOMDIR;
+if ($#ARGV == 0) {
+	$CUSTOMDIR = $ARGV[0];
+} else {
+	$CUSTOMDIR = "$ENV{'WORKDIR'}/custom/build";
+}
+
+my $FileName = "$CUSTOMDIR/flash_partition.xls";
+my $parser   = Spreadsheet::ParseExcel->new();
+my $workbook = $parser->parse($FileName);
+
+die $parser->error(), ".\n" if ( !defined $workbook );
+
+# Iterate through all worksheets
+
+system("rm -rf $CUSTOMDIR/*.cfg");
+
+for my $worksheet ( $workbook->worksheets() ) {
+
+	# Find out the worksheet ranges
+	my ( $row_min, $row_max ) = $worksheet->row_range();
+	my ( $col_min, $col_max ) = $worksheet->col_range();
+
+	my $FILE = "$CUSTOMDIR/$worksheet->{Name}.cfg";
+	open (MYFILE, ">$FILE");
+	print MYFILE "############################################################################################################\n";
+	print MYFILE "#\n";
+	print MYFILE "#  Android Region Setting\n";
+	print MYFILE "#\n";
+	print MYFILE "############################################################################################################\n";
+	print MYFILE "linux_region:\n";
+	print MYFILE "  partitions:\n";
+
+	for my $row ( $row_min .. $row_max ) {
+		next if ($row == 0);
+		print MYFILE "    - partition:\n";
+		for my $col ( $col_min .. $col_max ) {
+
+		# Return the cell object at $row and $col
+		my $cell = $worksheet->get_cell( $row, $col );
+		next unless $cell;
+
+		if ($col == 0) { print MYFILE "        name: ", $cell->value(), "\n"}
+		if ($col == 1) { print MYFILE "        file: ", $cell->value(), "\n"}
+		if ($col == 3) { print MYFILE "        address: 0x", sprintf("%X", $cell->value()*1024*1024), "\n";}
+		if ($col == 4) { print MYFILE "        type: ", $cell->value(), "\t\t#raw, yaffs, yaffs2\n"}
+
+		}
+	}
+	print MYFILE "\n";
+	close (MYFILE);
+}
diff --git a/src/bach/build.bach/tools/cfgGen.sh b/src/bach/build.bach/tools/cfgGen.sh
new file mode 100755
index 0000000..094e610
--- /dev/null
+++ b/src/bach/build.bach/tools/cfgGen.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+
+MOLYCFG=$1
+INS_FILE="EXT_BOOTLOADER"
+GEN_FILE="$MOLYCFG.tmp"
+OPTION_TMP_PATH="$WORKDIR/bootloader_lte/out"
+TMP_FOLDER="$OUTDIR/tmp/$PRJ_FILENAME"
+LOGDIR="$OUTDIR/log"
+NOWDIR="`pwd`"
+
+#Copy again for the new *.cfg file.
+TARGETFOLDER="$WORKDIR/install/$CROSS_PREFIX/out"
+
+if [ $# -ge 2 ]; then
+	PRJ_FILENAME=$2
+else
+	PRJ_FILENAME=`cat build.sdk | grep PRJ_FILENAME | head -n 1 | sed -e 's/\$PRJ_FILENAME=\"//g' -e 's/\";//g'`
+fi
+CUSTOMDIR="$WORKDIR/custom/$PRJ_FILENAME/build"
+CUSSYS_DIR="$WORKDIR/custom/$PRJ_FILENAME/system"
+
+if [ ! -d $CUSTOMDIR ]; then
+	CUSTOMDIR="$WORKDIR/custom/DEFAULT/build"
+fi
+
+if [ "$PLATFORM_CHIP" == "MT6735" ]; then
+perl tools/cfgGen_sp.pl $CUSTOMDIR
+CFGS=`ls $CUSTOMDIR | grep .txt`
+else
+perl tools/cfgGen.pl $CUSTOMDIR
+CFGS=`ls $CUSTOMDIR | grep .cfg`
+fi
+
+cd $TARGETFOLDER
+
+if [ "$PLATFORM_CHIP" != "MT6291" ] && [ "$PLATFORM_CHIP" != "MT6292" ] && [ "$PLATFORM_CHIP" != "MT6293" ] && [ "$PLATFORM_CHIP" != "MT6295" ] && [ "$PLATFORM_CHIP" != "MT6297" ] && [ "$PLATFORM_CHIP" != "MT6298" ]; then
+    if [ "$PLATFORM_CHIP" == "MT6735" ]; then
+        perl $NOWDIR/tools/preCfgGen_sp.pl . $PROJECT_FLAVOR
+    	cp $MOLYCFG $GEN_FILE
+    else
+	    INS_LINE=`grep -n $INS_FILE $MOLYCFG |awk -F":" '{ print $1 }'`
+    	AFTER_INS_LINE=$((`tac $MOLYCFG | grep -n $INS_FILE |awk -F":" '{ print $1 }'` - 1))
+
+	    head -n $INS_LINE $MOLYCFG > $GEN_FILE
+    	echo "    - file: preloader_gfh.bin" >> $GEN_FILE
+    	tail -n $AFTER_INS_LINE $MOLYCFG >> $GEN_FILE
+    fi
+else
+	cat "$OPTION_TMP_PATH/~customIncDef.tmp" | sed -e 's|-I\.\/|-I'"$WORKDIR"'\/bootloader_lte\/|g' -e 's|-I\.\.\/|-I'"$WORKDIR"'\/|g' > $OPTION_TMP_PATH/customIncDef.tmp
+    if [ "$PLATFORM_CHIP" == "MT6297" ] || [ "$PLATFORM_CHIP" == "MT6298" ]; then
+        perl $NOWDIR/tools/preCfgGen.pl . `cd $TARGETFOLDER && ls *_BOOTLOADER_*` $CUSTOMDIR/customer_feature_option $CUSSYS_DIR "arm-linux-gcc -mthumb" @ $OPTION_TMP_PATH/customIncDef.tmp $TMP_FOLDER lk_gfh.bin > "$LOGDIR/cfggen.log"
+    else
+        perl $NOWDIR/tools/preCfgGen.pl . `cd $TARGETFOLDER && ls TK6291_BOOTLOADER_*` $CUSTOMDIR/customer_feature_option $CUSSYS_DIR "arm-linux-gcc -mthumb" @ $OPTION_TMP_PATH/customIncDef.tmp $TMP_FOLDER preloader_gfh.bin > "$LOGDIR/cfggen.log"
+    fi
+	cp $MOLYCFG $GEN_FILE
+fi
+
+if [ ! -f $TARGETFOLDER/$MOLYCFG ]; then
+	echo "$TARGETFOLDER/$MOLYCFG not found!"
+	exit 1
+fi
+rm -f $MOLYCFG
+
+for cfgfile in $CFGS
+do
+  if [ "$PLATFORM_CHIP" != "MT6291" ] && [ "$PLATFORM_CHIP" != "MT6292" ] && [ "$PLATFORM_CHIP" != "MT6293" ] && [ "$PLATFORM_CHIP" != "MT6295" ] && [ "$PLATFORM_CHIP" != "MT6297" ]  && [ "$PLATFORM_CHIP" != "MT6298" ]; then
+      if [ "$PLATFORM_CHIP" = "MT6735" ]; then
+          cp -f $GEN_FILE "$PLATFORM_CHIP"_$cfgfile
+          cat $CUSTOMDIR/$cfgfile >> "$PLATFORM_CHIP"_$cfgfile
+      else
+          cp -f $GEN_FILE MT6290_$cfgfile
+          cat $CUSTOMDIR/$cfgfile >> MT6290_$cfgfile
+      fi
+  else
+	cp -f $GEN_FILE "$PLATFORM_CHIP"_$cfgfile
+	cat $CUSTOMDIR/$cfgfile >> "$PLATFORM_CHIP"_$cfgfile
+  fi
+done
+
+rm -f $GEN_FILE
+
diff --git a/src/bach/build.bach/tools/cfgGen_EMI.pl b/src/bach/build.bach/tools/cfgGen_EMI.pl
new file mode 100644
index 0000000..75b84e7
--- /dev/null
+++ b/src/bach/build.bach/tools/cfgGen_EMI.pl
@@ -0,0 +1,613 @@
+#!/usr/bin/perl
+#
+#  Copyright Statement:
+#  --------------------
+#  This software is protected by Copyright and the information contained
+#  herein is confidential. The software may not be copied and the information
+#  contained herein may not be used or disclosed except with the written
+#  permission of MediaTek Inc. (C) 2006
+#
+#  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+#  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+#  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON
+#  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+#  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+#  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+#  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH
+#  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO
+#  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S
+#  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.
+#
+#  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE
+#  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+#  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+#  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO
+#  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+#
+#  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE
+#  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF
+#  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND
+#  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER
+#  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).
+#
+#*****************************************************************************
+#*
+#* Filename:
+#* ---------
+#*   cfgGen_EMI.pl
+#*
+#* Project:
+#* --------
+#*
+#*
+#* Description:
+#* ------------
+#*   This script generates EMI part in the CFG file for flash tool for SV5
+#*
+#* Author:
+#* -------
+#*   Marvin Lin  (mtk03483)
+#*
+#*============================================================================
+#*             HISTORY
+#* Below this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
+#*------------------------------------------------------------------------------
+#* $Revision$
+#* $Modtime$
+#* $Log$
+#*
+#* 09 21 2015 holmes.lin
+#* [MOLY00140089] Bach bootloader emi init for Elbrus FPGA (base on TK6291)
+#* support emi cfg on Elbrus FPGA (not official platform, quick workaround).
+#*
+#* 09 02 2015 holmes.lin
+#* [MOLY00140089] Bach bootloader emi init for Elbrus FPGA (base on TK6291)
+#* support Elbrus FPGA (platfrom=MT6292)
+#*
+#* 08 23 2013 guo-huei.chang
+#* [MOLY00034592] [MT6290][Combo Memory Support] Enable Combo Memory Feature
+#* 1. Update Memory Device List for MT6290(m)
+#* 2. Update/re-arch cfggen
+#* 3. Update/re-arch emigen
+#* 4. remove emigen/cfggen legacy
+#* 5. Add combo memory supporting
+#* 6. auto-generate all header files that emigen related
+#*
+#* 04 16 2013 guo-huei.chang
+#* [MOLY00013409] [MT6290 Bring-up] DRAMC/EMI related Code Update
+#* 1. update emigen to auto-gen all header files
+#* 2. update init mempll
+#* 3. update cfggen
+#*
+#* 03 22 2013 guo-huei.chang
+#* [MOLY00012511] update auto-k for MT6290 FPGA, MT6290 EVB, and MT6290M EVB
+#* 1. add custom folder for MT6290M
+#* 2. update custom_EMI.h and custom_EMI_release.h with emigen
+#* 3. update custom_EMI_INFO.h with manual check in
+#* 4. update cfggen
+#*
+#* 03 19 2013 guo-huei.chang
+#* [MOLY00011607] update custom_EMI function and cfggen EMI part
+#* integrate CBr that update emigen and cfggen
+#*
+#* 03 08 2013 guo-huei.chang
+#* [MOLY00011607] update custom_EMI function and cfggen EMI part
+#* update custom_EMI function into custom_EMI_MT6290.c and remove it in devdrv_fakeapi.c
+#* update cfgGen_EMI.pl for MT6290
+#*
+#* 08 16 2012 marvin.lin
+#* [MOLY00002193] [MemoryStorage][Auto-Gen][EMI Gen/CFG Gen][Request For Design Change] support MT6280 combo memory
+#* .
+#*
+#*
+#*------------------------------------------------------------------------------
+#* Upper this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
+#*============================================================================
+#****************************************************************************/
+
+#****************************************************************************
+# Included Modules
+#****************************************************************************
+use strict;
+use warnings;
+use File::Spec ();
+no warnings 'redefine';
+local $| = 1;  # auto flush print function
+
+#****************************************************************************
+# Constants
+#****************************************************************************
+my $CFGGEN_EMI_VERNO = "v0.04";
+                      # v0.04, modify for MT6297
+                      # v0.03, Fix compile error when $PLATFORM is not found
+                      # v0.02, Support MT6255 SF+DDR or DISCRETE_NAND case
+                      # v0.01, Fix unable to pre-compie custom_EMI_MT6256.c when make new issue
+                      # v0.00, initial version
+
+#****************************************************************************
+# Input Parameters and Global Variables
+#****************************************************************************
+my $DebugPrint = 0;
+
+#****************************************************************************
+# subroutine:  error_handler
+# input:       $error_msg:     error message
+#****************************************************************************
+sub error_handler
+{
+    my ($error_msg, $file, $line_no) = @_;
+    my $final_error_msg = "CFGGEN ERROR: $error_msg at $file line $line_no\n";
+    print $final_error_msg;
+    die $final_error_msg;
+}
+
+#****************************************************************************
+# subroutine:  get_mem_dev_h_value
+# input:       $str_BB_path:  BB folder path
+# input:       $href_mem_dev: reference of hash of custom_MemoryDevice.h configurations
+#****************************************************************************
+sub get_mem_dev_h_value
+{
+    print((caller(0))[3], "\n");
+    my ($str_BB_path, $href_makefile_options) = @_;
+    my %mem_dev_options;
+    my $CUSTOM_MEM_DEV_H = $str_BB_path . "\/custom_MemoryDevice.h";
+
+    open (MEMDEVH_HANDLE, "<$CUSTOM_MEM_DEV_H") or &error_handler("$CUSTOM_MEM_DEV_H: $!", __FILE__, __LINE__);
+    while (<MEMDEVH_HANDLE>) {
+        if (/^#define\s+(\w+)\s+\(?(\w*)\)?/)
+        {
+            my $option = $1;
+            my $value  = $2;
+            &error_handler("$CUSTOM_MEM_DEV_H: $option redefined in custom_MemoryDevice.h!", __FILE__, __LINE__) if defined($mem_dev_options{$option});
+            $mem_dev_options{$option} = ((!defined $value) or ($value eq ''))? 'TRUE': $value;
+        }
+    }
+    close (MEMDEVH_HANDLE);
+
+    if ((!defined $href_makefile_options->{'combo_memory_support'}) or ($href_makefile_options->{'combo_memory_support'} eq 'FALSE'))
+    {
+        $mem_dev_options{COMBO_MEM_ENTRY_COUNT} = 1;
+    }
+
+    return %mem_dev_options;
+}
+
+#****************************************************************************
+# subroutine:  get_emi_settings
+# return:      EMI settings
+#****************************************************************************
+sub get_emi_settings_for_91
+{
+    my ($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, $href_mem_dev_options) = @_;
+    my @emi_settings;
+
+    ### Pre-compile custom_EMI.c
+    my ($custom_EMI_c_file);
+    my $custom_emi_command;
+    $custom_EMI_c_file = $str_BB_path . "\/custom_EMI_INFO.c";
+
+    print "custom_EMI.c = $custom_EMI_c_file\n";
+    $custom_emi_command = "$cc_cmd $via_cmd$option_tmp_file -E $custom_EMI_c_file > " . $custom_emi_temp_local . "\/" . "~custom_EMI_c.tmp";
+    print "$custom_emi_command\n";
+
+    my $status = system($custom_emi_command);
+    &error_handler("\/tools\/cfgGen_EMI.pl: pre-compile $custom_EMI_c_file erro!", __FILE__, __LINE__) if ($status != 0);
+
+    ### Get Memory Type
+    my ($emi_clk, $emi_dev_type, $memory_type);
+    my $CUSTOM_EMI_RELEASE_H;
+    $CUSTOM_EMI_RELEASE_H = $str_BB_path . "\/custom_EMI_release.h";
+
+    open (CUSTOM_EMI_RELEASE_H, "<$CUSTOM_EMI_RELEASE_H") or &error_handler("$CUSTOM_EMI_RELEASE_H: file error!", __FILE__, __LINE__);
+    while (<CUSTOM_EMI_RELEASE_H>)
+    {
+        if (/#define __EMI_CLK_(\d+)MHZ__/)
+        {
+            $emi_clk = $1;
+        }
+        elsif (/#define __EMI_DEVICE_(\w+)__/)
+        {
+            $emi_dev_type = $1;
+        }
+    }
+    close CUSTOM_EMI_RELEASE_H;
+    if($href_MAKEFILE_OPTIONS->{'sip_ram_size'} ne "NONE")
+    {
+        $memory_type = "SIP_";
+    }
+    $memory_type .= $emi_dev_type . "_" . $emi_clk . "MHZ";
+
+    ### Read EMI settings from ~custom_EMI_c.tmp
+    my $file_content;
+    my $custom_emi_path;
+    $custom_emi_path = "<" . $custom_emi_temp_local . "\/~custom_EMI_c.tmp";
+
+    print "path:$custom_emi_path\n";
+    open (CUSTOM_EMI_C_TMP, $custom_emi_path) or &error_handler("~custom_EMI_c.tmp: file error!", __FILE__, __LINE__);
+    {
+        local $/;
+        $file_content = <CUSTOM_EMI_C_TMP>;
+    }
+    close CUSTOM_EMI_C_TMP;
+
+    ### Get EMI_INFO type declaration
+    my $EMI_INFO_decl_str;
+    my %EMI_INFO_decl_hash;  # key: EMI register; value: index
+    my $EMI_INFO_decl_count = 0;
+    if ($file_content =~ /typedef\s*struct\s*\{([\S|\s]+?)\}\s*MTK_EMI_Info/)
+    {
+        $EMI_INFO_decl_str = $1;
+    }
+    while ($EMI_INFO_decl_str =~ /\{/)  # Skip all other contents between the beginning { and the real EMI info
+    {
+        $EMI_INFO_decl_str = $';
+    }
+    while ($EMI_INFO_decl_str =~ /unsigned int (\w+)\;/)
+    {
+        $EMI_INFO_decl_hash{$1} = $EMI_INFO_decl_count;
+        $EMI_INFO_decl_count++;
+        $EMI_INFO_decl_str = $';
+    }
+    print "EMI_INFO_decl_hash:\n" if ($DebugPrint == 1);
+    foreach my $idx (keys %EMI_INFO_decl_hash)
+    {
+        print "$idx\n" if ($DebugPrint == 1);
+    }
+
+    ### Trim EMI Info contents string
+    my $EMI_INFO_value_str;
+    if ($file_content =~ /MTK_EMI_Info\s*EMI_INFO\[\]\s*\=\s*\{([\s|\S]+?)\}\;/)
+    {
+        $EMI_INFO_value_str = $1;
+    }
+    $EMI_INFO_value_str =~ s/\s//g;  # EMI_INFO_value_str becomes {EMI_INFO set 1},{EMI_INFO set 2},......,{EMI_INFO set n}
+
+    my @EMI_INFO_value_array;
+    @EMI_INFO_value_array = split(/\},\{/,$EMI_INFO_value_str);
+
+    for my $idx (0..$#EMI_INFO_value_array)
+    {
+        $EMI_INFO_value_array[$idx] =~ s/^,//;  # Remove the beginning ,
+        $EMI_INFO_value_array[$idx] =~ s/^{//;  # Remove the beginning {
+        $EMI_INFO_value_array[$idx] =~ s/,$//;  # Remove the last ,
+        $EMI_INFO_value_array[$idx] =~ s/,}$//;  # Remove the last ,}
+        print "\$EMI_INFO_value_array[$idx]:\n$EMI_INFO_value_array[$idx]\n" if ($DebugPrint == 1);
+    }
+
+    for my $combo_idx (1..$href_mem_dev_options->{COMBO_MEM_ENTRY_COUNT})
+    {
+        ### Get EMI Info contents
+        my %cur_emi_info_value_list;
+        foreach (split(/,/, $EMI_INFO_value_array[$combo_idx-1]))
+        {
+            if ((/\.(\w+)=\((0x[\w]{1,8})\)/) or (/\.(\w+)=(0x[\w]{1,8})/))
+            {
+                $cur_emi_info_value_list{$1} = $2;
+            }
+        }
+        $emi_settings[$combo_idx] = <<"__TEMPLATE";
+        memory_type: $memory_type
+        EMI_Setting:
+__TEMPLATE
+        foreach my $key (sort keys %cur_emi_info_value_list)
+        {
+            if ($key =~ /(\w+)_val/)
+            {
+                $emi_settings[$combo_idx] .= "            " . uc($1) . ": " . $cur_emi_info_value_list{$key} . "\n";
+            }
+            else
+            {
+                $emi_settings[$combo_idx] .= "            " . uc($key) . ": " . $cur_emi_info_value_list{$key} . "\n";
+            }
+        }
+        print "combo_mem_idx:$combo_idx\n" if ($DebugPrint == 1);
+        print "$emi_settings[$combo_idx]\n" if ($DebugPrint == 1);
+    }
+
+    return @emi_settings;
+}
+
+sub get_emi_settings_for_92
+{
+    my ($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, $href_mem_dev_options) = @_;
+    my @emi_settings;
+
+    $emi_settings[1] = <<"__TEMPLATE";
+        memory_type: LPDDR2_166MHZ
+        EMI_Setting:
+            DRAM_AC_DERATING: 0x20300431
+            DRAM_LPDDR2_MR2: 0x00000003
+            DRAM_LPDDR2_MR3: 0x00000002
+            DRAMC_ACTIM0: 0x3315C140
+            DRAMC_ACTIM1: 0x00000000
+            DRAMC_ADDRODLY: 0x00000000
+            DRAMC_CLKODLY: 0x00000000
+            DRAMC_CONF1: 0x00008281
+            DRAMC_CONF2: 0x03000027
+            DRAMC_DDR2CTL: 0x200011D1
+            DRAMC_DQODLY: 0x00000000
+            DRAMC_DQSODLY: 0x00000000
+            DRAMC_DRVCTL0: 0xAA22AA22
+            DRAMC_DRVCTL1: 0xAA22AA22
+            DRAMC_GDDR3CTL1: 0x01000000
+            DRAMC_MISCTL0: 0x07100000
+            DRAMC_PADCTL4: 0x00000000
+            DRAMC_PD_CTRL: 0x00641542
+            DRAMC_TEST2_3: 0xBF0C0080
+            EMI_CONA: 0x00002122
+            EMI_TESTD: 0x00000100
+__TEMPLATE
+    return @emi_settings;
+}
+
+sub get_emi_settings_for_98
+{
+    my ($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, $href_mem_dev_options) = @_;
+    my $emi_custom_h = File::Spec->catfile($str_BB_path, "custom_emi.h");
+    my @emi_settings;
+
+    $emi_settings[1] = <<"__TEMPLATE";
+        memory_type: $href_mem_dev_options->{'MEMORY_DEVICE_TYPE'}
+        EMI_Setting:
+__TEMPLATE
+
+    open FHL, "<", $emi_custom_h or error_handler("$emi_custom_h: $!", __FILE__, __LINE__);
+    while (<FHL>) {
+        s/\/\*.*\*\///;
+        s/,$//;
+        if (/\s*\.(\w+)\s*\=\s*([\{0-9A-Fa-fx, \}]+)/) {
+            my $k = $1;
+            my $v = $2;
+            $v =~ s/\{/\[/;
+            $v =~ s/\}/\]/;
+            print "-> $k = $v\n" if ($DebugPrint == 1);
+            $emi_settings[1] .= <<"__TEMPLATE";
+            $k: $v
+__TEMPLATE
+        }
+    }
+    close FHL;
+
+    return @emi_settings;
+}
+
+sub get_emi_settings
+{
+    print((caller(0))[3], "\n");
+    my ($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, $href_mem_dev_options) = @_;
+    my @emi_settings;
+
+    if ($bb eq 'MT6291') {
+        return &get_emi_settings_for_91(@_);
+    } elsif ($bb =~ /^MT629[2357]$/) {
+        return &get_emi_settings_for_92(@_);
+    } elsif ($bb eq 'MT6298' and $href_MAKEFILE_OPTIONS->{'board_ver'} eq 'FPGA') {
+        return &get_emi_settings_for_92(@_);
+    } elsif ($bb =~ /^MT6298$/) {
+        return &get_emi_settings_for_98(@_);
+    } else {
+        error_handler("not support $bb", __FILE__, __LINE__);
+    }
+}
+
+#****************************************************************************
+# subroutine:  get_flash_settings
+# return:      Flash settings
+#****************************************************************************
+sub get_flash_settings_for_91
+{
+    my ($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, $href_mem_dev_options) = @_;
+    my @flash_settings;
+
+    ### Read flash ID from combo_flash_id.h
+    my (@flash_id_str_list, @flash_id, @valid_id_length_list);
+    my $flash_id_template;
+    my $decide_flash_type = "NAND";
+    my $mcp_count = 0;
+    my $COMBO_FLASH_ID_H;
+    $COMBO_FLASH_ID_H = $str_BB_path . "\/combo_flash_id.h";
+
+    open (COMBO_FLASH_ID_H, "<$COMBO_FLASH_ID_H") or &error_handler("$COMBO_FLASH_ID_H: file error!", __FILE__, __LINE__);
+    while (<COMBO_FLASH_ID_H>)
+    {
+        if (/\s+(\d+),\s*\/\/\s*Valid\s+ID\s+length/)
+        {
+            $valid_id_length_list[$mcp_count] = $1;
+        }
+        elsif (/\s+\{(.+)\}\s*\/\/\s*Flash\s+ID/)
+        {
+            $flash_id_str_list[$mcp_count] = $1;
+            ##$mcp_count++;
+            if($href_MAKEFILE_OPTIONS->{'serial_flash_support'} eq 'TRUE')##SF align with NAND, restruct 16bit type ID string
+            {
+                    $flash_id_template = undef;
+            		$decide_flash_type = "SF";
+            		$flash_id_str_list[$mcp_count] =~ s/\{//;
+            		$flash_id_str_list[$mcp_count] =~ s/\}//;
+            		$flash_id_str_list[$mcp_count] =~ s/\s+//g;
+            		print "Before restruct id string is : $flash_id_str_list[$mcp_count]\n";
+            		@flash_id = split /\,/, $flash_id_str_list[$mcp_count];
+            		my $cur_flash_id = "0xFFFF";
+            		for (0..7)  # there are totally 8 flash ID
+            		{
+            			$cur_flash_id = sprintf("0x%04X", hex($flash_id[$_]));
+            			print "cur_flash_id is : $cur_flash_id\n";
+            			if(7 == $_)
+            			{
+            				$flash_id_template .= "$cur_flash_id\n";
+            				###$block_info_lines .=  "   \{$tmp_start, $tmp_size\},\n";
+            			}
+            			else
+            			{
+            				$flash_id_template .= "$cur_flash_id, \n";
+            			}
+            			print "After restruct template id string is : $flash_id_template\n";
+            			chomp($flash_id_template);
+            		}
+            		$flash_id_str_list[$mcp_count] = $flash_id_template;
+            		print "After restruct id string is : $flash_id_template\n";
+            }
+            $mcp_count++;
+        }
+    }
+    close COMBO_FLASH_ID_H;
+
+
+    #Parse combo_nfi_config.h to gen NAND Parameters for Flashtool Download
+    my @combo_nfi_info_struct;
+    if($href_MAKEFILE_OPTIONS->{'nand_support'} eq 'TRUE')
+    {
+        my $COMBO_FLASH_CONFIG_H;##NAND Flash Config File
+        $COMBO_FLASH_CONFIG_H = $str_BB_path . "\/combo_nfi_config.h";
+
+        my $combo_flash_idx = -1;
+        print "$COMBO_FLASH_CONFIG_H\n";
+        open (COMBO_FLASH_CONFIG_H, "<$COMBO_FLASH_CONFIG_H") or &error_handler("$COMBO_FLASH_CONFIG_H: file error!", __FILE__, __LINE__);
+        while (<COMBO_FLASH_CONFIG_H>)
+        {
+            if (/\s*0x(\w+),\s*\/\/\s*NFI_(\w+)_(\w+)/)
+            {
+                #print "1-[$combo_flash_idx]: $1, $2, $3\n";
+                $combo_nfi_info_struct[$combo_flash_idx] .= "\n            " ."NFI_" ."$2_" . "$3" .": " ."0x$1";
+            }
+            elsif (/\s*(\w+),\s*\/\/\s*NFI_(\w+)_(\w+)/)
+            {
+                #print "2-[$combo_flash_idx]: $1, $2, $3\n";
+                $combo_nfi_info_struct[$combo_flash_idx] .= "\n            " ."NFI_" ."$2_" . "$3" .": " ."$1";
+            }
+            elsif (/\s*\{(.+)\},\s*\/\/\s*NFI_(\w+)_(\w+)/)
+            {
+                #print "3-[$combo_flash_idx]: $1, $2, $3\n";
+                if($2 eq 'BB')
+                {
+                    $combo_nfi_info_struct[$combo_flash_idx] .= "\n            " ."NFI_" ."$2_" . "$3" .": " ."[$1]";
+                }
+                else
+                {
+                    $combo_flash_idx++;#to calculate combo idx
+                    $combo_nfi_info_struct[$combo_flash_idx] .= "\n            " ."NFI_" ."$2_" . "$3" .": " ."$1";#NFI_DeviceName is the first parse line of combo flash
+                }
+            }
+        }
+    }
+
+    for (0..($mcp_count-1))
+    {
+        my $combo_idx = $_ + 1;
+
+
+        $flash_settings[$combo_idx] .= <<"__TEMPLATE";
+      - flash_info:
+            flash_type: $decide_flash_type
+            id_length: $valid_id_length_list[$_]
+__TEMPLATE
+        if (defined $combo_nfi_info_struct[$_])
+        {
+            $flash_settings[$combo_idx] .= <<"__TEMPLATE";
+            flash_id: [$flash_id_str_list[$_]]$combo_nfi_info_struct[$_]
+__TEMPLATE
+        }
+        else
+        {
+            $flash_settings[$combo_idx] .= <<"__TEMPLATE";
+            flash_id: [$flash_id_str_list[$_]]
+__TEMPLATE
+        }
+    }
+
+    return @flash_settings;
+}
+
+sub get_flash_settings_for_92
+{
+    my ($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, $href_mem_dev_options) = @_;
+    my @flash_settings;
+
+    $flash_settings[1] = <<"__TEMPLATE";
+      - flash_info:
+            flash_type: NAND
+            id_length: 5
+            flash_id: [0x002C, 0x00A3, 0x0090, 0x0026, 0x0064, 0x0000, 0x0000, 0x0000]
+            NFI_Device_Name: "MICRON MT29F8G08ABBCA"
+            NFI_Device_Size: 1024
+            NFI_Block_Size: 0x40000
+            NFI_ACCCON_VAL: 0x00001111
+            NFI_RDYTO_VAL: 0x0003ffff
+            NFI_ADNOB_VAL: 0x00000032
+            NFI_BB_MARK: [0x00AD0000, 0x00EE0000, 0x00EE0000, 0x00EE0000]
+            NFI_DC_VAL: 0x2
+            NFI_DRV_SET: 0xAD
+            NFI_PGFMT_VAL: 0x00008832
+            NFI_TNUM_VAL: 0x4
+__TEMPLATE
+
+    return @flash_settings;
+}
+
+sub get_flash_settings
+{
+    print((caller(0))[3], "\n");
+    my ($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, $href_mem_dev_options) = @_;
+
+    if ($bb eq "MT6291") {
+        return &get_flash_settings_for_91(@_);
+    } elsif ($bb =~ /^MT629[2357]$|^MT6298$/) {
+        return &get_flash_settings_for_92(@_);
+    } else {
+        error_handler("not support $bb", __FILE__, __LINE__);
+    }
+}
+
+#****************************************************************************
+# subroutine:  gen_external_memory_setting
+# input:       $str_BB_path:  BB folder path
+# return:      External Memory Setting
+#****************************************************************************
+sub gen_external_memory_setting
+{
+    print((caller(0))[3], "\n");
+    my ($str_BB_path, $str_board_ver, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS) = @_;
+    my (@emi_settings, @flash_settings);
+    my %custom_mem_dev_options;
+
+    %custom_mem_dev_options = &get_mem_dev_h_value($str_BB_path, $href_MAKEFILE_OPTIONS);
+
+    @emi_settings = &get_emi_settings($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, \%custom_mem_dev_options);
+    @flash_settings = &get_flash_settings($str_BB_path, $bb, $cc_cmd, $via_cmd, $option_tmp_file, $custom_emi_temp_local, $href_MAKEFILE_OPTIONS, \%custom_mem_dev_options);
+
+    my $combo_mem_param = "";
+    for my $combo_idx (1..$custom_mem_dev_options{COMBO_MEM_ENTRY_COUNT})
+    {
+        $combo_mem_param .= <<"__TEMPLATE";
+       # EMI $combo_idx
+$flash_settings[$combo_idx]
+$emi_settings[$combo_idx]
+__TEMPLATE
+    }
+
+    my $para_ver = ($bb eq 'MT6280')?        "v2":
+                   ($bb eq 'MT6290')?        "v3.1":
+                   ($bb eq 'MT6291')?        "v3.2":
+                   ($bb =~ /^MT629[2357]$/)? "v3.1":
+                   ($bb eq 'MT6298' and $href_MAKEFILE_OPTIONS->{'board_ver'} eq "FPGA")? "v3.1":
+                   ($bb =~ /^MT6298$/)?      "v4.0": "v4.0";
+
+    my $template = <<"__TEMPLATE";
+############################################################################################################
+#
+#  External Memory Setting
+#
+############################################################################################################
+
+external_memory:
+    parameters_version: $para_ver
+    PMIC: $href_MAKEFILE_OPTIONS->{'pmic'}
+    parameters:
+$combo_mem_param
+__TEMPLATE
+}
+
+return 1;
diff --git a/src/bach/build.bach/tools/cfgGen_flash.pl b/src/bach/build.bach/tools/cfgGen_flash.pl
new file mode 100644
index 0000000..05a59e5
--- /dev/null
+++ b/src/bach/build.bach/tools/cfgGen_flash.pl
@@ -0,0 +1,246 @@
+#!/usr/bin/perl

+#

+#  Copyright Statement:

+#  --------------------

+#  This software is protected by Copyright and the information contained

+#  herein is confidential. The software may not be copied and the information

+#  contained herein may not be used or disclosed except with the written

+#  permission of MediaTek Inc. (C) 2006

+#

+#  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES

+#  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")

+#  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON

+#  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,

+#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF

+#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.

+#  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE

+#  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR

+#  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH

+#  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO

+#  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S

+#  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.

+#

+#  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE

+#  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,

+#  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,

+#  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO

+#  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.

+#

+#  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE

+#  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF

+#  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND

+#  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER

+#  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).

+#

+#*****************************************************************************

+#*

+#* Filename:

+#* ---------

+#*   cfgGen_flash.pl

+#*

+#* Project:

+#* --------

+#*

+#*

+#* Description:

+#* ------------

+#*   This script generates flash part in the CFG file for flash tool for SV5

+#*

+#* Author:

+#* -------

+#*   Marvin Lin  (mtk03483)

+#*

+#*============================================================================

+#*             HISTORY

+#* Below this line, this part is controlled by PVCS VM. DO NOT MODIFY!!

+#*------------------------------------------------------------------------------

+#* $Revision$

+#* $Modtime$

+#* $Log$

+#*

+#*

+#*------------------------------------------------------------------------------

+#* Upper this line, this part is controlled by PVCS VM. DO NOT MODIFY!!

+#*============================================================================

+#****************************************************************************/

+

+#****************************************************************************

+# Included Modules

+#****************************************************************************

+use strict;

+

+1;

+

+#****************************************************************************

+# Constants

+#****************************************************************************

+my $CFGGEN_FLASH_VERNO = " V0.00";

+                        #  v0.00 , initial version 

+

+#****************************************************************************

+# Input Parameters and Global Variables

+#****************************************************************************

+

+#****************************************************************************

+# subroutine:  get_mem_dev_h_value

+# input:       $str_BB_path:  BB folder path

+# input:       $href_mem_dev: reference of hash of custom_MemoryDevice.h configurations

+#****************************************************************************

+sub get_mem_dev_h_value

+{

+    my ($str_BB_path, $href_mem_dev) = @_;

+    

+    my $CUSTOM_MEM_DEV_H = $str_BB_path . "\\custom_MemoryDevice.h";

+

+    open (MEMDEVH_HANDLE, "<$CUSTOM_MEM_DEV_H") or &error_handler("$CUSTOM_MEM_DEV_H: file error!", __FILE__, __LINE__);

+    while (<MEMDEVH_HANDLE>) {

+        if (/^#define\s+(\w+)\s+\((\w*)\)/ || /^#define\s+(\w+)\s+(\w*)/)

+        {

+            my $option = $1;

+            my $value  = $2;

+            

+            &error_handler("$CUSTOM_MEM_DEV_H: $option redefined in custom_MemoryDevice.h!", __FILE__, __LINE__) if defined($href_mem_dev->{$option});

+            if ((!defined $value) or ($value eq ''))

+            {

+                $href_mem_dev->{$option} = 'TRUE';

+            }

+            else

+            {

+                if ($option =~ /CS(\d+)_PART_NUMBER/)

+                {

+                    if (($href_mem_dev->{MEMORY_DEVICE_TYPE} eq 'LPSDRAM') or ($href_mem_dev->{MEMORY_DEVICE_TYPE} eq 'LPDDR') or ($href_mem_dev->{MEMORY_DEVICE_TYPE} eq 'LPDDR2'))

+                    {

+                        if ($value =~ /(\w+)_([A-Za-z0-9]+EVB)/ or $value =~ /(\w+)_(Sapphire28)/i)

+                        {

+                            $href_mem_dev->{$option} = $1;

+                        }

+                        else

+                        {

+                            $href_mem_dev->{$option} = $value;

+                        }

+                    }

+                    elsif ($href_mem_dev->{MEMORY_DEVICE_TYPE} eq 'NOR_LPSDRAM_MCP')

+                    {

+                        if ($value =~ /(\w+)_([A-Za-z0-9]+EVB)/ or $value =~ /(\w+)_(Sapphire28)/i)

+                        {

+                            $href_mem_dev->{$option} = $1;

+                        }

+                        else

+                        {

+                            $href_mem_dev->{$option} = $value;

+                        }

+                    }

+                    else

+                    {

+                        $href_mem_dev->{$option} = $value;

+                    }

+                }

+                else

+                {

+                    $href_mem_dev->{$option} = $value;

+                }

+            }

+        }

+    }

+    close (MEMDEVH_HANDLE);

+}

+

+#****************************************************************************

+# subroutine:  error_handler

+# input:       $error_msg:     error message

+#****************************************************************************

+sub error_handler

+{

+    my ($error_msg, $file, $line_no) = @_;

+    

+    my $final_error_msg = "CFGGEN ERROR: $error_msg at $file line $line_no\n";

+    print $final_error_msg;

+    die $final_error_msg;

+}

+

+#****************************************************************************

+# subroutine:  gen_flash_info

+# return:      Flash information

+#****************************************************************************

+sub gen_flash_info

+{

+    my ($str_BB_path, $str_board_ver, $bb) = @_;

+    

+    my %MEM_DEV_H_Value;

+    &get_mem_dev_h_value($str_BB_path, \%MEM_DEV_H_Value);

+    

+    ### DRAM

+    my $is_dram = (($MEM_DEV_H_Value{MEMORY_DEVICE_TYPE} eq 'LPSDRAM') or ($MEM_DEV_H_Value{MEMORY_DEVICE_TYPE} eq 'LPDDR') or ($MEM_DEV_H_Value{MEMORY_DEVICE_TYPE} eq 'LPDDR2')) ? 'TRUE' : 'FALSE';

+    

+    ### Flash type

+    my $flash_type;

+    if ($is_dram eq 'TRUE')

+    {

+        $flash_type = "[NAND Flash]";

+    }

+    elsif ($MEM_DEV_H_Value{MEMORY_DEVICE_TYPE} eq 'SERIAL_FLASH')

+    {

+        $flash_type = "[Serial Flash]";

+    }

+    else

+    {

+        $flash_type = "[NOR Flash]";

+    }

+

+    ### Read custom_flash.h for flash ID and NOR flash size

+    my ($flash_id_str, @flash_id, $flash_id_template);

+    my ($nor_flash_size_mb, $nor_flash_size_template);

+    my $CUSTOM_FLASH_H   = $str_BB_path . "\\custom_flash.h";

+    open (CUSTOM_FLASH_H, "<$CUSTOM_FLASH_H") or &error_handler("$CUSTOM_FLASH_H: file error!", __FILE__, __LINE__);

+    while (<CUSTOM_FLASH_H>)

+    {

+        if (/const\s+kal_char\s+FLASH_ID\[\]\s+=\s+\"(.+)\";/)

+        {

+            $flash_id_str = $1;

+        }

+        elsif (/NOR_FLASH_SIZE\(Mb\):\s+(\d+)/)

+        {

+            $nor_flash_size_mb = $1;

+        }

+    }

+    close CUSTOM_FLASH_H;

+    if ($MEM_DEV_H_Value{CS0_PART_NUMBER} eq 'EHD013111MA_60_MTKINTERN')  # work-around for QC

+    {

+        $flash_id_str = "0x0020,0x00BA,0x0010,0x0055";

+    }

+    print "FLASH_ID = $flash_id_str\n";

+    $flash_id_str =~ s/\{//;

+    $flash_id_str =~ s/\}//;

+    $flash_id_str =~ s/\s+//g;

+    @flash_id = split /\,/, $flash_id_str;

+    for (0..7)  # there are totally 8 flash ID

+    {

+        my $idx = $_ + 1;

+        my $cur_flash_id = (($is_dram eq 'TRUE') or ($MEM_DEV_H_Value{MEMORY_DEVICE_TYPE} eq 'SERIAL_FLASH')) ? "0xFF" : "0xFFFF";

+        if (defined $flash_id[$_])

+        {

+            if (($is_dram eq 'TRUE') or ($MEM_DEV_H_Value{MEMORY_DEVICE_TYPE} eq 'SERIAL_FLASH'))

+            {

+                $cur_flash_id = sprintf("0x%02X", hex($flash_id[$_]));

+            }

+            else

+            {

+                $cur_flash_id = sprintf("0x%04X", hex($flash_id[$_]));

+            }

+        }

+        $flash_id_template .= "ID$idx=$cur_flash_id\n";

+    }

+    chomp $flash_id_template;

+    

+    if ($is_dram eq 'FALSE')

+    {

+        $nor_flash_size_template = sprintf("Flash_Size=0x%x", $nor_flash_size_mb/8*1024*1024);

+    }

+    

+    my $template = <<"__TEMPLATE";

+$flash_type

+$flash_id_template

+$nor_flash_size_template

+

+__TEMPLATE

+}

diff --git a/src/bach/build.bach/tools/cfgGen_module.pl b/src/bach/build.bach/tools/cfgGen_module.pl
new file mode 100644
index 0000000..cc0caf7
--- /dev/null
+++ b/src/bach/build.bach/tools/cfgGen_module.pl
@@ -0,0 +1,171 @@
+#!/usr/bin/perl -w
+
+#use strict;
+use lib "tools/perl";
+use Spreadsheet::ParseExcel;
+my $CUSTOMDIR;
+my $OUTDIR;
+my $OUTSUBDIR;
+my $TYPE;
+
+if ($#ARGV < 2) {
+    print "Usage:   \n";
+    print "  cfgGen_module.pl CUSTOMDIR TYPE OUTDIR\n";
+    exit -1;
+}
+
+$CUSTOMDIR = $ARGV[0]."/".$ARGV[1];
+$TYPE = $ARGV[1];
+$OUTDIR = $ARGV[2];
+$OUTSUBDIR = $ARGV[2]."/".$ARGV[1];
+
+if (!-e $CUSTOMDIR) {
+    print $CUSTOMDIR," not exist!\n";
+    exit -1;
+}
+
+if ($TYPE eq "nf") {
+    genall();
+}
+if ($TYPE eq "sf") {
+    genall();
+}
+if ($TYPE eq "bf") {
+    genall();
+}
+
+sub genall {
+    my $FileName = "$CUSTOMDIR/flash_partition.xls";
+    my $parser   = Spreadsheet::ParseExcel->new();
+    my $workbook = $parser->parse($FileName);
+
+    die $parser->error(), ".\n" if ( !defined $workbook );
+
+    for my $worksheet ( $workbook->worksheets() ) {
+        if ($worksheet->{Name} =~ m/HOST/) {
+            gen_host_file($worksheet);
+        }
+        if ($worksheet->{Name} =~ m/FDA/) {
+            gen_fda_cfg($worksheet);
+        }
+        if ($worksheet->{Name} =~ m/SPI/) {
+            gen_spi_cfg();
+        }
+    }
+}
+
+sub gen_host_file {
+    my ($worksheet) = @_;
+    my ( $row_min, $row_max ) = $worksheet->row_range();
+    my ( $col_min, $col_max ) = $worksheet->col_range();
+
+    my $FILE = "$OUTSUBDIR/HOST.cfg";
+
+    open (MYFILE, ">$FILE");
+
+    for my $row ( $row_min .. $row_max ) {
+        next if ($row == 0);
+        my ($name, $file, $size, $type);
+        for my $col ( $col_min .. $col_max ) {
+            # Return the cell object at $row and $col
+            my $cell = $worksheet->get_cell( $row, $col );
+            next unless $cell;
+            if ($col == 0) {
+                $name = $cell->value();
+            }
+            if ($col == 1) {
+                $file = $cell->value();
+            }
+            if ($col == 2) {
+                $size = $cell->value();
+            }
+            if ($col == 3) {
+                $type = $cell->value();
+            }
+        }
+
+        my $new_file;
+        my $new_size;
+        if ($type =~ m/yaffs/) {
+            #$new_file = $file;
+            #$new_file =~ s/\..*$/\.oob/;
+            $new_file = lc($name).".oob";
+            $new_size = $size*(1024+32)*(1024+32);
+            system("dd if=/dev/zero bs=$new_size count=1 2>/dev/null | tr '\\000' '\\377' > $OUTSUBDIR/$new_file");
+            if ("$file" ne "__NODL") {
+                system("dd if=$OUTDIR/$file of=$OUTSUBDIR/$new_file conv=notrunc 2>/dev/null");
+            }
+            print MYFILE "name: ", $name, "\n";
+            print MYFILE "file: ", $new_file, "\n";
+        }
+        if ($type =~ m/raw/) {
+            $new_file = lc($name).".raw";
+            $new_size = $size*1024*1024;
+            system("dd if=/dev/zero bs=$new_size count=1 2>/dev/null | tr '\\000' '\\377' > $OUTSUBDIR/$new_file");
+            if ("$file" ne "__NODL") {
+                system("dd if=$OUTDIR/$file of=$OUTSUBDIR/$new_file conv=notrunc 2>/dev/null");
+            }
+            print MYFILE "name: ", $name, "\n";
+            print MYFILE "file: ", $new_file, "\n";
+        }
+        if ($type =~ m/reserved/) {
+            $new_file = lc($name).".raw";
+            $new_size = $size*1024*1024;
+            system("dd if=/dev/zero bs=$new_size count=1 2>/dev/null | tr '\\000' '\\377' > $OUTSUBDIR/$new_file");
+            print MYFILE "name: ", $name, "\n";
+            print MYFILE "file: ", $new_file, "\n";
+        }
+
+    }
+
+    print MYFILE "\n";
+    close (MYFILE);
+}
+
+sub gen_fda_cfg {
+    my ($worksheet) = @_;
+    my ( $row_min, $row_max ) = $worksheet->row_range();
+    my ( $col_min, $col_max ) = $worksheet->col_range();
+
+    my $FILE = "$OUTSUBDIR/FDA.cfg";
+
+    open (MYFILE, ">$FILE");
+    print MYFILE "############################################################################################################\n";
+    print MYFILE "#\n";
+    print MYFILE "#  Android Region Setting\n";
+    print MYFILE "#\n";
+    print MYFILE "############################################################################################################\n";
+    print MYFILE "linux_region:\n";
+    print MYFILE "  partitions:\n";
+
+    for my $row ( $row_min .. $row_max ) {
+        next if ($row == 0);
+        print MYFILE "    - partition:\n";
+        for my $col ( $col_min .. $col_max ) {
+
+            # Return the cell object at $row and $col
+            my $cell = $worksheet->get_cell( $row, $col );
+            next unless $cell;
+
+            if ($col == 0) {
+                print MYFILE "        name: ", $cell->value(), "\n";
+            }
+            if ($col == 1) {
+                print MYFILE "        file: ", $cell->value(), "\n";
+                my $file = $cell->value();
+                if ( -e "$OUTDIR/$file" ) {
+                    system("cp -f $OUTDIR/$file $OUTSUBDIR/");
+                }
+            }
+            if ($col == 2) {
+                print MYFILE "        address: ", $cell->value(), "\n";
+            }
+            if ($col == 3) {
+                print MYFILE "        type: ", $cell->value(), "\t\t#raw, yaffs, yaffs2\n";
+            }
+        }
+    }
+    print MYFILE "\n";
+    close (MYFILE);
+}
+
diff --git a/src/bach/build.bach/tools/cfgGen_module.sh b/src/bach/build.bach/tools/cfgGen_module.sh
new file mode 100755
index 0000000..afa77cc
--- /dev/null
+++ b/src/bach/build.bach/tools/cfgGen_module.sh
@@ -0,0 +1,188 @@
+#!/bin/bash
+
+copy_rename_files() {
+    OUTDIR=$1
+    OUTSUBDIR=$2
+    COPY_FILE_LIST=$3
+    RENAME_FILE_LIST=$4
+
+    for file in $COPY_FILE_LIST
+    do
+        if [ -e $OUTDIR/$file ]; then
+            cp -f $OUTDIR/$file $OUTSUBDIR/
+        else
+            echo "[MODULE GEN] $file not exist!"
+        fi
+    done
+    for file in $RENAME_FILE_LIST
+    do
+        if [ "$file" = "BOOTLOADER" ]; then
+            name=`cd $OUTDIR; ls *.bin | grep -v _ext | grep $file`
+            if [ "$name" != "" ]; then
+                cp -f $OUTDIR/$name $OUTSUBDIR/bootloader.bin
+            else
+                echo "[MODULE GEN] $file not exist!"
+            fi
+        fi
+        if [ "$file" = "MDBIN" ]; then
+            name=`cd $OUTDIR; ls *.bin | grep "^MT" | grep $file`
+            if [ "$name" != "" ]; then
+                cp -f $OUTDIR/$name $OUTSUBDIR/md.bin
+            else
+                echo "[MODULE GEN] $file not exist!"
+            fi
+        fi
+        if [ "$file" = "DSP" ]; then
+            name=`cd $OUTDIR; ls *.bin | grep "^MT" | grep $file`
+            if [ "$name" != "" ]; then
+                cp -f $OUTDIR/$name $OUTSUBDIR/dsp.bin
+            else
+                echo "[MODULE GEN] $file not exist!"
+            fi
+        fi
+        if [ "$file" = "BPLGUInfo" ]; then
+            name=`cd $OUTDIR; ls | grep -v _sec | grep "^$file"`
+            if [ "$name" != "" ]; then
+                cp -f $OUTDIR/$name $OUTSUBDIR/modem.database
+            else
+                echo "[MODULE GEN] $file not exist!"
+            fi
+        fi
+    done
+}
+
+gen_general_setting() {
+    cat << EOF
+############################################################################################################
+#
+#  General Setting 
+#    
+############################################################################################################
+
+general:
+    config_version : alpha # config file version ("alpha", "beta" is used before SQC done.)
+                           # After SQC done, the version should be "1" for the first release version.
+    platform: $1       # It is used for tool to identify the right setting for specific target
+EOF
+    echo ""
+}
+
+gen_boot_region_setting() {
+    cat << EOF
+############################################################################################################
+#
+#  Boot Region Setting
+#
+############################################################################################################
+
+boot_region:
+  alignment: block         # block[default], page(NAND:2K/512B, NOR: 1KB, eMMC: 512B, SF: 256B)
+  rom:
+EOF
+    for i in $*
+    do
+        echo "    - file: "$i
+    done
+    echo ""
+}
+
+gen_control_block_region_setting() {
+    cat << EOF
+############################################################################################################
+#
+#  Control Block Region Setting
+#
+############################################################################################################          
+
+control_block_region:
+  rom:
+EOF
+    echo ""
+}
+
+gen_main_region_setting() {
+    cat << EOF
+############################################################################################################
+#
+#  Main Region Setting
+#
+############################################################################################################
+
+main_region:
+  alignment: block         # block[default], page(NAND:2K/512B, NOR: 1KB, eMMC: 512B, SF: 256B)
+  rom:
+EOF
+    for i in $*
+    do
+        echo "    - file: "$i
+    done
+    echo ""
+}
+
+gen_fs_region_setting() {
+    cat << EOF
+############################################################################################################
+#
+#  File System Region Setting
+#
+############################################################################################################
+
+file_system_region:
+  rom:
+EOF
+    echo ""
+}
+
+gen_external_memory_setting() {
+    cat $1
+    echo ""
+}
+
+gen_nf_fda_cfg() {
+    GEN_FILE=$1
+    EMI_CFG=$CUSTOMDIR/$TYPE/emi.cfg
+    gen_general_setting $PLATFORM_CHIP >> $GEN_FILE
+    gen_boot_region_setting cmdScript.bin bootloader.bin preloader_gfh.bin >> $GEN_FILE
+    gen_control_block_region_setting >> $GEN_FILE
+    gen_main_region_setting md.bin dsp.bin >> $GEN_FILE
+    gen_fs_region_setting >> $GEN_FILE
+    gen_external_memory_setting $EMI_CFG >> $GEN_FILE
+}
+
+if [ $# -ge 2 ]; then
+    PRJ_FILENAME=$1
+    OUTFOLDER=$2
+else
+    PRJ_FILENAME=`cat build.sdk | grep PRJ_FILENAME | head -n 1 | sed -e 's/\$PRJ_FILENAME=\"//g' -e 's/\";//g'`
+    OUTFOLDER="`pwd`/out"
+fi
+CUSTOMDIR="$WORKDIR/custom/$PRJ_FILENAME/build"
+
+if [ ! -d $CUSTOMDIR ]; then
+    CUSTOMDIR="`$WORKDIR/custom/DEFAULT/build"
+fi
+
+COPY_FILE_LIST="cmdScript.bin preloader_gfh.bin catcher_filter.bin catcher_filter_ext.bin"
+RENAME_FILE_LIST="BOOTLOADER MDBIN DSP BPLGUInfo"
+
+#for TYPE in nf sf
+for TYPE in nf
+do
+    rm -rf $OUTFOLDER/$TYPE
+    mkdir -p $OUTFOLDER/$TYPE
+    copy_rename_files $OUTFOLDER $OUTFOLDER/$TYPE "$COPY_FILE_LIST" "$RENAME_FILE_LIST"
+    perl tools/cfgGen_module.pl $CUSTOMDIR $TYPE $OUTFOLDER
+
+    CFGS=`ls $OUTFOLDER/$TYPE | grep .cfg`
+
+    for CFGFILE in $CFGS
+    do
+        if [ "$TYPE" = "nf" ] && [ "$CFGFILE" = "FDA.cfg" ] ; then
+            GEN_FILE=$OUTFOLDER/$TYPE/$PRJ_FILENAME"_FDA.cfg"
+            gen_nf_fda_cfg $GEN_FILE
+            cat $OUTFOLDER/$TYPE/$CFGFILE >> $GEN_FILE
+            rm -f $OUTFOLDER/$TYPE/$CFGFILE
+        fi
+    done
+done
+
diff --git a/src/bach/build.bach/tools/cfgGen_sp.pl b/src/bach/build.bach/tools/cfgGen_sp.pl
new file mode 100644
index 0000000..0ba2554
--- /dev/null
+++ b/src/bach/build.bach/tools/cfgGen_sp.pl
@@ -0,0 +1,100 @@
+#!/usr/bin/perl -w
+
+#use strict;
+use lib "tools/perl";
+use Spreadsheet::ParseExcel;
+
+my $CUSTOMDIR;
+if ($#ARGV == 0) {
+	$CUSTOMDIR = $ARGV[0];
+} else {
+	$CUSTOMDIR = "$ENV{'WORKDIR'}/custom/build";
+}
+
+my $FileName = "$CUSTOMDIR/flash_partition.xls";
+my $parser   = Spreadsheet::ParseExcel->new();
+my $workbook = $parser->parse($FileName);
+
+die $parser->error(), ".\n" if ( !defined $workbook );
+
+# Iterate through all worksheets
+
+system("rm -rf $CUSTOMDIR/*.txt");
+
+for my $worksheet ( $workbook->worksheets() ) {
+
+	# Find out the worksheet ranges
+	my ( $row_min, $row_max ) = $worksheet->row_range();
+	my ( $col_min, $col_max ) = $worksheet->col_range();
+
+	my $FILE = "$CUSTOMDIR/$worksheet->{Name}.txt";
+	open (MYFILE, ">$FILE");
+    print MYFILE "############################################################################################################\n";
+    print MYFILE "#\n";
+    print MYFILE "#  Layout Setting\n";
+    print MYFILE "#\n";
+    print MYFILE "############################################################################################################\n";
+
+	for my $row ( $row_min .. $row_max ) {
+		next if ($row == 0);
+        $tmp = $row;
+	    print MYFILE "- partition_index: SYS", $tmp - 1, "\n";
+		for my $col ( $col_min .. $col_max ) {
+
+            # Return the cell object at $row and $col
+            my $cell = $worksheet->get_cell( $row, $col );
+            next unless $cell;
+
+            if ($col == 0 ) { print MYFILE "  partition_name: ", $cell->value(), "\n"}
+            if ($col == 1 ) {
+
+=head
+				if($cell->value() eq "modem.img")
+				{
+					if($ENV{'MODEM_NAME'} eq "modem.img")
+					{
+						#Default Value
+						print MYFILE "  file_name: ", $cell->value(), "\n"
+					}else{
+						print MYFILE "  file_name: ", $ENV{'MODEM_NAME'}, "\n"
+					}
+				}elsif($cell->value() eq "modemc2k.img"){
+					if($ENV{'MODEMC2K_NAME'} eq "modemc2k.img")
+					{
+						#Default Value
+						print MYFILE "  file_name: ", $cell->value(), "\n"
+					}else{
+						print MYFILE "  file_name: ", $ENV{'MODEMC2K_NAME'}, "\n"
+					}
+				}elsif($cell->value() eq "dsp.bin"){
+					if($ENV{'DSP_NAME'} eq "dsp.bin")
+					{
+						#Default Value
+						print MYFILE "  file_name: ", $cell->value(), "\n"
+					}else{
+						print MYFILE "  file_name: ", $ENV{'DSP_NAME'}, "\n"
+					}
+				}else{
+					print MYFILE "  file_name: ", $cell->value(), "\n"
+				}
+=cut
+				print MYFILE "  file_name: ", $cell->value(), "\n"
+
+			}
+            if ($col == 2 ) { print MYFILE "  is_download: ", $cell->value(), "\n"}
+            if ($col == 3 ) { print MYFILE "  type: ", $cell->value(), "\n"}
+            if ($col == 4 ) { print MYFILE "  linear_start_addr: ", $cell->value(), "\n"}
+            if ($col == 5 ) { print MYFILE "  physical_start_addr: ", $cell->value(), "\n"}
+            if ($col == 6 ) { print MYFILE "  partition_size: ", $cell->value(), "\n"}
+            if ($col == 7 ) { print MYFILE "  region: ", $cell->value(), "\n"}
+            if ($col == 8 ) { print MYFILE "  storage: ", $cell->value(), "\n"}
+            if ($col == 9 ) { print MYFILE "  boundary_check: ", $cell->value(), "\n"}
+            if ($col == 10) { print MYFILE "  is_reserved: ", $cell->value(), "\n"}
+            if ($col == 11) { print MYFILE "  operation_type: ", $cell->value(), "\n"}
+            if ($col == 12) { print MYFILE "  reserve: ", $cell->value(), "\n"}
+        }
+        print MYFILE "\n";
+	}
+	print MYFILE "\n";
+	close (MYFILE);
+}
diff --git a/src/bach/build.bach/tools/compress/compimg.py b/src/bach/build.bach/tools/compress/compimg.py
new file mode 100755
index 0000000..dea31ed
--- /dev/null
+++ b/src/bach/build.bach/tools/compress/compimg.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python3
+
+import os 
+import subprocess
+from argparse import ArgumentParser, RawTextHelpFormatter
+
+class bcolors:
+    HEADER = '\033[95m'
+    OKBLUE = '\033[94m'
+    OKGREEN = '\033[92m'
+    WARNING = '\033[93m'
+    FAIL = '\033[91m'
+    ENDC = '\033[0m'
+    BOLD = '\033[1m'
+    UNDERLINE = '\033[4m'
+
+def parse_arg():
+    arg_parser = ArgumentParser(description = "compress img with MD, DSP",
+                                formatter_class = RawTextHelpFormatter)
+
+    arg_parser.add_argument("img_folder", help = "Folder of BACH image")
+    arg_parser.print_help()
+    print()
+    return arg_parser.parse_args()
+
+def check_req(keyword, content):
+    if not keyword in content:
+        print(bcolors.FAIL + keyword + " doesn't exist" + bcolors.ENDC)
+        print()
+        return 1
+    return 0
+
+def check_bundle(content):
+    checksum = 0
+    checksum = checksum | check_req("modem.img", content)
+    checksum = checksum | check_req(".EDB", content)
+    checksum = checksum | check_req("dsp.bin", content)
+    assert checksum == 0, bcolors.FAIL + "\n  Please add them into image folder\n  You can copy them from BACH official release" + bcolors.ENDC + "\n"
+
+def tar_files(source):
+    cmd = "tar jcvf source.tar.bz2" + source
+    print(cmd)
+    subprocess.call(cmd, shell=True)
+    print(bcolors.OKGREEN + "compressed file is in " + os.getcwd() + bcolors.ENDC)
+
+if __name__ == '__main__':
+    console_args = parse_arg()
+    directory = "./" + console_args.img_folder
+    if not os.path.isdir(directory):
+        print(console_args.img_folder + " doesn't exist")
+        exit(1)
+    
+    compress_files = " "
+    for filename in os.listdir(directory):
+        if not os.path.isdir(directory + "/" + filename):
+            compress_files += filename + " "
+
+    check_bundle(compress_files)
+    os.chdir(directory)
+    tar_files(compress_files)
+    # TODO: show final tar file location
+
diff --git a/src/bach/build.bach/tools/config_gen.pl b/src/bach/build.bach/tools/config_gen.pl
new file mode 100644
index 0000000..5815844
--- /dev/null
+++ b/src/bach/build.bach/tools/config_gen.pl
@@ -0,0 +1,53 @@
+#!/bin/perl
+
+use File::Basename;
+
+$CROSS_PREFIX=$ENV{'CROSS_PREFIX'};
+$WORKDIR=$ENV{'WORKDIR'};
+$OUTDIR=$ENV{'OUTDIR'};
+$LOGDIR=$ENV{'OUTDIR'}."/log";
+$PRJ_FILE=$ENV{'PRJ_FILENAME'};
+$COMPLIST=`cat $LOGDIR/infomake.log |grep COMPLIST|sed -e 's/COMPLIST = //g'`;
+@COMPS=split(' ', $COMPLIST);
+
+if(-d "$WORKDIR/custom/$PRJ_FILE") {
+	$CONFIGDIR="$WORKDIR/custom/$PRJ_FILE/build/config";
+} else {
+	$CONFIGDIR="$WORKDIR/custom/DEFAULT/build/config";
+}
+
+#generate ConfigPack images
+system("rm -rf $OUTDIR/rootfs/usr/cfg");
+system("mkdir -p $OUTDIR/rootfs/usr/cfg");
+foreach my $comp (@COMPS)
+{
+	if(-e "$WORKDIR/custom/$PRJ_FILE/$comp/cfg") {
+		system("cp -rf $WORKDIR/custom/$PRJ_FILE/$comp/cfg/* $OUTDIR/rootfs/usr/cfg");
+	} elsif (-e "$WORKDIR/custom/DEFAULT/$comp/cfg") {
+		system("cp -rf $WORKDIR/custom/DEFAULT/$comp/cfg/* $OUTDIR/rootfs/usr/cfg");
+	}
+}
+
+system("$WORKDIR/install/$CROSS_PREFIX/host/cfgpack/mkcfgpack $OUTDIR/rootfs/usr/cfg $OUTDIR/tmp/default.bin > $LOGDIR/mkcfgpack_default.log");
+system("cp $OUTDIR/tmp/default.bin $OUTDIR/initramfs/usr/cfg/default.bin");
+system("cp $OUTDIR/tmp/default.bin $OUTDIR/rootfs/usr/cfg/default.bin");
+
+#generate doxygen document
+system("cd $WORKDIR/install/$CROSS_PREFIX/host/doxygen/doc;$WORKDIR/install/$CROSS_PREFIX/host/doxygen/bin/doxygen > $LOGDIR/doxygen.log 2>&1");
+system("rm -rf $OUTDIR/doc;mkdir $OUTDIR/doc");
+system("cp -afr $WORKDIR/install/$CROSS_PREFIX/host/doxygen/doc/html/* $OUTDIR/doc/");
+
+
+#generate FirmwareConfig FactoryConfig images
+system("mkdir -p $OUTDIR/tmp/empty_tmp_dir");
+@DEFAULTCFG=`ls $CONFIGDIR/*.cfg`;
+foreach my $file (@DEFAULTCFG)
+{
+	my $cfgfile = basename($file);
+	chomp $cfgfile;
+	my $cfg = $cfgfile;
+	$cfg =~ s/default-//gi;
+	$cfg =~ s/\.cfg//gi;
+	system("$WORKDIR/install/$CROSS_PREFIX/host/cfgpack/mkcfgpack -x $CONFIGDIR/$cfgfile $OUTDIR/tmp/empty_tmp_dir $OUTDIR/fc-$cfg.bin >> $LOGDIR/mkcfgpack_fc.log");
+}
+system("rm -rf $OUTDIR/tmp/empty_tmp_dir");
diff --git a/src/bach/build.bach/tools/emiGenSP.pl b/src/bach/build.bach/tools/emiGenSP.pl
new file mode 100644
index 0000000..a07d53d
--- /dev/null
+++ b/src/bach/build.bach/tools/emiGenSP.pl
@@ -0,0 +1,154 @@
+#!/usr/bin/perl
+#
+#  Copyright Statement:
+#  --------------------
+#  This software is protected by Copyright and the information contained
+#  herein is confidential. The software may not be copied and the information
+#  contained herein may not be used or disclosed except with the written
+#  permission of MediaTek Inc. (C) 2006
+#
+#  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+#  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+#  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON
+#  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+#  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+#  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+#  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH
+#  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO
+#  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S
+#  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.
+#
+#  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE
+#  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+#  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+#  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO
+#  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+#
+#  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE
+#  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF
+#  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND
+#  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER
+#  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).
+#
+#*****************************************************************************
+#*
+#* Filename:
+#* ---------
+#*   emiGenSP.pl
+#*
+#* Project:
+#* --------
+#*
+#*
+#* Description:
+#* ------------
+#*   This script will
+#*       1. generate fxied flash_opt.h for smart phone modem
+#*       2. generate fixed custom_EMI.c for smart phone modem
+#*       3. generate fixed custom_EMI.h for smart phone modem
+#*       4. generate fixed custom_flash.c for smart phone modem
+#*
+#* Author:
+#* -------
+#*   Guo-Huei Chang    (mtk04123)
+#*
+#*============================================================================
+#*             HISTORY
+#* Below this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
+#*------------------------------------------------------------------------------
+#* $Revision$
+#* $Modtime$
+#* $Log$
+#*
+#* 04 21 2014 guo-huei.chang
+#* [MOLY00063203] [EMIGEN] Update EMIGEN for Smart Phone Project

+#* 	Update emigen to generate EMI size for none EMI support
+#*
+#* 01 08 2014 guo-huei.chang
+#* [MOLY00052841] [EMIGEN] Update EMIGEN for SmartPhone project
+#* 	1. update emigen for smart phone to generate custom emi header
+#* 	2. update custom_EMI_SP.c for smart phone
+#* 
+#* 07 27 2012 marvin.lin
+#* [MOLY00001243] [MemoryStorage][Auto-Gen][EMI Gen/CFG Gen][Request For Design Change] to fix emi build warning on sp project
+#* .
+#*
+#*
+#*------------------------------------------------------------------------------
+#* Upper this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
+#*============================================================================
+#****************************************************************************/
+
+#****************************************************************************
+# Included Modules
+#****************************************************************************
+use strict;
+
+#****************************************************************************
+# Constants
+#****************************************************************************
+1;
+#my $DebugPrint    = 0; # 1 for debug; 0 for non-debug
+
+#****************************************************************************
+# subroutine:  custom_EMI_h_file_body_for_sp
+# return:      custom_EMI.h file content for SP
+#****************************************************************************
+sub custom_EMI_h_file_body_for_sp
+{
+    my ($bb) = @_;
+    my $template;
+	   
+    $template = <<"__TEMPLATE";
+#ifndef __CUSTOM_EMI__
+#define __CUSTOM_EMI__
+
+/****************************************************
+ * This part is for auto-gen validity CHECK *
+ * Don't modify any content in this comment section *
+ ****************************************************/
+
+#endif /* __CUSTOM_EMI__ */
+
+__TEMPLATE
+	   
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  custom_EMI_release_h_file_body_for_sp
+# return:      custom_EMI_release.h file content for SP
+#****************************************************************************
+sub custom_EMI_release_h_file_body_for_sp
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL, $LPSDRAM_CHIP_SELECT_LOCAL, $emi_clk_config_LOCAL) = @_;
+    my $template;
+	   
+    $template = <<"__TEMPLATE";
+#ifndef __CUSTOM_EMI_RELEASE__
+#define __CUSTOM_EMI_RELEASE__
+
+/***********************************
+    *
+    * Definition
+    * 
+    *********************************/
+
+/**
+  * Define memory's mode.
+  */
+#define __EMI_DEVICE_NONE__
+/**
+  * Define RAM size in Bytes.
+  */
+#define EMI_EXTSRAM_SIZE ((($COMM_MDL_INFO_LOCAL->{1}->{'Size (Mb)'})>>3)<<20)
+
+#endif /* __CUSTOM_EMI_RELEASE__ */
+
+__TEMPLATE
+	   
+    return $template;
+}
+
diff --git a/src/bach/build.bach/tools/emigenMD.pl b/src/bach/build.bach/tools/emigenMD.pl
new file mode 100644
index 0000000..f938f28
--- /dev/null
+++ b/src/bach/build.bach/tools/emigenMD.pl
@@ -0,0 +1,1470 @@
+#!/usr/bin/perl
+#
+#  Copyright Statement:
+#  --------------------
+#  This software is protected by Copyright and the information contained
+#  herein is confidential. The software may not be copied and the information
+#  contained herein may not be used or disclosed except with the written
+#  permission of MediaTek Inc. (C) 2006
+#
+#  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+#  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+#  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON
+#  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+#  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+#  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+#  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH
+#  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO
+#  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S
+#  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.
+#
+#  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE
+#  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+#  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+#  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO
+#  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+#
+#  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE
+#  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF
+#  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND
+#  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER
+#  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).
+#
+#****************************************************************************
+#*
+#* Filename:
+#* ---------
+#*   emigenMD.pl
+#*
+#* Project:
+#* --------
+#*
+#*
+#* Description:
+#* ------------
+#*   generate emi/flash configuration files
+#*
+#* Author:
+#* -------
+#*   Guo-Huei Chang    (mtk04123)
+#*   Way Chen          (mtk54483)
+#****************************************************************************
+
+#****************************************************************************
+# Included Modules
+#****************************************************************************
+use strict;
+use warnings;
+use Class::Struct ();
+use File::Basename ();
+use File::Copy ();
+use File::Spec ();
+use POSIX ();
+use Storable ();
+push @INC, ("tools/perl",".");
+require Spreadsheet::ParseExcel;
+
+local $| = 1;  #auto flush 'print' function
+
+my $EMIGEN_VERNO  = "v10.001";
+                    #v10.001, by Holmes at 2019/08/01, modify for MT6297
+                    # v9.008, by Guo-Huei at 2014/04/15, Update EMIGEN to generate SmartPhone's DRAM Size by MDL
+                    # v9.007, by Guo-Huei at 2014/01/08, Update EMIGEN for SmartPhone project
+                    # v9.006, by Guo-Huei at 2013/12/09, Update custom_EMI for MT6595
+                    # v9.005, by Way at 2013/11/15, Optimal EMIGEN Flash Part Code for ROME.
+                    # v9.004, by Guo-Huei at 2013/11/06, update emigen for MT6595 build pass
+                    # v9.003, by Way at 2013/10/24, Support MP Level MDL
+                    # v9.002, by Guo-Huei at 2013/08/07, 1. remove lagacy code. 2. auto-generate header file according MDL content 3. Add combo memory support 4. error check for combo memory configuration.
+                    # v9.001, by Guo-Huei at 2013/08/05, 1. remove lagacy code. 2. re-arch emigen flow. 3. remove WIN32 OLE related code. 4. use warnings
+                    # v8.276, by Way at 2013/06/24, 1. remove lagacy code; 2. Re-arch EMIGEN flow; 3. Implement combo flash part auto gen flow; 4. Add combo_nfi_config.h for enhance NAND flash flow of mT6290.
+                    # v8.275, by Way at 2013/06/7, enable auto emigen flash part.
+                    # v8.274, by Way at 2013/05/23, change checking smart phone method without flash.
+                    # v8.273, by Guo-Huei at 2013/05/15, change checking smart phone method without emi.
+                    # v8.272, by Guo-Huei at 2013/05/02, add emigen to support combo memory.
+                    # v8.271, by Guo-Huei at 2013/04/12, update emigen to auto-gen custom_EMI_INFO.h
+                    # v8.270, by Guo-Huei at 2013/03/27, update emigen to support parsing flash.
+                    # v8.269, by Guo-Huei at 2013/03/22, update emigen to auto-generate emi related output files.
+                    # v8.268, by Guo-Huei at 2013/03/19, update custom_EMI function and cfggen EMI part
+                    # v8.267, by Guo-Huei at 2013/02/05, rename emigen from MT7208 to MT6290
+                    # v8.266, by Marvin  at 2012/11/01 , autogen support MT6290
+my $DebugPrint    = 0; # 1 for debug; 0 for non-debug
+
+my $BOOL_EMI_CLEAN            = $ARGV[0];  # FALSE
+my $CUSTOM_MEMORY_DEVICE_HDR  = $ARGV[1];  # ./work/custom/TK6291E1_SUPER_HOSTED/system/custom_MemoryDevice.h
+my $THEMF                     = $ARGV[2];  # ./work/custom/TK6291E1_SUPER_HOSTED/build/customer_feature_option
+my $BOOL_MD_INFO              = $ARGV[3];  # FALSE
+my $INSIDE_MTK                = $ARGV[4];  # 1
+my $LOAD_TMP_PATH             = $ARGV[5];  # ./out/tmp/TK6291E1_SUPER_HOSTED
+my $CUSTOM_SYSTEM_DIR = File::Basename::dirname($CUSTOM_MEMORY_DEVICE_HDR);
+my $CUSTOM_FEATURE_CONFIG_HDR = File::Spec->catfile($CUSTOM_SYSTEM_DIR, "custom_FeatureConfig.h");
+
+# to align error message file name format
+$CUSTOM_MEMORY_DEVICE_HDR =~ s/^.\\|^\\//;
+$THEMF                    =~ s/^.\\|^\\//;
+
+# global parameters
+my %PART_NUMBER;
+my $emi_clk_config;
+my $sfi_clk_config;
+my %BBtbl_SFI_FAST_CLK = ( 'MT6291'  => 78 );
+my %BBtbl_SFI_SLOW_CLK = ( 'MT6291'  => 13 );
+my $NOR_FLASH_BASE_ADDRESS_VAL;
+my $NOR_ALLOCATED_FAT_SPACE_VAL;
+my $LPSDRAM_CHIP_SELECT = 0xFF;
+my $emi_is_existed = 1;
+my @MCP_LIST;  # list of PART_NUMBER hash references
+my @MDL_INFO_LIST;  # list of MEM_DEV_LIST_INFO has references
+my %COMM_MDL_INFO;  # common MDL info, obtained from MDL_INFO_LIST.
+my %MAKEFILE_OPTIONS; #list of makefile option
+my %CUSTOM_MEM_DEV_OPTIONS;  #list of custom_memory_device option
+my %CUSTOM_FEATURE_CFG_OPTIONS;
+my $MEMORY_DEVICE_LIST_XLS;  #CORRECT MDL FILE
+my $MEMORY_DEVICE_LIST_INT_XLS; #INTERNAL MDL FILE
+
+print "os environment: $^O\n";
+print "load temp path: $LOAD_TMP_PATH\n";
+print "custom memory device: $CUSTOM_MEMORY_DEVICE_HDR\n";
+print "makefile: $THEMF\n";
+print "inside_mtk: $INSIDE_MTK\n";
+
+require "tools/emigenemi.pl";
+require "tools/emigenflash.pl";
+
+#****************************************************************************
+# parse makefile
+#****************************************************************************
+&Parse_Makefile();
+if ($MAKEFILE_OPTIONS{'platform'} =~ '^MT629[1235]$') {
+    $MAKEFILE_OPTIONS{'platform'} = 'MT6291';
+} elsif ($MAKEFILE_OPTIONS{'platform'} =~ '^MT629[78]$') {
+    $MAKEFILE_OPTIONS{'platform'} = 'MT6297';
+} else {
+    &error_handler("not support platform, ".$MAKEFILE_OPTIONS{'platform'}, __FILE__, __LINE__);
+}
+
+#****************************************************************************
+# generated file list
+#****************************************************************************
+printf "\n%s#%d: %s\n", __FILE__, __LINE__, "generated file list";
+Class::Struct::struct(GEN_FLS_S => {name => '$', owner => '$', path => '$', exist => '$', description => '$'});
+my %GEN_FLS = (
+    custom_emi_h => GEN_FLS_S->new(name => (($MAKEFILE_OPTIONS{'platform'} eq 'MT6291')? "custom_EMI.h": "custom_emi.h"), owner => "holmes.lin (mtk09097)", description => "This Module defines the EMI (external memory interface) related setting."),
+    custom_emi_info_c => GEN_FLS_S->new(name => "custom_EMI_INFO.c", owner => "holmes.lin (mtk09097)", description => "This Module defines the EMI (external memory interface) related setting."),
+    custom_emi_release_h => GEN_FLS_S->new(name => "custom_EMI_release.h", owner => "holmes.lin (mtk09097)", description => "This Module defines the EMI (external memory interface) related setting."),
+    #custom_emi_c => GEN_FLS_S->new(name => "custom_EMI.c", owner => "holmes.lin (mtk09097)", description => ""),
+    #custom_sfi_h => GEN_FLS_S->new(name => "custom_SFI.h", owner => "way.chen (mtk54483)", description => ""),
+    custom_flash_h => GEN_FLS_S->new(name => "custom_flash.h", owner => "way.chen (mtk54483)", description => "This Module defines flash related settings."),
+    flash_opt_gen_h => GEN_FLS_S->new(name => "flash_opt_gen.h", owner => "way.chen (mtk54483)", description => "NOR flash related options."),
+    custom_flash_norfdm5_h => GEN_FLS_S->new(name => "custom_flash_norfdm5.h", owner => "way.chen (mtk54483)", description => "defines prototypes and data structure which will be used in NOR FDM 5.0"),
+    combo_flash_config_h => GEN_FLS_S->new(name => "combo_flash_config.h", owner => "way.chen (mtk54483)", description => "This Module defines the Flash configurations for Combo Memory."),
+    combo_flash_id_h => GEN_FLS_S->new(name => "combo_flash_id.h", owner => "way.chen (mtk54483)", description => "This Module defines the Flash ID table for Combo Memory."),
+    #combo_sfi_config_h => GEN_FLS_S->new(name => "combo_sfi_config.h", owner => "way.chen (mtk54483)", description => ""),
+    combo_sfi_defs_h => GEN_FLS_S->new(name => "combo_sfi_defs.h", owner => "way.chen (mtk54483)", description => "defines flash ID table"),
+    #custom_sfi_clock_h => GEN_FLS_S->new(name => "custom_sfi_clock.h", owner => "way.chen (mtk54483)", description => ""),
+    combo_nfi_config_h => GEN_FLS_S->new(name => "combo_nfi_config.h", owner => "way.chen (mtk54483)", description => "gen NAND Flash Related Configuration and Table"),
+);
+map {$GEN_FLS{$_}->path(File::Spec->catfile($CUSTOM_SYSTEM_DIR, $GEN_FLS{$_}->name))} keys(%GEN_FLS);
+print "output file: ", join(", ", map {$GEN_FLS{$_}->path} sort(keys(%GEN_FLS))), "\n";
+
+#****************************************************************************
+# emiclean
+#****************************************************************************
+printf "\n%s#%d: %s\n", __FILE__, __LINE__, "emiclean";
+my @gen_depends = ("tools/emigenMD.pl", "tools/emigenSP.pl", "tools/emigenemi.pl", "tools/emigenflash.pl", "tools/emigenSF.pl", $THEMF, $CUSTOM_MEMORY_DEVICE_HDR, $MEMORY_DEVICE_LIST_XLS, $MEMORY_DEVICE_LIST_INT_XLS, $CUSTOM_FEATURE_CONFIG_HDR);
+if ($BOOL_EMI_CLEAN eq 'TRUE') {
+    map {&Check_ManualCheckIn($GEN_FLS{$_}->path, "TRUE", @gen_depends)} keys(%GEN_FLS);
+    exit;
+} else {
+    map {&Check_ManualCheckIn($GEN_FLS{$_}->path, "FALSE", @gen_depends)} keys(%GEN_FLS);
+}
+map {$GEN_FLS{$_}->exist((-e $GEN_FLS{$_}->path)? 1: 0)} keys(%GEN_FLS);
+
+#****************************************************************************
+# find the correct EMI database
+#****************************************************************************
+&Find_MDL();
+
+#****************************************************************************
+# parse custom_MemoryDevice.h to extract MEMORY_DEVICE_TYPE & PART_NUMBER
+#****************************************************************************
+&Parse_Memory_Device();
+
+#****************************************************************************
+# parse custom_FeatureConfig.h to extract feature-related configurations
+#****************************************************************************
+&Parse_custom_FeatureConfig();
+
+#****************************************************************************
+# Check EMI exist
+#****************************************************************************
+if ($CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE} eq 'NONE')
+{
+    require "tools/emiGenSP.pl";
+    $emi_is_existed = 0;
+}
+
+#****************************************************************************
+# parse EMI database to get EMI settings
+#****************************************************************************
+&Parse_MDL();
+
+#****************************************************************************
+# Check Combo Memory configuration validity
+#****************************************************************************
+my $nor_size_Mb;
+&CheckConfigValid();
+
+#****************************************************************************
+# check consistency among FEATURE, MEMORY_DEVICE_TYPE, PLATFORM, MCU_CLOCK and MemoryDeviceList data
+#****************************************************************************
+$sfi_clk_config = $BBtbl_SFI_FAST_CLK{$MAKEFILE_OPTIONS{'platform'}};
+&Decide_MemClockSpeed();
+
+#****************************************************************************
+# get common MDL information from a list of MDL information for Combo Memory
+#****************************************************************************
+printf "\n%s#%d: %s\n", __FILE__, __LINE__, "get_common_MDL_info";
+&get_common_MDL_info(\@MDL_INFO_LIST, \%CUSTOM_MEM_DEV_OPTIONS, \%COMM_MDL_INFO,\%MAKEFILE_OPTIONS, $LPSDRAM_CHIP_SELECT, $CUSTOM_MEMORY_DEVICE_HDR, $emi_is_existed);
+if ($DebugPrint == 1)
+{
+    print "COMM_MDL_INFO: Flash Size = $COMM_MDL_INFO{0}->{'Flash Size'}, NAND Block Size(KB) = $COMM_MDL_INFO{0}->{'NAND Block Size(KB)'}, Small Block Start = $COMM_MDL_INFO{0}->{'Small Block Start'}, Default FAT Base = $COMM_MDL_INFO{0}->{'Default FAT Base'}, Default FAT Size = $COMM_MDL_INFO{0}->{'Default FAT Size'}\n";
+    #print "RAM \"Size (Mb)\" = $COMM_MDL_INFO{1}->{'Size (Mb)'}, \"CS0 ADMUX\" = $COMM_MDL_INFO{0}->{'ADMUX'}, \"CS1 ADMUX\" = $COMM_MDL_INFO{1}->{'ADMUX'}, \"DRAM\" = $COMM_MDL_INFO{1}->{'DRAM'}, \"CS0 Comm. Series\" = $COMM_MDL_INFO{0}->{'Comm. Series'}, \"CS1 Comm. Series\" = $COMM_MDL_INFO{1}->{'Comm. Series'}, \"Bank\" = $COMM_MDL_INFO{0}->{'Bank'}, \"PBP->Y / N\" = $COMM_MDL_INFO{0}->{'PBP'}->{'Y / N'}, \"PBP->Size(W)\" = $COMM_MDL_INFO{0}->{'PBP'}->{'Size(W)'}\n";
+}
+
+#****************************************************************************
+# PROJECT EXPECTED CONFIGURE
+#****************************************************************************
+my $physical_ram_density;
+my $project_expected_ram_limit_common = (exists $MAKEFILE_OPTIONS{'nand_flash_booting'} and $MAKEFILE_OPTIONS{'nand_flash_booting'} ne 'NONE') ? $CUSTOM_FEATURE_CFG_OPTIONS{PROJECT_EXPECTED_RAM_LIMIT_NFB} : $CUSTOM_FEATURE_CFG_OPTIONS{PROJECT_EXPECTED_RAM_LIMIT};
+if (defined $project_expected_ram_limit_common)
+{
+    #ensure practical
+    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Cannot configure expected RAMSIZE larger than physical RAM size!", __FILE__, __LINE__)
+        if ( hex($project_expected_ram_limit_common) > ($COMM_MDL_INFO{1}->{'Size (Mb)'} * 1024 * 1024 / 8) );
+
+    #ensure 2^n
+    my $i;
+    for( $i = 1 ; $i < hex($project_expected_ram_limit_common) ; $i *= 2 ){1;}
+    if ( $i < ($COMM_MDL_INFO{1}->{'Size (Mb)'} * 1024 * 1024 / 8) )
+    {
+        $physical_ram_density = "// PHYSICAL RAM DENSITY $COMM_MDL_INFO{1}->{'Size (Mb)'} Mb\n";
+        $COMM_MDL_INFO{1}->{'Size (Mb)'} = $i * 8 / ( 1024 * 1024);
+    }
+}
+chomp $physical_ram_density if (defined $physical_ram_density);
+
+#****************************************************************************
+# get common entire geometry information for Combo Memory
+#****************************************************************************
+my (@entire_region_info_size_list, @entire_region_info_count_list, @entire_bank_info_size_list, @entire_bank_info_count_list);
+my (@entire_block_info_start_list, @entire_block_info_size_list);
+if ($MAKEFILE_OPTIONS{'serial_flash_support'} eq 'TRUE')
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "get_common_MDL_Geo_info, convert_regions_to_blocks";
+    &get_common_MDL_Geo_info(\@MDL_INFO_LIST, $CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT}, $CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE}, 0, $COMM_MDL_INFO{0}->{'Flash Size'}, \@entire_region_info_size_list, \@entire_region_info_count_list, \@entire_bank_info_size_list, \@entire_bank_info_count_list);
+    &convert_regions_to_blocks(0, \@entire_region_info_size_list, \@entire_region_info_count_list, \@entire_block_info_start_list, \@entire_block_info_size_list);
+
+}
+#NAND Flash Also need to check FAT for MT6290, we may use NAND FAT Infomation
+printf "\n%s#%d: %s\n", __FILE__, __LINE__, "Calculate_FAT_Info";
+&Calculate_FAT_Info(\%MAKEFILE_OPTIONS, \%CUSTOM_MEM_DEV_OPTIONS, \%COMM_MDL_INFO, \@MDL_INFO_LIST, \$NOR_FLASH_BASE_ADDRESS_VAL, \$NOR_ALLOCATED_FAT_SPACE_VAL, $nor_size_Mb, \%CUSTOM_FEATURE_CFG_OPTIONS, \@entire_block_info_start_list, \@entire_block_info_size_list);
+
+#****************************************************************************
+# get common FS geometry information for Combo Memory
+#****************************************************************************
+my (@fs_region_info_size_list, @fs_region_info_count_list, @fs_bank_info_size_list, @fs_bank_info_count_list);
+my (@fs_block_info_start_list, @fs_block_info_size_list);
+if ($MAKEFILE_OPTIONS{'serial_flash_support'} eq 'TRUE')
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "get_common_MDL_Geo_info; convert_regions_to_blocks";
+    &get_common_MDL_Geo_info(\@MDL_INFO_LIST, $CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT}, $CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE}, $NOR_FLASH_BASE_ADDRESS_VAL, $NOR_FLASH_BASE_ADDRESS_VAL+$NOR_ALLOCATED_FAT_SPACE_VAL, \@fs_region_info_size_list, \@fs_region_info_count_list, \@fs_bank_info_size_list, \@fs_bank_info_count_list);
+    &convert_regions_to_blocks($NOR_FLASH_BASE_ADDRESS_VAL, \@fs_region_info_size_list, \@fs_region_info_count_list, \@fs_block_info_start_list, \@fs_block_info_size_list);
+    if ($DebugPrint == 1)
+    {
+        print "Entire RegionInfo: ";
+        for (0..$#entire_region_info_size_list)
+        {
+            print "\{$entire_region_info_size_list[$_], $entire_region_info_count_list[$_]\}, ";
+        }
+        print "\nEntire BankInfo: ";
+        for (0..$#entire_bank_info_size_list)
+        {
+            print "\{$entire_bank_info_size_list[$_], $entire_bank_info_count_list[$_]\}, ";
+        }
+        print "\nEntire BlockInfo: ";
+        for (0..$#entire_block_info_start_list)
+        {
+            print "\{$entire_block_info_start_list[$_], $entire_block_info_size_list[$_]\}, ";
+        }
+        print "\nFS RegionInfo: ";
+        for (0..$#fs_region_info_size_list)
+        {
+            print "\{$fs_region_info_size_list[$_], $fs_region_info_count_list[$_]\}, ";
+        }
+        print "\nFS BankInfo: ";
+        for (0..$#fs_bank_info_size_list)
+        {
+            print "\{$fs_bank_info_size_list[$_], $fs_bank_info_count_list[$_]\}, ";
+        }
+        print "\nFS BlockInfo: ";
+        for (0..$#fs_block_info_start_list)
+        {
+            print "\{$fs_block_info_start_list[$_], $fs_block_info_size_list[$_]\}, ";
+        }
+        print "\n";
+    }
+}
+
+### Validate FAT and NOR Raw disk overlapping case
+printf "\n%s#%d: %s\n", __FILE__, __LINE__, "Validate_FAT_NORRAWDISK_OVERLAP; Calculate_NORRAWDISK_Region_Info";
+&Validate_FAT_NORRAWDISK_OVERLAP(\%CUSTOM_MEM_DEV_OPTIONS, \%COMM_MDL_INFO, \$NOR_FLASH_BASE_ADDRESS_VAL, \$NOR_ALLOCATED_FAT_SPACE_VAL, $CUSTOM_MEMORY_DEVICE_HDR);
+### Calculate NOR Raw disk Region Info
+&Calculate_NORRAWDISK_Region_Info(\%CUSTOM_MEM_DEV_OPTIONS);
+
+### Check CODE setting validity
+my $expected_code_limit;
+if (defined $CUSTOM_FEATURE_CFG_OPTIONS{PROJECT_EXPECTED_CODE_LIMIT})
+{
+    #ensure practical
+    &error_handler("$CUSTOM_FEATURE_CONFIG_HDR: Cannot configure expected CODESIZE larger than (physical NOR flash size - FAT space)!", __FILE__, __LINE__)
+        if ( hex($CUSTOM_FEATURE_CFG_OPTIONS{PROJECT_EXPECTED_CODE_LIMIT}) > $NOR_FLASH_BASE_ADDRESS_VAL );
+}
+
+#****************************************************************************
+# generate custom_EMI.h
+#****************************************************************************
+if (($GEN_FLS{custom_emi_h}->exist == 0) and
+    ($MAKEFILE_OPTIONS{'platform'} =~ 'MT629[17]'))
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "custom_emi_h";
+    my $gf = $GEN_FLS{custom_emi_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    if ($emi_is_existed == 0) {
+        print GFH &custom_EMI_h_file_body_for_sp($MAKEFILE_OPTIONS{'platform'});
+    } elsif ($MAKEFILE_OPTIONS{'platform'} eq 'MT6291') {
+        print GFH &custom_EMI_h_file_body_for_91(\%MAKEFILE_OPTIONS,\%CUSTOM_MEM_DEV_OPTIONS,\@MDL_INFO_LIST, \%COMM_MDL_INFO, \%PART_NUMBER, $CUSTOM_MEMORY_DEVICE_HDR, $LPSDRAM_CHIP_SELECT, $emi_clk_config);
+    } else {
+        print GFH &custom_emi_h_file_body_for_97(\%MAKEFILE_OPTIONS,\@MDL_INFO_LIST);
+    }
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate custom_EMI_INFO.c
+#****************************************************************************
+if (($GEN_FLS{custom_emi_info_c}->exist == 0) and
+    ($MAKEFILE_OPTIONS{'platform'} eq 'MT6291'))
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "custom_emi_info_c";
+    my $gf = $GEN_FLS{custom_emi_info_c};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    if ($emi_is_existed == 0) {
+        print GFH &custom_EMI_info_h_file_body_for_sp($MAKEFILE_OPTIONS{'platform'});
+    } else {
+        print GFH &custom_EMI_info_c_file_body_for_91(\%MAKEFILE_OPTIONS,\%CUSTOM_MEM_DEV_OPTIONS,\@MDL_INFO_LIST, \%COMM_MDL_INFO, \%PART_NUMBER, $CUSTOM_MEMORY_DEVICE_HDR, $LPSDRAM_CHIP_SELECT, $emi_clk_config);
+    }
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate custom_EMI_release.h
+#****************************************************************************
+if (($GEN_FLS{custom_emi_release_h}->exist == 0) and
+    ($MAKEFILE_OPTIONS{'platform'} eq 'MT6291'))
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "custom_emi_release_h";
+    my $gf = $GEN_FLS{custom_emi_release_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    if ($emi_is_existed == 0) {
+        print GFH &custom_EMI_release_h_file_body_for_sp(\%MAKEFILE_OPTIONS,\%CUSTOM_MEM_DEV_OPTIONS,\@MDL_INFO_LIST, \%COMM_MDL_INFO, $LPSDRAM_CHIP_SELECT, $emi_clk_config);
+    } else {
+        print GFH &custom_EMI_release_h_file_body_for_91(\%MAKEFILE_OPTIONS,\%CUSTOM_MEM_DEV_OPTIONS,\@MDL_INFO_LIST, \%COMM_MDL_INFO, $LPSDRAM_CHIP_SELECT, $emi_clk_config);
+    }
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate flash_opt_gen.h
+#****************************************************************************
+if ($GEN_FLS{flash_opt_gen_h}->exist == 0)
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "flash_opt_gen_h";
+    my $gf = $GEN_FLS{flash_opt_gen_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    print GFH &flash_opt_gen_h_file_body(\%MAKEFILE_OPTIONS,\%CUSTOM_MEM_DEV_OPTIONS,\@MDL_INFO_LIST, \%COMM_MDL_INFO, $CUSTOM_MEMORY_DEVICE_HDR, $THEMF, \@entire_bank_info_size_list, \@entire_bank_info_count_list, \@entire_block_info_start_list, \@entire_block_info_size_list, \@entire_region_info_size_list, \@entire_region_info_count_list);
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate custom_flash.h
+#****************************************************************************
+if ($GEN_FLS{custom_flash_h}->exist == 0)
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "custom_flash_h";
+    my $gf = $GEN_FLS{custom_flash_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    print GFH &custom_flash_h_file_body(\%MAKEFILE_OPTIONS,\%CUSTOM_MEM_DEV_OPTIONS, \@MDL_INFO_LIST, \%COMM_MDL_INFO, \@MCP_LIST, $NOR_FLASH_BASE_ADDRESS_VAL, $NOR_ALLOCATED_FAT_SPACE_VAL, $LPSDRAM_CHIP_SELECT, $CUSTOM_MEMORY_DEVICE_HDR, $nor_size_Mb, \@entire_bank_info_size_list, \@entire_bank_info_count_list, \@entire_block_info_start_list, \@entire_block_info_size_list,\@fs_region_info_size_list, \@fs_region_info_count_list, \@entire_region_info_size_list, \@entire_region_info_count_list);
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate custom_flash_norfdm5.h
+#****************************************************************************
+if (($GEN_FLS{custom_flash_norfdm5_h}->exist == 0) and
+    (defined $CUSTOM_MEM_DEV_OPTIONS{__NOR_FDM5__})) ##Only __NOR_FDM5__ defined will gen custom_flash_norfdm5.h
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "custom_flash_norfdm5_h";
+    my $gf = $GEN_FLS{custom_flash_norfdm5_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    print GFH &custom_flash_norfdm5_h_file_body(\%MAKEFILE_OPTIONS, \%CUSTOM_MEM_DEV_OPTIONS, \@MDL_INFO_LIST, \%COMM_MDL_INFO, $NOR_FLASH_BASE_ADDRESS_VAL, $NOR_ALLOCATED_FAT_SPACE_VAL,\@entire_bank_info_size_list, \@entire_bank_info_count_list);
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate combo_flash_config.h
+#****************************************************************************
+if ($GEN_FLS{combo_flash_config_h}->exist == 0)
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "combo_flash_config_h";
+    my $gf = $GEN_FLS{combo_flash_config_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    print GFH &combo_flash_config_h_file_body(\%MAKEFILE_OPTIONS, \%CUSTOM_MEM_DEV_OPTIONS, \@MDL_INFO_LIST, \%COMM_MDL_INFO);
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate combo_flash_id.h
+#****************************************************************************
+if ($GEN_FLS{combo_flash_id_h}->exist == 0)
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "combo_flash_id_h";
+    my $gf = $GEN_FLS{combo_flash_id_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    print GFH &combo_flash_id_h_file_body(\%MAKEFILE_OPTIONS, \%CUSTOM_MEM_DEV_OPTIONS, \@MDL_INFO_LIST, \%COMM_MDL_INFO);
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate combo_nfi_config.h
+#****************************************************************************
+if (($GEN_FLS{combo_nfi_config_h}->exist == 0) and
+    ($MAKEFILE_OPTIONS{'nand_support'} eq 'TRUE') and
+    ($MAKEFILE_OPTIONS{'platform'} =~ /MT629[17]/))
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "combo_nfi_config_h";
+    my $gf = $GEN_FLS{combo_nfi_config_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    print GFH &combo_nfi_config_h_file_body(\%MAKEFILE_OPTIONS, \%CUSTOM_MEM_DEV_OPTIONS, \@MDL_INFO_LIST, \%COMM_MDL_INFO);
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+#****************************************************************************
+# generate combo_sfi_defs.h
+#****************************************************************************
+if (($GEN_FLS{combo_sfi_defs_h}->exist == 0) and
+    ($MAKEFILE_OPTIONS{'serial_flash_support'} eq 'TRUE') and
+    ($MAKEFILE_OPTIONS{'platform'} =~ 'MT629[17]'))
+{
+    printf "\n%s#%d: %s\n", __FILE__, __LINE__, "combo_sfi_defs_h";
+    my $gf = $GEN_FLS{combo_sfi_defs_h};
+    open (GFH, ">", $gf->path) or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print GFH &copyright_file_header();
+    print GFH &description_file_header($gf->name, $gf->description, $gf->owner);
+    print GFH &combo_sfi_defs_h_file_body(\%MAKEFILE_OPTIONS, \%CUSTOM_MEM_DEV_OPTIONS, \@MDL_INFO_LIST, \%COMM_MDL_INFO);
+    close GFH or &error_handler($gf->path.": $!", __FILE__, __LINE__);
+    print $gf->path, " is generated\n";
+}
+
+exit;
+
+#****************************************************************************
+# find the correct EMI database
+#****************************************************************************
+sub Find_MDL
+{
+    print("\n", (caller(0))[3], "\n");
+    my $MDL_DIR = "tools/MemoryDeviceList/";
+    my $platform = $MAKEFILE_OPTIONS{'platform'};
+    my $ver = 0000;
+
+    $MEMORY_DEVICE_LIST_XLS = "";
+    $MEMORY_DEVICE_LIST_INT_XLS = "";
+
+    opendir(DIR, $MDL_DIR) or &error_handler("$MDL_DIR: $!", __FILE__, __LINE__);;
+
+    if (defined($MAKEFILE_OPTIONS{'ram_support_type'}) && $MAKEFILE_OPTIONS{'ram_support_type'} eq 'NONE') {
+        $platform = $MAKEFILE_OPTIONS{'ram_support_type'};
+    }
+    while (my $mdl = readdir(DIR)) {
+        if ($mdl =~ /MemoryDeviceList_$platform\_SinceW(\w+)\.xls/) {
+            if ($1 > $ver) {
+                $ver = $1;
+                $MEMORY_DEVICE_LIST_XLS = $MDL_DIR . $mdl;
+            }
+        } elsif ($mdl =~ /MemoryDeviceList_$platform\_Internal\.xls/) {
+            $MEMORY_DEVICE_LIST_INT_XLS = $MDL_DIR . $mdl;
+        }
+    }
+
+    close(DIR);
+
+    if ((! -e $MEMORY_DEVICE_LIST_XLS) && (! -e $MEMORY_DEVICE_LIST_INT_XLS)) {
+            &error_handler("MDL Excel is not exist for platform '$platform':$MEMORY_DEVICE_LIST_XLS and $MEMORY_DEVICE_LIST_INT_XLS", __FILE__, __LINE__);
+    }
+
+    if ($DebugPrint == 1) {
+        print "custom memory device header file: $CUSTOM_MEMORY_DEVICE_HDR\n";
+        print "MDL Excel: $MEMORY_DEVICE_LIST_XLS, Internal MDL Excel: $MEMORY_DEVICE_LIST_INT_XLS\n";
+        print "Makefile: $THEMF\n";
+    }
+}
+
+#****************************************************************************
+# parse Project Makefile
+#****************************************************************************
+sub Parse_Makefile
+{
+    print("\n", (caller(0))[3], "\n");
+    my @filelist = ($THEMF, undef);
+    my $keyname;
+
+    foreach my $filename (@filelist) {
+        print "filename: $filename\n" if ($DebugPrint == 1);
+        open (FILE_HANDLE, "<$filename") or &error_handler("$filename: $!", __FILE__, __LINE__);
+        while (<FILE_HANDLE>) {
+            if (/^(\w+)\s*=\s*(\S+)/) {
+                $keyname = lc($1);
+                defined($MAKEFILE_OPTIONS{$keyname}) && warn "$1 redefined in $THEMF!\n";
+                $MAKEFILE_OPTIONS{$keyname} = $2;
+                if ($keyname eq 'internal_feature_option') {
+                    ($filelist[1] = $2) =~ s/\$WORKDIR/$ENV{WORKDIR}/;
+                }
+                print "$1 = $2\n" if ($DebugPrint == 1);
+            }
+        }
+        close (FILE_HANDLE);
+    }
+
+    $MAKEFILE_OPTIONS{'platform'} = $MAKEFILE_OPTIONS{'platform_chip'} || 'MT6291';
+}
+
+#****************************************************************************
+# parse Memory Device
+#****************************************************************************
+sub Parse_Memory_Device
+{
+    print("\n", (caller(0))[3], "\n");
+
+    open CUSTOM_MEMORY_DEVICE_HDR, "<$CUSTOM_MEMORY_DEVICE_HDR" or &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: $!", __FILE__, __LINE__);
+    while (<CUSTOM_MEMORY_DEVICE_HDR>)
+    {
+        # error-checking
+        if ((/^#if|^#ifdef|^#ifndef|^#elif|^#else/) and !(/^#ifndef\s+__CUSTOM_MEMORYDEVICE__/))
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Not allowed to set conditional keywords $_ in custom_MemoryDevice.h!", __FILE__, __LINE__);
+        }
+
+        if (/^#define\s+(\w+)\s+\(?([\w|\-]*)\)?/)
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: $1 redefined in custom_MemoryDevice.h!", __FILE__, __LINE__) if defined($CUSTOM_MEM_DEV_OPTIONS{$1});
+            $CUSTOM_MEM_DEV_OPTIONS{$1} = ((!defined $2) or ($2 eq ''))? 'TRUE': $2;
+
+            my $option = $1;
+            my $content = $2;
+            if ($option =~ /COMBO_MEM(\d+)_CS(\d+)_PART_NUMBER/)
+            {
+                next if ((!defined $MAKEFILE_OPTIONS{'combo_memory_support'}) or ($MAKEFILE_OPTIONS{'combo_memory_support'} eq 'FALSE'));
+                print "$option = $content\n" if ($DebugPrint == 1);
+                &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: COMBO_MEM_ENTRY_COUNT needs to be defined when COMBO_MEMORY_SUPPORT is turned on!", __FILE__, __LINE__) if (!defined $CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT});
+                my %TMP_PART_NUMBER;
+                my $mcp_idx = $1;
+                my $cs = $2;
+                $LPSDRAM_CHIP_SELECT = $cs;
+                &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Only COMBO_MEM0n_CS0 or COMBO_MEM0n_CS1 is allowed!", __FILE__, __LINE__) if (($LPSDRAM_CHIP_SELECT != 0) && ($LPSDRAM_CHIP_SELECT != 1));
+                $TMP_PART_NUMBER{$cs} = $content;
+
+                if (defined $MCP_LIST[$mcp_idx])
+                {
+                    if (defined $MCP_LIST[$mcp_idx]->{$cs})
+                    {
+                        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: COMBO_MEM$mcp_idx\_CS$cs\_PART_NUMBER multiply defined!", __FILE__, __LINE__);
+                    }
+                    else
+                    {
+                        $MCP_LIST[$mcp_idx]->{$cs} = $TMP_PART_NUMBER{$cs};
+                    }
+                }
+                else
+                {
+                    $MCP_LIST[$mcp_idx] = \%TMP_PART_NUMBER;
+                }
+            }
+            elsif ($option =~ /CS(\d+)_PART_NUMBER/)
+            {
+                next if ($MAKEFILE_OPTIONS{'combo_memory_support'} eq 'TRUE');  # when COMBO_MEMORY_SUPPORT is turned on, CSx_PART_NUMBER is ignored
+                print "$option = $content\n" if ($DebugPrint == 1);
+                my $cs = $1;
+                $LPSDRAM_CHIP_SELECT = $cs;
+                &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Only COMBO_MEM0n_CS0 or COMBO_MEM0n_CS1 is allowed!", __FILE__, __LINE__) if (($LPSDRAM_CHIP_SELECT != 0) && ($LPSDRAM_CHIP_SELECT != 1));
+                $PART_NUMBER{$cs} = $content;
+            }
+        }
+    }
+    close CUSTOM_MEMORY_DEVICE_HDR;
+
+    $CUSTOM_MEM_DEV_OPTIONS{NOR_FDM4_ESB_PARAMETER_ERASE_QUEUE_SIZE}   = (defined $CUSTOM_MEM_DEV_OPTIONS{NOR_FDM4_ESB_PARAMETER_ERASE_QUEUE_SIZE}) ? $CUSTOM_MEM_DEV_OPTIONS{NOR_FDM4_ESB_PARAMETER_ERASE_QUEUE_SIZE} : 5;
+    $CUSTOM_MEM_DEV_OPTIONS{NOR_PARAMETER_SYSTEM_DRIVE_RESERVED_BLOCK} = (defined $CUSTOM_MEM_DEV_OPTIONS{NOR_PARAMETER_SYSTEM_DRIVE_RESERVED_BLOCK}) ? $CUSTOM_MEM_DEV_OPTIONS{NOR_PARAMETER_SYSTEM_DRIVE_RESERVED_BLOCK} : 3;
+
+    # No Combo MCP case
+    if ($MAKEFILE_OPTIONS{'combo_memory_support'} ne 'TRUE')
+    {
+        $MCP_LIST[1] = \%PART_NUMBER;
+        $CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT} = 1;
+    }
+
+    # COMBO_MEM_ENTRY_COUNT and the number of COMBO_MEMxx_CSx_PART_NUMBER does not match
+    if ($CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT} != $#MCP_LIST)
+    {
+        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: COMBO_MEM_ENTRY_COUNT and the number of COMBO_MEMxx_CSx_PART_NUMBER does not match!", __FILE__, __LINE__);
+    }
+
+    ###Check MEMORY_DEVICE_TYPE correction
+    if (($CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE} !~ /$MAKEFILE_OPTIONS{'ram_support_type'}/) ||
+        (($MAKEFILE_OPTIONS{'ram_support_type'} =~ /DDR$/) && ($CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE} =~ /DDR\d+/))) {
+        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: ram type of MEMORY_DEVICE_TYPE ($CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE}) and ram_support_type ($MAKEFILE_OPTIONS{'ram_support_type'}) should be the same", __FILE__, __LINE__);
+    }
+}
+
+#****************************************************************************
+# parse EMI database to get EMI settings
+#****************************************************************************
+sub Parse_MDL
+{
+    print("\n", (caller(0))[3], "\n");
+
+    my $result;
+    my %tmp_hash;
+
+    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Incorrect memory device type!", __FILE__, __LINE__) unless $CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE};
+
+    ##Parse DRAM Parameters
+    my $dram_sheet = $CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE};
+    if ($MAKEFILE_OPTIONS{'sip_ram_size'} ne 'NONE') {
+        $dram_sheet = 'SIP_' . $dram_sheet;
+    }
+    if ($emi_is_existed == 1) {
+        for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+        {
+            $result = &get_info($MEMORY_DEVICE_LIST_XLS, $dram_sheet, $MCP_LIST[$_], \%tmp_hash);
+            if ($result ne 'TRUE')
+            {
+                print "INTERNAL FIND part number: $MCP_LIST[$_]->{0}, $MCP_LIST[$_]->{1}\n";
+                $result = &get_info($MEMORY_DEVICE_LIST_INT_XLS, $dram_sheet, $MCP_LIST[$_], \%tmp_hash);
+            }
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Part Number $MCP_LIST[$_]->{0} not found!", __FILE__, __LINE__) if ($result ne 'TRUE');
+            $MDL_INFO_LIST[$_] = Storable::dclone(\%tmp_hash);
+        }
+    } else {
+        for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+        {
+            $result = &get_info($MEMORY_DEVICE_LIST_XLS, 'RAM', $MCP_LIST[$_], \%tmp_hash);
+            if ($result ne 'TRUE')
+            {
+                print "INTERNAL FIND part number: $MCP_LIST[$_]->{0}, $MCP_LIST[$_]->{1}\n";
+                $result = &get_info($MEMORY_DEVICE_LIST_INT_XLS, 'RAM', $MCP_LIST[$_], \%tmp_hash);
+            }
+            &error_handler("Cannot find $MAKEFILE_OPTIONS{'platform'} in MDL!", __FILE__, __LINE__) if ($result ne 'TRUE');
+            $MDL_INFO_LIST[$_] = Storable::dclone(\%tmp_hash);
+        }
+    }
+
+    undef %tmp_hash;
+    ##Parse Flash Parameters
+    ###disctete type should parse flash info sheet
+    if (($CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE} !~ /\w+_\w+_MCP/) && (($MAKEFILE_OPTIONS{'serial_flash_support'} eq 'TRUE') || ($MAKEFILE_OPTIONS{'nand_support'} eq 'TRUE')))
+    {
+        ###Parse Flash MDL info
+
+        my $flash_sheet = '';
+        if($MAKEFILE_OPTIONS{'serial_flash_support'} eq 'TRUE')#sheet "SERIAL_FLASH" in MDL
+        {
+            $flash_sheet = 'SERIAL_FLASH';
+        }
+        elsif($MAKEFILE_OPTIONS{'nand_support'} eq 'TRUE')
+        {
+            $flash_sheet = 'NAND_FLASH';
+        }
+
+        for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+        {
+            print "part number: $MCP_LIST[$_]->{0}, $MCP_LIST[$_]->{1}\n";
+            $result = &get_info($MEMORY_DEVICE_LIST_XLS, $flash_sheet, $MCP_LIST[$_], \%tmp_hash);
+            if ($result ne 'TRUE')
+            {
+                print "INTERNAL FIND part number: $MCP_LIST[$_]->{0}, $MCP_LIST[$_]->{1}\n";
+                $result = &get_info($MEMORY_DEVICE_LIST_INT_XLS, $flash_sheet, $MCP_LIST[$_], \%tmp_hash);
+            }
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: $flash_sheet Part Number $MCP_LIST[$_]->{1} not found!", __FILE__, __LINE__) if ($result ne 'TRUE');
+            $MDL_INFO_LIST[$_]->{1} = Storable::dclone($tmp_hash{1});
+        }
+    }
+
+    for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+    {
+        &dump_mdl_info($MDL_INFO_LIST[$_], "\$MDL_INFO_LIST[$_]->");
+    }
+}
+
+#****************************************************************************
+# check emi clock driving config
+#****************************************************************************
+sub check_emi_clk_driving_cfg
+{
+    my ($emi_clk_driving, $bb_chip) = @_;
+    if ($bb_chip eq 'MT6290')
+    {
+        if (($emi_clk_driving->{'DRAMC_ACTIM0_VAL'} !~ /0x[0-9a-fA-F]{8,8}/) || ($emi_clk_driving->{'DRAMC_DRVCTL0_VAL'} !~ /0x[0-9a-fA-F]{8,8}/) || ($emi_clk_driving->{'DRAMC_DRVCTL1_VAL'} !~ /0x[0-9a-fA-F]{8,8}/))
+        {
+            return 0;
+        }
+    }
+    elsif ($bb_chip eq 'MT6291')
+    {
+        if (($emi_clk_driving->{'DRAMC_ACTIM0_VAL'} !~ /0x[0-9a-fA-F]{8,8}/) || ($emi_clk_driving->{'DRAMC_IODRV8_VAL'} !~ /0x[0-9a-fA-F]{8,8}/) || ($emi_clk_driving->{'DRAMC_DRVCTL1_VAL'} !~ /0x[0-9a-fA-F]{8,8}/))
+        {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+#****************************************************************************
+# check consistency among FEATURE, MEMORY_DEVICE_TYPE, PLATFORM, MCU_CLOCK and MemoryDeviceList data
+#****************************************************************************
+sub Decide_MemClockSpeed
+{
+    print("\n", (caller(0))[3], "\n");
+
+    my $emi_force_clk  = 0;
+    my $sfi_force_clk  = 0;
+    if ((defined $CUSTOM_MEM_DEV_OPTIONS{EMI_CLK}) and $CUSTOM_MEM_DEV_OPTIONS{EMI_CLK} ne 'DEFAULT')
+    {
+        $emi_clk_config = $emi_force_clk = $CUSTOM_MEM_DEV_OPTIONS{EMI_CLK};
+    }
+    if ((defined $CUSTOM_MEM_DEV_OPTIONS{SFI_CLK}) and $CUSTOM_MEM_DEV_OPTIONS{SFI_CLK}ne 'DEFAULT')
+    {
+        if ($CUSTOM_MEM_DEV_OPTIONS{SFI_CLK} eq '104M')
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: $MAKEFILE_OPTIONS{'platform'} does not support SFI clock as 104MHz! Please configure SFI_CLK as DEFAULT!", __FILE__, __LINE__) if ($BBtbl_SFI_FAST_CLK{$MAKEFILE_OPTIONS{'platform'}} != 104);
+            $sfi_clk_config = $sfi_force_clk = 104;
+        }
+        elsif ($CUSTOM_MEM_DEV_OPTIONS{SFI_CLK} eq '78M')
+        {
+            $sfi_clk_config = $sfi_force_clk = 78;
+        }
+        else
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Please configure SFI_CLK as 104M, 78M, or DEFAULT!", __FILE__, __LINE__);
+        }
+    }
+
+    if($MAKEFILE_OPTIONS{'serial_flash_support'} eq 'TRUE')
+    {
+        my $sfi_104_support = 1;  # assume all devices can support 104; set as 0 if found not support
+        my $sfi_78_support  = 1;  # assume all devices can support 78; set as 0 if found not support
+
+        #print "test $CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE}\n";
+        ### Check if all devices can support the required clock
+        if ($sfi_force_clk != 0)  # force specific clock
+        {
+            for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+            {
+                if ((!defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, $sfi_clk_config, $MAKEFILE_OPTIONS{'platform'}, 'SF_CTL', \@MDL_INFO_LIST)) or (&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, $sfi_clk_config, $MAKEFILE_OPTIONS{'platform'}, 'DRIVING', \@MDL_INFO_LIST) eq 'x') or (&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, $sfi_clk_config, $MAKEFILE_OPTIONS{'platform'}, 'DRIVING', \@MDL_INFO_LIST) eq ' '))  # unable to find the specified clock setting
+                {
+                    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Device $_ not support $sfi_clk_config MHz settings!", __FILE__, __LINE__);
+                }
+            }
+        }
+        else
+        {
+            for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+            {
+                #if ((!defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS{'platform'}, 'SF_CTL', \@MDL_INFO_LIST)) or (&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS{'platform'}, 'SFI_MAC_CTL', \@MDL_INFO_LIST) eq 'x') or (&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS{'platform'}, 'SFI_MAC_CTL', \@MDL_INFO_LIST) eq ' '))  # unable to find 104MHz clock setting
+                if (!defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS{'platform'}, 'SF_CTL', \@MDL_INFO_LIST))
+                {
+                    $sfi_104_support = 0;
+                }
+                #if ((!defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS{'platform'}, 'SF_CTL', \@MDL_INFO_LIST)) or (&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS{'platform'}, 'SFI_MAC_CTL', \@MDL_INFO_LIST) eq 'x') or (&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS{'platform'}, 'SFI_MAC_CTL', \@MDL_INFO_LIST) eq ' '))  # unable to find 104MHz clock setting
+                if (!defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS{'platform'}, 'SF_CTL', \@MDL_INFO_LIST))
+                {
+                    $sfi_78_support = 0;
+                }
+                if (($sfi_104_support == 0) and ($sfi_78_support == 0))
+                {
+                    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Unable to find valid SFI settings!", __FILE__, __LINE__);
+                }
+                elsif ($sfi_104_support != 0)
+                {
+                    $sfi_clk_config = 104;
+                }
+                else  # sfi_104_support == 0 and sfi_78_support != 0
+                {
+                    $sfi_clk_config = 78;
+                }
+            }
+        }
+        print "SFI_CLK configured as $sfi_clk_config\n" if ($DebugPrint == 1);
+    }
+
+    if (($emi_is_existed == 1) and ($MAKEFILE_OPTIONS{'platform'} eq 'MT6291')) {
+        ### Check if all devices can support the required clock
+        if ($emi_force_clk != 0)  # force specific clock
+        {
+            for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+            {
+                my $mhz_emi_driving = $emi_clk_config . 'MHZ EMI Driving';
+                my $emi_clk_driving = $MDL_INFO_LIST[$_]->{0}->{$MAKEFILE_OPTIONS{'platform'}}->{$mhz_emi_driving};
+                if (&check_emi_clk_driving_cfg($emi_clk_driving, $MAKEFILE_OPTIONS{'platform'}) == 0)
+                {
+                    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Device $_ not support $emi_clk_config MHz settings!", __FILE__, __LINE__);
+                }
+            }
+        }
+        else
+        {
+            my %emi_clk;
+            for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+            {
+                my $mdl_info_platform = $MDL_INFO_LIST[$_]->{0}->{$MAKEFILE_OPTIONS{'platform'}};
+                foreach my $mdl_info_content (sort keys %{$mdl_info_platform})
+                {
+                    if ($mdl_info_content =~ /(\d+)MHZ EMI Driving/)
+                    {
+                        if (!defined $emi_clk{$1})
+                        {
+                            $emi_clk{$1} = 'TRUE';
+                        }
+                        my $emi_clk_driving = $mdl_info_platform->{$mdl_info_content};
+                        if (&check_emi_clk_driving_cfg($emi_clk_driving, $MAKEFILE_OPTIONS{'platform'}) == 0)
+                        {
+                            $emi_clk{$1} = 'FALSE';
+                        }
+                    }
+                }
+            }
+            undef $emi_clk_config;
+            while( (my $clk_speed, my $value) = each %emi_clk)
+            {
+                if ($value eq 'TRUE')
+                {
+                    next if ((defined $emi_clk_config) && ($emi_clk_config >= $clk_speed));
+
+                    $emi_clk_config = $clk_speed;
+                }
+
+            }
+            if (!defined $emi_clk_config) {
+                &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Device $_ not support $emi_clk_config MHz settings!", __FILE__, __LINE__);
+            }
+        }
+        print "EMI_CLK configured as $emi_clk_config\n" if ($DebugPrint == 1);
+    }
+}
+
+#****************************************************************************
+# subroutine:  dump_mdl_info
+# return:      none
+# input:       $mdl_ref:     MDL hash reference
+#              $pre_string:  to print prefix string
+#****************************************************************************
+sub dump_mdl_info
+{
+    my ($mdl_ref, $pre_string) = @_;
+    my $prefix_string;
+    foreach my $key (keys %{$mdl_ref})
+    {
+        $prefix_string = $pre_string if (defined $pre_string);
+        $prefix_string = $prefix_string . "{$key}";
+        if (ref $mdl_ref->{$key} eq 'HASH')
+        {
+            &dump_mdl_info($mdl_ref->{$key}, "$prefix_string->");
+        }
+        else
+        {
+            print "$prefix_string = " . $mdl_ref->{$key} . "\n";
+        }
+    }
+}
+
+#****************************************************************************
+# subroutine:  fs_read_excel
+# return:      hash of matching rows and indexing rows
+# input:       $file:        excel file to be read
+#              $sheet:       sheet to open
+#              $target_href: the target patterns to be searched
+#****************************************************************************
+sub fs_read_excel
+{
+    my ($file, $sheet, $target_href, $BACKUP_MDL_PATH) = @_;
+    my %mdl_list;
+    my $copied_file;
+    my $curr_time_str = &get_CurrTime_str();
+    my $WorkSheet;
+    my $Excel;
+    my $Book;
+    my $parser;
+    my $workbook;
+    my ($row_min, $row_max);
+    my ($col_min, $col_max);
+
+    if (! -e $file) {
+        return %mdl_list;
+    }
+
+    ### copy and rename the current excel file to prevent concurrency build problem
+    # remove ^.\ or ^\
+    $BACKUP_MDL_PATH =~ s/^.\\|^\\//;
+
+    if($BACKUP_MDL_PATH =~ /out/)
+    {
+        print "BAKCUP_MDL_PATH match with /build/ PREMATCH:$`, MATCH:$&, POSTMATCH:$'\n";
+        $BACKUP_MDL_PATH = $& . $';
+    }
+    print "file is $file\n";
+    if($file =~ /^(\w+.*?)\.xls$/)
+    {
+        if($1 =~ /^(\w*.*?)tools(\w*.*?)MemoryDeviceList_(\w+)$/)
+        {
+            print "1:$1,2:$2,3:$3\n";
+            $copied_file = $1 . $BACKUP_MDL_PATH . "\/". "MemoryDeviceList_" . $3;
+        }
+    }
+    $copied_file = $copied_file . "_" . $sheet . "_" . $curr_time_str . $$ . ".xls";
+    File::Copy::copy($file, $copied_file);
+    $file = $copied_file;
+
+    $parser   = Spreadsheet::ParseExcel->new();
+    $workbook = $parser->parse($file) or &error_handler("$file: $!", __FILE__, __LINE__);;
+    $WorkSheet = $workbook->worksheet($sheet);
+    ($row_min, $row_max) = $WorkSheet->row_range();
+    ($col_min, $col_max) = $WorkSheet->col_range();
+
+    # get the sheet name
+    my $name = $WorkSheet->get_name();
+    print "sheetname: $name, row: {$row_min, $row_max}, col: {$col_min, $col_max}\n";
+
+    my $xls_content_row_start;
+    my $xls_content_num;
+    for my $col ($col_min..$col_max)
+    {
+        my @mdl_each_col_content = undef;
+        my $array_content_row_start = undef;
+        foreach my $row ($row_min..$row_max)
+        {
+            if (($col == 0) && (!defined $WorkSheet->get_cell($row, $col)->is_merged()) && (!defined $xls_content_row_start))
+            {
+                $xls_content_row_start = $row;
+                $xls_content_num = $row_max - $xls_content_row_start + 1;
+            }
+            if (((defined $xls_content_row_start) && ($row >= $xls_content_row_start)) || (!defined $mdl_each_col_content[-1]) || ($mdl_each_col_content[-1] ne &xls_cell_value($WorkSheet, $row, $col)))
+            {
+                push(@mdl_each_col_content, &xls_cell_value($WorkSheet, $row, $col));
+            }
+            if ((defined $xls_content_row_start) && ($row == $xls_content_row_start))
+            {
+                $array_content_row_start = $#mdl_each_col_content;
+            }
+        }
+        my @hash_tail_array;
+        my $hash_pointer;
+        for (1..$xls_content_num)
+        {
+            push(@hash_tail_array, \%{$mdl_list{$_}});
+        }
+        print "col" if ($DebugPrint == 1);
+        foreach my $idx (1..$array_content_row_start-1)
+        {
+            my $data = $mdl_each_col_content[$idx];
+            print "{$data}" if ($DebugPrint == 1);
+            for my $content_idx (1..$xls_content_num)
+            {
+                $hash_pointer = shift(@hash_tail_array);
+                if ($idx != $array_content_row_start-1)
+                {
+                    print "->" if ($content_idx == 1) && ($DebugPrint == 1);
+                    if (!defined $hash_pointer->{$data})
+                    {
+                        $hash_pointer->{$data} = {};
+                    }
+                    $hash_pointer = $hash_pointer->{$data};
+                    #print "[$idx][$content_idx]hash_pointer:$hash_pointer\n";
+                }
+                push(@hash_tail_array, $hash_pointer);
+            }
+        }
+        print "=" if ($DebugPrint == 1);
+        foreach my $idx ($array_content_row_start..$#mdl_each_col_content)
+        {
+            my $mdl_idx = $idx - $array_content_row_start + 1;
+            my $data = $mdl_each_col_content[$idx];
+            $hash_pointer = shift(@hash_tail_array);
+            $hash_pointer->{$mdl_each_col_content[$array_content_row_start-1]} = $data;
+            print "$mdl_idx:$data" if ($DebugPrint == 1);
+            if ($idx != $#mdl_each_col_content)
+            {
+                print ", " if ($DebugPrint == 1);
+            }
+        }
+        print "\n" if ($DebugPrint == 1);
+    }
+
+    return %mdl_list;
+}
+
+#****************************************************************************
+# subroutine:  get_info
+# input:       $file:        file path
+#              $sheet:       sheet to open
+#              $target_href: the target patterns to be searched
+#              $href:        output matching hash reference
+#****************************************************************************
+sub get_info
+{
+    my ($file, $sheet, $target_href, $href) = @_;
+    my %mdl_list;
+
+    %mdl_list = &fs_read_excel($file, $sheet,  $target_href, $LOAD_TMP_PATH);
+
+    if (keys(%mdl_list) == 0)
+    {
+        print "get_info return FALSE!\n" if ($DebugPrint == 1);
+        return "FALSE";
+    }
+
+    foreach my $idx (keys %mdl_list)
+    {
+        if ($sheet =~ /\w+_\w+_MCP/)
+        {
+            if ($target_href->{0} eq $mdl_list{$idx}->{'Part Number'})
+            {
+                $href->{0} = $mdl_list{$idx}->{'DRAM Parameters'};
+                $href->{1} = $mdl_list{$idx}->{'Flash Parameters'};
+                for my $keys (keys %{$mdl_list{$idx}})
+                {
+                    if (($keys ne 'DRAM Parameters') && ($keys ne 'Flash Parameters'))
+                    {
+                        for (0..1)
+                        {
+                            $href->{$_}->{$keys} = $mdl_list{$idx}->{$keys};
+                        }
+                    }
+                }
+            }
+        }
+        else
+        {
+            my $tgt = 0;
+            $tgt = $tgt + 1 if ($sheet =~ /\w+_FLASH/);
+            if ($target_href->{$tgt} eq $mdl_list{$idx}->{'Part Number'})
+            {
+                $href->{$tgt} = $mdl_list{$idx};
+            }
+        }
+    }
+    &dump_mdl_info($href, "\$href->") if ($DebugPrint == 1);
+
+    if (keys(%{$href}) == 0)
+    {
+        return "FALSE";
+    }
+
+    return "TRUE";
+}
+
+#****************************************************************************
+# subroutine:  xls_cell_value
+# return:      excel cell value no matter it's in merge area or not
+# input:       $sheet:  specified Excel Sheet
+#              $row:    specified row number
+#              $col:    specified column number
+#****************************************************************************
+sub xls_cell_value
+{
+    my ($sheet, $row, $col) = @_;
+
+    my $cell = $sheet->get_cell($row, $col);
+    if ($cell)
+    {
+        if ((defined $cell->is_merged()) && ($cell->is_merged() == 1))
+        {
+            my $mareas =$sheet->get_merged_areas();
+            # get merged areas' value if cell is merged
+            for (0..$#$mareas)
+            {
+                if(($row >= $mareas->[$_]->[0] && $row <= $mareas->[$_]->[2]) && ($col >= $mareas->[$_]->[1] && $col <= $mareas->[$_]->[3]))
+                {
+                    return $sheet->get_cell($mareas->[$_]->[0], $mareas->[$_]->[1])->value();
+                }
+            }
+        } else {
+            # cell is not merged cell => get cell value
+            return $cell->value();
+        }
+    } else {
+        my $value;
+        return $value;
+    }
+}
+
+#****************************************************************************
+# subroutine:  dependency check
+# return:      None
+#****************************************************************************
+sub Check_ManualCheckIn
+{
+    my ($target, $force_del_new_file, @depends) = @_;
+
+    return unless (-e $target);
+
+    ## Now check if the $target file check-in or auto-gen
+    ## Read whole file ##
+    open SRC_FILE_R, "<$target" or &error_handler("$target: file error!", __FILE__, __LINE__);
+    local $/;
+    my $reading = <SRC_FILE_R>;
+    close SRC_FILE_R;
+
+    ## Look for check-in pattern ##
+    if ($reading =~ /MANUAL-CHECKIN/i)
+    {
+        print "$target: Check-in message is found. No need to update.\n";
+        return;
+    }
+
+    unlink $target if (-e $target);
+    return;
+}
+
+#****************************************************************************
+# subroutine:  CheckConfigValid
+# return:      None
+#****************************************************************************
+sub CheckConfigValid
+{
+    print("\n", (caller(0))[3], "\n");
+
+    my $dram_size = eval($MDL_INFO_LIST[1]->{0}->{'Density (Mb)'});
+
+    ###Check DRAM Size
+    for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+    {
+        if ($emi_is_existed == 1)
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Please select correct dram! dram_size is $dram_size.", __FILE__, __LINE__) if ((!defined $dram_size) || ($dram_size <= 0));
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Please select dram with the same size ($dram_size vs. $MDL_INFO_LIST[$_]->{0}->{'Density (Mb)'}) when COMBO_MEMORY_SUPPORT is enabled!", __FILE__, __LINE__) if ($dram_size ne eval($MDL_INFO_LIST[$_]->{0}->{'Density (Mb)'}));
+        }
+    }
+
+    ###Check Combo Memory Supporting
+    if($MAKEFILE_OPTIONS{'combo_memory_support'} eq 'TRUE')
+    {
+        my @combo_mem_part_number;
+        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: MEMORY_DEVICE_TYPE supports only XXX_MCP when COMBO_MEMORY_SUPPORT is enabled!", __FILE__, __LINE__) if ((($CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE} !~ /\w+_MCP/) && ($CUSTOM_MEM_DEV_OPTIONS{MEMORY_DEVICE_TYPE} ne 'NONE')) && ($MAKEFILE_OPTIONS{'sip_ram_size'} eq "NONE"));
+        for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: COMBO MEM Part Number ($MCP_LIST[$_]->{0}) of DRAM should be the same that ($MCP_LIST[$_]->{1}) of Flash!", __FILE__, __LINE__) if (($MCP_LIST[$_]->{0} ne $MCP_LIST[$_]->{1}) && ($MAKEFILE_OPTIONS{'sip_ram_size'} eq "NONE"));
+            for my $combo_mem_idx (0..$#combo_mem_part_number)
+            {
+                &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Each COMBO MEM Part Number should be difference ($combo_mem_part_number[$combo_mem_idx]:COMBO_MEM0$combo_mem_idx and COMBO_MEM0$_)!", __FILE__, __LINE__) if ($MCP_LIST[$_]->{1} eq $combo_mem_part_number[$combo_mem_idx]);
+            }
+            push(@combo_mem_part_number, $MCP_LIST[$_]->{1});
+        }
+    }
+
+    ###Combo Flash Size Should be same
+    if($MAKEFILE_OPTIONS{'combo_memory_support'} eq 'TRUE')
+    {
+        if($MAKEFILE_OPTIONS{'serial_flash_support'} eq 'TRUE')
+        {
+            for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+            {
+                my $cur_nor_size_Mb;
+                $cur_nor_size_Mb = $MDL_INFO_LIST[$_]->{1}->{'Size (Mb)'};
+                if (!defined $nor_size_Mb)
+                {
+                    $nor_size_Mb = $cur_nor_size_Mb;
+                }
+                else
+                {
+                    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Please select flash with the same size when COMBO_MEMORY_SUPPORT is enabled!", __FILE__, __LINE__) if ($nor_size_Mb != $cur_nor_size_Mb);
+                }
+            }
+        }
+        elsif($MAKEFILE_OPTIONS{'nand_support'} eq 'TRUE')
+        {
+            my $nand_size_Mb;
+            my $nand_block_size;
+            for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+            {
+                my $cur_nand_size_Mb;
+                my $cur_nand_block_size;
+                $cur_nand_size_Mb = $MDL_INFO_LIST[$_]->{1}->{'NAND Size(MB)'};
+                $cur_nand_block_size = $MDL_INFO_LIST[$_]->{1}->{'NAND Block Size(KB)'};
+                if (!defined $nand_size_Mb)
+                {
+                    $nand_size_Mb = $cur_nand_size_Mb;
+                    $nand_block_size = $cur_nand_block_size;
+                }
+                else
+                {
+                    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Please select flash with the same size when COMBO_MEMORY_SUPPORT is enabled!", __FILE__, __LINE__) if ($nand_size_Mb != $cur_nand_size_Mb);
+                    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR: Please select flash with the same block size when COMBO_MEMORY_SUPPORT is enabled!", __FILE__, __LINE__) if ($nand_block_size != $cur_nand_block_size);
+                }
+            }
+        }
+    }
+
+    ###Serail Flash Support and NAND Support Should Not be TRUE at the same time
+    if(($MAKEFILE_OPTIONS{'serial_flash_support'} eq 'TRUE') and ($MAKEFILE_OPTIONS{'nand_support'} eq 'TRUE'))
+    {
+        &error_handler("Serial Flash and NAND Flash Support at the same time, please check makefile setting!!", __FILE__, __LINE__);
+    }
+
+    ###The Flash Name max length is 64
+    if($MAKEFILE_OPTIONS{'nand_support'} eq 'TRUE')
+    {
+        for (1..$CUSTOM_MEM_DEV_OPTIONS{COMBO_MEM_ENTRY_COUNT})
+        {
+            my $vendor = uc($MDL_INFO_LIST[$_]->{1}->{'Vendor'});
+            my $PartNumber = $MDL_INFO_LIST[$_]->{1}->{'Part Number'};
+            my $flash_name .= "$vendor " if (defined $vendor);
+            $flash_name .= "$PartNumber" if (defined $PartNumber);
+            my $name_length = length($flash_name);
+            if($name_length > 64)
+            {
+                &error_handler("Flash Name is too long, DA Support Max Length is 64 byte!!", __FILE__, __LINE__);
+            }
+        }
+    }
+
+    return;
+}
+
+#****************************************************************************
+# subroutine:  error_handler
+# input:       $error_msg:     error message
+#****************************************************************************
+sub error_handler
+{
+    my ($error_msg, $file, $line_no) = @_;
+    my $final_error_msg = "EMIGEN ERROR: $error_msg at $file line $line_no\n";
+    print $final_error_msg;
+    die $final_error_msg;
+}
+
+#****************************************************************************
+# subroutine:  copyright_file_header
+# return:      file header -- copyright
+#****************************************************************************
+sub copyright_file_header
+{
+    my $template = <<"__TEMPLATE";
+/*****************************************************************************
+*  Copyright Statement:
+*  --------------------
+*  This software is protected by Copyright and the information contained
+*  herein is confidential. The software may not be copied and the information
+*  contained herein may not be used or disclosed except with the written
+*  permission of MediaTek Inc. (C) 2019
+*
+*  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+*  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+*  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON
+*  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+*  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+*  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+*  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+*  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH
+*  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO
+*  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S
+*  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.
+*
+*  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE
+*  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+*  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+*  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO
+*  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+*
+*  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE
+*  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF
+*  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND
+*  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER
+*  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).
+*
+*****************************************************************************/
+
+__TEMPLATE
+
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  description_file_header
+# return:      file header -- description
+# input:       $filename:     filename
+# input:       $description:  one line description
+# input:       $author:       optional
+#****************************************************************************
+sub description_file_header
+{
+    my ($filename, $description, $author) = @_;
+    my $src_file = (-e $MEMORY_DEVICE_LIST_XLS)? $MEMORY_DEVICE_LIST_XLS:
+                   (-e $MEMORY_DEVICE_LIST_INT_XLS)? $MEMORY_DEVICE_LIST_INT_XLS:
+                   __FILE__;
+    my $datetime = POSIX::strftime("%Y/%m/%d_%H:%M:%S", localtime((stat $src_file)[9]));
+
+    my $template = <<"__TEMPLATE";
+/*****************************************************************************
+ *
+ * Filename:
+ * ---------
+ *   $filename
+ *
+ * Project:
+ * --------
+ *   MOLY
+ *
+ * Description:
+ * ------------
+ *   $description
+ *
+ * Author:
+ * -------
+ *  $author by emigen $EMIGEN_VERNO
+ *
+ *   Memory Device database last modified on $datetime
+ *
+ *============================================================================
+ *             HISTORY
+ * Below this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
+ *------------------------------------------------------------------------------
+ * \$Revision\$
+ * \$Modtime\$
+ * \$Log\$
+ *
+ *------------------------------------------------------------------------------
+ * Upper this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
+ *============================================================================
+ ****************************************************************************/
+
+__TEMPLATE
+
+    return $template;
+}
+
+#******************************************************************************
+# subroutine:  get_CurrTime_str
+# return:      string of the current time
+# input:       None
+#******************************************************************************
+sub get_CurrTime_str
+{
+    my($sec, $min, $hour, $mday, $mon, $year) = localtime(time);
+    return (sprintf "%04d_%02d_%02d_%02d_%02d_%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
+}
+
+#****************************************************************************
+# subroutine:  SCHEME Configure Routines :: Query :: MD Information
+#****************************************************************************
+sub query_MD_INFO
+{
+    my ($MAKEFILE_OPTIONS_LOCAL,$CUSTOM_MEM_DEV_OPTIONS_LOCAL,$COMM_MDL_INFO_LOCAL) = @_;
+    open write_file, ">", "MD_INFO.txt" or &error_handler("MD_INFO.txt: $!", __FILE__, __LINE__);
+    #if(($CUSTOM_MEM_DEV_OPTIONS{'MEMORY_DEVICE_TYPE'} = 'LPDDR') || ($CUSTOM_MEM_DEV_OPTIONS{'MEMORY_DEVICE_TYPE'} = 'LPDDR2') || ($CUSTOM_MEM_DEV_OPTIONS{'MEMORY_DEVICE_TYPE'} = 'LPSDRAM'))
+    #{
+        #if(defined $MAKEFILE_OPTIONS{'sip_ram_size'})
+        #{
+            #print write_file "Memory device type:$CUSTOM_MEM_DEV_OPTIONS{'MEMORY_DEVICE_TYPE'}";
+            #print write_file "size:$COMM_MDL_INFO{1}->{'Size (Mb)'}"
+        #}
+        #else
+        #{
+            #print write_file "Memory device type:$CUSTOM_MEM_DEV_OPTIONS{'MEMORY_DEVICE_TYPE'}";
+            #print write_file "size:$COMM_MDL_INFO{1}->{'Size (Mb)'}"
+        #}
+
+    #}
+    #else
+    #{
+        #if(defined $MAKEFILE_OPTIONS{'sip_ram_size'})
+        #{
+            #print write_file "Memory device type:$CUSTOM_MEM_DEV_OPTIONS{'MEMORY_DEVICE_TYPE'}";
+            #print write_file "size:$COMM_MDL_INFO{1}->{'Size (Mb)'}"
+        #}
+        #else
+        #{
+            #print write_file "Memory device type:$CUSTOM_MEM_DEV_OPTIONS{'MEMORY_DEVICE_TYPE'}";
+            #print write_file "size:$COMM_MDL_INFO{1}->{'Size (Mb)'}"
+        #}
+    #}
+
+    print write_file "Memory device type:$CUSTOM_MEM_DEV_OPTIONS{'MEMORY_DEVICE_TYPE'}\n";
+    print write_file "size:$COMM_MDL_INFO{1}->{'Size (Mb)'}\n";
+    close(write_file);
+}
+
+#****************************************************************************
+# subroutine:  Parse Custom Feature Configuration
+#****************************************************************************
+sub Parse_custom_FeatureConfig
+{
+    print("\n", (caller(0))[3], "\n");
+
+    open CUSTOM_FEATURE_CONFIG_HDR, "<$CUSTOM_FEATURE_CONFIG_HDR" or &error_handler("$CUSTOM_FEATURE_CONFIG_HDR: $!", __FILE__, __LINE__);
+    while (<CUSTOM_FEATURE_CONFIG_HDR>)
+    {
+        # error-checking
+        if ((/^#if|^#ifdef|^#ifndef|^#elif|^#else/) && !(/^#ifndef\s+__CUSTOM_FEATURECONFIG_H__/))
+        {
+            &error_handler("$CUSTOM_FEATURE_CONFIG_HDR: Not allowed to set conditional keywords $_ in custom_FeatureConfig.h!", __FILE__, __LINE__);
+        }
+
+        if (/^#define\s+(\w+)\s+\(?([\w|\-]*)\)?/)
+        {
+            &error_handler("$CUSTOM_FEATURE_CONFIG_HDR: $1 redefined in custom_FeatureConfig.h!", __FILE__, __LINE__) if defined($CUSTOM_FEATURE_CFG_OPTIONS{$1});
+            $CUSTOM_FEATURE_CFG_OPTIONS{$1} = ((!defined $2) or ($2 eq ''))? 'TRUE': $2;
+        }
+    }
+    close CUSTOM_FEATURE_CONFIG_HDR;
+}
+
+return 1;
diff --git a/src/bach/build.bach/tools/emigenSF.pl b/src/bach/build.bach/tools/emigenSF.pl
new file mode 100644
index 0000000..dba2b5b
--- /dev/null
+++ b/src/bach/build.bach/tools/emigenSF.pl
@@ -0,0 +1,848 @@
+#!/usr/bin/perl
+#
+my $DebugPrint    = 0; # 1 for debug; 0 for non-debug
+
+#****************************************************************************
+# subroutine:  Lookup_SFI_setting_by_IDX_CLK_BB_REG
+# input:       MEM_IDX, CLK , PLATFORM , REGISTER
+# return:      SFI setting of the input MEM_IDX/MCU_CLOCK/PLATFORM/REGISTER
+#****************************************************************************
+sub Lookup_SFI_setting_by_IDX_CLK_BB_REG
+{
+    my ($idx, $clk, $bb, $reg, $MDL_INFO_LIST_LOCAL) = @_;
+    my $clk_str = sprintf("%sMHZ SFC Setting", $clk);
+
+    return $MDL_INFO_LIST_LOCAL->[$idx]->{1}->{$bb}->{$clk_str}->{$reg};
+}
+
+#****************************************************************************
+# subroutine:  combo_sfi_config_h_file_body
+# return:
+#****************************************************************************
+sub combo_sfi_config_h_file_body
+{
+	  my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL) = @_;
+    ###
+    my $MAX_SFI_COMMAND = 32;
+
+    ### Fill-in the information of each memory
+    my $combo_mem_info_struct;
+    for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+    {
+        my $comma = ($_ < $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT}) ? "," : "";
+
+        my @device_setup_command = &split_sfi_command($MDL_INFO_LIST_LOCAL->[$_]->{0}->{'Device Setup'}->{'Command'});
+        my $sf_mac_ctl_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+        my ($sf_misc_ctl_1_104, $sf_dly_ctl_1_104, $sf_misc_ctl_2_104, $sf_dly_ctl_2_104);
+        my ($sf_direct_ctl_1_104, $sf_misc_ctl_1_104, $sf_dly_ctl_2_104, $sf_dly_ctl_3_104, $sf_dly_ctl_4_104, $sf_dly_ctl_5_104);
+        if ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252')
+        {
+            $sf_misc_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_misc_ctl_2_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        elsif($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6255')
+        {
+        	  $sf_misc_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_direct_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_3_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_4_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_5_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        else
+        {
+            $sf_misc_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_misc_ctl_2_104 = "0";
+            $sf_dly_ctl_2_104 = "0";
+        }
+        my (@sf_driving_104, $sf_driving1_104, $sf_driving2_104, @dev_set_burst_104, $devinit_104 );
+        my ($sf_driving_104, @dev_set_burst_104, $devinit_104);
+        if(($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252') || ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6251'))
+        {
+        	     @sf_driving_104 = &split_sfi_driving(&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL));
+               $sf_driving1_104 = (defined $sf_driving_104[0] and $sf_driving_104[0] ne 'x') ? $sf_driving_104[0] : "0x0";
+               $sf_driving2_104 = (defined $sf_driving_104[1] and $sf_driving_104[1] ne 'x') ? $sf_driving_104[1] : "0x0";
+               @dev_set_burst_104 = &split_sfi_command(&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DEV_SET_BURST', $MDL_INFO_LIST_LOCAL));
+               $devinit_104 = &sfi_dev_init(\@device_setup_command, \@dev_set_burst_104, $MAX_SFI_COMMAND);
+        }
+        else
+        {
+              $sf_driving_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) : "0";
+               print "driving: $sf_driving_104\n";
+               @dev_set_burst_104 = &split_sfi_command(&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DEV_SET_BURST', $MDL_INFO_LIST_LOCAL));
+               $devinit_104 = &sfi_dev_init(\@device_setup_command, \@dev_set_burst_104, $MAX_SFI_COMMAND);
+               print "dev init : $devinit_104";
+        }
+        my $sf_mac_ctl_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+        my ($sf_misc_ctl_1_78, $sf_dly_ctl_1_78, $sf_misc_ctl_2_78, $sf_dly_ctl_2_78);
+        my ($sf_direct_ctl_1_78, $sf_misc_ctl_1_78, $sf_dly_ctl_1_78, $sf_dly_ctl_2_78, $sf_dly_ctl_3_78, $sf_dly_ctl_4_78, $sf_dly_ctl_5_78);
+        if ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252')
+        {
+            $sf_misc_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_misc_ctl_2_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        elsif($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6255')
+        {
+        	    $sf_misc_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_direct_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_3_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_4_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_5_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        else
+        {
+            $sf_misc_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_misc_ctl_2_78 = "0";
+            $sf_dly_ctl_2_78 = "0";
+        }
+        my (@sf_driving_78, $sf_driving1_78, $sf_driving2_78, @dev_set_burst_78, $devinit_78 );
+        my ($sf_driving_78, @dev_set_burst_78, $devinit_78);
+        if(($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252') || ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6251'))
+        {
+        	   @sf_driving_78 = &split_sfi_driving(&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL));
+             $sf_driving1_78 = (defined $sf_driving_78[0] and $sf_driving_78[0] ne 'x') ? $sf_driving_78[0] : "0x0";
+             $sf_driving2_78 = (defined $sf_driving_78[1] and $sf_driving_78[1] ne 'x') ? $sf_driving_78[1] : "0x0";
+             @device_setup_command = &split_sfi_command($MDL_INFO_LIST_LOCAL->[$_]->{0}->{'Device Setup'}->{'Command'});
+             @dev_set_burst_78 = &split_sfi_command(&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DEV_SET_BURST', $MDL_INFO_LIST_LOCAL));
+             $devinit_78 = &sfi_dev_init(\@device_setup_command, \@dev_set_burst_78, $MAX_SFI_COMMAND);
+
+        }
+        else
+        {
+        	    $sf_driving_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) : "0";
+              @dev_set_burst_78 = &split_sfi_command(&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DEV_SET_BURST', $MDL_INFO_LIST_LOCAL));
+              $devinit_78 = &sfi_dev_init(\@device_setup_command, \@dev_set_burst_104, $MAX_SFI_COMMAND);
+        }
+        my ($sf_mac_ctl_13, $sf_misc_ctl_13, $sf_dly_ctl_13, @sf_driving_13, $sf_driving1_13, $sf_driving2_13);
+        my ($sf_misc_ctl_1_26, $sf_direct_ctl_1_26, $sf_dly_ctl_2_26, $sf_dly_ctl_3_26, $sf_dly_ctl_4_26, $sf_dly_ctl_5_26, $sf_driving_26);
+        if(($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252') || ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6251'))
+        {
+           $sf_mac_ctl_13 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+           $sf_misc_ctl_13 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+           $sf_dly_ctl_13 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+           @sf_driving_13 = &split_sfi_driving(&Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL));
+           $sf_driving1_13 = (defined $sf_driving_13[0] and $sf_driving_13[0] ne 'x') ? $sf_driving_13[0] : "0x0";
+           $sf_driving2_13 = (defined $sf_driving_13[1] and $sf_driving_13[1] ne 'x') ? $sf_driving_13[1] : "0x0";
+        }
+        else
+        {
+            $sf_mac_ctl_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_misc_ctl_1_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_direct_ctl_1_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_3_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_4_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_5_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) : "0";
+             $sf_driving_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'DRIVING', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        if(($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252') || ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6251'))
+        {
+        $combo_mem_info_struct .= <<"__TEMPLATE";
+        {   // $MDL_INFO_LIST_LOCAL->[$_]->{0}->{'Part Number'}
+            {   // HW config 104Mhz Start
+                $sf_mac_ctl_104,  // SF_MAC_CTL
+                $sf_misc_ctl_1_104,  // 1st SF_MISC_CTL
+                $sf_dly_ctl_1_104,  // 1st SF_DLY_CTL
+                0x000001F0,  // SF_DR_CTL
+                $sf_driving1_104,         // SF_DRVING1
+                $sf_driving2_104,         // SF_DRVING2
+                0,            // Reserved
+                $sf_misc_ctl_2_104,  // 2nd SF_MISC_CTL
+                $sf_dly_ctl_2_104  // 2nd SF_DLY_CTL
+            },  // HW config End
+            {
+$devinit_104
+            },
+            {   // HW config 78Mhz Start
+                $sf_mac_ctl_78,  // SF_MAC_CTL
+                $sf_misc_ctl_1_78,  // 1st SF_MISC_CTL
+                $sf_dly_ctl_1_78,  // 1st SF_DLY_CTL
+                0x000000F0,  // SF_DR_CTL
+                $sf_driving1_78,         // SF_DRVING1
+                $sf_driving2_78,         // SF_DRVING2
+                0,            // Reserved
+                $sf_misc_ctl_2_78,  // 2nd SF_MISC_CTL
+                $sf_dly_ctl_2_78  // 2nd SF_DLY_CTL
+            },  // HW config End
+            {
+$devinit_78
+            },
+            {   // HW config 13Mhz Start
+                $sf_mac_ctl_13,  // SF_MAC_CTL
+                $sf_misc_ctl_13,  // SF_MISC_CTL
+                $sf_dly_ctl_13,  // SF_DLY_CTL
+                0x000000F0,  // SF_DR_CTL
+                $sf_driving1_13,         // SF_DRVING1
+                $sf_driving2_13,         // SF_DRVING2
+                0            // Reserved
+            }  // HW config End
+
+        }$comma
+__TEMPLATE
+    }
+     else
+  {
+  	 $combo_mem_info_struct .= <<"__TEMPLATE";
+        {   // $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Part Number'}
+            {   // HW config 104Mhz Start
+                $sf_mac_ctl_104,  // SFI_MAC_CTL
+                $sf_direct_ctl_1_104,  // SFI_DIRECT_CTL
+                $sf_misc_ctl_1_104,     //  SFI_MISC_CTL
+                $sf_dly_ctl_2_104,     // 1st SFI_DLY_CTL_2
+                $sf_dly_ctl_3_104,         // 1st SFI_DLY_CTL_3
+                $sf_driving_104,           // DRIVING
+                0,                  // Reserved
+                $sf_dly_ctl_4_104,  // 2nd SFI_DLY_CTL_4
+                $sf_dly_ctl_5_104  // 2nd SFI_DLY_CTL_5
+            },  // HW config End
+            {
+$devinit_104
+            },
+            {   // HW config 78Mhz Start
+               $sf_mac_ctl_78,  // SFI_MAC_CTL
+                $sf_direct_ctl_1_78,  // SFI_DIRECT_CTL
+                $sf_misc_ctl_1_78,     //  SFI_MISC_CTL
+                $sf_dly_ctl_2_78,     // 1st SFI_DLY_CTL_2
+                $sf_dly_ctl_3_78,         // 1st SFI_DLY_CTL_3
+                $sf_driving_78,           // DRIVING
+                0,                  // Reserved
+                $sf_dly_ctl_4_78,  // 2nd SFI_DLY_CTL_4
+                $sf_dly_ctl_5_78  // 2nd SFI_DLY_CTL_5
+            },  // HW config End
+            {
+$devinit_78
+            },
+            {   // HW config 26Mhz Start
+               $sf_mac_ctl_26,  // SFI_MAC_CTL
+                $sf_direct_ctl_1_26,  // SFI_DIRECT_CTL
+                $sf_misc_ctl_1_26,     //  SFI_MISC_CTL
+                $sf_dly_ctl_2_26,     // 1st SFI_DLY_CTL_2
+                $sf_dly_ctl_3_26,         // 1st SFI_DLY_CTL_3
+                $sf_driving_26,           // DRIVING
+                0                 // Reserved
+            }  // HW config End
+
+        }$comma
+__TEMPLATE
+  }
+  }
+
+
+    ###
+    my $template = <<"__TEMPLATE";
+//-----------------------------------------------------------------------------
+// MCP Serial Flash HW settings (for ComboMEM only, do not include this header)
+//-----------------------------------------------------------------------------
+COMBO_MEM_HW_TYPE_MODIFIER CMEMEntrySFIList COMBO_MEM_HW_INST_NAME = {   // (to be renamed by SFI owner)
+#if defined(MT6251) || defined(MT6255) || defined(MT6250) || defined(MT6280)
+        "COMBO_MEM_SFI",
+#elif defined(MT6253L)||defined(MT6252)
+    #ifdef __SV5_ENABLED__
+        GFH_HEADER(GFH_EPP_PARAM, 1),
+    #else
+        "COMBO_MEM_SFI",
+    #endif
+#endif //defined(MT6251)
+        COMBO_SFI_VER,           // SFI structure version
+        SFI_COMBO_COUNT,   // defined in custom_Memorydevice.h
+    {
+$combo_mem_info_struct
+    }
+};
+
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  combo_sfi_defs_h_file_body
+# return:
+#****************************************************************************
+sub combo_sfi_defs_h_file_body
+{
+	  my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL) = @_;
+    ###
+    my $MAX_SFI_COMMAND = 32;
+
+    ### Define SIP compile option
+    my $sfi_sip;
+    #if (($PLATFORM eq 'MT6252') and ($MAKEFILE_OPTIONS{'sip_serial_flash_size'} eq '16M_BITS'))
+    if (($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252') and (defined $MAKEFILE_OPTIONS_LOCAL->{'sip_serial_flash_size'}))
+    {
+        $sfi_sip = "#define _SFI_SIP_SerialFlash\n";
+    }
+    chomp $sfi_sip;
+
+    ### Define SIP sfi count
+    my $sfi_sip_count = $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT};
+
+    ###
+    my $template = <<"__TEMPLATE";
+#ifndef _COMBO_SFI_DEFS_H
+#define _COMBO_SFI_DEFS_H
+
+//-----------------------------------------------------------------------------
+// Combo MEM HW Settings
+//-----------------------------------------------------------------------------
+#define COMBO_SFI_VER  1
+
+$sfi_sip
+
+#if defined(_SFI_SIP_SerialFlash)
+#define SFI_COMBO_COUNT    $sfi_sip_count
+#elif defined(__COMBO_MEMORY_SUPPORT__)
+#define SFI_COMBO_COUNT    COMBO_MEM_ENTRY_COUNT
+#else
+#define SFI_COMBO_COUNT    1
+#endif
+
+#if defined(MT6250) || defined(MT6280)
+typedef struct {
+#if defined(__SFI_CLK_130MHZ__)
+    kal_uint32      HWConf_130M[10];     // to be defined by SFI/EMI owner
+    kal_uint8       DevInit_130M[32];   // to be defined by SFI/EMI owner  (reserved for serial flash)
+#endif //defined(__SFI_CLK_130MHZ__)
+#if defined(__SFI_CLK_104MHZ__)
+    kal_uint32      HWConf_104M[10];     // to be defined by SFI/EMI owner
+    kal_uint8       DevInit_104M[32];   // to be defined by SFI/EMI owner  (reserved for serial flash)
+#endif //defined(__SFI_CLK_104MHZ__)
+#if defined(__SFI_CLK_78MHZ__) || defined(__SFI_CLK_80MHZ__)
+    kal_uint32      HWConf_78M[10];     // to be defined by SFI/EMI owner
+    kal_uint8       DevInit_78M[32];   // to be defined by SFI/EMI owner  (reserved for serial flash)
+#endif //defined(__SFI_CLK_78MHZ__)
+    kal_uint32      HWConf_26M[8];     // to be defined by SFI/EMI owner
+
+} CMEMEntrySFI;
+
+#elif defined(MT6290)  //TODO: For sync with MT7208 excel file
+typedef struct {
+    kal_uint32      HWConf_FPGA[7];     // to be defined by SFI/EMI owner
+} CMEMEntrySPIC;
+
+#else
+typedef struct {
+    kal_uint32      HWConf_104M[9];     // to be defined by SFI/EMI owner
+    kal_uint8       DevInit_104M[$MAX_SFI_COMMAND];   // to be defined by SFI/EMI owner  (reserved for serial flash)
+    kal_uint32      HWConf_78M[9];     // to be defined by SFI/EMI owner
+    kal_uint8       DevInit_78M[$MAX_SFI_COMMAND];   // to be defined by SFI/EMI owner  (reserved for serial flash)
+    kal_uint32      HWConf_13M[7];     // to be defined by SFI/EMI owner
+
+} CMEMEntrySFI;
+#endif
+
+#if defined(MT6250) || defined(MT6280)
+typedef struct {
+#if defined(__SFI_CLK_130MHZ__)
+    kal_uint32      HWConf_130M[5];     // to be defined by SFI/EMI owner
+#endif //defined(__SFI_CLK_130MHZ__)
+#if defined(__SFI_CLK_104MHZ__)
+    kal_uint32      HWConf_104M[5];     // to be defined by SFI/EMI owner
+#endif //defined(__SFI_CLK_104MHZ__)
+#if defined(__SFI_CLK_78MHZ__) || defined(__SFI_CLK_80MHZ__)
+    kal_uint32      HWConf_78M[5];     // to be defined by SFI/EMI owner
+#endif //defined(__SFI_CLK_78MHZ__)
+    kal_uint32      HWConf_26M[3];     // to be defined by SFI/EMI owner
+} CMEMEntrySFI_DCM;
+#else
+
+typedef struct {
+    kal_uint32      HWConf_104M[5];     // to be defined by SFI/EMI owner
+    kal_uint32      HWConf_78M[5];     // to be defined by SFI/EMI owner
+    kal_uint32      HWConf_13M[3];     // to be defined by SFI/EMI owner
+} CMEMEntrySFI_DCM;
+
+#endif
+typedef struct {
+    kal_uint32      HWConf[16];     // to be defined by EMI owner
+} CMEMEntryEMI;
+
+
+typedef struct {
+#if defined(MT6251)  || defined(MT6255) || defined(MT6250) || defined(MT6280)
+    char               m_identifier[16];   // MTK_COMBO_ID_INFO
+#elif defined(MT6253L)||defined(MT6252)
+#ifdef __SV5_ENABLED__
+    GFH_Header_Type    mem_info;   //
+#else
+    char               m_identifier[16];   // MTK_COMBO_ID_INFO
+#endif
+#endif //defined(MT6251)
+    unsigned int       m_ver;
+    unsigned int       Count;
+#if defined(MT6290)
+        CMEMEntrySPIC       List[SFI_COMBO_COUNT];  // to be defined by SFI/EMI owner
+#else
+        CMEMEntrySFI       List[SFI_COMBO_COUNT];  // to be defined by SFI/EMI owner
+#endif
+
+} CMEMEntrySFIList;
+
+typedef struct {
+    CMEMEntrySFI_DCM      List[SFI_COMBO_COUNT];  // to be defined by SFI/EMI owner
+} CMEMEntrySFIList_dcm;
+
+#endif
+
+__TEMPLATE
+}
+
+
+#****************************************************************************
+# subroutine:  custom_SFI_h_file_body
+# return:
+#****************************************************************************
+sub custom_SFI_h_file_body
+{
+	  my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL, $sfi_clk_config_LOCAL) = @_;
+    my $sfi_clk_str = sprintf("#define __SFI_CLK_%sMHZ__", $sfi_clk_config_LOCAL);
+
+    ### Fill-in the information of each memory
+    my $combo_mem_info_struct;
+    for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+    {
+        my $comma = ($_ < $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT}) ? "," : "";
+
+        my @device_setup_command = &split_sfi_command($MDL_INFO_LIST_LOCAL->[$_]->{0}->{'Device Setup'}->{'Command'});
+        my $sf_mac_ctl_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+        my ($sf_misc_ctl_1_104, $sf_dly_ctl_1_104, $sf_misc_ctl_2_104, $sf_dly_ctl_2_104);
+        my ($sf_misc_ctl_1_104, $sf_direct_ctl_1_104, $sf_dly_ctl_2_104, $sf_dly_ctl_3_104, $sf_dly_ctl_4_104, $sf_dly_ctl_5_104);
+        if ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252')
+        {
+            $sf_misc_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_misc_ctl_2_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        elsif($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6255')
+        {
+            $sf_misc_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_direct_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_3_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_4_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_5_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        else
+        {
+            if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6290')
+            {
+                $sf_misc_ctl_1_104 = '0';
+                $sf_dly_ctl_1_104 = '0';
+                $sf_misc_ctl_2_104 = "0";
+                $sf_dly_ctl_2_104 = "0";
+                $sf_dly_ctl_3_104 = "0";
+                $sf_dly_ctl_4_104 = "0";
+                $sf_dly_ctl_5_104 = "0";
+            }
+            else
+            {
+                $sf_misc_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+                $sf_dly_ctl_1_104 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 104, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+                $sf_misc_ctl_2_104 = "0";
+                $sf_dly_ctl_2_104 = "0";
+            }
+        }
+
+        my $sf_mac_ctl_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+        my ($sf_misc_ctl_1_78, $sf_dly_ctl_1_78, $sf_misc_ctl_2_78, $sf_dly_ctl_2_78);
+        my ($sf_misc_ctl_1_78, $sf_direct_ctl_1_78, $sf_dly_ctl_2_78, $sf_dly_ctl_3_78, $sf_dly_ctl_4_78, $sf_dly_ctl_5_78);
+        if ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252')
+        {
+            $sf_misc_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_1', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_1', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_misc_ctl_2_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        elsif($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6255')
+        {
+        	  $sf_misc_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_direct_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_2_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_3_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_4_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_4', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_5_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_5', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        else
+        {
+            if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6290')
+            {
+                $sf_misc_ctl_1_78 = '0';
+                $sf_dly_ctl_1_78 = '0';
+                $sf_misc_ctl_2_78 = "0";
+                $sf_dly_ctl_2_78 = "0";
+                $sf_dly_ctl_3_78 = "0";
+                $sf_dly_ctl_4_78 = "0";
+                $sf_dly_ctl_5_78 = "0";
+            }
+            else
+            {
+                $sf_misc_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+                $sf_dly_ctl_1_78 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 78, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+                $sf_misc_ctl_2_78 = "0";
+                $sf_dly_ctl_2_78 = "0";
+            }
+        }
+        my ($sf_mac_ctl_13, $sf_misc_ctl_13, $sf_dly_ctl_13);
+        my ($sf_mac_ctl_26, $sf_misc_ctl_26, $sf_direct_ctl_26);
+        if(($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252') || ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6251'))
+        {
+            $sf_mac_ctl_13 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_misc_ctl_13 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+            $sf_dly_ctl_13 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 13, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+        }
+        else
+        {
+            if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6290')
+            {
+                $sf_mac_ctl_26 = '0';
+                $sf_misc_ctl_26 = '0';
+                $sf_direct_ctl_26 = "0";
+                $sf_dly_ctl_2_26 = "0";
+                $sf_dly_ctl_3_26 = "0";
+            }
+            else
+            {
+                $sf_mac_ctl_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MAC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+                $sf_misc_ctl_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_MISC_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+                $sf_direct_ctl_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DIRECT_CTL', $MDL_INFO_LIST_LOCAL) : "0";
+                $sf_dly_ctl_2_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_2', $MDL_INFO_LIST_LOCAL) : "0";
+                $sf_dly_ctl_3_26 = (defined &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne 'x' and &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) ne ' ') ? &Lookup_SFI_setting_by_IDX_CLK_BB_REG($_, 26, $MAKEFILE_OPTIONS_LOCAL->{'platform'}, 'SFI_DLY_CTL_3', $MDL_INFO_LIST_LOCAL) : "0";
+            }
+        }
+        if(($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6252') || ($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6251'))
+        {
+        $combo_mem_info_struct .= <<"__TEMPLATE";
+        {   // $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Part Number'}
+            {   // HW config 104Mhz Start
+                $sf_mac_ctl_104,  // SF_MAC_CTL
+                $sf_misc_ctl_1_104,  // SF_MISC_CTL
+                $sf_dly_ctl_1_104,  // SF_DLY_CTL
+                $sf_dly_ctl_2_104,  // SF_MISC_CTL
+                $sf_dly_ctl_2_104,  // SF_DLY_CTL
+            },  // HW config End
+            {   // HW config 78Mhz Start
+                $sf_mac_ctl_78,  // SF_MAC_CTL
+                $sf_misc_ctl_1_78,  // SF_MISC_CTL
+                $sf_dly_ctl_1_78,  // SF_DLY_CTL
+                $sf_dly_ctl_2_78,  // SF_MISC_CTL
+                $sf_dly_ctl_2_78,  // SF_DLY_CTL
+            },  // HW config End
+            {   // HW config 13Mhz Start
+                $sf_mac_ctl_13,  // SF_MAC_CTL
+                $sf_misc_ctl_13,  // SF_MISC_CTL
+                $sf_dly_ctl_13,  // SF_DLY_CTL
+            }  // HW config End
+
+        }$comma
+__TEMPLATE
+    }
+    else
+    {
+    	  $combo_mem_info_struct .= <<"__TEMPLATE";
+        {   // $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Part Number'}
+            {   // HW config 104Mhz Start
+                $sf_misc_ctl_1_104,  // SF_MISC_CTL
+                $sf_dly_ctl_2_104,  // SF_DLY_2_CTL
+                $sf_dly_ctl_3_104,  // SF_DLY_3_CTL
+                $sf_dly_ctl_4_104,  // SF_DLY_4_CTL
+                $sf_dly_ctl_5_104,  // SF_DLY_5_CTL
+            },  // HW config End
+            {   // HW config 78Mhz Start
+                $sf_misc_ctl_1_78,  // SF_MISC_CTL
+                $sf_dly_ctl_2_78,  // SF_DLY_2_CTL
+                $sf_dly_ctl_3_78,  // SF_DLY_3_CTL
+                $sf_dly_ctl_4_78,  // SF_DLY_4_CTL
+                $sf_dly_ctl_5_78,  // SF_DLY_5_CTL
+            },  // HW config End
+            {   // HW config 26Mhz Start
+                $sf_misc_ctl_26,  // SF_MISC_CTL
+                $sf_dly_ctl_2_26,  // SF_DLY_2_CTL
+                $sf_dly_ctl_3_26,  // SF_DLY_3_CTL
+            }  // HW config End
+
+        }$comma
+__TEMPLATE
+    }
+    }
+
+
+    my $template = <<"__TEMPLATE";
+#ifndef __CUSTOM_SFI__
+#define __CUSTOM_SFI__
+
+/*
+ ****************************************************************************
+ Specify the chip select configuration
+ Possible choices, NOR_FLASH, RAM, LPSDRAM, UNUSED
+ ****************************************************************************
+ */
+
+/*
+ ****************************************************************************
+ Specify RAM size in Bytes
+ ****************************************************************************
+*/
+
+
+/*
+ ****************************************************************************
+ Define the driving and cache/prefetch setting (optional)
+ ****************************************************************************
+*/
+
+$sfi_clk_str
+
+#define __SFI_DCM_COMBO_INDEX_UND   0xffffffff
+
+#if defined(MT6252) || defined(MT6251)
+typedef enum {
+    SFC_MAC_CTL            = 0x0      // Write Enable Latch
+   ,SFC_MISC_CTL_1         = 0x1      // Write Suspend Program Status
+   ,SFC_DLY_CTL_1          = 0x2      // Security ID status (Once set by Lock Security ID (0x85h) it can not be reset)
+   ,SFC_DR_CTL             = 0x3      // Write operation status (Write In Progress Bit)
+   ,SFC_Driving_1          = 0x4      // Write operation status (Write In Progress Bit)
+   ,SFC_Driving_2          = 0x5      // Write operation status (Write In Progress Bit)
+   ,SFC_Reserved           = 0x6      // Write operation status (Write In Progress Bit)
+   ,SFC_MISC_CTL_2         = 0x7      // Write Protection Lockdown Status (Resets to 0 after a power cycle)
+   ,SFC_DLY_CTL_2          = 0x8      // Write operation status (Write In Progress Bit)
+} SF_SFC_Control_Reg_Enum;
+
+#elif defined(MT6255)
+typedef enum {
+    SFC_MAC_CTL            = 0x0      // Write Enable Latch
+   ,SFC_DR_CTL             = 0x1      // Write Suspend Program Status
+   ,SFC_MISC_CTL           = 0x2      // Security ID status (Once set by Lock Security ID (0x85h) it can not be reset)
+   ,SFC_DLY_CTL2           = 0x3      // Write operation status (Write In Progress Bit)
+   ,SFC_DLY_CTL3           = 0x4      // Write operation status (Write In Progress Bit)
+   ,SFC_Driving            = 0x5      // Write operation status (Write In Progress Bit)
+   ,SFC_Reserved           = 0x6      // Write operation status (Write In Progress Bit)
+   ,SFC_DLY_CTL4           = 0x7      // Write operation status (Write In Progress Bit)
+   ,SFC_DLY_CTL5           = 0x8      // Write operation status (Write In Progress Bit)
+} SF_SFC_Control_Reg_Enum;
+
+#elif defined(MT6250) || defined(MT6280)
+typedef enum {
+    SFC_MAC_CTL            = 0x0
+   ,SFC_DR_CTL             = 0x1
+   ,SFC_MISC_CTL           = 0x2
+   ,SFC_MISC_CTL2          = 0x3
+   ,SFC_DLY_CTL2           = 0x4
+   ,SFC_DLY_CTL3           = 0x5
+   ,SFC_Driving            = 0x6
+   ,SFC_Reserved           = 0x7
+   ,SFC_DLY_CTL4           = 0x8
+   ,SFC_DLY_CTL5           = 0x9
+} SF_SFC_Control_Reg_Enum;
+
+#endif
+#define ProcessID          ((volatile UINT32P)(EFUSE_base+0x0024))  /* Chip ID register 3 */
+
+#if defined(MT6251)
+#define ACIF_CON4        ((volatile UINT16P)(CONFIG_base+0x718))
+#define ACIF_CON5            ((volatile UINT16P)(CONFIG_base+0x71C))
+//#define SFI_CLOCK     78
+
+#elif defined(MT6253L)||defined(MT6252)
+#define ACIF_CON0        ((volatile UINT32P)(CONFIG_base+0x700))
+#elif defined(MT6255)
+#define IO_DRV2        ((volatile UINT32P)(CONFIG_base+0x508))
+#define SFC_CTRL_SET        ((volatile UINT32P)(CONFIG_base+0x61C))
+
+#define VSF_CON0       ((volatile UINT16P)(MIXED_base+0x700))
+#define VSF_CON3       ((volatile UINT16P)(MIXED_base+0x70C))
+#elif defined(MT6250)
+#define GPIO_DRV0      ((volatile UINT16P)(GPIO_base+0x800))
+#define SFC_EFUSE      ((volatile UINT32P)(EFUSE_base+0x108))
+
+#define GPIO_DRV0_OFFSET (8)
+#define GPIO_DRV0_SIZE (3)
+#define GPIO_DRV0_MASK (0x0700)
+#define SFIFO_WR_EN_DLY_SEL_OFFSET (24)
+
+
+#define SFC_DRIVING_OFFSET (0)
+#define SFC_DRIVING_SIZE   (3)
+#define SFC_INPUT0_DLY_OFFSET (3)
+#define SFC_INPUT0_DLY_SIZE   (2)
+#define SFC_INPUT1_DLY_OFFSET (5)
+#define SFC_INPUT1_DLY_SIZE   (2)
+#define SFC_INPUT2_DLY_OFFSET (7)
+#define SFC_INPUT2_DLY_SIZE   (2)
+#define SFC_INPUT3_DLY_OFFSET (9)
+#define SFC_INPUT3_DLY_SIZE   (2)
+#define SFC_SAMPLECLK_DLY_OFFSET (11)
+#define SFC_SAMPLECLK_DLY_SIZE   (6)
+#define SFC_GET_EfuseBits(offset, size) ((*SFC_EFUSE <<(32 - (offset+size))) >> (32-size))
+#define SFC_IS_NEGATIVE(number, size) (number &(1<<(size-1)))
+#define SFC_NEGATIVE_TO_POSITIVE(number, size) ((1<<size)-number)
+
+#elif defined(MT6280)
+#define GPIO_DRV6      ((volatile UINT16P)(GPIO_base+0x3500))
+#define GPIO84_MODE_SFIO2   (2)
+#define GPIO85_MODE_SFIO1   (2)
+#define GPIO86_MODE_SFIO0   (2)
+#define GPIO87_MODE_SFCS    (2)
+#define GPIO88_MODE_SFCLK   (2)
+#define GPIO89_MODE_SFIO3   (2)
+
+#define GPIO_MODE_BASE      (GPIO_base + 0x1000)
+#define GPIO_GET_MODE(_n)       *(U16 *)(GPIO_MODE_BASE + (_n >> 2) * 0x100)
+#define GPIO_SET_MODE(_n, mode) *(U16 *)(GPIO_MODE_BASE + (_n >> 2) * 0x100) =
+                                    (*(U16*)(GPIO_MODE_BASE + (_n >> 2) * 0x100) & (~(0xF << (4 * (_n & 0x3))))) | (mode << (4 * (_n & 0x3)))
+
+
+#endif
+
+static const CMEMEntrySFIList_dcm combo_mem_hw_list_dcm = {   // (to be renamed by SFI owner)
+
+    //COMBO_SFI_VER,           // SFI structure version
+    //COMBO_MEM_ENTRY_COUNT,   // defined in custom_Memorydevice.h
+    {
+$combo_mem_info_struct
+    }
+};
+
+/*
+ ****************************************************************************
+ Specify the related EMI Setting
+ ****************************************************************************
+*/
+
+/*
+ ****************************************************************************
+ Specify additional information
+ ****************************************************************************
+*/
+
+#endif /* __CUSTOM_SFI__ */
+
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  split_sfi_command
+# return:      List of SFI commands
+# input:       $command_str: Excel value to be split
+#****************************************************************************
+sub split_sfi_command
+{
+		my ($command_str) = @_;
+    my @ret_command;
+
+    while ($command_str =~ /(\{.+\})/)
+    {
+        my $tmp_str = $1;
+        $command_str = $';
+				$tmp_str =~ s/\{//;  # remove parentheses
+        $tmp_str =~ s/\}//;  # remove parentheses
+        $tmp_str =~ s/\s+//g;  # remove spaces
+        ### parse the driving string
+        my $saved_sep = $/;
+        undef $/;
+        my @tmp_list = split(/\,/, $tmp_str);
+        $/ = $saved_sep;
+
+        ### for commands {SPI, 0x35}, output "SPI, 1, 0x35", where 1 is the number of commands
+        if ($tmp_list[0] eq 'SPI' or $tmp_list[0] eq 'QPI')
+        {
+            push @ret_command, $tmp_list[0];
+            push @ret_command, $#tmp_list;
+            for (1..$#tmp_list)
+            {
+                push @ret_command, $tmp_list[$_];
+            }
+        }
+        else
+        {
+            &error_handler("$MEMORY_DEVICE_LIST_XLS_E: Unknown SFI commands $command_str!", __FILE__, __LINE__);
+        }
+    }
+
+    return @ret_command;
+
+}
+
+#****************************************************************************
+# subroutine:  split_sfi_driving
+# return:      List of SFI driving
+# input:       $driving_str: Excel value to be split
+#****************************************************************************
+sub split_sfi_driving
+{
+    my ($driving_str) = @_;
+    my @ret_driving;
+
+    $driving_str =~ s/\{//;  # remove parentheses
+    $driving_str =~ s/\}//;  # remove parentheses
+    $driving_str =~ s/\s+//g;  # remove spaces
+    ### parse the driving string
+    my $saved_sep = $/;
+    undef $/;
+    @ret_driving = split(/\,/, $driving_str);
+    $/ = $saved_sep;
+
+    return @ret_driving;
+}
+
+#****************************************************************************
+# subroutine:  sfi_dev_init
+# input:       $dev_setup_list_href:     hash reference of Device Setup Command list
+#              $dev_set_burst_list_href: hash reference of Device Set Burst list
+#              $max_cmd:                 maximum number of commands
+# return:      template of DevInit part in CMEMEntrySFI structure in combo_sfi_config.h
+#****************************************************************************
+sub sfi_dev_init
+{
+    my ($dev_setup_list_href, $dev_set_burst_list_href, $max_cmd) = @_;
+    my $ret_template;
+
+    for (0..($max_cmd-1))
+    {
+        if (($_%8) == 0)
+        {
+            $ret_template .= "                ";
+        }
+
+        if (defined $dev_setup_list_href->[$_])
+        {
+            $ret_template .= ($dev_setup_list_href->[$_] eq 'x') ? "0" : "$dev_setup_list_href->[$_]";
+        }
+        elsif (defined $dev_set_burst_list_href->[$_ - ($#$dev_setup_list_href+1)])
+        {
+            $ret_template .= ($dev_set_burst_list_href->[$_ - ($#$dev_setup_list_href+1)] eq 'x') ? "0" : "$dev_set_burst_list_href->[$_ - ($#$dev_setup_list_href+1)]";
+        }
+        else
+        {
+            if ($_ == (($#$dev_setup_list_href + 1) + ($#$dev_set_burst_list_href + 1)))
+            {
+                $ret_template .= "SF_UNDEF";
+            }
+            else
+            {
+                $ret_template .= "0";
+            }
+        }
+
+        if ($_ < ($max_cmd-1))
+        {
+            if (($_%8) == 7)
+            {
+                $ret_template .= ",\n";
+            }
+            else
+            {
+                $ret_template .= ", ";
+            }
+        }
+    }
+
+    return $ret_template;
+}
+
+return 1;
\ No newline at end of file
diff --git a/src/bach/build.bach/tools/emigenemi.pl b/src/bach/build.bach/tools/emigenemi.pl
new file mode 100644
index 0000000..090ff60
--- /dev/null
+++ b/src/bach/build.bach/tools/emigenemi.pl
@@ -0,0 +1,540 @@
+#!/usr/bin/perl
+
+use Class::Struct ();
+
+my $DebugPrint    = 0;
+
+#****************************************************************************
+# subroutine:  custom_EMI_release_h_file_body_for_91
+# return:
+#****************************************************************************
+sub custom_EMI_release_h_file_body_for_91
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL, $LPSDRAM_CHIP_SELECT_LOCAL, $emi_clk_config_LOCAL) = @_;
+    my ($cus_include, $cus_def, $cus_enum, $cus_struct, $cus_api);
+    my $emi_clk_str;
+    my $emi_device_mode;
+
+    if (($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{FLASH_ACCESS_TYPE} eq 'SYNC_ACCESS') && ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{RAM_ACCESS_TYPE} eq 'SYNC_ACCESS'))
+    {
+        $emi_clk_str = sprintf("#define __EMI_CLK_%sMHZ__", $emi_clk_config_LOCAL);
+        if ($emi_clk_str =~ /\./)
+        {
+            $emi_clk_str =~ s/\./_/;
+        }
+    }
+    if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} =~ /LPDDR3/)
+    {
+        $emi_device_mode = "#define __EMI_DEVICE_LPDDR3__\n#define __EMI_MODE_2X__";
+    }
+    elsif ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} =~ /LPDDR2/)
+    {
+        $emi_device_mode = "#define __EMI_DEVICE_LPDDR2__\n#define __EMI_MODE_2X__";
+    }
+    elsif ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} =~ /LPDDR/)
+    {
+        $emi_device_mode = "#define __EMI_DEVICE_LPDDR1__\n#define __EMI_MODE_1X__";
+    }
+    else
+    {
+        &error_handler("MEMORY_DEVICE_TYPE:$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} didn't support yet!", __FILE__, __LINE__);
+    }
+
+    print "MDL info:$COMM_MDL_INFO_LOCAL->{1}->{'Size (Mb)'}\n" if ($DebugPrint == 1);
+
+    my $template = <<"__TEMPLATE";
+#ifndef __CUSTOM_EMI_RELEASE_H__
+#define __CUSTOM_EMI_RELEASE_H__
+/********************************************
+ * Include.
+ ********************************************/
+$cus_include
+/********************************************
+ * Definition.
+ ********************************************/
+/**
+  * Define EMI's clock rate.
+  * comes from EMI_CLK definition in custom_MemoryDevice.h, or highest freq in MDL
+  */
+$emi_clk_str
+
+/**
+  * Define memory's mode.
+  */
+$emi_device_mode
+
+/**
+  * Define RAM size in Bytes.
+  */
+#define EMI_EXTSRAM_SIZE ((($COMM_MDL_INFO_LOCAL->{1}->{'Size (Mb)'})>>3)<<20)
+$cus_def
+/********************************************
+ * Enum.
+ ********************************************/
+$cus_enum
+/********************************************
+ * Struct.
+ ********************************************/
+$cus_struct
+/********************************************
+ * Exposed APIs.
+ ********************************************/
+$cus_api
+#endif /* __CUSTOM_EMI_RELEASE_H__ */
+
+__TEMPLATE
+
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  custom_EMI_h_file_body_for_91
+# return:
+#****************************************************************************
+sub custom_EMI_h_file_body_for_91
+{
+    ### EMI register value for each MCP
+    #my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $cus_include, $cus_def, $cus_enum, $cus_struct, $cus_api) = @_;
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL, $PART_NUMBER_LOCAL, $CUSTOM_MEMORY_DEVICE_HDR_LOCAL, $LPSDRAM_CHIP_SELECT_LOCAL, $emi_clk_config_LOCAL) = @_;
+    my ($cus_include, $cus_def, $cus_enum, $cus_struct, $cus_api);
+    my ($combo_mem_emi_reg, $combo_mem_emi_reg_clk);
+    my $emi_reg_bb_key_ref;
+    my $clk, $first_parse_clk;
+    my $mtk_emi_info;
+
+    $cus_def = <<"__TEMPLATE";
+/* Initial EMI Definition */
+#define DRAM_START          (0x00000000)
+
+#define MAX_DQ_DATA_WIDTH   (32)
+#define DQ_NUMBER_PER_DQS   (8)
+#define DQS_NUMBER          (MAX_DQ_DATA_WIDTH / DQ_NUMBER_PER_DQS)
+__TEMPLATE
+
+    $cus_api = <<"__TEMPLATE";
+int custom_InitDRAM(void);
+__TEMPLATE
+
+    for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+    {
+        my $combo_idx = $_ - 1;
+        my $def_pre = undef;
+        undef $mtk_emi_info;
+        undef $first_parse_clk;
+
+        $combo_mem_emi_reg .= <<"__TEMPLATE";
+/*
+ * EMI register value definition
+ * EMI/MEM configuration information of MCP$combo_idx
+ */
+__TEMPLATE
+        $def_pre = "#define DRAM_VENDOR_MCP$combo_idx";
+        $combo_mem_emi_reg .= $def_pre;
+        $combo_mem_emi_reg .= " " x (47 - length($def_pre));
+        $combo_mem_emi_reg .= " (\"$MDL_INFO_LIST_LOCAL->[$_]->{0}->{'Vendor'}\")\n";
+        $def_pre = "#define DRAM_PART_NUMBER_MCP$combo_idx";
+        $combo_mem_emi_reg .= $def_pre;
+        $combo_mem_emi_reg .= " " x (47 - length($def_pre));
+        $combo_mem_emi_reg .= " (\"$MDL_INFO_LIST_LOCAL->[$_]->{0}->{'Part Number'}\")\n";
+        my $emi_reg_bb_key_ref = $MDL_INFO_LIST_LOCAL->[$_]->{0}->{$MAKEFILE_OPTIONS_LOCAL->{'platform'}};
+        foreach my $emi_key (sort keys %{$emi_reg_bb_key_ref})
+        {
+            if ($emi_key =~ /(\d+)MHZ EMI Driving/)
+            {
+                $clk = $1;
+                if (! defined $first_parse_clk)
+                {
+                    $first_parse_clk = $clk;
+                }
+                if (! defined $combo_mem_emi_reg_clk)
+                {
+                    $combo_mem_emi_reg_clk = "#if defined(__EMI_CLK_$clk\MHZ__)\n";
+                }
+                else
+                {
+                    $combo_mem_emi_reg_clk .= "#elif defined(__EMI_CLK_$clk\MHZ__)\n";
+                }
+                my $emi_clk_dri = $emi_key;
+                foreach my $emi_key (sort keys %{$emi_reg_bb_key_ref->{$emi_clk_dri}})
+                {
+                    my $val = $emi_reg_bb_key_ref->{$emi_clk_dri}->{$emi_key};
+                    if (($val ne 'x') && ($val ne 'X') && ($val ne ''))
+                    {
+                        $def_pre = "#define $emi_key\_MCP$combo_idx";
+                        $combo_mem_emi_reg_clk .= $def_pre;
+                        # for alignment
+                        $combo_mem_emi_reg_clk .= " " x (47 - length($def_pre));
+                        $combo_mem_emi_reg_clk .= " ($val)\n";
+                    }
+
+                    if ($first_parse_clk == $clk)
+                    {
+                        $mtk_emi_info .= "    unsigned int " . lc($emi_key) . ";\n";
+                    }
+                }
+            }
+            else
+            {
+                my $val = $emi_reg_bb_key_ref->{$emi_key};
+                $def_pre = "#define $emi_key\_MCP$combo_idx";
+                if (($val ne 'x') && ($val ne 'X') && ($val ne ''))
+                {
+                    $combo_mem_emi_reg .= $def_pre;
+                    # for alignment
+                    $combo_mem_emi_reg .= " " x (47 - length($def_pre));
+                    $combo_mem_emi_reg .= " ($val)\n";
+                }
+                $mtk_emi_info .= "    unsigned int " . lc($emi_key) . ";\n";
+            }
+        }
+        $combo_mem_emi_reg_clk .= "#endif /* __EMI_CLK_$clk\MHZ__ */\n";
+
+        $combo_mem_emi_reg .= <<"__TEMPLATE";
+
+$combo_mem_emi_reg_clk
+/*
+ * End of EMI/MEM configuration information of MCP$combo_idx
+ */
+__TEMPLATE
+
+        undef $combo_mem_emi_reg_clk;
+    }
+
+    my $template = <<"__TEMPLATE";
+#ifndef __CUSTOM_EMI_H__
+#define __CUSTOM_EMI_H__
+/********************************************
+ * Include.
+ ********************************************/
+#include "custom_EMI_release.h"
+$cus_include
+/********************************************
+ * Definition.
+ ********************************************/
+$combo_mem_emi_reg
+$cus_def
+/********************************************
+ * Enum.
+ ********************************************/
+typedef enum {
+    DRAMType_Invalid = 0x0,
+    DRAMType_DDR,
+    DRAMType_DDR2,
+    DRAMType_DDR_166M,
+    DRAMType_DDR_200M,
+    DRAMType_DDR2_166M,
+    DRAMType_DDR2_200M,
+    DRAMType_LPDDR = 0x800,
+    DRAMType_LPDDR2,
+    DRAMType_LPDDR_166M,
+    DRAMType_LPDDR_200M,
+    DRAMType_LPDDR2_166M,
+    DRAMType_LPDDR2_200M,
+    DRAMType_LPDDR2_266M,
+    DRAMType_LPDDR3_303M = 0x900,
+    DRAMType_DDR_166M_SIP = 0x1000,
+    DRAMType_DDR_200M_SIP,
+    DRAMType_DDR2_166M_SIP,
+    DRAMType_DDR2_200M_SIP,
+    DRAMType_LPDDR_SIP = 0x1800,
+    DRAMType_LPDDR2_SIP,
+    DRAMType_LPDDR_166M_SIP,
+    DRAMType_LPDDR_200M_SIP,
+    DRAMType_LPDDR2_166M_SIP,
+    DRAMType_LPDDR2_200M_SIP,
+    DRAMType_End = 0x42424242
+} DRAMType;
+$cus_enum
+/********************************************
+ * Struct.
+ ********************************************/
+typedef struct {
+    DRAMType ramType;
+$mtk_emi_info} MTK_EMI_Info;
+$cus_struct
+/********************************************
+ * Exposed APIs.
+ ********************************************/
+$cus_api
+#endif /* end of __CUSTOM_EMI_H__ */
+__TEMPLATE
+
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  custom_EMI_info_h_file_body
+# return:
+#****************************************************************************
+sub custom_EMI_info_h_file_body
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $emi_clk_config_LOCAL, $cus_include, $cus_def, $cus_enum, $cus_struct, $cus_api) = @_;
+    my $combo_mem_emi_info_struct;
+    my $combo_mem_emi_type;
+
+    $combo_mem_emi_type = "DRAMType_";
+    if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} =~ /[A-Za-z]*DDR[0-9]*/)
+    {
+        $combo_mem_emi_type .= $&;
+    }
+    if (($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{FLASH_ACCESS_TYPE} eq 'SYNC_ACCESS') && ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{RAM_ACCESS_TYPE} eq 'SYNC_ACCESS'))
+    {
+        $combo_mem_emi_type .= sprintf("_%sM", $emi_clk_config_LOCAL);
+    }
+    if ($MAKEFILE_OPTIONS_LOCAL->{'sip_ram_size'} ne "NONE") {
+        $combo_mem_emi_type .= "_SIP";
+    }
+    for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+    {
+        my $combo_idx = $_ - 1;
+        $combo_mem_emi_info_struct .= <<"__TEMPLATE";
+    {
+        .ramType = $combo_mem_emi_type,
+__TEMPLATE
+        my $emi_reg_bb_key_ref = $MDL_INFO_LIST_LOCAL->[$_]->{0}->{$MAKEFILE_OPTIONS_LOCAL->{'platform'}};
+        foreach my $emi_key (sort keys %{$emi_reg_bb_key_ref})
+        {
+            if ($emi_key =~ /(\d+)MHZ EMI Driving/)
+            {
+                my $emi_clk_dri = $emi_key;
+                foreach my $emi_key (sort keys %{$emi_reg_bb_key_ref->{$emi_clk_dri}})
+                {
+                    my $val = $emi_reg_bb_key_ref->{$emi_clk_dri}->{$emi_key};
+                    if (($val ne 'x') && ($val ne 'X') && ($val ne ''))
+                    {
+                        my $emi_info_def = "$emi_key\_MCP$combo_idx";
+                        $combo_mem_emi_info_struct .= "        ." . lc($emi_key) . " = " . $emi_info_def . ",\n";
+                    }
+                }
+            }
+            else
+            {
+                my $val = $emi_reg_bb_key_ref->{$emi_key};
+                if (($val ne 'x') && ($val ne 'X') && ($val ne ''))
+                {
+                    my $emi_info_def = "$emi_key\_MCP$combo_idx";
+                    $combo_mem_emi_info_struct .= "        ." . lc($emi_key) . " = " . $emi_info_def . ",\n";
+                }
+            }
+        }
+        $combo_mem_emi_info_struct .= "    },\n";
+    }
+    my $template = <<"__TEMPLATE";
+#ifndef __CUSTOM_EMI_INFO_H__
+#define __CUSTOM_EMI_INFO_H__
+/********************************************
+ * Include.
+ ********************************************/
+#include "custom_EMI_release.h"
+#include "custom_EMI.h"
+$cus_include
+/********************************************
+ * Definition.
+ ********************************************/
+$cus_def
+/********************************************
+ * Enum.
+ ********************************************/
+$cus_enum
+/********************************************
+ * Struct.
+ ********************************************/
+MTK_EMI_Info EMI_INFO[] = {
+$combo_mem_emi_info_struct
+}; /* End of EMI_INFO struct */
+$cus_struct
+/********************************************
+ * Exposed APIs.
+ ********************************************/
+$cus_api
+
+#endif /* __CUSTOM_EMI_INFO_H__ */
+__TEMPLATE
+
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  custom_EMI_info_c_file_body
+# return:
+#****************************************************************************
+sub custom_EMI_info_c_file_body_for_91
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL, $PART_NUMBER_LOCAL, $CUSTOM_MEMORY_DEVICE_HDR_LOCAL, $LPSDRAM_CHIP_SELECT_LOCAL, $emi_clk_config_LOCAL, $emi_nor_cmd_num_max_LOCAL, $emi_psram_cmd_num_max_LOCAL) = @_;
+    my ($cus_include, $cus_def, $cus_enum, $cus_struct, $cus_api);
+    my $combo_mem_emi_info_struct;
+    my $combo_mem_emi_type;
+
+    $combo_mem_emi_type = "DRAMType_";
+    if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} =~ /[A-Za-z]*DDR[0-9]*/)
+    {
+        $combo_mem_emi_type .= $&;
+    }
+    if (($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{FLASH_ACCESS_TYPE} eq 'SYNC_ACCESS') && ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{RAM_ACCESS_TYPE} eq 'SYNC_ACCESS'))
+    {
+        $combo_mem_emi_type .= sprintf("_%sM", $emi_clk_config_LOCAL);
+    }
+    if ($MAKEFILE_OPTIONS_LOCAL->{'sip_ram_size'} ne "NONE") {
+        $combo_mem_emi_type .= "_SIP";
+    }
+    for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+    {
+        my $combo_idx = $_ - 1;
+        $combo_mem_emi_info_struct .= <<"__TEMPLATE";
+    {
+        .ramType = $combo_mem_emi_type,
+__TEMPLATE
+        my $emi_reg_bb_key_ref = $MDL_INFO_LIST_LOCAL->[$_]->{0}->{$MAKEFILE_OPTIONS_LOCAL->{'platform'}};
+        foreach my $emi_key (sort keys %{$emi_reg_bb_key_ref})
+        {
+            if ($emi_key =~ /(\d+)MHZ EMI Driving/)
+            {
+                my $emi_clk_dri = $emi_key;
+                foreach my $emi_key (sort keys %{$emi_reg_bb_key_ref->{$emi_clk_dri}})
+                {
+                    my $val = $emi_reg_bb_key_ref->{$emi_clk_dri}->{$emi_key};
+                    if (($val ne 'x') && ($val ne 'X') && ($val ne ''))
+                    {
+                        my $emi_info_def = "$emi_key\_MCP$combo_idx";
+                        $combo_mem_emi_info_struct .= "        ." . lc($emi_key) . " = " . $emi_info_def . ",\n";
+                    }
+                }
+            }
+            else
+            {
+                my $val = $emi_reg_bb_key_ref->{$emi_key};
+                if (($val ne 'x') && ($val ne 'X') && ($val ne ''))
+                {
+                    my $emi_info_def = "$emi_key\_MCP$combo_idx";
+                    $combo_mem_emi_info_struct .= "        ." . lc($emi_key) . " = " . $emi_info_def . ",\n";
+                }
+            }
+        }
+        $combo_mem_emi_info_struct .= "    },\n";
+    }
+    my $template = <<"__TEMPLATE";
+/********************************************
+ * Include.
+ ********************************************/
+#include "custom_EMI_release.h"
+#include "custom_EMI.h"
+$cus_include
+/********************************************
+ * Definition.
+ ********************************************/
+$cus_def
+/********************************************
+ * Enum.
+ ********************************************/
+$cus_enum
+/********************************************
+ * Struct.
+ ********************************************/
+MTK_EMI_Info EMI_INFO[] = {
+$combo_mem_emi_info_struct
+}; /* End of EMI_INFO struct */
+$cus_struct
+/********************************************
+ * Exposed APIs.
+ ********************************************/
+$cus_api
+
+__TEMPLATE
+
+    return $template;
+}
+
+
+#****************************************************************************
+# custom_emi_h for MT6297
+#****************************************************************************
+Class::Struct::struct(EMI_SETTING => {name => '$', xls_name => '$', value => '$'});
+my @EMI_SETTINGS = (
+    EMI_SETTING->new(name => "sub_version", xls_name => undef, value => 0x1),
+    EMI_SETTING->new(name => "type", xls_name => "Type"),
+    EMI_SETTING->new(name => "dram_cbt_mode_extern", xls_name => "Mode"),
+    EMI_SETTING->new(name => "DRAM_RANK_SIZE", xls_name => "Density (Mb)"),
+    EMI_SETTING->new(name => "DRAM_RANK_SIZE_0L", xls_name => undef),
+    EMI_SETTING->new(name => "DRAM_RANK_SIZE_0H", xls_name => undef),
+    EMI_SETTING->new(name => "DRAM_RANK_SIZE_1L", xls_name => undef),
+    EMI_SETTING->new(name => "DRAM_RANK_SIZE_1H", xls_name => undef),
+    # by platform
+    EMI_SETTING->new(name => "EMI_CONA_VAL", xls_name => "CONA_VAL"),
+    EMI_SETTING->new(name => "CHN0_EMI_CONA_VAL", xls_name => "CHN0_CONA_VAL"),
+    EMI_SETTING->new(name => "CHN1_EMI_CONA_VAL", xls_name => "CHN1_CONA_VAL"),
+    EMI_SETTING->new(name => "EMI_CONF_VAL", xls_name => "CONF_VAL"),
+    EMI_SETTING->new(name => "EMI_CONH_VAL", xls_name => "CONH_VAL"),
+    EMI_SETTING->new(name => "iLPDDR3_MODE_REG_5", xls_name => "MODE_REG5"),
+    EMI_SETTING->new(name => "PIN_MUX_TYPE", xls_name => "PIN_MUX_TYPE"),
+);
+
+sub emi_setting_type
+{
+    my $typ = shift;
+    my %mcp = ("Discrete" => 0, "NAND" => 1, "eMMC" => 2, "UFS" => 3);
+    my %ddr = ("DDR1" => 1, "LPDDR2" => 2, "LPDDR3" => 3, "PCDDR3" => 4, "LPDDR4" => 5, "LPDR4X" => 6, "LPDDR4P" => 7);
+
+    if (($typ =~ /(Discrete)\s+(\w+)/) or ($typ =~ /MCP\((\w+)\+(\w+)\)/)) {
+        return ($mcp{$1} << 8) + ($ddr{$2});
+    }
+    return undef;
+}
+
+sub emi_setting_cbt
+{
+    my $cbt = shift;
+    my %cbt_h = ( "CBT_R0_R1_NORMAL" => 0, "CBT_R0_R1_BYTE" => 1, "CBT_R0_NORMAL_R1_BYTE" => 2, "CBT_R0_BYTE_R1_NORMAL" => 3);
+
+    return $cbt_h{$cbt};
+}
+
+sub custom_emi_h_file_body_for_97
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL) = @_;
+    my $platform = $MAKEFILE_OPTIONS_LOCAL->{platform};
+    my @size;
+
+    for my $emi (@EMI_SETTINGS) {  # update value
+        if ($emi->name eq "type") {
+            $emi->value(sprintf "%#05x", &emi_setting_type($MDL_INFO_LIST_LOCAL->[1]->{0}->{$emi->xls_name}));
+        } elsif ($emi->name eq "dram_cbt_mode_extern") {
+            my $v = $MDL_INFO_LIST_LOCAL->[1]->{0}->{$emi->xls_name};
+            $emi->value(sprintf "%d/*%s*/", &emi_setting_cbt($v), $v);
+        } elsif ($emi->name eq "DRAM_RANK_SIZE") {
+            for (split(/\+/, $MDL_INFO_LIST_LOCAL->[1]->{0}->{$emi->xls_name})) {
+                my $s = int($_) * 1024 * 1024 / 8; # Mbit -> Byte
+                push @size, sprintf("%#x", $s & 0xffffffff);
+                push @size, sprintf("%#x", $s >> 32);
+            }
+        } elsif ($emi->name =~ /DRAM_RANK_SIZE_\d[LH]/i) {
+            $emi->value(shift @size);
+        } elsif (defined $emi->xls_name) {
+            $emi->value($MDL_INFO_LIST_LOCAL->[1]->{0}->{$platform}->{$emi->xls_name});
+        }
+    }
+
+    my @setting;
+    for my $emi (@EMI_SETTINGS) {
+        push(@setting, ".".$emi->name." = ".$emi->value) if defined($emi->value);
+    }
+    my $setting_str = join(",\n    ", @setting);
+
+    my $template = <<"__TEMPLATE";
+#ifndef __CUSTOM_EMI_H__
+#define __CUSTOM_EMI_H__
+
+#include "emi.h"
+
+// $MDL_INFO_LIST_LOCAL->[1]->{0}->{"Part Number"}
+EMI_SETTINGS default_emi_setting =
+{
+    $setting_str
+};
+
+#endif
+__TEMPLATE
+
+    return $template;
+}
+
+return 1;
diff --git a/src/bach/build.bach/tools/emigenflash.pl b/src/bach/build.bach/tools/emigenflash.pl
new file mode 100644
index 0000000..bcdaeeb
--- /dev/null
+++ b/src/bach/build.bach/tools/emigenflash.pl
@@ -0,0 +1,2405 @@
+#!/usr/bin/perl
+#
+#  Copyright Statement:
+#  --------------------
+#  This software is protected by Copyright and the information contained
+#  herein is confidential. The software may not be copied and the information
+#  contained herein may not be used or disclosed except with the written
+#  permission of MediaTek Inc. (C) 2006
+#
+#  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+#  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+#  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON
+#  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+#  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+#  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+#  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH
+#  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO
+#  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S
+#  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.
+#
+#  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE
+#  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+#  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+#  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO
+#  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+#
+#  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE
+#  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF
+#  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND
+#  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER
+#  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).
+#
+#*****************************************************************************
+#*
+#* Filename:
+#* ---------
+#*   emigenflash.pl
+#*
+#* Project:
+#* --------
+#*
+#*
+#* Description:
+#* ------------
+#*   Parse and generate flash parameter
+#*
+#* Author:
+#* -------
+#*   Way Chen      (mtk54483)
+#*
+#*============================================================================
+#*             HISTORY
+#* Below this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
+#*------------------------------------------------------------------------------
+#* $Revision$
+#* $Modtime$
+#* $Log$
+#*
+#* 04 21 2014 guo-huei.chang
+#* [MOLY00063203] [EMIGEN] Update EMIGEN for Smart Phone Project
+#* 	Update emigen to generate EMI size for none EMI support
+#*
+#*
+#*------------------------------------------------------------------------------
+#* Upper this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
+#*============================================================================
+#****************************************************************************/
+
+my $nor_fat_base_offset;
+my $nor_fat_size_offset;
+my $nor_partition_sector_offset;
+my $nor_nand_fat_base_offset;
+my $nor_nand_fat_size_offset;
+my $nor_nand_partition_sector_offset;
+my $nfb_fat_base_offset;
+my $nfb_fat_size_offset;
+my $nfb_partition_sector_offset;
+my $cmem_max_blocks = 0;
+my $cmem_max_sectors = 0;
+my $DebugPrint    = 1; # 1 for debug; 0 for non-debug
+#****************************************************************************
+# subroutine:  Calculate_FAT_Info
+# input:       $info_list_href:   input list reference of list of MDL info
+#              $combo_mem_count:  number of memory devices selected
+#              $combo_sip_count:  number of SIPs
+#              $mem_dev_type:     MEMORY_DEVICE_TYPE
+#              $info_output_href: output hash reference for common MDL info,
+#                                 including minimum physical flash size,
+#                                           minimum small block start address
+#                                           maximum default FAT base address
+#                                           minimum default FAT size
+#                                           minimum RAM size
+#                                           common flash series information
+#                                           common single/multiple-bank definition
+#                                           common PBP information
+#                                           minimum PBP size
+#****************************************************************************
+sub Calculate_FAT_Info
+{
+	my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $COMM_MDL_INFO_LOCAL, $MDL_INFO_LIST_LOCAL, $NOR_FLASH_BASE_ADDRESS_VAL_LOCAL, $NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL,$nor_size_Mb_LOCAL, $CUSTOM_FEATURE_CFG_OPTIONS_LOCAL, $ENTIRE_BLOCK_INFO_START_LIST_LOCAL, $ENTIRE_BLOCK_INFO_SIZE_LIST_LOCAL) = @_;
+	### Print out the physical available flash size
+my $flash_limit = sprintf("0x%08X", $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+
+#print "$COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'}, $COMM_MDL_INFO_LOCAL->{1}->{'Flash Size'}\n" if ($DebugPrint == 1);
+
+### Calculate real FAT start address and size based on customization
+if ((defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_BASE_ADDRESS}) && (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_SIZE}))
+{
+    $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL = hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_BASE_ADDRESS});
+    $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL = hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_SIZE});
+}
+elsif ((defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_BASE_ADDRESS}) && (!defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_SIZE}))
+{
+    $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL = hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_BASE_ADDRESS});
+    if (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{__NOR_FDM5__})  # for NOR FDM5, small blocks at the end of the flash should not be used
+    {
+        $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL = $COMM_MDL_INFO_LOCAL->{0}->{'Small Block Start'} - hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_BASE_ADDRESS});
+    }
+    else
+    {
+        $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL = $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'} - hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_BASE_ADDRESS});
+    }
+}
+elsif ((!defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_BASE_ADDRESS}) && (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_SIZE}))
+{
+    $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL = $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Base'};
+    $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL = hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_SIZE});
+}
+else
+{
+    $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL = $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Base'};
+    $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL = $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Size'};
+}
+
+if($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+{
+    $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Base'} = $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL;
+    $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Size'} = $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL;
+}
+elsif($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'TRUE')
+{
+    $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Base'} = hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_BASE_ADDRESS});
+    $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Size'} = hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_SIZE});
+}
+
+### work-around for bad small block issue
+my $fat_size_shrink;
+if ((($$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL+$$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL)>$COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'}) && (($$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL+$$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL)<=($nor_size_Mb_LOCAL/8*1024*1024)))
+{
+    $fat_size_shrink = sprintf(" - 0x%08x", $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL+$$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL-$COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+    $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL = $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL - ($$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL+$$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL-$COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+}
+
+#print "NOR_FLASH_BASE_ADDRESS_VAL = $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL, NOR_ALLOCATED_FAT_SPACE_VAL = $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL\n" if ($DebugPrint == 1);
+
+### Check FAT settings validity
+&error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: FAT space cannot exceed physical NOR flash size!", __FILE__, __LINE__)
+    if (($$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL<0) || ($$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL<0));
+&error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: FAT space cannot exceed physical NOR flash size!", __FILE__, __LINE__)
+    if (($$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL+$$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL) > ($COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'}));
+if (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{__NOR_FDM5__})
+{
+    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: FAT space cannot include small blocks in NOR FDM 5.0!", __FILE__, __LINE__)
+        if (($$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL+$$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL) > $COMM_MDL_INFO_LOCAL->{0}->{'Small Block Start'});
+}
+
+### FAT tuning for FOTA
+$nor_fat_base_offset = "0x0";
+$nor_fat_size_offset = "0x0";
+$nor_partition_sector_offset = 0;
+$nor_nand_fat_base_offset = "0x0";
+$nor_nand_fat_size_offset = "0x0";
+$nor_nand_partition_sector_offset = 0;
+$nfb_fat_base_offset = "0x0";
+$nfb_fat_size_offset = "0x0";
+$nfb_partition_sector_offset = 0;
+
+##FOTA Feature Related Code, Currently we do not use in MT6291
+if ($MAKEFILE_OPTIONS_LOCAL->{'fota_enable'} eq 'FOTA_DM')
+{
+        if ($MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} ne 'NONE')
+        {
+        ###
+        # image head area size = block number * block size (0x20000)
+        #                      = (1(FOTA_BL_IMG_MAX_SIZE) + 6(FOTA_UE_RESIDENT_FLASH_SPACE_SIZE) + 6(FOTA_UE_BACKUP_FLASH_SPACE_SIZE) + 8(image list block) + 1(ext_bootloader) + 2(bad block margin)) * 0x20000 = 0x300000
+        my $image_head_area_size = 0x300000;
+
+        my ($package_storage_base, $package_storage_size);
+        ###
+        # FOTA_PACKAGE_STORAGE_BASE = image head area size + all ROM sizes (ROM, SECONDARY_ROM, THIRD_ROM, DEMAND_PAGING_ROM0)
+        if (defined $MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} and $MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} eq 'ENFB')
+        {
+              $package_storage_base = $image_head_area_size + 0x900000 + 0x1500000 + 0x800000;
+        }
+        elsif (defined $MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} and $MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} eq 'ONDEMAND')
+        {
+              $package_storage_base = $image_head_area_size + 0x400000 + 0xC00000 + 0xE00000;
+        }
+        elsif (defined $MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} and $MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} eq 'MIXED')
+        {
+              $package_storage_base = $image_head_area_size + 0x400000 + 0xF00000 + 0x800000 + 0x400000;
+        }
+        elsif (defined $MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} and $MAKEFILE_OPTIONS_LOCAL->{'nand_flash_booting'} eq 'BASIC')
+        {
+              $package_storage_base = $image_head_area_size + 0x800000 + 0x1600000;
+        }
+        $package_storage_size = (hex($CUSTOM_FEATURE_CFG_OPTIONS_LOCAL->{CONFIG_FOTA_PACKAGE_AREA_SIZE}) / 0x20000) * 0x20000;
+
+        my $recommend_fat_base;
+        if ($MAKEFILE_OPTIONS_LOCAL->{'secure_support'} eq 'TRUE')
+        {
+              # When SECURE_SUPPORT = TRUE, FAT_BASE_ADDRESS = FOTA_PACKAGE_STORAGE_BASE + FOTA_PACKAGE_STORAGE_SIZE + ((4 + FOTA_EXTRA_RESERVED_BLOCKS) * (block size)0x20000)
+              $recommend_fat_base = $package_storage_base + $package_storage_size + ((4 + 5) * 0x20000);
+        }
+        else
+        {
+              # When SECURE_SUPPORT = FALSE, FAT_BASE_ADDRESS = FOTA_PACKAGE_STORAGE_BASE + FOTA_PACKAGE_STORAGE_SIZE
+              $recommend_fat_base = $package_storage_base + $package_storage_size;
+        }
+        if (($recommend_fat_base - hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_BASE_ADDRESS})) > 0)
+        {
+            $nfb_fat_base_offset = sprintf("0x%08X", $recommend_fat_base - hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_BASE_ADDRESS}));
+            $nfb_fat_size_offset = sprintf("0x%08X", $recommend_fat_base - hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_BASE_ADDRESS}));
+            if (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_FIRST_DRIVE_SECTORS})
+            {
+                my $nand_fat_partition_sectors;
+                if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_FIRST_DRIVE_SECTORS} =~ /\(*(\d+)\)*/)
+                {
+                    $nand_fat_partition_sectors = $1;
+                }
+                $nfb_partition_sector_offset = sprintf("%d", ($recommend_fat_base - hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_BASE_ADDRESS})) / 512) if (($nand_fat_partition_sectors - ($recommend_fat_base-hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NAND_BOOTING_NAND_FS_BASE_ADDRESS}))/512) >= 4096);  # NAND user drive should be no less than 4096 sectors
+            }
+        }
+    }
+    elsif ($MAKEFILE_OPTIONS_LOCAL->{'system_drive_on_nand'} eq 'TRUE' or $MAKEFILE_OPTIONS_LOCAL->{'fota_update_package_on_nand'} eq 'TRUE')
+    {
+        my $package_storage_size = (hex($CUSTOM_FEATURE_CFG_OPTIONS_LOCAL->{CONFIG_FOTA_PACKAGE_AREA_SIZE}) / 0x20000) * 0x20000;
+        $nor_nand_fat_base_offset = sprintf("0x%08X", $package_storage_size);
+        $nor_nand_fat_size_offset = sprintf("0x%08X", $package_storage_size);
+        if (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NAND_FS_FIRST_DRIVE_SECTORS})
+        {
+            my $nand_fat_partition_sectors;
+            if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NAND_FS_FIRST_DRIVE_SECTORS} =~ /\(*(\d+)\)*/)
+            {
+                $nand_fat_partition_sectors = $1;
+            }
+            $nor_nand_partition_sector_offset = sprintf("%d", $package_storage_size / 512) if (($nand_fat_partition_sectors - ($package_storage_size)/512) >= 4096);  # NAND user drive should be no less than 4096 sectors
+        }
+    }
+    elsif (!exists $MAKEFILE_OPTIONS_LOCAL->{'emmc_booting'} or $MAKEFILE_OPTIONS_LOCAL->{'emmc_booting'} eq 'NONE')  # FOTA update package will be put in a different partition from FS
+    {
+        my $fota_block_number = $CUSTOM_FEATURE_CFG_OPTIONS_LOCAL->{CONFIG_FOTA_PACKAGE_BLOCK_NUMBER};
+        my $ori_nor_flash_base_address_val = $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL;
+        my $ori_nor_allocated_fat_space_val = $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL;
+
+        ### Package Storage
+        while ($fota_block_number > 0)
+        {
+            my $tmp_block_sz;
+            $tmp_block_sz = &config_query_flash_block_sz($$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL, $COMM_MDL_INFO_LOCAL,$ENTIRE_BLOCK_INFO_START_LIST_LOCAL, $ENTIRE_BLOCK_INFO_SIZE_LIST_LOCAL);
+
+            $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL += $tmp_block_sz;
+            $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL -= $tmp_block_sz;
+            $fota_block_number--;
+        }
+
+        ### Backup Storage
+    	my $query_address = 0;
+        my $max_block_size = 0;
+        while ($query_address < $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'})
+        {
+            my $cur_block_size = &config_query_flash_block_sz($query_address, $COMM_MDL_INFO_LOCAL, $ENTIRE_BLOCK_INFO_START_LIST_LOCAL, $ENTIRE_BLOCK_INFO_SIZE_LIST_LOCAL);
+            if ($cur_block_size > $max_block_size)
+            {
+                $max_block_size = $cur_block_size;
+            }
+            $query_address += $cur_block_size;
+        }
+        $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL += 2 * $max_block_size;
+        $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL -= 2 * $max_block_size;
+
+        if (($$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL - $ori_nor_flash_base_address_val) > 0)
+        {
+            $nor_fat_base_offset = sprintf("0x%08X", $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL - $ori_nor_flash_base_address_val);
+            $nor_fat_size_offset = sprintf("0x%08X", $$ori_nor_allocated_fat_space_val - $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL);
+            if (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_FIRST_DRIVE_SECTORS})
+            {
+                my $nor_fat_partition_sectors;
+                if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_FS_FIRST_DRIVE_SECTORS} =~ /\(*(\d+)\)*/)
+                {
+                    $nor_fat_partition_sectors = $1;
+                }
+                $nor_partition_sector_offset = sprintf("%d", ($ori_nor_allocated_fat_space_val - $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL) / 512) if (($nor_fat_partition_sectors - ($ori_nor_allocated_fat_space_val-$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL)/512) >= 128);  # NOR user drive should be no less than 128 sectors
+            }
+        }
+    }
+}##end if ($MAKEFILE_OPTIONS_LOCAL->{'fota_enable'} eq 'FOTA_DM')
+
+### Calculate TOTAL_BLOCKS and NOR_BLOCK_SIZE by different memory devices
+$cmem_max_blocks = 0;
+$cmem_max_sectors = 0;
+$nor_block_size = 0;
+for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+{
+    my $mem_idx    = $_;
+    my (@cur_regions, @cur_banks, @cur_blocks, @cur_new_regions, @cur_blocks_start, @cur_blocks_size);
+    my $cur_blocks_sum  = 0;
+    my $cur_regions_sum = 0;
+    my $cur_total_blocks = 0;
+    my $cur_nor_block_size = 0;
+    my $cur_total_sectors = 0;
+    my $cur_sector_size = 0;
+    my $cur_bank_str = 0;
+    my $cur_block_str = 0;
+
+    $cur_sector_size = ($MDL_INFO_LIST_LOCAL->[$mem_idx]->{1}->{'Comm. Series'} eq 'INTEL_SIBLEY') ? 0x400 : 0x200;
+    ##MDL CS1 is flash, COMM MDL number 0 is flash info
+    @cur_regions = &split_info($MDL_INFO_LIST_LOCAL->[$mem_idx]->{1}->{'Last Bank'}->{'Region'});
+    $cur_bank_str = $MDL_INFO_LIST_LOCAL->[$mem_idx]->{1}->{'Device Geometry'}->{'Bank Info.'};
+    @cur_banks   = &split_info($cur_bank_str);
+    $cur_block_str = $MDL_INFO_LIST_LOCAL->[$mem_idx]->{1}->{'Device Geometry'}->{'Block Info.'};
+    @cur_blocks  = &split_info($cur_block_str);
+
+    for (0..$#cur_blocks)
+    {
+        if ($cur_blocks[$_] =~ /\{(0x\w+),\s*(0x\w+)\}/) # match {0x0,0x10000}
+        {
+            last if (hex($1) >= $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});  # end block infor traversal when the flash end among all memories has been reached
+            push @cur_blocks_start, $1;
+            push @cur_blocks_size, $2;
+        }
+    }
+    push @cur_blocks_start, $flash_limit;
+    for (0..($#cur_blocks_start-1))
+    {
+        my $target_region = $cur_blocks_size[$_];
+        my $tmp_block_count = 0;
+        print "$_: $target_region\n"if ($DebugPrint == 1);
+
+        while ($cur_blocks_sum < hex($cur_blocks_start[$_+1]))
+        {
+            $cur_blocks_sum += hex($cur_blocks_size[$_]);
+            if ($cur_blocks_sum > $$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL)
+            {
+                $cur_regions_sum += hex($cur_blocks_size[$_]);
+                $tmp_block_count++;
+            }
+            last if ($cur_regions_sum >= $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL);
+        }
+        if ($tmp_block_count > 0)
+        {
+            $cur_total_blocks += $tmp_block_count;
+            if (hex($target_region) > hex($cur_nor_block_size))  # Find the largest block size within FS
+            {
+                $cur_nor_block_size = $target_region;
+            }
+        }
+        last if ($cur_regions_sum >= $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL);
+    }
+    &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: FAT space must be multiples of blocks!", __FILE__, __LINE__) if ($cur_regions_sum > $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL);
+
+    ### Calculate the current number of sectors
+    ### S1 = ((NOR_ALLOCATED_FAT_SPACE - NOR_BLOCK_SIZEMCP1 * SNOR_ERASE_QUEUE_SIZE) / SECTOR_SIZE)
+    ### NOR_ALLOCATED_FAT_SPACE := NOR_BOOTING_NOR_FS_SIZE
+    ### SNOR_ERASE_QUEUE_SIZE := NOR_FDM4_ESB_PARAMETER_ERASE_QUEUE_SIZE or 5 (default value)
+    ### NOR_SYSTEM_DRIVE_RESERVED_BLOCK := NOR_PARAMETER_SYSTEM_DRIVE_RESERVED_BLOCK or 3 (default value)
+    ### NOR_BLOCK_SIZEMCP1 := The largest block size within FAT in MCP 1.
+    ### SECTOR_SIZE:
+    ### Sibley:=0x400
+    ### Non-Sibley := 0x200
+    if ($MAKEFILE_OPTIONS_LOCAL->{'enhanced_single_bank_nor_flash_support'} eq 'TRUE')
+    {
+        $cur_total_sectors = ($$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL - (hex($cur_nor_block_size) * $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_FDM4_ESB_PARAMETER_ERASE_QUEUE_SIZE})) / $cur_sector_size;
+    }
+    else
+    {
+        $cur_total_sectors = ($$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL - (hex($cur_nor_block_size) * $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_PARAMETER_SYSTEM_DRIVE_RESERVED_BLOCK})) / $cur_sector_size;
+    }
+    print "FAT SPACE is: $$NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL, BlockSize is: $cur_nor_block_size, SectorSize is $cur_sector_size\n"if ($DebugPrint == 1);
+
+    ### Find the overall max TOTAL_BLOCKS and NOR_BLOCK_SIZE
+    if ($nor_block_size < hex($cur_nor_block_size))
+    {
+        $nor_block_size = hex($cur_nor_block_size);
+    }
+    if ($cmem_max_blocks < $cur_total_blocks)
+    {
+        $cmem_max_blocks = $cur_total_blocks;
+    }
+    if ($cmem_max_sectors < $cur_total_sectors)
+    {
+        $cmem_max_sectors = $cur_total_sectors;
+    }
+}
+print "TOTAL_BLOCKS > 127 , recommend enable FDM5.0 feature!\n" if ($cmem_max_blocks > 127);
+}
+
+#****************************************************************************
+# subroutine:  Validate_FAT_NORRAWDISK_OVERLAP
+# input:       $info_list_href:   input list reference of list of MDL info
+#              $combo_mem_count:  number of memory devices selected
+#              $combo_sip_count:  number of SIPs
+#              $mem_dev_type:     MEMORY_DEVICE_TYPE
+#              $info_output_href: output hash reference for common MDL info,
+#                                 including minimum physical flash size,
+#                                           minimum small block start address
+#                                           maximum default FAT base address
+#                                           minimum default FAT size
+#                                           minimum RAM size
+#                                           common flash series information
+#                                           common single/multiple-bank definition
+#                                           common PBP information
+#                                           minimum PBP size
+#****************************************************************************
+sub Validate_FAT_NORRAWDISK_OVERLAP
+{
+    my ($CUSTOM_MEM_DEV_OPTIONS_LOCAL, $COMM_MDL_INFO, $NOR_FLASH_BASE_ADDRESS_VAL_LOCAL, $NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL, $CUSTOM_MEMORY_DEVICE_HDR_LOCAL) = @_;
+    my %dsk_layout_hash;
+    my @dsk_layout_sort_list;
+    $dsk_layout_hash{$NOR_FLASH_BASE_ADDRESS_VAL_LOCAL} = $NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL;
+    if (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK0_BASE_ADDRESS})
+    {
+        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot configure NOR Raw Disk0 larger than physical NOR flash size!", __FILE__, __LINE__)
+        if ((hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK0_BASE_ADDRESS})+hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK0_SIZE})) > $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot configure NOR Raw Disk0 the same base address as FAT!", __FILE__, __LINE__)
+        if (hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK0_BASE_ADDRESS}) == $NOR_FLASH_BASE_ADDRESS_VAL_LOCAL);
+        $dsk_layout_hash{hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK0_BASE_ADDRESS})} = hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK0_SIZE});
+    }
+    if (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL{NOR_BOOTING_NOR_DISK1_BASE_ADDRESS})
+    {
+        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot configure NOR Raw Disk1 larger than physical NOR flash size!", __FILE__, __LINE__)
+        if ((hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK1_BASE_ADDRESS})+hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK1_SIZE})) > $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot configure NOR Raw Disk1 the same base address as FAT!", __FILE__, __LINE__)
+        if (hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK1_BASE_ADDRESS}) == $NOR_FLASH_BASE_ADDRESS_VAL_LOCAL);
+        &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot configure NOR Raw Disk1 the same base address as NOR Raw Disk0!", __FILE__, __LINE__)
+        if (hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK1_BASE_ADDRESS}) == hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK0_BASE_ADDRESS}));
+        $dsk_layout_hash{hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK1_BASE_ADDRESS})} = hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK1_SIZE});
+    }
+    foreach (sort {$a <=> $b} keys %dsk_layout_hash)
+    {
+        push @dsk_layout_sort_list, $_;
+    }
+    foreach (1..$#dsk_layout_sort_list)
+    {
+        error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: NOR Flash disk overlapping detected. Please check NOR Raw disk and FAT setting!", __FILE__, __LINE__)
+        if (($dsk_layout_sort_list[$_-1]+$dsk_layout_hash{$dsk_layout_sort_list[$_-1]}) > $dsk_layout_sort_list[$_]);
+    }
+}
+
+#****************************************************************************
+# subroutine:  Calculate_NORRAWDISK_Region_Info
+# input:       $info_list_href:   input list reference of list of MDL info
+#              $combo_mem_count:  number of memory devices selected
+#              $combo_sip_count:  number of SIPs
+#              $mem_dev_type:     MEMORY_DEVICE_TYPE
+#              $info_output_href: output hash reference for common MDL info,
+#                                 including minimum physical flash size,
+#                                           minimum small block start address
+#                                           maximum default FAT base address
+#                                           minimum default FAT size
+#                                           minimum RAM size
+#                                           common flash series information
+#                                           common single/multiple-bank definition
+#                                           common PBP information
+#                                           minimum PBP size
+#****************************************************************************
+sub Calculate_NORRAWDISK_Region_Info
+{
+    my ($CUSTOM_MEM_DEV_OPTIONS_LOCAL) = @_;
+    my (@regions_disk0, @regions_disk1);
+    for (0..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{NOR_BOOTING_NOR_DISK_NUM})
+    {
+       my $nor_booting_nor_disk_base_address = sprintf("NOR_BOOTING_NOR_DISK%d_BASE_ADDRESS", $_);
+       my $nor_booting_nor_disk_size         = sprintf("NOR_BOOTING_NOR_DISK%d_SIZE", $_);
+       my $nor_booting_nor_disk_index        = $_;
+       #print "[$nor_booting_nor_disk_index] Nor Disk Base: $nor_booting_nor_disk_base_address\n"if ($DebugPrint == 1);
+       #print "[$nor_booting_nor_disk_index] Nor Disk Size: $nor_booting_nor_disk_size\n"if ($DebugPrint == 1);
+
+       if (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{$nor_booting_nor_disk_base_address})
+       {
+           #print "RAW DISK Check!\n"if ($DebugPrint == 1);
+           for (0..($#entire_block_info_start_list-1))
+           {
+               my $target_region;
+               my $tmp_block_count = 0;
+               my $tmp_blocks_sum = 0;
+               my $tmp_regions_sum = 0;
+               while ($tmp_blocks_sum < $entire_block_info_start_list[$_+1])
+               {
+                   $tmp_blocks_sum += $entire_block_info_size_list[$_];
+                   if ($tmp_blocks_sum > hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{$nor_booting_nor_disk_base_address}))
+                   {
+                       $tmp_regions_sum += $entire_block_info_size_list[$_];
+                       $target_region = sprintf("0x%X", $entire_block_info_size_list[$_]);
+                       $tmp_block_count++;
+                   }
+                   if ($tmp_regions_sum >= hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{$nor_booting_nor_disk_size}))
+                   {
+                       last;
+                   }
+               }
+               if ($tmp_block_count > 0)
+               {
+                   if ($nor_booting_nor_disk_index == 0)
+                   {
+                       push @regions_disk0, "{$target_region,$tmp_block_count}";
+                   }
+                   elsif ($nor_booting_nor_disk_index == 1)
+                   {
+                       push @regions_disk1, "{$target_region,$tmp_block_count}";
+                   }
+               }
+               if ($tmp_regions_sum >= hex($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{$nor_booting_nor_disk_size}))
+               {
+                   last;
+               }
+           }
+       }
+    }
+}
+#****************************************************************************
+# subroutine:  get_common_MDL_info
+# input:       $info_list_href:   input list reference of list of MDL info
+#              $combo_mem_count:  number of memory devices selected
+#              $combo_sip_count:  number of SIPs
+#              $mem_dev_type:     MEMORY_DEVICE_TYPE
+#              $info_output_href: output hash reference for common MDL info,
+#                                 including minimum physical flash size,
+#                                           minimum small block start address
+#                                           maximum default FAT base address
+#                                           minimum default FAT size
+#                                           minimum RAM size
+#                                           common flash series information
+#                                           common single/multiple-bank definition
+#                                           common PBP information
+#                                           minimum PBP size
+#****************************************************************************
+sub get_common_MDL_info
+{
+    #my ($info_list_href, $combo_mem_count, $combo_sip_count, $mem_dev_type, $info_output_href, $LPSDRAM_CHIP_SELECT_LOCAL, $MAKEFILE_OPTIONS, $CUSTOM_MEM_DEV_OPTIONS, $CUSTOM_MEMORY_DEVICE_HDR) = @_;
+    my ($info_list_href, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $info_output_href, $MAKEFILE_OPTIONS_LOCAL, $LPSDRAM_CHIP_SELECT_LOCAL, $CUSTOM_MEMORY_DEVICE_HDR_LOCAL, $emi_is_existed) = @_;
+    my $combo_mem_count = $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT};
+    my $combo_sip_count = $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_SIP_ENTRY_COUNT};
+    my $mem_dev_type = $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE};
+    my $platform = $MAKEFILE_OPTIONS_LOCAL->{'platform'};
+    my $min_flash_size = 0xffffffff;
+    my $max_nand_block_size = 0;
+    my $min_small_block_start = 0xffffffff;
+    my $max_fat_base = 0;
+    my $min_fat_space = 0xffffffff;
+    my $Size_Mb_ram_min = 0xffffffff*8/1024/1024;  # minimum size in "Size (Mb)" field for RAM
+    my $ADMUX_CS0;
+    my $ADMUX_CS1;
+    my $if_DRAM;
+    my $Comm_Series_CS0_all = '*';  # all "Comm. Series" information
+    my $Comm_Series_CS1_all = '*';  # all "Comm. Series" information
+    my $Bank_if_S = 'M';  # mark as 'S' if there is any device whose "Bank" field is 'S'
+    my $PBP_Y_N_if_Y = 'N';  # mark as 'N' if there is any device whose "PBP -> Y/N" field is 'N'
+    my $PBP_Size_W_min = 0xffffffff;  # minimum size in "PBP -> Size(W)" field
+    my $Last_Bank_Region;  # common "Last Bank -> Region" field
+    my $Geometry_Bank_Info;  # common "Geometry -> Bank Info." field
+    my $Geometry_Block_Info;  # common "Geometry -> Block Info." field
+
+    #print "Flash PN: $info_list_href->[1]->{1}->{'Part Number'}, $info_list_href->[1]->{1}->{'Flash Size'}\n";
+
+    for (1..$combo_mem_count)
+    {
+        ### Find the smallest default FAT configuration among all MCPs
+        my $cur_flash_size = 0;  # physical flash size (excluding bad small blocks for Toshiba)
+        my $cur_small_block_start = 0;  # the starting location of small blocks at the end of a flash
+        my $cur_sum_of_regions = 0;  # the sum of the regions in the last bank
+        my $cur_fat_base = 0;  # default FAT base (physical flash size - last bank size)
+        my $cur_fat_space = 0;  # default FAT size (last bank size)
+        my @cur_blocks_start;  # list of Block Info starting addresses
+        my @cur_blocks_size;  # list of Block Info sizes
+        my @cur_regions = &split_info($info_list_href->[$_]->{1}->{'Last Bank'}->{'Region'});  # list of Last Bank -> Region Info.
+        my $cur_bank_str = $info_list_href->[$_]->{1}->{'Device Geometry'}->{'Bank Info.'};
+        my @cur_banks = &split_info($cur_bank_str);  # list of Bank Info.
+        my $cur_block_str = $info_list_href->[$_]->{1}->{'Device Geometry'}->{'Block Info.'};
+        my @cur_blocks = &split_info($cur_block_str);  # list of Block Info.
+
+        # Calculate default FAT size/start address (Last Bank)
+        for (0..$#cur_regions)  # calculate the sum of last bank
+        {
+            if ($cur_regions[$_] =~ /\{(0x\w+)\s*,\s*(\d+)\}/) # match {0x20000, 7}
+            {
+                $cur_sum_of_regions += hex($1) * $2;
+            }
+        }
+        for (0..$#cur_banks)  # calculate the physical flash size
+        {
+            if ($cur_banks[$_] =~ /\{(0x\w+)\s*,\s*(\d+)\}/) # match {0x20000, 7}
+            {
+                $cur_flash_size += hex($1) * $2;
+                $cur_small_block_start += hex($1) * $2;
+            }
+        }
+        if (($#cur_regions>0) && (defined $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{__NOR_FDM5__}))  # for NOR FDM5, small blocks at the end of the flash should be excluded
+        {
+            if ($cur_blocks[$#cur_blocks] =~ /\{(0x\w+)\s*,\s*(0x\w+)\}/) # match {0xFF0000,0x2000}
+            {
+                $cur_small_block_start = hex($1);
+            }
+            $cur_fat_space = $cur_sum_of_regions - ($cur_flash_size-$cur_small_block_start);
+        }
+        else
+        {
+            $cur_fat_space = $cur_sum_of_regions;
+        }
+        $cur_fat_base = $cur_flash_size - $cur_sum_of_regions;
+
+        ##MT6291 Operation
+        #if($platform eq 'MT6291') # FIXME
+        {
+            if ($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'TRUE')
+            {
+                if (defined $info_list_href->[$_]->{1}->{'NAND Size(MB)'})##Fill NAND Flash Size if NAND Flash is exist
+                {
+                    $cur_flash_size = $info_list_href->[$_]->{1}->{'NAND Size(MB)'} * 1024 * 1024;
+                }
+                else
+                {
+                    $cur_flash_size = 0xffffffff;
+                }
+            }
+        }
+
+        # Temporarily store physical flash size, small block start, and default FAT size/base for future reference
+        $info_list_href->[$_]->{1}->{'Flash Size'} = $cur_flash_size;
+        $info_list_href->[$_]->{1}->{'Small Block Start'} = $cur_small_block_start;
+        $info_list_href->[$_]->{1}->{'Default FAT Base'} = $cur_fat_base;
+        $info_list_href->[$_]->{1}->{'Default FAT Size'} = $cur_fat_space;
+
+        #print "Flash PN: $info_list_href->[$_]->{1}->{'Part Number'}, FlashSize: $info_list_href->[$_]->{1}->{'Flash Size'}, SmallBlockStart: $info_list_href->[$_]->{1}->{'Small Block Start'}, FATBase: $info_list_href->[$_]->{1}->{'Default FAT Base'}, FATSize: $info_list_href->[$_]->{1}->{'Default FAT Size'}\n"if ($DebugPrint == 1);
+        # In order to speed-up, assume the largest FAT base will be smaller than the physical flash size first
+        # Find the largest FAT base
+        if ($max_fat_base < $cur_fat_base)
+        {
+            $max_fat_base = $cur_fat_base;
+        }
+        # Find the smallest flash size
+        if ($min_flash_size > $cur_flash_size)
+        {
+            $min_flash_size = $cur_flash_size;
+        }
+        # Find the smallest small block start
+        if ($min_small_block_start > $cur_small_block_start)
+        {
+            $min_small_block_start = $cur_small_block_start;
+        }
+
+        ### Find the minimum RAM size in Mb
+
+        my $cur_Size_Mb_ram = $info_list_href->[$_]->{0}->{'Density (Mb)'};
+        if ($Size_Mb_ram_min > $cur_Size_Mb_ram)
+        {
+            $Size_Mb_ram_min = $cur_Size_Mb_ram;
+        }
+
+        ### Find common ADMUX definition
+        if ($info_list_href->[$_]->{0}->{'ADMUX'} =~ /YES/i)
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot select ADMUX/AD-DEMUX at one time! Please change selected MCP!", __FILE__, __LINE__) if ($ADMUX_CS0 eq "NO");
+            $ADMUX_CS0 = "YES";
+        }
+        elsif ($info_list_href->[$_]->{0}->{'ADMUX'} =~ /NO/i)
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot select ADMUX/AD-DEMUX at one time! Please change selected MCP!", __FILE__, __LINE__) if ($ADMUX_CS0 eq "YES");
+            $ADMUX_CS0 = "NO";
+        }
+        if ($info_list_href->[$_]->{1}->{'ADMUX'} =~ /YES/i)
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot select ADMUX/AD-DEMUX at one time! Please change selected MCP!", __FILE__, __LINE__) if ($ADMUX_CS1 eq "NO");
+            $ADMUX_CS1 = "YES";
+        }
+        elsif ($info_list_href->[$_]->{1}->{'ADMUX'} =~ /NO/i)
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot select ADMUX/AD-DEMUX at one time! Please change selected MCP!", __FILE__, __LINE__) if ($ADMUX_CS1 eq "YES");
+            $ADMUX_CS1 = "NO";
+        }
+
+        ### Find common "DRAM" information
+        if ($info_list_href->[$_]->{1}->{'DRAM'} =~ /YES/i)
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot select DRAM/PSRAM at one time! Please change selected MCP!", __FILE__, __LINE__) if ($if_DRAM eq "NO");
+            $if_DRAM = "YES";
+        }
+        elsif ($info_list_href->[$_]->{1}->{'DRAM'} =~ /NO/i)
+        {
+            &error_handler("$CUSTOM_MEMORY_DEVICE_HDR_LOCAL: Cannot select DRAM/PSRAM at one time! Please change selected MCP!", __FILE__, __LINE__) if ($if_DRAM eq "YES");
+            $if_DRAM = "NO";
+        }
+
+        ### Find common "Comm. Series" information
+        if ((defined $info_list_href->[$_]->{1}->{'Comm. Series'}) and ($info_list_href->[$_]->{1}->{'Comm. Series'} ne '*'))
+        {
+            if ($Comm_Series_CS0_all eq '*')
+            {
+                $Comm_Series_CS0_all = $info_list_href->[$_]->{1}->{'Comm. Series'};
+            }
+            else
+            {
+                $Comm_Series_CS0_all .= " $info_list_href->[$_]->{1}->{'Comm. Series'}";
+            }
+        }
+
+        if ((defined $info_list_href->[$_]->{1}->{'Comm. Series'}) and ($info_list_href->[$_]->{1}->{'Comm. Series'} ne '*'))
+        {
+            if ($Comm_Series_CS1_all eq '*')
+            {
+                $Comm_Series_CS1_all = $info_list_href->[$_]->{1}->{'Comm. Series'};
+            }
+            else
+            {
+                $Comm_Series_CS1_all .= " $info_list_href->[$_]->{1}->{'Comm. Series'}";
+            }
+        }
+
+        ### Find common single/multiple-bank definition
+        if ($info_list_href->[$_]->{1}->{'Bank'} =~ /S/i)
+        {
+            $Bank_if_S = 'S';
+        }
+
+        ### Find common PBP information
+        if ($info_list_href->[$_]->{1}->{'PBP'}->{'Y / N'} =~ /Y/i)
+        {
+            $PBP_Y_N_if_Y = 'Y';
+        }
+        #print "PBP1: $info_list_href->[$_]->{1}->{'PBP'}->{'Y / N'}, PBP2: $info_list_href->[$_]->{1}->{'PBP'}->{'Size (Byte)'}\n"if ($DebugPrint == 1);
+
+        ### Find the minimum PBP size
+        my $cur_PBP_size_W = ($mem_dev_type eq 'NOR_RAM_MCP') ? ($info_list_href->[$_]->{0}->{'PBP'}->{'Size(W)'}) : ($info_list_href->[$_]->{1}->{'PBP'}->{'Size (Byte)'}/2);
+        if ($cur_PBP_size_W eq '*' or $cur_PBP_size_W eq 'x' or $cur_PBP_size_W eq ' ')
+        {
+            $cur_PBP_size_W = 0;
+        }
+        if ($PBP_Size_W_min > $cur_PBP_size_W)
+        {
+            $PBP_Size_W_min = $cur_PBP_size_W;
+        }
+
+       ### Find the max block size
+       if (defined $info_list_href->[$_]->{$LPSDRAM_CHIP_SELECT_LOCAL}->{'NAND Block Size(KB)'})
+       {
+           if ($max_nand_block_size < $info_list_href->[$_]->{$LPSDRAM_CHIP_SELECT_LOCAL}->{'NAND Block Size(KB)'} * 1024)
+           {
+               $max_nand_block_size = $info_list_href->[$_]->{$LPSDRAM_CHIP_SELECT_LOCAL}->{'NAND Block Size(KB)'} * 1024;
+           }
+       }
+
+    }##end for (1..$combo_mem_count)
+
+    ### Work around when the FAT base is larger than some flash size
+    if (($max_fat_base > $min_flash_size) or ($max_fat_base > $min_small_block_start))
+    {
+        $max_fat_base = 0;
+        for (1..$combo_mem_count)
+        {
+            if (($info_list_href->[$_]->{1}->{'Default FAT Base'} < $min_flash_size) and ($info_list_href->[$_]->{1}->{'Default FAT Base'} < $min_small_block_start) and ($max_fat_base < $info_list_href->[$_]->{1}->{'Default FAT Base'}))
+            {
+                $max_fat_base = $info_list_href->[$_]->{1}->{'Default FAT Base'};
+            }
+        }
+    }
+    $min_fat_space = $min_flash_size - $max_fat_base;
+
+    for (1..$combo_mem_count)
+    {
+        my $cur_Size_Mb_ram;
+        $cur_Size_Mb_ram = $info_list_href->[$_]->{0}->{'Density (Mb)'};
+        if ($Size_Mb_ram_min > $cur_Size_Mb_ram)
+        {
+            $Size_Mb_ram_min = $cur_Size_Mb_ram;
+        }
+    }
+
+    $info_output_href->{0}->{'Flash Size'} = $min_flash_size;
+    $info_output_href->{0}->{'NAND Block Size(KB)'} = $max_nand_block_size / 1024;
+    $info_output_href->{0}->{'Small Block Start'} = $min_small_block_start;
+    $info_output_href->{0}->{'Default FAT Base'} = $max_fat_base;
+    $info_output_href->{0}->{'Default FAT Size'} = $min_fat_space;
+    $info_output_href->{1}->{'Size (Mb)'} = $Size_Mb_ram_min;
+    $info_output_href->{0}->{'ADMUX'} = $ADMUX_CS0;
+    $info_output_href->{1}->{'ADMUX'} = $ADMUX_CS1;
+    $info_output_href->{1}->{'DRAM'} = $if_DRAM;
+    $info_output_href->{0}->{'Comm. Series'} = $Comm_Series_CS0_all;
+    $info_output_href->{1}->{'Comm. Series'} = $Comm_Series_CS1_all;
+    $info_output_href->{0}->{'Bank'} = $Bank_if_S;
+    $info_output_href->{0}->{'PBP'}->{'Y / N'} = $PBP_Y_N_if_Y;
+    $info_output_href->{0}->{'PBP'}->{'Size(W)'} = $PBP_Size_W_min;
+
+
+    #print "FlashSize[0] is $info_output_href->{0}->{'Flash Size'}, FlashSize[1] is $info_output_href->{1}->{'Flash Size'}\n"if ($DebugPrint == 1);
+    #print "NANDBlockSize[0] is $info_output_href->{0}->{'NAND Block Size(KB)'}, NANDBlockSize[1] is $info_output_href->{1}->{'NAND Block Size(KB)'}\n"if ($DebugPrint == 1);
+    #print "SmallBlockStart[0] is $info_output_href->{0}->{'Small Block Start'}, SmallBlockStart[1] is $info_output_href->{1}->{'Small Block Start'}\n"if ($DebugPrint == 1);
+    #print "DefaultFATBase[0] is $info_output_href->{0}->{'Default FAT Base'}, DefaultFATBase[1] is $info_output_href->{1}->{'Default FAT Base'}\n"if ($DebugPrint == 1);
+    #print "DefaultFATSize[0] is $info_output_href->{0}->{'Default FAT Size'}, DefaultFATSize[1] is $info_output_href->{1}->{'Default FAT Size'}\n"if ($DebugPrint == 1);
+    #print "Size[0] is $info_output_href->{0}->{'Size (Mb)'}, Size[1] is $info_output_href->{1}->{'Size (Mb)'}\n"if ($DebugPrint == 1);
+    #print "ADMUX[0] is $info_output_href->{0}->{'ADMUX'}, ADMUX[1] is $info_output_href->{1}->{'ADMUX'}\n"if ($DebugPrint == 1);
+    #print "DRAM[0] is $info_output_href->{0}->{'DRAM'}, DRAM[1] is $info_output_href->{1}->{'DRAM'}\n"if ($DebugPrint == 1);
+    #print "CommSeries[0] is $info_output_href->{0}->{'Comm. Series'}, CommSeries[1] is $info_output_href->{1}->{'Comm. Series'}\n"if ($DebugPrint == 1);
+    #print "Bank[0] is $info_output_href->{0}->{'Bank'}, Bank[1] is $info_output_href->{1}->{'Bank'}\n"if ($DebugPrint == 1);
+    #print "PBPYN[0] is $info_output_href->{0}->{'PBP'}->{'Y / N'}, PBPYN[1] is $info_output_href->{1}->{'PBP'}->{'Y / N'}\n"if ($DebugPrint == 1);
+    #print "PBPSize[0] is $info_output_href->{0}->{'PBP'}->{'Size(W)'}, PBPSize[1] is $info_output_href->{1}->{'PBP'}->{'Size(W)'}\n"if ($DebugPrint == 1);
+}
+#****************************************************************************
+# subroutine:  get_common_MDL_Geo_info
+# input:       $info_list_href:         input list reference of list of MDL info
+#              $combo_count:            number of memory devices selected
+#              $mem_dev_type:           MEMORY_DEVICE_TYPE
+#              $baseaddr:               base address where the geometry list starts
+#              $endaddr:                end address where the geometry list ends
+#              $region_info_size_href:  list reference of common RegionInfo of the specified area (Size)
+#              $region_info_count_href: list of common RegionInfo of the specified area (Count)
+#              $bank_info_size_href:    list of common BankInfo of the specified area (Size)
+#              $bank_info_count_href:   list of common BankInfo of the specified area (Count)
+#****************************************************************************
+sub get_common_MDL_Geo_info
+{
+    my ($info_list_href, $combo_count, $mem_dev_type, $baseaddr, $endaddr, $region_info_size_href, $region_info_count_href, $bank_info_size_href, $bank_info_count_href) = @_;
+    my ($prev_addr, $curr_addr, $idx);
+
+    ### Get common RegionInfo
+    $prev_addr = $baseaddr;
+    $curr_addr = $baseaddr;
+    $idx = 0;
+
+    while ($curr_addr < $endaddr)  # traverse till the endaddr
+    {
+        ### Find the largest block/bank offset from the current address to the next boundary
+        ### Stop if the end of BankInfo or RegionInfo has been reached
+        my $largest_offset = 0;  # largest offset from curr_addr to the next block/bank boundary
+        my $common_size = 0;  # final(common) block/bank size of this round
+        my $size;  # current offset from curr_addr to the next block/bank boundary
+        my $cur_combo_idx;
+
+        for (1..$combo_count)
+        {
+            $cur_combo_idx = $_;
+            my $cur_block_str = $info_list_href->[$_]->{1}->{'Device Geometry'}->{'Block Info.'};
+            my @cur_blocks = &split_info($cur_block_str);  # list of Block Info.
+            my @cur_entire_regions = &convert_blocks_to_regions(\@cur_blocks, $endaddr);  # list of Region Info. (of the entire flash)
+            my (@cur_entire_regions_size, @cur_entire_regions_count);
+            &convert_geo_hash_from_list(\@cur_entire_regions, \@cur_entire_regions_size, \@cur_entire_regions_count);
+            ### Get the offset to the next boundary
+            $size = &get_next_Geo_boundary(\@cur_entire_regions_size, \@cur_entire_regions_count, $curr_addr);
+            last if ($size == 0);  # reach the end of RegionInfo/BankInfo
+            if ($size > $largest_offset)
+            {
+                $largest_offset = $size;
+            }
+        }
+        last if ($cur_combo_idx != $combo_count);  # early break in for loop represents end of RegionInfo/BankInfo is reached
+
+        ### Check whether curr_address+largest_offset is still on the boundaries of all MCPs
+        for (1..$combo_count)
+        {
+            $cur_combo_idx = $_;
+            my $cur_block_str = $info_list_href->[$_]->{1}->{'Device Geometry'}->{'Block Info.'};
+            my @cur_blocks = &split_info($cur_block_str);  # list of Block Info.
+            my @cur_entire_regions = &convert_blocks_to_regions(\@cur_blocks, $endaddr);  # list of Region Info. (of the entire flash)
+            my (@cur_entire_regions_size, @cur_entire_regions_count);
+            &convert_geo_hash_from_list(\@cur_entire_regions, \@cur_entire_regions_size, \@cur_entire_regions_count);
+            last if (&query_if_Geo_boundary(\@cur_entire_regions_size, \@cur_entire_regions_count, $curr_addr+$largest_offset) != 1);
+        }
+        # The address is not on the boundaries of all MCPs, need to go to the next address
+        # The next address is curr_addr + largest_offset
+        if ($cur_combo_idx != $combo_count)
+        {
+            $curr_addr += $largest_offset;
+            next;
+        }
+
+        ### Add the address into the output RegionInfo/BankInfo
+        # Get the size of common block/bank size
+        $common_size = $curr_addr + $largest_offset - $prev_addr;
+        if ($prev_addr == $baseaddr)  # the first entry
+        {
+            $region_info_size_href->[0] = $common_size;
+            $region_info_count_href->[0] = 1;
+        }
+        elsif ($common_size == $region_info_size_href->[$idx])  # the size is identical to the last bank we added --> count + 1
+        {
+            $region_info_count_href->[$idx]++;
+        }
+        else  # tje soze os different from the last bank we added --> append new entry
+        {
+            $idx++;
+            $region_info_size_href->[$idx] = $common_size;
+            $region_info_count_href->[$idx] = 1;
+        }
+
+        ### Increment the current address and then perform the loop again
+        $curr_addr += $largest_offset;
+        $prev_addr = $curr_addr;
+    }
+    print "pass block info\n";
+    ### Get common BankInfo
+    $prev_addr = $baseaddr;
+    $curr_addr = $baseaddr;
+    $idx = 0;
+    while ($curr_addr < $endaddr)  # traverse till the endaddr
+    {
+        ### Find the largest block/bank offset from the current address to the next boundary
+        ### Stop if the end of BankInfo or RegionInfo has been reached
+        my $largest_offset = 0;  # largest offset from curr_addr to the next block/bank boundary
+        my $common_size = 0;  # final(common) block/bank size of this round
+        my $size;  # current offset from curr_addr to the next block/bank boundary
+        my $cur_combo_idx;
+
+        for (1..$combo_count)
+        {
+            $cur_combo_idx = $_;
+            my $cur_bank_str = $info_list_href->[$_]->{1}->{'Device Geometry'}->{'Bank Info.'};
+            my @cur_banks = &split_info($cur_bank_str);  # list of Bank Info.
+            my (@cur_banks_size, @cur_banks_count);
+            &convert_geo_hash_from_list(\@cur_banks, \@cur_banks_size, \@cur_banks_count);
+
+            ### Get the offset to the next boundary
+            $size = &get_next_Geo_boundary(\@cur_banks_size, \@cur_banks_count, $curr_addr);
+            last if ($size == 0);  # reach the end of RegionInfo/BankInfo
+            if ($size > $largest_offset)
+            {
+                $largest_offset = $size;
+            }
+        }
+        last if ($cur_combo_idx != $combo_count);  # early break in for loop represents end of RegionInfo/BankInfo is reached
+
+        ### Check whether curr_address+largest_offset is still on the boundaries of all MCPs
+        for (1..$combo_count)
+        {
+            $cur_combo_idx = $_;
+            my $cur_bank_str = $info_list_href->[$_]->{1}->{'Device Geometry'}->{'Bank Info.'};
+            my @cur_banks = &split_info($cur_bank_str);  # list of Bank Info.
+            my (@cur_banks_size, @cur_banks_count);
+            &convert_geo_hash_from_list(\@cur_banks, \@cur_banks_size, \@cur_banks_count);
+            last if (&query_if_Geo_boundary(\@cur_banks_size, \@cur_banks_count, $curr_addr+$largest_offset) != 1);
+        }
+        # The address is not on the boundaries of all MCPs, need to go to the next address
+        # The next address is curr_addr + largest_offset
+        if ($cur_combo_idx != $combo_count)
+        {
+            $curr_addr += $largest_offset;
+            next;
+        }
+
+        ### Add the address into the output RegionInfo/BankInfo
+        # Get the size of common block/bank size
+        $common_size = $curr_addr + $largest_offset - $prev_addr;
+
+        if ($prev_addr == $baseaddr)  # the first entry
+        {
+            $bank_info_size_href->[0] = $common_size;
+            $bank_info_count_href->[0] = 1;
+        }
+        elsif ($common_size == $bank_info_size_href->[$idx])  # the size is identical to the last bank we added --> count + 1
+        {
+            $bank_info_count_href->[$idx]++;
+        }
+        else  # tje soze os different from the last bank we added --> append new entry
+        {
+            $idx++;
+            $bank_info_size_href->[$idx] = $common_size;
+            $bank_info_count_href->[$idx] = 1;
+        }
+
+        ### Increment the current address and then perform the loop again
+        $curr_addr += $largest_offset;
+        $prev_addr = $curr_addr;
+    }
+}
+
+#****************************************************************************
+# subroutine:  get_next_Geo_boundary
+# input:       $geo_size_list_href:  input list reference of geometry (Size list)
+#              $geo_count_list_href: input list reference of geometry (Size list)
+#              $addr:                current address from which to query next boundary
+# output:      $size:                size from current address to the next boundary
+#****************************************************************************
+sub get_next_Geo_boundary
+{
+    my ($geo_size_list_href, $geo_count_list_href, $addr) = @_;
+    my $size;
+
+    ### for all {Size, Count} pairs
+    for (0..$#$geo_size_list_href)
+    {
+        ### Treat the address as a pile of blocks. Remove them region by region.
+        if (($geo_size_list_href->[$_]*$geo_count_list_href->[$_]) <= $addr)
+        {
+            $addr -= $geo_size_list_href->[$_]*$geo_count_list_href->[$_];
+            next;
+        }
+
+        ### Blocks that cannot fit-into one region.
+        # 1. We have found the region that addr belongs to.
+        # 2. The addr is aligned, if (addr % Size[$_]) equals 0.
+        # 3. The offset to the next boundary is (Size[$_] - (addr % Size[$_]))
+        return ($geo_size_list_href->[$_] - ($addr % $geo_size_list_href->[$_]));
+    }
+
+    return 0;
+}
+
+#****************************************************************************
+# subroutine:  query_if_Geo_boundary
+# input:       $geo_size_list_href:  input list reference of geometry (Size list)
+#              $geo_count_list_href: input list reference of geometry (Size list)
+#              $addr:                address to be checked whether it is on the boundary or not
+# output:      $is_boundary:         1 if the queried address is on the boundary of all MCPs
+#****************************************************************************
+sub query_if_Geo_boundary
+{
+    my ($geo_size_list_href, $geo_count_list_href, $addr) = @_;
+    my $ret;
+
+    ### for all {Size, Count} pairs
+    for (0..$#$geo_size_list_href)
+    {
+        ### Treat the address as a pile of blocks. Remove them region by region.
+        if (($geo_size_list_href->[$_]*$geo_count_list_href->[$_]) <= $addr)
+        {
+            $addr -= $geo_size_list_href->[$_]*$geo_count_list_href->[$_];
+            next;
+        }
+
+        ### Blocks that cannot fit-into one region.
+        # 1. We have found the region that addr belongs to.
+        # 2. The addr is aligned, if (addr % Size[$_]) equals 0.
+        # 3. The offset to the next boundary is (Size[$_] - (addr % Size[$_]))
+        if (($addr % $geo_size_list_href->[$_]) != 0)
+        {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+#****************************************************************************
+# subroutine:  split_info
+# return:      List of RegionInfo/BlockInfo/BankInfo
+# input:       $info: Excel value to be split
+#****************************************************************************
+sub split_info
+{
+    my ($info) = @_;
+    my $ret_str;
+    my @ret_info;
+
+    if ($info eq '*')
+    {
+        push @ret_info, $info;
+        return @ret_info;
+    }
+
+    while ($info =~ /\{(0x\w+)\s*,\s*(\w+)\}/)
+    {
+         $ret_str = "{" . $1 . "," . $2 . "}";
+         push @ret_info, $ret_str;
+         $info = $';
+    }
+    return @ret_info;
+}
+
+#****************************************************************************
+# subroutine:  split_sfi_driving
+# return:      List of SFI driving
+# input:       $driving_str: Excel value to be split
+#****************************************************************************
+sub split_sfi_driving
+{
+    my ($driving_str) = @_;
+    my @ret_driving;
+
+    $driving_str =~ s/\{//;  # remove parentheses
+    $driving_str =~ s/\}//;  # remove parentheses
+    $driving_str =~ s/\s+//g;  # remove spaces
+    ### parse the driving string
+    my $saved_sep = $/;
+    undef $/;
+    @ret_driving = split(/\,/, $driving_str);
+    $/ = $saved_sep;
+
+    return @ret_driving;
+}
+
+#****************************************************************************
+# subroutine:  split_sfi_command
+# return:      List of SFI commands
+# input:       $command_str: Excel value to be split
+#****************************************************************************
+sub split_sfi_command
+{
+    my ($command_str) = @_;
+    my @ret_command;
+
+    while ($command_str =~ /(\{.+\})/)
+    {
+        my $tmp_str = $1;
+        $command_str = $';
+        $tmp_str =~ s/\{//;  # remove parentheses
+        $tmp_str =~ s/\}//;  # remove parentheses
+        $tmp_str =~ s/\s+//g;  # remove spaces
+        ### parse the driving string
+        my $saved_sep = $/;
+        undef $/;
+        my @tmp_list = split(/\,/, $tmp_str);
+        $/ = $saved_sep;
+
+        ### for commands {SPI, 0x35}, output "SPI, 1, 0x35", where 1 is the number of commands
+        if ($tmp_list[0] eq 'SPI' or $tmp_list[0] eq 'QPI')
+        {
+            push @ret_command, $tmp_list[0];
+            push @ret_command, $#tmp_list;
+            for (1..$#tmp_list)
+            {
+                push @ret_command, $tmp_list[$_];
+            }
+        }
+        else
+        {
+            &error_handler("$MEMORY_DEVICE_LIST_XLS_E: Unknown SFI commands $command_str!", __FILE__, __LINE__);
+        }
+    }
+
+    return @ret_command;
+}
+
+#****************************************************************************
+# subroutine:  region_info
+# return:      template of part "configure flash memory for FAT"
+#****************************************************************************
+sub region_info
+{
+    my ($region_size_href, $region_count_href, $naming, $newline) = @_;
+    my $region_info_lines;
+
+    for (0..$#$region_size_href)
+    {
+        my $tmp_size = sprintf("0x%X", $region_size_href->[$_]);
+        my $tmp_count = sprintf("%d", $region_count_href->[$_]);
+        if ($_ == $#$region_size_href)
+        {
+            $region_info_lines .=  "   \{$tmp_size, $tmp_count\},\n";
+        }
+        else
+        {
+            $region_info_lines .=  "   \{$tmp_size, $tmp_count\}, \\$newline\n";
+        }
+    }
+    chomp $region_info_lines;
+
+    if (!defined $naming)
+    {
+        return $region_info_lines;
+    }
+
+    ###
+    my $template;
+    if (defined $newline)
+    {
+        $template .= <<"__TEMPLATE";
+$region_info_lines
+__TEMPLATE
+        return $template;
+    }
+    my $naming_str = sprintf("$naming\[\]");
+    if ($naming eq 'oriRegionInfo')
+    {
+        $template .= <<"__TEMPLATE";
+FLASH_REGIONINFO_VAR_MODIFIER FlashRegionInfo $naming_str =
+{
+$region_info_lines
+   EndoriRegionInfo
+};
+__TEMPLATE
+    }
+    else
+    {
+        $template .= <<"__TEMPLATE";
+#define $naming $region_info_lines
+__TEMPLATE
+    }
+    chomp $template;
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  block_info
+# return:      template of part "configure flash memory for FAT"
+# input:       $tmp_blocks: Block Info
+#              $newline:    whether there exists newline character and comments in the output Block Info string
+#****************************************************************************
+sub block_info
+{
+    my ($block_start_href, $block_size_href, $newline) = @_;
+    my $block_info_lines;
+
+    for (0..$#$block_start_href)
+    {
+        my $tmp_start = sprintf("0x%X", $block_start_href->[$_]);
+        my $tmp_size = sprintf("0x%X", $block_size_href->[$_]);
+        if ($_ == $#$block_start_href)
+        {
+            $block_info_lines .=  "   \{$tmp_start, $tmp_size\},\n";
+        }
+        else
+        {
+            $block_info_lines .=  "   \{$tmp_start, $tmp_size\}, $newline\n";
+        }
+    }
+    chomp $block_info_lines;
+
+    ###
+    my $template;
+    if (defined $newline)
+    {
+        $template .= <<"__TEMPLATE";
+$block_info_lines
+__TEMPLATE
+    }
+    else
+    {
+        $template .= <<"__TEMPLATE";
+/*
+FLASH_REGIONINFO_VAR_MODIFIER FlashBlockTBL NOTREADYYET[] =
+{
+$block_info_lines
+   EndBlockInfo
+};
+*/
+__TEMPLATE
+   }
+
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  bank_info
+# return:      template of part "configure flash memory for FAT"
+# input:       $bank_size_href:  Bank Info size
+#              $bank_count_href: Bank Info count
+#              $offset:          the offset to start counting bank
+#              $target_size:     the size of the FAT
+#              $newline:         whether there exists newline character and comments in the output Bank Info string
+#****************************************************************************
+sub bank_info
+{
+    my ($bank_size_href, $bank_count_href, $offset, $target_size, $newline) = @_;
+    my $template;
+
+    if (defined $newline)
+    {
+        for (0..$#$bank_size_href)
+        {
+            my $tmp_size = sprintf("0x%X", $bank_size_href->[$_]);
+            my $tmp_count = sprintf("%d", $bank_count_href->[$_]);
+            if ($_ == $#$bank_size_href)
+            {
+                $template .= "   \{$tmp_size, $tmp_count\},\n";
+            }
+            else
+            {
+                $template .= "   \{$tmp_size, $tmp_count\}, $newline\n";
+            }
+        }
+        return $template;
+    }
+    for (0..$#$bank_size_href)
+    {
+        my $tmp_size = sprintf("0x%X", $bank_size_href->[$_]);
+        my $tmp_count = sprintf("%d", $bank_count_href->[$_]);
+
+        my $segment = ( hex($tmp_size) * $tmp_count );
+        if ($offset <= 0)
+        # Bank already in
+        {
+            if ($target_size >= $segment)
+            # Bank full in-used
+            {
+                $template .= sprintf("   \{ 0x%X, %d \}, %s \\ \n", hex($tmp_size), $tmp_count);
+            }
+            elsif ($target_size > 0)
+            # Bank partial used
+            {
+                my $tmp = int($target_size / hex($tmp_size));;
+                if ( int($target_size / hex($tmp_size)) > 0 )
+                {
+                    $template .= sprintf("   \{ 0x%X, %d \}, %s\\\n", hex($tmp_size), int($target_size / hex($tmp_size)) );
+                    $target_size    -= hex($tmp_size) * int($target_size / hex($tmp_size));
+                }
+                if ($target_size > 0)
+                {
+                    $template .= sprintf("   \{ 0x%X, %d \}, %s\\\n", $target_size , 1 );
+                }
+            }
+            else
+            # Bank discarded
+            {
+            }
+            $target_size -= $segment;
+        }
+        elsif ($offset < $segment)
+        # Segment cross the FAT Baseaddr
+        {
+            my $c = $tmp_count;
+            while ($offset >= hex($tmp_size))
+            {
+                $c--;
+                $offset -= hex($tmp_size);
+            }
+            # Bank partial in-used
+            if ($offset > 0 and (hex($tmp_size) - $offset) >= $target_size)
+            {
+                $template .= sprintf("   \{ 0x%X, %d \}, %s\\\n", $target_size, 1);
+                $target_size    -= hex($tmp_size);
+                $c--;
+            }
+            elsif ($offset > 0)
+            {
+                $template .= sprintf("   \{ 0x%X, %d \}, %s\\\n", (hex($tmp_size) - $offset), 1);
+                $target_size    -= (hex($tmp_size) - $offset);
+                $c--;
+            }
+            # discount $offset and make $offset to negative value
+            $offset   -= hex($tmp_size);
+
+            next if ($c == 0); # already last one bank on the FAT boundary
+            if ($target_size >= (hex($tmp_size) * $c))
+            {
+                $template .= sprintf("   \{ 0x%X, %d \}, %s \\\n", hex($tmp_size), $c);
+                $target_size    -= (hex($tmp_size) * $c);
+            }
+            elsif ($target_size > 0)
+            {
+                if ( int($target_size / hex($tmp_size)) > 0 )
+                {
+                    $template .= sprintf("   \{ 0x%X, %d \}, %s \\\n", hex($tmp_size), int($target_size / hex($tmp_size)) );
+                    $target_size    -= hex($1) * int($target_size / hex($tmp_size));
+                }
+                if ($target_size > 0)
+                {
+                    $template .= sprintf("   \{ 0x%X, %d \}, %s \\\n", $target_size , 1 );
+                }
+            }
+        }
+        # No output and down counting to reach FAT Baseaddr
+        else
+        {
+            $offset -= $segment;
+        }
+    }
+
+    $template       = "   {x , x},\n" if not defined $template;
+    chomp($template);
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  convert_blocks_to_regions
+# return:      RegionInfo of the entire flash, in {size, count} format instead of {start, size} format
+# input:       $tmp_blocks: input list of blocks
+# output:      @regions:    output list of regions
+#****************************************************************************
+sub convert_blocks_to_regions
+{
+    my ($tmp_blocks, $endaddr) = @_;
+    my @regions;
+    my ($cur_reg_start, $cur_block_size, $prev_reg_start, $prev_block_size);
+
+    for (0..$#$tmp_blocks)
+    {
+        if ($tmp_blocks->[$_] =~ /\{(\w+)\s*,\s*(\w+)\}/) # match {0x20000, 7}
+        {
+            $cur_reg_start = hex($1);
+            $cur_block_size = hex($2);
+        }
+        if ($_ > 0)
+        {
+            if ($tmp_blocks->[$_-1] =~ /\{(\w+)\s*,\s*(\w+)\}/) # match {0x20000, 7}
+            {
+                $prev_reg_start = hex($1);
+                $prev_block_size = hex($2);
+            }
+            my $tmp_region_info_line = sprintf("\{0x%X,%d\}", $prev_block_size, ($cur_reg_start-$prev_reg_start)/$prev_block_size);
+            push @regions, $tmp_region_info_line;
+        }
+    }
+    my $tmp_region_info_line;
+    if ($#$tmp_blocks >= 0)
+    {
+        $tmp_region_info_line = sprintf("\{0x%X,%d\}", $cur_block_size, ($endaddr-$cur_reg_start)/$cur_block_size);
+    }
+    push @regions, $tmp_region_info_line;
+
+    return @regions;
+}
+
+#****************************************************************************
+# subroutine:  convert_regions_to_blocks
+# return:      BlockInfo of the input RegionInfo, in {start, size} format instead of {size, count} format
+# input:       $regions_start:          start address of region info
+#              $tmp_regions_size_list:  input list of regions size
+#              $tmp_regions_count_list: input list of regions count
+# output:      $output_blocks_start_list: output list of blocks start
+#              $output_blocks_size_list:  output list of blocks size
+#****************************************************************************
+sub convert_regions_to_blocks
+{
+    my ($regions_start, $tmp_regions_size_list, $tmp_regions_count_list, $output_blocks_start_list, $output_blocks_size_list) = @_;
+    my @ret_blocks;
+
+    my $idx = 0;
+    $output_blocks_start_list->[0] = $regions_start;
+    for (0..$#$tmp_regions_size_list)
+    {
+        $idx = $_;
+        # the block size is identical to the region block size
+        $output_blocks_size_list->[$_] = $tmp_regions_size_list->[$_];
+        # the offset equals to the accumulated size of former regions
+        if ($_ != $#$tmp_regions_size_list)
+        {
+            $output_blocks_start_list->[$_+1] = $output_blocks_start_list->[$_] + ($tmp_regions_size_list->[$_]*$tmp_regions_count_list->[$_]);
+        }
+    }
+}
+
+#****************************************************************************
+# subroutine:  convert_geo_hash_from_list
+# return:      lists of flash geometry in {size, count} format
+# input:       $list_href:                  input list of blocks/banks
+#              $geo_size_list_output_href:  Size part of flash geometry pairs
+#              $geo_count_list_output_href: Count part of flash geometry pairs
+#****************************************************************************
+sub convert_geo_hash_from_list
+{
+    my ($list_href, $geo_size_list_output_href, $geo_count_list_output_href) = @_;
+
+    for (0..$#$list_href)
+    {
+        if ($list_href->[$_] =~ /\{(\w+)\s*,\s*(\w+)\}/) # match {0x20000, 7}
+        {
+            $geo_size_list_output_href->[$_] = hex($1);
+            $geo_count_list_output_href->[$_] = $2;
+        }
+    }
+}
+
+
+#****************************************************************************
+# subroutine:  SCHEME Configure Routines :: Query Flash Block Size
+# input:       Flash Offset Address
+# Output:      Flash block size
+#****************************************************************************
+sub config_query_flash_block_sz
+{
+    my ($offset, $COMM_MDL_INFO_LOCAL, $ENTIRE_BLOCK_INFO_START_LIST_LOCAL, $ENTIRE_BLOCK_INFO_SIZE_LIST_LOCAL) = @_;
+
+    &error_handler("tools\\emigenflash.pl: Query Block Size at $offset larger than available size!", __FILE__, __LINE__) if ($offset > $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+
+    for (0..$#$ENTIRE_BLOCK_INFO_START_LIST_LOCAL)
+    {
+        return ($ENTIRE_BLOCK_INFO_SIZE_LIST_LOCAL->[$#$ENTIRE_BLOCK_INFO_START_LIST_LOCAL-$_]) if ($offset >= $ENTIRE_BLOCK_INFO_START_LIST_LOCAL->[$#$ENTIRE_BLOCK_INFO_START_LIST_LOCAL-$_]);
+    }
+    &error_handler("tools\\emigenflash.pl: Unreachable!", __FILE__, __LINE__);
+}
+
+#****************************************************************************
+# subroutine:  flash_opt_h_file_body
+# return:      flash opt header file
+#****************************************************************************
+sub flash_opt_gen_h_file_body
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL, $CUSTOM_MEMORY_DEVICE_HDR_LOCAL, $THEMF_LOCAL, $entire_bank_info_size_list_LOCAL, $entire_bank_info_count_list_LOCAL, $entire_block_info_start_list_LOCAL, $entire_block_info_size_list_LOCAL, $entire_region_info_size_list_LOCAL, $entire_region_info_count_list_LOCAL) = @_;
+    my $current_nor_opt;
+    ##if (($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} ne 'LPSDRAM') and ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} ne 'LPDDR') and ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} ne 'LPDDR2'))
+    #if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} eq 'LPDDR2')
+    #if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6291')  # FIXME
+    {
+        if ($COMM_MDL_INFO_LOCAL->{0}->{'PBP'}->{'Y / N'} =~ /Y/i)
+        {
+            $current_nor_opt .= "#define __PAGE_BUFFER_PROGRAM__\n";
+        }
+
+        #if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} eq 'SERIAL_FLASH')
+        if ($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+        {
+            $current_nor_opt .= "#define __SERIAL_FLASH__\n";
+        }
+
+        ### Define flash types
+        my $cnt_sibley = 0;
+        for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+        {
+            if ($MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Comm. Series'} =~ /(\w*)/i)
+            {
+                my $tmp_series = $1;
+                if ($current_nor_opt =~ /$tmp_series/)
+                {
+                }
+                else
+                {
+                    #if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} eq 'SERIAL_FLASH')
+                    if ($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+                    {
+                        $current_nor_opt .= "#define SF_DAL_" . $tmp_series . "\n";
+                    }
+                    else
+                    {
+                        $current_nor_opt .= "#define NOR_FLASH_TYPE_" . $tmp_series . "\n";
+                        if ($tmp_series eq 'INTEL_SIBLEY')
+                        {
+                            $cnt_sibley++;
+                        }
+                    }
+                }
+            }
+        }
+        if ($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+        {
+            if ($cnt_sibley != $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+            {
+                $current_nor_opt .= "#define __NON_INTEL_SIBLEY__\n";
+            }
+
+
+            $current_nor_opt .= "\n";
+            $current_nor_opt .= "\n";
+
+            if ($COMM_MDL_INFO_LOCAL->{0}->{'Bank'} =~ /M/i)
+            {
+                $current_nor_opt .= "#define __MULTI_BANK_NOR_DEVICE__\n";
+            }
+            elsif ($COMM_MDL_INFO_LOCAL->{0}->{'Bank'} =~ /S/i)
+            {
+                $current_nor_opt .= "#define __SINGLE_BANK_NOR_DEVICE__\n";
+            }
+        }
+    }
+
+    if (($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'FALSE') and ($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'FALSE'))
+    {
+        $current_nor_opt .= "#define __SMART_PHONE_PLATFORM__\n";##for Not Run CkSysDrv.pl, will check FAT valid, but no flash now
+    }
+
+    ###
+    my $pbp_size            = ($COMM_MDL_INFO_LOCAL->{0}->{'PBP'}->{'Y / N'} =~ /Y/i)
+                            ?  $COMM_MDL_INFO_LOCAL->{0}->{'PBP'}->{'Size(W)'}
+                            : 0;
+
+    ###
+    my $nor_block_size_str = sprintf("0x%X", $nor_block_size);
+
+    ###
+    my $nand_total_size = 0;
+    my $nand_block_size = 0;
+
+    #if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6291')  # FIXME
+    {
+        if ($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'TRUE')
+        {
+            $nand_total_size = ($COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'} == 0xffffffff)
+                             ? 0
+                             : ($COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'} / (1024*1024));
+            $nand_block_size = $COMM_MDL_INFO_LOCAL->{0}->{'NAND Block Size(KB)'};
+        }
+    }
+
+    my $base_addr_string    = sprintf("0x%08X", $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Base'});
+    my $fat_space_string    = sprintf("0x%08X", $COMM_MDL_INFO_LOCAL->{0}->{'Default FAT Size'});
+
+    ###
+    my $fota_check;
+    if ($MAKEFILE_OPTIONS_LOCAL->{'fota_enable'} eq 'FOTA_DM')
+    {
+           $fota_check = <<"__TEMPLATE";
+#ifndef __FOTA_DM__
+    #error "$THEMF: Error! FOTA_ENABLE should be set to FOTA_DM!"
+#endif /* __FOTA_DM__ */
+__TEMPLATE
+    }
+    else
+    {
+           $fota_check = <<"__TEMPLATE";
+#ifdef __FOTA_DM__
+    #error "$THEMF_LOCAL: Error! FOTA_ENABLE should not be defined!"
+#endif /* __FOTA_DM__ */
+__TEMPLATE
+    }
+
+    ###
+    my ($fota_nor_region_def, $fota_nor_block_def, $fota_nor_bank_def);
+    if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} eq 'NOR_RAM_MCP')
+    {
+        $fota_nor_region_def = &region_info($entire_region_info_size_list_LOCAL, $entire_region_info_count_list_LOCAL, undef, '\\');
+        $fota_nor_block_def = &block_info($entire_block_info_start_list_LOCAL, $entire_block_info_size_list_LOCAL, '\\');
+        $fota_nor_bank_def = &bank_info($entire_bank_info_size_list_LOCAL, $entire_bank_info_count_list_LOCAL, 0, $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'}, '\\');
+    }
+
+    ###
+    my $template = <<"__TEMPLATE";
+
+/*
+ *******************************************************************************
+ PART 1:
+   FLASH CONFIG Options Definition here
+ *******************************************************************************
+*/
+$current_nor_opt
+
+/*
+ *******************************************************************************
+ PART 2:
+   FLASH FDM FEATURE CONFIG PARAMETERS translated from Manual custom_Memorydevice.h
+ *******************************************************************************
+*/
+
+#define BUFFER_PROGRAM_ITERATION_LENGTH  ($pbp_size)
+
+/*
+ *******************************************************************************
+ PART 3:
+   FLASH GEOMETRY translated from MEMORY DEVICE DATABASE
+ *******************************************************************************
+*/
+
+/* NOR flash maximum block size (Byte) in file system region */
+#define NOR_BLOCK_SIZE  $nor_block_size_str
+
+/* NAND flash total size (MB). PLEASE configure it as 0 if it is unknown. */
+#define NAND_TOTAL_SIZE $nand_total_size
+
+/* NAND flash block size (KB). PLEASE configure it as 0 if it is unknown. */
+#define NAND_BLOCK_SIZE $nand_block_size
+
+/*
+ *******************************************************************************
+ PART 4:
+   FLASH FAT CONFIG translated from Manual custom_Memorydevice.h
+ *******************************************************************************
+*/
+
+    #define NOR_FLASH_BASE_ADDRESS_DEFAULT     ($base_addr_string)
+    #define NOR_ALLOCATED_FAT_SPACE_DEFAULT    ($fat_space_string)
+    #define FOTA_DM_FS_OFFSET $nfb_fat_size_offset
+    #define FOTA_DM_FS_SECTOR_OFFSET $nfb_partition_sector_offset
+
+/*
+ *******************************************************************************
+ PART 6:
+   FOTA UPDATABLE FLASH AREA
+ *******************************************************************************
+*/
+
+$fota_check
+
+#define CONFIG_FOTA_NOR_REGION_DEF \\
+$fota_nor_region_def
+
+#define CONFIG_FOTA_NOR_BLOCK_DEF \\
+$fota_nor_block_def
+
+#define CONFIG_FOTA_NOR_BANK_DEF \\
+$fota_nor_bank_def
+
+__TEMPLATE
+
+  return $template;
+}
+
+#****************************************************************************
+# subroutine:  combo_flash_config_h_file_body
+# return:
+#****************************************************************************
+sub combo_flash_config_h_file_body
+{
+	  my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL) = @_;
+    ### There should be totally 8 BlockInfo and BankInfo
+    my $MAX_BLOCK_INFO = 8;
+    my $MAX_BANK_INFO = 8;
+
+    ### Fill-in the information of each memory
+    my $combo_mem_info_struct;
+    for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+    {
+        my $comma;
+        my $cmem_fdm_type;
+        my $pbp_size;
+        my $cur_block_str;
+        my @cur_blocks;
+        my $cur_bank_str;
+        my @cur_banks;
+        my $cur_uni_block_str;
+        my $cur_uni_block;
+    	 my ($block_info_template, $bank_info_template);
+        #if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6291')  # FIXME
+        {
+             if ($CUSTOM_MEM_DEV_OPTIONS_LOCAL->{MEMORY_DEVICE_TYPE} eq 'NAND_RAM_MCP')##MT6291 MCP
+             {
+                 #todo
+             }
+             else ##MT6291 Discrete DRAM/FLASH
+             {
+                 if ($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+                 {
+                     #print "Serial Flash Support!\n"if ($DebugPrint == 1);
+                     $comma = ($_ < $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT}) ? "," : "";
+                     $cmem_fdm_type =  'CMEM_FDM_NOR_DEFAULT';
+                     $pbp_size = 0;
+                     if ($MDL_INFO_LIST_LOCAL->[$_]->{1}->{'PBP'}->{'Y / N'} =~ /Y/i)
+                     {
+                         $pbp_size = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'PBP'}->{'Size (Byte)'};
+                     }
+                     $cur_block_str = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Device Geometry'}->{'Block Info.'};
+                     @cur_blocks = &split_info($cur_block_str);  # list of Block Info.
+                     $cur_bank_str = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Device Geometry'}->{'Bank Info.'};
+                     @cur_banks = &split_info($cur_bank_str);  # list of Bank Info.
+                     $cur_uni_block_str = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Device Geometry'}->{'Uniform Block (KB)'};
+                     $cur_uni_block = &split_info_for_UB($cur_uni_block_str);
+                 }
+                 else  #if ($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'TRUE'), No flash or NAND gen dummy file
+                 {
+                     #print "NAND Flash Gen Dummy!\n"if ($DebugPrint == 1);
+                     #$comma = 0;
+                     $comma = ($_ < $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT}) ? "," : "";
+                     $cmem_fdm_type =  '';
+                     $pbp_size = 0;
+                     $cur_block_str = '';
+                     @cur_blocks = &split_info($cur_block_str);  # list of Block Info.
+                     $cur_bank_str = '';
+                     @cur_banks = &split_info($cur_bank_str);  # list of Bank Info.
+                     $cur_uni_block_str = '';
+                     $cur_uni_block = &split_info_for_UB($cur_uni_block_str);
+                 }
+             }
+        }
+
+        for (0..($MAX_BLOCK_INFO-1))
+        {
+            $block_info_template .= "            ";
+            if (defined $cur_blocks[$_])
+            {
+                $block_info_template .= $cur_blocks[$_];
+            }
+            else
+            {
+                $block_info_template .= "EndRegionInfo";
+            }
+            if ($_ != ($MAX_BLOCK_INFO-1))
+            {
+                $block_info_template .= ",\n";
+            }
+        }
+        for (0..($MAX_BANK_INFO-1))
+        {
+            $bank_info_template .= "            ";
+            if (defined $cur_banks[$_])
+            {
+                $bank_info_template .= $cur_banks[$_];
+            }
+            else
+            {
+                $bank_info_template .= "EndBankInfo";
+            }
+            if ($_ != ($MAX_BANK_INFO-1))
+            {
+                $bank_info_template .= ",\n";
+            }
+        }
+
+        $combo_mem_info_struct .= <<"__TEMPLATE";
+    {   // $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Part Number'}
+        $cmem_fdm_type,
+        $pbp_size,      // Page Buffer Program Size
+        $cur_uni_block, // Unifom Blocks
+        {   // BlockInfo Start
+$block_info_template
+        },  // BlockInfo End
+        {   // BankInfo Start
+$bank_info_template
+        }  // BankInfo End
+    }$comma
+__TEMPLATE
+    }
+
+    ###
+    my $template = <<"__TEMPLATE";
+#define CMEM_MAX_BLOCKS       $cmem_max_blocks
+#define CMEM_MAX_SECTORS      $cmem_max_sectors
+
+COMBO_MEM_TYPE_MODIFIER COMBO_MEM_TYPE_NAME COMBO_MEM_INST_NAME = {
+    COMBO_MEM_STRUCT_HEAD
+$combo_mem_info_struct
+    COMBO_MEM_STRUCT_FOOT
+};
+__TEMPLATE
+}
+
+
+#****************************************************************************
+# subroutine:  combo_nfi_config_h_file_body
+# return:
+#****************************************************************************
+sub combo_nfi_config_h_file_body
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL) = @_;
+
+    ### Fill-in the information of each memory
+    my $combo_nfi_info_struct;
+    ### There should be totally 8 Flash ID entries
+    my $MAX_FLASH_ID = 8;
+
+    for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+    {
+        $combo_nfi_info_struct .= "/* $_ */\n";
+        my $vendor = uc($MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Vendor'});
+        #$combo_nfi_info_struct .= "    {\n" ."        {" . "\"$vendor $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Part Number'}\"" ."}, // DevName_str (64 bytes)\n";#part time number
+        $combo_nfi_info_struct .= "    {\n" ."        0x00000301,\n" ."        {" . "\"$vendor $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Part Number'}\"" ."}, // NFI_Device_Name (64 bytes)\n";#part time number
+
+        ###get flash id string
+        my $flash_id_str = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Flash ID'};
+
+        $flash_id_str =~ s/\{//;  # remove parentheses
+        $flash_id_str =~ s/\}//;  # remove parentheses
+        $flash_id_str =~ s/\s+//g;  # remove spaces
+        ### parse the flash id string
+        my $saved_sep = $/;
+        undef $/;
+        my @flash_id = split(/\,/, $flash_id_str);
+        $/ = $saved_sep;
+        my $flash_id_cnt = $#flash_id + 1;
+
+        my $flash_id_template;
+        my $flash_id_mask;
+        #$flash_id_template .= sprintf("0x%02X", hex($flash_id[$_])) .",";
+        #$flash_id_mask .= "0xFF" .",";
+        for (0..($MAX_FLASH_ID-1))
+        {
+            if (defined $flash_id[$_])
+            {
+                $flash_id_template .= sprintf("0x%02X", hex($flash_id[$_]));
+                $flash_id_mask .= "0xFF";
+            }
+            else
+            {
+                $flash_id_template .= "0xFF";
+                $flash_id_mask .= "0x00";
+            }
+
+            if ($_ != ($MAX_FLASH_ID-1))
+            {
+                $flash_id_template .= ", ";
+                $flash_id_mask .= ", ";
+            }
+        }
+        $combo_nfi_info_struct .= "        {" .$flash_id_template ."}, // ID_info\n";
+        $combo_nfi_info_struct .= "        {" .$flash_id_mask ."}, // ID_valid_mask\n";
+
+        #$combo_nfi_info_struct .= "        " .$MDL_INFO_LIST_LOCAL->[$_]->{1}->{'NAND Size(MB)'} .", // deviceSize (MByte)\n";
+        $combo_nfi_info_struct .= "        " .$MDL_INFO_LIST_LOCAL->[$_]->{1}->{'NAND Size(MB)'} .", // NFI_Device_Size (MByte)\n";
+
+        #$combo_nfi_info_struct .= "        " .sprintf("0x%05x", $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'NAND Block Size(KB)'}*1024) .", // blockSize (byte)\n";
+        $combo_nfi_info_struct .= "        " .sprintf("0x%05x", $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'NAND Block Size(KB)'}*1024) .", // NFI_Block_Size (byte)\n";
+
+        ###Parsing special info
+        my $nand_flash_key_ref = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{$MAKEFILE_OPTIONS_LOCAL->{'platform'}};
+
+        foreach my $nand_flash_key (sort keys %{$nand_flash_key_ref})
+        {
+            #print "1-1 $nand_flash_key\n"if ($DebugPrint == 1);
+            if ($nand_flash_key =~ /(\d+)MHz NFIC Config/)
+            {
+                my $nand_flash_cfg = $nand_flash_key;
+                foreach my $nand_flash_key (sort keys %{$nand_flash_key_ref->{$nand_flash_cfg}})
+                {
+                    my $val = $nand_flash_key_ref->{$nand_flash_cfg}->{$nand_flash_key};
+                    if (($val ne 'x') && ($val ne 'X') && ($val ne ''))
+                    {
+                        $combo_nfi_info_struct .="        " .$val .", // $nand_flash_key" . "\n";
+                    }
+                }
+            }
+            else
+            {
+                my $val = $nand_flash_key_ref->{$nand_flash_key};
+                #print "1-2 $val\n"if ($DebugPrint == 1);
+                if (($val ne 'x') && ($val ne 'X') && ($val ne ''))
+                {
+                    $combo_nfi_info_struct .= "        " .$val .", // $nand_flash_key" . "\n";
+                }
+            }
+        }
+
+        if($_ != $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+        {
+            $combo_nfi_info_struct .= "    },\n";
+        }
+        else
+        {
+            $combo_nfi_info_struct .= "    }\n";
+        }
+__TEMPLATE
+    }
+
+    ###
+    my $template = <<"__TEMPLATE";
+#define COMBO_NAND_FLASH_NUM $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT}
+const combo_nand_flash_list_v01 COMBO_NAND_Table[] =
+{
+$combo_nfi_info_struct
+};
+__TEMPLATE
+
+    return $template;
+}
+
+#****************************************************************************
+# subroutine:  combo_flash_id_h_file_body
+# return:
+#****************************************************************************
+sub combo_flash_id_h_file_body
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL) = @_;
+    ### There should be totally 8 Flash ID entries
+    my $MAX_FLASH_ID = 8;
+
+    ### Fill-in the information of each memory
+    my $combo_mem_info_struct;
+    for (1..$CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT})
+    {
+        my $comma = ($_ < $CUSTOM_MEM_DEV_OPTIONS_LOCAL->{COMBO_MEM_ENTRY_COUNT}) ? "," : "";
+        my $cmem_type;
+
+        if ($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'TRUE')
+        {
+            $cmem_type = "CMEM_TYPE_NAND";
+        }
+        elsif ($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+        {
+            $cmem_type = "CMEM_TYPE_SERIAL_NOR_FLASH";
+        }
+        else
+        {
+            print "No flash support!\n"if ($DebugPrint == 1);
+        }
+
+        ###get nand page size
+        my $nand_page_size = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'NAND Page size(B)'};
+        my $nand_block_size = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'NAND Block Size(KB)'};
+        $nand_page_size = $nand_page_size . "," . $nand_block_size;
+
+        ###get flash id string
+        my $flash_id_str = $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Flash ID'};
+
+        $flash_id_str =~ s/\{//;  # remove parentheses
+        $flash_id_str =~ s/\}//;  # remove parentheses
+        $flash_id_str =~ s/\s+//g;  # remove spaces
+        ### parse the flash id string
+        my $saved_sep = $/;
+        undef $/;
+        my @flash_id = split(/\,/, $flash_id_str);
+        $/ = $saved_sep;
+        my $flash_id_cnt = $#flash_id + 1;
+
+        my $flash_id_template;
+        for (0..($MAX_FLASH_ID-1))
+        {
+            if (defined $flash_id[$_])
+            {
+                ##$flash_id_template .= $flash_id[$_];
+                if ($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'TRUE')
+                {
+                	$flash_id_template .= sprintf("0x%04X", hex($flash_id[$_]));
+                	#print "$_: $flash_id_template\n"if ($DebugPrint == 1);
+                }
+		  elsif($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+		  {
+                	$flash_id_template .= sprintf("0x%02X", hex($flash_id[$_]));
+                }
+            }
+            else
+            {
+                if ($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'TRUE')
+                {
+                	$flash_id_template .= "0x0000";
+                }
+                elsif($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+                {
+                	$flash_id_template .= "0x00";
+                }
+            }
+            if ($_ != ($MAX_FLASH_ID-1))
+            {
+                $flash_id_template .= ", ";
+            }
+        }
+
+        $combo_mem_info_struct .= <<"__TEMPLATE";
+        {   // $MDL_INFO_LIST_LOCAL->[$_]->{1}->{'Part Number'}
+            $cmem_type,
+            $flash_id_cnt,  // Valid ID length
+            {$flash_id_template}  // Flash ID
+        }$comma
+__TEMPLATE
+    }
+
+    ###
+    my $template = <<"__TEMPLATE";
+COMBO_MEM_TYPE_MODIFIER COMBO_MEM_TYPE_NAME COMBO_MEM_INST_NAME = {
+    COMBO_MEM_STRUCT_HEAD
+$combo_mem_info_struct
+    COMBO_MEM_STRUCT_FOOT
+};
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  custom_flash_c_file_body
+# return:
+#****************************************************************************
+sub custom_flash_h_file_body
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL, $MCP_LIST_LOCAL, $NOR_FLASH_BASE_ADDRESS_VAL_LOCAL, $NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL,$LPSDRAM_CHIP_SELECT_LOCAL, $CUSTOM_MEMORY_DEVICE_HDR_LOCAL, $nor_size_Mb_LOCAL, $entire_bank_info_size_list_LOCAL, $entire_bank_info_count_list_LOCAL, $entire_block_info_start_list_LOCAL, $entire_block_info_size_list_LOCAL, $fs_region_info_size_list_LOCAL, $fs_region_info_count_list_LOCAL, $entire_region_info_size_list_LOCAL, $entire_region_info_count_list_LOCAL) = @_;
+    my $cs1_base_address = 0;
+    my $flash_limit = 0;
+    my $flash_size = 0;
+    #if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6291')  # FIXME
+    {
+        my $SPI_Base = 0xA0000000;
+        $cs1_base_address = sprintf("0x%08X", $SPI_Base);#MT6291 Flash Memory Base Address
+        $flash_limit = sprintf("0x%08X", $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+        $flash_size = sprintf("0x%08X", $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+        #print "MT6291: $cs1_base_address, $flash_limit, $flash_size\n"if ($DebugPrint == 1);
+
+        if($MAKEFILE_OPTIONS_LOCAL->{'nand_support'} eq 'TRUE')
+        {
+            ##MT6291 Work aroud for SDS, when in HOSTED/ROUTER Project, NOR_FLASH_SIZE is MD End(96MB), other Projects is Flash Size
+            if((($MAKEFILE_OPTIONS_LOCAL->{'smart_phone_core'} eq 'ANDROID_MODEM') and ($MAKEFILE_OPTIONS_LOCAL->{'ccci_fs_support'} eq 'FALSE')) or (($MAKEFILE_OPTIONS_LOCAL->{'modem_card'} eq 'FULL') and ($MAKEFILE_OPTIONS_LOCAL->{'smart_phone_core'} eq 'MODEM_HOST')))
+            #if($MAKEFILE_OPTIONS_LOCAL->{'modem_card'} eq 'FULL')
+            {
+                my $MDFlashEnd = 96*1024*1024;
+                $flash_size = sprintf("0x%08X", $MDFlashEnd);
+            }
+        }
+    }
+
+    ###
+    my $pbp_size            = ($COMM_MDL_INFO_LOCAL->{0}->{'PBP'}->{'Y / N'} =~ /Y/i)
+                            ?  $COMM_MDL_INFO_LOCAL->{0}->{'PBP'}->{'Size(W)'}
+                            : 0;
+
+    ###
+    my $bank_info_string = &bank_info( $entire_bank_info_size_list_LOCAL, $entire_bank_info_count_list_LOCAL, $NOR_FLASH_BASE_ADDRESS_VAL_LOCAL, $NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL );
+
+    ###
+    my $sub_content_block1 = &region_info($fs_region_info_size_list_LOCAL, $fs_region_info_count_list_LOCAL, 'REGION_INFO_LAYOUT');
+    my $sub_content_block2 = &block_info($entire_block_info_start_list_LOCAL, $entire_block_info_size_list_LOCAL);
+    my $sub_content_block3;
+    my $sub_content_block4;
+    #if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6291')  # FIXME
+    {
+        my $ori_bank_info = &bank_info($entire_bank_info_size_list_LOCAL, $entire_bank_info_count_list_LOCAL, 0, $COMM_MDL_INFO_LOCAL->{0}->{'Flash Size'});
+        ### ? what is the purpose?
+        $sub_content_block3 = &region_info($fs_region_info_size_list_LOCAL, $fs_region_info_count_list_LOCAL, 'oriRegionInfo');
+        $sub_content_block4 .= <<"__TEMPLATE";
+
+static NORBankInfo oriBankInfo[] =
+{
+$ori_bank_info
+   EndBankInfo
+};
+
+__TEMPLATE
+        chomp $sub_content_block4;
+    }
+
+    ###
+    my $region_info_disk0 = &region_info($fs_region_info_size_list_LOCAL, $fs_region_info_count_list_LOCAL, 'DISK0_REGION_INFO_LAYOUT');
+    my $region_info_disk1 = &region_info($fs_region_info_size_list_LOCAL, $fs_region_info_count_list_LOCAL, 'DISK1_REGION_INFO_LAYOUT');
+    my $region_info_entire = &region_info($entire_region_info_size_list_LOCAL, $entire_region_info_count_list_LOCAL, 'ENTIRE_DISK_REGION_INFO_LAYOUT');
+
+    ###
+    my $template1;
+
+        $template1 .= <<"__TEMPLATE__";
+/*******************************************************************************
+   NOTICE: Fill the flash region information table, a region is the memory space
+           that contains continuous sectors of equal size. Each region element
+           in the table is the format as below:
+           {S_sector, N_sector},
+               S_sector: the size of sector in the region
+               N_sector: the number of sectors in the region
+ *******************************************************************************/
+$sub_content_block1
+
+$region_info_disk0
+
+$region_info_disk1
+
+$region_info_entire
+
+/*******************************************************************************
+   NOTICE. Modify the value of TOTAL_BLOCKS, which is the sum of the number of
+           sectors in all regions.
+           Note : The Maximum value of TOTAL_BLOCKS is (127).
+ *******************************************************************************/
+#define TOTAL_BLOCKS          $cmem_max_blocks
+
+/*******************************************************************************
+   NOTICE. Modify the value of page buffer size in WORD for page buffer program
+ *******************************************************************************/
+//kal_uint32 PAGE_BUFFER_SIZE = $pbp_size;
+
+#define BANK_INFO_LAYOUT $bank_info_string
+
+/*******************************************************************************
+   NOTICE. NOR FLASH BLOCKS SIZE LOOKUP TABLE
+          Each entry element
+          {Offset, Block_Size},
+               Offset:     the offset address
+               Block_Size: the size of block
+ *******************************************************************************/
+$sub_content_block2
+__TEMPLATE__
+
+    ###
+    my $flash_id_str;
+    #if($MAKEFILE_OPTIONS_LOCAL->{'platform'} eq 'MT6291')  # FIXME
+    {
+        #print "Flash ID: $MDL_INFO_LIST_LOCAL->[1]->{1}->{'Flash ID'}!\n"if ($DebugPrint == 1);
+        $flash_id_str .= <<"__TEMPLATE__";
+const kal_char FLASH_ID[] = "$MDL_INFO_LIST_LOCAL->[1]->{1}->{'Flash ID'}";
+__TEMPLATE__
+    }
+    chomp $flash_id_str;
+
+    ###
+    my ($cs0_info, $cs1_info);
+    my ($pn0_str, $pn1_str);
+    if ((!defined $MAKEFILE_OPTIONS_LOCAL->{'combo_memory_support'}) or ($MAKEFILE_OPTIONS_LOCAL->{'combo_memory_support'} eq 'FALSE'))
+    {
+        if (defined $MCP_LIST_LOCAL->[1]->{0})
+        {
+            $cs0_info .= <<"__TEMPLATE__";
+ * CS0_PART_NUMBER: $MCP_LIST_LOCAL->[1]->{0}
+__TEMPLATE__
+            chomp $cs0_info;
+
+            $pn0_str .= <<"__TEMPLATE__";
+const kal_char PART_NUMBER_0[] = "$MCP_LIST_LOCAL->[1]->{0}";
+__TEMPLATE__
+            chomp $pn0_str;
+        }
+        if (defined $MCP_LIST_LOCAL->[1]->{1})
+        {
+            $cs1_info .= <<"__TEMPLATE__";
+ * CS1_PART_NUMBER: $MCP_LIST_LOCAL->[1]->{1}
+__TEMPLATE__
+            chomp $cs1_info;
+
+            $pn1_str .= <<"__TEMPLATE__";
+const kal_char PART_NUMBER_1[] = "$MCP_LIST_LOCAL->[1]->{1}";
+__TEMPLATE__
+            chomp $pn1_str;
+        }
+    }
+
+    ###
+    my $flash_density;
+    if ($MAKEFILE_OPTIONS_LOCAL->{'serial_flash_support'} eq 'TRUE')
+    {
+        $flash_density .= <<"__TEMPLATE__";
+ * NOR_FLASH_DENSITY: $flash_limit
+ * NOR_FLASH_SIZE(Mb): $nor_size_Mb_LOCAL
+__TEMPLATE__
+        chomp $flash_density;
+    }
+
+    ###
+    my $template = <<"__TEMPLATE";
+
+#define NOR_FLASH_BASE_ADDR $cs1_base_address
+/*
+#ifdef REMAPPING
+#define NOR_FLASH_BASE_ADDR $cs1_base_address
+#define RAM_BASE_ADDR       0x00000000
+#else
+#define NOR_FLASH_BASE_ADDR 0x00000000
+#define RAM_BASE_ADDR       $cs1_base_address
+#endif
+*/
+
+/*
+ ****************************************************************************
+ PART 2:
+ Essential Information of NOR Flash Geometry Layout Information
+ ****************************************************************************
+*/
+$template1
+
+#define NOR_FLASH_SIZE $flash_size
+
+$flash_id_str
+
+/****************************************************
+ * This part is for auto-gen validity CHECK *
+ * Don't modify any content in this comment section *
+$cs0_info
+$cs1_info
+$flash_density
+
+$sub_content_block3
+
+$sub_content_block4
+ ****************************************************/
+
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  custom_flash_norfdm5_c_file_body
+# return:
+#****************************************************************************
+sub custom_flash_norfdm5_h_file_body
+{
+    my ($MAKEFILE_OPTIONS_LOCAL, $CUSTOM_MEM_DEV_OPTIONS_LOCAL, $MDL_INFO_LIST_LOCAL, $COMM_MDL_INFO_LOCAL, $NOR_FLASH_BASE_ADDRESS_VAL_LOCAL, $NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL, $entire_bank_info_size_list_LOCAL, $entire_bank_info_count_list_LOCAL) = @_;
+    my $bank_info_string = &bank_info( $entire_bank_info_size_list_LOCAL, $entire_bank_info_count_list_LOCAL, $NOR_FLASH_BASE_ADDRESS_VAL_LOCAL, $NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL );
+
+    ###
+    my $nor_total_lsmt  = 1;
+    my $count           = 0;
+    my $sum             = 1;
+    my $tmp_regions_sum = $NOR_ALLOCATED_FAT_SPACE_VAL_LOCAL;
+
+    if ($MEM_DEV_LIST_INFO{0}->{'Comm. Series'} eq 'INTEL_SIBLEY')
+    {
+        if ($tmp_regions_sum > (64*1024*1024))
+        {
+            my $count = 0;
+            my $sum  = 1;
+            $tmp_regions_sum /= 1024;
+            while ($sum < $tmp_regions_sum)
+            {
+                $sum *= 2;
+                $count++;
+            }
+            $count -= 8;
+            for (my $i=0; $i<$count; $i++)
+            {
+                $nor_total_lsmt *= 2;
+            }
+        }
+        else
+        {
+          my $count = 0;
+          my $sum  = 1;
+          $tmp_regions_sum /= 1024;
+          while ($sum < $tmp_regions_sum)
+          {
+              $sum *= 2;
+              $count++;
+          }
+          if (($count/2) == 1)
+          {
+              $count += 1;
+          }
+          $count /= 2;
+          for (my $i=0; $i<$count; $i++)
+          {
+              $nor_total_lsmt *= 2;
+          }
+        }
+    }
+    else
+    {
+        if ($tmp_regions_sum > (32*1024*1024))
+        {
+            my $count = 0;
+            my $sum  = 1;
+            $tmp_regions_sum /= 512;
+            while ($sum < $tmp_regions_sum)
+            {
+                $sum *= 2;
+                $count++;
+            }
+            $count -= 8;
+            for (my $i=0; $i<$count; $i++)
+            {
+                $nor_total_lsmt *= 2;
+            }
+        }
+        else
+        {
+            my $count = 0;
+            my $sum  = 1;
+            $tmp_regions_sum /= 512;
+            while ($sum < $tmp_regions_sum)
+            {
+                $sum *= 2;
+                $count++;
+            }
+            if (($count/2) == 1)
+            {
+                $count += 1;
+            }
+            $count /= 2;
+            for (my $i=0; $i<$count; $i++)
+            {
+                $nor_total_lsmt *= 2;
+            }
+        }
+    }
+
+    ###
+    my $template = <<"__TEMPLATE";
+
+#ifdef __NOR_FDM5__
+
+/*******************************************************************************
+   Follow the 4 steps below to configure flash memory
+
+   Step 1. Fill the flash bank (partition) information table,
+           flash device features flexible, multi-bank read-while-program and
+           read-while-erase capability, enabling background programming or erasing in
+           one bank simultaneously with code execution or data reads in another bank.
+           Each element in the table is the format as below:
+           {bank size, bank number},
+
+   Step 2. Modify the value of LSMT, you can see MemoryDevice_FlashDisk_FAQ for
+           detail information
+
+   Step 2. Define the toal sectors (512bytes) of system drive
+           the remainder is the size of public drive
+           If there is no partiton (just one drive, system drive)
+           set this value to STORAGE_NO_PARTITION (0xFFFFFFFF)
+
+
+   Note : Code region and FAT region can not share the same bank (partition)
+*******************************************************************************/
+
+
+/***********
+ * Step 1. *
+ ***********/
+#define NOR_FDM5_BANK_INFO_LAYOUT $bank_info_string
+
+/***********
+ * Step 2. *
+ ***********/
+#define NOR_TOTAL_LSMT $nor_total_lsmt
+
+/***********
+ * Step 3. *
+ ***********/
+// Set NOR_SYSDRV_SECTORS to 0 will disable this setting and use PARTITION_SECTORS in custom_memorydevice.h to set user drive size.
+#define NOR_SYSDRV_SECTORS 0
+
+#endif //__NOR_FDM5__
+
+__TEMPLATE
+
+   return $template;
+}
+
+#****************************************************************************
+# subroutine:  split_info_for_UB
+# return:      List of RegionInfo/BlockInfo/BankInfo
+# input:       $info: Excel value to be split
+#****************************************************************************
+sub split_info_for_UB
+{
+  my $text = shift;
+  my @new = ();
+  my $ref_str;
+  my $i;
+  push(@new, $+) while $text =~ m{
+      "([^\"\\]*(?:\\.[^\"\\]*)*)", ?
+      | ([^,]+), ?
+      | ,
+  }gx;
+  push(@new, undef) if substr($text, -1,1) eq ',';
+  for($i = 0; $i < @new; $i++){
+     print "$i: $new[$i]\n";
+  }
+  $ref_str = "(";
+  for($i = 0; $i < @new; $i++){
+     if($i == (@new -1))
+     {
+        $ref_str .= $new[$i];
+     }
+     else
+     {
+        $ref_str .= $new[$i] . "|";
+     }
+
+  }
+  $ref_str .= ")";
+  print "$ref_str\n";
+  return $ref_str;
+}
+
+return 1;
diff --git a/src/bach/build.bach/tools/f_size b/src/bach/build.bach/tools/f_size
new file mode 100755
index 0000000..50cc6a9
--- /dev/null
+++ b/src/bach/build.bach/tools/f_size
Binary files differ
diff --git a/src/bach/build.bach/tools/fileparser b/src/bach/build.bach/tools/fileparser
new file mode 100755
index 0000000..e3eee91
--- /dev/null
+++ b/src/bach/build.bach/tools/fileparser
Binary files differ
diff --git a/src/bach/build.bach/tools/make_ext4fs b/src/bach/build.bach/tools/make_ext4fs
new file mode 100755
index 0000000..3ec6048
--- /dev/null
+++ b/src/bach/build.bach/tools/make_ext4fs
Binary files differ
diff --git a/src/bach/build.bach/tools/mkbootimg b/src/bach/build.bach/tools/mkbootimg
new file mode 100755
index 0000000..8bfa0b9
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg
Binary files differ
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg.zip b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg.zip
new file mode 100755
index 0000000..62f6bd7
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg.zip
Binary files differ
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/.gitignore b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/.gitignore
new file mode 100755
index 0000000..73f4d0d
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/.gitignore
@@ -0,0 +1,5 @@
+*.o
+*.a
+*.exe
+mkbootimg
+unpackbootimg
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/Makefile b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/Makefile
new file mode 100755
index 0000000..aed6b57
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/Makefile
@@ -0,0 +1,51 @@
+# Andrew Huang <bluedrum@163.com>
+ifeq ($(CC),cc)
+CC = gcc
+endif
+AR = ar rcv
+ifeq ($(windir),)
+EXE =
+RM = rm -f
+else
+EXE = .exe
+RM = del
+endif
+
+CFLAGS = -ffunction-sections -O3
+
+ifneq (,$(findstring darwin,$(CROSS_COMPILE)))
+    UNAME_S := Darwin
+else
+    UNAME_S := $(shell uname -s)
+endif
+ifeq ($(UNAME_S),Darwin)
+    LDFLAGS += -Wl,-dead_strip
+else
+    LDFLAGS += -Wl,--gc-sections -s
+endif
+
+all:mkbootimg$(EXE) unpackbootimg$(EXE)
+
+static:
+	$(MAKE) LDFLAGS="$(LDFLAGS) -static"
+
+libmincrypt.a:
+	$(MAKE) -C libmincrypt
+
+mkbootimg$(EXE):mkbootimg.o libmincrypt.a
+	$(CROSS_COMPILE)$(CC) -o $@ $^ -L. -lmincrypt $(LDFLAGS)
+
+mkbootimg.o:mkbootimg.c
+	$(CROSS_COMPILE)$(CC) -o $@ $(CFLAGS) -c $< -I. -Werror
+
+unpackbootimg$(EXE):unpackbootimg.o
+	$(CROSS_COMPILE)$(CC) -o $@ $^ $(LDFLAGS)
+
+unpackbootimg.o:unpackbootimg.c
+	$(CROSS_COMPILE)$(CC) -o $@ $(CFLAGS) -c $< -Werror
+
+clean:
+	$(RM) mkbootimg unpackbootimg
+	$(RM) *.a *.~ *.exe *.o
+	$(MAKE) -C libmincrypt clean
+
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/NOTICE b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/NOTICE
new file mode 100755
index 0000000..430d3d6
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/NOTICE
@@ -0,0 +1,23 @@
+ Copyright 2008, The Android Open Source Project
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+     * Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+     * Neither the name of Google Inc. nor the names of its contributors may
+       be used to endorse or promote products derived from this software
+       without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR 
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 
+ EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/bootimg.h b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/bootimg.h
new file mode 100755
index 0000000..ff2f111
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/bootimg.h
@@ -0,0 +1,114 @@
+/* tools/mkbootimg/bootimg.h
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+#include <stdint.h>
+
+#ifndef _BOOT_IMAGE_H_
+#define _BOOT_IMAGE_H_
+
+typedef struct boot_img_hdr boot_img_hdr;
+
+#define BOOT_MAGIC "ANDROID!"
+#define BOOT_MAGIC_SIZE 8
+#define BOOT_NAME_SIZE 16
+#define BOOT_ARGS_SIZE 512
+#define BOOT_EXTRA_ARGS_SIZE 1024
+
+struct boot_img_hdr
+{
+    uint8_t magic[BOOT_MAGIC_SIZE];
+
+    uint32_t kernel_size;  /* size in bytes */
+    uint32_t kernel_addr;  /* physical load addr */
+
+    uint32_t ramdisk_size; /* size in bytes */
+    uint32_t ramdisk_addr; /* physical load addr */
+
+    uint32_t second_size;  /* size in bytes */
+    uint32_t second_addr;  /* physical load addr */
+
+    uint32_t tags_addr;    /* physical addr for kernel tags */
+    uint32_t page_size;    /* flash page size we assume */
+    uint32_t dt_size;      /* device tree in bytes */
+
+    /* operating system version and security patch level; for
+     * version "A.B.C" and patch level "Y-M-D":
+     * ver = A << 14 | B << 7 | C         (7 bits for each of A, B, C)
+     * lvl = ((Y - 2000) & 127) << 4 | M  (7 bits for Y, 4 bits for M)
+     * os_version = ver << 11 | lvl */
+    uint32_t os_version;
+
+    uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */
+
+    uint8_t cmdline[BOOT_ARGS_SIZE];
+
+    uint32_t id[8]; /* timestamp / checksum / sha1 / etc */
+
+    /* Supplemental command line data; kept here to maintain
+     * binary compatibility with older versions of mkbootimg */
+    uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
+} __attribute__((packed));
+
+/*
+** +-----------------+ 
+** | boot header     | 1 page
+** +-----------------+
+** | kernel          | n pages  
+** +-----------------+
+** | ramdisk         | m pages  
+** +-----------------+
+** | second stage    | o pages
+** +-----------------+
+** | device tree     | p pages
+** +-----------------+
+**
+** n = (kernel_size + page_size - 1) / page_size
+** m = (ramdisk_size + page_size - 1) / page_size
+** o = (second_size + page_size - 1) / page_size
+** p = (dt_size + page_size - 1) / page_size
+**
+** 0. all entities are page_size aligned in flash
+** 1. kernel and ramdisk are required (size != 0)
+** 2. second is optional (second_size == 0 -> no second)
+** 3. load each element (kernel, ramdisk, second) at
+**    the specified physical address (kernel_addr, etc)
+** 4. prepare tags at tag_addr.  kernel_args[] is
+**    appended to the kernel commandline in the tags.
+** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
+** 6. if second_size != 0: jump to second_addr
+**    else: jump to kernel_addr
+*/
+
+#if 0
+typedef struct ptentry ptentry;
+
+struct ptentry {
+    char name[16];      /* asciiz partition name    */
+    unsigned start;     /* starting block number    */
+    unsigned length;    /* length in blocks         */
+    unsigned flags;     /* set to zero              */
+};
+
+/* MSM Partition Table ATAG
+**
+** length: 2 + 7 * n
+** atag:   0x4d534d70
+**         <ptentry> x n
+*/
+#endif
+
+#endif
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/Makefile b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/Makefile
new file mode 100755
index 0000000..ad482de
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/Makefile
@@ -0,0 +1,31 @@
+ifeq ($(CC),cc)
+CC = gcc
+endif
+AR = ar rc
+ifeq ($(windir),)
+RM = rm -f
+CP = cp
+else
+RM = del
+CP = copy /y
+endif
+
+CFLAGS = -ffunction-sections -O3
+EXT = a
+LIB = libmincrypt.$(EXT)
+LIB_OBJS = dsa_sig.o p256.o p256_ec.o p256_ecdsa.o rsa.o sha.o sha256.o
+INC  = -I..
+
+all:$(LIB)
+
+clean:
+	$(RM) $(LIB_OBJS) $(LIB)
+
+$(LIB):$(LIB_OBJS)
+	$(CROSS_COMPILE)$(AR) $@ $^
+	$(CP) $@ ..
+
+
+%.o:%.c
+	$(CROSS_COMPILE)$(CC) -o $@ $(CFLAGS) -c $< $(INC)
+
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/dsa_sig.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/dsa_sig.c
new file mode 100755
index 0000000..101314b
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/dsa_sig.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "mincrypt/dsa_sig.h"
+#include "mincrypt/p256.h"
+
+/**
+ * Trims off the leading zero bytes and copy it to a buffer aligning it to the end.
+ */
+static inline int trim_to_p256_bytes(unsigned char dst[P256_NBYTES], unsigned char *src,
+        int src_len) {
+    int dst_offset;
+    while (*src == '\0' && src_len > 0) {
+        src++;
+        src_len--;
+    }
+    if (src_len > P256_NBYTES || src_len < 1) {
+        return 0;
+    }
+    dst_offset = P256_NBYTES - src_len;
+    memset(dst, 0, dst_offset);
+    memcpy(dst + dst_offset, src, src_len);
+    return 1;
+}
+
+/**
+ * Unpacks the ASN.1 DSA signature sequence.
+ */
+int dsa_sig_unpack(unsigned char* sig, int sig_len, p256_int* r_int, p256_int* s_int) {
+    /*
+     * Structure is:
+     *   0x30 0xNN  SEQUENCE + s_length
+     *     0x02 0xNN  INTEGER + r_length
+     *       0xAA 0xBB ..   r_length bytes of "r" (offset 4)
+     *     0x02 0xNN  INTEGER + s_length
+     *       0xMM 0xNN ..   s_length bytes of "s" (offset 6 + r_len)
+     */
+    int seq_len;
+    unsigned char r_bytes[P256_NBYTES];
+    unsigned char s_bytes[P256_NBYTES];
+    int r_len;
+    int s_len;
+
+    memset(r_bytes, 0, sizeof(r_bytes));
+    memset(s_bytes, 0, sizeof(s_bytes));
+
+    /*
+     * Must have at least:
+     * 2 bytes sequence header and length
+     * 2 bytes R integer header and length
+     * 1 byte of R
+     * 2 bytes S integer header and length
+     * 1 byte of S
+     *
+     * 8 bytes total
+     */
+    if (sig_len < 8 || sig[0] != 0x30 || sig[2] != 0x02) {
+        return 0;
+    }
+
+    seq_len = sig[1];
+    if ((seq_len <= 0) || (seq_len + 2 != sig_len)) {
+        return 0;
+    }
+
+    r_len = sig[3];
+    /*
+     * Must have at least:
+     * 2 bytes for R header and length
+     * 2 bytes S integer header and length
+     * 1 byte of S
+     */
+    if ((r_len < 1) || (r_len > seq_len - 5) || (sig[4 + r_len] != 0x02)) {
+        return 0;
+    }
+    s_len = sig[5 + r_len];
+
+    /**
+     * Must have:
+     * 2 bytes for R header and length
+     * r_len bytes for R
+     * 2 bytes S integer header and length
+     */
+    if ((s_len < 1) || (s_len != seq_len - 4 - r_len)) {
+        return 0;
+    }
+
+    /*
+     * ASN.1 encoded integers are zero-padded for positive integers. Make sure we have
+     * a correctly-sized buffer and that the resulting integer isn't too large.
+     */
+    if (!trim_to_p256_bytes(r_bytes, &sig[4], r_len)
+            || !trim_to_p256_bytes(s_bytes, &sig[6 + r_len], s_len)) {
+        return 0;
+    }
+
+    p256_from_bin(r_bytes, r_int);
+    p256_from_bin(s_bytes, s_int);
+
+    return 1;
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256.c
new file mode 100755
index 0000000..555a07a
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This is an implementation of the P256 elliptic curve group. It's written to
+// be portable 32-bit, although it's still constant-time.
+//
+// WARNING: Implementing these functions in a constant-time manner is far from
+//          obvious. Be careful when touching this code.
+//
+// See http://www.imperialviolet.org/2010/12/04/ecc.html ([1]) for background.
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "mincrypt/p256.h"
+
+const p256_int SECP256r1_n =  // curve order
+  {{0xfc632551, 0xf3b9cac2, 0xa7179e84, 0xbce6faad, -1, -1, 0, -1}};
+
+const p256_int SECP256r1_p =  // curve field size
+  {{-1, -1, -1, 0, 0, 0, 1, -1 }};
+
+const p256_int SECP256r1_b =  // curve b
+  {{0x27d2604b, 0x3bce3c3e, 0xcc53b0f6, 0x651d06b0,
+    0x769886bc, 0xb3ebbd55, 0xaa3a93e7, 0x5ac635d8}};
+
+void p256_init(p256_int* a) {
+  memset(a, 0, sizeof(*a));
+}
+
+void p256_clear(p256_int* a) { p256_init(a); }
+
+int p256_get_bit(const p256_int* scalar, int bit) {
+  return (P256_DIGIT(scalar, bit / P256_BITSPERDIGIT)
+              >> (bit & (P256_BITSPERDIGIT - 1))) & 1;
+}
+
+int p256_is_zero(const p256_int* a) {
+  int i, result = 0;
+  for (i = 0; i < P256_NDIGITS; ++i) result |= P256_DIGIT(a, i);
+  return !result;
+}
+
+// top, c[] += a[] * b
+// Returns new top
+static p256_digit mulAdd(const p256_int* a,
+                         p256_digit b,
+                         p256_digit top,
+                         p256_digit* c) {
+  int i;
+  p256_ddigit carry = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    carry += *c;
+    carry += (p256_ddigit)P256_DIGIT(a, i) * b;
+    *c++ = (p256_digit)carry;
+    carry >>= P256_BITSPERDIGIT;
+  }
+  return top + (p256_digit)carry;
+}
+
+// top, c[] -= top_a, a[]
+static p256_digit subTop(p256_digit top_a,
+                         const p256_digit* a,
+                         p256_digit top_c,
+                         p256_digit* c) {
+  int i;
+  p256_sddigit borrow = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    borrow += *c;
+    borrow -= *a++;
+    *c++ = (p256_digit)borrow;
+    borrow >>= P256_BITSPERDIGIT;
+  }
+  borrow += top_c;
+  borrow -= top_a;
+  top_c = (p256_digit)borrow;
+  assert((borrow >> P256_BITSPERDIGIT) == 0);
+  return top_c;
+}
+
+// top, c[] -= MOD[] & mask (0 or -1)
+// returns new top.
+static p256_digit subM(const p256_int* MOD,
+                       p256_digit top,
+                       p256_digit* c,
+                       p256_digit mask) {
+  int i;
+  p256_sddigit borrow = 0;
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    borrow += *c;
+    borrow -= P256_DIGIT(MOD, i) & mask;
+    *c++ = (p256_digit)borrow;
+    borrow >>= P256_BITSPERDIGIT;
+  }
+  return top + (p256_digit)borrow;
+}
+
+// top, c[] += MOD[] & mask (0 or -1)
+// returns new top.
+static p256_digit addM(const p256_int* MOD,
+                       p256_digit top,
+                       p256_digit* c,
+                       p256_digit mask) {
+  int i;
+  p256_ddigit carry = 0;
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    carry += *c;
+    carry += P256_DIGIT(MOD, i) & mask;
+    *c++ = (p256_digit)carry;
+    carry >>= P256_BITSPERDIGIT;
+  }
+  return top + (p256_digit)carry;
+}
+
+// c = a * b mod MOD. c can be a and/or b.
+void p256_modmul(const p256_int* MOD,
+                 const p256_int* a,
+                 const p256_digit top_b,
+                 const p256_int* b,
+                 p256_int* c) {
+  p256_digit tmp[P256_NDIGITS * 2 + 1] = { 0 };
+  p256_digit top = 0;
+  int i;
+
+  // Multiply/add into tmp.
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    if (i) tmp[i + P256_NDIGITS - 1] = top;
+    top = mulAdd(a, P256_DIGIT(b, i), 0, tmp + i);
+  }
+
+  // Multiply/add top digit
+  tmp[i + P256_NDIGITS - 1] = top;
+  top = mulAdd(a, top_b, 0, tmp + i);
+
+  // Reduce tmp, digit by digit.
+  for (; i >= 0; --i) {
+    p256_digit reducer[P256_NDIGITS] = { 0 };
+    p256_digit top_reducer;
+
+    // top can be any value at this point.
+    // Guestimate reducer as top * MOD, since msw of MOD is -1.
+    top_reducer = mulAdd(MOD, top, 0, reducer);
+
+    // Subtract reducer from top | tmp.
+    top = subTop(top_reducer, reducer, top, tmp + i);
+
+    // top is now either 0 or 1. Make it 0, fixed-timing.
+    assert(top <= 1);
+
+    top = subM(MOD, top, tmp + i, ~(top - 1));
+
+    assert(top == 0);
+
+    // We have now reduced the top digit off tmp. Fetch new top digit.
+    top = tmp[i + P256_NDIGITS - 1];
+  }
+
+  // tmp might still be larger than MOD, yet same bit length.
+  // Make sure it is less, fixed-timing.
+  addM(MOD, 0, tmp, subM(MOD, 0, tmp, -1));
+
+  memcpy(c, tmp, P256_NBYTES);
+}
+int p256_is_odd(const p256_int* a) { return P256_DIGIT(a, 0) & 1; }
+int p256_is_even(const p256_int* a) { return !(P256_DIGIT(a, 0) & 1); }
+
+p256_digit p256_shl(const p256_int* a, int n, p256_int* b) {
+  int i;
+  p256_digit top = P256_DIGIT(a, P256_NDIGITS - 1);
+
+  n %= P256_BITSPERDIGIT;
+  for (i = P256_NDIGITS - 1; i > 0; --i) {
+    p256_digit accu = (P256_DIGIT(a, i) << n);
+    accu |= (P256_DIGIT(a, i - 1) >> (P256_BITSPERDIGIT - n));
+    P256_DIGIT(b, i) = accu;
+  }
+  P256_DIGIT(b, i) = (P256_DIGIT(a, i) << n);
+
+  top = (p256_digit)((((p256_ddigit)top) << n) >> P256_BITSPERDIGIT);
+
+  return top;
+}
+
+void p256_shr(const p256_int* a, int n, p256_int* b) {
+  int i;
+
+  n %= P256_BITSPERDIGIT;
+  for (i = 0; i < P256_NDIGITS - 1; ++i) {
+    p256_digit accu = (P256_DIGIT(a, i) >> n);
+    accu |= (P256_DIGIT(a, i + 1) << (P256_BITSPERDIGIT - n));
+    P256_DIGIT(b, i) = accu;
+  }
+  P256_DIGIT(b, i) = (P256_DIGIT(a, i) >> n);
+}
+
+static void p256_shr1(const p256_int* a, int highbit, p256_int* b) {
+  int i;
+
+  for (i = 0; i < P256_NDIGITS - 1; ++i) {
+    p256_digit accu = (P256_DIGIT(a, i) >> 1);
+    accu |= (P256_DIGIT(a, i + 1) << (P256_BITSPERDIGIT - 1));
+    P256_DIGIT(b, i) = accu;
+  }
+  P256_DIGIT(b, i) = (P256_DIGIT(a, i) >> 1) |
+      (highbit << (P256_BITSPERDIGIT - 1));
+}
+
+// Return -1, 0, 1 for a < b, a == b or a > b respectively.
+int p256_cmp(const p256_int* a, const p256_int* b) {
+  int i;
+  p256_sddigit borrow = 0;
+  p256_digit notzero = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    borrow += (p256_sddigit)P256_DIGIT(a, i) - P256_DIGIT(b, i);
+    // Track whether any result digit is ever not zero.
+    // Relies on !!(non-zero) evaluating to 1, e.g., !!(-1) evaluating to 1.
+    notzero |= !!((p256_digit)borrow);
+    borrow >>= P256_BITSPERDIGIT;
+  }
+  return (int)borrow | notzero;
+}
+
+// c = a - b. Returns borrow: 0 or -1.
+int p256_sub(const p256_int* a, const p256_int* b, p256_int* c) {
+  int i;
+  p256_sddigit borrow = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    borrow += (p256_sddigit)P256_DIGIT(a, i) - P256_DIGIT(b, i);
+    if (c) P256_DIGIT(c, i) = (p256_digit)borrow;
+    borrow >>= P256_BITSPERDIGIT;
+  }
+  return (int)borrow;
+}
+
+// c = a + b. Returns carry: 0 or 1.
+int p256_add(const p256_int* a, const p256_int* b, p256_int* c) {
+  int i;
+  p256_ddigit carry = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    carry += (p256_ddigit)P256_DIGIT(a, i) + P256_DIGIT(b, i);
+    if (c) P256_DIGIT(c, i) = (p256_digit)carry;
+    carry >>= P256_BITSPERDIGIT;
+  }
+  return (int)carry;
+}
+
+// b = a + d. Returns carry, 0 or 1.
+int p256_add_d(const p256_int* a, p256_digit d, p256_int* b) {
+  int i;
+  p256_ddigit carry = d;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    carry += (p256_ddigit)P256_DIGIT(a, i);
+    if (b) P256_DIGIT(b, i) = (p256_digit)carry;
+    carry >>= P256_BITSPERDIGIT;
+  }
+  return (int)carry;
+}
+
+// b = 1/a mod MOD, binary euclid.
+void p256_modinv_vartime(const p256_int* MOD,
+                         const p256_int* a,
+                         p256_int* b) {
+  p256_int R = P256_ZERO;
+  p256_int S = P256_ONE;
+  p256_int U = *MOD;
+  p256_int V = *a;
+
+  for (;;) {
+    if (p256_is_even(&U)) {
+      p256_shr1(&U, 0, &U);
+      if (p256_is_even(&R)) {
+        p256_shr1(&R, 0, &R);
+      } else {
+        // R = (R+MOD)/2
+        p256_shr1(&R, p256_add(&R, MOD, &R), &R);
+      }
+    } else if (p256_is_even(&V)) {
+      p256_shr1(&V, 0, &V);
+      if (p256_is_even(&S)) {
+        p256_shr1(&S, 0, &S);
+      } else {
+        // S = (S+MOD)/2
+        p256_shr1(&S, p256_add(&S, MOD, &S) , &S);
+      }
+    } else {  // U,V both odd.
+      if (!p256_sub(&V, &U, NULL)) {
+        p256_sub(&V, &U, &V);
+        if (p256_sub(&S, &R, &S)) p256_add(&S, MOD, &S);
+        if (p256_is_zero(&V)) break;  // done.
+      } else {
+        p256_sub(&U, &V, &U);
+        if (p256_sub(&R, &S, &R)) p256_add(&R, MOD, &R);
+      }
+    }
+  }
+
+  p256_mod(MOD, &R, b);
+}
+
+void p256_mod(const p256_int* MOD,
+              const p256_int* in,
+              p256_int* out) {
+  if (out != in) *out = *in;
+  addM(MOD, 0, P256_DIGITS(out), subM(MOD, 0, P256_DIGITS(out), -1));
+}
+
+// Verify y^2 == x^3 - 3x + b mod p
+// and 0 < x < p and 0 < y < p
+int p256_is_valid_point(const p256_int* x, const p256_int* y) {
+  p256_int y2, x3;
+
+  if (p256_cmp(&SECP256r1_p, x) <= 0 ||
+      p256_cmp(&SECP256r1_p, y) <= 0 ||
+      p256_is_zero(x) ||
+      p256_is_zero(y)) return 0;
+
+  p256_modmul(&SECP256r1_p, y, 0, y, &y2);  // y^2
+
+  p256_modmul(&SECP256r1_p, x, 0, x, &x3);  // x^2
+  p256_modmul(&SECP256r1_p, x, 0, &x3, &x3);  // x^3
+  if (p256_sub(&x3, x, &x3)) p256_add(&x3, &SECP256r1_p, &x3);  // x^3 - x
+  if (p256_sub(&x3, x, &x3)) p256_add(&x3, &SECP256r1_p, &x3);  // x^3 - 2x
+  if (p256_sub(&x3, x, &x3)) p256_add(&x3, &SECP256r1_p, &x3);  // x^3 - 3x
+  if (p256_add(&x3, &SECP256r1_b, &x3))  // x^3 - 3x + b
+    p256_sub(&x3, &SECP256r1_p, &x3);
+
+  return p256_cmp(&y2, &x3) == 0;
+}
+
+void p256_from_bin(const uint8_t src[P256_NBYTES], p256_int* dst) {
+  int i;
+  const uint8_t* p = &src[0];
+
+  for (i = P256_NDIGITS - 1; i >= 0; --i) {
+    P256_DIGIT(dst, i) =
+        (p[0] << 24) |
+        (p[1] << 16) |
+        (p[2] << 8) |
+        p[3];
+    p += 4;
+  }
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256_ec.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256_ec.c
new file mode 100755
index 0000000..90262cc
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256_ec.c
@@ -0,0 +1,1279 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This is an implementation of the P256 elliptic curve group. It's written to
+// be portable 32-bit, although it's still constant-time.
+//
+// WARNING: Implementing these functions in a constant-time manner is far from
+//          obvious. Be careful when touching this code.
+//
+// See http://www.imperialviolet.org/2010/12/04/ecc.html ([1]) for background.
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "mincrypt/p256.h"
+
+typedef uint8_t u8;
+typedef uint32_t u32;
+typedef int32_t s32;
+typedef uint64_t u64;
+
+/* Our field elements are represented as nine 32-bit limbs.
+ *
+ * The value of an felem (field element) is:
+ *   x[0] + (x[1] * 2**29) + (x[2] * 2**57) + ... + (x[8] * 2**228)
+ *
+ * That is, each limb is alternately 29 or 28-bits wide in little-endian
+ * order.
+ *
+ * This means that an felem hits 2**257, rather than 2**256 as we would like. A
+ * 28, 29, ... pattern would cause us to hit 2**256, but that causes problems
+ * when multiplying as terms end up one bit short of a limb which would require
+ * much bit-shifting to correct.
+ *
+ * Finally, the values stored in an felem are in Montgomery form. So the value
+ * |y| is stored as (y*R) mod p, where p is the P-256 prime and R is 2**257.
+ */
+typedef u32 limb;
+#define NLIMBS 9
+typedef limb felem[NLIMBS];
+
+static const limb kBottom28Bits = 0xfffffff;
+static const limb kBottom29Bits = 0x1fffffff;
+
+/* kOne is the number 1 as an felem. It's 2**257 mod p split up into 29 and
+ * 28-bit words. */
+static const felem kOne = {
+    2, 0, 0, 0xffff800,
+    0x1fffffff, 0xfffffff, 0x1fbfffff, 0x1ffffff,
+    0
+};
+static const felem kZero = {0};
+static const felem kP = {
+    0x1fffffff, 0xfffffff, 0x1fffffff, 0x3ff,
+    0, 0, 0x200000, 0xf000000,
+    0xfffffff
+};
+static const felem k2P = {
+    0x1ffffffe, 0xfffffff, 0x1fffffff, 0x7ff,
+    0, 0, 0x400000, 0xe000000,
+    0x1fffffff
+};
+/* kPrecomputed contains precomputed values to aid the calculation of scalar
+ * multiples of the base point, G. It's actually two, equal length, tables
+ * concatenated.
+ *
+ * The first table contains (x,y) felem pairs for 16 multiples of the base
+ * point, G.
+ *
+ *   Index  |  Index (binary) | Value
+ *       0  |           0000  | 0G (all zeros, omitted)
+ *       1  |           0001  | G
+ *       2  |           0010  | 2**64G
+ *       3  |           0011  | 2**64G + G
+ *       4  |           0100  | 2**128G
+ *       5  |           0101  | 2**128G + G
+ *       6  |           0110  | 2**128G + 2**64G
+ *       7  |           0111  | 2**128G + 2**64G + G
+ *       8  |           1000  | 2**192G
+ *       9  |           1001  | 2**192G + G
+ *      10  |           1010  | 2**192G + 2**64G
+ *      11  |           1011  | 2**192G + 2**64G + G
+ *      12  |           1100  | 2**192G + 2**128G
+ *      13  |           1101  | 2**192G + 2**128G + G
+ *      14  |           1110  | 2**192G + 2**128G + 2**64G
+ *      15  |           1111  | 2**192G + 2**128G + 2**64G + G
+ *
+ * The second table follows the same style, but the terms are 2**32G,
+ * 2**96G, 2**160G, 2**224G.
+ *
+ * This is ~2KB of data. */
+static const limb kPrecomputed[NLIMBS * 2 * 15 * 2] = {
+    0x11522878, 0xe730d41, 0xdb60179, 0x4afe2ff, 0x12883add, 0xcaddd88, 0x119e7edc, 0xd4a6eab, 0x3120bee,
+    0x1d2aac15, 0xf25357c, 0x19e45cdd, 0x5c721d0, 0x1992c5a5, 0xa237487, 0x154ba21, 0x14b10bb, 0xae3fe3,
+    0xd41a576, 0x922fc51, 0x234994f, 0x60b60d3, 0x164586ae, 0xce95f18, 0x1fe49073, 0x3fa36cc, 0x5ebcd2c,
+    0xb402f2f, 0x15c70bf, 0x1561925c, 0x5a26704, 0xda91e90, 0xcdc1c7f, 0x1ea12446, 0xe1ade1e, 0xec91f22,
+    0x26f7778, 0x566847e, 0xa0bec9e, 0x234f453, 0x1a31f21a, 0xd85e75c, 0x56c7109, 0xa267a00, 0xb57c050,
+    0x98fb57, 0xaa837cc, 0x60c0792, 0xcfa5e19, 0x61bab9e, 0x589e39b, 0xa324c5, 0x7d6dee7, 0x2976e4b,
+    0x1fc4124a, 0xa8c244b, 0x1ce86762, 0xcd61c7e, 0x1831c8e0, 0x75774e1, 0x1d96a5a9, 0x843a649, 0xc3ab0fa,
+    0x6e2e7d5, 0x7673a2a, 0x178b65e8, 0x4003e9b, 0x1a1f11c2, 0x7816ea, 0xf643e11, 0x58c43df, 0xf423fc2,
+    0x19633ffa, 0x891f2b2, 0x123c231c, 0x46add8c, 0x54700dd, 0x59e2b17, 0x172db40f, 0x83e277d, 0xb0dd609,
+    0xfd1da12, 0x35c6e52, 0x19ede20c, 0xd19e0c0, 0x97d0f40, 0xb015b19, 0x449e3f5, 0xe10c9e, 0x33ab581,
+    0x56a67ab, 0x577734d, 0x1dddc062, 0xc57b10d, 0x149b39d, 0x26a9e7b, 0xc35df9f, 0x48764cd, 0x76dbcca,
+    0xca4b366, 0xe9303ab, 0x1a7480e7, 0x57e9e81, 0x1e13eb50, 0xf466cf3, 0x6f16b20, 0x4ba3173, 0xc168c33,
+    0x15cb5439, 0x6a38e11, 0x73658bd, 0xb29564f, 0x3f6dc5b, 0x53b97e, 0x1322c4c0, 0x65dd7ff, 0x3a1e4f6,
+    0x14e614aa, 0x9246317, 0x1bc83aca, 0xad97eed, 0xd38ce4a, 0xf82b006, 0x341f077, 0xa6add89, 0x4894acd,
+    0x9f162d5, 0xf8410ef, 0x1b266a56, 0xd7f223, 0x3e0cb92, 0xe39b672, 0x6a2901a, 0x69a8556, 0x7e7c0,
+    0x9b7d8d3, 0x309a80, 0x1ad05f7f, 0xc2fb5dd, 0xcbfd41d, 0x9ceb638, 0x1051825c, 0xda0cf5b, 0x812e881,
+    0x6f35669, 0x6a56f2c, 0x1df8d184, 0x345820, 0x1477d477, 0x1645db1, 0xbe80c51, 0xc22be3e, 0xe35e65a,
+    0x1aeb7aa0, 0xc375315, 0xf67bc99, 0x7fdd7b9, 0x191fc1be, 0x61235d, 0x2c184e9, 0x1c5a839, 0x47a1e26,
+    0xb7cb456, 0x93e225d, 0x14f3c6ed, 0xccc1ac9, 0x17fe37f3, 0x4988989, 0x1a90c502, 0x2f32042, 0xa17769b,
+    0xafd8c7c, 0x8191c6e, 0x1dcdb237, 0x16200c0, 0x107b32a1, 0x66c08db, 0x10d06a02, 0x3fc93, 0x5620023,
+    0x16722b27, 0x68b5c59, 0x270fcfc, 0xfad0ecc, 0xe5de1c2, 0xeab466b, 0x2fc513c, 0x407f75c, 0xbaab133,
+    0x9705fe9, 0xb88b8e7, 0x734c993, 0x1e1ff8f, 0x19156970, 0xabd0f00, 0x10469ea7, 0x3293ac0, 0xcdc98aa,
+    0x1d843fd, 0xe14bfe8, 0x15be825f, 0x8b5212, 0xeb3fb67, 0x81cbd29, 0xbc62f16, 0x2b6fcc7, 0xf5a4e29,
+    0x13560b66, 0xc0b6ac2, 0x51ae690, 0xd41e271, 0xf3e9bd4, 0x1d70aab, 0x1029f72, 0x73e1c35, 0xee70fbc,
+    0xad81baf, 0x9ecc49a, 0x86c741e, 0xfe6be30, 0x176752e7, 0x23d416, 0x1f83de85, 0x27de188, 0x66f70b8,
+    0x181cd51f, 0x96b6e4c, 0x188f2335, 0xa5df759, 0x17a77eb6, 0xfeb0e73, 0x154ae914, 0x2f3ec51, 0x3826b59,
+    0xb91f17d, 0x1c72949, 0x1362bf0a, 0xe23fddf, 0xa5614b0, 0xf7d8f, 0x79061, 0x823d9d2, 0x8213f39,
+    0x1128ae0b, 0xd095d05, 0xb85c0c2, 0x1ecb2ef, 0x24ddc84, 0xe35e901, 0x18411a4a, 0xf5ddc3d, 0x3786689,
+    0x52260e8, 0x5ae3564, 0x542b10d, 0x8d93a45, 0x19952aa4, 0x996cc41, 0x1051a729, 0x4be3499, 0x52b23aa,
+    0x109f307e, 0x6f5b6bb, 0x1f84e1e7, 0x77a0cfa, 0x10c4df3f, 0x25a02ea, 0xb048035, 0xe31de66, 0xc6ecaa3,
+    0x28ea335, 0x2886024, 0x1372f020, 0xf55d35, 0x15e4684c, 0xf2a9e17, 0x1a4a7529, 0xcb7beb1, 0xb2a78a1,
+    0x1ab21f1f, 0x6361ccf, 0x6c9179d, 0xb135627, 0x1267b974, 0x4408bad, 0x1cbff658, 0xe3d6511, 0xc7d76f,
+    0x1cc7a69, 0xe7ee31b, 0x54fab4f, 0x2b914f, 0x1ad27a30, 0xcd3579e, 0xc50124c, 0x50daa90, 0xb13f72,
+    0xb06aa75, 0x70f5cc6, 0x1649e5aa, 0x84a5312, 0x329043c, 0x41c4011, 0x13d32411, 0xb04a838, 0xd760d2d,
+    0x1713b532, 0xbaa0c03, 0x84022ab, 0x6bcf5c1, 0x2f45379, 0x18ae070, 0x18c9e11e, 0x20bca9a, 0x66f496b,
+    0x3eef294, 0x67500d2, 0xd7f613c, 0x2dbbeb, 0xb741038, 0xe04133f, 0x1582968d, 0xbe985f7, 0x1acbc1a,
+    0x1a6a939f, 0x33e50f6, 0xd665ed4, 0xb4b7bd6, 0x1e5a3799, 0x6b33847, 0x17fa56ff, 0x65ef930, 0x21dc4a,
+    0x2b37659, 0x450fe17, 0xb357b65, 0xdf5efac, 0x15397bef, 0x9d35a7f, 0x112ac15f, 0x624e62e, 0xa90ae2f,
+    0x107eecd2, 0x1f69bbe, 0x77d6bce, 0x5741394, 0x13c684fc, 0x950c910, 0x725522b, 0xdc78583, 0x40eeabb,
+    0x1fde328a, 0xbd61d96, 0xd28c387, 0x9e77d89, 0x12550c40, 0x759cb7d, 0x367ef34, 0xae2a960, 0x91b8bdc,
+    0x93462a9, 0xf469ef, 0xb2e9aef, 0xd2ca771, 0x54e1f42, 0x7aaa49, 0x6316abb, 0x2413c8e, 0x5425bf9,
+    0x1bed3e3a, 0xf272274, 0x1f5e7326, 0x6416517, 0xea27072, 0x9cedea7, 0x6e7633, 0x7c91952, 0xd806dce,
+    0x8e2a7e1, 0xe421e1a, 0x418c9e1, 0x1dbc890, 0x1b395c36, 0xa1dc175, 0x1dc4ef73, 0x8956f34, 0xe4b5cf2,
+    0x1b0d3a18, 0x3194a36, 0x6c2641f, 0xe44124c, 0xa2f4eaa, 0xa8c25ba, 0xf927ed7, 0x627b614, 0x7371cca,
+    0xba16694, 0x417bc03, 0x7c0a7e3, 0x9c35c19, 0x1168a205, 0x8b6b00d, 0x10e3edc9, 0x9c19bf2, 0x5882229,
+    0x1b2b4162, 0xa5cef1a, 0x1543622b, 0x9bd433e, 0x364e04d, 0x7480792, 0x5c9b5b3, 0xe85ff25, 0x408ef57,
+    0x1814cfa4, 0x121b41b, 0xd248a0f, 0x3b05222, 0x39bb16a, 0xc75966d, 0xa038113, 0xa4a1769, 0x11fbc6c,
+    0x917e50e, 0xeec3da8, 0x169d6eac, 0x10c1699, 0xa416153, 0xf724912, 0x15cd60b7, 0x4acbad9, 0x5efc5fa,
+    0xf150ed7, 0x122b51, 0x1104b40a, 0xcb7f442, 0xfbb28ff, 0x6ac53ca, 0x196142cc, 0x7bf0fa9, 0x957651,
+    0x4e0f215, 0xed439f8, 0x3f46bd5, 0x5ace82f, 0x110916b6, 0x6db078, 0xffd7d57, 0xf2ecaac, 0xca86dec,
+    0x15d6b2da, 0x965ecc9, 0x1c92b4c2, 0x1f3811, 0x1cb080f5, 0x2d8b804, 0x19d1c12d, 0xf20bd46, 0x1951fa7,
+    0xa3656c3, 0x523a425, 0xfcd0692, 0xd44ddc8, 0x131f0f5b, 0xaf80e4a, 0xcd9fc74, 0x99bb618, 0x2db944c,
+    0xa673090, 0x1c210e1, 0x178c8d23, 0x1474383, 0x10b8743d, 0x985a55b, 0x2e74779, 0x576138, 0x9587927,
+    0x133130fa, 0xbe05516, 0x9f4d619, 0xbb62570, 0x99ec591, 0xd9468fe, 0x1d07782d, 0xfc72e0b, 0x701b298,
+    0x1863863b, 0x85954b8, 0x121a0c36, 0x9e7fedf, 0xf64b429, 0x9b9d71e, 0x14e2f5d8, 0xf858d3a, 0x942eea8,
+    0xda5b765, 0x6edafff, 0xa9d18cc, 0xc65e4ba, 0x1c747e86, 0xe4ea915, 0x1981d7a1, 0x8395659, 0x52ed4e2,
+    0x87d43b7, 0x37ab11b, 0x19d292ce, 0xf8d4692, 0x18c3053f, 0x8863e13, 0x4c146c0, 0x6bdf55a, 0x4e4457d,
+    0x16152289, 0xac78ec2, 0x1a59c5a2, 0x2028b97, 0x71c2d01, 0x295851f, 0x404747b, 0x878558d, 0x7d29aa4,
+    0x13d8341f, 0x8daefd7, 0x139c972d, 0x6b7ea75, 0xd4a9dde, 0xff163d8, 0x81d55d7, 0xa5bef68, 0xb7b30d8,
+    0xbe73d6f, 0xaa88141, 0xd976c81, 0x7e7a9cc, 0x18beb771, 0xd773cbd, 0x13f51951, 0x9d0c177, 0x1c49a78,
+};
+
+
+/* Field element operations: */
+
+/* NON_ZERO_TO_ALL_ONES returns:
+ *   0xffffffff for 0 < x <= 2**31
+ *   0 for x == 0 or x > 2**31.
+ *
+ * x must be a u32 or an equivalent type such as limb. */
+#define NON_ZERO_TO_ALL_ONES(x) ((((u32)(x) - 1) >> 31) - 1)
+
+/* felem_reduce_carry adds a multiple of p in order to cancel |carry|,
+ * which is a term at 2**257.
+ *
+ * On entry: carry < 2**3, inout[0,2,...] < 2**29, inout[1,3,...] < 2**28.
+ * On exit: inout[0,2,..] < 2**30, inout[1,3,...] < 2**29. */
+static void felem_reduce_carry(felem inout, limb carry) {
+  const u32 carry_mask = NON_ZERO_TO_ALL_ONES(carry);
+
+  inout[0] += carry << 1;
+  inout[3] += 0x10000000 & carry_mask;
+  /* carry < 2**3 thus (carry << 11) < 2**14 and we added 2**28 in the
+   * previous line therefore this doesn't underflow. */
+  inout[3] -= carry << 11;
+  inout[4] += (0x20000000 - 1) & carry_mask;
+  inout[5] += (0x10000000 - 1) & carry_mask;
+  inout[6] += (0x20000000 - 1) & carry_mask;
+  inout[6] -= carry << 22;
+  /* This may underflow if carry is non-zero but, if so, we'll fix it in the
+   * next line. */
+  inout[7] -= 1 & carry_mask;
+  inout[7] += carry << 25;
+}
+
+/* felem_sum sets out = in+in2.
+ *
+ * On entry, in[i]+in2[i] must not overflow a 32-bit word.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29 */
+static void felem_sum(felem out, const felem in, const felem in2) {
+  limb carry = 0;
+  unsigned i;
+
+  for (i = 0;; i++) {
+    out[i] = in[i] + in2[i];
+    out[i] += carry;
+    carry = out[i] >> 29;
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    out[i] = in[i] + in2[i];
+    out[i] += carry;
+    carry = out[i] >> 28;
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+#define two31m3 (((limb)1) << 31) - (((limb)1) << 3)
+#define two30m2 (((limb)1) << 30) - (((limb)1) << 2)
+#define two30p13m2 (((limb)1) << 30) + (((limb)1) << 13) - (((limb)1) << 2)
+#define two31m2 (((limb)1) << 31) - (((limb)1) << 2)
+#define two31p24m2 (((limb)1) << 31) + (((limb)1) << 24) - (((limb)1) << 2)
+#define two30m27m2 (((limb)1) << 30) - (((limb)1) << 27) - (((limb)1) << 2)
+
+/* zero31 is 0 mod p. */
+static const felem zero31 = { two31m3, two30m2, two31m2, two30p13m2, two31m2, two30m2, two31p24m2, two30m27m2, two31m2 };
+
+/* felem_diff sets out = in-in2.
+ *
+ * On entry: in[0,2,...] < 2**30, in[1,3,...] < 2**29 and
+ *           in2[0,2,...] < 2**30, in2[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_diff(felem out, const felem in, const felem in2) {
+  limb carry = 0;
+  unsigned i;
+
+   for (i = 0;; i++) {
+    out[i] = in[i] - in2[i];
+    out[i] += zero31[i];
+    out[i] += carry;
+    carry = out[i] >> 29;
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    out[i] = in[i] - in2[i];
+    out[i] += zero31[i];
+    out[i] += carry;
+    carry = out[i] >> 28;
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_reduce_degree sets out = tmp/R mod p where tmp contains 64-bit words
+ * with the same 29,28,... bit positions as an felem.
+ *
+ * The values in felems are in Montgomery form: x*R mod p where R = 2**257.
+ * Since we just multiplied two Montgomery values together, the result is
+ * x*y*R*R mod p. We wish to divide by R in order for the result also to be
+ * in Montgomery form.
+ *
+ * On entry: tmp[i] < 2**64
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29 */
+static void felem_reduce_degree(felem out, u64 tmp[17]) {
+   /* The following table may be helpful when reading this code:
+    *
+    * Limb number:   0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10...
+    * Width (bits):  29| 28| 29| 28| 29| 28| 29| 28| 29| 28| 29
+    * Start bit:     0 | 29| 57| 86|114|143|171|200|228|257|285
+    *   (odd phase): 0 | 28| 57| 85|114|142|171|199|228|256|285 */
+  limb tmp2[18], carry, x, xMask;
+  unsigned i;
+
+  /* tmp contains 64-bit words with the same 29,28,29-bit positions as an
+   * felem. So the top of an element of tmp might overlap with another
+   * element two positions down. The following loop eliminates this
+   * overlap. */
+  tmp2[0] = (limb)(tmp[0] & kBottom29Bits);
+
+  /* In the following we use "(limb) tmp[x]" and "(limb) (tmp[x]>>32)" to try
+   * and hint to the compiler that it can do a single-word shift by selecting
+   * the right register rather than doing a double-word shift and truncating
+   * afterwards. */
+  tmp2[1] = ((limb) tmp[0]) >> 29;
+  tmp2[1] |= (((limb)(tmp[0] >> 32)) << 3) & kBottom28Bits;
+  tmp2[1] += ((limb) tmp[1]) & kBottom28Bits;
+  carry = tmp2[1] >> 28;
+  tmp2[1] &= kBottom28Bits;
+
+  for (i = 2; i < 17; i++) {
+    tmp2[i] = ((limb)(tmp[i - 2] >> 32)) >> 25;
+    tmp2[i] += ((limb)(tmp[i - 1])) >> 28;
+    tmp2[i] += (((limb)(tmp[i - 1] >> 32)) << 4) & kBottom29Bits;
+    tmp2[i] += ((limb) tmp[i]) & kBottom29Bits;
+    tmp2[i] += carry;
+    carry = tmp2[i] >> 29;
+    tmp2[i] &= kBottom29Bits;
+
+    i++;
+    if (i == 17)
+      break;
+    tmp2[i] = ((limb)(tmp[i - 2] >> 32)) >> 25;
+    tmp2[i] += ((limb)(tmp[i - 1])) >> 29;
+    tmp2[i] += (((limb)(tmp[i - 1] >> 32)) << 3) & kBottom28Bits;
+    tmp2[i] += ((limb) tmp[i]) & kBottom28Bits;
+    tmp2[i] += carry;
+    carry = tmp2[i] >> 28;
+    tmp2[i] &= kBottom28Bits;
+  }
+
+  tmp2[17] = ((limb)(tmp[15] >> 32)) >> 25;
+  tmp2[17] += ((limb)(tmp[16])) >> 29;
+  tmp2[17] += (((limb)(tmp[16] >> 32)) << 3);
+  tmp2[17] += carry;
+
+  /* Montgomery elimination of terms.
+   *
+   * Since R is 2**257, we can divide by R with a bitwise shift if we can
+   * ensure that the right-most 257 bits are all zero. We can make that true by
+   * adding multiplies of p without affecting the value.
+   *
+   * So we eliminate limbs from right to left. Since the bottom 29 bits of p
+   * are all ones, then by adding tmp2[0]*p to tmp2 we'll make tmp2[0] == 0.
+   * We can do that for 8 further limbs and then right shift to eliminate the
+   * extra factor of R. */
+  for (i = 0;; i += 2) {
+    tmp2[i + 1] += tmp2[i] >> 29;
+    x = tmp2[i] & kBottom29Bits;
+    xMask = NON_ZERO_TO_ALL_ONES(x);
+    tmp2[i] = 0;
+
+    /* The bounds calculations for this loop are tricky. Each iteration of
+     * the loop eliminates two words by adding values to words to their
+     * right.
+     *
+     * The following table contains the amounts added to each word (as an
+     * offset from the value of i at the top of the loop). The amounts are
+     * accounted for from the first and second half of the loop separately
+     * and are written as, for example, 28 to mean a value <2**28.
+     *
+     * Word:                   3   4   5   6   7   8   9   10
+     * Added in top half:     28  11      29  21  29  28
+     *                                        28  29
+     *                                            29
+     * Added in bottom half:      29  10      28  21  28   28
+     *                                            29
+     *
+     * The value that is currently offset 7 will be offset 5 for the next
+     * iteration and then offset 3 for the iteration after that. Therefore
+     * the total value added will be the values added at 7, 5 and 3.
+     *
+     * The following table accumulates these values. The sums at the bottom
+     * are written as, for example, 29+28, to mean a value < 2**29+2**28.
+     *
+     * Word:                   3   4   5   6   7   8   9  10  11  12  13
+     *                        28  11  10  29  21  29  28  28  28  28  28
+     *                            29  28  11  28  29  28  29  28  29  28
+     *                                    29  28  21  21  29  21  29  21
+     *                                        10  29  28  21  28  21  28
+     *                                        28  29  28  29  28  29  28
+     *                                            11  10  29  10  29  10
+     *                                            29  28  11  28  11
+     *                                                    29      29
+     *                        --------------------------------------------
+     *                                                30+ 31+ 30+ 31+ 30+
+     *                                                28+ 29+ 28+ 29+ 21+
+     *                                                21+ 28+ 21+ 28+ 10
+     *                                                10  21+ 10  21+
+     *                                                    11      11
+     *
+     * So the greatest amount is added to tmp2[10] and tmp2[12]. If
+     * tmp2[10/12] has an initial value of <2**29, then the maximum value
+     * will be < 2**31 + 2**30 + 2**28 + 2**21 + 2**11, which is < 2**32,
+     * as required. */
+    tmp2[i + 3] += (x << 10) & kBottom28Bits;
+    tmp2[i + 4] += (x >> 18);
+
+    tmp2[i + 6] += (x << 21) & kBottom29Bits;
+    tmp2[i + 7] += x >> 8;
+
+    /* At position 200, which is the starting bit position for word 7, we
+     * have a factor of 0xf000000 = 2**28 - 2**24. */
+    tmp2[i + 7] += 0x10000000 & xMask;
+    /* Word 7 is 28 bits wide, so the 2**28 term exactly hits word 8. */
+    tmp2[i + 8] += (x - 1) & xMask;
+    tmp2[i + 7] -= (x << 24) & kBottom28Bits;
+    tmp2[i + 8] -= x >> 4;
+
+    tmp2[i + 8] += 0x20000000 & xMask;
+    tmp2[i + 8] -= x;
+    tmp2[i + 8] += (x << 28) & kBottom29Bits;
+    tmp2[i + 9] += ((x >> 1) - 1) & xMask;
+
+    if (i+1 == NLIMBS)
+      break;
+    tmp2[i + 2] += tmp2[i + 1] >> 28;
+    x = tmp2[i + 1] & kBottom28Bits;
+    xMask = NON_ZERO_TO_ALL_ONES(x);
+    tmp2[i + 1] = 0;
+
+    tmp2[i + 4] += (x << 11) & kBottom29Bits;
+    tmp2[i + 5] += (x >> 18);
+
+    tmp2[i + 7] += (x << 21) & kBottom28Bits;
+    tmp2[i + 8] += x >> 7;
+
+    /* At position 199, which is the starting bit of the 8th word when
+     * dealing with a context starting on an odd word, we have a factor of
+     * 0x1e000000 = 2**29 - 2**25. Since we have not updated i, the 8th
+     * word from i+1 is i+8. */
+    tmp2[i + 8] += 0x20000000 & xMask;
+    tmp2[i + 9] += (x - 1) & xMask;
+    tmp2[i + 8] -= (x << 25) & kBottom29Bits;
+    tmp2[i + 9] -= x >> 4;
+
+    tmp2[i + 9] += 0x10000000 & xMask;
+    tmp2[i + 9] -= x;
+    tmp2[i + 10] += (x - 1) & xMask;
+  }
+
+  /* We merge the right shift with a carry chain. The words above 2**257 have
+   * widths of 28,29,... which we need to correct when copying them down.  */
+  carry = 0;
+  for (i = 0; i < 8; i++) {
+    /* The maximum value of tmp2[i + 9] occurs on the first iteration and
+     * is < 2**30+2**29+2**28. Adding 2**29 (from tmp2[i + 10]) is
+     * therefore safe. */
+    out[i] = tmp2[i + 9];
+    out[i] += carry;
+    out[i] += (tmp2[i + 10] << 28) & kBottom29Bits;
+    carry = out[i] >> 29;
+    out[i] &= kBottom29Bits;
+
+    i++;
+    out[i] = tmp2[i + 9] >> 1;
+    out[i] += carry;
+    carry = out[i] >> 28;
+    out[i] &= kBottom28Bits;
+  }
+
+  out[8] = tmp2[17];
+  out[8] += carry;
+  carry = out[8] >> 29;
+  out[8] &= kBottom29Bits;
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_square sets out=in*in.
+ *
+ * On entry: in[0,2,...] < 2**30, in[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_square(felem out, const felem in) {
+  u64 tmp[17];
+
+  tmp[0] = ((u64) in[0]) * in[0];
+  tmp[1] = ((u64) in[0]) * (in[1] << 1);
+  tmp[2] = ((u64) in[0]) * (in[2] << 1) +
+           ((u64) in[1]) * (in[1] << 1);
+  tmp[3] = ((u64) in[0]) * (in[3] << 1) +
+           ((u64) in[1]) * (in[2] << 1);
+  tmp[4] = ((u64) in[0]) * (in[4] << 1) +
+           ((u64) in[1]) * (in[3] << 2) + ((u64) in[2]) * in[2];
+  tmp[5] = ((u64) in[0]) * (in[5] << 1) + ((u64) in[1]) *
+           (in[4] << 1) + ((u64) in[2]) * (in[3] << 1);
+  tmp[6] = ((u64) in[0]) * (in[6] << 1) + ((u64) in[1]) *
+           (in[5] << 2) + ((u64) in[2]) * (in[4] << 1) +
+           ((u64) in[3]) * (in[3] << 1);
+  tmp[7] = ((u64) in[0]) * (in[7] << 1) + ((u64) in[1]) *
+           (in[6] << 1) + ((u64) in[2]) * (in[5] << 1) +
+           ((u64) in[3]) * (in[4] << 1);
+  /* tmp[8] has the greatest value of 2**61 + 2**60 + 2**61 + 2**60 + 2**60,
+   * which is < 2**64 as required. */
+  tmp[8] = ((u64) in[0]) * (in[8] << 1) + ((u64) in[1]) *
+           (in[7] << 2) + ((u64) in[2]) * (in[6] << 1) +
+           ((u64) in[3]) * (in[5] << 2) + ((u64) in[4]) * in[4];
+  tmp[9] = ((u64) in[1]) * (in[8] << 1) + ((u64) in[2]) *
+           (in[7] << 1) + ((u64) in[3]) * (in[6] << 1) +
+           ((u64) in[4]) * (in[5] << 1);
+  tmp[10] = ((u64) in[2]) * (in[8] << 1) + ((u64) in[3]) *
+            (in[7] << 2) + ((u64) in[4]) * (in[6] << 1) +
+            ((u64) in[5]) * (in[5] << 1);
+  tmp[11] = ((u64) in[3]) * (in[8] << 1) + ((u64) in[4]) *
+            (in[7] << 1) + ((u64) in[5]) * (in[6] << 1);
+  tmp[12] = ((u64) in[4]) * (in[8] << 1) +
+            ((u64) in[5]) * (in[7] << 2) + ((u64) in[6]) * in[6];
+  tmp[13] = ((u64) in[5]) * (in[8] << 1) +
+            ((u64) in[6]) * (in[7] << 1);
+  tmp[14] = ((u64) in[6]) * (in[8] << 1) +
+            ((u64) in[7]) * (in[7] << 1);
+  tmp[15] = ((u64) in[7]) * (in[8] << 1);
+  tmp[16] = ((u64) in[8]) * in[8];
+
+  felem_reduce_degree(out, tmp);
+}
+
+/* felem_mul sets out=in*in2.
+ *
+ * On entry: in[0,2,...] < 2**30, in[1,3,...] < 2**29 and
+ *           in2[0,2,...] < 2**30, in2[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_mul(felem out, const felem in, const felem in2) {
+  u64 tmp[17];
+
+  tmp[0] = ((u64) in[0]) * in2[0];
+  tmp[1] = ((u64) in[0]) * (in2[1] << 0) +
+           ((u64) in[1]) * (in2[0] << 0);
+  tmp[2] = ((u64) in[0]) * (in2[2] << 0) + ((u64) in[1]) *
+           (in2[1] << 1) + ((u64) in[2]) * (in2[0] << 0);
+  tmp[3] = ((u64) in[0]) * (in2[3] << 0) + ((u64) in[1]) *
+           (in2[2] << 0) + ((u64) in[2]) * (in2[1] << 0) +
+           ((u64) in[3]) * (in2[0] << 0);
+  tmp[4] = ((u64) in[0]) * (in2[4] << 0) + ((u64) in[1]) *
+           (in2[3] << 1) + ((u64) in[2]) * (in2[2] << 0) +
+           ((u64) in[3]) * (in2[1] << 1) +
+           ((u64) in[4]) * (in2[0] << 0);
+  tmp[5] = ((u64) in[0]) * (in2[5] << 0) + ((u64) in[1]) *
+           (in2[4] << 0) + ((u64) in[2]) * (in2[3] << 0) +
+           ((u64) in[3]) * (in2[2] << 0) + ((u64) in[4]) *
+           (in2[1] << 0) + ((u64) in[5]) * (in2[0] << 0);
+  tmp[6] = ((u64) in[0]) * (in2[6] << 0) + ((u64) in[1]) *
+           (in2[5] << 1) + ((u64) in[2]) * (in2[4] << 0) +
+           ((u64) in[3]) * (in2[3] << 1) + ((u64) in[4]) *
+           (in2[2] << 0) + ((u64) in[5]) * (in2[1] << 1) +
+           ((u64) in[6]) * (in2[0] << 0);
+  tmp[7] = ((u64) in[0]) * (in2[7] << 0) + ((u64) in[1]) *
+           (in2[6] << 0) + ((u64) in[2]) * (in2[5] << 0) +
+           ((u64) in[3]) * (in2[4] << 0) + ((u64) in[4]) *
+           (in2[3] << 0) + ((u64) in[5]) * (in2[2] << 0) +
+           ((u64) in[6]) * (in2[1] << 0) +
+           ((u64) in[7]) * (in2[0] << 0);
+  /* tmp[8] has the greatest value but doesn't overflow. See logic in
+   * felem_square. */
+  tmp[8] = ((u64) in[0]) * (in2[8] << 0) + ((u64) in[1]) *
+           (in2[7] << 1) + ((u64) in[2]) * (in2[6] << 0) +
+           ((u64) in[3]) * (in2[5] << 1) + ((u64) in[4]) *
+           (in2[4] << 0) + ((u64) in[5]) * (in2[3] << 1) +
+           ((u64) in[6]) * (in2[2] << 0) + ((u64) in[7]) *
+           (in2[1] << 1) + ((u64) in[8]) * (in2[0] << 0);
+  tmp[9] = ((u64) in[1]) * (in2[8] << 0) + ((u64) in[2]) *
+           (in2[7] << 0) + ((u64) in[3]) * (in2[6] << 0) +
+           ((u64) in[4]) * (in2[5] << 0) + ((u64) in[5]) *
+           (in2[4] << 0) + ((u64) in[6]) * (in2[3] << 0) +
+           ((u64) in[7]) * (in2[2] << 0) +
+           ((u64) in[8]) * (in2[1] << 0);
+  tmp[10] = ((u64) in[2]) * (in2[8] << 0) + ((u64) in[3]) *
+            (in2[7] << 1) + ((u64) in[4]) * (in2[6] << 0) +
+            ((u64) in[5]) * (in2[5] << 1) + ((u64) in[6]) *
+            (in2[4] << 0) + ((u64) in[7]) * (in2[3] << 1) +
+            ((u64) in[8]) * (in2[2] << 0);
+  tmp[11] = ((u64) in[3]) * (in2[8] << 0) + ((u64) in[4]) *
+            (in2[7] << 0) + ((u64) in[5]) * (in2[6] << 0) +
+            ((u64) in[6]) * (in2[5] << 0) + ((u64) in[7]) *
+            (in2[4] << 0) + ((u64) in[8]) * (in2[3] << 0);
+  tmp[12] = ((u64) in[4]) * (in2[8] << 0) + ((u64) in[5]) *
+            (in2[7] << 1) + ((u64) in[6]) * (in2[6] << 0) +
+            ((u64) in[7]) * (in2[5] << 1) +
+            ((u64) in[8]) * (in2[4] << 0);
+  tmp[13] = ((u64) in[5]) * (in2[8] << 0) + ((u64) in[6]) *
+            (in2[7] << 0) + ((u64) in[7]) * (in2[6] << 0) +
+            ((u64) in[8]) * (in2[5] << 0);
+  tmp[14] = ((u64) in[6]) * (in2[8] << 0) + ((u64) in[7]) *
+            (in2[7] << 1) + ((u64) in[8]) * (in2[6] << 0);
+  tmp[15] = ((u64) in[7]) * (in2[8] << 0) +
+            ((u64) in[8]) * (in2[7] << 0);
+  tmp[16] = ((u64) in[8]) * (in2[8] << 0);
+
+  felem_reduce_degree(out, tmp);
+}
+
+static void felem_assign(felem out, const felem in) {
+  memcpy(out, in, sizeof(felem));
+}
+
+/* felem_inv calculates |out| = |in|^{-1}
+ *
+ * Based on Fermat's Little Theorem:
+ *   a^p = a (mod p)
+ *   a^{p-1} = 1 (mod p)
+ *   a^{p-2} = a^{-1} (mod p)
+ */
+static void felem_inv(felem out, const felem in) {
+  felem ftmp, ftmp2;
+  /* each e_I will hold |in|^{2^I - 1} */
+  felem e2, e4, e8, e16, e32, e64;
+  unsigned i;
+
+  felem_square(ftmp, in); /* 2^1 */
+  felem_mul(ftmp, in, ftmp); /* 2^2 - 2^0 */
+  felem_assign(e2, ftmp);
+  felem_square(ftmp, ftmp); /* 2^3 - 2^1 */
+  felem_square(ftmp, ftmp); /* 2^4 - 2^2 */
+  felem_mul(ftmp, ftmp, e2); /* 2^4 - 2^0 */
+  felem_assign(e4, ftmp);
+  felem_square(ftmp, ftmp); /* 2^5 - 2^1 */
+  felem_square(ftmp, ftmp); /* 2^6 - 2^2 */
+  felem_square(ftmp, ftmp); /* 2^7 - 2^3 */
+  felem_square(ftmp, ftmp); /* 2^8 - 2^4 */
+  felem_mul(ftmp, ftmp, e4); /* 2^8 - 2^0 */
+  felem_assign(e8, ftmp);
+  for (i = 0; i < 8; i++) {
+    felem_square(ftmp, ftmp);
+  } /* 2^16 - 2^8 */
+  felem_mul(ftmp, ftmp, e8); /* 2^16 - 2^0 */
+  felem_assign(e16, ftmp);
+  for (i = 0; i < 16; i++) {
+    felem_square(ftmp, ftmp);
+  } /* 2^32 - 2^16 */
+  felem_mul(ftmp, ftmp, e16); /* 2^32 - 2^0 */
+  felem_assign(e32, ftmp);
+  for (i = 0; i < 32; i++) {
+    felem_square(ftmp, ftmp);
+  } /* 2^64 - 2^32 */
+  felem_assign(e64, ftmp);
+  felem_mul(ftmp, ftmp, in); /* 2^64 - 2^32 + 2^0 */
+  for (i = 0; i < 192; i++) {
+    felem_square(ftmp, ftmp);
+  } /* 2^256 - 2^224 + 2^192 */
+
+  felem_mul(ftmp2, e64, e32); /* 2^64 - 2^0 */
+  for (i = 0; i < 16; i++) {
+    felem_square(ftmp2, ftmp2);
+  } /* 2^80 - 2^16 */
+  felem_mul(ftmp2, ftmp2, e16); /* 2^80 - 2^0 */
+  for (i = 0; i < 8; i++) {
+    felem_square(ftmp2, ftmp2);
+  } /* 2^88 - 2^8 */
+  felem_mul(ftmp2, ftmp2, e8); /* 2^88 - 2^0 */
+  for (i = 0; i < 4; i++) {
+    felem_square(ftmp2, ftmp2);
+  } /* 2^92 - 2^4 */
+  felem_mul(ftmp2, ftmp2, e4); /* 2^92 - 2^0 */
+  felem_square(ftmp2, ftmp2); /* 2^93 - 2^1 */
+  felem_square(ftmp2, ftmp2); /* 2^94 - 2^2 */
+  felem_mul(ftmp2, ftmp2, e2); /* 2^94 - 2^0 */
+  felem_square(ftmp2, ftmp2); /* 2^95 - 2^1 */
+  felem_square(ftmp2, ftmp2); /* 2^96 - 2^2 */
+  felem_mul(ftmp2, ftmp2, in); /* 2^96 - 3 */
+
+  felem_mul(out, ftmp2, ftmp); /* 2^256 - 2^224 + 2^192 + 2^96 - 3 */
+}
+
+/* felem_scalar_3 sets out=3*out.
+ *
+ * On entry: out[0,2,...] < 2**30, out[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_scalar_3(felem out) {
+  limb carry = 0;
+  unsigned i;
+
+  for (i = 0;; i++) {
+    out[i] *= 3;
+    out[i] += carry;
+    carry = out[i] >> 29;
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    out[i] *= 3;
+    out[i] += carry;
+    carry = out[i] >> 28;
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_scalar_4 sets out=4*out.
+ *
+ * On entry: out[0,2,...] < 2**30, out[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_scalar_4(felem out) {
+  limb carry = 0, next_carry;
+  unsigned i;
+
+  for (i = 0;; i++) {
+    next_carry = out[i] >> 27;
+    out[i] <<= 2;
+    out[i] &= kBottom29Bits;
+    out[i] += carry;
+    carry = next_carry + (out[i] >> 29);
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    next_carry = out[i] >> 26;
+    out[i] <<= 2;
+    out[i] &= kBottom28Bits;
+    out[i] += carry;
+    carry = next_carry + (out[i] >> 28);
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_scalar_8 sets out=8*out.
+ *
+ * On entry: out[0,2,...] < 2**30, out[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_scalar_8(felem out) {
+  limb carry = 0, next_carry;
+  unsigned i;
+
+  for (i = 0;; i++) {
+    next_carry = out[i] >> 26;
+    out[i] <<= 3;
+    out[i] &= kBottom29Bits;
+    out[i] += carry;
+    carry = next_carry + (out[i] >> 29);
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    next_carry = out[i] >> 25;
+    out[i] <<= 3;
+    out[i] &= kBottom28Bits;
+    out[i] += carry;
+    carry = next_carry + (out[i] >> 28);
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_is_zero_vartime returns 1 iff |in| == 0. It takes a variable amount of
+ * time depending on the value of |in|. */
+static char felem_is_zero_vartime(const felem in) {
+  limb carry;
+  int i;
+  limb tmp[NLIMBS];
+
+  felem_assign(tmp, in);
+
+  /* First, reduce tmp to a minimal form. */
+  do {
+    carry = 0;
+    for (i = 0;; i++) {
+      tmp[i] += carry;
+      carry = tmp[i] >> 29;
+      tmp[i] &= kBottom29Bits;
+
+      i++;
+      if (i == NLIMBS)
+        break;
+
+      tmp[i] += carry;
+      carry = tmp[i] >> 28;
+      tmp[i] &= kBottom28Bits;
+    }
+
+    felem_reduce_carry(tmp, carry);
+  } while (carry);
+
+  /* tmp < 2**257, so the only possible zero values are 0, p and 2p. */
+  return memcmp(tmp, kZero, sizeof(tmp)) == 0 ||
+         memcmp(tmp, kP, sizeof(tmp)) == 0 ||
+         memcmp(tmp, k2P, sizeof(tmp)) == 0;
+}
+
+
+/* Group operations:
+ *
+ * Elements of the elliptic curve group are represented in Jacobian
+ * coordinates: (x, y, z). An affine point (x', y') is x'=x/z**2, y'=y/z**3 in
+ * Jacobian form. */
+
+/* point_double sets {x_out,y_out,z_out} = 2*{x,y,z}.
+ *
+ * See http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l */
+static void point_double(felem x_out, felem y_out, felem z_out, const felem x,
+                         const felem y, const felem z) {
+  felem delta, gamma, alpha, beta, tmp, tmp2;
+
+  felem_square(delta, z);
+  felem_square(gamma, y);
+  felem_mul(beta, x, gamma);
+
+  felem_sum(tmp, x, delta);
+  felem_diff(tmp2, x, delta);
+  felem_mul(alpha, tmp, tmp2);
+  felem_scalar_3(alpha);
+
+  felem_sum(tmp, y, z);
+  felem_square(tmp, tmp);
+  felem_diff(tmp, tmp, gamma);
+  felem_diff(z_out, tmp, delta);
+
+  felem_scalar_4(beta);
+  felem_square(x_out, alpha);
+  felem_diff(x_out, x_out, beta);
+  felem_diff(x_out, x_out, beta);
+
+  felem_diff(tmp, beta, x_out);
+  felem_mul(tmp, alpha, tmp);
+  felem_square(tmp2, gamma);
+  felem_scalar_8(tmp2);
+  felem_diff(y_out, tmp, tmp2);
+}
+
+/* point_add_mixed sets {x_out,y_out,z_out} = {x1,y1,z1} + {x2,y2,1}.
+ * (i.e. the second point is affine.)
+ *
+ * See http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
+ *
+ * Note that this function does not handle P+P, infinity+P nor P+infinity
+ * correctly. */
+static void point_add_mixed(felem x_out, felem y_out, felem z_out,
+                            const felem x1, const felem y1, const felem z1,
+                            const felem x2, const felem y2) {
+  felem z1z1, z1z1z1, s2, u2, h, i, j, r, rr, v, tmp;
+
+  felem_square(z1z1, z1);
+  felem_sum(tmp, z1, z1);
+
+  felem_mul(u2, x2, z1z1);
+  felem_mul(z1z1z1, z1, z1z1);
+  felem_mul(s2, y2, z1z1z1);
+  felem_diff(h, u2, x1);
+  felem_sum(i, h, h);
+  felem_square(i, i);
+  felem_mul(j, h, i);
+  felem_diff(r, s2, y1);
+  felem_sum(r, r, r);
+  felem_mul(v, x1, i);
+
+  felem_mul(z_out, tmp, h);
+  felem_square(rr, r);
+  felem_diff(x_out, rr, j);
+  felem_diff(x_out, x_out, v);
+  felem_diff(x_out, x_out, v);
+
+  felem_diff(tmp, v, x_out);
+  felem_mul(y_out, tmp, r);
+  felem_mul(tmp, y1, j);
+  felem_diff(y_out, y_out, tmp);
+  felem_diff(y_out, y_out, tmp);
+}
+
+/* point_add sets {x_out,y_out,z_out} = {x1,y1,z1} + {x2,y2,z2}.
+ *
+ * See http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
+ *
+ * Note that this function does not handle P+P, infinity+P nor P+infinity
+ * correctly. */
+static void point_add(felem x_out, felem y_out, felem z_out, const felem x1,
+                      const felem y1, const felem z1, const felem x2,
+                      const felem y2, const felem z2) {
+  felem z1z1, z1z1z1, z2z2, z2z2z2, s1, s2, u1, u2, h, i, j, r, rr, v, tmp;
+
+  felem_square(z1z1, z1);
+  felem_square(z2z2, z2);
+  felem_mul(u1, x1, z2z2);
+
+  felem_sum(tmp, z1, z2);
+  felem_square(tmp, tmp);
+  felem_diff(tmp, tmp, z1z1);
+  felem_diff(tmp, tmp, z2z2);
+
+  felem_mul(z2z2z2, z2, z2z2);
+  felem_mul(s1, y1, z2z2z2);
+
+  felem_mul(u2, x2, z1z1);
+  felem_mul(z1z1z1, z1, z1z1);
+  felem_mul(s2, y2, z1z1z1);
+  felem_diff(h, u2, u1);
+  felem_sum(i, h, h);
+  felem_square(i, i);
+  felem_mul(j, h, i);
+  felem_diff(r, s2, s1);
+  felem_sum(r, r, r);
+  felem_mul(v, u1, i);
+
+  felem_mul(z_out, tmp, h);
+  felem_square(rr, r);
+  felem_diff(x_out, rr, j);
+  felem_diff(x_out, x_out, v);
+  felem_diff(x_out, x_out, v);
+
+  felem_diff(tmp, v, x_out);
+  felem_mul(y_out, tmp, r);
+  felem_mul(tmp, s1, j);
+  felem_diff(y_out, y_out, tmp);
+  felem_diff(y_out, y_out, tmp);
+}
+
+/* point_add_or_double_vartime sets {x_out,y_out,z_out} = {x1,y1,z1} +
+ *                                                        {x2,y2,z2}.
+ *
+ * See http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
+ *
+ * This function handles the case where {x1,y1,z1}={x2,y2,z2}. */
+static void point_add_or_double_vartime(
+    felem x_out, felem y_out, felem z_out, const felem x1, const felem y1,
+    const felem z1, const felem x2, const felem y2, const felem z2) {
+  felem z1z1, z1z1z1, z2z2, z2z2z2, s1, s2, u1, u2, h, i, j, r, rr, v, tmp;
+  char x_equal, y_equal;
+
+  felem_square(z1z1, z1);
+  felem_square(z2z2, z2);
+  felem_mul(u1, x1, z2z2);
+
+  felem_sum(tmp, z1, z2);
+  felem_square(tmp, tmp);
+  felem_diff(tmp, tmp, z1z1);
+  felem_diff(tmp, tmp, z2z2);
+
+  felem_mul(z2z2z2, z2, z2z2);
+  felem_mul(s1, y1, z2z2z2);
+
+  felem_mul(u2, x2, z1z1);
+  felem_mul(z1z1z1, z1, z1z1);
+  felem_mul(s2, y2, z1z1z1);
+  felem_diff(h, u2, u1);
+  x_equal = felem_is_zero_vartime(h);
+  felem_sum(i, h, h);
+  felem_square(i, i);
+  felem_mul(j, h, i);
+  felem_diff(r, s2, s1);
+  y_equal = felem_is_zero_vartime(r);
+  if (x_equal && y_equal) {
+    point_double(x_out, y_out, z_out, x1, y1, z1);
+    return;
+  }
+  felem_sum(r, r, r);
+  felem_mul(v, u1, i);
+
+  felem_mul(z_out, tmp, h);
+  felem_square(rr, r);
+  felem_diff(x_out, rr, j);
+  felem_diff(x_out, x_out, v);
+  felem_diff(x_out, x_out, v);
+
+  felem_diff(tmp, v, x_out);
+  felem_mul(y_out, tmp, r);
+  felem_mul(tmp, s1, j);
+  felem_diff(y_out, y_out, tmp);
+  felem_diff(y_out, y_out, tmp);
+}
+
+/* copy_conditional sets out=in if mask = 0xffffffff in constant time.
+ *
+ * On entry: mask is either 0 or 0xffffffff. */
+static void copy_conditional(felem out, const felem in, limb mask) {
+  int i;
+
+  for (i = 0; i < NLIMBS; i++) {
+    const limb tmp = mask & (in[i] ^ out[i]);
+    out[i] ^= tmp;
+  }
+}
+
+/* select_affine_point sets {out_x,out_y} to the index'th entry of table.
+ * On entry: index < 16, table[0] must be zero. */
+static void select_affine_point(felem out_x, felem out_y, const limb* table,
+                                limb index) {
+  limb i, j;
+
+  memset(out_x, 0, sizeof(felem));
+  memset(out_y, 0, sizeof(felem));
+
+  for (i = 1; i < 16; i++) {
+    limb mask = i ^ index;
+    mask |= mask >> 2;
+    mask |= mask >> 1;
+    mask &= 1;
+    mask--;
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_x[j] |= *table & mask;
+    }
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_y[j] |= *table & mask;
+    }
+  }
+}
+
+/* select_jacobian_point sets {out_x,out_y,out_z} to the index'th entry of
+ * table. On entry: index < 16, table[0] must be zero. */
+static void select_jacobian_point(felem out_x, felem out_y, felem out_z,
+                                  const limb* table, limb index) {
+  limb i, j;
+
+  memset(out_x, 0, sizeof(felem));
+  memset(out_y, 0, sizeof(felem));
+  memset(out_z, 0, sizeof(felem));
+
+  /* The implicit value at index 0 is all zero. We don't need to perform that
+   * iteration of the loop because we already set out_* to zero. */
+  table += 3 * NLIMBS;
+
+  // Hit all entries to obscure cache profiling.
+  for (i = 1; i < 16; i++) {
+    limb mask = i ^ index;
+    mask |= mask >> 2;
+    mask |= mask >> 1;
+    mask &= 1;
+    mask--;
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_x[j] |= *table & mask;
+    }
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_y[j] |= *table & mask;
+    }
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_z[j] |= *table & mask;
+    }
+  }
+}
+
+/* scalar_base_mult sets {nx,ny,nz} = scalar*G where scalar is a little-endian
+ * number. Note that the value of scalar must be less than the order of the
+ * group. */
+static void scalar_base_mult(felem nx, felem ny, felem nz,
+                             const p256_int* scalar) {
+  int i, j;
+  limb n_is_infinity_mask = -1, p_is_noninfinite_mask, mask;
+  u32 table_offset;
+
+  felem px, py;
+  felem tx, ty, tz;
+
+  memset(nx, 0, sizeof(felem));
+  memset(ny, 0, sizeof(felem));
+  memset(nz, 0, sizeof(felem));
+
+  /* The loop adds bits at positions 0, 64, 128 and 192, followed by
+   * positions 32,96,160 and 224 and does this 32 times. */
+  for (i = 0; i < 32; i++) {
+    if (i) {
+      point_double(nx, ny, nz, nx, ny, nz);
+    }
+    table_offset = 0;
+    for (j = 0; j <= 32; j += 32) {
+      char bit0 = p256_get_bit(scalar, 31 - i + j);
+      char bit1 = p256_get_bit(scalar, 95 - i + j);
+      char bit2 = p256_get_bit(scalar, 159 - i + j);
+      char bit3 = p256_get_bit(scalar, 223 - i + j);
+      limb index = bit0 | (bit1 << 1) | (bit2 << 2) | (bit3 << 3);
+
+      select_affine_point(px, py, kPrecomputed + table_offset, index);
+      table_offset += 30 * NLIMBS;
+
+      /* Since scalar is less than the order of the group, we know that
+       * {nx,ny,nz} != {px,py,1}, unless both are zero, which we handle
+       * below. */
+      point_add_mixed(tx, ty, tz, nx, ny, nz, px, py);
+      /* The result of point_add_mixed is incorrect if {nx,ny,nz} is zero
+       * (a.k.a.  the point at infinity). We handle that situation by
+       * copying the point from the table. */
+      copy_conditional(nx, px, n_is_infinity_mask);
+      copy_conditional(ny, py, n_is_infinity_mask);
+      copy_conditional(nz, kOne, n_is_infinity_mask);
+
+      /* Equally, the result is also wrong if the point from the table is
+       * zero, which happens when the index is zero. We handle that by
+       * only copying from {tx,ty,tz} to {nx,ny,nz} if index != 0. */
+      p_is_noninfinite_mask = NON_ZERO_TO_ALL_ONES(index);
+      mask = p_is_noninfinite_mask & ~n_is_infinity_mask;
+      copy_conditional(nx, tx, mask);
+      copy_conditional(ny, ty, mask);
+      copy_conditional(nz, tz, mask);
+      /* If p was not zero, then n is now non-zero. */
+      n_is_infinity_mask &= ~p_is_noninfinite_mask;
+    }
+  }
+}
+
+/* point_to_affine converts a Jacobian point to an affine point. If the input
+ * is the point at infinity then it returns (0, 0) in constant time. */
+static void point_to_affine(felem x_out, felem y_out, const felem nx,
+                            const felem ny, const felem nz) {
+  felem z_inv, z_inv_sq;
+  felem_inv(z_inv, nz);
+  felem_square(z_inv_sq, z_inv);
+  felem_mul(x_out, nx, z_inv_sq);
+  felem_mul(z_inv, z_inv, z_inv_sq);
+  felem_mul(y_out, ny, z_inv);
+}
+
+/* scalar_base_mult sets {nx,ny,nz} = scalar*{x,y}. */
+static void scalar_mult(felem nx, felem ny, felem nz, const felem x,
+                        const felem y, const p256_int* scalar) {
+  int i;
+  felem px, py, pz, tx, ty, tz;
+  felem precomp[16][3];
+  limb n_is_infinity_mask, index, p_is_noninfinite_mask, mask;
+
+  /* We precompute 0,1,2,... times {x,y}. */
+  memset(precomp, 0, sizeof(felem) * 3);
+  memcpy(&precomp[1][0], x, sizeof(felem));
+  memcpy(&precomp[1][1], y, sizeof(felem));
+  memcpy(&precomp[1][2], kOne, sizeof(felem));
+
+  for (i = 2; i < 16; i += 2) {
+    point_double(precomp[i][0], precomp[i][1], precomp[i][2],
+                 precomp[i / 2][0], precomp[i / 2][1], precomp[i / 2][2]);
+
+    point_add_mixed(precomp[i + 1][0], precomp[i + 1][1], precomp[i + 1][2],
+                    precomp[i][0], precomp[i][1], precomp[i][2], x, y);
+  }
+
+  memset(nx, 0, sizeof(felem));
+  memset(ny, 0, sizeof(felem));
+  memset(nz, 0, sizeof(felem));
+  n_is_infinity_mask = -1;
+
+  /* We add in a window of four bits each iteration and do this 64 times. */
+  for (i = 0; i < 256; i += 4) {
+    if (i) {
+      point_double(nx, ny, nz, nx, ny, nz);
+      point_double(nx, ny, nz, nx, ny, nz);
+      point_double(nx, ny, nz, nx, ny, nz);
+      point_double(nx, ny, nz, nx, ny, nz);
+    }
+
+    index = (p256_get_bit(scalar, 255 - i - 0) << 3) |
+            (p256_get_bit(scalar, 255 - i - 1) << 2) |
+            (p256_get_bit(scalar, 255 - i - 2) << 1) |
+            p256_get_bit(scalar, 255 - i - 3);
+
+    /* See the comments in scalar_base_mult about handling infinities. */
+    select_jacobian_point(px, py, pz, precomp[0][0], index);
+    point_add(tx, ty, tz, nx, ny, nz, px, py, pz);
+    copy_conditional(nx, px, n_is_infinity_mask);
+    copy_conditional(ny, py, n_is_infinity_mask);
+    copy_conditional(nz, pz, n_is_infinity_mask);
+
+    p_is_noninfinite_mask = NON_ZERO_TO_ALL_ONES(index);
+    mask = p_is_noninfinite_mask & ~n_is_infinity_mask;
+
+    copy_conditional(nx, tx, mask);
+    copy_conditional(ny, ty, mask);
+    copy_conditional(nz, tz, mask);
+    n_is_infinity_mask &= ~p_is_noninfinite_mask;
+  }
+}
+
+#define kRDigits {2, 0, 0, 0xfffffffe, 0xffffffff, 0xffffffff, 0xfffffffd, 1} // 2^257 mod p256.p
+
+#define kRInvDigits {0x80000000, 1, 0xffffffff, 0, 0x80000001, 0xfffffffe, 1, 0x7fffffff}  // 1 / 2^257 mod p256.p
+
+static const p256_int kR = { kRDigits };
+static const p256_int kRInv = { kRInvDigits };
+
+/* to_montgomery sets out = R*in. */
+static void to_montgomery(felem out, const p256_int* in) {
+  p256_int in_shifted;
+  int i;
+
+  p256_init(&in_shifted);
+  p256_modmul(&SECP256r1_p, in, 0, &kR, &in_shifted);
+
+  for (i = 0; i < NLIMBS; i++) {
+    if ((i & 1) == 0) {
+      out[i] = P256_DIGIT(&in_shifted, 0) & kBottom29Bits;
+      p256_shr(&in_shifted, 29, &in_shifted);
+    } else {
+      out[i] = P256_DIGIT(&in_shifted, 0) & kBottom28Bits;
+      p256_shr(&in_shifted, 28, &in_shifted);
+    }
+  }
+
+  p256_clear(&in_shifted);
+}
+
+/* from_montgomery sets out=in/R. */
+static void from_montgomery(p256_int* out, const felem in) {
+  p256_int result, tmp;
+  int i, top;
+
+  p256_init(&result);
+  p256_init(&tmp);
+
+  p256_add_d(&tmp, in[NLIMBS - 1], &result);
+  for (i = NLIMBS - 2; i >= 0; i--) {
+    if ((i & 1) == 0) {
+      top = p256_shl(&result, 29, &tmp);
+    } else {
+      top = p256_shl(&result, 28, &tmp);
+    }
+    top |= p256_add_d(&tmp, in[i], &result);
+  }
+
+  p256_modmul(&SECP256r1_p, &kRInv, top, &result, out);
+
+  p256_clear(&result);
+  p256_clear(&tmp);
+}
+
+/* p256_base_point_mul sets {out_x,out_y} = nG, where n is < the
+ * order of the group. */
+void p256_base_point_mul(const p256_int* n, p256_int* out_x, p256_int* out_y) {
+  felem x, y, z;
+
+  scalar_base_mult(x, y, z, n);
+
+  {
+    felem x_affine, y_affine;
+
+    point_to_affine(x_affine, y_affine, x, y, z);
+    from_montgomery(out_x, x_affine);
+    from_montgomery(out_y, y_affine);
+  }
+}
+
+/* p256_points_mul_vartime sets {out_x,out_y} = n1*G + n2*{in_x,in_y}, where
+ * n1 and n2 are < the order of the group.
+ *
+ * As indicated by the name, this function operates in variable time. This
+ * is safe because it's used for signature validation which doesn't deal
+ * with secrets. */
+void p256_points_mul_vartime(
+    const p256_int* n1, const p256_int* n2, const p256_int* in_x,
+    const p256_int* in_y, p256_int* out_x, p256_int* out_y) {
+  felem x1, y1, z1, x2, y2, z2, px, py;
+
+  /* If both scalars are zero, then the result is the point at infinity. */
+  if (p256_is_zero(n1) != 0 && p256_is_zero(n2) != 0) {
+    p256_clear(out_x);
+    p256_clear(out_y);
+    return;
+  }
+
+  to_montgomery(px, in_x);
+  to_montgomery(py, in_y);
+  scalar_base_mult(x1, y1, z1, n1);
+  scalar_mult(x2, y2, z2, px, py, n2);
+
+  if (p256_is_zero(n2) != 0) {
+    /* If n2 == 0, then {x2,y2,z2} is zero and the result is just
+         * {x1,y1,z1}. */
+  } else if (p256_is_zero(n1) != 0) {
+    /* If n1 == 0, then {x1,y1,z1} is zero and the result is just
+         * {x2,y2,z2}. */
+    memcpy(x1, x2, sizeof(x2));
+    memcpy(y1, y2, sizeof(y2));
+    memcpy(z1, z2, sizeof(z2));
+  } else {
+    /* This function handles the case where {x1,y1,z1} == {x2,y2,z2}. */
+    point_add_or_double_vartime(x1, y1, z1, x1, y1, z1, x2, y2, z2);
+  }
+
+  point_to_affine(px, py, x1, y1, z1);
+  from_montgomery(out_x, px);
+  from_montgomery(out_y, py);
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256_ecdsa.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256_ecdsa.c
new file mode 100755
index 0000000..f2264b0
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/p256_ecdsa.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "mincrypt/p256_ecdsa.h"
+#include "mincrypt/p256.h"
+
+int p256_ecdsa_verify(const p256_int* key_x, const p256_int* key_y,
+                      const p256_int* message,
+                      const p256_int* r, const p256_int* s) {
+  p256_int u, v;
+
+  // Check public key.
+  if (!p256_is_valid_point(key_x, key_y)) return 0;
+
+  // Check r and s are != 0 % n.
+  p256_mod(&SECP256r1_n, r, &u);
+  p256_mod(&SECP256r1_n, s, &v);
+  if (p256_is_zero(&u) || p256_is_zero(&v)) return 0;
+
+  p256_modinv_vartime(&SECP256r1_n, s, &v);
+  p256_modmul(&SECP256r1_n, message, 0, &v, &u);  // message / s % n
+  p256_modmul(&SECP256r1_n, r, 0, &v, &v);  // r / s % n
+
+  p256_points_mul_vartime(&u, &v,
+                          key_x, key_y,
+                          &u, &v);
+
+  p256_mod(&SECP256r1_n, &u, &u);  // (x coord % p) % n
+  return p256_cmp(r, &u) == 0;
+}
+
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/rsa.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/rsa.c
new file mode 100755
index 0000000..9061b3a
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/rsa.c
@@ -0,0 +1,308 @@
+/* rsa.c
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**     * Redistributions of source code must retain the above copyright
+**       notice, this list of conditions and the following disclaimer.
+**     * Redistributions in binary form must reproduce the above copyright
+**       notice, this list of conditions and the following disclaimer in the
+**       documentation and/or other materials provided with the distribution.
+**     * Neither the name of Google Inc. nor the names of its contributors may
+**       be used to endorse or promote products derived from this software
+**       without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "mincrypt/rsa.h"
+#include "mincrypt/sha.h"
+#include "mincrypt/sha256.h"
+
+// a[] -= mod
+static void subM(const RSAPublicKey* key,
+                 uint32_t* a) {
+    int64_t A = 0;
+    int i;
+    for (i = 0; i < key->len; ++i) {
+        A += (uint64_t)a[i] - key->n[i];
+        a[i] = (uint32_t)A;
+        A >>= 32;
+    }
+}
+
+// return a[] >= mod
+static int geM(const RSAPublicKey* key,
+               const uint32_t* a) {
+    int i;
+    for (i = key->len; i;) {
+        --i;
+        if (a[i] < key->n[i]) return 0;
+        if (a[i] > key->n[i]) return 1;
+    }
+    return 1;  // equal
+}
+
+// montgomery c[] += a * b[] / R % mod
+static void montMulAdd(const RSAPublicKey* key,
+                       uint32_t* c,
+                       const uint32_t a,
+                       const uint32_t* b) {
+    uint64_t A = (uint64_t)a * b[0] + c[0];
+    uint32_t d0 = (uint32_t)A * key->n0inv;
+    uint64_t B = (uint64_t)d0 * key->n[0] + (uint32_t)A;
+    int i;
+
+    for (i = 1; i < key->len; ++i) {
+        A = (A >> 32) + (uint64_t)a * b[i] + c[i];
+        B = (B >> 32) + (uint64_t)d0 * key->n[i] + (uint32_t)A;
+        c[i - 1] = (uint32_t)B;
+    }
+
+    A = (A >> 32) + (B >> 32);
+
+    c[i - 1] = (uint32_t)A;
+
+    if (A >> 32) {
+        subM(key, c);
+    }
+}
+
+// montgomery c[] = a[] * b[] / R % mod
+static void montMul(const RSAPublicKey* key,
+                    uint32_t* c,
+                    const uint32_t* a,
+                    const uint32_t* b) {
+    int i;
+    for (i = 0; i < key->len; ++i) {
+        c[i] = 0;
+    }
+    for (i = 0; i < key->len; ++i) {
+        montMulAdd(key, c, a[i], b);
+    }
+}
+
+// In-place public exponentiation.
+// Input and output big-endian byte array in inout.
+static void modpow(const RSAPublicKey* key,
+                   uint8_t* inout) {
+    uint32_t a[RSANUMWORDS];
+    uint32_t aR[RSANUMWORDS];
+    uint32_t aaR[RSANUMWORDS];
+    uint32_t* aaa = 0;
+    int i;
+
+    // Convert from big endian byte array to little endian word array.
+    for (i = 0; i < key->len; ++i) {
+        uint32_t tmp =
+            (inout[((key->len - 1 - i) * 4) + 0] << 24) |
+            (inout[((key->len - 1 - i) * 4) + 1] << 16) |
+            (inout[((key->len - 1 - i) * 4) + 2] << 8) |
+            (inout[((key->len - 1 - i) * 4) + 3] << 0);
+        a[i] = tmp;
+    }
+
+    if (key->exponent == 65537) {
+        aaa = aaR;  // Re-use location.
+        montMul(key, aR, a, key->rr);  // aR = a * RR / R mod M
+        for (i = 0; i < 16; i += 2) {
+            montMul(key, aaR, aR, aR);  // aaR = aR * aR / R mod M
+            montMul(key, aR, aaR, aaR);  // aR = aaR * aaR / R mod M
+        }
+        montMul(key, aaa, aR, a);  // aaa = aR * a / R mod M
+    } else if (key->exponent == 3) {
+        aaa = aR;  // Re-use location.
+        montMul(key, aR, a, key->rr);  /* aR = a * RR / R mod M   */
+        montMul(key, aaR, aR, aR);     /* aaR = aR * aR / R mod M */
+        montMul(key, aaa, aaR, a);     /* aaa = aaR * a / R mod M */
+    }
+
+    // Make sure aaa < mod; aaa is at most 1x mod too large.
+    if (geM(key, aaa)) {
+        subM(key, aaa);
+    }
+
+    // Convert to bigendian byte array
+    for (i = key->len - 1; i >= 0; --i) {
+        uint32_t tmp = aaa[i];
+        *inout++ = tmp >> 24;
+        *inout++ = tmp >> 16;
+        *inout++ = tmp >> 8;
+        *inout++ = tmp >> 0;
+    }
+}
+
+// Expected PKCS1.5 signature padding bytes, for a keytool RSA signature.
+// Has the 0-length optional parameter encoded in the ASN1 (as opposed to the
+// other flavor which omits the optional parameter entirely). This code does not
+// accept signatures without the optional parameter.
+
+/*
+static const uint8_t sha_padding[RSANUMBYTES] = {
+    0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x21, 0x30,
+    0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a,
+    0x05, 0x00, 0x04, 0x14,
+
+    // 20 bytes of hash go here.
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+};
+*/
+
+// SHA-1 of PKCS1.5 signature sha_padding for 2048 bit, as above.
+// At the location of the bytes of the hash all 00 are hashed.
+static const uint8_t kExpectedPadShaRsa2048[SHA_DIGEST_SIZE] = {
+    0xdc, 0xbd, 0xbe, 0x42, 0xd5, 0xf5, 0xa7, 0x2e,
+    0x6e, 0xfc, 0xf5, 0x5d, 0xaf, 0x9d, 0xea, 0x68,
+    0x7c, 0xfb, 0xf1, 0x67
+};
+
+/*
+static const uint8_t sha256_padding[RSANUMBYTES] = {
+    0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x31, 0x30,
+    0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+    0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20,
+
+    // 32 bytes of hash go here.
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+};
+*/
+
+// SHA-256 of PKCS1.5 signature sha256_padding for 2048 bit, as above.
+// At the location of the bytes of the hash all 00 are hashed.
+static const uint8_t kExpectedPadSha256Rsa2048[SHA256_DIGEST_SIZE] = {
+    0xab, 0x28, 0x8d, 0x8a, 0xd7, 0xd9, 0x59, 0x92,
+    0xba, 0xcc, 0xf8, 0x67, 0x20, 0xe1, 0x15, 0x2e,
+    0x39, 0x8d, 0x80, 0x36, 0xd6, 0x6f, 0xf0, 0xfd,
+    0x90, 0xe8, 0x7d, 0x8b, 0xe1, 0x7c, 0x87, 0x59,
+};
+
+// Verify a 2048-bit RSA PKCS1.5 signature against an expected hash.
+// Both e=3 and e=65537 are supported.  hash_len may be
+// SHA_DIGEST_SIZE (== 20) to indicate a SHA-1 hash, or
+// SHA256_DIGEST_SIZE (== 32) to indicate a SHA-256 hash.  No other
+// values are supported.
+//
+// Returns 1 on successful verification, 0 on failure.
+int RSA_verify(const RSAPublicKey *key,
+               const uint8_t *signature,
+               const int len,
+               const uint8_t *hash,
+               const int hash_len) {
+    uint8_t buf[RSANUMBYTES];
+    int i;
+    const uint8_t* padding_hash;
+
+    if (key->len != RSANUMWORDS) {
+        return 0;  // Wrong key passed in.
+    }
+
+    if (len != sizeof(buf)) {
+        return 0;  // Wrong input length.
+    }
+
+    if (hash_len != SHA_DIGEST_SIZE &&
+        hash_len != SHA256_DIGEST_SIZE) {
+        return 0;  // Unsupported hash.
+    }
+
+    if (key->exponent != 3 && key->exponent != 65537) {
+        return 0;  // Unsupported exponent.
+    }
+
+    for (i = 0; i < len; ++i) {  // Copy input to local workspace.
+        buf[i] = signature[i];
+    }
+
+    modpow(key, buf);  // In-place exponentiation.
+
+    // Xor sha portion, so it all becomes 00 iff equal.
+    for (i = len - hash_len; i < len; ++i) {
+        buf[i] ^= *hash++;
+    }
+
+    // Hash resulting buf, in-place.
+    switch (hash_len) {
+        case SHA_DIGEST_SIZE:
+            padding_hash = kExpectedPadShaRsa2048;
+            SHA_hash(buf, len, buf);
+            break;
+        case SHA256_DIGEST_SIZE:
+            padding_hash = kExpectedPadSha256Rsa2048;
+            SHA256_hash(buf, len, buf);
+            break;
+        default:
+            return 0;
+    }
+
+    // Compare against expected hash value.
+    for (i = 0; i < hash_len; ++i) {
+        if (buf[i] != padding_hash[i]) {
+            return 0;
+        }
+    }
+
+    return 1;  // All checked out OK.
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/sha.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/sha.c
new file mode 100755
index 0000000..5bef32e
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/sha.c
@@ -0,0 +1,155 @@
+/* sha.c
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**     * Redistributions of source code must retain the above copyright
+**       notice, this list of conditions and the following disclaimer.
+**     * Redistributions in binary form must reproduce the above copyright
+**       notice, this list of conditions and the following disclaimer in the
+**       documentation and/or other materials provided with the distribution.
+**     * Neither the name of Google Inc. nor the names of its contributors may
+**       be used to endorse or promote products derived from this software
+**       without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// Optimized for minimal code size.
+
+#include "mincrypt/sha.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#define rol(bits, value) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+static void SHA1_Transform(SHA_CTX* ctx) {
+    uint32_t W[80];
+    uint32_t A, B, C, D, E;
+    uint8_t* p = ctx->buf;
+    int t;
+
+    for(t = 0; t < 16; ++t) {
+        uint32_t tmp =  *p++ << 24;
+        tmp |= *p++ << 16;
+        tmp |= *p++ << 8;
+        tmp |= *p++;
+        W[t] = tmp;
+    }
+
+    for(; t < 80; t++) {
+        W[t] = rol(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
+    }
+
+    A = ctx->state[0];
+    B = ctx->state[1];
+    C = ctx->state[2];
+    D = ctx->state[3];
+    E = ctx->state[4];
+
+    for(t = 0; t < 80; t++) {
+        uint32_t tmp = rol(5,A) + E + W[t];
+
+        if (t < 20)
+            tmp += (D^(B&(C^D))) + 0x5A827999;
+        else if ( t < 40)
+            tmp += (B^C^D) + 0x6ED9EBA1;
+        else if ( t < 60)
+            tmp += ((B&C)|(D&(B|C))) + 0x8F1BBCDC;
+        else
+            tmp += (B^C^D) + 0xCA62C1D6;
+
+        E = D;
+        D = C;
+        C = rol(30,B);
+        B = A;
+        A = tmp;
+    }
+
+    ctx->state[0] += A;
+    ctx->state[1] += B;
+    ctx->state[2] += C;
+    ctx->state[3] += D;
+    ctx->state[4] += E;
+}
+
+static const HASH_VTAB SHA_VTAB = {
+    SHA_init,
+    SHA_update,
+    SHA_final,
+    SHA_hash,
+    SHA_DIGEST_SIZE
+};
+
+void SHA_init(SHA_CTX* ctx) {
+    ctx->f = &SHA_VTAB;
+    ctx->state[0] = 0x67452301;
+    ctx->state[1] = 0xEFCDAB89;
+    ctx->state[2] = 0x98BADCFE;
+    ctx->state[3] = 0x10325476;
+    ctx->state[4] = 0xC3D2E1F0;
+    ctx->count = 0;
+}
+
+
+void SHA_update(SHA_CTX* ctx, const void* data, int len) {
+    int i = (int) (ctx->count & 63);
+    const uint8_t* p = (const uint8_t*)data;
+
+    ctx->count += len;
+
+    while (len--) {
+        ctx->buf[i++] = *p++;
+        if (i == 64) {
+            SHA1_Transform(ctx);
+            i = 0;
+        }
+    }
+}
+
+
+const uint8_t* SHA_final(SHA_CTX* ctx) {
+    uint8_t *p = ctx->buf;
+    uint64_t cnt = ctx->count * 8;
+    int i;
+
+    SHA_update(ctx, (uint8_t*)"\x80", 1);
+    while ((ctx->count & 63) != 56) {
+        SHA_update(ctx, (uint8_t*)"\0", 1);
+    }
+    for (i = 0; i < 8; ++i) {
+        uint8_t tmp = (uint8_t) (cnt >> ((7 - i) * 8));
+        SHA_update(ctx, &tmp, 1);
+    }
+
+    for (i = 0; i < 5; i++) {
+        uint32_t tmp = ctx->state[i];
+        *p++ = tmp >> 24;
+        *p++ = tmp >> 16;
+        *p++ = tmp >> 8;
+        *p++ = tmp >> 0;
+    }
+
+    return ctx->buf;
+}
+
+/* Convenience function */
+const uint8_t* SHA_hash(const void* data, int len, uint8_t* digest) {
+    SHA_CTX ctx;
+    SHA_init(&ctx);
+    SHA_update(&ctx, data, len);
+    memcpy(digest, SHA_final(&ctx), SHA_DIGEST_SIZE);
+    return digest;
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/sha256.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/sha256.c
new file mode 100755
index 0000000..eb6e308
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/sha256.c
@@ -0,0 +1,184 @@
+/* sha256.c
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**     * Redistributions of source code must retain the above copyright
+**       notice, this list of conditions and the following disclaimer.
+**     * Redistributions in binary form must reproduce the above copyright
+**       notice, this list of conditions and the following disclaimer in the
+**       documentation and/or other materials provided with the distribution.
+**     * Neither the name of Google Inc. nor the names of its contributors may
+**       be used to endorse or promote products derived from this software
+**       without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// Optimized for minimal code size.
+
+#include "mincrypt/sha256.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#define ror(value, bits) (((value) >> (bits)) | ((value) << (32 - (bits))))
+#define shr(value, bits) ((value) >> (bits))
+
+static const uint32_t K[64] = {
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 };
+
+static void SHA256_Transform(SHA256_CTX* ctx) {
+    uint32_t W[64];
+    uint32_t A, B, C, D, E, F, G, H;
+    uint8_t* p = ctx->buf;
+    int t;
+
+    for(t = 0; t < 16; ++t) {
+        uint32_t tmp =  *p++ << 24;
+        tmp |= *p++ << 16;
+        tmp |= *p++ << 8;
+        tmp |= *p++;
+        W[t] = tmp;
+    }
+
+    for(; t < 64; t++) {
+        uint32_t s0 = ror(W[t-15], 7) ^ ror(W[t-15], 18) ^ shr(W[t-15], 3);
+        uint32_t s1 = ror(W[t-2], 17) ^ ror(W[t-2], 19) ^ shr(W[t-2], 10);
+        W[t] = W[t-16] + s0 + W[t-7] + s1;
+    }
+
+    A = ctx->state[0];
+    B = ctx->state[1];
+    C = ctx->state[2];
+    D = ctx->state[3];
+    E = ctx->state[4];
+    F = ctx->state[5];
+    G = ctx->state[6];
+    H = ctx->state[7];
+
+    for(t = 0; t < 64; t++) {
+        uint32_t s0 = ror(A, 2) ^ ror(A, 13) ^ ror(A, 22);
+        uint32_t maj = (A & B) ^ (A & C) ^ (B & C);
+        uint32_t t2 = s0 + maj;
+        uint32_t s1 = ror(E, 6) ^ ror(E, 11) ^ ror(E, 25);
+        uint32_t ch = (E & F) ^ ((~E) & G);
+        uint32_t t1 = H + s1 + ch + K[t] + W[t];
+
+        H = G;
+        G = F;
+        F = E;
+        E = D + t1;
+        D = C;
+        C = B;
+        B = A;
+        A = t1 + t2;
+    }
+
+    ctx->state[0] += A;
+    ctx->state[1] += B;
+    ctx->state[2] += C;
+    ctx->state[3] += D;
+    ctx->state[4] += E;
+    ctx->state[5] += F;
+    ctx->state[6] += G;
+    ctx->state[7] += H;
+}
+
+static const HASH_VTAB SHA256_VTAB = {
+    SHA256_init,
+    SHA256_update,
+    SHA256_final,
+    SHA256_hash,
+    SHA256_DIGEST_SIZE
+};
+
+void SHA256_init(SHA256_CTX* ctx) {
+    ctx->f = &SHA256_VTAB;
+    ctx->state[0] = 0x6a09e667;
+    ctx->state[1] = 0xbb67ae85;
+    ctx->state[2] = 0x3c6ef372;
+    ctx->state[3] = 0xa54ff53a;
+    ctx->state[4] = 0x510e527f;
+    ctx->state[5] = 0x9b05688c;
+    ctx->state[6] = 0x1f83d9ab;
+    ctx->state[7] = 0x5be0cd19;
+    ctx->count = 0;
+}
+
+
+void SHA256_update(SHA256_CTX* ctx, const void* data, int len) {
+    int i = (int) (ctx->count & 63);
+    const uint8_t* p = (const uint8_t*)data;
+
+    ctx->count += len;
+
+    while (len--) {
+        ctx->buf[i++] = *p++;
+        if (i == 64) {
+            SHA256_Transform(ctx);
+            i = 0;
+        }
+    }
+}
+
+
+const uint8_t* SHA256_final(SHA256_CTX* ctx) {
+    uint8_t *p = ctx->buf;
+    uint64_t cnt = ctx->count * 8;
+    int i;
+
+    SHA256_update(ctx, (uint8_t*)"\x80", 1);
+    while ((ctx->count & 63) != 56) {
+        SHA256_update(ctx, (uint8_t*)"\0", 1);
+    }
+    for (i = 0; i < 8; ++i) {
+        uint8_t tmp = (uint8_t) (cnt >> ((7 - i) * 8));
+        SHA256_update(ctx, &tmp, 1);
+    }
+
+    for (i = 0; i < 8; i++) {
+        uint32_t tmp = ctx->state[i];
+        *p++ = tmp >> 24;
+        *p++ = tmp >> 16;
+        *p++ = tmp >> 8;
+        *p++ = tmp >> 0;
+    }
+
+    return ctx->buf;
+}
+
+/* Convenience function */
+const uint8_t* SHA256_hash(const void* data, int len, uint8_t* digest) {
+    SHA256_CTX ctx;
+    SHA256_init(&ctx);
+    SHA256_update(&ctx, data, len);
+    memcpy(digest, SHA256_final(&ctx), SHA256_DIGEST_SIZE);
+    return digest;
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/tools/DumpPublicKey.java b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/tools/DumpPublicKey.java
new file mode 100755
index 0000000..3eb1398
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/libmincrypt/tools/DumpPublicKey.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumpkey;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import java.io.FileInputStream;
+import java.math.BigInteger;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.KeyStore;
+import java.security.Key;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECPoint;
+
+/**
+ * Command line tool to extract RSA public keys from X.509 certificates
+ * and output source code with data initializers for the keys.
+ * @hide
+ */
+class DumpPublicKey {
+    /**
+     * @param key to perform sanity checks on
+     * @return version number of key.  Supported versions are:
+     *     1: 2048-bit RSA key with e=3 and SHA-1 hash
+     *     2: 2048-bit RSA key with e=65537 and SHA-1 hash
+     *     3: 2048-bit RSA key with e=3 and SHA-256 hash
+     *     4: 2048-bit RSA key with e=65537 and SHA-256 hash
+     * @throws Exception if the key has the wrong size or public exponent
+     */
+    static int checkRSA(RSAPublicKey key, boolean useSHA256) throws Exception {
+        BigInteger pubexp = key.getPublicExponent();
+        BigInteger modulus = key.getModulus();
+        int version;
+
+        if (pubexp.equals(BigInteger.valueOf(3))) {
+            version = useSHA256 ? 3 : 1;
+        } else if (pubexp.equals(BigInteger.valueOf(65537))) {
+            version = useSHA256 ? 4 : 2;
+        } else {
+            throw new Exception("Public exponent should be 3 or 65537 but is " +
+                                pubexp.toString(10) + ".");
+        }
+
+        if (modulus.bitLength() != 2048) {
+             throw new Exception("Modulus should be 2048 bits long but is " +
+                        modulus.bitLength() + " bits.");
+        }
+
+        return version;
+    }
+
+    /**
+     * @param key to perform sanity checks on
+     * @return version number of key.  Supported versions are:
+     *     5: 256-bit EC key with curve NIST P-256
+     * @throws Exception if the key has the wrong size or public exponent
+     */
+    static int checkEC(ECPublicKey key) throws Exception {
+        if (key.getParams().getCurve().getField().getFieldSize() != 256) {
+            throw new Exception("Curve must be NIST P-256");
+        }
+
+        return 5;
+    }
+
+    /**
+     * Perform sanity check on public key.
+     */
+    static int check(PublicKey key, boolean useSHA256) throws Exception {
+        if (key instanceof RSAPublicKey) {
+            return checkRSA((RSAPublicKey) key, useSHA256);
+        } else if (key instanceof ECPublicKey) {
+            if (!useSHA256) {
+                throw new Exception("Must use SHA-256 with EC keys!");
+            }
+            return checkEC((ECPublicKey) key);
+        } else {
+            throw new Exception("Unsupported key class: " + key.getClass().getName());
+        }
+    }
+
+    /**
+     * @param key to output
+     * @return a String representing this public key.  If the key is a
+     *    version 1 key, the string will be a C initializer; this is
+     *    not true for newer key versions.
+     */
+    static String printRSA(RSAPublicKey key, boolean useSHA256) throws Exception {
+        int version = check(key, useSHA256);
+
+        BigInteger N = key.getModulus();
+
+        StringBuilder result = new StringBuilder();
+
+        int nwords = N.bitLength() / 32;    // # of 32 bit integers in modulus
+
+        if (version > 1) {
+            result.append("v");
+            result.append(Integer.toString(version));
+            result.append(" ");
+        }
+
+        result.append("{");
+        result.append(nwords);
+
+        BigInteger B = BigInteger.valueOf(0x100000000L);  // 2^32
+        BigInteger N0inv = B.subtract(N.modInverse(B));   // -1 / N[0] mod 2^32
+
+        result.append(",0x");
+        result.append(N0inv.toString(16));
+
+        BigInteger R = BigInteger.valueOf(2).pow(N.bitLength());
+        BigInteger RR = R.multiply(R).mod(N);    // 2^4096 mod N
+
+        // Write out modulus as little endian array of integers.
+        result.append(",{");
+        for (int i = 0; i < nwords; ++i) {
+            long n = N.mod(B).longValue();
+            result.append(n);
+
+            if (i != nwords - 1) {
+                result.append(",");
+            }
+
+            N = N.divide(B);
+        }
+        result.append("}");
+
+        // Write R^2 as little endian array of integers.
+        result.append(",{");
+        for (int i = 0; i < nwords; ++i) {
+            long rr = RR.mod(B).longValue();
+            result.append(rr);
+
+            if (i != nwords - 1) {
+                result.append(",");
+            }
+
+            RR = RR.divide(B);
+        }
+        result.append("}");
+
+        result.append("}");
+        return result.toString();
+    }
+
+    /**
+     * @param key to output
+     * @return a String representing this public key.  If the key is a
+     *    version 1 key, the string will be a C initializer; this is
+     *    not true for newer key versions.
+     */
+    static String printEC(ECPublicKey key) throws Exception {
+        int version = checkEC(key);
+
+        StringBuilder result = new StringBuilder();
+
+        result.append("v");
+        result.append(Integer.toString(version));
+        result.append(" ");
+
+        BigInteger X = key.getW().getAffineX();
+        BigInteger Y = key.getW().getAffineY();
+        int nbytes = key.getParams().getCurve().getField().getFieldSize() / 8;    // # of 32 bit integers in X coordinate
+
+        result.append("{");
+        result.append(nbytes);
+
+        BigInteger B = BigInteger.valueOf(0x100L);  // 2^8
+
+        // Write out Y coordinate as array of characters.
+        result.append(",{");
+        for (int i = 0; i < nbytes; ++i) {
+            long n = X.mod(B).longValue();
+            result.append(n);
+
+            if (i != nbytes - 1) {
+                result.append(",");
+            }
+
+            X = X.divide(B);
+        }
+        result.append("}");
+
+        // Write out Y coordinate as array of characters.
+        result.append(",{");
+        for (int i = 0; i < nbytes; ++i) {
+            long n = Y.mod(B).longValue();
+            result.append(n);
+
+            if (i != nbytes - 1) {
+                result.append(",");
+            }
+
+            Y = Y.divide(B);
+        }
+        result.append("}");
+
+        result.append("}");
+        return result.toString();
+    }
+
+    static String print(PublicKey key, boolean useSHA256) throws Exception {
+        if (key instanceof RSAPublicKey) {
+            return printRSA((RSAPublicKey) key, useSHA256);
+        } else if (key instanceof ECPublicKey) {
+            return printEC((ECPublicKey) key);
+        } else {
+            throw new Exception("Unsupported key class: " + key.getClass().getName());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length < 1) {
+            System.err.println("Usage: DumpPublicKey certfile ... > source.c");
+            System.exit(1);
+        }
+        Security.addProvider(new BouncyCastleProvider());
+        try {
+            for (int i = 0; i < args.length; i++) {
+                FileInputStream input = new FileInputStream(args[i]);
+                CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
+
+                boolean useSHA256 = false;
+                String sigAlg = cert.getSigAlgName();
+                if ("SHA1withRSA".equals(sigAlg) || "MD5withRSA".equals(sigAlg)) {
+                    // SignApk has historically accepted "MD5withRSA"
+                    // certificates, but treated them as "SHA1withRSA"
+                    // anyway.  Continue to do so for backwards
+                    // compatibility.
+                  useSHA256 = false;
+                } else if ("SHA256withRSA".equals(sigAlg) || "SHA256withECDSA".equals(sigAlg)) {
+                  useSHA256 = true;
+                } else {
+                  System.err.println(args[i] + ": unsupported signature algorithm \"" +
+                                     sigAlg + "\"");
+                  System.exit(1);
+                }
+
+                PublicKey key = cert.getPublicKey();
+                check(key, useSHA256);
+                System.out.print(print(key, useSHA256));
+                System.out.println(i < args.length - 1 ? "," : "");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        System.exit(0);
+    }
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/dsa_sig.h b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/dsa_sig.h
new file mode 100755
index 0000000..b0d91cd
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/dsa_sig.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_DSA_SIG_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_DSA_SIG_H_
+
+#include "mincrypt/p256.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Returns 0 if input sig is not a valid ASN.1 sequence
+int dsa_sig_unpack(unsigned char* sig, int sig_len, p256_int* r_int, p256_int* s_int);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SYSTEM_CORE_INCLUDE_MINCRYPT_DSA_SIG_H_ */
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/hash-internal.h b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/hash-internal.h
new file mode 100755
index 0000000..c813b44
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/hash-internal.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+struct HASH_CTX;  // forward decl
+
+typedef struct HASH_VTAB {
+  void (* const init)(struct HASH_CTX*);
+  void (* const update)(struct HASH_CTX*, const void*, int);
+  const uint8_t* (* const final)(struct HASH_CTX*);
+  const uint8_t* (* const hash)(const void*, int, uint8_t*);
+  int size;
+} HASH_VTAB;
+
+typedef struct HASH_CTX {
+  const HASH_VTAB * f;
+  uint64_t count;
+  uint8_t buf[64];
+  uint32_t state[8];  // upto SHA2
+} HASH_CTX;
+
+#define HASH_init(ctx) (ctx)->f->init(ctx)
+#define HASH_update(ctx, data, len) (ctx)->f->update(ctx, data, len)
+#define HASH_final(ctx) (ctx)->f->final(ctx)
+#define HASH_hash(data, len, digest) (ctx)->f->hash(data, len, digest)
+#define HASH_size(ctx) (ctx)->f->size
+
+#ifdef __cplusplus
+}
+#endif  // __cplusplus
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/p256.h b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/p256.h
new file mode 100755
index 0000000..465a1b9
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/p256.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_LITE_P256_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_LITE_P256_H_
+
+// Collection of routines manipulating 256 bit unsigned integers.
+// Just enough to implement ecdsa-p256 and related algorithms.
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define P256_BITSPERDIGIT 32
+#define P256_NDIGITS 8
+#define P256_NBYTES 32
+
+typedef int p256_err;
+typedef uint32_t p256_digit;
+typedef int32_t p256_sdigit;
+typedef uint64_t p256_ddigit;
+typedef int64_t p256_sddigit;
+
+// Defining p256_int as struct to leverage struct assigment.
+typedef struct {
+  p256_digit a[P256_NDIGITS];
+} p256_int;
+
+extern const p256_int SECP256r1_n;  // Curve order
+extern const p256_int SECP256r1_p;  // Curve prime
+extern const p256_int SECP256r1_b;  // Curve param
+
+// Initialize a p256_int to zero.
+void p256_init(p256_int* a);
+
+// Clear a p256_int to zero.
+void p256_clear(p256_int* a);
+
+// Return bit. Index 0 is least significant.
+int p256_get_bit(const p256_int* a, int index);
+
+// b := a % MOD
+void p256_mod(
+    const p256_int* MOD,
+    const p256_int* a,
+    p256_int* b);
+
+// c := a * (top_b | b) % MOD
+void p256_modmul(
+    const p256_int* MOD,
+    const p256_int* a,
+    const p256_digit top_b,
+    const p256_int* b,
+    p256_int* c);
+
+// b := 1 / a % MOD
+// MOD best be SECP256r1_n
+void p256_modinv(
+    const p256_int* MOD,
+    const p256_int* a,
+    p256_int* b);
+
+// b := 1 / a % MOD
+// MOD best be SECP256r1_n
+// Faster than p256_modinv()
+void p256_modinv_vartime(
+    const p256_int* MOD,
+    const p256_int* a,
+    p256_int* b);
+
+// b := a << (n % P256_BITSPERDIGIT)
+// Returns the bits shifted out of most significant digit.
+p256_digit p256_shl(const p256_int* a, int n, p256_int* b);
+
+// b := a >> (n % P256_BITSPERDIGIT)
+void p256_shr(const p256_int* a, int n, p256_int* b);
+
+int p256_is_zero(const p256_int* a);
+int p256_is_odd(const p256_int* a);
+int p256_is_even(const p256_int* a);
+
+// Returns -1, 0 or 1.
+int p256_cmp(const p256_int* a, const p256_int *b);
+
+// c: = a - b
+// Returns -1 on borrow.
+int p256_sub(const p256_int* a, const p256_int* b, p256_int* c);
+
+// c := a + b
+// Returns 1 on carry.
+int p256_add(const p256_int* a, const p256_int* b, p256_int* c);
+
+// c := a + (single digit)b
+// Returns carry 1 on carry.
+int p256_add_d(const p256_int* a, p256_digit b, p256_int* c);
+
+// ec routines.
+
+// {out_x,out_y} := nG
+void p256_base_point_mul(const p256_int *n,
+                         p256_int *out_x,
+                         p256_int *out_y);
+
+// {out_x,out_y} := n{in_x,in_y}
+void p256_point_mul(const p256_int *n,
+                    const p256_int *in_x,
+                    const p256_int *in_y,
+                    p256_int *out_x,
+                    p256_int *out_y);
+
+// {out_x,out_y} := n1G + n2{in_x,in_y}
+void p256_points_mul_vartime(
+    const p256_int *n1, const p256_int *n2,
+    const p256_int *in_x, const p256_int *in_y,
+    p256_int *out_x, p256_int *out_y);
+
+// Return whether point {x,y} is on curve.
+int p256_is_valid_point(const p256_int* x, const p256_int* y);
+
+// Outputs big-endian binary form. No leading zero skips.
+void p256_to_bin(const p256_int* src, uint8_t dst[P256_NBYTES]);
+
+// Reads from big-endian binary form,
+// thus pre-pad with leading zeros if short.
+void p256_from_bin(const uint8_t src[P256_NBYTES], p256_int* dst);
+
+#define P256_DIGITS(x) ((x)->a)
+#define P256_DIGIT(x,y) ((x)->a[y])
+
+#define P256_ZERO {{0}}
+#define P256_ONE {{1}}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_LITE_P256_H_
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/p256_ecdsa.h b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/p256_ecdsa.h
new file mode 100755
index 0000000..da339fa
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/p256_ecdsa.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_P256_ECDSA_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_P256_ECDSA_H_
+
+// Using current directory as relative include path here since
+// this code typically gets lifted into a variety of build systems
+// and directory structures.
+#include "p256.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Returns 0 if {r,s} is not a signature on message for
+// public key {key_x,key_y}.
+//
+// Note: message is a p256_int.
+// Convert from a binary string using p256_from_bin().
+int p256_ecdsa_verify(const p256_int* key_x,
+                      const p256_int* key_y,
+                      const p256_int* message,
+                      const p256_int* r, const p256_int* s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_P256_ECDSA_H_
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/rsa.h b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/rsa.h
new file mode 100755
index 0000000..3d0556b
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/rsa.h
@@ -0,0 +1,58 @@
+/* rsa.h
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**     * Redistributions of source code must retain the above copyright
+**       notice, this list of conditions and the following disclaimer.
+**     * Redistributions in binary form must reproduce the above copyright
+**       notice, this list of conditions and the following disclaimer in the
+**       documentation and/or other materials provided with the distribution.
+**     * Neither the name of Google Inc. nor the names of its contributors may
+**       be used to endorse or promote products derived from this software
+**       without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
+
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define RSANUMBYTES 256           /* 2048 bit key length */
+#define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t))
+
+typedef struct RSAPublicKey {
+    int len;                  /* Length of n[] in number of uint32_t */
+    uint32_t n0inv;           /* -1 / n[0] mod 2^32 */
+    uint32_t n[RSANUMWORDS];  /* modulus as little endian array */
+    uint32_t rr[RSANUMWORDS]; /* R^2 as little endian array */
+    int exponent;             /* 3 or 65537 */
+} RSAPublicKey;
+
+int RSA_verify(const RSAPublicKey *key,
+               const uint8_t* signature,
+               const int len,
+               const uint8_t* hash,
+               const int hash_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/sha.h b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/sha.h
new file mode 100755
index 0000000..ef60aab
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/sha.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2005 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
+
+#include <stdint.h>
+#include "hash-internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+typedef HASH_CTX SHA_CTX;
+
+void SHA_init(SHA_CTX* ctx);
+void SHA_update(SHA_CTX* ctx, const void* data, int len);
+const uint8_t* SHA_final(SHA_CTX* ctx);
+
+// Convenience method. Returns digest address.
+// NOTE: *digest needs to hold SHA_DIGEST_SIZE bytes.
+const uint8_t* SHA_hash(const void* data, int len, uint8_t* digest);
+
+#define SHA_DIGEST_SIZE 20
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/sha256.h b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/sha256.h
new file mode 100755
index 0000000..3a87c31
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mincrypt/sha256.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_
+
+#include <stdint.h>
+#include "hash-internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+typedef HASH_CTX SHA256_CTX;
+
+void SHA256_init(SHA256_CTX* ctx);
+void SHA256_update(SHA256_CTX* ctx, const void* data, int len);
+const uint8_t* SHA256_final(SHA256_CTX* ctx);
+
+// Convenience method. Returns digest address.
+const uint8_t* SHA256_hash(const void* data, int len, uint8_t* digest);
+
+#define SHA256_DIGEST_SIZE 32
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mkbootimg.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mkbootimg.c
new file mode 100755
index 0000000..bc72971
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mkbootimg.c
@@ -0,0 +1,464 @@
+/* tools/mkbootimg/mkbootimg.c
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include "mincrypt/sha.h"
+#include "mincrypt/sha256.h"
+#include "bootimg.h"
+
+static void *load_file(const char *fn, unsigned *_sz)
+{
+    char *data;
+    int sz;
+    int fd;
+
+    data = 0;
+    fd = open(fn, O_RDONLY);
+    if(fd < 0) return 0;
+
+    sz = lseek(fd, 0, SEEK_END);
+    if(sz < 0) goto oops;
+
+    if(lseek(fd, 0, SEEK_SET) != 0) goto oops;
+
+    data = (char*) malloc(sz);
+    if(data == 0) goto oops;
+
+    if(read(fd, data, sz) != sz) goto oops;
+    close(fd);
+
+    if(_sz) *_sz = sz;
+    return data;
+
+oops:
+    close(fd);
+    if(data != 0) free(data);
+    return 0;
+}
+
+int usage(void)
+{
+    fprintf(stderr,"usage: mkbootimg\n"
+            "       --kernel <filename>\n"
+            "       [ --ramdisk <filename> ]\n"
+            "       [ --second <2ndbootloader-filename> ]\n"
+            "       [ --cmdline <kernel-commandline> ]\n"
+            "       [ --board <boardname> ]\n"
+            "       [ --base <address> ]\n"
+            "       [ --pagesize <pagesize> ]\n"
+            "       [ --dt <filename> ]\n"
+            "       [ --kernel_offset <base offset> ]\n"
+            "       [ --ramdisk_offset <base offset> ]\n"
+            "       [ --second_offset <base offset> ]\n"
+            "       [ --tags_offset <base offset> ]\n"
+            "       [ --os_version <A.B.C version> ]\n"
+            "       [ --os_patch_level <YYYY-MM-DD date> ]\n"
+            "       [ --hash <sha1(default)|sha256> ]\n"
+            "       [ --id ]\n"
+            "       -o|--output <filename>\n"
+            );
+    return 1;
+}
+
+
+
+static unsigned char padding[131072] = { 0, };
+
+static void print_id(const uint8_t *id, size_t id_len) {
+    printf("0x");
+    unsigned i = 0;
+    for (i = 0; i < id_len; i++) {
+        printf("%02x", id[i]);
+    }
+    printf("\n");
+}
+
+int write_padding(int fd, unsigned pagesize, unsigned itemsize)
+{
+    unsigned pagemask = pagesize - 1;
+    ssize_t count;
+
+    if((itemsize & pagemask) == 0) {
+        return 0;
+    }
+
+    count = pagesize - (itemsize & pagemask);
+
+    if(write(fd, padding, count) != count) {
+        return -1;
+    } else {
+        return 0;
+    }
+}
+
+int parse_os_version(char *ver)
+{
+    char *token;
+    int verArray[3] = {0};
+    int a,b,c = 0;
+    int i = 0;
+
+    token = strtok(ver, ".");
+    while(token != NULL) {
+        sscanf(token, "%d", &verArray[i]);
+        token = strtok(NULL, ".");
+        i++;
+    }
+    a = verArray[0];
+    b = verArray[1];
+    c = verArray[2];
+    if((a < 128) && (b < 128) && (c < 128))
+        return (a << 14) | (b << 7) | c;
+    return 0;
+}
+
+int parse_os_patch_level(char *lvl)
+{
+    char *token;
+    int lvlArray[2] = {0};
+    int y,m = 0;
+    int i = 0;
+
+    token = strtok(lvl, "-");
+    while(token != NULL) {
+        sscanf(token, "%d", &lvlArray[i]);
+        token = strtok(NULL, "-");
+        i++;
+    }
+    y = lvlArray[0] - 2000;
+    m = lvlArray[1];
+    if((y >= 0) && (y < 128) && (m > 0) && (m <= 12))
+        return (y << 4) | m;
+    return 0;
+}
+
+enum hash_alg {
+    HASH_UNKNOWN = -1,
+    HASH_SHA1 = 0,
+    HASH_SHA256,
+};
+
+struct hash_name {
+    const char *name;
+    enum hash_alg alg;
+};
+
+const struct hash_name hash_names[] = {
+    { "sha1", HASH_SHA1 },
+    { "sha256", HASH_SHA256 },
+    { NULL, /* Sentinel */ },
+};
+
+enum hash_alg parse_hash_alg(char *name)
+{
+    const struct hash_name *ptr = hash_names;
+
+    while (ptr->name) {
+        if (!strcmp(ptr->name, name))
+            return ptr->alg;
+        ptr++;
+    }
+
+    return HASH_UNKNOWN;
+}
+
+void generate_id_sha1(boot_img_hdr *hdr, void *kernel_data, void *ramdisk_data,
+                      void *second_data, void *dt_data)
+{
+    SHA_CTX ctx;
+    const uint8_t *sha;
+
+    SHA_init(&ctx);
+    SHA_update(&ctx, kernel_data, hdr->kernel_size);
+    SHA_update(&ctx, &hdr->kernel_size, sizeof(hdr->kernel_size));
+    SHA_update(&ctx, ramdisk_data, hdr->ramdisk_size);
+    SHA_update(&ctx, &hdr->ramdisk_size, sizeof(hdr->ramdisk_size));
+    SHA_update(&ctx, second_data, hdr->second_size);
+    SHA_update(&ctx, &hdr->second_size, sizeof(hdr->second_size));
+    if(dt_data) {
+        SHA_update(&ctx, dt_data, hdr->dt_size);
+        SHA_update(&ctx, &hdr->dt_size, sizeof(hdr->dt_size));
+    }
+    sha = SHA_final(&ctx);
+    memcpy(hdr->id, sha,
+           SHA_DIGEST_SIZE > sizeof(hdr->id) ? sizeof(hdr->id) : SHA_DIGEST_SIZE);
+}
+
+void generate_id_sha256(boot_img_hdr *hdr, void *kernel_data, void *ramdisk_data,
+                        void *second_data, void *dt_data)
+{
+    SHA256_CTX ctx;
+    const uint8_t *sha;
+
+    SHA256_init(&ctx);
+    SHA256_update(&ctx, kernel_data, hdr->kernel_size);
+    SHA256_update(&ctx, &hdr->kernel_size, sizeof(hdr->kernel_size));
+    SHA256_update(&ctx, ramdisk_data, hdr->ramdisk_size);
+    SHA256_update(&ctx, &hdr->ramdisk_size, sizeof(hdr->ramdisk_size));
+    SHA256_update(&ctx, second_data, hdr->second_size);
+    SHA256_update(&ctx, &hdr->second_size, sizeof(hdr->second_size));
+    if(dt_data) {
+        SHA256_update(&ctx, dt_data, hdr->dt_size);
+        SHA256_update(&ctx, &hdr->dt_size, sizeof(hdr->dt_size));
+    }
+    sha = SHA256_final(&ctx);
+    memcpy(hdr->id, sha,
+           SHA256_DIGEST_SIZE > sizeof(hdr->id) ? sizeof(hdr->id) : SHA256_DIGEST_SIZE);
+}
+
+void generate_id(enum hash_alg alg, boot_img_hdr *hdr, void *kernel_data,
+                 void *ramdisk_data, void *second_data, void *dt_data)
+{
+    switch (alg) {
+        case HASH_SHA1:
+            generate_id_sha1(hdr, kernel_data, ramdisk_data,
+                             second_data, dt_data);
+            break;
+        case HASH_SHA256:
+            generate_id_sha256(hdr, kernel_data, ramdisk_data,
+                               second_data, dt_data);
+            break;
+        case HASH_UNKNOWN:
+        default:
+            fprintf(stderr, "Unknown hash type.\n");
+    }
+}
+
+int main(int argc, char **argv)
+{
+    boot_img_hdr hdr;
+
+    char *kernel_fn = NULL;
+    void *kernel_data = NULL;
+    char *ramdisk_fn = NULL;
+    void *ramdisk_data = NULL;
+    char *second_fn = NULL;
+    void *second_data = NULL;
+    char *cmdline = "";
+    char *bootimg = NULL;
+    char *board = "";
+    int os_version = 0;
+    int os_patch_level = 0;
+    char *dt_fn = NULL;
+    void *dt_data = NULL;
+    uint32_t pagesize = 2048;
+    int fd;
+    uint32_t base           = 0x10000000U;
+    uint32_t kernel_offset  = 0x00008000U;
+    uint32_t ramdisk_offset = 0x01000000U;
+    uint32_t second_offset  = 0x00f00000U;
+    uint32_t tags_offset    = 0x00000100U;
+    uint32_t kernel_sz      = 0;
+    uint32_t ramdisk_sz     = 0;
+    uint32_t second_sz      = 0;
+    uint32_t dt_sz          = 0;
+    size_t cmdlen;
+    enum hash_alg hash_alg = HASH_SHA1;
+
+    argc--;
+    argv++;
+
+    memset(&hdr, 0, sizeof(hdr));
+
+    bool get_id = false;
+    while(argc > 0){
+        char *arg = argv[0];
+        if (!strcmp(arg, "--id")) {
+            get_id = true;
+            argc -= 1;
+            argv += 1;
+        } else if(argc >= 2) {
+            char *val = argv[1];
+            argc -= 2;
+            argv += 2;
+            if(!strcmp(arg, "--output") || !strcmp(arg, "-o")) {
+                bootimg = val;
+            } else if(!strcmp(arg, "--kernel")) {
+                kernel_fn = val;
+            } else if(!strcmp(arg, "--ramdisk")) {
+                ramdisk_fn = val;
+            } else if(!strcmp(arg, "--second")) {
+                second_fn = val;
+            } else if(!strcmp(arg, "--cmdline")) {
+                cmdline = val;
+            } else if(!strcmp(arg, "--base")) {
+                base = strtoul(val, 0, 16);
+            } else if(!strcmp(arg, "--kernel_offset")) {
+                kernel_offset = strtoul(val, 0, 16);
+            } else if(!strcmp(arg, "--ramdisk_offset")) {
+                ramdisk_offset = strtoul(val, 0, 16);
+            } else if(!strcmp(arg, "--second_offset")) {
+                second_offset = strtoul(val, 0, 16);
+            } else if(!strcmp(arg, "--tags_offset")) {
+                tags_offset = strtoul(val, 0, 16);
+            } else if(!strcmp(arg, "--board")) {
+                board = val;
+            } else if(!strcmp(arg,"--pagesize")) {
+                pagesize = strtoul(val, 0, 10);
+                if ((pagesize != 2048) && (pagesize != 4096)
+                    && (pagesize != 8192) && (pagesize != 16384)
+                    && (pagesize != 32768) && (pagesize != 65536)
+                    && (pagesize != 131072)) {
+                    fprintf(stderr,"error: unsupported page size %d\n", pagesize);
+                    return -1;
+                }
+            } else if(!strcmp(arg, "--dt")) {
+                dt_fn = val;
+            } else if(!strcmp(arg, "--os_version")) {
+                os_version = parse_os_version(val);
+            } else if(!strcmp(arg, "--os_patch_level")) {
+                os_patch_level = parse_os_patch_level(val);
+            } else if(!strcmp(arg, "--hash")) {
+                hash_alg = parse_hash_alg(val);
+                if (hash_alg == HASH_UNKNOWN) {
+                    fprintf(stderr, "error: unknown hash algorithm '%s'\n", val);
+                    return -1;
+                }
+            } else {
+                return usage();
+            }
+        } else {
+            return usage();
+        }
+    }
+    hdr.page_size = pagesize;
+
+    hdr.kernel_addr =  base + kernel_offset;
+    hdr.ramdisk_addr = base + ramdisk_offset;
+    hdr.second_addr =  base + second_offset;
+    hdr.tags_addr =    base + tags_offset;
+
+    hdr.os_version = (os_version << 11) | os_patch_level;
+
+    if(bootimg == 0) {
+        fprintf(stderr,"error: no output filename specified\n");
+        return usage();
+    }
+
+    if(kernel_fn == 0) {
+        fprintf(stderr,"error: no kernel image specified\n");
+        return usage();
+    }
+
+    if(strlen(board) >= BOOT_NAME_SIZE) {
+        fprintf(stderr,"error: board name too large\n");
+        return usage();
+    }
+
+    strcpy((char *) hdr.name, board);
+
+    memcpy(hdr.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE);
+
+    cmdlen = strlen(cmdline);
+    if(cmdlen <= BOOT_ARGS_SIZE) {
+        strcpy((char *)hdr.cmdline, cmdline);
+    } else if(cmdlen <= BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE) {
+        /* exceeds the limits of the base command-line size, go for the extra */
+        memcpy(hdr.cmdline, cmdline, BOOT_ARGS_SIZE);
+        strcpy((char *)hdr.extra_cmdline, cmdline+BOOT_ARGS_SIZE);
+    } else {
+        fprintf(stderr,"error: kernel commandline too large\n");
+        return 1;
+    }
+
+    kernel_data = load_file(kernel_fn, &kernel_sz);
+    if(kernel_data == 0) {
+        fprintf(stderr,"error: could not load kernel '%s'\n", kernel_fn);
+        return 1;
+    }
+    hdr.kernel_size = kernel_sz;
+
+    if(ramdisk_fn == NULL) {
+        ramdisk_data = 0;
+        hdr.ramdisk_size = 0;
+    } else {
+        ramdisk_data = load_file(ramdisk_fn, &ramdisk_sz);
+        if(ramdisk_data == 0) {
+            fprintf(stderr,"error: could not load ramdisk '%s'\n", ramdisk_fn);
+            return 1;
+        }
+        hdr.ramdisk_size = ramdisk_sz;
+    }
+
+    if(second_fn) {
+        second_data = load_file(second_fn, &second_sz);
+        if(second_data == 0) {
+            fprintf(stderr,"error: could not load secondstage '%s'\n", second_fn);
+            return 1;
+        }
+    }
+    hdr.second_size = second_sz;
+
+    if(dt_fn) {
+        dt_data = load_file(dt_fn, &dt_sz);
+        if (dt_data == 0) {
+            fprintf(stderr,"error: could not load device tree image '%s'\n", dt_fn);
+            return 1;
+        }
+    }
+    hdr.dt_size = dt_sz;
+
+    /* put a hash of the contents in the header so boot images can be
+     * differentiated based on their first 2k.
+     */
+    generate_id(hash_alg, &hdr, kernel_data, ramdisk_data, second_data,
+                dt_data);
+
+    fd = open(bootimg, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+    if(fd < 0) {
+        fprintf(stderr,"error: could not create '%s'\n", bootimg);
+        return 1;
+    }
+
+    if(write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) goto fail;
+    if(write_padding(fd, pagesize, sizeof(hdr))) goto fail;
+
+    if(write(fd, kernel_data, hdr.kernel_size) != (ssize_t) hdr.kernel_size) goto fail;
+    if(write_padding(fd, pagesize, hdr.kernel_size)) goto fail;
+
+    if(write(fd, ramdisk_data, hdr.ramdisk_size) != (ssize_t) hdr.ramdisk_size) goto fail;
+    if(write_padding(fd, pagesize, hdr.ramdisk_size)) goto fail;
+
+    if(second_data) {
+        if(write(fd, second_data, hdr.second_size) != (ssize_t) hdr.second_size) goto fail;
+        if(write_padding(fd, pagesize, hdr.second_size)) goto fail;
+    }
+
+    if(dt_data) {
+        if(write(fd, dt_data, hdr.dt_size) != (ssize_t) hdr.dt_size) goto fail;
+        if(write_padding(fd, pagesize, hdr.dt_size)) goto fail;
+    }
+
+    if (get_id) {
+        print_id((uint8_t *) hdr.id, sizeof(hdr.id));
+    }
+    return 0;
+
+fail:
+    unlink(bootimg);
+    close(fd);
+    fprintf(stderr,"error: failed writing '%s': %s\n", bootimg,
+            strerror(errno));
+    return 1;
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mkbootimg_dts b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mkbootimg_dts
new file mode 100755
index 0000000..e9ce189
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/mkbootimg_dts
Binary files differ
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/unpackbootimg.c b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/unpackbootimg.c
new file mode 100755
index 0000000..4447be9
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/unpackbootimg.c
@@ -0,0 +1,306 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <libgen.h>
+
+#include "mincrypt/sha.h"
+#include "bootimg.h"
+
+typedef unsigned char byte;
+
+int read_padding(FILE* f, unsigned itemsize, int pagesize)
+{
+    byte* buf = (byte*)malloc(sizeof(byte) * pagesize);
+    unsigned pagemask = pagesize - 1;
+    unsigned count;
+    
+    if((itemsize & pagemask) == 0) {
+        free(buf);
+        return 0;
+    }
+    
+    count = pagesize - (itemsize & pagemask);
+    
+    if(fread(buf, count, 1, f)){};
+    free(buf);
+    return count;
+}
+
+void write_string_to_file(const char* file, const char* string)
+{
+    FILE* f = fopen(file, "w");
+    fwrite(string, strlen(string), 1, f);
+    fwrite("\n", 1, 1, f);
+    fclose(f);
+}
+
+const char *detect_hash_type(const struct boot_img_hdr *hdr)
+{
+    /*
+     * This isn't a sophisticated or 100% reliable method to detect the hash
+     * type but it's probably good enough.
+     *
+     * sha256 is expected to have no zeroes in the id array
+     * sha1 is expected to have zeroes in id[5], id[6] and id[7]
+     * Zeroes anywhere else probably indicates neither.
+     */
+    const uint32_t *id = hdr->id;
+    if (id[0] != 0 && id[1] != 0 && id[2] != 0 && id[3] != 0 &&
+        id[4] != 0 && id[5] != 0 && id[6] != 0 && id[7] != 0)
+        return "sha256";
+    else if (id[0] != 0 && id[1] != 0 && id[2] != 0 && id[3] != 0 &&
+        id[4] != 0 && id[5] == 0 && id[6] == 0 && id[7] == 0)
+        return "sha1";
+    else
+        return "unknown";
+}
+
+int usage() {
+    printf("usage: unpackbootimg\n");
+    printf("\t-i|--input boot.img\n");
+    printf("\t[ -o|--output output_directory]\n");
+    printf("\t[ -p|--pagesize <size-in-hexadecimal> ]\n");
+    return 0;
+}
+
+int main(int argc, char** argv)
+{
+    char tmp[PATH_MAX];
+    char* directory = "./";
+    char* filename = NULL;
+    int pagesize = 0;
+   // int base = 0;
+    
+    argc--;
+    argv++;
+    while(argc > 0){
+        char *arg = argv[0];
+        char *val = argv[1];
+        argc -= 2;
+        argv += 2;
+        if(!strcmp(arg, "--input") || !strcmp(arg, "-i")) {
+            filename = val;
+        } else if(!strcmp(arg, "--output") || !strcmp(arg, "-o")) {
+            directory = val;
+        } else if(!strcmp(arg, "--pagesize") || !strcmp(arg, "-p")) {
+            pagesize = strtoul(val, 0, 16);
+        } else {
+            return usage();
+        }
+    }
+    
+    if (filename == NULL) {
+        return usage();
+    }
+    
+    int total_read = 0;
+    FILE* f = fopen(filename, "rb");
+    boot_img_hdr header;
+    
+    //printf("Reading header...\n");
+    int i;
+    int seeklimit = 65536;
+    for (i = 0; i <= seeklimit; i++) {
+        fseek(f, i, SEEK_SET);
+        if(fread(tmp, BOOT_MAGIC_SIZE, 1, f)){};
+        if (memcmp(tmp, BOOT_MAGIC, BOOT_MAGIC_SIZE) == 0)
+            break;
+    }
+    total_read = i;
+    if (i > seeklimit) {
+        printf("Android boot magic not found.\n");
+        return 1;
+    }
+    fseek(f, i, SEEK_SET);
+    if (i > 0) {
+        printf("Android magic found at: %d\n", i);
+    }
+    
+    if(fread(&header, sizeof(header), 1, f)){};
+    //base = header.kernel_addr - 0x00008000;
+    printf("BOARD_KERNEL_CMDLINE %.*s%.*s\n", BOOT_ARGS_SIZE, header.cmdline, BOOT_EXTRA_ARGS_SIZE, header.extra_cmdline);
+    //printf("BOARD_KERNEL_BASE %08x\n", base);
+    printf("BOARD_NAME         %s\n", header.name);
+    printf("BOARD_PAGE_SIZE    %d\n", header.page_size);
+    printf("BOARD_HASH_TYPE    %s\n", detect_hash_type(&header));
+    printf("BOARD_KERNEL_ADDR  0x%08x\n", header.kernel_addr);
+    printf("BOARD_KERNEL_SIZE  0x%08x\n", header.kernel_size);
+    printf("BOARD_RAMDISK_ADDR 0x%08x\n", header.ramdisk_addr);
+    printf("BOARD_RAMDISK_SIZE 0x%08x\n", header.ramdisk_size);
+    printf("BOARD_SECOND_ADDR  0x%08x\n", header.second_addr);
+    printf("BOARD_SECOND_SIZE  0x%08x\n", header.second_size);
+    printf("BOARD_TAGS_ADDR    0x%08x\n", header.tags_addr);
+    printf("BOARD_DTB_SIZE     0x%08x\n", header.dt_size);
+    int a=0, b=0, c=0, y=0, m=0;
+    if (header.os_version != 0) {
+        int os_version,os_patch_level;
+        os_version = header.os_version >> 11;
+        os_patch_level = header.os_version&0x7ff;
+        
+        a = (os_version >> 14)&0x7f;
+        b = (os_version >> 7)&0x7f;
+        c = os_version&0x7f;
+        
+        y = (os_patch_level >> 4) + 2000;
+        m = os_patch_level&0xf;
+        
+        if((a < 128) && (b < 128) && (c < 128) && (y >= 2000) && (y < 2128) && (m > 0) && (m <= 12)) {
+            printf("BOARD_OS_VERSION %d.%d.%d\n", a, b, c);
+            printf("BOARD_OS_PATCH_LEVEL %d-%02d\n", y, m);
+        } else {
+            header.os_version = 0;
+        }
+    }
+    if (header.dt_size != 0) {
+        printf("BOARD_DT_SIZE %d\n", header.dt_size);
+    }
+    
+    if (pagesize == 0) {
+        pagesize = header.page_size;
+    }
+    
+    //printf("cmdline...\n");
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-cmdline");
+    char cmdlinetmp[BOOT_ARGS_SIZE+BOOT_EXTRA_ARGS_SIZE+1];
+    sprintf(cmdlinetmp, "%.*s%.*s", BOOT_ARGS_SIZE, header.cmdline, BOOT_EXTRA_ARGS_SIZE, header.extra_cmdline);
+    cmdlinetmp[BOOT_ARGS_SIZE+BOOT_EXTRA_ARGS_SIZE]='\0';
+    write_string_to_file(tmp, cmdlinetmp);
+    
+    //printf("board...\n");
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-board");
+    write_string_to_file(tmp, (char *)header.name);
+    
+    //printf("base...\n");
+#if 0
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-base");
+    char basetmp[200];
+    sprintf(basetmp, "%08x", base);
+    write_string_to_file(tmp, basetmp);
+#endif 
+    
+    //printf("pagesize...\n");
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-pagesize");
+    char pagesizetmp[200];
+    sprintf(pagesizetmp, "%d", header.page_size);
+    write_string_to_file(tmp, pagesizetmp);
+    
+    //printf("kerneloff...\n");
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-kerneladdr");
+    char kernelofftmp[200];
+    sprintf(kernelofftmp, "%08x", header.kernel_addr);
+    write_string_to_file(tmp, kernelofftmp);
+    
+    //printf("ramdiskoff...\n");
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-ramdiskaddr");
+    char ramdiskofftmp[200];
+    sprintf(ramdiskofftmp, "%08x", header.ramdisk_addr);
+    write_string_to_file(tmp, ramdiskofftmp);
+    
+    //printf("secondoff...\n");
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-secondaddr");
+    char secondofftmp[200];
+    sprintf(secondofftmp, "%08x", header.second_addr);
+    write_string_to_file(tmp, secondofftmp);
+    
+    //printf("tagsoff...\n");
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-tagsaddr");
+    char tagsofftmp[200];
+    sprintf(tagsofftmp, "%08x", header.tags_addr);
+    write_string_to_file(tmp, tagsofftmp);
+    
+    if (header.os_version != 0) {
+        //printf("os_version...\n");
+        sprintf(tmp, "%s/%s", directory, basename(filename));
+        strcat(tmp, "-osversion");
+        char osvertmp[200];
+        sprintf(osvertmp, "%d.%d.%d", a, b, c);
+        write_string_to_file(tmp, osvertmp);
+        
+        //printf("os_patch_level...\n");
+        sprintf(tmp, "%s/%s", directory, basename(filename));
+        strcat(tmp, "-oslevel");
+        char oslvltmp[200];
+        sprintf(oslvltmp, "%d-%02d", y, m);
+        write_string_to_file(tmp, oslvltmp);
+    }
+    
+    //printf("hash...\n");
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-hash");
+    const char *hashtype = detect_hash_type(&header);
+    write_string_to_file(tmp, hashtype);
+    
+    total_read += sizeof(header);
+    //printf("total read: %d\n", total_read);
+    total_read += read_padding(f, sizeof(header), pagesize);
+    
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-zImage");
+    FILE *k = fopen(tmp, "wb");
+    byte* kernel = (byte*)malloc(header.kernel_size);
+    //printf("Reading kernel...\n");
+    if(fread(kernel, header.kernel_size, 1, f)){};
+    total_read += header.kernel_size;
+    fwrite(kernel, header.kernel_size, 1, k);
+    fclose(k);
+    
+    //printf("total read: %d\n", header.kernel_size);
+    total_read += read_padding(f, header.kernel_size, pagesize);
+    
+    sprintf(tmp, "%s/%s", directory, basename(filename));
+    strcat(tmp, "-ramdisk.gz");
+    FILE *r = fopen(tmp, "wb");
+    byte* ramdisk = (byte*)malloc(header.ramdisk_size);
+    //printf("Reading ramdisk...\n");
+    if(fread(ramdisk, header.ramdisk_size, 1, f)){};
+    total_read += header.ramdisk_size;
+    fwrite(ramdisk, header.ramdisk_size, 1, r);
+    fclose(r);
+    
+    //printf("total read: %d\n", header.ramdisk_size);
+    total_read += read_padding(f, header.ramdisk_size, pagesize);
+    
+    if (header.second_size != 0) {
+        sprintf(tmp, "%s/%s", directory, basename(filename));
+        strcat(tmp, "-second");
+        FILE *s = fopen(tmp, "wb");
+        byte* second = (byte*)malloc(header.second_size);
+        //printf("Reading second...\n");
+        if(fread(second, header.second_size, 1, f)){};
+        total_read += header.second_size;
+        fwrite(second, header.second_size, 1, s);
+        fclose(s);
+    }
+    
+    //printf("total read: %d\n", header.second_size);
+    total_read += read_padding(f, header.second_size, pagesize);
+    
+    if (header.dt_size != 0) {
+        sprintf(tmp, "%s/%s", directory, basename(filename));
+        strcat(tmp, "-dtb");
+        FILE *d = fopen(tmp, "wb");
+        byte* dtb = (byte*)malloc(header.dt_size);
+        //printf("Reading dtb...\n");
+        if(fread(dtb, header.dt_size, 1, f)){};
+        total_read += header.dt_size;
+        fwrite(dtb, header.dt_size, 1, d);
+        fclose(d);
+    }
+    
+    fclose(f);
+    
+    //printf("Total Read: %d\n", total_read);
+    return 0;
+}
diff --git a/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/unpackbootimg_dts b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/unpackbootimg_dts
new file mode 100755
index 0000000..b434f4e
--- /dev/null
+++ b/src/bach/build.bach/tools/mkbootimg_dts/mkbootimg/unpackbootimg_dts
Binary files differ
diff --git a/src/bach/build.bach/tools/mkfatftl/mkfatftl b/src/bach/build.bach/tools/mkfatftl/mkfatftl
new file mode 100755
index 0000000..69663a4
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/mkfatftl
Binary files differ
diff --git a/src/bach/build.bach/tools/mkfatftl/src/Makefile b/src/bach/build.bach/tools/mkfatftl/src/Makefile
new file mode 100644
index 0000000..09c700a
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/src/Makefile
@@ -0,0 +1,90 @@
+CFLAGS		?= -Os -Wall
+CXXFLAGS	?= -Os -Wall
+
+VERSION ?= 1.0
+
+#-------------------------------------------------------------
+# external source path
+#-------------------------------------------------------------
+Z_ZEPHYR_PATH=$(abspath ../../../work/zephyr/zephyr-src/zephyr)
+Z_DISK_SRC_PATH=$(abspath ../../../work/zephyr/zephyr-bach/bach/system/fs/disk)
+FAT_SRC_PATH=$(abspath ../../../work/zephyr/zephyr-src/modules/fs/fatfs)
+DHARA_SRC_PATH=$(abspath ../../../work/zephyr/zephyr-bach/bach/system/fs/ftl/dhara)
+
+#-------------------------------------------------------------
+# macro configuration
+#-------------------------------------------------------------
+MKIMG_DEF=-DBUILD_FROM_MKFATFTL
+DEBUG_DEF=-DDHARA_NO_LOG
+# fatfs configuration, enable LFN, should be aligned to RTOS configurations
+FAT_DEF=-DCONFIG_FS_FATFS_LFN -DCONFIG_FS_FATFS_LFN_MODE_BSS=1 -DCONFIG_FS_FATFS_CODEPAGE=437
+
+# all macro definitition
+ALL_DEF=$(DEBUG_DEF) $(FAT_DEF) $(MKIMG_DEF)
+
+#-------------------------------------------------------------
+# makefile variable configuration
+#-------------------------------------------------------------
+INC_PATH=-I. -I$(Z_DISK_SRC_PATH) -I$(FAT_SRC_PATH)/include -I$(DHARA_SRC_PATH)
+BUILD_DIR=build
+CC=gcc
+STRIP=strip
+CXX=g++
+TARGET_CFLAGS=-m32 -Os -Wall $(INC_PATH) $(ALL_DEF) -DVERSION=\"$(VERSION)\"
+TARGET=mkfatftl
+
+#-------------------------------------------------------------
+# source list
+#-------------------------------------------------------------
+SRCS := $(DHARA_SRC_PATH)/error.c \
+	mkfatftl.c \
+	scan_dir.c \
+        disk/disk_access_mkfatftl.c \
+	dhara/nand_img.c \
+        $(DHARA_SRC_PATH)/journal.c \
+	$(DHARA_SRC_PATH)/map.c \
+	$(Z_DISK_SRC_PATH)/disk_access_flash_dhara_core.c \
+	$(FAT_SRC_PATH)/ff.c \
+	$(FAT_SRC_PATH)/zfs_diskio.c \
+	$(FAT_SRC_PATH)/option/unicode.c \
+	$(FAT_SRC_PATH)/option/syscall.c
+# output to build folder
+OBJS := $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(SRCS)))
+#$(info $(OBJS))
+
+# add vpath to search
+VPATH := $(shell pwd) $(Z_DISK_SRC_PATH) $(DHARA_SRC_PATH) $(FAT_SRC_PATH) $(FAT_SRC_PATH)/option dhara disk
+#$(info VPATH: $(VPATH))
+#-------------------------------------------------------------
+
+
+
+#-------------------------------------------------------------
+# buld rules
+#-------------------------------------------------------------
+.PHONY: all clean uncrustify
+
+all: precheck $(OBJS) $(TARGET)
+
+uncrustify:
+	@cd .. && find . -name *.c -o -name *.h | xargs uncrustify --replace --no-backup -l C -c $(Z_ZEPHYR_PATH)/.uncrustify.cfg
+
+precheck:
+	@if [ ! -d "$(BUILD_DIR)" ]; then \
+		echo ">> cannot find '$(BUILD_DIR)' directory, try to create one~"; \
+		mkdir -p $(BUILD_DIR); \
+	fi
+
+$(BUILD_DIR)/%.o: %.c
+	$(CC) $(TARGET_CFLAGS) -c -o $@ $<
+
+$(TARGET): $(OBJS)
+	@echo "Building $(TARGET) ..."
+	$(CC) $(TARGET_CFLAGS) -o $(TARGET) $(OBJS) $(TARGET_LDFLAGS)
+	$(STRIP) $@
+	cp -f $@ ../
+
+clean:
+	@rm -f $(OBJS)
+	@rm -fr $(BUILD_DIR)
+	@rm -f $(TARGET)
diff --git a/src/bach/build.bach/tools/mkfatftl/src/dhara/nand_img.c b/src/bach/build.bach/tools/mkfatftl/src/dhara/nand_img.c
new file mode 100644
index 0000000..857038c
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/src/dhara/nand_img.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+
+#include <mkfatftl.h>
+
+/* disk dhara */
+#ifdef DHARA_NO_LOG
+#define dhara_debug(fmt, ...)
+#else
+#define dhara_debug(fmt, ...) \
+	printf("[DHARA] " fmt, ##__VA_ARGS__)
+#endif
+#define dhara_info(fmt, ...) \
+	printf("[DHARA] " fmt, ##__VA_ARGS__)
+
+#define NAND_DEV_DATA(n) \
+	(flashdisk_t *)(n->priv_data)
+
+/* calculate number of blocks required for a given size */
+#define GET_NUM_BLOCK(total_size, block_size) \
+	((total_size + block_size - 1) / block_size)
+
+static u32_t lba_to_address(u32_t p_or_b, u32_t unit_size, const struct dhara_nand *n)
+{
+	flashdisk_t *dev_data = NAND_DEV_DATA(n);
+	u32_t fl_addr = 0;
+
+	fl_addr = dev_data->fa_off + p_or_b * unit_size;
+
+	__ASSERT(fl_addr < (dev_data->fa_off + dev_data->fa_size),
+		 "FS bound error addr 0x%08X, bp 0x%08X, sz %u, off %08X, size %08X",
+		 fl_addr, p_or_b, unit_size, dev_data->fa_off, dev_data->fa_size);
+
+	return fl_addr;
+}
+
+/* Is the given block bad? */
+int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b)
+{
+	/* always no bad block */
+	/* flash driver will handle BBM */
+	return 0;
+}
+
+/* Mark bad the given block (or attempt to). No return value is
+ * required, because there's nothing that can be done in response.
+ */
+void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b)
+{
+	/* NULL */
+}
+
+/* Erase the given block. This function should return 0 on success or -1
+ * on failure.
+ *
+ * The status reported by the chip should be checked. If an erase
+ * operation fails, return -1 and set err to E_BAD_BLOCK.
+ */
+int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b,
+		     dhara_error_t *err)
+{
+	flashdisk_t *dev_data = NAND_DEV_DATA(n);
+	u32_t fl_addr = lba_to_address((u32_t)b, FS_NAND_ERASE_BLOCK_SIZE, n);
+
+	dhara_debug("e b %u, addr 0x%08X\n", b, fl_addr);
+
+	/* set to all 0xFF */
+	dhara_set_error(err, DHARA_E_NONE);
+	memset(dev_data->img_buf + fl_addr, 0xFF, FS_NAND_ERASE_BLOCK_SIZE);
+
+	return 0;
+}
+
+
+/* Program the given page. The data pointer is a pointer to an entire
+ * page ((1 << log2_page_size) bytes). The operation status should be
+ * checked. If the operation fails, return -1 and set err to
+ * E_BAD_BLOCK.
+ *
+ * Pages will be programmed sequentially within a block, and will not be
+ * reprogrammed.
+ */
+int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p,
+		    const uint8_t *data,
+		    dhara_error_t *err)
+{
+	flashdisk_t *dev_data = NAND_DEV_DATA(n);
+	u32_t fl_addr = lba_to_address((u32_t)p, FS_NAND_PAGE_SIZE, n);
+
+	dhara_debug("w p %u, addr 0x%08X\n", p, fl_addr);
+
+	/* copy to image */
+	dhara_set_error(err, DHARA_E_NONE);
+	memcpy(dev_data->img_buf + fl_addr, data, FS_NAND_PAGE_SIZE);
+
+	if (fl_addr + FS_NAND_PAGE_SIZE > dev_data->max_img_size) {
+		/* store max size */
+		dev_data->max_img_size = fl_addr + FS_NAND_PAGE_SIZE;
+	}
+
+	return 0;
+}
+
+/* Check that the given page is erased */
+int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p)
+{
+	int i;
+	flashdisk_t *dev_data = NAND_DEV_DATA(n);
+	u32_t fl_addr = lba_to_address((u32_t)p, FS_NAND_PAGE_SIZE, n);
+
+	dhara_debug("f p %u, addr 0x%08X\n", p, fl_addr);
+
+	for (i = 0; i < FS_NAND_PAGE_SIZE; ++i) {
+		if (dev_data->img_buf[fl_addr + i] != 0xFF) {
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+/* Read a portion of a page. ECC must be handled by the NAND
+ * implementation. Returns 0 on sucess or -1 if an error occurs. If an
+ * uncorrectable ECC error occurs, return -1 and set err to E_ECC.
+ */
+int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p,
+		    size_t offset, size_t length,
+		    uint8_t *data,
+		    dhara_error_t *err)
+{
+	flashdisk_t *dev_data = NAND_DEV_DATA(n);
+	u32_t fl_addr = lba_to_address((u32_t)p, FS_NAND_PAGE_SIZE, n) + offset;
+
+	dhara_debug("r p %u, off %u, len %u, addr 0x%08X\n", p, (u32_t)offset, (u32_t)length, fl_addr);
+
+	/* copy to image */
+	dhara_set_error(err, DHARA_E_NONE);
+	memcpy(data, dev_data->img_buf + fl_addr, length);
+
+	return 0;
+}
+
+/* Read a page from one location and reprogram it in another location.
+ * This might be done using the chip's internal buffers, but it must use
+ * ECC.
+ */
+int dhara_nand_copy(const struct dhara_nand *n,
+		    dhara_page_t src, dhara_page_t dst,
+		    dhara_error_t *err)
+{
+	flashdisk_t *dev_data = NAND_DEV_DATA(n);
+
+	dhara_debug("c src %u / dst %u\n", src, dst);
+
+	u32_t fl_addr = lba_to_address((u32_t)src, FS_NAND_PAGE_SIZE, n);
+	u8_t buf[FS_NAND_PAGE_SIZE];
+
+	/* read src firstly */
+	dhara_set_error(err, DHARA_E_NONE);
+	memcpy(buf, dev_data->img_buf + fl_addr, FS_NAND_PAGE_SIZE);
+
+	/* write to dest */
+	return dhara_nand_prog(n, dst, buf, err);
+}
diff --git a/src/bach/build.bach/tools/mkfatftl/src/disk/disk_access.h b/src/bach/build.bach/tools/mkfatftl/src/disk/disk_access.h
new file mode 100644
index 0000000..de5be2c
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/src/disk/disk_access.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+
+#ifndef _DISK_ACCESS_H_
+#define _DISK_ACCESS_H_
+
+/* disk layer macros, align to Zephyr */
+#define DISK_IOCTL_GET_SECTOR_COUNT     1
+#define DISK_IOCTL_GET_SECTOR_SIZE      2
+#define DISK_IOCTL_GET_ERASE_BLOCK_SZ   4
+#define DISK_IOCTL_CTRL_SYNC            5
+#define DISK_STATUS_OK                  0x00
+
+#define __ASSERT(...)
+
+#endif
\ No newline at end of file
diff --git a/src/bach/build.bach/tools/mkfatftl/src/disk/disk_access_mkfatftl.c b/src/bach/build.bach/tools/mkfatftl/src/disk/disk_access_mkfatftl.c
new file mode 100644
index 0000000..ec01547
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/src/disk/disk_access_mkfatftl.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+
+#include <mkfatftl.h>
+#include <disk_access_flash_dhara_core.h>
+
+#ifdef DHARA_NO_LOG
+#define disk_debug(fmt, ...)
+#else
+#define disk_debug(fmt, ...) \
+	printf("[DISK] " fmt, ##__VA_ARGS__)
+#endif
+#define disk_info(fmt, ...) \
+	printf("[DISK] " fmt, ##__VA_ARGS__)
+
+#if FS_FAT_SECTOR_SIZE == FS_NAND_PAGE_SIZE
+#define FAT_SECTOR_OTOO_NAND_PAGE
+#endif
+
+static struct device flashdisk_dev[FS_MAX_USED_MOUNT_PART];
+static struct disk_info flashdisk_info[FS_MAX_USED_MOUNT_PART] = {
+	{
+		.name = FS_PART_0_NAME,
+	},
+};
+static flashdisk_t flashdisk[FS_MAX_USED_MOUNT_PART] = {
+	{
+		.name = FS_PART_0_NAME,
+	},
+};
+
+/* page buffer for each partition, 4KB * N */
+static u8_t dhara_pagebuf[FS_MAX_USED_MOUNT_PART][FS_NAND_PAGE_SIZE];
+
+int disk_access_init(const char *pdrv)
+{
+	return disk_flash_dhara_access_init(&flashdisk_info[0]);
+}
+
+int disk_access_status(const char *pdrv)
+{
+	return disk_flash_dhara_access_status(&flashdisk_info[0]);
+}
+
+int disk_access_read(const char *pdrv, u8_t *data_buf,
+		     u32_t start_sector, u32_t num_sector)
+{
+	return disk_flash_dhara_access_read(&flashdisk_info[0], data_buf,
+					    start_sector, num_sector);
+}
+
+int disk_access_write(const char *pdrv, const u8_t *data_buf,
+		      u32_t start_sector, u32_t num_sector)
+{
+	return disk_flash_dhara_access_write(&flashdisk_info[0], data_buf,
+					     start_sector, num_sector);
+}
+
+int disk_access_ioctl(const char *pdrv, u8_t cmd, void *buf)
+{
+	return disk_flash_dhara_access_ioctl(&flashdisk_info[0], cmd, buf);
+}
+
+
+int disk_access_sync(const char *pdrv)
+{
+	return disk_flash_dhara_sync(&flashdisk[0]);
+}
+
+static void flashdisk_init(u32_t fs_size, u8_t *img_buf)
+{
+	int i = 0;
+
+	for (i = 0; i < FS_MAX_USED_MOUNT_PART; ++i) {
+		flashdisk[i].max_img_size = 0;
+		flashdisk[i].img_buf = img_buf;
+		flashdisk[i].fa_off  = 0;
+		flashdisk[i].fa_size = fs_size;
+
+		/* assign driver data & disk dev */
+		flashdisk_dev[i].driver_data = (void *)&flashdisk[i];
+		flashdisk_info[i].dev = &flashdisk_dev[i];
+
+		/* Dhara init */
+		disk_flash_dhara_core_init(&flashdisk[i], dhara_pagebuf[i]);
+
+		disk_info("flash disk %s, off 0x%08X, size 0x%08X\n",
+			  flashdisk[i].name, flashdisk[i].fa_off, flashdisk[i].fa_size);
+	}
+}
+
+int bach_disk_mkfatftl_init(u32_t fs_size, u8_t *img_buf)
+{
+	int ret = 0;
+
+	/* flash init firstly */
+	flashdisk_init(fs_size, img_buf);
+
+	return ret;
+}
+
+u32_t bach_disk_mkfatftl_maxsize(void)
+{
+	return flashdisk[0].max_img_size;
+}
\ No newline at end of file
diff --git a/src/bach/build.bach/tools/mkfatftl/src/mkfatftl.c b/src/bach/build.bach/tools/mkfatftl/src/mkfatftl.c
new file mode 100644
index 0000000..364d6e6
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/src/mkfatftl.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *     * The WHITECAT logotype cannot be changed, you can remove it, but you
+ *       cannot change it in any way. The WHITECAT logotype is:
+ *
+ *          /\       /\
+ *         /  \_____/  \
+ *        /_____________\
+ *        W H I T E C A T
+ *
+ *     * Redistributions in binary form must retain all copyright notices printed
+ *       to any local or remote output device. This include any reference to
+ *       Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ *       appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, a tool for make a LFS file system image
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <mkfatftl.h>
+
+static FATFS fat_data;
+static uint8_t *data = NULL;
+
+void create_dir(char *src)
+{
+	char dst_path[PATH_MAX];
+	char *path = NULL;
+	int ret;
+
+	/* skip base folder */
+	path = strchr(src, '/');
+	if (!path) {
+		return;
+	}
+
+	sprintf(dst_path, "%s%s", FS_FAT_PART_0_NAME, path);
+	printf("<Create DIR> path %s --> dst %s\n", path, dst_path);
+	if ((ret = f_mkdir(dst_path)) < 0) {
+		fprintf(stderr, "can't create directory %s: error=%d\r\n", dst_path, ret);
+		exit(1);
+	}
+}
+
+void create_file(char *src)
+{
+	char dst_path[PATH_MAX];
+	char *path = NULL;
+	int ret;
+	unsigned int bw;
+
+	/* skip base folder */
+	path = strchr(src, '/');
+	if (!path) {
+		return;
+	}
+
+	sprintf(dst_path, "%s%s", FS_FAT_PART_0_NAME, path);
+	printf("<Create FIL> path %s --> dst %s\n", path, dst_path);
+
+	// Open source file
+	FILE *srcf = fopen(src, "rb");
+	if (!srcf) {
+		fprintf(stderr, "can't open source file %s: errno=%d (%s)\r\n", src, errno, strerror(errno));
+		exit(1);
+	}
+
+	// Open destination file
+	FIL dstf;
+	ret = f_open(&dstf, dst_path, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
+	if (ret < 0) {
+		fprintf(stderr, "can't open destination file %s: error=%d\r\n", dst_path, ret);
+		exit(1);
+	}
+
+	char c = fgetc(srcf);
+	while (!feof(srcf)) {
+		ret = f_write(&dstf, &c, 1, &bw);
+		if (ret < 0) {
+			fprintf(stderr, "can't write to destination file %s: error=%d\r\n", dst_path, ret);
+			exit(1);
+		}
+		c = fgetc(srcf);
+	}
+
+	// Close destination file
+	ret = f_close(&dstf);
+	if (ret < 0) {
+		fprintf(stderr, "can't close destination file %s: error=%d\r\n", dst_path, ret);
+		exit(1);
+	}
+
+	// Close source file
+	fclose(srcf);
+}
+
+static void usage(char *bin)
+{
+	printf("usage: %s -c <pack-dir> -i <image-file-path>\n", bin);
+}
+
+static void mount_fat_fs(void)
+{
+	int ret = 0;
+	unsigned char work[_MAX_SS];
+
+	ret = f_mkfs(FS_FAT_PART_0_NAME, (FM_FAT | FM_SFD), 0, work, sizeof(work));
+	if (ret != FR_OK) {
+		fprintf(stderr, "FAT format error: error=%d\n", ret);
+		exit(-1);
+	}
+
+	ret = f_mount(&fat_data, FS_FAT_PART_0_NAME, 1);
+	if (ret != FR_OK) {
+		fprintf(stderr, "FAT mount error: error=%d\n", ret);
+		exit(-1);
+	}
+}
+
+static void dump_final_image(char *img_path, unsigned char *img_buf, unsigned int img_size)
+{
+	FILE *img = fopen(img_path, "wb+");
+
+	if (!img) {
+		fprintf(stderr, "can't create image file: errno=%d (%s)\n", errno, strerror(errno));
+		exit(-1);
+	}
+
+	fwrite(img_buf, 1, img_size, img);
+	fclose(img);
+}
+
+int main(int argc, char **argv)
+{
+	char *src_dir = NULL;                                           // Source directory
+	char *dst_img = NULL;                                           // Destination image
+	int c;                                                          // Current option
+	unsigned int img_size_befsync = 0, img_size_aftsync = 0;        // File system image size
+
+	while ((c = getopt(argc, argv, "y:c:i:b:p:r:s:")) != -1) {
+		switch (c) {
+		case 'c':
+			src_dir = optarg;
+			break;
+
+		case 'i':
+			dst_img = optarg;
+			break;
+		}
+	}
+
+	if ((src_dir == NULL) || (dst_img == NULL)) {
+		usage(argv[0]);
+		exit(1);
+	}
+
+	if (MAX_IMG_SIZE < FS_NAND_ERASE_BLOCK_SIZE) {
+		fprintf(stderr, "the image size %d at least should > %u\n", MAX_IMG_SIZE, FS_NAND_ERASE_BLOCK_SIZE);
+		exit(1);
+	}
+
+	/* buffer allocation */
+	data = calloc(1, MAX_IMG_SIZE);
+	if (!data) {
+		fprintf(stderr, "no memory for mount\n");
+		exit(1);
+	}
+	/* initial value for flash */
+	memset(data, 0xFF, MAX_IMG_SIZE);
+
+	/* init after allocation */
+	/* disk layer initialization */
+	bach_disk_mkfatftl_init(MAX_IMG_SIZE, data);
+
+	/* mount FS */
+	mount_fat_fs();
+
+	/* scan all data */
+	extern void scan_dir(char *src);
+	scan_dir(src_dir);
+
+	/* final barrier */
+	img_size_befsync = bach_disk_mkfatftl_maxsize();
+	disk_access_sync(NULL);
+
+	/* get max size after scanning */
+	img_size_aftsync = bach_disk_mkfatftl_maxsize();
+	printf("New size after compact 0x%08X, old 0x%08X, all 0x%08X\n", img_size_aftsync, img_size_befsync, MAX_IMG_SIZE);
+
+	/* dump final image */
+	dump_final_image(dst_img, data, img_size_aftsync);
+
+	return 0;
+}
diff --git a/src/bach/build.bach/tools/mkfatftl/src/mkfatftl.h b/src/bach/build.bach/tools/mkfatftl/src/mkfatftl.h
new file mode 100644
index 0000000..84872e3
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/src/mkfatftl.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+
+#ifndef _MKFATFTL_H_
+#define _MKFATFTL_H_
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <mkfatftl_config.h>
+#include <ff.h>
+#include <disk/disk_access.h>
+
+/* wrapper macros */
+#define ROUND_DOWN(x, align) \
+	((unsigned long)(x) & ~((unsigned long)(align) - 1))
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+
+typedef unsigned char u8_t;
+typedef unsigned int u32_t;
+
+/* dhara */
+#include <map.h>
+#include <nand.h>
+
+struct device {
+	void *driver_data;
+};
+
+typedef struct {
+	char *name;
+
+	/* flash info */
+	u32_t max_img_size;
+	u8_t *img_buf;
+	u32_t fa_off;
+	u32_t fa_size;
+
+	/* dhara info */
+	u8_t dhara_gc_ratio;
+	u8_t *dhara_pagebuf;
+	struct dhara_map dmap;
+	struct dhara_nand dnand;
+} flashdisk_t;
+
+struct disk_info {
+	char *name;
+	struct device *dev;
+};
+
+int disk_access_init(const char *pdrv);
+int disk_access_status(const char *pdrv);
+int disk_access_read(const char *pdrv, u8_t *data_buf,
+		     u32_t start_sector, u32_t num_sector);
+int disk_access_write(const char *pdrv, const u8_t *data_buf,
+		      u32_t start_sector, u32_t num_sector);
+int disk_access_ioctl(const char *pdrv, u8_t cmd, void *buf);
+int disk_access_sync(const char *pdrv);
+
+
+int bach_disk_mkfatftl_init(u32_t fs_size, u8_t *img_buf);
+u32_t bach_disk_mkfatftl_maxsize(void);
+
+#endif
diff --git a/src/bach/build.bach/tools/mkfatftl/src/mkfatftl_config.h b/src/bach/build.bach/tools/mkfatftl/src/mkfatftl_config.h
new file mode 100644
index 0000000..e823637
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/src/mkfatftl_config.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+
+#ifndef _MKFATFTL_CFG_H_
+#define _MKFATFTL_CFG_H_
+
+/* fat and nand configuration */
+#define FS_FAT_SECTOR_SIZE       512
+#define FS_NAND_PAGE_SIZE        4096
+#define FS_NAND_ERASE_BLOCK_SIZE 262144
+#define FS_PART_0_NAME           "SD"
+#define FS_FAT_PART_0_NAME       "SD:"
+
+/* max image size */
+#define MAX_IMG_SIZE (50 * 1024 * 1024)
+
+/* use only one partition to create images */
+#define FS_MAX_USED_MOUNT_PART   1
+
+#endif
diff --git a/src/bach/build.bach/tools/mkfatftl/src/scan_dir.c b/src/bach/build.bach/tools/mkfatftl/src/scan_dir.c
new file mode 100644
index 0000000..4fdb080
--- /dev/null
+++ b/src/bach/build.bach/tools/mkfatftl/src/scan_dir.c
@@ -0,0 +1,41 @@
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+
+/* separate this function out of mkfatflt.c */
+/* to avoid type conflict with 'DIR' between FAT and dirent.h */
+
+extern void create_dir(char *src);
+extern void create_file(char *src);
+
+void scan_dir(char *src)
+{
+	DIR *dir;
+	struct dirent *ent;
+	char curr_path[PATH_MAX];
+
+	dir = opendir(src);
+	if (dir) {
+		while ((ent = readdir(dir))) {
+			// Skip . and .. directories
+			if ((strcmp(ent->d_name, ".") != 0) && (strcmp(ent->d_name, "..") != 0)) {
+				// Update the current path
+				strcpy(curr_path, src);
+				strcat(curr_path, "/");
+				strcat(curr_path, ent->d_name);
+
+				if (ent->d_type == DT_DIR) {
+					create_dir(curr_path);
+					scan_dir(curr_path);
+				} else if (ent->d_type == DT_REG) {
+					create_file(curr_path);
+				}
+			}
+		}
+
+		closedir(dir);
+	}
+}
diff --git a/src/bach/build.bach/tools/mkimage b/src/bach/build.bach/tools/mkimage
new file mode 100755
index 0000000..513a5b4
--- /dev/null
+++ b/src/bach/build.bach/tools/mkimage
Binary files differ
diff --git a/src/bach/build.bach/tools/mklfs/mklfs b/src/bach/build.bach/tools/mklfs/mklfs
new file mode 100755
index 0000000..829b718
--- /dev/null
+++ b/src/bach/build.bach/tools/mklfs/mklfs
Binary files differ
diff --git a/src/bach/build.bach/tools/mklfs/mklfs.sh b/src/bach/build.bach/tools/mklfs/mklfs.sh
new file mode 100755
index 0000000..1577bff
--- /dev/null
+++ b/src/bach/build.bach/tools/mklfs/mklfs.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+SCRIPT=$(readlink -f "$0")
+SCRIPTDIR=$(dirname "$SCRIPT")
+MKLFS=$SCRIPTDIR/mklfs
+
+# parameters
+INPUT=$1
+OUTPUT=$2
+INLINE_SIZE=$3
+PAGE_SIZE=$4
+BLK_SIZE=$5
+CYCLE_SIZE=$6
+
+# config limitation size, 80MB
+LIMIT_SIZE=`expr 80 \* 1024 \* 1024`
+
+nr_dir=`find $INPUT -type d | wc -l`
+nr_file_s_1k=`find $INPUT -type f -size -${INLINE_SIZE}c | wc -l`
+nr_file_b_1k_s_blk=`find $INPUT -type f -size +${INLINE_SIZE}c -size -${BLK_SIZE}c | wc -l`
+nr_file_b_blk=`find $INPUT -type f -size +${BLK_SIZE}c | wc -l`
+
+# 2 blocks per dir
+est_nr_blk=`expr "$nr_dir" \* 2`
+#est_nr_blk=`expr "$nr_dir" + 2`
+# other files
+est_nr_blk=`expr "$est_nr_blk" + "$nr_file_b_1k_s_blk" + "$nr_file_b_blk"`
+est_img_size=`expr "$est_nr_blk" \* "$BLK_SIZE"` 
+nr_iter=1
+
+echo "MKLFS: $MKLFS"
+echo "DIR #: $nr_dir"
+echo "FILE # (0 ~ ${INLINE_SIZE}): $nr_file_s_1k"
+echo "FILE # (${INLINE_SIZE} ~ ${BLK_SIZE}): $nr_file_b_1k_s_blk"
+echo "FILE # (> ${BLK_SIZE}): $nr_file_b_blk"
+echo "Estimated block #: $est_nr_blk"
+echo "Estimated image size: $est_img_size B"
+echo "Limited size: $LIMIT_SIZE B"
+
+# do iterations
+while [ "true" ]; do
+    echo "iter $nr_iter: mklfs -c $INPUT -y $CYCLE_SIZE -b $BLK_SIZE -r $PAGE_SIZE -p $PAGE_SIZE -s $est_img_size -i $OUTPUT"
+    # size check
+    if [ "$est_img_size" -gt "$LIMIT_SIZE" ]; then
+        echo "Sorry... image exceeded the limit size $est_img_size > $LIMIT_SIZE B"
+        # error return
+        exit 1
+    fi
+
+    rm -f $OUTPUT
+    tmp=`$MKLFS -c $INPUT -y 512 -b $BLK_SIZE -r $PAGE_SIZE -p $PAGE_SIZE -s $est_img_size -i $OUTPUT 2>/dev/null`
+    nofree=`echo "$tmp" | grep "No more free space"`
+    #echo "tmp: $tmp"
+    #echo "nofree: $nofree"
+    if [ "$nofree" != "" ]; then
+        #echo "no free space"
+        # add 1 block per step
+        est_img_size=`expr $est_img_size + $BLK_SIZE`
+    else
+        # okay... all is done
+        break
+    fi
+    nr_iter=`expr $nr_iter + 1`
+done
+
+#est_img_size=`expr $est_img_size / 1024`
+echo "Final image size: ${est_img_size}B, # iter: $nr_iter"
diff --git a/src/bach/build.bach/tools/mklfs/src/Makefile b/src/bach/build.bach/tools/mklfs/src/Makefile
new file mode 100644
index 0000000..73e37dd
--- /dev/null
+++ b/src/bach/build.bach/tools/mklfs/src/Makefile
@@ -0,0 +1,77 @@
+CFLAGS		?= -std=gnu99 -Os -Wall
+CXXFLAGS	?= -std=gnu++11 -Os -Wall
+
+VERSION ?= 1.0
+
+ifeq ($(OS),Windows_NT)
+	TARGET_OS := WINDOWS
+	DIST_SUFFIX := windows
+	ARCHIVE_CMD := 7z a
+	ARCHIVE_EXTENSION := zip
+	TARGET := mklfs.exe
+	TARGET_CFLAGS := -mno-ms-bitfields -Ilfs -I. -DVERSION=\"$(VERSION)\" -D__NO_INLINE__
+	TARGET_LDFLAGS := -Wl,-static -static-libgcc
+	TARGET_CXXFLAGS := -Ilfs -I. -DVERSION=\"$(VERSION)\" -D__NO_INLINE__
+	CC=gcc
+	CXX=g++
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S),Linux)
+		TARGET_OS := LINUX
+		UNAME_P := $(shell uname -p)
+		ifeq ($(UNAME_P),x86_64)
+			DIST_SUFFIX := linux64
+		endif
+		ifneq ($(filter %86,$(UNAME_P)),)
+			DIST_SUFFIX := linux32
+		endif
+		CC=gcc
+		STRIP=strip
+		CXX=g++
+		TARGET_CFLAGS   = -std=gnu99 -Os -Wall -Ilfs -I. -D$(TARGET_OS) -DVERSION=\"$(VERSION)\" -D__NO_INLINE__
+		TARGET_CXXFLAGS = -std=gnu++11 -Os -Wall -Ilfs -I. -D$(TARGET_OS) -DVERSION=\"$(VERSION)\" -D__NO_INLINE__
+	endif
+	ifeq ($(UNAME_S),Darwin)
+		TARGET_OS := OSX
+		DIST_SUFFIX := osx
+		CC=clang
+		CXX=clang++
+		TARGET_CFLAGS   = -std=gnu99 -Os -Wall -Ilfs -I. -D$(TARGET_OS) -DVERSION=\"$(VERSION)\" -D__NO_INLINE__ -mmacosx-version-min=10.7 -arch x86_64
+		TARGET_CXXFLAGS = -std=gnu++11 -Os -Wall -Ilfs -I. -D$(TARGET_OS) -DVERSION=\"$(VERSION)\" -D__NO_INLINE__ -mmacosx-version-min=10.7 -arch x86_64 -stdlib=libc++
+		TARGET_LDFLAGS  = -arch x86_64 -stdlib=libc++
+	endif
+	ARCHIVE_CMD := tar czf
+	ARCHIVE_EXTENSION := tar.gz
+	TARGET := mklfs
+endif
+
+SRCS := mklfs.c \
+        lfs/lfs.c \
+        lfs/lfs_util.c
+OBJS := ${SRCS:c=o}
+				   
+VERSION ?= 1.0
+
+.PHONY: all clean
+
+all: update $(TARGET)
+
+update:
+	@echo "Updating lfs source ..."
+	rm -fr lfs/
+	mkdir -p lfs/
+	cp -a ../../../work/zephyr/zephyr-src/modules/fs/littlefs/*.c lfs/
+	cp -a ../../../work/zephyr/zephyr-src/modules/fs/littlefs/*.h lfs/
+
+$(TARGET): $(OBJS)
+	@echo "Building mklfs ..."
+	$(CC) $(TARGET_CFLAGS) -o $(TARGET) $(OBJS) $(TARGET_LDFLAGS)
+	$(STRIP) $@
+	cp -f $@ ../
+
+%.o: %.c Makefile
+	$(CC) $(TARGET_CFLAGS) -c -o $@ $<
+
+clean:
+	@rm -f $(OBJS)
+	@rm -f $(TARGET)
diff --git a/src/bach/build.bach/tools/mklfs/src/lfs/lfs.c b/src/bach/build.bach/tools/mklfs/src/lfs/lfs.c
new file mode 100644
index 0000000..b10c186
--- /dev/null
+++ b/src/bach/build.bach/tools/mklfs/src/lfs/lfs.c
@@ -0,0 +1,4736 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include "lfs.h"
+#include "lfs_util.h"
+
+#define LFS_BLOCK_NULL ((lfs_block_t)-1)
+#define LFS_BLOCK_INLINE ((lfs_block_t)-2)
+
+/// Caching block device operations ///
+static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) {
+    // do not zero, cheaper if cache is readonly or only going to be
+    // written with identical data (during relocates)
+    (void)lfs;
+    rcache->block = LFS_BLOCK_NULL;
+}
+
+static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) {
+    // zero to avoid information leak
+    memset(pcache->buffer, 0xff, lfs->cfg->cache_size);
+    pcache->block = LFS_BLOCK_NULL;
+}
+
+static int lfs_bd_read(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
+        lfs_block_t block, lfs_off_t off,
+        void *buffer, lfs_size_t size) {
+    uint8_t *data = buffer;
+    LFS_ASSERT(block != LFS_BLOCK_NULL);
+    if (off+size > lfs->cfg->block_size) {
+        return LFS_ERR_CORRUPT;
+    }
+
+    while (size > 0) {
+        lfs_size_t diff = size;
+
+        if (pcache && block == pcache->block &&
+                off < pcache->off + pcache->size) {
+            if (off >= pcache->off) {
+                // is already in pcache?
+                diff = lfs_min(diff, pcache->size - (off-pcache->off));
+                memcpy(data, &pcache->buffer[off-pcache->off], diff);
+
+                data += diff;
+                off += diff;
+                size -= diff;
+                continue;
+            }
+
+            // pcache takes priority
+            diff = lfs_min(diff, pcache->off-off);
+        }
+
+        if (block == rcache->block &&
+                off < rcache->off + rcache->size) {
+            if (off >= rcache->off) {
+                // is already in rcache?
+                diff = lfs_min(diff, rcache->size - (off-rcache->off));
+                memcpy(data, &rcache->buffer[off-rcache->off], diff);
+
+                data += diff;
+                off += diff;
+                size -= diff;
+                continue;
+            }
+
+            // rcache takes priority
+            diff = lfs_min(diff, rcache->off-off);
+        }
+
+        // load to cache, first condition can no longer fail
+        LFS_ASSERT(block < lfs->cfg->block_count);
+        rcache->block = block;
+        rcache->off = lfs_aligndown(off, lfs->cfg->read_size);
+        rcache->size = lfs_min(
+                lfs_min(
+                    lfs_alignup(off+hint, lfs->cfg->read_size),
+                    lfs->cfg->block_size)
+                - rcache->off,
+                lfs->cfg->cache_size);
+        int err = lfs->cfg->read(lfs->cfg, rcache->block,
+                rcache->off, rcache->buffer, rcache->size);
+        LFS_ASSERT(err <= 0);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+enum {
+    LFS_CMP_EQ = 0,
+    LFS_CMP_LT = 1,
+    LFS_CMP_GT = 2,
+};
+
+static int lfs_bd_cmp(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
+        lfs_block_t block, lfs_off_t off,
+        const void *buffer, lfs_size_t size) {
+    const uint8_t *data = buffer;
+
+    for (lfs_off_t i = 0; i < size; i++) {
+        uint8_t dat;
+        int err = lfs_bd_read(lfs,
+                pcache, rcache, hint-i,
+                block, off+i, &dat, 1);
+        if (err) {
+            return err;
+        }
+
+        if (dat != data[i]) {
+            return (dat < data[i]) ? LFS_CMP_LT : LFS_CMP_GT;
+        }
+    }
+
+    return LFS_CMP_EQ;
+}
+
+static int lfs_bd_flush(lfs_t *lfs,
+        lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
+    if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) {
+        LFS_ASSERT(pcache->block < lfs->cfg->block_count);
+        lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size);
+        int err = lfs->cfg->prog(lfs->cfg, pcache->block,
+                pcache->off, pcache->buffer, diff);
+        LFS_ASSERT(err <= 0);
+        if (err) {
+            return err;
+        }
+
+        if (validate) {
+            // check data on disk
+            lfs_cache_drop(lfs, rcache);
+            int res = lfs_bd_cmp(lfs,
+                    NULL, rcache, diff,
+                    pcache->block, pcache->off, pcache->buffer, diff);
+            if (res < 0) {
+                return res;
+            }
+
+            if (res != LFS_CMP_EQ) {
+                return LFS_ERR_CORRUPT;
+            }
+        }
+
+        lfs_cache_zero(lfs, pcache);
+    }
+
+    return 0;
+}
+
+static int lfs_bd_sync(lfs_t *lfs,
+        lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
+    lfs_cache_drop(lfs, rcache);
+
+    int err = lfs_bd_flush(lfs, pcache, rcache, validate);
+    if (err) {
+        return err;
+    }
+
+    err = lfs->cfg->sync(lfs->cfg);
+    LFS_ASSERT(err <= 0);
+    return err;
+}
+
+static int lfs_bd_prog(lfs_t *lfs,
+        lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate,
+        lfs_block_t block, lfs_off_t off,
+        const void *buffer, lfs_size_t size) {
+    const uint8_t *data = buffer;
+    LFS_ASSERT(block != LFS_BLOCK_NULL);
+    LFS_ASSERT(off + size <= lfs->cfg->block_size);
+
+    while (size > 0) {
+        if (block == pcache->block &&
+                off >= pcache->off &&
+                off < pcache->off + lfs->cfg->cache_size) {
+            // already fits in pcache?
+            lfs_size_t diff = lfs_min(size,
+                    lfs->cfg->cache_size - (off-pcache->off));
+            memcpy(&pcache->buffer[off-pcache->off], data, diff);
+
+            data += diff;
+            off += diff;
+            size -= diff;
+
+            pcache->size = lfs_max(pcache->size, off - pcache->off);
+            if (pcache->size == lfs->cfg->cache_size) {
+                // eagerly flush out pcache if we fill up
+                int err = lfs_bd_flush(lfs, pcache, rcache, validate);
+                if (err) {
+                    return err;
+                }
+            }
+
+            continue;
+        }
+
+        // pcache must have been flushed, either by programming and
+        // entire block or manually flushing the pcache
+        LFS_ASSERT(pcache->block == LFS_BLOCK_NULL);
+
+        // prepare pcache, first condition can no longer fail
+        pcache->block = block;
+        pcache->off = lfs_aligndown(off, lfs->cfg->prog_size);
+        pcache->size = 0;
+    }
+
+    return 0;
+}
+
+static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
+    LFS_ASSERT(block < lfs->cfg->block_count);
+    int err = lfs->cfg->erase(lfs->cfg, block);
+    LFS_ASSERT(err <= 0);
+    return err;
+}
+
+
+/// Small type-level utilities ///
+// operations on block pairs
+static inline void lfs_pair_swap(lfs_block_t pair[2]) {
+    lfs_block_t t = pair[0];
+    pair[0] = pair[1];
+    pair[1] = t;
+}
+
+static inline bool lfs_pair_isnull(const lfs_block_t pair[2]) {
+    return pair[0] == LFS_BLOCK_NULL || pair[1] == LFS_BLOCK_NULL;
+}
+
+static inline int lfs_pair_cmp(
+        const lfs_block_t paira[2],
+        const lfs_block_t pairb[2]) {
+    return !(paira[0] == pairb[0] || paira[1] == pairb[1] ||
+             paira[0] == pairb[1] || paira[1] == pairb[0]);
+}
+
+static inline bool lfs_pair_sync(
+        const lfs_block_t paira[2],
+        const lfs_block_t pairb[2]) {
+    return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
+           (paira[0] == pairb[1] && paira[1] == pairb[0]);
+}
+
+static inline void lfs_pair_fromle32(lfs_block_t pair[2]) {
+    pair[0] = lfs_fromle32(pair[0]);
+    pair[1] = lfs_fromle32(pair[1]);
+}
+
+static inline void lfs_pair_tole32(lfs_block_t pair[2]) {
+    pair[0] = lfs_tole32(pair[0]);
+    pair[1] = lfs_tole32(pair[1]);
+}
+
+// operations on 32-bit entry tags
+typedef uint32_t lfs_tag_t;
+typedef int32_t lfs_stag_t;
+
+#define LFS_MKTAG(type, id, size) \
+    (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size))
+
+static inline bool lfs_tag_isvalid(lfs_tag_t tag) {
+    return !(tag & 0x80000000);
+}
+
+static inline bool lfs_tag_isdelete(lfs_tag_t tag) {
+    return ((int32_t)(tag << 22) >> 22) == -1;
+}
+
+static inline uint16_t lfs_tag_type1(lfs_tag_t tag) {
+    return (tag & 0x70000000) >> 20;
+}
+
+static inline uint16_t lfs_tag_type3(lfs_tag_t tag) {
+    return (tag & 0x7ff00000) >> 20;
+}
+
+static inline uint8_t lfs_tag_chunk(lfs_tag_t tag) {
+    return (tag & 0x0ff00000) >> 20;
+}
+
+static inline int8_t lfs_tag_splice(lfs_tag_t tag) {
+    return (int8_t)lfs_tag_chunk(tag);
+}
+
+static inline uint16_t lfs_tag_id(lfs_tag_t tag) {
+    return (tag & 0x000ffc00) >> 10;
+}
+
+static inline lfs_size_t lfs_tag_size(lfs_tag_t tag) {
+    return tag & 0x000003ff;
+}
+
+static inline lfs_size_t lfs_tag_dsize(lfs_tag_t tag) {
+    return sizeof(tag) + lfs_tag_size(tag + lfs_tag_isdelete(tag));
+}
+
+// operations on attributes in attribute lists
+struct lfs_mattr {
+    lfs_tag_t tag;
+    const void *buffer;
+};
+
+struct lfs_diskoff {
+    lfs_block_t block;
+    lfs_off_t off;
+};
+
+#define LFS_MKATTRS(...) \
+    (struct lfs_mattr[]){__VA_ARGS__}, \
+    sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr)
+
+// operations on global state
+static inline void lfs_gstate_xor(struct lfs_gstate *a,
+        const struct lfs_gstate *b) {
+    for (int i = 0; i < 3; i++) {
+        ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i];
+    }
+}
+
+static inline bool lfs_gstate_iszero(const struct lfs_gstate *a) {
+    for (int i = 0; i < 3; i++) {
+        if (((uint32_t*)a)[i] != 0) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static inline bool lfs_gstate_hasorphans(const struct lfs_gstate *a) {
+    return lfs_tag_size(a->tag);
+}
+
+static inline uint8_t lfs_gstate_getorphans(const struct lfs_gstate *a) {
+    return lfs_tag_size(a->tag);
+}
+
+static inline bool lfs_gstate_hasmove(const struct lfs_gstate *a) {
+    return lfs_tag_type1(a->tag);
+}
+
+static inline bool lfs_gstate_hasmovehere(const struct lfs_gstate *a,
+        const lfs_block_t *pair) {
+    return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0;
+}
+
+static inline void lfs_gstate_xororphans(struct lfs_gstate *a,
+        const struct lfs_gstate *b, bool orphans) {
+    a->tag ^= LFS_MKTAG(0x800, 0, 0) & (b->tag ^ (orphans << 31));
+}
+
+static inline void lfs_gstate_xormove(struct lfs_gstate *a,
+        const struct lfs_gstate *b, uint16_t id, const lfs_block_t pair[2]) {
+    a->tag ^= LFS_MKTAG(0x7ff, 0x3ff, 0) & (b->tag ^ (
+            (id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0));
+    a->pair[0] ^= b->pair[0] ^ ((id != 0x3ff) ? pair[0] : 0);
+    a->pair[1] ^= b->pair[1] ^ ((id != 0x3ff) ? pair[1] : 0);
+}
+
+static inline void lfs_gstate_fromle32(struct lfs_gstate *a) {
+    a->tag     = lfs_fromle32(a->tag);
+    a->pair[0] = lfs_fromle32(a->pair[0]);
+    a->pair[1] = lfs_fromle32(a->pair[1]);
+}
+
+static inline void lfs_gstate_tole32(struct lfs_gstate *a) {
+    a->tag     = lfs_tole32(a->tag);
+    a->pair[0] = lfs_tole32(a->pair[0]);
+    a->pair[1] = lfs_tole32(a->pair[1]);
+}
+
+// other endianness operations
+static void lfs_ctz_fromle32(struct lfs_ctz *ctz) {
+    ctz->head = lfs_fromle32(ctz->head);
+    ctz->size = lfs_fromle32(ctz->size);
+}
+
+static void lfs_ctz_tole32(struct lfs_ctz *ctz) {
+    ctz->head = lfs_tole32(ctz->head);
+    ctz->size = lfs_tole32(ctz->size);
+}
+
+static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) {
+    superblock->version     = lfs_fromle32(superblock->version);
+    superblock->block_size  = lfs_fromle32(superblock->block_size);
+    superblock->block_count = lfs_fromle32(superblock->block_count);
+    superblock->name_max    = lfs_fromle32(superblock->name_max);
+    superblock->file_max    = lfs_fromle32(superblock->file_max);
+    superblock->attr_max    = lfs_fromle32(superblock->attr_max);
+}
+
+static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) {
+    superblock->version     = lfs_tole32(superblock->version);
+    superblock->block_size  = lfs_tole32(superblock->block_size);
+    superblock->block_count = lfs_tole32(superblock->block_count);
+    superblock->name_max    = lfs_tole32(superblock->name_max);
+    superblock->file_max    = lfs_tole32(superblock->file_max);
+    superblock->attr_max    = lfs_tole32(superblock->attr_max);
+}
+
+
+/// Internal operations predeclared here ///
+static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
+        const struct lfs_mattr *attrs, int attrcount);
+static int lfs_dir_compact(lfs_t *lfs,
+        lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
+        lfs_mdir_t *source, uint16_t begin, uint16_t end);
+static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file);
+static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file);
+static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans);
+static void lfs_fs_prepmove(lfs_t *lfs,
+        uint16_t id, const lfs_block_t pair[2]);
+static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2],
+        lfs_mdir_t *pdir);
+static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2],
+        lfs_mdir_t *parent);
+static int lfs_fs_relocate(lfs_t *lfs,
+        const lfs_block_t oldpair[2], lfs_block_t newpair[2]);
+static int lfs_fs_forceconsistency(lfs_t *lfs);
+static int lfs_deinit(lfs_t *lfs);
+#ifdef LFS_MIGRATE
+static int lfs1_traverse(lfs_t *lfs,
+        int (*cb)(void*, lfs_block_t), void *data);
+#endif
+
+/// Block allocator ///
+static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
+    lfs_t *lfs = (lfs_t*)p;
+    lfs_block_t off = ((block - lfs->free.off)
+            + lfs->cfg->block_count) % lfs->cfg->block_count;
+
+    if (off < lfs->free.size) {
+        lfs->free.buffer[off / 32] |= 1U << (off % 32);
+    }
+
+    return 0;
+}
+
+static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
+    while (true) {
+        while (lfs->free.i != lfs->free.size) {
+            lfs_block_t off = lfs->free.i;
+            lfs->free.i += 1;
+            lfs->free.ack -= 1;
+
+            if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
+                // found a free block
+                *block = (lfs->free.off + off) % lfs->cfg->block_count;
+
+                // eagerly find next off so an alloc ack can
+                // discredit old lookahead blocks
+                while (lfs->free.i != lfs->free.size &&
+                        (lfs->free.buffer[lfs->free.i / 32]
+                            & (1U << (lfs->free.i % 32)))) {
+                    lfs->free.i += 1;
+                    lfs->free.ack -= 1;
+                }
+
+                return 0;
+            }
+        }
+
+        // check if we have looked at all blocks since last ack
+        if (lfs->free.ack == 0) {
+            LFS_ERROR("No more free space %"PRIu32,
+                    lfs->free.i + lfs->free.off);
+            return LFS_ERR_NOSPC;
+        }
+
+        lfs->free.off = (lfs->free.off + lfs->free.size)
+                % lfs->cfg->block_count;
+        lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack);
+        lfs->free.i = 0;
+
+        // find mask of free blocks from tree
+        memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
+        int err = lfs_fs_traverse(lfs, lfs_alloc_lookahead, lfs);
+        if (err) {
+            return err;
+        }
+    }
+}
+
+static void lfs_alloc_ack(lfs_t *lfs) {
+    lfs->free.ack = lfs->cfg->block_count;
+}
+
+
+/// Metadata pair and directory operations ///
+static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir,
+        lfs_tag_t gmask, lfs_tag_t gtag,
+        lfs_off_t goff, void *gbuffer, lfs_size_t gsize) {
+    lfs_off_t off = dir->off;
+    lfs_tag_t ntag = dir->etag;
+    lfs_stag_t gdiff = 0;
+
+    if (lfs_gstate_hasmovehere(&lfs->gstate, dir->pair) &&
+            lfs_tag_id(gtag) <= lfs_tag_id(lfs->gstate.tag)) {
+        // synthetic moves
+        gdiff -= LFS_MKTAG(0, 1, 0);
+    }
+
+    // iterate over dir block backwards (for faster lookups)
+    while (off >= sizeof(lfs_tag_t) + lfs_tag_dsize(ntag)) {
+        off -= lfs_tag_dsize(ntag);
+        lfs_tag_t tag = ntag;
+        int err = lfs_bd_read(lfs,
+                NULL, &lfs->rcache, sizeof(ntag),
+                dir->pair[0], off, &ntag, sizeof(ntag));
+        if (err) {
+            return err;
+        }
+
+        ntag = (lfs_frombe32(ntag) ^ tag) & 0x7fffffff;
+
+        if (lfs_tag_id(gmask) != 0 &&
+                lfs_tag_type1(tag) == LFS_TYPE_SPLICE &&
+                lfs_tag_id(tag) <= lfs_tag_id(gtag - gdiff)) {
+            if (tag == (LFS_MKTAG(LFS_TYPE_CREATE, 0, 0) |
+                    (LFS_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) {
+                // found where we were created
+                return LFS_ERR_NOENT;
+            }
+
+            // move around splices
+            gdiff += LFS_MKTAG(0, lfs_tag_splice(tag), 0);
+        }
+
+        if ((gmask & tag) == (gmask & (gtag - gdiff))) {
+            if (lfs_tag_isdelete(tag)) {
+                return LFS_ERR_NOENT;
+            }
+
+            lfs_size_t diff = lfs_min(lfs_tag_size(tag), gsize);
+            err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, diff,
+                    dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff);
+            if (err) {
+                return err;
+            }
+
+            memset((uint8_t*)gbuffer + diff, 0, gsize - diff);
+
+            return tag + gdiff;
+        }
+    }
+
+    return LFS_ERR_NOENT;
+}
+
+static lfs_stag_t lfs_dir_get(lfs_t *lfs, const lfs_mdir_t *dir,
+        lfs_tag_t gmask, lfs_tag_t gtag, void *buffer) {
+    return lfs_dir_getslice(lfs, dir,
+            gmask, gtag,
+            0, buffer, lfs_tag_size(gtag));
+}
+
+static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
+        lfs_tag_t gmask, lfs_tag_t gtag,
+        lfs_off_t off, void *buffer, lfs_size_t size) {
+    uint8_t *data = buffer;
+    if (off+size > lfs->cfg->block_size) {
+        return LFS_ERR_CORRUPT;
+    }
+
+    while (size > 0) {
+        lfs_size_t diff = size;
+
+        if (pcache && pcache->block == LFS_BLOCK_INLINE &&
+                off < pcache->off + pcache->size) {
+            if (off >= pcache->off) {
+                // is already in pcache?
+                diff = lfs_min(diff, pcache->size - (off-pcache->off));
+                memcpy(data, &pcache->buffer[off-pcache->off], diff);
+
+                data += diff;
+                off += diff;
+                size -= diff;
+                continue;
+            }
+
+            // pcache takes priority
+            diff = lfs_min(diff, pcache->off-off);
+        }
+
+        if (rcache->block == LFS_BLOCK_INLINE &&
+                off < rcache->off + rcache->size) {
+            if (off >= rcache->off) {
+                // is already in rcache?
+                diff = lfs_min(diff, rcache->size - (off-rcache->off));
+                memcpy(data, &rcache->buffer[off-rcache->off], diff);
+
+                data += diff;
+                off += diff;
+                size -= diff;
+                continue;
+            }
+
+            // rcache takes priority
+            diff = lfs_min(diff, rcache->off-off);
+        }
+
+        // load to cache, first condition can no longer fail
+        rcache->block = LFS_BLOCK_INLINE;
+        rcache->off = lfs_aligndown(off, lfs->cfg->read_size);
+        rcache->size = lfs_min(lfs_alignup(off+hint, lfs->cfg->read_size),
+                lfs->cfg->cache_size);
+        int err = lfs_dir_getslice(lfs, dir, gmask, gtag,
+                rcache->off, rcache->buffer, rcache->size);
+        if (err < 0) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+static int lfs_dir_traverse_filter(void *p,
+        lfs_tag_t tag, const void *buffer) {
+    lfs_tag_t *filtertag = p;
+    (void)buffer;
+
+    // which mask depends on unique bit in tag structure
+    uint32_t mask = (tag & LFS_MKTAG(0x100, 0, 0))
+            ? LFS_MKTAG(0x7ff, 0x3ff, 0)
+            : LFS_MKTAG(0x700, 0x3ff, 0);
+
+    // check for redundancy
+    if ((mask & tag) == (mask & *filtertag) ||
+            lfs_tag_isdelete(*filtertag) ||
+            (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == (
+                LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) |
+                    (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) {
+        return true;
+    }
+
+    // check if we need to adjust for created/deleted tags
+    if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE &&
+            lfs_tag_id(tag) <= lfs_tag_id(*filtertag)) {
+        *filtertag += LFS_MKTAG(0, lfs_tag_splice(tag), 0);
+    }
+
+    return false;
+}
+
+static int lfs_dir_traverse(lfs_t *lfs,
+        const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag,
+        const struct lfs_mattr *attrs, int attrcount, bool hasseenmove,
+        lfs_tag_t tmask, lfs_tag_t ttag,
+        uint16_t begin, uint16_t end, int16_t diff,
+        int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) {
+    // iterate over directory and attrs
+    while (true) {
+        lfs_tag_t tag;
+        const void *buffer;
+        struct lfs_diskoff disk;
+        if (off+lfs_tag_dsize(ptag) < dir->off) {
+            off += lfs_tag_dsize(ptag);
+            int err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, sizeof(tag),
+                    dir->pair[0], off, &tag, sizeof(tag));
+            if (err) {
+                return err;
+            }
+
+            tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000;
+            disk.block = dir->pair[0];
+            disk.off = off+sizeof(lfs_tag_t);
+            buffer = &disk;
+            ptag = tag;
+        } else if (attrcount > 0) {
+            tag = attrs[0].tag;
+            buffer = attrs[0].buffer;
+            attrs += 1;
+            attrcount -= 1;
+        } else if (!hasseenmove &&
+                lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) {
+            // Wait, we have pending move? Handle this here (we need to
+            // or else we risk letting moves fall out of date)
+            tag = lfs->gpending.tag & LFS_MKTAG(0x7ff, 0x3ff, 0);
+            buffer = NULL;
+            hasseenmove = true;
+        } else {
+            return 0;
+        }
+
+        lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0);
+        if ((mask & tmask & tag) != (mask & tmask & ttag)) {
+            continue;
+        }
+
+        // do we need to filter? inlining the filtering logic here allows
+        // for some minor optimizations
+        if (lfs_tag_id(tmask) != 0) {
+            // scan for duplicates and update tag based on creates/deletes
+            int filter = lfs_dir_traverse(lfs,
+                    dir, off, ptag, attrs, attrcount, hasseenmove,
+                    0, 0, 0, 0, 0,
+                    lfs_dir_traverse_filter, &tag);
+            if (filter < 0) {
+                return filter;
+            }
+
+            if (filter) {
+                continue;
+            }
+
+            // in filter range?
+            if (!(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) {
+                continue;
+            }
+        }
+
+        // handle special cases for mcu-side operations
+        if (lfs_tag_type3(tag) == LFS_FROM_NOOP) {
+            // do nothing
+        } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) {
+            uint16_t fromid = lfs_tag_size(tag);
+            uint16_t toid = lfs_tag_id(tag);
+            int err = lfs_dir_traverse(lfs,
+                    buffer, 0, LFS_BLOCK_NULL, NULL, 0, true,
+                    LFS_MKTAG(0x600, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0),
+                    fromid, fromid+1, toid-fromid+diff,
+                    cb, data);
+            if (err) {
+                return err;
+            }
+        } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) {
+            for (unsigned i = 0; i < lfs_tag_size(tag); i++) {
+                const struct lfs_attr *a = buffer;
+                int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type,
+                        lfs_tag_id(tag) + diff, a[i].size), a[i].buffer);
+                if (err) {
+                    return err;
+                }
+            }
+        } else {
+            int err = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer);
+            if (err) {
+                return err;
+            }
+        }
+    }
+}
+
+static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
+        lfs_mdir_t *dir, const lfs_block_t pair[2],
+        lfs_tag_t fmask, lfs_tag_t ftag, uint16_t *id,
+        int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) {
+    // we can find tag very efficiently during a fetch, since we're already
+    // scanning the entire directory
+    lfs_stag_t besttag = -1;
+
+    // find the block with the most recent revision
+    uint32_t revs[2] = {0, 0};
+    int r = 0;
+    for (int i = 0; i < 2; i++) {
+        int err = lfs_bd_read(lfs,
+                NULL, &lfs->rcache, sizeof(revs[i]),
+                pair[i], 0, &revs[i], sizeof(revs[i]));
+        revs[i] = lfs_fromle32(revs[i]);
+        if (err && err != LFS_ERR_CORRUPT) {
+            return err;
+        }
+
+        if (err != LFS_ERR_CORRUPT &&
+                lfs_scmp(revs[i], revs[(i+1)%2]) > 0) {
+            r = i;
+        }
+    }
+
+    dir->pair[0] = pair[(r+0)%2];
+    dir->pair[1] = pair[(r+1)%2];
+    dir->rev = revs[(r+0)%2];
+    dir->off = 0; // nonzero = found some commits
+
+    // now scan tags to fetch the actual dir and find possible match
+    for (int i = 0; i < 2; i++) {
+        lfs_off_t off = 0;
+        lfs_tag_t ptag = LFS_BLOCK_NULL;
+
+        uint16_t tempcount = 0;
+        lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
+        bool tempsplit = false;
+        lfs_stag_t tempbesttag = besttag;
+
+        dir->rev = lfs_tole32(dir->rev);
+        uint32_t crc = lfs_crc(LFS_BLOCK_NULL, &dir->rev, sizeof(dir->rev));
+        dir->rev = lfs_fromle32(dir->rev);
+
+        while (true) {
+            // extract next tag
+            lfs_tag_t tag;
+            off += lfs_tag_dsize(ptag);
+            int err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, lfs->cfg->block_size,
+                    dir->pair[0], off, &tag, sizeof(tag));
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    // can't continue?
+                    dir->erased = false;
+                    break;
+                }
+                return err;
+            }
+
+            crc = lfs_crc(crc, &tag, sizeof(tag));
+            tag = lfs_frombe32(tag) ^ ptag;
+
+            // next commit not yet programmed or we're not in valid range
+            if (!lfs_tag_isvalid(tag) ||
+                    off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
+                dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC &&
+                        dir->off % lfs->cfg->prog_size == 0);
+                break;
+            }
+
+            ptag = tag;
+
+            if (lfs_tag_type1(tag) == LFS_TYPE_CRC) {
+                // check the crc attr
+                uint32_t dcrc;
+                err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->block_size,
+                        dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc));
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        dir->erased = false;
+                        break;
+                    }
+                    return err;
+                }
+                dcrc = lfs_fromle32(dcrc);
+
+                if (crc != dcrc) {
+                    dir->erased = false;
+                    break;
+                }
+
+                // reset the next bit if we need to
+                ptag ^= (lfs_tag_chunk(tag) & 1U) << 31;
+
+                // toss our crc into the filesystem seed for
+                // pseudorandom numbers
+                lfs->seed ^= crc;
+
+                // update with what's found so far
+                besttag = tempbesttag;
+                dir->off = off + lfs_tag_dsize(tag);
+                dir->etag = ptag;
+                dir->count = tempcount;
+                dir->tail[0] = temptail[0];
+                dir->tail[1] = temptail[1];
+                dir->split = tempsplit;
+
+                // reset crc
+                crc = LFS_BLOCK_NULL;
+                continue;
+            }
+
+            // crc the entry first, hopefully leaving it in the cache
+            for (lfs_off_t j = sizeof(tag); j < lfs_tag_dsize(tag); j++) {
+                uint8_t dat;
+                err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->block_size,
+                        dir->pair[0], off+j, &dat, 1);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        dir->erased = false;
+                        break;
+                    }
+                    return err;
+                }
+
+                crc = lfs_crc(crc, &dat, 1);
+            }
+
+            // directory modification tags?
+            if (lfs_tag_type1(tag) == LFS_TYPE_NAME) {
+                // increase count of files if necessary
+                if (lfs_tag_id(tag) >= tempcount) {
+                    tempcount = lfs_tag_id(tag) + 1;
+                }
+            } else if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE) {
+                tempcount += lfs_tag_splice(tag);
+
+                if (tag == (LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) |
+                        (LFS_MKTAG(0, 0x3ff, 0) & tempbesttag))) {
+                    tempbesttag |= 0x80000000;
+                } else if (tempbesttag != -1 &&
+                        lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) {
+                    tempbesttag += LFS_MKTAG(0, lfs_tag_splice(tag), 0);
+                }
+            } else if (lfs_tag_type1(tag) == LFS_TYPE_TAIL) {
+                tempsplit = (lfs_tag_chunk(tag) & 1);
+
+                err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->block_size,
+                        dir->pair[0], off+sizeof(tag), &temptail, 8);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        dir->erased = false;
+                        break;
+                    }
+                }
+                lfs_pair_fromle32(temptail);
+            }
+
+            // found a match for our fetcher?
+            if ((fmask & tag) == (fmask & ftag)) {
+                int res = cb(data, tag, &(struct lfs_diskoff){
+                        dir->pair[0], off+sizeof(tag)});
+                if (res < 0) {
+                    if (res == LFS_ERR_CORRUPT) {
+                        dir->erased = false;
+                        break;
+                    }
+                    return res;
+                }
+
+                if (res == LFS_CMP_EQ) {
+                    // found a match
+                    tempbesttag = tag;
+                } else if (res == LFS_CMP_GT &&
+                        lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) {
+                    // found a greater match, keep track to keep things sorted
+                    tempbesttag = tag | 0x80000000;
+                }
+            }
+        }
+
+        // consider what we have good enough
+        if (dir->off > 0) {
+            // synthetic move
+            if (lfs_gstate_hasmovehere(&lfs->gstate, dir->pair)) {
+                if (lfs_tag_id(lfs->gstate.tag) == lfs_tag_id(besttag)) {
+                    besttag |= 0x80000000;
+                } else if (besttag != -1 &&
+                        lfs_tag_id(lfs->gstate.tag) < lfs_tag_id(besttag)) {
+                    besttag -= LFS_MKTAG(0, 1, 0);
+                }
+            }
+
+            // found tag? or found best id?
+            if (id) {
+                *id = lfs_min(lfs_tag_id(besttag), dir->count);
+            }
+
+            if (lfs_tag_isvalid(besttag)) {
+                return besttag;
+            } else if (lfs_tag_id(besttag) < dir->count) {
+                return LFS_ERR_NOENT;
+            } else {
+                return 0;
+            }
+        }
+
+        // failed, try the other block?
+        lfs_pair_swap(dir->pair);
+        dir->rev = revs[(r+1)%2];
+    }
+
+    LFS_ERROR("Corrupted dir pair at %"PRIx32" %"PRIx32,
+            dir->pair[0], dir->pair[1]);
+    return LFS_ERR_CORRUPT;
+}
+
+static int lfs_dir_fetch(lfs_t *lfs,
+        lfs_mdir_t *dir, const lfs_block_t pair[2]) {
+    // note, mask=-1, tag=0 can never match a tag since this
+    // pattern has the invalid bit set
+    return lfs_dir_fetchmatch(lfs, dir, pair, -1, 0, NULL, NULL, NULL);
+}
+
+static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir,
+        struct lfs_gstate *gstate) {
+    struct lfs_gstate temp;
+    lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0),
+            LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp);
+    if (res < 0 && res != LFS_ERR_NOENT) {
+        return res;
+    }
+
+    if (res != LFS_ERR_NOENT) {
+        // xor together to find resulting gstate
+        lfs_gstate_fromle32(&temp);
+        lfs_gstate_xor(gstate, &temp);
+    }
+
+    return 0;
+}
+
+static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir,
+        uint16_t id, struct lfs_info *info) {
+    if (id == 0x3ff) {
+        // special case for root
+        strcpy(info->name, "/");
+        info->type = LFS_TYPE_DIR;
+        return 0;
+    }
+
+    lfs_stag_t tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x780, 0x3ff, 0),
+            LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name);
+    if (tag < 0) {
+        return tag;
+    }
+
+    info->type = lfs_tag_type3(tag);
+
+    struct lfs_ctz ctz;
+    tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0),
+            LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz);
+    if (tag < 0) {
+        return tag;
+    }
+    lfs_ctz_fromle32(&ctz);
+
+    if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) {
+        info->size = ctz.size;
+    } else if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) {
+        info->size = lfs_tag_size(tag);
+    }
+
+    return 0;
+}
+
+struct lfs_dir_find_match {
+    lfs_t *lfs;
+    const void *name;
+    lfs_size_t size;
+};
+
+static int lfs_dir_find_match(void *data,
+        lfs_tag_t tag, const void *buffer) {
+    struct lfs_dir_find_match *name = data;
+    lfs_t *lfs = name->lfs;
+    const struct lfs_diskoff *disk = buffer;
+
+    // compare with disk
+    lfs_size_t diff = lfs_min(name->size, lfs_tag_size(tag));
+    int res = lfs_bd_cmp(lfs,
+            NULL, &lfs->rcache, diff,
+            disk->block, disk->off, name->name, diff);
+    if (res != LFS_CMP_EQ) {
+        return res;
+    }
+
+    // only equal if our size is still the same
+    if (name->size != lfs_tag_size(tag)) {
+        return (name->size < lfs_tag_size(tag)) ? LFS_CMP_LT : LFS_CMP_GT;
+    }
+
+    // found a match!
+    return LFS_CMP_EQ;
+}
+
+static int lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir,
+        const char **path, uint16_t *id) {
+    // we reduce path to a single name if we can find it
+    const char *name = *path;
+    if (id) {
+        *id = 0x3ff;
+    }
+
+    // default to root dir
+    lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0);
+    dir->tail[0] = lfs->root[0];
+    dir->tail[1] = lfs->root[1];
+
+    while (true) {
+nextname:
+        // skip slashes
+        name += strspn(name, "/");
+        lfs_size_t namelen = strcspn(name, "/");
+
+        // skip '.' and root '..'
+        if ((namelen == 1 && memcmp(name, ".", 1) == 0) ||
+            (namelen == 2 && memcmp(name, "..", 2) == 0)) {
+            name += namelen;
+            goto nextname;
+        }
+
+        // skip if matched by '..' in name
+        const char *suffix = name + namelen;
+        lfs_size_t sufflen;
+        int depth = 1;
+        while (true) {
+            suffix += strspn(suffix, "/");
+            sufflen = strcspn(suffix, "/");
+            if (sufflen == 0) {
+                break;
+            }
+
+            if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
+                depth -= 1;
+                if (depth == 0) {
+                    name = suffix + sufflen;
+                    goto nextname;
+                }
+            } else {
+                depth += 1;
+            }
+
+            suffix += sufflen;
+        }
+
+        // found path
+        if (name[0] == '\0') {
+            return tag;
+        }
+
+        // update what we've found so far
+        *path = name;
+
+        // only continue if we hit a directory
+        if (lfs_tag_type3(tag) != LFS_TYPE_DIR) {
+            return LFS_ERR_NOTDIR;
+        }
+
+        // grab the entry data
+        if (lfs_tag_id(tag) != 0x3ff) {
+            lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), dir->tail);
+            if (res < 0) {
+                return res;
+            }
+            lfs_pair_fromle32(dir->tail);
+        }
+
+        // find entry matching name
+        while (true) {
+            tag = lfs_dir_fetchmatch(lfs, dir, dir->tail,
+                    LFS_MKTAG(0x780, 0, 0),
+                    LFS_MKTAG(LFS_TYPE_NAME, 0, namelen),
+                     // are we last name?
+                    (strchr(name, '/') == NULL) ? id : NULL,
+                    lfs_dir_find_match, &(struct lfs_dir_find_match){
+                        lfs, name, namelen});
+            if (tag < 0) {
+                return tag;
+            }
+
+            if (tag) {
+                break;
+            }
+
+            if (!dir->split) {
+                return LFS_ERR_NOENT;
+            }
+        }
+
+        // to next name
+        name += namelen;
+    }
+}
+
+// commit logic
+struct lfs_commit {
+    lfs_block_t block;
+    lfs_off_t off;
+    lfs_tag_t ptag;
+    uint32_t crc;
+
+    lfs_off_t begin;
+    lfs_off_t end;
+};
+
+static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit,
+        const void *buffer, lfs_size_t size) {
+    int err = lfs_bd_prog(lfs,
+            &lfs->pcache, &lfs->rcache, false,
+            commit->block, commit->off ,
+            (const uint8_t*)buffer, size);
+    if (err) {
+        return err;
+    }
+
+    commit->crc = lfs_crc(commit->crc, buffer, size);
+    commit->off += size;
+    return 0;
+}
+
+static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit,
+        lfs_tag_t tag, const void *buffer) {
+    // check if we fit
+    lfs_size_t dsize = lfs_tag_dsize(tag);
+    if (commit->off + dsize > commit->end) {
+        return LFS_ERR_NOSPC;
+    }
+
+    // write out tag
+    lfs_tag_t ntag = lfs_tobe32((tag & 0x7fffffff) ^ commit->ptag);
+    int err = lfs_dir_commitprog(lfs, commit, &ntag, sizeof(ntag));
+    if (err) {
+        return err;
+    }
+
+    if (!(tag & 0x80000000)) {
+        // from memory
+        err = lfs_dir_commitprog(lfs, commit, buffer, dsize-sizeof(tag));
+        if (err) {
+            return err;
+        }
+    } else {
+        // from disk
+        const struct lfs_diskoff *disk = buffer;
+        for (lfs_off_t i = 0; i < dsize-sizeof(tag); i++) {
+            // rely on caching to make this efficient
+            uint8_t dat;
+            err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, dsize-sizeof(tag)-i,
+                    disk->block, disk->off+i, &dat, 1);
+            if (err) {
+                return err;
+            }
+
+            err = lfs_dir_commitprog(lfs, commit, &dat, 1);
+            if (err) {
+                return err;
+            }
+        }
+    }
+
+    commit->ptag = tag & 0x7fffffff;
+    return 0;
+}
+
+static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
+    // align to program units
+    const lfs_off_t off1 = commit->off + sizeof(lfs_tag_t);
+    const lfs_off_t end = lfs_alignup(off1 + sizeof(uint32_t),
+            lfs->cfg->prog_size);
+
+    // create crc tags to fill up remainder of commit, note that
+    // padding is not crcd, which lets fetches skip padding but
+    // makes committing a bit more complicated
+    while (commit->off < end) {
+        lfs_off_t off = commit->off + sizeof(lfs_tag_t);
+        lfs_off_t noff = lfs_min(end - off, 0x3fe) + off;
+        if (noff < end) {
+            noff = lfs_min(noff, end - 2*sizeof(uint32_t));
+        }
+
+        // read erased state from next program unit
+        lfs_tag_t tag = LFS_BLOCK_NULL;
+        int err = lfs_bd_read(lfs,
+                NULL, &lfs->rcache, sizeof(tag),
+                commit->block, noff, &tag, sizeof(tag));
+        if (err && err != LFS_ERR_CORRUPT) {
+            return err;
+        }
+
+        // build crc tag
+        bool reset = ~lfs_frombe32(tag) >> 31;
+        tag = LFS_MKTAG(LFS_TYPE_CRC + reset, 0x3ff, noff - off);
+
+        // write out crc
+        uint32_t footer[2];
+        footer[0] = lfs_tobe32(tag ^ commit->ptag);
+        commit->crc = lfs_crc(commit->crc, &footer[0], sizeof(footer[0]));
+        footer[1] = lfs_tole32(commit->crc);
+        err = lfs_bd_prog(lfs,
+                &lfs->pcache, &lfs->rcache, false,
+                commit->block, commit->off, &footer, sizeof(footer));
+        if (err) {
+            return err;
+        }
+
+        commit->off += sizeof(tag)+lfs_tag_size(tag);
+        commit->ptag = tag ^ (reset << 31);
+        commit->crc = LFS_BLOCK_NULL; // reset crc for next "commit"
+    }
+
+    // flush buffers
+    int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false);
+    if (err) {
+        return err;
+    }
+
+    // successful commit, check checksums to make sure
+    lfs_off_t off = commit->begin;
+    lfs_off_t noff = off1;
+    while (off < end) {
+        uint32_t crc = LFS_BLOCK_NULL;
+        for (lfs_off_t i = off; i < noff+sizeof(uint32_t); i++) {
+            // leave it up to caching to make this efficient
+            uint8_t dat;
+            err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, noff+sizeof(uint32_t)-i,
+                    commit->block, i, &dat, 1);
+            if (err) {
+                return err;
+            }
+
+            crc = lfs_crc(crc, &dat, 1);
+        }
+
+        // detected write error?
+        if (crc != 0) {
+            return LFS_ERR_CORRUPT;
+        }
+
+        // skip padding
+        off = lfs_min(end - noff, 0x3fe) + noff;
+        if (off < end) {
+            off = lfs_min(off, end - 2*sizeof(uint32_t));
+        }
+        noff = off + sizeof(uint32_t);
+    }
+
+    return 0;
+}
+
+static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) {
+    // allocate pair of dir blocks (backwards, so we write block 1 first)
+    for (int i = 0; i < 2; i++) {
+        int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]);
+        if (err) {
+            return err;
+        }
+    }
+
+    // rather than clobbering one of the blocks we just pretend
+    // the revision may be valid
+    int err = lfs_bd_read(lfs,
+            NULL, &lfs->rcache, sizeof(dir->rev),
+            dir->pair[0], 0, &dir->rev, sizeof(dir->rev));
+    dir->rev = lfs_fromle32(dir->rev);
+    if (err && err != LFS_ERR_CORRUPT) {
+        return err;
+    }
+
+    // make sure we don't immediately evict
+    dir->rev += dir->rev & 1;
+
+    // set defaults
+    dir->off = sizeof(dir->rev);
+    dir->etag = LFS_BLOCK_NULL;
+    dir->count = 0;
+    dir->tail[0] = LFS_BLOCK_NULL;
+    dir->tail[1] = LFS_BLOCK_NULL;
+    dir->erased = false;
+    dir->split = false;
+
+    // don't write out yet, let caller take care of that
+    return 0;
+}
+
+static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) {
+    // steal state
+    int err = lfs_dir_getgstate(lfs, tail, &lfs->gdelta);
+    if (err) {
+        return err;
+    }
+
+    // steal tail
+    lfs_pair_tole32(tail->tail);
+    err = lfs_dir_commit(lfs, dir, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail}));
+    lfs_pair_fromle32(tail->tail);
+    if (err) {
+        return err;
+    }
+
+    return 0;
+}
+
+static int lfs_dir_split(lfs_t *lfs,
+        lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
+        lfs_mdir_t *source, uint16_t split, uint16_t end) {
+    // create tail directory
+    lfs_mdir_t tail;
+    int err = lfs_dir_alloc(lfs, &tail);
+    if (err) {
+        return err;
+    }
+
+    tail.split = dir->split;
+    tail.tail[0] = dir->tail[0];
+    tail.tail[1] = dir->tail[1];
+
+    err = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end);
+    if (err) {
+        return err;
+    }
+
+    dir->tail[0] = tail.pair[0];
+    dir->tail[1] = tail.pair[1];
+    dir->split = true;
+
+    // update root if needed
+    if (lfs_pair_cmp(dir->pair, lfs->root) == 0 && split == 0) {
+        lfs->root[0] = tail.pair[0];
+        lfs->root[1] = tail.pair[1];
+    }
+
+    return 0;
+}
+
+static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) {
+    lfs_size_t *size = p;
+    (void)buffer;
+
+    *size += lfs_tag_dsize(tag);
+    return 0;
+}
+
+struct lfs_dir_commit_commit {
+    lfs_t *lfs;
+    struct lfs_commit *commit;
+};
+
+static int lfs_dir_commit_commit(void *p, lfs_tag_t tag, const void *buffer) {
+    struct lfs_dir_commit_commit *commit = p;
+    return lfs_dir_commitattr(commit->lfs, commit->commit, tag, buffer);
+}
+
+static int lfs_dir_compact(lfs_t *lfs,
+        lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
+        lfs_mdir_t *source, uint16_t begin, uint16_t end) {
+    // save some state in case block is bad
+    const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]};
+    bool relocated = false;
+    bool exhausted = false;
+
+    // should we split?
+    while (end - begin > 1) {
+        // find size
+        lfs_size_t size = 0;
+        int err = lfs_dir_traverse(lfs,
+                source, 0, LFS_BLOCK_NULL, attrs, attrcount, false,
+                LFS_MKTAG(0x400, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_NAME, 0, 0),
+                begin, end, -begin,
+                lfs_dir_commit_size, &size);
+        if (err) {
+            return err;
+        }
+
+        // space is complicated, we need room for tail, crc, gstate,
+        // cleanup delete, and we cap at half a block to give room
+        // for metadata updates.
+        if (end - begin < 0xff &&
+                size <= lfs_min(lfs->cfg->block_size - 36,
+                    lfs_alignup(lfs->cfg->block_size/2,
+                        lfs->cfg->prog_size))) {
+            break;
+        }
+
+        // can't fit, need to split, we should really be finding the
+        // largest size that fits with a small binary search, but right now
+        // it's not worth the code size
+        uint16_t split = (end - begin) / 2;
+        err = lfs_dir_split(lfs, dir, attrs, attrcount,
+                source, begin+split, end);
+        if (err) {
+            // if we fail to split, we may be able to overcompact, unless
+            // we're too big for even the full block, in which case our
+            // only option is to error
+            if (err == LFS_ERR_NOSPC && size <= lfs->cfg->block_size - 36) {
+                break;
+            }
+            return err;
+        }
+
+        end = begin + split;
+    }
+
+    // increment revision count
+    dir->rev += 1;
+    if (lfs->cfg->block_cycles > 0 &&
+            (dir->rev % (lfs->cfg->block_cycles+1) == 0)) {
+        if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) {
+            // oh no! we're writing too much to the superblock,
+            // should we expand?
+            lfs_ssize_t res = lfs_fs_size(lfs);
+            if (res < 0) {
+                return res;
+            }
+
+            // do we have extra space? littlefs can't reclaim this space
+            // by itself, so expand cautiously
+            if ((lfs_size_t)res < lfs->cfg->block_count/2) {
+                LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev);
+                int err = lfs_dir_split(lfs, dir, attrs, attrcount,
+                        source, begin, end);
+                if (err && err != LFS_ERR_NOSPC) {
+                    return err;
+                }
+
+                // welp, we tried, if we ran out of space there's not much
+                // we can do, we'll error later if we've become frozen
+                if (!err) {
+                    end = begin;
+                }
+            }
+#ifdef LFS_MIGRATE
+        } else if (lfs_pair_cmp(dir->pair, lfs->root) == 0 && lfs->lfs1) {
+            // we can't relocate our root during migrations, as this would
+            // cause the superblock to get updated, which would clobber v1
+#endif
+        } else {
+            // we're writing too much, time to relocate
+            exhausted = true;
+            goto relocate;
+        }
+    }
+
+    // begin loop to commit compaction to blocks until a compact sticks
+    while (true) {
+        {
+            // There's nothing special about our global delta, so feed it into
+            // our local global delta
+            int err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta);
+            if (err) {
+                return err;
+            }
+
+            // setup commit state
+            struct lfs_commit commit = {
+                .block = dir->pair[1],
+                .off = 0,
+                .ptag = LFS_BLOCK_NULL,
+                .crc = LFS_BLOCK_NULL,
+
+                .begin = 0,
+                .end = lfs->cfg->block_size - 8,
+            };
+
+            // erase block to write to
+            err = lfs_bd_erase(lfs, dir->pair[1]);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            // write out header
+            dir->rev = lfs_tole32(dir->rev);
+            err = lfs_dir_commitprog(lfs, &commit,
+                    &dir->rev, sizeof(dir->rev));
+            dir->rev = lfs_fromle32(dir->rev);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            // traverse the directory, this time writing out all unique tags
+            err = lfs_dir_traverse(lfs,
+                    source, 0, LFS_BLOCK_NULL, attrs, attrcount, false,
+                    LFS_MKTAG(0x400, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_NAME, 0, 0),
+                    begin, end, -begin,
+                    lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){
+                        lfs, &commit});
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            // commit tail, which may be new after last size check
+            if (!lfs_pair_isnull(dir->tail)) {
+                lfs_pair_tole32(dir->tail);
+                err = lfs_dir_commitattr(lfs, &commit,
+                        LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8),
+                        dir->tail);
+                lfs_pair_fromle32(dir->tail);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        goto relocate;
+                    }
+                    return err;
+                }
+            }
+
+            if (!relocated && !lfs_gstate_iszero(&lfs->gdelta)) {
+                // commit any globals, unless we're relocating,
+                // in which case our parent will steal our globals
+                lfs_gstate_tole32(&lfs->gdelta);
+                err = lfs_dir_commitattr(lfs, &commit,
+                        LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff,
+                            sizeof(lfs->gdelta)), &lfs->gdelta);
+                lfs_gstate_fromle32(&lfs->gdelta);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        goto relocate;
+                    }
+                    return err;
+                }
+            }
+
+            err = lfs_dir_commitcrc(lfs, &commit);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            // successful compaction, swap dir pair to indicate most recent
+            LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0);
+            lfs_pair_swap(dir->pair);
+            dir->count = end - begin;
+            dir->off = commit.off;
+            dir->etag = commit.ptag;
+            // note we able to have already handled move here
+            if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) {
+                lfs_gstate_xormove(&lfs->gpending,
+                    &lfs->gpending, 0x3ff, NULL);
+            }
+        }
+        break;
+
+relocate:
+        // commit was corrupted, drop caches and prepare to relocate block
+        relocated = true;
+        lfs_cache_drop(lfs, &lfs->pcache);
+        if (!exhausted) {
+            LFS_DEBUG("Bad block at %"PRIx32, dir->pair[1]);
+        }
+
+        // can't relocate superblock, filesystem is now frozen
+        if (lfs_pair_cmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) {
+            LFS_WARN("Superblock %"PRIx32" has become unwritable", oldpair[1]);
+            return LFS_ERR_NOSPC;
+        }
+
+        // relocate half of pair
+        int err = lfs_alloc(lfs, &dir->pair[1]);
+        if (err && (err != LFS_ERR_NOSPC && !exhausted)) {
+            return err;
+        }
+
+        continue;
+    }
+
+    if (!relocated) {
+        lfs->gstate = lfs->gpending;
+        lfs->gdelta = (struct lfs_gstate){0};
+    } else {
+        // update references if we relocated
+        LFS_DEBUG("Relocating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32,
+                oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]);
+        int err = lfs_fs_relocate(lfs, oldpair, dir->pair);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
+        const struct lfs_mattr *attrs, int attrcount) {
+    // check for any inline files that aren't RAM backed and
+    // forcefully evict them, needed for filesystem consistency
+    for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
+        if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 &&
+                f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) &&
+                f->ctz.size > lfs->cfg->cache_size) {
+            int err = lfs_file_outline(lfs, f);
+            if (err) {
+                return err;
+            }
+
+            err = lfs_file_flush(lfs, f);
+            if (err) {
+                return err;
+            }
+        }
+    }
+
+    // calculate changes to the directory
+    lfs_tag_t deletetag = LFS_BLOCK_NULL;
+    lfs_tag_t createtag = LFS_BLOCK_NULL;
+    for (int i = 0; i < attrcount; i++) {
+        if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) {
+            createtag = attrs[i].tag;
+            dir->count += 1;
+        } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) {
+            deletetag = attrs[i].tag;
+            LFS_ASSERT(dir->count > 0);
+            dir->count -= 1;
+        } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) {
+            dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0];
+            dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1];
+            dir->split = (lfs_tag_chunk(attrs[i].tag) & 1);
+            lfs_pair_fromle32(dir->tail);
+        }
+    }
+
+    // do we have a pending move?
+    if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) {
+        deletetag = lfs->gpending.tag & LFS_MKTAG(0x7ff, 0x3ff, 0);
+        LFS_ASSERT(dir->count > 0);
+        dir->count -= 1;
+
+        // mark gdelta so we reflect the move we will fix
+        lfs_gstate_xormove(&lfs->gdelta, &lfs->gpending, 0x3ff, NULL);
+    }
+
+    // should we actually drop the directory block?
+    if (lfs_tag_isvalid(deletetag) && dir->count == 0) {
+        lfs_mdir_t pdir;
+        int err = lfs_fs_pred(lfs, dir->pair, &pdir);
+        if (err && err != LFS_ERR_NOENT) {
+            return err;
+        }
+
+        if (err != LFS_ERR_NOENT && pdir.split) {
+            return lfs_dir_drop(lfs, &pdir, dir);
+        }
+    }
+
+    if (dir->erased || dir->count >= 0xff) {
+        // try to commit
+        struct lfs_commit commit = {
+            .block = dir->pair[0],
+            .off = dir->off,
+            .ptag = dir->etag,
+            .crc = LFS_BLOCK_NULL,
+
+            .begin = dir->off,
+            .end = lfs->cfg->block_size - 8,
+        };
+
+        // traverse attrs that need to be written out
+        lfs_pair_tole32(dir->tail);
+        int err = lfs_dir_traverse(lfs,
+                dir, dir->off, dir->etag, attrs, attrcount, false,
+                0, 0, 0, 0, 0,
+                lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){
+                    lfs, &commit});
+        lfs_pair_fromle32(dir->tail);
+        if (err) {
+            if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+                goto compact;
+            }
+            return err;
+        }
+
+        // commit any global diffs if we have any
+        if (!lfs_gstate_iszero(&lfs->gdelta)) {
+            err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta);
+            if (err) {
+                return err;
+            }
+
+            lfs_gstate_tole32(&lfs->gdelta);
+            err = lfs_dir_commitattr(lfs, &commit,
+                    LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff,
+                        sizeof(lfs->gdelta)), &lfs->gdelta);
+            lfs_gstate_fromle32(&lfs->gdelta);
+            if (err) {
+                if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+                    goto compact;
+                }
+                return err;
+            }
+        }
+
+        // finalize commit with the crc
+        err = lfs_dir_commitcrc(lfs, &commit);
+        if (err) {
+            if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+                goto compact;
+            }
+            return err;
+        }
+
+        // successful commit, update dir
+        LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0);
+        dir->off = commit.off;
+        dir->etag = commit.ptag;
+
+        // note we able to have already handled move here
+        if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) {
+            lfs_gstate_xormove(&lfs->gpending, &lfs->gpending, 0x3ff, NULL);
+        }
+
+        // update gstate
+        lfs->gstate = lfs->gpending;
+        lfs->gdelta = (struct lfs_gstate){0};
+    } else {
+compact:
+        // fall back to compaction
+        lfs_cache_drop(lfs, &lfs->pcache);
+
+        int err = lfs_dir_compact(lfs, dir, attrs, attrcount,
+                dir, 0, dir->count);
+        if (err) {
+            return err;
+        }
+    }
+
+    // update any directories that are affected
+    lfs_mdir_t copy = *dir;
+
+    // two passes, once for things that aren't us, and one
+    // for things that are
+    for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) {
+        if (lfs_pair_cmp(d->m.pair, copy.pair) == 0) {
+            d->m = *dir;
+            if (d->id == lfs_tag_id(deletetag)) {
+                d->m.pair[0] = LFS_BLOCK_NULL;
+                d->m.pair[1] = LFS_BLOCK_NULL;
+            } else if (d->id > lfs_tag_id(deletetag)) {
+                d->id -= 1;
+                if (d->type == LFS_TYPE_DIR) {
+                    ((lfs_dir_t*)d)->pos -= 1;
+                }
+            } else if (&d->m != dir && d->id >= lfs_tag_id(createtag)) {
+                d->id += 1;
+                if (d->type == LFS_TYPE_DIR) {
+                    ((lfs_dir_t*)d)->pos += 1;
+                }
+            }
+
+            while (d->id >= d->m.count && d->m.split) {
+                // we split and id is on tail now
+                d->id -= d->m.count;
+                int err = lfs_dir_fetch(lfs, &d->m, d->m.tail);
+                if (err) {
+                    return err;
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/// Top level directory operations ///
+int lfs_mkdir(lfs_t *lfs, const char *path) {
+    LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path);
+    // deorphan if we haven't yet, needed at most once after poweron
+    int err = lfs_fs_forceconsistency(lfs);
+    if (err) {
+        LFS_TRACE("lfs_mkdir -> %d", err);
+        return err;
+    }
+
+    lfs_mdir_t cwd;
+    uint16_t id;
+    err = lfs_dir_find(lfs, &cwd, &path, &id);
+    if (!(err == LFS_ERR_NOENT && id != 0x3ff)) {
+        LFS_TRACE("lfs_mkdir -> %d", (err < 0) ? err : LFS_ERR_EXIST);
+        return (err < 0) ? err : LFS_ERR_EXIST;
+    }
+
+    // check that name fits
+    lfs_size_t nlen = strlen(path);
+    if (nlen > lfs->name_max) {
+        LFS_TRACE("lfs_mkdir -> %d", LFS_ERR_NAMETOOLONG);
+        return LFS_ERR_NAMETOOLONG;
+    }
+
+    // build up new directory
+    lfs_alloc_ack(lfs);
+    lfs_mdir_t dir;
+    err = lfs_dir_alloc(lfs, &dir);
+    if (err) {
+        LFS_TRACE("lfs_mkdir -> %d", err);
+        return err;
+    }
+
+    // find end of list
+    lfs_mdir_t pred = cwd;
+    while (pred.split) {
+        err = lfs_dir_fetch(lfs, &pred, pred.tail);
+        if (err) {
+            LFS_TRACE("lfs_mkdir -> %d", err);
+            return err;
+        }
+    }
+
+    // setup dir
+    lfs_pair_tole32(pred.tail);
+    err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail}));
+    lfs_pair_fromle32(pred.tail);
+    if (err) {
+        LFS_TRACE("lfs_mkdir -> %d", err);
+        return err;
+    }
+
+    // current block end of list?
+    if (cwd.split) {
+        // update tails, this creates a desync
+        lfs_fs_preporphans(lfs, +1);
+        lfs_pair_tole32(dir.pair);
+        err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair}));
+        lfs_pair_fromle32(dir.pair);
+        if (err) {
+            LFS_TRACE("lfs_mkdir -> %d", err);
+            return err;
+        }
+        lfs_fs_preporphans(lfs, -1);
+    }
+
+    // now insert into our parent block
+    lfs_pair_tole32(dir.pair);
+    err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL},
+            {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path},
+            {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair},
+            {!cwd.split
+                ? LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8)
+                : LFS_MKTAG(LFS_FROM_NOOP, 0, 0), dir.pair}));
+    lfs_pair_fromle32(dir.pair);
+    if (err) {
+        LFS_TRACE("lfs_mkdir -> %d", err);
+        return err;
+    }
+
+    LFS_TRACE("lfs_mkdir -> %d", 0);
+    return 0;
+}
+
+int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) {
+    LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path);
+    lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL);
+    if (tag < 0) {
+        LFS_TRACE("lfs_dir_open -> %d", tag);
+        return tag;
+    }
+
+    if (lfs_tag_type3(tag) != LFS_TYPE_DIR) {
+        LFS_TRACE("lfs_dir_open -> %d", LFS_ERR_NOTDIR);
+        return LFS_ERR_NOTDIR;
+    }
+
+    lfs_block_t pair[2];
+    if (lfs_tag_id(tag) == 0x3ff) {
+        // handle root dir separately
+        pair[0] = lfs->root[0];
+        pair[1] = lfs->root[1];
+    } else {
+        // get dir pair from parent
+        lfs_stag_t res = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x700, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair);
+        if (res < 0) {
+            LFS_TRACE("lfs_dir_open -> %d", res);
+            return res;
+        }
+        lfs_pair_fromle32(pair);
+    }
+
+    // fetch first pair
+    int err = lfs_dir_fetch(lfs, &dir->m, pair);
+    if (err) {
+        LFS_TRACE("lfs_dir_open -> %d", err);
+        return err;
+    }
+
+    // setup entry
+    dir->head[0] = dir->m.pair[0];
+    dir->head[1] = dir->m.pair[1];
+    dir->id = 0;
+    dir->pos = 0;
+
+    // add to list of mdirs
+    dir->type = LFS_TYPE_DIR;
+    dir->next = (lfs_dir_t*)lfs->mlist;
+    lfs->mlist = (struct lfs_mlist*)dir;
+
+    LFS_TRACE("lfs_dir_open -> %d", 0);
+    return 0;
+}
+
+int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) {
+    LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir);
+    // remove from list of mdirs
+    for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) {
+        if (*p == (struct lfs_mlist*)dir) {
+            *p = (*p)->next;
+            break;
+        }
+    }
+
+    LFS_TRACE("lfs_dir_close -> %d", 0);
+    return 0;
+}
+
+int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
+    LFS_TRACE("lfs_dir_read(%p, %p, %p)",
+            (void*)lfs, (void*)dir, (void*)info);
+    memset(info, 0, sizeof(*info));
+
+    // special offset for '.' and '..'
+    if (dir->pos == 0) {
+        info->type = LFS_TYPE_DIR;
+        strcpy(info->name, ".");
+        dir->pos += 1;
+        LFS_TRACE("lfs_dir_read -> %d", true);
+        return true;
+    } else if (dir->pos == 1) {
+        info->type = LFS_TYPE_DIR;
+        strcpy(info->name, "..");
+        dir->pos += 1;
+        LFS_TRACE("lfs_dir_read -> %d", true);
+        return true;
+    }
+
+    while (true) {
+        if (dir->id == dir->m.count) {
+            if (!dir->m.split) {
+                LFS_TRACE("lfs_dir_read -> %d", false);
+                return false;
+            }
+
+            int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
+            if (err) {
+                LFS_TRACE("lfs_dir_read -> %d", err);
+                return err;
+            }
+
+            dir->id = 0;
+        }
+
+        int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info);
+        if (err && err != LFS_ERR_NOENT) {
+            LFS_TRACE("lfs_dir_read -> %d", err);
+            return err;
+        }
+
+        dir->id += 1;
+        if (err != LFS_ERR_NOENT) {
+            break;
+        }
+    }
+
+    dir->pos += 1;
+    LFS_TRACE("lfs_dir_read -> %d", true);
+    return true;
+}
+
+int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
+    LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")",
+            (void*)lfs, (void*)dir, off);
+    // simply walk from head dir
+    int err = lfs_dir_rewind(lfs, dir);
+    if (err) {
+        LFS_TRACE("lfs_dir_seek -> %d", err);
+        return err;
+    }
+
+    // first two for ./..
+    dir->pos = lfs_min(2, off);
+    off -= dir->pos;
+
+    while (off != 0) {
+        dir->id = lfs_min(dir->m.count, off);
+        dir->pos += dir->id;
+        off -= dir->id;
+
+        if (dir->id == dir->m.count) {
+            if (!dir->m.split) {
+                LFS_TRACE("lfs_dir_seek -> %d", LFS_ERR_INVAL);
+                return LFS_ERR_INVAL;
+            }
+
+            err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
+            if (err) {
+                LFS_TRACE("lfs_dir_seek -> %d", err);
+                return err;
+            }
+        }
+    }
+
+    LFS_TRACE("lfs_dir_seek -> %d", 0);
+    return 0;
+}
+
+lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) {
+    LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir);
+    (void)lfs;
+    LFS_TRACE("lfs_dir_tell -> %"PRId32, dir->pos);
+    return dir->pos;
+}
+
+int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) {
+    LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir);
+    // reload the head dir
+    int err = lfs_dir_fetch(lfs, &dir->m, dir->head);
+    if (err) {
+        LFS_TRACE("lfs_dir_rewind -> %d", err);
+        return err;
+    }
+
+    dir->m.pair[0] = dir->head[0];
+    dir->m.pair[1] = dir->head[1];
+    dir->id = 0;
+    dir->pos = 0;
+    LFS_TRACE("lfs_dir_rewind -> %d", 0);
+    return 0;
+}
+
+
+/// File index list operations ///
+static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) {
+    lfs_off_t size = *off;
+    lfs_off_t b = lfs->cfg->block_size - 2*4;
+    lfs_off_t i = size / b;
+    if (i == 0) {
+        return 0;
+    }
+
+    i = (size - 4*(lfs_popc(i-1)+2)) / b;
+    *off = size - b*i - 4*lfs_popc(i);
+    return i;
+}
+
+static int lfs_ctz_find(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache,
+        lfs_block_t head, lfs_size_t size,
+        lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) {
+    if (size == 0) {
+        *block = LFS_BLOCK_NULL;
+        *off = 0;
+        return 0;
+    }
+
+    lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1});
+    lfs_off_t target = lfs_ctz_index(lfs, &pos);
+
+    while (current > target) {
+        lfs_size_t skip = lfs_min(
+                lfs_npw2(current-target+1) - 1,
+                lfs_ctz(current));
+
+        int err = lfs_bd_read(lfs,
+                pcache, rcache, sizeof(head),
+                head, 4*skip, &head, sizeof(head));
+        head = lfs_fromle32(head);
+        if (err) {
+            return err;
+        }
+
+        LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
+        current -= 1 << skip;
+    }
+
+    *block = head;
+    *off = pos;
+    return 0;
+}
+
+static int lfs_ctz_extend(lfs_t *lfs,
+        lfs_cache_t *pcache, lfs_cache_t *rcache,
+        lfs_block_t head, lfs_size_t size,
+        lfs_block_t *block, lfs_off_t *off) {
+    while (true) {
+        // go ahead and grab a block
+        lfs_block_t nblock;
+        int err = lfs_alloc(lfs, &nblock);
+        if (err) {
+            return err;
+        }
+        LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count);
+
+        {
+            err = lfs_bd_erase(lfs, nblock);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            if (size == 0) {
+                *block = nblock;
+                *off = 0;
+                return 0;
+            }
+
+            size -= 1;
+            lfs_off_t index = lfs_ctz_index(lfs, &size);
+            size += 1;
+
+            // just copy out the last block if it is incomplete
+            if (size != lfs->cfg->block_size) {
+                for (lfs_off_t i = 0; i < size; i++) {
+                    uint8_t data;
+                    err = lfs_bd_read(lfs,
+                            NULL, rcache, size-i,
+                            head, i, &data, 1);
+                    if (err) {
+                        return err;
+                    }
+
+                    err = lfs_bd_prog(lfs,
+                            pcache, rcache, true,
+                            nblock, i, &data, 1);
+                    if (err) {
+                        if (err == LFS_ERR_CORRUPT) {
+                            goto relocate;
+                        }
+                        return err;
+                    }
+                }
+
+                *block = nblock;
+                *off = size;
+                return 0;
+            }
+
+            // append block
+            index += 1;
+            lfs_size_t skips = lfs_ctz(index) + 1;
+
+            for (lfs_off_t i = 0; i < skips; i++) {
+                head = lfs_tole32(head);
+                err = lfs_bd_prog(lfs, pcache, rcache, true,
+                        nblock, 4*i, &head, 4);
+                head = lfs_fromle32(head);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        goto relocate;
+                    }
+                    return err;
+                }
+
+                if (i != skips-1) {
+                    err = lfs_bd_read(lfs,
+                            NULL, rcache, sizeof(head),
+                            head, 4*i, &head, sizeof(head));
+                    head = lfs_fromle32(head);
+                    if (err) {
+                        return err;
+                    }
+                }
+
+                LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
+            }
+
+            *block = nblock;
+            *off = 4*skips;
+            return 0;
+        }
+
+relocate:
+        LFS_DEBUG("Bad block at %"PRIx32, nblock);
+
+        // just clear cache and try a new block
+        lfs_cache_drop(lfs, pcache);
+    }
+}
+
+static int lfs_ctz_traverse(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache,
+        lfs_block_t head, lfs_size_t size,
+        int (*cb)(void*, lfs_block_t), void *data) {
+    if (size == 0) {
+        return 0;
+    }
+
+    lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1});
+
+    while (true) {
+        int err = cb(data, head);
+        if (err) {
+            return err;
+        }
+
+        if (index == 0) {
+            return 0;
+        }
+
+        lfs_block_t heads[2];
+        int count = 2 - (index & 1);
+        err = lfs_bd_read(lfs,
+                pcache, rcache, count*sizeof(head),
+                head, 0, &heads, count*sizeof(head));
+        heads[0] = lfs_fromle32(heads[0]);
+        heads[1] = lfs_fromle32(heads[1]);
+        if (err) {
+            return err;
+        }
+
+        for (int i = 0; i < count-1; i++) {
+            err = cb(data, heads[i]);
+            if (err) {
+                return err;
+            }
+        }
+
+        head = heads[count-1];
+        index -= count;
+    }
+}
+
+
+/// Top level file operations ///
+int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags,
+        const struct lfs_file_config *cfg) {
+    LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {"
+                 ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})",
+            (void*)lfs, (void*)file, path, flags,
+            (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count);
+
+    // deorphan if we haven't yet, needed at most once after poweron
+    if ((flags & 3) != LFS_O_RDONLY) {
+        int err = lfs_fs_forceconsistency(lfs);
+        if (err) {
+            LFS_TRACE("lfs_file_opencfg -> %d", err);
+            return err;
+        }
+    }
+
+    // setup simple file details
+    int err;
+    file->cfg = cfg;
+    file->flags = flags | LFS_F_OPENED;
+    file->pos = 0;
+    file->off = 0;
+    file->cache.buffer = NULL;
+
+    // allocate entry for file if it doesn't exist
+    lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id);
+    if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) {
+        err = tag;
+        goto cleanup;
+    }
+
+    // get id, add to list of mdirs to catch update changes
+    file->type = LFS_TYPE_REG;
+    file->next = (lfs_file_t*)lfs->mlist;
+    lfs->mlist = (struct lfs_mlist*)file;
+
+    if (tag == LFS_ERR_NOENT) {
+        if (!(flags & LFS_O_CREAT)) {
+            err = LFS_ERR_NOENT;
+            goto cleanup;
+        }
+
+        // check that name fits
+        lfs_size_t nlen = strlen(path);
+        if (nlen > lfs->name_max) {
+            err = LFS_ERR_NAMETOOLONG;
+            goto cleanup;
+        }
+
+        // get next slot and create entry to remember name
+        err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL},
+                {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path},
+                {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL}));
+        if (err) {
+            err = LFS_ERR_NAMETOOLONG;
+            goto cleanup;
+        }
+
+        tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0);
+    } else if (flags & LFS_O_EXCL) {
+        err = LFS_ERR_EXIST;
+        goto cleanup;
+    } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) {
+        err = LFS_ERR_ISDIR;
+        goto cleanup;
+    } else if (flags & LFS_O_TRUNC) {
+        // truncate if requested
+        tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0);
+        file->flags |= LFS_F_DIRTY;
+    } else {
+        // try to load what's on disk, if it's inlined we'll fix it later
+        tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz);
+        if (tag < 0) {
+            err = tag;
+            goto cleanup;
+        }
+        lfs_ctz_fromle32(&file->ctz);
+    }
+
+    // fetch attrs
+    for (unsigned i = 0; i < file->cfg->attr_count; i++) {
+        if ((file->flags & 3) != LFS_O_WRONLY) {
+            lfs_stag_t res = lfs_dir_get(lfs, &file->m,
+                    LFS_MKTAG(0x7ff, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type,
+                        file->id, file->cfg->attrs[i].size),
+                        file->cfg->attrs[i].buffer);
+            if (res < 0 && res != LFS_ERR_NOENT) {
+                err = res;
+                goto cleanup;
+            }
+        }
+
+        if ((file->flags & 3) != LFS_O_RDONLY) {
+            if (file->cfg->attrs[i].size > lfs->attr_max) {
+                err = LFS_ERR_NOSPC;
+                goto cleanup;
+            }
+
+            file->flags |= LFS_F_DIRTY;
+        }
+    }
+
+    // allocate buffer if needed
+    if (file->cfg->buffer) {
+        file->cache.buffer = file->cfg->buffer;
+    } else {
+        file->cache.buffer = lfs_malloc(lfs->cfg->cache_size);
+        if (!file->cache.buffer) {
+            err = LFS_ERR_NOMEM;
+            goto cleanup;
+        }
+    }
+
+    // zero to avoid information leak
+    lfs_cache_zero(lfs, &file->cache);
+
+    if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) {
+        // load inline files
+        file->ctz.head = LFS_BLOCK_INLINE;
+        file->ctz.size = lfs_tag_size(tag);
+        file->flags |= LFS_F_INLINE;
+        file->cache.block = file->ctz.head;
+        file->cache.off = 0;
+        file->cache.size = lfs->cfg->cache_size;
+
+        // don't always read (may be new/trunc file)
+        if (file->ctz.size > 0) {
+            lfs_stag_t res = lfs_dir_get(lfs, &file->m,
+                    LFS_MKTAG(0x700, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_STRUCT, file->id,
+                        lfs_min(file->cache.size, 0x3fe)),
+                    file->cache.buffer);
+            if (res < 0) {
+                err = res;
+                goto cleanup;
+            }
+        }
+    }
+
+    LFS_TRACE("lfs_file_opencfg -> %d", 0);
+    return 0;
+
+cleanup:
+    // clean up lingering resources
+    file->flags |= LFS_F_ERRED;
+    lfs_file_close(lfs, file);
+    LFS_TRACE("lfs_file_opencfg -> %d", err);
+    return err;
+}
+
+int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags) {
+    LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)",
+            (void*)lfs, (void*)file, path, flags);
+    static const struct lfs_file_config defaults = {0};
+    int err = lfs_file_opencfg(lfs, file, path, flags, &defaults);
+    LFS_TRACE("lfs_file_open -> %d", err);
+    return err;
+}
+
+int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
+    LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file);
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+
+    int err = lfs_file_sync(lfs, file);
+
+    // remove from list of mdirs
+    for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) {
+        if (*p == (struct lfs_mlist*)file) {
+            *p = (*p)->next;
+            break;
+        }
+    }
+
+    // clean up memory
+    if (!file->cfg->buffer) {
+        lfs_free(file->cache.buffer);
+    }
+
+    file->flags &= ~LFS_F_OPENED;
+    LFS_TRACE("lfs_file_close -> %d", err);
+    return err;
+}
+
+static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) {
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+
+    while (true) {
+        // just relocate what exists into new block
+        lfs_block_t nblock;
+        int err = lfs_alloc(lfs, &nblock);
+        if (err) {
+            return err;
+        }
+
+        err = lfs_bd_erase(lfs, nblock);
+        if (err) {
+            if (err == LFS_ERR_CORRUPT) {
+                goto relocate;
+            }
+            return err;
+        }
+
+        // either read from dirty cache or disk
+        for (lfs_off_t i = 0; i < file->off; i++) {
+            uint8_t data;
+            if (file->flags & LFS_F_INLINE) {
+                err = lfs_dir_getread(lfs, &file->m,
+                        // note we evict inline files before they can be dirty
+                        NULL, &file->cache, file->off-i,
+                        LFS_MKTAG(0xfff, 0x1ff, 0),
+                        LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0),
+                        i, &data, 1);
+                if (err) {
+                    return err;
+                }
+            } else {
+                err = lfs_bd_read(lfs,
+                        &file->cache, &lfs->rcache, file->off-i,
+                        file->block, i, &data, 1);
+                if (err) {
+                    return err;
+                }
+            }
+
+            err = lfs_bd_prog(lfs,
+                    &lfs->pcache, &lfs->rcache, true,
+                    nblock, i, &data, 1);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+        }
+
+        // copy over new state of file
+        memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size);
+        file->cache.block = lfs->pcache.block;
+        file->cache.off = lfs->pcache.off;
+        file->cache.size = lfs->pcache.size;
+        lfs_cache_zero(lfs, &lfs->pcache);
+
+        file->block = nblock;
+        file->flags |= LFS_F_WRITING;
+        return 0;
+
+relocate:
+        LFS_DEBUG("Bad block at %"PRIx32, nblock);
+
+        // just clear cache and try a new block
+        lfs_cache_drop(lfs, &lfs->pcache);
+    }
+}
+
+static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) {
+    file->off = file->pos;
+    lfs_alloc_ack(lfs);
+    int err = lfs_file_relocate(lfs, file);
+    if (err) {
+        return err;
+    }
+
+    file->flags &= ~LFS_F_INLINE;
+    return 0;
+}
+
+static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+
+    if (file->flags & LFS_F_READING) {
+        if (!(file->flags & LFS_F_INLINE)) {
+            lfs_cache_drop(lfs, &file->cache);
+        }
+        file->flags &= ~LFS_F_READING;
+    }
+
+    if (file->flags & LFS_F_WRITING) {
+        lfs_off_t pos = file->pos;
+
+        if (!(file->flags & LFS_F_INLINE)) {
+            // copy over anything after current branch
+            lfs_file_t orig = {
+                .ctz.head = file->ctz.head,
+                .ctz.size = file->ctz.size,
+                .flags = LFS_O_RDONLY | LFS_F_OPENED,
+                .pos = file->pos,
+                .cache = lfs->rcache,
+            };
+            lfs_cache_drop(lfs, &lfs->rcache);
+
+            while (file->pos < file->ctz.size) {
+                // copy over a byte at a time, leave it up to caching
+                // to make this efficient
+                uint8_t data;
+                lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1);
+                if (res < 0) {
+                    return res;
+                }
+
+                res = lfs_file_write(lfs, file, &data, 1);
+                if (res < 0) {
+                    return res;
+                }
+
+                // keep our reference to the rcache in sync
+                if (lfs->rcache.block != LFS_BLOCK_NULL) {
+                    lfs_cache_drop(lfs, &orig.cache);
+                    lfs_cache_drop(lfs, &lfs->rcache);
+                }
+            }
+
+            // write out what we have
+            while (true) {
+                int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        goto relocate;
+                    }
+                    return err;
+                }
+
+                break;
+
+relocate:
+                LFS_DEBUG("Bad block at %"PRIx32, file->block);
+                err = lfs_file_relocate(lfs, file);
+                if (err) {
+                    return err;
+                }
+            }
+        } else {
+            file->pos = lfs_max(file->pos, file->ctz.size);
+        }
+
+        // actual file updates
+        file->ctz.head = file->block;
+        file->ctz.size = file->pos;
+        file->flags &= ~LFS_F_WRITING;
+        file->flags |= LFS_F_DIRTY;
+
+        file->pos = pos;
+    }
+
+    return 0;
+}
+
+int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
+    LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file);
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+
+    while (true) {
+        int err = lfs_file_flush(lfs, file);
+        if (err) {
+            file->flags |= LFS_F_ERRED;
+            LFS_TRACE("lfs_file_sync -> %d", err);
+            return err;
+        }
+
+        if ((file->flags & LFS_F_DIRTY) &&
+                !(file->flags & LFS_F_ERRED) &&
+                !lfs_pair_isnull(file->m.pair)) {
+            // update dir entry
+            uint16_t type;
+            const void *buffer;
+            lfs_size_t size;
+            struct lfs_ctz ctz;
+            if (file->flags & LFS_F_INLINE) {
+                // inline the whole file
+                type = LFS_TYPE_INLINESTRUCT;
+                buffer = file->cache.buffer;
+                size = file->ctz.size;
+            } else {
+                // update the ctz reference
+                type = LFS_TYPE_CTZSTRUCT;
+                // copy ctz so alloc will work during a relocate
+                ctz = file->ctz;
+                lfs_ctz_tole32(&ctz);
+                buffer = &ctz;
+                size = sizeof(ctz);
+            }
+
+            // commit file data and attributes
+            err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS(
+                    {LFS_MKTAG(type, file->id, size), buffer},
+                    {LFS_MKTAG(LFS_FROM_USERATTRS, file->id,
+                        file->cfg->attr_count), file->cfg->attrs}));
+            if (err) {
+                if (err == LFS_ERR_NOSPC && (file->flags & LFS_F_INLINE)) {
+                    goto relocate;
+                }
+                file->flags |= LFS_F_ERRED;
+                LFS_TRACE("lfs_file_sync -> %d", err);
+                return err;
+            }
+
+            file->flags &= ~LFS_F_DIRTY;
+        }
+
+        LFS_TRACE("lfs_file_sync -> %d", 0);
+        return 0;
+
+relocate:
+        // inline file doesn't fit anymore
+        err = lfs_file_outline(lfs, file);
+        if (err) {
+            file->flags |= LFS_F_ERRED;
+            LFS_TRACE("lfs_file_sync -> %d", err);
+            return err;
+        }
+    }
+}
+
+lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
+        void *buffer, lfs_size_t size) {
+    LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")",
+            (void*)lfs, (void*)file, buffer, size);
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+    LFS_ASSERT((file->flags & 3) != LFS_O_WRONLY);
+
+    uint8_t *data = buffer;
+    lfs_size_t nsize = size;
+
+    if (file->flags & LFS_F_WRITING) {
+        // flush out any writes
+        int err = lfs_file_flush(lfs, file);
+        if (err) {
+            LFS_TRACE("lfs_file_read -> %"PRId32, err);
+            return err;
+        }
+    }
+
+    if (file->pos >= file->ctz.size) {
+        // eof if past end
+        LFS_TRACE("lfs_file_read -> %"PRId32, 0);
+        return 0;
+    }
+
+    size = lfs_min(size, file->ctz.size - file->pos);
+    nsize = size;
+
+    while (nsize > 0) {
+        // check if we need a new block
+        if (!(file->flags & LFS_F_READING) ||
+                file->off == lfs->cfg->block_size) {
+            if (!(file->flags & LFS_F_INLINE)) {
+                int err = lfs_ctz_find(lfs, NULL, &file->cache,
+                        file->ctz.head, file->ctz.size,
+                        file->pos, &file->block, &file->off);
+                if (err) {
+                    LFS_TRACE("lfs_file_read -> %"PRId32, err);
+                    return err;
+                }
+            } else {
+                file->block = LFS_BLOCK_INLINE;
+                file->off = file->pos;
+            }
+
+            file->flags |= LFS_F_READING;
+        }
+
+        // read as much as we can in current block
+        lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
+        if (file->flags & LFS_F_INLINE) {
+            int err = lfs_dir_getread(lfs, &file->m,
+                    NULL, &file->cache, lfs->cfg->block_size,
+                    LFS_MKTAG(0xfff, 0x1ff, 0),
+                    LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0),
+                    file->off, data, diff);
+            if (err) {
+                LFS_TRACE("lfs_file_read -> %"PRId32, err);
+                return err;
+            }
+        } else {
+            int err = lfs_bd_read(lfs,
+                    NULL, &file->cache, lfs->cfg->block_size,
+                    file->block, file->off, data, diff);
+            if (err) {
+                LFS_TRACE("lfs_file_read -> %"PRId32, err);
+                return err;
+            }
+        }
+
+        file->pos += diff;
+        file->off += diff;
+        data += diff;
+        nsize -= diff;
+    }
+
+    LFS_TRACE("lfs_file_read -> %"PRId32, size);
+    return size;
+}
+
+lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
+        const void *buffer, lfs_size_t size) {
+    LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")",
+            (void*)lfs, (void*)file, buffer, size);
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+    LFS_ASSERT((file->flags & 3) != LFS_O_RDONLY);
+
+    const uint8_t *data = buffer;
+    lfs_size_t nsize = size;
+
+    if (file->flags & LFS_F_READING) {
+        // drop any reads
+        int err = lfs_file_flush(lfs, file);
+        if (err) {
+            LFS_TRACE("lfs_file_write -> %"PRId32, err);
+            return err;
+        }
+    }
+
+    if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) {
+        file->pos = file->ctz.size;
+    }
+
+    if (file->pos + size > lfs->file_max) {
+        // Larger than file limit?
+        LFS_TRACE("lfs_file_write -> %"PRId32, LFS_ERR_FBIG);
+        return LFS_ERR_FBIG;
+    }
+
+    if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) {
+        // fill with zeros
+        lfs_off_t pos = file->pos;
+        file->pos = file->ctz.size;
+
+        while (file->pos < pos) {
+            lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
+            if (res < 0) {
+                LFS_TRACE("lfs_file_write -> %"PRId32, res);
+                return res;
+            }
+        }
+    }
+
+    if ((file->flags & LFS_F_INLINE) &&
+            lfs_max(file->pos+nsize, file->ctz.size) >
+            lfs_min(0x3fe, lfs_min(
+                lfs->cfg->cache_size, lfs->cfg->block_size/8))) {
+        // inline file doesn't fit anymore
+        int err = lfs_file_outline(lfs, file);
+        if (err) {
+            file->flags |= LFS_F_ERRED;
+            LFS_TRACE("lfs_file_write -> %"PRId32, err);
+            return err;
+        }
+    }
+
+    while (nsize > 0) {
+        // check if we need a new block
+        if (!(file->flags & LFS_F_WRITING) ||
+                file->off == lfs->cfg->block_size) {
+            if (!(file->flags & LFS_F_INLINE)) {
+                if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
+                    // find out which block we're extending from
+                    int err = lfs_ctz_find(lfs, NULL, &file->cache,
+                            file->ctz.head, file->ctz.size,
+                            file->pos-1, &file->block, &file->off);
+                    if (err) {
+                        file->flags |= LFS_F_ERRED;
+                        LFS_TRACE("lfs_file_write -> %"PRId32, err);
+                        return err;
+                    }
+
+                    // mark cache as dirty since we may have read data into it
+                    lfs_cache_zero(lfs, &file->cache);
+                }
+
+                // extend file with new blocks
+                lfs_alloc_ack(lfs);
+                int err = lfs_ctz_extend(lfs, &file->cache, &lfs->rcache,
+                        file->block, file->pos,
+                        &file->block, &file->off);
+                if (err) {
+                    file->flags |= LFS_F_ERRED;
+                    LFS_TRACE("lfs_file_write -> %"PRId32, err);
+                    return err;
+                }
+            } else {
+                file->block = LFS_BLOCK_INLINE;
+                file->off = file->pos;
+            }
+
+            file->flags |= LFS_F_WRITING;
+        }
+
+        // program as much as we can in current block
+        lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
+        while (true) {
+            int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true,
+                    file->block, file->off, data, diff);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                file->flags |= LFS_F_ERRED;
+                LFS_TRACE("lfs_file_write -> %"PRId32, err);
+                return err;
+            }
+
+            break;
+relocate:
+            err = lfs_file_relocate(lfs, file);
+            if (err) {
+                file->flags |= LFS_F_ERRED;
+                LFS_TRACE("lfs_file_write -> %"PRId32, err);
+                return err;
+            }
+        }
+
+        file->pos += diff;
+        file->off += diff;
+        data += diff;
+        nsize -= diff;
+
+        lfs_alloc_ack(lfs);
+    }
+
+    file->flags &= ~LFS_F_ERRED;
+    LFS_TRACE("lfs_file_write -> %"PRId32, size);
+    return size;
+}
+
+lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
+        lfs_soff_t off, int whence) {
+    LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)",
+            (void*)lfs, (void*)file, off, whence);
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+
+    // write out everything beforehand, may be noop if rdonly
+    int err = lfs_file_flush(lfs, file);
+    if (err) {
+        LFS_TRACE("lfs_file_seek -> %"PRId32, err);
+        return err;
+    }
+
+    // find new pos
+    lfs_off_t npos = file->pos;
+    if (whence == LFS_SEEK_SET) {
+        npos = off;
+    } else if (whence == LFS_SEEK_CUR) {
+        npos = file->pos + off;
+    } else if (whence == LFS_SEEK_END) {
+        npos = file->ctz.size + off;
+    }
+
+    if (npos > lfs->file_max) {
+        // file position out of range
+        LFS_TRACE("lfs_file_seek -> %"PRId32, LFS_ERR_INVAL);
+        return LFS_ERR_INVAL;
+    }
+
+    // update pos
+    file->pos = npos;
+    LFS_TRACE("lfs_file_seek -> %"PRId32, npos);
+    return npos;
+}
+
+int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
+    LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")",
+            (void*)lfs, (void*)file, size);
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+    LFS_ASSERT((file->flags & 3) != LFS_O_RDONLY);
+
+    if (size > LFS_FILE_MAX) {
+        LFS_TRACE("lfs_file_truncate -> %d", LFS_ERR_INVAL);
+        return LFS_ERR_INVAL;
+    }
+
+    lfs_off_t pos = file->pos;
+    lfs_off_t oldsize = lfs_file_size(lfs, file);
+    if (size < oldsize) {
+        // need to flush since directly changing metadata
+        int err = lfs_file_flush(lfs, file);
+        if (err) {
+            LFS_TRACE("lfs_file_truncate -> %d", err);
+            return err;
+        }
+
+        // lookup new head in ctz skip list
+        err = lfs_ctz_find(lfs, NULL, &file->cache,
+                file->ctz.head, file->ctz.size,
+                size, &file->block, &file->off);
+        if (err) {
+            LFS_TRACE("lfs_file_truncate -> %d", err);
+            return err;
+        }
+
+        file->ctz.head = file->block;
+        file->ctz.size = size;
+        file->flags |= LFS_F_DIRTY | LFS_F_READING;
+    } else if (size > oldsize) {
+        // flush+seek if not already at end
+        if (file->pos != oldsize) {
+            int err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END);
+            if (err < 0) {
+                LFS_TRACE("lfs_file_truncate -> %d", err);
+                return err;
+            }
+        }
+
+        // fill with zeros
+        while (file->pos < size) {
+            lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
+            if (res < 0) {
+                LFS_TRACE("lfs_file_truncate -> %d", res);
+                return res;
+            }
+        }
+    }
+
+    // restore pos
+    int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET);
+    if (err < 0) {
+      LFS_TRACE("lfs_file_truncate -> %d", err);
+      return err;
+    }
+
+    LFS_TRACE("lfs_file_truncate -> %d", 0);
+    return 0;
+}
+
+lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
+    LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file);
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+    (void)lfs;
+    LFS_TRACE("lfs_file_tell -> %"PRId32, file->pos);
+    return file->pos;
+}
+
+int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
+    LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file);
+    lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET);
+    if (res < 0) {
+        LFS_TRACE("lfs_file_rewind -> %d", res);
+        return res;
+    }
+
+    LFS_TRACE("lfs_file_rewind -> %d", 0);
+    return 0;
+}
+
+lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
+    LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file);
+    LFS_ASSERT(file->flags & LFS_F_OPENED);
+    (void)lfs;
+    if (file->flags & LFS_F_WRITING) {
+        LFS_TRACE("lfs_file_size -> %"PRId32,
+                lfs_max(file->pos, file->ctz.size));
+        return lfs_max(file->pos, file->ctz.size);
+    } else {
+        LFS_TRACE("lfs_file_size -> %"PRId32, file->ctz.size);
+        return file->ctz.size;
+    }
+}
+
+
+/// General fs operations ///
+int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
+    LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info);
+    lfs_mdir_t cwd;
+    lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL);
+    if (tag < 0) {
+        LFS_TRACE("lfs_stat -> %d", tag);
+        return tag;
+    }
+
+    int err = lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info);
+    LFS_TRACE("lfs_stat -> %d", err);
+    return err;
+}
+
+int lfs_remove(lfs_t *lfs, const char *path) {
+    LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path);
+    // deorphan if we haven't yet, needed at most once after poweron
+    int err = lfs_fs_forceconsistency(lfs);
+    if (err) {
+        LFS_TRACE("lfs_remove -> %d", err);
+        return err;
+    }
+
+    lfs_mdir_t cwd;
+    lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL);
+    if (tag < 0 || lfs_tag_id(tag) == 0x3ff) {
+        LFS_TRACE("lfs_remove -> %d", (tag < 0) ? tag : LFS_ERR_INVAL);
+        return (tag < 0) ? tag : LFS_ERR_INVAL;
+    }
+
+    lfs_mdir_t dir;
+    if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
+        // must be empty before removal
+        lfs_block_t pair[2];
+        lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair);
+        if (res < 0) {
+            LFS_TRACE("lfs_remove -> %d", res);
+            return res;
+        }
+        lfs_pair_fromle32(pair);
+
+        err = lfs_dir_fetch(lfs, &dir, pair);
+        if (err) {
+            LFS_TRACE("lfs_remove -> %d", err);
+            return err;
+        }
+
+        if (dir.count > 0 || dir.split) {
+            LFS_TRACE("lfs_remove -> %d", LFS_ERR_NOTEMPTY);
+            return LFS_ERR_NOTEMPTY;
+        }
+
+        // mark fs as orphaned
+        lfs_fs_preporphans(lfs, +1);
+    }
+
+    // delete the entry
+    err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL}));
+    if (err) {
+        LFS_TRACE("lfs_remove -> %d", err);
+        return err;
+    }
+
+    if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
+        // fix orphan
+        lfs_fs_preporphans(lfs, -1);
+
+        err = lfs_fs_pred(lfs, dir.pair, &cwd);
+        if (err) {
+            LFS_TRACE("lfs_remove -> %d", err);
+            return err;
+        }
+
+        err = lfs_dir_drop(lfs, &cwd, &dir);
+        if (err) {
+            LFS_TRACE("lfs_remove -> %d", err);
+            return err;
+        }
+    }
+
+    LFS_TRACE("lfs_remove -> %d", 0);
+    return 0;
+}
+
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
+    LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath);
+
+    // deorphan if we haven't yet, needed at most once after poweron
+    int err = lfs_fs_forceconsistency(lfs);
+    if (err) {
+        LFS_TRACE("lfs_rename -> %d", err);
+        return err;
+    }
+
+    // find old entry
+    lfs_mdir_t oldcwd;
+    lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL);
+    if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) {
+        LFS_TRACE("lfs_rename -> %d", (oldtag < 0) ? oldtag : LFS_ERR_INVAL);
+        return (oldtag < 0) ? oldtag : LFS_ERR_INVAL;
+    }
+
+    // find new entry
+    lfs_mdir_t newcwd;
+    uint16_t newid;
+    lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid);
+    if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) &&
+            !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) {
+        LFS_TRACE("lfs_rename -> %d", (prevtag < 0) ? prevtag : LFS_ERR_INVAL);
+        return (prevtag < 0) ? prevtag : LFS_ERR_INVAL;
+    }
+
+    lfs_mdir_t prevdir;
+    if (prevtag == LFS_ERR_NOENT) {
+        // check that name fits
+        lfs_size_t nlen = strlen(newpath);
+        if (nlen > lfs->name_max) {
+            LFS_TRACE("lfs_rename -> %d", LFS_ERR_NAMETOOLONG);
+            return LFS_ERR_NAMETOOLONG;
+        }
+    } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) {
+        LFS_TRACE("lfs_rename -> %d", LFS_ERR_ISDIR);
+        return LFS_ERR_ISDIR;
+    } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) {
+        // must be empty before removal
+        lfs_block_t prevpair[2];
+        lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair);
+        if (res < 0) {
+            LFS_TRACE("lfs_rename -> %d", res);
+            return res;
+        }
+        lfs_pair_fromle32(prevpair);
+
+        // must be empty before removal
+        err = lfs_dir_fetch(lfs, &prevdir, prevpair);
+        if (err) {
+            LFS_TRACE("lfs_rename -> %d", err);
+            return err;
+        }
+
+        if (prevdir.count > 0 || prevdir.split) {
+            LFS_TRACE("lfs_rename -> %d", LFS_ERR_NOTEMPTY);
+            return LFS_ERR_NOTEMPTY;
+        }
+
+        // mark fs as orphaned
+        lfs_fs_preporphans(lfs, +1);
+    }
+
+    // create move to fix later
+    uint16_t newoldtagid = lfs_tag_id(oldtag);
+    if (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0 &&
+            prevtag == LFS_ERR_NOENT && newid <= newoldtagid) {
+        // there is a small chance we are being renamed in the same directory
+        // to an id less than our old id, the global update to handle this
+        // is a bit messy
+        newoldtagid += 1;
+    }
+
+    lfs_fs_prepmove(lfs, newoldtagid, oldcwd.pair);
+
+    // move over all attributes
+    err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS(
+            {prevtag != LFS_ERR_NOENT
+                ? LFS_MKTAG(LFS_TYPE_DELETE, newid, 0)
+                : LFS_MKTAG(LFS_FROM_NOOP, 0, 0), NULL},
+            {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL},
+            {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)),
+                newpath},
+            {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}));
+    if (err) {
+        LFS_TRACE("lfs_rename -> %d", err);
+        return err;
+    }
+
+    // let commit clean up after move (if we're different! otherwise move
+    // logic already fixed it for us)
+    if (lfs_pair_cmp(oldcwd.pair, newcwd.pair) != 0) {
+        err = lfs_dir_commit(lfs, &oldcwd, NULL, 0);
+        if (err) {
+            LFS_TRACE("lfs_rename -> %d", err);
+            return err;
+        }
+    }
+
+    if (prevtag != LFS_ERR_NOENT && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) {
+        // fix orphan
+        lfs_fs_preporphans(lfs, -1);
+
+        err = lfs_fs_pred(lfs, prevdir.pair, &newcwd);
+        if (err) {
+            LFS_TRACE("lfs_rename -> %d", err);
+            return err;
+        }
+
+        err = lfs_dir_drop(lfs, &newcwd, &prevdir);
+        if (err) {
+            LFS_TRACE("lfs_rename -> %d", err);
+            return err;
+        }
+    }
+
+    LFS_TRACE("lfs_rename -> %d", 0);
+    return 0;
+}
+
+lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
+        uint8_t type, void *buffer, lfs_size_t size) {
+    LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")",
+            (void*)lfs, path, type, buffer, size);
+    lfs_mdir_t cwd;
+    lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL);
+    if (tag < 0) {
+        LFS_TRACE("lfs_getattr -> %"PRId32, tag);
+        return tag;
+    }
+
+    uint16_t id = lfs_tag_id(tag);
+    if (id == 0x3ff) {
+        // special case for root
+        id = 0;
+        int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
+        if (err) {
+            LFS_TRACE("lfs_getattr -> %"PRId32, err);
+            return err;
+        }
+    }
+
+    tag = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x7ff, 0x3ff, 0),
+            LFS_MKTAG(LFS_TYPE_USERATTR + type,
+                id, lfs_min(size, lfs->attr_max)),
+            buffer);
+    if (tag < 0) {
+        if (tag == LFS_ERR_NOENT) {
+            LFS_TRACE("lfs_getattr -> %"PRId32, LFS_ERR_NOATTR);
+            return LFS_ERR_NOATTR;
+        }
+
+        LFS_TRACE("lfs_getattr -> %"PRId32, tag);
+        return tag;
+    }
+
+    size = lfs_tag_size(tag);
+    LFS_TRACE("lfs_getattr -> %"PRId32, size);
+    return size;
+}
+
+static int lfs_commitattr(lfs_t *lfs, const char *path,
+        uint8_t type, const void *buffer, lfs_size_t size) {
+    lfs_mdir_t cwd;
+    lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL);
+    if (tag < 0) {
+        return tag;
+    }
+
+    uint16_t id = lfs_tag_id(tag);
+    if (id == 0x3ff) {
+        // special case for root
+        id = 0;
+        int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
+        if (err) {
+            return err;
+        }
+    }
+
+    return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer}));
+}
+
+int lfs_setattr(lfs_t *lfs, const char *path,
+        uint8_t type, const void *buffer, lfs_size_t size) {
+    LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")",
+            (void*)lfs, path, type, buffer, size);
+    if (size > lfs->attr_max) {
+        LFS_TRACE("lfs_setattr -> %d", LFS_ERR_NOSPC);
+        return LFS_ERR_NOSPC;
+    }
+
+    int err = lfs_commitattr(lfs, path, type, buffer, size);
+    LFS_TRACE("lfs_setattr -> %d", err);
+    return err;
+}
+
+int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
+    LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type);
+    int err = lfs_commitattr(lfs, path, type, NULL, 0x3ff);
+    LFS_TRACE("lfs_removeattr -> %d", err);
+    return err;
+}
+
+
+/// Filesystem operations ///
+static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
+    lfs->cfg = cfg;
+    int err = 0;
+
+    // check that block size is a multiple of cache size is a multiple
+    // of prog and read sizes
+    LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0);
+    LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0);
+    LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0);
+
+    // check that the block size is large enough to fit ctz pointers
+    LFS_ASSERT(4*lfs_npw2(LFS_BLOCK_NULL / (lfs->cfg->block_size-2*4))
+            <= lfs->cfg->block_size);
+
+    // block_cycles = 0 is no longer supported.
+    //
+    // block_cycles is the number of erase cycles before littlefs evicts
+    // metadata logs as a part of wear leveling. Suggested values are in the
+    // range of 100-1000, or set block_cycles to -1 to disable block-level
+    // wear-leveling.
+    LFS_ASSERT(lfs->cfg->block_cycles != 0);
+
+
+    // setup read cache
+    if (lfs->cfg->read_buffer) {
+        lfs->rcache.buffer = lfs->cfg->read_buffer;
+    } else {
+        lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size);
+        if (!lfs->rcache.buffer) {
+            err = LFS_ERR_NOMEM;
+            goto cleanup;
+        }
+    }
+
+    // setup program cache
+    if (lfs->cfg->prog_buffer) {
+        lfs->pcache.buffer = lfs->cfg->prog_buffer;
+    } else {
+        lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size);
+        if (!lfs->pcache.buffer) {
+            err = LFS_ERR_NOMEM;
+            goto cleanup;
+        }
+    }
+
+    // zero to avoid information leaks
+    lfs_cache_zero(lfs, &lfs->rcache);
+    lfs_cache_zero(lfs, &lfs->pcache);
+
+    // setup lookahead, must be multiple of 64-bits, 32-bit aligned
+    LFS_ASSERT(lfs->cfg->lookahead_size > 0);
+    LFS_ASSERT(lfs->cfg->lookahead_size % 8 == 0 &&
+            (uintptr_t)lfs->cfg->lookahead_buffer % 4 == 0);
+    if (lfs->cfg->lookahead_buffer) {
+        lfs->free.buffer = lfs->cfg->lookahead_buffer;
+    } else {
+        lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead_size);
+        if (!lfs->free.buffer) {
+            err = LFS_ERR_NOMEM;
+            goto cleanup;
+        }
+    }
+
+    // check that the size limits are sane
+    LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX);
+    lfs->name_max = lfs->cfg->name_max;
+    if (!lfs->name_max) {
+        lfs->name_max = LFS_NAME_MAX;
+    }
+
+    LFS_ASSERT(lfs->cfg->file_max <= LFS_FILE_MAX);
+    lfs->file_max = lfs->cfg->file_max;
+    if (!lfs->file_max) {
+        lfs->file_max = LFS_FILE_MAX;
+    }
+
+    LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX);
+    lfs->attr_max = lfs->cfg->attr_max;
+    if (!lfs->attr_max) {
+        lfs->attr_max = LFS_ATTR_MAX;
+    }
+
+    // setup default state
+    lfs->root[0] = LFS_BLOCK_NULL;
+    lfs->root[1] = LFS_BLOCK_NULL;
+    lfs->mlist = NULL;
+    lfs->seed = 0;
+    lfs->gstate = (struct lfs_gstate){0};
+    lfs->gpending = (struct lfs_gstate){0};
+    lfs->gdelta = (struct lfs_gstate){0};
+#ifdef LFS_MIGRATE
+    lfs->lfs1 = NULL;
+#endif
+
+    return 0;
+
+cleanup:
+    lfs_deinit(lfs);
+    return err;
+}
+
+static int lfs_deinit(lfs_t *lfs) {
+    // free allocated memory
+    if (!lfs->cfg->read_buffer) {
+        lfs_free(lfs->rcache.buffer);
+    }
+
+    if (!lfs->cfg->prog_buffer) {
+        lfs_free(lfs->pcache.buffer);
+    }
+
+    if (!lfs->cfg->lookahead_buffer) {
+        lfs_free(lfs->free.buffer);
+    }
+
+    return 0;
+}
+
+int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
+    LFS_TRACE("lfs_format(%p, %p {.context=%p, "
+                ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
+                ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
+                ".block_size=%"PRIu32", .block_count=%"PRIu32", "
+                ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
+                ".lookahead_size=%"PRIu32", .read_buffer=%p, "
+                ".prog_buffer=%p, .lookahead_buffer=%p, "
+                ".name_max=%"PRIu32", .file_max=%"PRIu32", "
+                ".attr_max=%"PRIu32"})",
+            (void*)lfs, (void*)cfg, cfg->context,
+            (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
+            (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
+            cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
+            cfg->block_cycles, cfg->cache_size, cfg->lookahead_size,
+            cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer,
+            cfg->name_max, cfg->file_max, cfg->attr_max);
+    int err = 0;
+    {
+        err = lfs_init(lfs, cfg);
+        if (err) {
+            LFS_TRACE("lfs_format -> %d", err);
+            return err;
+        }
+
+        // create free lookahead
+        memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
+        lfs->free.off = 0;
+        lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size,
+                lfs->cfg->block_count);
+        lfs->free.i = 0;
+        lfs_alloc_ack(lfs);
+
+        // create root dir
+        lfs_mdir_t root;
+        err = lfs_dir_alloc(lfs, &root);
+        if (err) {
+            goto cleanup;
+        }
+
+        // write one superblock
+        lfs_superblock_t superblock = {
+            .version     = LFS_DISK_VERSION,
+            .block_size  = lfs->cfg->block_size,
+            .block_count = lfs->cfg->block_count,
+            .name_max    = lfs->name_max,
+            .file_max    = lfs->file_max,
+            .attr_max    = lfs->attr_max,
+        };
+
+        lfs_superblock_tole32(&superblock);
+        err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
+                {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
+                {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                    &superblock}));
+        if (err) {
+            goto cleanup;
+        }
+
+        // sanity check that fetch works
+        err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1});
+        if (err) {
+            goto cleanup;
+        }
+
+        // force compaction to prevent accidentally mounting any
+        // older version of littlefs that may live on disk
+        root.erased = false;
+        err = lfs_dir_commit(lfs, &root, NULL, 0);
+        if (err) {
+            goto cleanup;
+        }
+    }
+
+cleanup:
+    lfs_deinit(lfs);
+    LFS_TRACE("lfs_format -> %d", err);
+    return err;
+}
+
+int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
+    LFS_TRACE("lfs_mount(%p, %p {.context=%p, "
+                ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
+                ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
+                ".block_size=%"PRIu32", .block_count=%"PRIu32", "
+                ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
+                ".lookahead_size=%"PRIu32", .read_buffer=%p, "
+                ".prog_buffer=%p, .lookahead_buffer=%p, "
+                ".name_max=%"PRIu32", .file_max=%"PRIu32", "
+                ".attr_max=%"PRIu32"})",
+            (void*)lfs, (void*)cfg, cfg->context,
+            (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
+            (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
+            cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
+            cfg->block_cycles, cfg->cache_size, cfg->lookahead_size,
+            cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer,
+            cfg->name_max, cfg->file_max, cfg->attr_max);
+    int err = lfs_init(lfs, cfg);
+    if (err) {
+        LFS_TRACE("lfs_mount -> %d", err);
+        return err;
+    }
+
+    // scan directory blocks for superblock and any global updates
+    lfs_mdir_t dir = {.tail = {0, 1}};
+    while (!lfs_pair_isnull(dir.tail)) {
+        // fetch next block in tail list
+        lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
+                LFS_MKTAG(0x7ff, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8),
+                NULL,
+                lfs_dir_find_match, &(struct lfs_dir_find_match){
+                    lfs, "littlefs", 8});
+        if (tag < 0) {
+            err = tag;
+            goto cleanup;
+        }
+
+        // has superblock?
+        if (tag && !lfs_tag_isdelete(tag)) {
+            // update root
+            lfs->root[0] = dir.pair[0];
+            lfs->root[1] = dir.pair[1];
+
+            // grab superblock
+            lfs_superblock_t superblock;
+            tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                    &superblock);
+            if (tag < 0) {
+                err = tag;
+                goto cleanup;
+            }
+            lfs_superblock_fromle32(&superblock);
+
+            // check version
+            uint16_t major_version = (0xffff & (superblock.version >> 16));
+            uint16_t minor_version = (0xffff & (superblock.version >>  0));
+            if ((major_version != LFS_DISK_VERSION_MAJOR ||
+                 minor_version > LFS_DISK_VERSION_MINOR)) {
+                LFS_ERROR("Invalid version %"PRIu16".%"PRIu16,
+                        major_version, minor_version);
+                err = LFS_ERR_INVAL;
+                goto cleanup;
+            }
+
+            // check superblock configuration
+            if (superblock.name_max) {
+                if (superblock.name_max > lfs->name_max) {
+                    LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")",
+                            superblock.name_max, lfs->name_max);
+                    err = LFS_ERR_INVAL;
+                    goto cleanup;
+                }
+
+                lfs->name_max = superblock.name_max;
+            }
+
+            if (superblock.file_max) {
+                if (superblock.file_max > lfs->file_max) {
+                    LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")",
+                            superblock.file_max, lfs->file_max);
+                    err = LFS_ERR_INVAL;
+                    goto cleanup;
+                }
+
+                lfs->file_max = superblock.file_max;
+            }
+
+            if (superblock.attr_max) {
+                if (superblock.attr_max > lfs->attr_max) {
+                    LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")",
+                            superblock.attr_max, lfs->attr_max);
+                    err = LFS_ERR_INVAL;
+                    goto cleanup;
+                }
+
+                lfs->attr_max = superblock.attr_max;
+            }
+        }
+
+        // has gstate?
+        err = lfs_dir_getgstate(lfs, &dir, &lfs->gpending);
+        if (err) {
+            goto cleanup;
+        }
+    }
+
+    // found superblock?
+    if (lfs_pair_isnull(lfs->root)) {
+        err = LFS_ERR_INVAL;
+        goto cleanup;
+    }
+
+    // update littlefs with gstate
+    lfs->gpending.tag += !lfs_tag_isvalid(lfs->gpending.tag);
+    lfs->gstate = lfs->gpending;
+    if (lfs_gstate_hasmove(&lfs->gstate)) {
+        LFS_DEBUG("Found move %"PRIx32" %"PRIx32" %"PRIx16,
+                lfs->gstate.pair[0],
+                lfs->gstate.pair[1],
+                lfs_tag_id(lfs->gstate.tag));
+    }
+
+    // setup free lookahead
+    lfs->free.off = lfs->seed % lfs->cfg->block_size;
+    lfs->free.size = 0;
+    lfs->free.i = 0;
+    lfs_alloc_ack(lfs);
+
+    LFS_TRACE("lfs_mount -> %d", 0);
+    return 0;
+
+cleanup:
+    lfs_unmount(lfs);
+    LFS_TRACE("lfs_mount -> %d", err);
+    return err;
+}
+
+int lfs_unmount(lfs_t *lfs) {
+    LFS_TRACE("lfs_unmount(%p)", (void*)lfs);
+    int err = lfs_deinit(lfs);
+    LFS_TRACE("lfs_unmount -> %d", err);
+    return err;
+}
+
+
+/// Filesystem filesystem operations ///
+int lfs_fs_traverse(lfs_t *lfs,
+        int (*cb)(void *data, lfs_block_t block), void *data) {
+    LFS_TRACE("lfs_fs_traverse(%p, %p, %p)",
+            (void*)lfs, (void*)(uintptr_t)cb, data);
+    // iterate over metadata pairs
+    lfs_mdir_t dir = {.tail = {0, 1}};
+
+#ifdef LFS_MIGRATE
+    // also consider v1 blocks during migration
+    if (lfs->lfs1) {
+        int err = lfs1_traverse(lfs, cb, data);
+        if (err) {
+            LFS_TRACE("lfs_fs_traverse -> %d", err);
+            return err;
+        }
+
+        dir.tail[0] = lfs->root[0];
+        dir.tail[1] = lfs->root[1];
+    }
+#endif
+
+    while (!lfs_pair_isnull(dir.tail)) {
+        for (int i = 0; i < 2; i++) {
+            int err = cb(data, dir.tail[i]);
+            if (err) {
+                LFS_TRACE("lfs_fs_traverse -> %d", err);
+                return err;
+            }
+        }
+
+        // iterate through ids in directory
+        int err = lfs_dir_fetch(lfs, &dir, dir.tail);
+        if (err) {
+            LFS_TRACE("lfs_fs_traverse -> %d", err);
+            return err;
+        }
+
+        for (uint16_t id = 0; id < dir.count; id++) {
+            struct lfs_ctz ctz;
+            lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x700, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz);
+            if (tag < 0) {
+                if (tag == LFS_ERR_NOENT) {
+                    continue;
+                }
+                LFS_TRACE("lfs_fs_traverse -> %d", tag);
+                return tag;
+            }
+            lfs_ctz_fromle32(&ctz);
+
+            if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) {
+                err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache,
+                        ctz.head, ctz.size, cb, data);
+                if (err) {
+                    LFS_TRACE("lfs_fs_traverse -> %d", err);
+                    return err;
+                }
+            }
+        }
+    }
+
+    // iterate over any open files
+    for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
+        if (f->type != LFS_TYPE_REG) {
+            continue;
+        }
+
+        if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) {
+            int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache,
+                    f->ctz.head, f->ctz.size, cb, data);
+            if (err) {
+                LFS_TRACE("lfs_fs_traverse -> %d", err);
+                return err;
+            }
+        }
+
+        if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) {
+            int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache,
+                    f->block, f->pos, cb, data);
+            if (err) {
+                LFS_TRACE("lfs_fs_traverse -> %d", err);
+                return err;
+            }
+        }
+    }
+
+    LFS_TRACE("lfs_fs_traverse -> %d", 0);
+    return 0;
+}
+
+static int lfs_fs_pred(lfs_t *lfs,
+        const lfs_block_t pair[2], lfs_mdir_t *pdir) {
+    // iterate over all directory directory entries
+    pdir->tail[0] = 0;
+    pdir->tail[1] = 1;
+    while (!lfs_pair_isnull(pdir->tail)) {
+        if (lfs_pair_cmp(pdir->tail, pair) == 0) {
+            return 0;
+        }
+
+        int err = lfs_dir_fetch(lfs, pdir, pdir->tail);
+        if (err) {
+            return err;
+        }
+    }
+
+    return LFS_ERR_NOENT;
+}
+
+struct lfs_fs_parent_match {
+    lfs_t *lfs;
+    const lfs_block_t pair[2];
+};
+
+static int lfs_fs_parent_match(void *data,
+        lfs_tag_t tag, const void *buffer) {
+    struct lfs_fs_parent_match *find = data;
+    lfs_t *lfs = find->lfs;
+    const struct lfs_diskoff *disk = buffer;
+    (void)tag;
+
+    lfs_block_t child[2];
+    int err = lfs_bd_read(lfs,
+            &lfs->pcache, &lfs->rcache, lfs->cfg->block_size,
+            disk->block, disk->off, &child, sizeof(child));
+    if (err) {
+        return err;
+    }
+
+    lfs_pair_fromle32(child);
+    return (lfs_pair_cmp(child, find->pair) == 0) ? LFS_CMP_EQ : LFS_CMP_LT;
+}
+
+static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
+        lfs_mdir_t *parent) {
+    // use fetchmatch with callback to find pairs
+    parent->tail[0] = 0;
+    parent->tail[1] = 1;
+    while (!lfs_pair_isnull(parent->tail)) {
+        lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail,
+                LFS_MKTAG(0x7ff, 0, 0x3ff),
+                LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8),
+                NULL,
+                lfs_fs_parent_match, &(struct lfs_fs_parent_match){
+                    lfs, {pair[0], pair[1]}});
+        if (tag && tag != LFS_ERR_NOENT) {
+            return tag;
+        }
+    }
+
+    return LFS_ERR_NOENT;
+}
+
+static int lfs_fs_relocate(lfs_t *lfs,
+        const lfs_block_t oldpair[2], lfs_block_t newpair[2]) {
+    // update internal root
+    if (lfs_pair_cmp(oldpair, lfs->root) == 0) {
+        LFS_DEBUG("Relocating root %"PRIx32" %"PRIx32,
+                newpair[0], newpair[1]);
+        lfs->root[0] = newpair[0];
+        lfs->root[1] = newpair[1];
+    }
+
+    // update internally tracked dirs
+    for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) {
+        if (lfs_pair_cmp(oldpair, d->m.pair) == 0) {
+            d->m.pair[0] = newpair[0];
+            d->m.pair[1] = newpair[1];
+        }
+    }
+
+    // find parent
+    lfs_mdir_t parent;
+    lfs_stag_t tag = lfs_fs_parent(lfs, oldpair, &parent);
+    if (tag < 0 && tag != LFS_ERR_NOENT) {
+        return tag;
+    }
+
+    if (tag != LFS_ERR_NOENT) {
+        // update disk, this creates a desync
+        lfs_fs_preporphans(lfs, +1);
+
+        lfs_pair_tole32(newpair);
+        int err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS({tag, newpair}));
+        lfs_pair_fromle32(newpair);
+        if (err) {
+            return err;
+        }
+
+        // next step, clean up orphans
+        lfs_fs_preporphans(lfs, -1);
+    }
+
+    // find pred
+    int err = lfs_fs_pred(lfs, oldpair, &parent);
+    if (err && err != LFS_ERR_NOENT) {
+        return err;
+    }
+
+    // if we can't find dir, it must be new
+    if (err != LFS_ERR_NOENT) {
+        // replace bad pair, either we clean up desync, or no desync occured
+        lfs_pair_tole32(newpair);
+        err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_TAIL + parent.split, 0x3ff, 8), newpair}));
+        lfs_pair_fromle32(newpair);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) {
+    lfs->gpending.tag += orphans;
+    lfs_gstate_xororphans(&lfs->gdelta,   &lfs->gpending,
+            lfs_gstate_hasorphans(&lfs->gpending));
+    lfs_gstate_xororphans(&lfs->gpending, &lfs->gpending,
+            lfs_gstate_hasorphans(&lfs->gpending));
+}
+
+static void lfs_fs_prepmove(lfs_t *lfs,
+        uint16_t id, const lfs_block_t pair[2]) {
+    lfs_gstate_xormove(&lfs->gdelta,   &lfs->gpending, id, pair);
+    lfs_gstate_xormove(&lfs->gpending, &lfs->gpending, id, pair);
+}
+
+
+static int lfs_fs_demove(lfs_t *lfs) {
+    if (!lfs_gstate_hasmove(&lfs->gstate)) {
+        return 0;
+    }
+
+    // Fix bad moves
+    LFS_DEBUG("Fixing move %"PRIx32" %"PRIx32" %"PRIx16,
+            lfs->gstate.pair[0],
+            lfs->gstate.pair[1],
+            lfs_tag_id(lfs->gstate.tag));
+
+    // fetch and delete the moved entry
+    lfs_mdir_t movedir;
+    int err = lfs_dir_fetch(lfs, &movedir, lfs->gstate.pair);
+    if (err) {
+        return err;
+    }
+
+    // rely on cancel logic inside commit
+    err = lfs_dir_commit(lfs, &movedir, NULL, 0);
+    if (err) {
+        return err;
+    }
+
+    return 0;
+}
+
+static int lfs_fs_deorphan(lfs_t *lfs) {
+    if (!lfs_gstate_hasorphans(&lfs->gstate)) {
+        return 0;
+    }
+
+    // Fix any orphans
+    lfs_mdir_t pdir = {.split = true};
+    lfs_mdir_t dir = {.tail = {0, 1}};
+
+    // iterate over all directory directory entries
+    while (!lfs_pair_isnull(dir.tail)) {
+        int err = lfs_dir_fetch(lfs, &dir, dir.tail);
+        if (err) {
+            return err;
+        }
+
+        // check head blocks for orphans
+        if (!pdir.split) {
+            // check if we have a parent
+            lfs_mdir_t parent;
+            lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent);
+            if (tag < 0 && tag != LFS_ERR_NOENT) {
+                return tag;
+            }
+
+            if (tag == LFS_ERR_NOENT) {
+                // we are an orphan
+                LFS_DEBUG("Fixing orphan %"PRIx32" %"PRIx32,
+                        pdir.tail[0], pdir.tail[1]);
+
+                err = lfs_dir_drop(lfs, &pdir, &dir);
+                if (err) {
+                    return err;
+                }
+
+                break;
+            }
+
+            lfs_block_t pair[2];
+            lfs_stag_t res = lfs_dir_get(lfs, &parent,
+                    LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair);
+            if (res < 0) {
+                return res;
+            }
+            lfs_pair_fromle32(pair);
+
+            if (!lfs_pair_sync(pair, pdir.tail)) {
+                // we have desynced
+                LFS_DEBUG("Fixing half-orphan %"PRIx32" %"PRIx32,
+                        pair[0], pair[1]);
+
+                lfs_pair_tole32(pair);
+                err = lfs_dir_commit(lfs, &pdir, LFS_MKATTRS(
+                        {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pair}));
+                lfs_pair_fromle32(pair);
+                if (err) {
+                    return err;
+                }
+
+                break;
+            }
+        }
+
+        memcpy(&pdir, &dir, sizeof(pdir));
+    }
+
+    // mark orphans as fixed
+    lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate));
+    lfs->gstate = lfs->gpending;
+    return 0;
+}
+
+static int lfs_fs_forceconsistency(lfs_t *lfs) {
+    int err = lfs_fs_demove(lfs);
+    if (err) {
+        return err;
+    }
+
+    err = lfs_fs_deorphan(lfs);
+    if (err) {
+        return err;
+    }
+
+    return 0;
+}
+
+static int lfs_fs_size_count(void *p, lfs_block_t block) {
+    (void)block;
+    lfs_size_t *size = p;
+    *size += 1;
+    return 0;
+}
+
+lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
+    LFS_TRACE("lfs_fs_size(%p)", (void*)lfs);
+    lfs_size_t size = 0;
+    int err = lfs_fs_traverse(lfs, lfs_fs_size_count, &size);
+    if (err) {
+        LFS_TRACE("lfs_fs_size -> %"PRId32, err);
+        return err;
+    }
+
+    LFS_TRACE("lfs_fs_size -> %"PRId32, err);
+    return size;
+}
+
+#ifdef LFS_MIGRATE
+////// Migration from littelfs v1 below this //////
+
+/// Version info ///
+
+// Software library version
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS1_VERSION 0x00010007
+#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16))
+#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >>  0))
+
+// Version of On-disk data structures
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS1_DISK_VERSION 0x00010001
+#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16))
+#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >>  0))
+
+
+/// v1 Definitions ///
+
+// File types
+enum lfs1_type {
+    LFS1_TYPE_REG        = 0x11,
+    LFS1_TYPE_DIR        = 0x22,
+    LFS1_TYPE_SUPERBLOCK = 0x2e,
+};
+
+typedef struct lfs1 {
+    lfs_block_t root[2];
+} lfs1_t;
+
+typedef struct lfs1_entry {
+    lfs_off_t off;
+
+    struct lfs1_disk_entry {
+        uint8_t type;
+        uint8_t elen;
+        uint8_t alen;
+        uint8_t nlen;
+        union {
+            struct {
+                lfs_block_t head;
+                lfs_size_t size;
+            } file;
+            lfs_block_t dir[2];
+        } u;
+    } d;
+} lfs1_entry_t;
+
+typedef struct lfs1_dir {
+    struct lfs1_dir *next;
+    lfs_block_t pair[2];
+    lfs_off_t off;
+
+    lfs_block_t head[2];
+    lfs_off_t pos;
+
+    struct lfs1_disk_dir {
+        uint32_t rev;
+        lfs_size_t size;
+        lfs_block_t tail[2];
+    } d;
+} lfs1_dir_t;
+
+typedef struct lfs1_superblock {
+    lfs_off_t off;
+
+    struct lfs1_disk_superblock {
+        uint8_t type;
+        uint8_t elen;
+        uint8_t alen;
+        uint8_t nlen;
+        lfs_block_t root[2];
+        uint32_t block_size;
+        uint32_t block_count;
+        uint32_t version;
+        char magic[8];
+    } d;
+} lfs1_superblock_t;
+
+
+/// Low-level wrappers v1->v2 ///
+static void lfs1_crc(uint32_t *crc, const void *buffer, size_t size) {
+    *crc = lfs_crc(*crc, buffer, size);
+}
+
+static int lfs1_bd_read(lfs_t *lfs, lfs_block_t block,
+        lfs_off_t off, void *buffer, lfs_size_t size) {
+    // if we ever do more than writes to alternating pairs,
+    // this may need to consider pcache
+    return lfs_bd_read(lfs, &lfs->pcache, &lfs->rcache, size,
+            block, off, buffer, size);
+}
+
+static int lfs1_bd_crc(lfs_t *lfs, lfs_block_t block,
+        lfs_off_t off, lfs_size_t size, uint32_t *crc) {
+    for (lfs_off_t i = 0; i < size; i++) {
+        uint8_t c;
+        int err = lfs1_bd_read(lfs, block, off+i, &c, 1);
+        if (err) {
+            return err;
+        }
+
+        lfs1_crc(crc, &c, 1);
+    }
+
+    return 0;
+}
+
+
+/// Endian swapping functions ///
+static void lfs1_dir_fromle32(struct lfs1_disk_dir *d) {
+    d->rev     = lfs_fromle32(d->rev);
+    d->size    = lfs_fromle32(d->size);
+    d->tail[0] = lfs_fromle32(d->tail[0]);
+    d->tail[1] = lfs_fromle32(d->tail[1]);
+}
+
+static void lfs1_dir_tole32(struct lfs1_disk_dir *d) {
+    d->rev     = lfs_tole32(d->rev);
+    d->size    = lfs_tole32(d->size);
+    d->tail[0] = lfs_tole32(d->tail[0]);
+    d->tail[1] = lfs_tole32(d->tail[1]);
+}
+
+static void lfs1_entry_fromle32(struct lfs1_disk_entry *d) {
+    d->u.dir[0] = lfs_fromle32(d->u.dir[0]);
+    d->u.dir[1] = lfs_fromle32(d->u.dir[1]);
+}
+
+static void lfs1_entry_tole32(struct lfs1_disk_entry *d) {
+    d->u.dir[0] = lfs_tole32(d->u.dir[0]);
+    d->u.dir[1] = lfs_tole32(d->u.dir[1]);
+}
+
+static void lfs1_superblock_fromle32(struct lfs1_disk_superblock *d) {
+    d->root[0]     = lfs_fromle32(d->root[0]);
+    d->root[1]     = lfs_fromle32(d->root[1]);
+    d->block_size  = lfs_fromle32(d->block_size);
+    d->block_count = lfs_fromle32(d->block_count);
+    d->version     = lfs_fromle32(d->version);
+}
+
+
+///// Metadata pair and directory operations ///
+static inline lfs_size_t lfs1_entry_size(const lfs1_entry_t *entry) {
+    return 4 + entry->d.elen + entry->d.alen + entry->d.nlen;
+}
+
+static int lfs1_dir_fetch(lfs_t *lfs,
+        lfs1_dir_t *dir, const lfs_block_t pair[2]) {
+    // copy out pair, otherwise may be aliasing dir
+    const lfs_block_t tpair[2] = {pair[0], pair[1]};
+    bool valid = false;
+
+    // check both blocks for the most recent revision
+    for (int i = 0; i < 2; i++) {
+        struct lfs1_disk_dir test;
+        int err = lfs1_bd_read(lfs, tpair[i], 0, &test, sizeof(test));
+        lfs1_dir_fromle32(&test);
+        if (err) {
+            if (err == LFS_ERR_CORRUPT) {
+                continue;
+            }
+            return err;
+        }
+
+        if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) {
+            continue;
+        }
+
+        if ((0x7fffffff & test.size) < sizeof(test)+4 ||
+            (0x7fffffff & test.size) > lfs->cfg->block_size) {
+            continue;
+        }
+
+        uint32_t crc = LFS_BLOCK_NULL;
+        lfs1_dir_tole32(&test);
+        lfs1_crc(&crc, &test, sizeof(test));
+        lfs1_dir_fromle32(&test);
+        err = lfs1_bd_crc(lfs, tpair[i], sizeof(test),
+                (0x7fffffff & test.size) - sizeof(test), &crc);
+        if (err) {
+            if (err == LFS_ERR_CORRUPT) {
+                continue;
+            }
+            return err;
+        }
+
+        if (crc != 0) {
+            continue;
+        }
+
+        valid = true;
+
+        // setup dir in case it's valid
+        dir->pair[0] = tpair[(i+0) % 2];
+        dir->pair[1] = tpair[(i+1) % 2];
+        dir->off = sizeof(dir->d);
+        dir->d = test;
+    }
+
+    if (!valid) {
+        LFS_ERROR("Corrupted dir pair at %" PRIx32 " %" PRIx32 ,
+                tpair[0], tpair[1]);
+        return LFS_ERR_CORRUPT;
+    }
+
+    return 0;
+}
+
+static int lfs1_dir_next(lfs_t *lfs, lfs1_dir_t *dir, lfs1_entry_t *entry) {
+    while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) {
+        if (!(0x80000000 & dir->d.size)) {
+            entry->off = dir->off;
+            return LFS_ERR_NOENT;
+        }
+
+        int err = lfs1_dir_fetch(lfs, dir, dir->d.tail);
+        if (err) {
+            return err;
+        }
+
+        dir->off = sizeof(dir->d);
+        dir->pos += sizeof(dir->d) + 4;
+    }
+
+    int err = lfs1_bd_read(lfs, dir->pair[0], dir->off,
+            &entry->d, sizeof(entry->d));
+    lfs1_entry_fromle32(&entry->d);
+    if (err) {
+        return err;
+    }
+
+    entry->off = dir->off;
+    dir->off += lfs1_entry_size(entry);
+    dir->pos += lfs1_entry_size(entry);
+    return 0;
+}
+
+/// littlefs v1 specific operations ///
+int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
+    if (lfs_pair_isnull(lfs->lfs1->root)) {
+        return 0;
+    }
+
+    // iterate over metadata pairs
+    lfs1_dir_t dir;
+    lfs1_entry_t entry;
+    lfs_block_t cwd[2] = {0, 1};
+
+    while (true) {
+        for (int i = 0; i < 2; i++) {
+            int err = cb(data, cwd[i]);
+            if (err) {
+                return err;
+            }
+        }
+
+        int err = lfs1_dir_fetch(lfs, &dir, cwd);
+        if (err) {
+            return err;
+        }
+
+        // iterate over contents
+        while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) {
+            err = lfs1_bd_read(lfs, dir.pair[0], dir.off,
+                    &entry.d, sizeof(entry.d));
+            lfs1_entry_fromle32(&entry.d);
+            if (err) {
+                return err;
+            }
+
+            dir.off += lfs1_entry_size(&entry);
+            if ((0x70 & entry.d.type) == (0x70 & LFS1_TYPE_REG)) {
+                err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache,
+                        entry.d.u.file.head, entry.d.u.file.size, cb, data);
+                if (err) {
+                    return err;
+                }
+            }
+        }
+
+        // we also need to check if we contain a threaded v2 directory
+        lfs_mdir_t dir2 = {.split=true, .tail={cwd[0], cwd[1]}};
+        while (dir2.split) {
+            err = lfs_dir_fetch(lfs, &dir2, dir2.tail);
+            if (err) {
+                break;
+            }
+
+            for (int i = 0; i < 2; i++) {
+                err = cb(data, dir2.pair[i]);
+                if (err) {
+                    return err;
+                }
+            }
+        }
+
+        cwd[0] = dir.d.tail[0];
+        cwd[1] = dir.d.tail[1];
+
+        if (lfs_pair_isnull(cwd)) {
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static int lfs1_moved(lfs_t *lfs, const void *e) {
+    if (lfs_pair_isnull(lfs->lfs1->root)) {
+        return 0;
+    }
+
+    // skip superblock
+    lfs1_dir_t cwd;
+    int err = lfs1_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1});
+    if (err) {
+        return err;
+    }
+
+    // iterate over all directory directory entries
+    lfs1_entry_t entry;
+    while (!lfs_pair_isnull(cwd.d.tail)) {
+        err = lfs1_dir_fetch(lfs, &cwd, cwd.d.tail);
+        if (err) {
+            return err;
+        }
+
+        while (true) {
+            err = lfs1_dir_next(lfs, &cwd, &entry);
+            if (err && err != LFS_ERR_NOENT) {
+                return err;
+            }
+
+            if (err == LFS_ERR_NOENT) {
+                break;
+            }
+
+            if (!(0x80 & entry.d.type) &&
+                 memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+/// Filesystem operations ///
+static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1,
+        const struct lfs_config *cfg) {
+    int err = 0;
+    {
+        err = lfs_init(lfs, cfg);
+        if (err) {
+            return err;
+        }
+
+        lfs->lfs1 = lfs1;
+        lfs->lfs1->root[0] = LFS_BLOCK_NULL;
+        lfs->lfs1->root[1] = LFS_BLOCK_NULL;
+
+        // setup free lookahead
+        lfs->free.off = 0;
+        lfs->free.size = 0;
+        lfs->free.i = 0;
+        lfs_alloc_ack(lfs);
+
+        // load superblock
+        lfs1_dir_t dir;
+        lfs1_superblock_t superblock;
+        err = lfs1_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
+        if (err && err != LFS_ERR_CORRUPT) {
+            goto cleanup;
+        }
+
+        if (!err) {
+            err = lfs1_bd_read(lfs, dir.pair[0], sizeof(dir.d),
+                    &superblock.d, sizeof(superblock.d));
+            lfs1_superblock_fromle32(&superblock.d);
+            if (err) {
+                goto cleanup;
+            }
+
+            lfs->lfs1->root[0] = superblock.d.root[0];
+            lfs->lfs1->root[1] = superblock.d.root[1];
+        }
+
+        if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) {
+            LFS_ERROR("Invalid superblock at %d %d", 0, 1);
+            err = LFS_ERR_CORRUPT;
+            goto cleanup;
+        }
+
+        uint16_t major_version = (0xffff & (superblock.d.version >> 16));
+        uint16_t minor_version = (0xffff & (superblock.d.version >>  0));
+        if ((major_version != LFS1_DISK_VERSION_MAJOR ||
+             minor_version > LFS1_DISK_VERSION_MINOR)) {
+            LFS_ERROR("Invalid version %d.%d", major_version, minor_version);
+            err = LFS_ERR_INVAL;
+            goto cleanup;
+        }
+
+        return 0;
+    }
+
+cleanup:
+    lfs_deinit(lfs);
+    return err;
+}
+
+static int lfs1_unmount(lfs_t *lfs) {
+    return lfs_deinit(lfs);
+}
+
+/// v1 migration ///
+int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
+    LFS_TRACE("lfs_migrate(%p, %p {.context=%p, "
+                ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
+                ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
+                ".block_size=%"PRIu32", .block_count=%"PRIu32", "
+                ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", "
+                ".lookahead_size=%"PRIu32", .read_buffer=%p, "
+                ".prog_buffer=%p, .lookahead_buffer=%p, "
+                ".name_max=%"PRIu32", .file_max=%"PRIu32", "
+                ".attr_max=%"PRIu32"})",
+            (void*)lfs, (void*)cfg, cfg->context,
+            (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
+            (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
+            cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
+            cfg->block_cycles, cfg->cache_size, cfg->lookahead_size,
+            cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer,
+            cfg->name_max, cfg->file_max, cfg->attr_max);
+    struct lfs1 lfs1;
+    int err = lfs1_mount(lfs, &lfs1, cfg);
+    if (err) {
+        LFS_TRACE("lfs_migrate -> %d", err);
+        return err;
+    }
+
+    {
+        // iterate through each directory, copying over entries
+        // into new directory
+        lfs1_dir_t dir1;
+        lfs_mdir_t dir2;
+        dir1.d.tail[0] = lfs->lfs1->root[0];
+        dir1.d.tail[1] = lfs->lfs1->root[1];
+        while (!lfs_pair_isnull(dir1.d.tail)) {
+            // iterate old dir
+            err = lfs1_dir_fetch(lfs, &dir1, dir1.d.tail);
+            if (err) {
+                goto cleanup;
+            }
+
+            // create new dir and bind as temporary pretend root
+            err = lfs_dir_alloc(lfs, &dir2);
+            if (err) {
+                goto cleanup;
+            }
+
+            dir2.rev = dir1.d.rev;
+            dir1.head[0] = dir1.pair[0];
+            dir1.head[1] = dir1.pair[1];
+            lfs->root[0] = dir2.pair[0];
+            lfs->root[1] = dir2.pair[1];
+
+            err = lfs_dir_commit(lfs, &dir2, NULL, 0);
+            if (err) {
+                goto cleanup;
+            }
+
+            while (true) {
+                lfs1_entry_t entry1;
+                err = lfs1_dir_next(lfs, &dir1, &entry1);
+                if (err && err != LFS_ERR_NOENT) {
+                    goto cleanup;
+                }
+
+                if (err == LFS_ERR_NOENT) {
+                    break;
+                }
+
+                // check that entry has not been moved
+                if (entry1.d.type & 0x80) {
+                    int moved = lfs1_moved(lfs, &entry1.d.u);
+                    if (moved < 0) {
+                        err = moved;
+                        goto cleanup;
+                    }
+
+                    if (moved) {
+                        continue;
+                    }
+
+                    entry1.d.type &= ~0x80;
+                }
+
+                // also fetch name
+                char name[LFS_NAME_MAX+1];
+                memset(name, 0, sizeof(name));
+                err = lfs1_bd_read(lfs, dir1.pair[0],
+                        entry1.off + 4+entry1.d.elen+entry1.d.alen,
+                        name, entry1.d.nlen);
+                if (err) {
+                    goto cleanup;
+                }
+
+                bool isdir = (entry1.d.type == LFS1_TYPE_DIR);
+
+                // create entry in new dir
+                err = lfs_dir_fetch(lfs, &dir2, lfs->root);
+                if (err) {
+                    goto cleanup;
+                }
+
+                uint16_t id;
+                err = lfs_dir_find(lfs, &dir2, &(const char*){name}, &id);
+                if (!(err == LFS_ERR_NOENT && id != 0x3ff)) {
+                    err = (err < 0) ? err : LFS_ERR_EXIST;
+                    goto cleanup;
+                }
+
+                lfs1_entry_tole32(&entry1.d);
+                err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS(
+                        {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL},
+                        {LFS_MKTAG(
+                            isdir ? LFS_TYPE_DIR : LFS_TYPE_REG,
+                            id, entry1.d.nlen), name},
+                        {LFS_MKTAG(
+                            isdir ? LFS_TYPE_DIRSTRUCT : LFS_TYPE_CTZSTRUCT,
+                            id, sizeof(&entry1.d.u)), &entry1.d.u}));
+                lfs1_entry_fromle32(&entry1.d);
+                if (err) {
+                    goto cleanup;
+                }
+            }
+
+            if (!lfs_pair_isnull(dir1.d.tail)) {
+                // find last block and update tail to thread into fs
+                err = lfs_dir_fetch(lfs, &dir2, lfs->root);
+                if (err) {
+                    goto cleanup;
+                }
+
+                while (dir2.split) {
+                    err = lfs_dir_fetch(lfs, &dir2, dir2.tail);
+                    if (err) {
+                        goto cleanup;
+                    }
+                }
+
+                lfs_pair_tole32(dir2.pair);
+                err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS(
+                        {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 0),
+                            dir1.d.tail}));
+                lfs_pair_fromle32(dir2.pair);
+                if (err) {
+                    goto cleanup;
+                }
+            }
+
+            // Copy over first block to thread into fs. Unfortunately
+            // if this fails there is not much we can do.
+            LFS_DEBUG("Migrating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32,
+                    lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]);
+
+            err = lfs_bd_erase(lfs, dir1.head[1]);
+            if (err) {
+                goto cleanup;
+            }
+
+            err = lfs_dir_fetch(lfs, &dir2, lfs->root);
+            if (err) {
+                goto cleanup;
+            }
+
+            for (lfs_off_t i = 0; i < dir2.off; i++) {
+                uint8_t dat;
+                err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, dir2.off,
+                        dir2.pair[0], i, &dat, 1);
+                if (err) {
+                    goto cleanup;
+                }
+
+                err = lfs_bd_prog(lfs,
+                        &lfs->pcache, &lfs->rcache, true,
+                        dir1.head[1], i, &dat, 1);
+                if (err) {
+                    goto cleanup;
+                }
+            }
+
+            err = lfs_bd_flush(lfs, &lfs->pcache, &lfs->rcache, true);
+            if (err) {
+                goto cleanup;
+            }
+        }
+
+        // Create new superblock. This marks a successful migration!
+        err = lfs1_dir_fetch(lfs, &dir1, (const lfs_block_t[2]){0, 1});
+        if (err) {
+            goto cleanup;
+        }
+
+        dir2.pair[0] = dir1.pair[0];
+        dir2.pair[1] = dir1.pair[1];
+        dir2.rev = dir1.d.rev;
+        dir2.off = sizeof(dir2.rev);
+        dir2.etag = LFS_BLOCK_NULL;
+        dir2.count = 0;
+        dir2.tail[0] = lfs->lfs1->root[0];
+        dir2.tail[1] = lfs->lfs1->root[1];
+        dir2.erased = false;
+        dir2.split = true;
+
+        lfs_superblock_t superblock = {
+            .version     = LFS_DISK_VERSION,
+            .block_size  = lfs->cfg->block_size,
+            .block_count = lfs->cfg->block_count,
+            .name_max    = lfs->name_max,
+            .file_max    = lfs->file_max,
+            .attr_max    = lfs->attr_max,
+        };
+
+        lfs_superblock_tole32(&superblock);
+        err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
+                {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
+                {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                    &superblock}));
+        if (err) {
+            goto cleanup;
+        }
+
+        // sanity check that fetch works
+        err = lfs_dir_fetch(lfs, &dir2, (const lfs_block_t[2]){0, 1});
+        if (err) {
+            goto cleanup;
+        }
+
+        // force compaction to prevent accidentally mounting v1
+        dir2.erased = false;
+        err = lfs_dir_commit(lfs, &dir2, NULL, 0);
+        if (err) {
+            goto cleanup;
+        }
+    }
+
+cleanup:
+    lfs1_unmount(lfs);
+    LFS_TRACE("lfs_migrate -> %d", err);
+    return err;
+}
+
+#endif
diff --git a/src/bach/build.bach/tools/mklfs/src/lfs/lfs.h b/src/bach/build.bach/tools/mklfs/src/lfs/lfs.h
new file mode 100644
index 0000000..04054e9
--- /dev/null
+++ b/src/bach/build.bach/tools/mklfs/src/lfs/lfs.h
@@ -0,0 +1,651 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef LFS_H
+#define LFS_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+/// Version info ///
+
+// Software library version
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS_VERSION 0x00020001
+#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
+#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
+
+// Version of On-disk data structures
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS_DISK_VERSION 0x00020000
+#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
+#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >>  0))
+
+
+/// Definitions ///
+
+// Type definitions
+typedef uint32_t lfs_size_t;
+typedef uint32_t lfs_off_t;
+
+typedef int32_t  lfs_ssize_t;
+typedef int32_t  lfs_soff_t;
+
+typedef uint32_t lfs_block_t;
+
+// Maximum name size in bytes, may be redefined to reduce the size of the
+// info struct. Limited to <= 1022. Stored in superblock and must be
+// respected by other littlefs drivers.
+#ifndef LFS_NAME_MAX
+#define LFS_NAME_MAX 255
+#endif
+
+// Maximum size of a file in bytes, may be redefined to limit to support other
+// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the
+// functions lfs_file_seek, lfs_file_size, and lfs_file_tell will return
+// incorrect values due to using signed integers. Stored in superblock and
+// must be respected by other littlefs drivers.
+#ifndef LFS_FILE_MAX
+#define LFS_FILE_MAX 2147483647
+#endif
+
+// Maximum size of custom attributes in bytes, may be redefined, but there is
+// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022.
+#ifndef LFS_ATTR_MAX
+#define LFS_ATTR_MAX 1022
+#endif
+
+// Possible error codes, these are negative to allow
+// valid positive return values
+enum lfs_error {
+    LFS_ERR_OK          = 0,    // No error
+    LFS_ERR_IO          = -5,   // Error during device operation
+    LFS_ERR_CORRUPT     = -84,  // Corrupted
+    LFS_ERR_NOENT       = -2,   // No directory entry
+    LFS_ERR_EXIST       = -17,  // Entry already exists
+    LFS_ERR_NOTDIR      = -20,  // Entry is not a dir
+    LFS_ERR_ISDIR       = -21,  // Entry is a dir
+    LFS_ERR_NOTEMPTY    = -39,  // Dir is not empty
+    LFS_ERR_BADF        = -9,   // Bad file number
+    LFS_ERR_FBIG        = -27,  // File too large
+    LFS_ERR_INVAL       = -22,  // Invalid parameter
+    LFS_ERR_NOSPC       = -28,  // No space left on device
+    LFS_ERR_NOMEM       = -12,  // No more memory available
+    LFS_ERR_NOATTR      = -61,  // No data/attr available
+    LFS_ERR_NAMETOOLONG = -36,  // File name too long
+};
+
+// File types
+enum lfs_type {
+    // file types
+    LFS_TYPE_REG            = 0x001,
+    LFS_TYPE_DIR            = 0x002,
+
+    // internally used types
+    LFS_TYPE_SPLICE         = 0x400,
+    LFS_TYPE_NAME           = 0x000,
+    LFS_TYPE_STRUCT         = 0x200,
+    LFS_TYPE_USERATTR       = 0x300,
+    LFS_TYPE_FROM           = 0x100,
+    LFS_TYPE_TAIL           = 0x600,
+    LFS_TYPE_GLOBALS        = 0x700,
+    LFS_TYPE_CRC            = 0x500,
+
+    // internally used type specializations
+    LFS_TYPE_CREATE         = 0x401,
+    LFS_TYPE_DELETE         = 0x4ff,
+    LFS_TYPE_SUPERBLOCK     = 0x0ff,
+    LFS_TYPE_DIRSTRUCT      = 0x200,
+    LFS_TYPE_CTZSTRUCT      = 0x202,
+    LFS_TYPE_INLINESTRUCT   = 0x201,
+    LFS_TYPE_SOFTTAIL       = 0x600,
+    LFS_TYPE_HARDTAIL       = 0x601,
+    LFS_TYPE_MOVESTATE      = 0x7ff,
+
+    // internal chip sources
+    LFS_FROM_NOOP           = 0x000,
+    LFS_FROM_MOVE           = 0x101,
+    LFS_FROM_USERATTRS      = 0x102,
+};
+
+// File open flags
+enum lfs_open_flags {
+    // open flags
+    LFS_O_RDONLY = 1,         // Open a file as read only
+    LFS_O_WRONLY = 2,         // Open a file as write only
+    LFS_O_RDWR   = 3,         // Open a file as read and write
+    LFS_O_CREAT  = 0x0100,    // Create a file if it does not exist
+    LFS_O_EXCL   = 0x0200,    // Fail if a file already exists
+    LFS_O_TRUNC  = 0x0400,    // Truncate the existing file to zero size
+    LFS_O_APPEND = 0x0800,    // Move to end of file on every write
+
+    // internally used flags
+    LFS_F_DIRTY   = 0x010000, // File does not match storage
+    LFS_F_WRITING = 0x020000, // File has been written since last flush
+    LFS_F_READING = 0x040000, // File has been read since last flush
+    LFS_F_ERRED   = 0x080000, // An error occured during write
+    LFS_F_INLINE  = 0x100000, // Currently inlined in directory entry
+    LFS_F_OPENED  = 0x200000, // File has been opened
+};
+
+// File seek flags
+enum lfs_whence_flags {
+    LFS_SEEK_SET = 0,   // Seek relative to an absolute position
+    LFS_SEEK_CUR = 1,   // Seek relative to the current file position
+    LFS_SEEK_END = 2,   // Seek relative to the end of the file
+};
+
+
+// Configuration provided during initialization of the littlefs
+struct lfs_config {
+    // Opaque user provided context that can be used to pass
+    // information to the block device operations
+    void *context;
+
+    // Read a region in a block. Negative error codes are propogated
+    // to the user.
+    int (*read)(const struct lfs_config *c, lfs_block_t block,
+            lfs_off_t off, void *buffer, lfs_size_t size);
+
+    // Program a region in a block. The block must have previously
+    // been erased. Negative error codes are propogated to the user.
+    // May return LFS_ERR_CORRUPT if the block should be considered bad.
+    int (*prog)(const struct lfs_config *c, lfs_block_t block,
+            lfs_off_t off, const void *buffer, lfs_size_t size);
+
+    // Erase a block. A block must be erased before being programmed.
+    // The state of an erased block is undefined. Negative error codes
+    // are propogated to the user.
+    // May return LFS_ERR_CORRUPT if the block should be considered bad.
+    int (*erase)(const struct lfs_config *c, lfs_block_t block);
+
+    // Sync the state of the underlying block device. Negative error codes
+    // are propogated to the user.
+    int (*sync)(const struct lfs_config *c);
+
+    // Minimum size of a block read. All read operations will be a
+    // multiple of this value.
+    lfs_size_t read_size;
+
+    // Minimum size of a block program. All program operations will be a
+    // multiple of this value.
+    lfs_size_t prog_size;
+
+    // Size of an erasable block. This does not impact ram consumption and
+    // may be larger than the physical erase size. However, non-inlined files
+    // take up at minimum one block. Must be a multiple of the read
+    // and program sizes.
+    lfs_size_t block_size;
+
+    // Number of erasable blocks on the device.
+    lfs_size_t block_count;
+
+    // Number of erase cycles before littlefs evicts metadata logs and moves 
+    // the metadata to another block. Suggested values are in the
+    // range 100-1000, with large values having better performance at the cost
+    // of less consistent wear distribution.
+    //
+    // Set to -1 to disable block-level wear-leveling.
+    int32_t block_cycles;
+
+    // Size of block caches. Each cache buffers a portion of a block in RAM.
+    // The littlefs needs a read cache, a program cache, and one additional
+    // cache per file. Larger caches can improve performance by storing more
+    // data and reducing the number of disk accesses. Must be a multiple of
+    // the read and program sizes, and a factor of the block size.
+    lfs_size_t cache_size;
+
+    // Size of the lookahead buffer in bytes. A larger lookahead buffer
+    // increases the number of blocks found during an allocation pass. The
+    // lookahead buffer is stored as a compact bitmap, so each byte of RAM
+    // can track 8 blocks. Must be a multiple of 8.
+    lfs_size_t lookahead_size;
+
+    // Optional statically allocated read buffer. Must be cache_size.
+    // By default lfs_malloc is used to allocate this buffer.
+    void *read_buffer;
+
+    // Optional statically allocated program buffer. Must be cache_size.
+    // By default lfs_malloc is used to allocate this buffer.
+    void *prog_buffer;
+
+    // Optional statically allocated lookahead buffer. Must be lookahead_size
+    // and aligned to a 32-bit boundary. By default lfs_malloc is used to
+    // allocate this buffer.
+    void *lookahead_buffer;
+
+    // Optional upper limit on length of file names in bytes. No downside for
+    // larger names except the size of the info struct which is controlled by
+    // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in
+    // superblock and must be respected by other littlefs drivers.
+    lfs_size_t name_max;
+
+    // Optional upper limit on files in bytes. No downside for larger files
+    // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored
+    // in superblock and must be respected by other littlefs drivers.
+    lfs_size_t file_max;
+
+    // Optional upper limit on custom attributes in bytes. No downside for
+    // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to
+    // LFS_ATTR_MAX when zero.
+    lfs_size_t attr_max;
+};
+
+// File info structure
+struct lfs_info {
+    // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
+    uint8_t type;
+
+    // Size of the file, only valid for REG files. Limited to 32-bits.
+    lfs_size_t size;
+
+    // Name of the file stored as a null-terminated string. Limited to
+    // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to
+    // reduce RAM. LFS_NAME_MAX is stored in superblock and must be
+    // respected by other littlefs drivers.
+    char name[LFS_NAME_MAX+1];
+};
+
+// Custom attribute structure, used to describe custom attributes
+// committed atomically during file writes.
+struct lfs_attr {
+    // 8-bit type of attribute, provided by user and used to
+    // identify the attribute
+    uint8_t type;
+
+    // Pointer to buffer containing the attribute
+    void *buffer;
+
+    // Size of attribute in bytes, limited to LFS_ATTR_MAX
+    lfs_size_t size;
+};
+
+// Optional configuration provided during lfs_file_opencfg
+struct lfs_file_config {
+    // Optional statically allocated file buffer. Must be cache_size.
+    // By default lfs_malloc is used to allocate this buffer.
+    void *buffer;
+
+    // Optional list of custom attributes related to the file. If the file
+    // is opened with read access, these attributes will be read from disk
+    // during the open call. If the file is opened with write access, the
+    // attributes will be written to disk every file sync or close. This
+    // write occurs atomically with update to the file's contents.
+    //
+    // Custom attributes are uniquely identified by an 8-bit type and limited
+    // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller
+    // than the buffer, it will be padded with zeros. If the stored attribute
+    // is larger, then it will be silently truncated. If the attribute is not
+    // found, it will be created implicitly.
+    struct lfs_attr *attrs;
+
+    // Number of custom attributes in the list
+    lfs_size_t attr_count;
+};
+
+
+/// internal littlefs data structures ///
+typedef struct lfs_cache {
+    lfs_block_t block;
+    lfs_off_t off;
+    lfs_size_t size;
+    uint8_t *buffer;
+} lfs_cache_t;
+
+typedef struct lfs_mdir {
+    lfs_block_t pair[2];
+    uint32_t rev;
+    lfs_off_t off;
+    uint32_t etag;
+    uint16_t count;
+    bool erased;
+    bool split;
+    lfs_block_t tail[2];
+} lfs_mdir_t;
+
+// littlefs directory type
+typedef struct lfs_dir {
+    struct lfs_dir *next;
+    uint16_t id;
+    uint8_t type;
+    lfs_mdir_t m;
+
+    lfs_off_t pos;
+    lfs_block_t head[2];
+} lfs_dir_t;
+
+// littlefs file type
+typedef struct lfs_file {
+    struct lfs_file *next;
+    uint16_t id;
+    uint8_t type;
+    lfs_mdir_t m;
+
+    struct lfs_ctz {
+        lfs_block_t head;
+        lfs_size_t size;
+    } ctz;
+
+    uint32_t flags;
+    lfs_off_t pos;
+    lfs_block_t block;
+    lfs_off_t off;
+    lfs_cache_t cache;
+
+    const struct lfs_file_config *cfg;
+} lfs_file_t;
+
+typedef struct lfs_superblock {
+    uint32_t version;
+    lfs_size_t block_size;
+    lfs_size_t block_count;
+    lfs_size_t name_max;
+    lfs_size_t file_max;
+    lfs_size_t attr_max;
+} lfs_superblock_t;
+
+// The littlefs filesystem type
+typedef struct lfs {
+    lfs_cache_t rcache;
+    lfs_cache_t pcache;
+
+    lfs_block_t root[2];
+    struct lfs_mlist {
+        struct lfs_mlist *next;
+        uint16_t id;
+        uint8_t type;
+        lfs_mdir_t m;
+    } *mlist;
+    uint32_t seed;
+
+    struct lfs_gstate {
+        uint32_t tag;
+        lfs_block_t pair[2];
+    } gstate, gpending, gdelta;
+
+    struct lfs_free {
+        lfs_block_t off;
+        lfs_block_t size;
+        lfs_block_t i;
+        lfs_block_t ack;
+        uint32_t *buffer;
+    } free;
+
+    const struct lfs_config *cfg;
+    lfs_size_t name_max;
+    lfs_size_t file_max;
+    lfs_size_t attr_max;
+
+#ifdef LFS_MIGRATE
+    struct lfs1 *lfs1;
+#endif
+} lfs_t;
+
+
+/// Filesystem functions ///
+
+// Format a block device with the littlefs
+//
+// Requires a littlefs object and config struct. This clobbers the littlefs
+// object, and does not leave the filesystem mounted. The config struct must
+// be zeroed for defaults and backwards compatibility.
+//
+// Returns a negative error code on failure.
+int lfs_format(lfs_t *lfs, const struct lfs_config *config);
+
+// Mounts a littlefs
+//
+// Requires a littlefs object and config struct. Multiple filesystems
+// may be mounted simultaneously with multiple littlefs objects. Both
+// lfs and config must be allocated while mounted. The config struct must
+// be zeroed for defaults and backwards compatibility.
+//
+// Returns a negative error code on failure.
+int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
+
+// Unmounts a littlefs
+//
+// Does nothing besides releasing any allocated resources.
+// Returns a negative error code on failure.
+int lfs_unmount(lfs_t *lfs);
+
+/// General operations ///
+
+// Removes a file or directory
+//
+// If removing a directory, the directory must be empty.
+// Returns a negative error code on failure.
+int lfs_remove(lfs_t *lfs, const char *path);
+
+// Rename or move a file or directory
+//
+// If the destination exists, it must match the source in type.
+// If the destination is a directory, the directory must be empty.
+//
+// Returns a negative error code on failure.
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
+
+// Find info about a file or directory
+//
+// Fills out the info structure, based on the specified file or directory.
+// Returns a negative error code on failure.
+int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
+
+// Get a custom attribute
+//
+// Custom attributes are uniquely identified by an 8-bit type and limited
+// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than
+// the buffer, it will be padded with zeros. If the stored attribute is larger,
+// then it will be silently truncated. If no attribute is found, the error
+// LFS_ERR_NOATTR is returned and the buffer is filled with zeros.
+//
+// Returns the size of the attribute, or a negative error code on failure.
+// Note, the returned size is the size of the attribute on disk, irrespective
+// of the size of the buffer. This can be used to dynamically allocate a buffer
+// or check for existance.
+lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
+        uint8_t type, void *buffer, lfs_size_t size);
+
+// Set custom attributes
+//
+// Custom attributes are uniquely identified by an 8-bit type and limited
+// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be
+// implicitly created.
+//
+// Returns a negative error code on failure.
+int lfs_setattr(lfs_t *lfs, const char *path,
+        uint8_t type, const void *buffer, lfs_size_t size);
+
+// Removes a custom attribute
+//
+// If an attribute is not found, nothing happens.
+//
+// Returns a negative error code on failure.
+int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type);
+
+
+/// File operations ///
+
+// Open a file
+//
+// The mode that the file is opened in is determined by the flags, which
+// are values from the enum lfs_open_flags that are bitwise-ored together.
+//
+// Returns a negative error code on failure.
+int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags);
+
+// Open a file with extra configuration
+//
+// The mode that the file is opened in is determined by the flags, which
+// are values from the enum lfs_open_flags that are bitwise-ored together.
+//
+// The config struct provides additional config options per file as described
+// above. The config struct must be allocated while the file is open, and the
+// config struct must be zeroed for defaults and backwards compatibility.
+//
+// Returns a negative error code on failure.
+int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags,
+        const struct lfs_file_config *config);
+
+// Close a file
+//
+// Any pending writes are written out to storage as though
+// sync had been called and releases any allocated resources.
+//
+// Returns a negative error code on failure.
+int lfs_file_close(lfs_t *lfs, lfs_file_t *file);
+
+// Synchronize a file on storage
+//
+// Any pending writes are written out to storage.
+// Returns a negative error code on failure.
+int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
+
+// Read data from file
+//
+// Takes a buffer and size indicating where to store the read data.
+// Returns the number of bytes read, or a negative error code on failure.
+lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
+        void *buffer, lfs_size_t size);
+
+// Write data to file
+//
+// Takes a buffer and size indicating the data to write. The file will not
+// actually be updated on the storage until either sync or close is called.
+//
+// Returns the number of bytes written, or a negative error code on failure.
+lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
+        const void *buffer, lfs_size_t size);
+
+// Change the position of the file
+//
+// The change in position is determined by the offset and whence flag.
+// Returns the new position of the file, or a negative error code on failure.
+lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
+        lfs_soff_t off, int whence);
+
+// Truncates the size of the file to the specified size
+//
+// Returns a negative error code on failure.
+int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
+
+// Return the position of the file
+//
+// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
+// Returns the position of the file, or a negative error code on failure.
+lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file);
+
+// Change the position of the file to the beginning of the file
+//
+// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET)
+// Returns a negative error code on failure.
+int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file);
+
+// Return the size of the file
+//
+// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END)
+// Returns the size of the file, or a negative error code on failure.
+lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file);
+
+
+/// Directory operations ///
+
+// Create a directory
+//
+// Returns a negative error code on failure.
+int lfs_mkdir(lfs_t *lfs, const char *path);
+
+// Open a directory
+//
+// Once open a directory can be used with read to iterate over files.
+// Returns a negative error code on failure.
+int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path);
+
+// Close a directory
+//
+// Releases any allocated resources.
+// Returns a negative error code on failure.
+int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir);
+
+// Read an entry in the directory
+//
+// Fills out the info structure, based on the specified file or directory.
+// Returns a positive value on success, 0 at the end of directory,
+// or a negative error code on failure.
+int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);
+
+// Change the position of the directory
+//
+// The new off must be a value previous returned from tell and specifies
+// an absolute offset in the directory seek.
+//
+// Returns a negative error code on failure.
+int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off);
+
+// Return the position of the directory
+//
+// The returned offset is only meant to be consumed by seek and may not make
+// sense, but does indicate the current position in the directory iteration.
+//
+// Returns the position of the directory, or a negative error code on failure.
+lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);
+
+// Change the position of the directory to the beginning of the directory
+//
+// Returns a negative error code on failure.
+int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
+
+
+/// Filesystem-level filesystem operations
+
+// Finds the current size of the filesystem
+//
+// Note: Result is best effort. If files share COW structures, the returned
+// size may be larger than the filesystem actually is.
+//
+// Returns the number of allocated blocks, or a negative error code on failure.
+lfs_ssize_t lfs_fs_size(lfs_t *lfs);
+
+// Traverse through all blocks in use by the filesystem
+//
+// The provided callback will be called with each block address that is
+// currently in use by the filesystem. This can be used to determine which
+// blocks are in use or how much of the storage is available.
+//
+// Returns a negative error code on failure.
+int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
+
+#ifdef LFS_MIGRATE
+// Attempts to migrate a previous version of littlefs
+//
+// Behaves similarly to the lfs_format function. Attempts to mount
+// the previous version of littlefs and update the filesystem so it can be
+// mounted with the current version of littlefs.
+//
+// Requires a littlefs object and config struct. This clobbers the littlefs
+// object, and does not leave the filesystem mounted. The config struct must
+// be zeroed for defaults and backwards compatibility.
+//
+// Returns a negative error code on failure.
+int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg);
+#endif
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/src/bach/build.bach/tools/mklfs/src/lfs/lfs_util.c b/src/bach/build.bach/tools/mklfs/src/lfs/lfs_util.c
new file mode 100644
index 0000000..0b60e3b
--- /dev/null
+++ b/src/bach/build.bach/tools/mklfs/src/lfs/lfs_util.c
@@ -0,0 +1,33 @@
+/*
+ * lfs util functions
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include "lfs_util.h"
+
+// Only compile if user does not provide custom config
+#ifndef LFS_CONFIG
+
+
+// Software CRC implementation with small lookup table
+uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
+    static const uint32_t rtable[16] = {
+        0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
+        0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+        0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+        0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
+    };
+
+    const uint8_t *data = buffer;
+
+    for (size_t i = 0; i < size; i++) {
+        crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf];
+        crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf];
+    }
+
+    return crc;
+}
+
+
+#endif
diff --git a/src/bach/build.bach/tools/mklfs/src/lfs/lfs_util.h b/src/bach/build.bach/tools/mklfs/src/lfs/lfs_util.h
new file mode 100644
index 0000000..aaeb929
--- /dev/null
+++ b/src/bach/build.bach/tools/mklfs/src/lfs/lfs_util.h
@@ -0,0 +1,267 @@
+/*
+ * lfs utility functions
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef LFS_UTIL_H
+#define LFS_UTIL_H
+
+// Users can override lfs_util.h with their own configuration by defining
+// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
+//
+// If LFS_CONFIG is used, none of the default utils will be emitted and must be
+// provided by the config file. To start, I would suggest copying lfs_util.h
+// and modifying as needed.
+#ifdef LFS_CONFIG
+#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
+#define LFS_STRINGIZE2(x) #x
+#include LFS_STRINGIZE(LFS_CONFIG)
+#else
+
+// System includes
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+
+#ifndef LFS_NO_MALLOC
+#include <stdlib.h>
+#endif
+#ifndef LFS_NO_ASSERT
+#ifdef __ZEPHYR__
+#include <sys/__assert.h>
+#else  /* __ZEPHYR__ */
+#include <assert.h>
+#endif  /* __ZEPHYR__ */
+#endif
+
+#if !defined(LFS_NO_DEBUG) || \
+        !defined(LFS_NO_WARN) || \
+        !defined(LFS_NO_ERROR) || \
+        defined(LFS_YES_TRACE)
+
+#ifdef __ZEPHYR__
+#include <logging/log.h>
+
+#ifdef LFS_LOG_REGISTER
+LOG_MODULE_REGISTER(littlefs, CONFIG_FS_LOG_LEVEL);
+#else
+LOG_MODULE_DECLARE(littlefs);
+#endif
+
+#endif  /* __ZEPHYR__ */
+
+#include <stdio.h>
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+// Macros, may be replaced by system specific wrappers. Arguments to these
+// macros must not have side-effects as the macros can be removed for a smaller
+// code footprint
+
+// Logging functions
+#ifdef LFS_YES_TRACE
+#ifdef __ZEPHYR__
+#define LFS_TRACE(fmt, ...) LOG_DBG("trace:%d: " fmt, __LINE__, __VA_ARGS__)
+#else /* __ZEPHYR__ */
+#define LFS_TRACE(fmt, ...) \
+    printf("lfs_trace:%d: " fmt "\n", __LINE__, __VA_ARGS__)
+#endif /* __ZEPHYR__ */
+#else
+#define LFS_TRACE(fmt, ...)
+#endif
+
+#ifndef LFS_NO_DEBUG
+#ifdef __ZEPHYR__
+#define LFS_DEBUG(fmt, ...) LOG_DBG(fmt, __VA_ARGS__)
+#else /* __ZEPHYR__ */
+#define LFS_DEBUG(fmt, ...) \
+    printf("lfs_debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
+#endif /* __ZEPHYR__ */
+#else
+#define LFS_DEBUG(fmt, ...)
+#endif
+
+#ifndef LFS_NO_WARN
+#ifdef __ZEPHYR__
+#define LFS_WARN(fmt, ...) LOG_WRN(fmt, __VA_ARGS__)
+#else /* __ZEPHYR__ */
+#define LFS_WARN(fmt, ...) \
+    printf("lfs_warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
+#endif /* __ZEPHYR__ */
+#else
+#define LFS_WARN(fmt, ...)
+#endif
+
+#ifndef LFS_NO_ERROR
+#ifdef __ZEPHYR__
+#define LFS_ERROR(fmt, ...) LOG_ERR(fmt, __VA_ARGS__)
+#else /* __ZEPHYR__ */
+#define LFS_ERROR(fmt, ...) \
+    printf("lfs_error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
+#endif /* __ZEPHYR__ */
+#else
+#define LFS_ERROR(fmt, ...)
+#endif
+
+// Runtime assertions
+#ifndef LFS_NO_ASSERT
+#ifdef __ZEPHYR__
+#define LFS_ASSERT(test) __ASSERT_NO_MSG(test)
+#else /* __ZEPHYR__ */
+#define LFS_ASSERT(test) assert(test)
+#endif /* __ZEPHYR__ */
+#else
+#define LFS_ASSERT(test)
+#endif
+
+
+// Builtin functions, these may be replaced by more efficient
+// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
+// expensive basic C implementation for debugging purposes
+
+// Min/max functions for unsigned 32-bit numbers
+static inline uint32_t lfs_max(uint32_t a, uint32_t b) {
+    return (a > b) ? a : b;
+}
+
+static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
+    return (a < b) ? a : b;
+}
+
+// Align to nearest multiple of a size
+static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) {
+    return a - (a % alignment);
+}
+
+static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) {
+    return lfs_aligndown(a + alignment-1, alignment);
+}
+
+// Find the next smallest power of 2 less than or equal to a
+static inline uint32_t lfs_npw2(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
+    return 32 - __builtin_clz(a-1);
+#else
+    uint32_t r = 0;
+    uint32_t s;
+    a -= 1;
+    s = (a > 0xffff) << 4; a >>= s; r |= s;
+    s = (a > 0xff  ) << 3; a >>= s; r |= s;
+    s = (a > 0xf   ) << 2; a >>= s; r |= s;
+    s = (a > 0x3   ) << 1; a >>= s; r |= s;
+    return (r | (a >> 1)) + 1;
+#endif
+}
+
+// Count the number of trailing binary zeros in a
+// lfs_ctz(0) may be undefined
+static inline uint32_t lfs_ctz(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__)
+    return __builtin_ctz(a);
+#else
+    return lfs_npw2((a & -a) + 1) - 1;
+#endif
+}
+
+// Count the number of binary ones in a
+static inline uint32_t lfs_popc(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
+    return __builtin_popcount(a);
+#else
+    a = a - ((a >> 1) & 0x55555555);
+    a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
+    return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
+#endif
+}
+
+// Find the sequence comparison of a and b, this is the distance
+// between a and b ignoring overflow
+static inline int lfs_scmp(uint32_t a, uint32_t b) {
+    return (int)(unsigned)(a - b);
+}
+
+// Convert between 32-bit little-endian and native order
+static inline uint32_t lfs_fromle32(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && ( \
+    (defined(  BYTE_ORDER  ) && defined(  ORDER_LITTLE_ENDIAN  ) &&   BYTE_ORDER   ==   ORDER_LITTLE_ENDIAN  ) || \
+    (defined(__BYTE_ORDER  ) && defined(__ORDER_LITTLE_ENDIAN  ) && __BYTE_ORDER   == __ORDER_LITTLE_ENDIAN  ) || \
+    (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+    return a;
+#elif !defined(LFS_NO_INTRINSICS) && ( \
+    (defined(  BYTE_ORDER  ) && defined(  ORDER_BIG_ENDIAN  ) &&   BYTE_ORDER   ==   ORDER_BIG_ENDIAN  ) || \
+    (defined(__BYTE_ORDER  ) && defined(__ORDER_BIG_ENDIAN  ) && __BYTE_ORDER   == __ORDER_BIG_ENDIAN  ) || \
+    (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
+    return __builtin_bswap32(a);
+#else
+    return (((uint8_t*)&a)[0] <<  0) |
+           (((uint8_t*)&a)[1] <<  8) |
+           (((uint8_t*)&a)[2] << 16) |
+           (((uint8_t*)&a)[3] << 24);
+#endif
+}
+
+static inline uint32_t lfs_tole32(uint32_t a) {
+    return lfs_fromle32(a);
+}
+
+// Convert between 32-bit big-endian and native order
+static inline uint32_t lfs_frombe32(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && ( \
+    (defined(  BYTE_ORDER  ) && defined(  ORDER_LITTLE_ENDIAN  ) &&   BYTE_ORDER   ==   ORDER_LITTLE_ENDIAN  ) || \
+    (defined(__BYTE_ORDER  ) && defined(__ORDER_LITTLE_ENDIAN  ) && __BYTE_ORDER   == __ORDER_LITTLE_ENDIAN  ) || \
+    (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+    return __builtin_bswap32(a);
+#elif !defined(LFS_NO_INTRINSICS) && ( \
+    (defined(  BYTE_ORDER  ) && defined(  ORDER_BIG_ENDIAN  ) &&   BYTE_ORDER   ==   ORDER_BIG_ENDIAN  ) || \
+    (defined(__BYTE_ORDER  ) && defined(__ORDER_BIG_ENDIAN  ) && __BYTE_ORDER   == __ORDER_BIG_ENDIAN  ) || \
+    (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
+    return a;
+#else
+    return (((uint8_t*)&a)[0] << 24) |
+           (((uint8_t*)&a)[1] << 16) |
+           (((uint8_t*)&a)[2] <<  8) |
+           (((uint8_t*)&a)[3] <<  0);
+#endif
+}
+
+static inline uint32_t lfs_tobe32(uint32_t a) {
+    return lfs_frombe32(a);
+}
+
+// Calculate CRC-32 with polynomial = 0x04c11db7
+uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size);
+
+// Allocate memory, only used if buffers are not provided to littlefs
+// Note, memory must be 64-bit aligned
+static inline void *lfs_malloc(size_t size) {
+#ifndef LFS_NO_MALLOC
+    return malloc(size);
+#else
+    (void)size;
+    return NULL;
+#endif
+}
+
+// Deallocate memory, only used if buffers are not provided to littlefs
+static inline void lfs_free(void *p) {
+#ifndef LFS_NO_MALLOC
+    free(p);
+#else
+    (void)p;
+#endif
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
+#endif
diff --git a/src/bach/build.bach/tools/mklfs/src/mklfs.c b/src/bach/build.bach/tools/mklfs/src/mklfs.c
new file mode 100644
index 0000000..dbabdfe
--- /dev/null
+++ b/src/bach/build.bach/tools/mklfs/src/mklfs.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *     * The WHITECAT logotype cannot be changed, you can remove it, but you
+ *       cannot change it in any way. The WHITECAT logotype is:
+ *
+ *          /\       /\
+ *         /  \_____/  \
+ *        /_____________\
+ *        W H I T E C A T
+ *
+ *     * Redistributions in binary form must retain all copyright notices printed
+ *       to any local or remote output device. This include any reference to
+ *       Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ *       appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, a tool for make a LFS file system image
+ *
+ */
+
+#include "lfs/lfs.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/types.h>
+
+static struct lfs_config cfg;
+static lfs_t lfs;
+static uint8_t *data;
+
+static int lfs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) {
+    memcpy(buffer, data + (block * c->block_size) + off, size);
+    return 0;
+}
+
+static int lfs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) {
+	memcpy(data + (block * c->block_size) + off, buffer, size);
+    return 0;
+}
+
+static int lfs_erase(const struct lfs_config *c, lfs_block_t block) {
+    memset(data + (block * c->block_size), 0, c->block_size);
+    return 0;
+}
+
+static int lfs_sync(const struct lfs_config *c) {
+	return 0;
+}
+
+static void create_dir(char *src) {
+    char *path;
+    int ret;
+
+    path = strchr(src, '/');
+    if (path) {
+        fprintf(stdout, "%s\r\n", path);
+
+		if ((ret = lfs_mkdir(&lfs, path)) < 0) {
+			fprintf(stderr,"can't create directory %s: error=%d\r\n", path, ret);
+			exit(1);
+		}
+	}
+}
+
+static void create_file(char *src) {
+    char *path;
+    int ret;
+
+    path = strchr(src, '/');
+    if (path) {
+        fprintf(stdout, "%s\r\n", path);
+
+        // Open source file
+        FILE *srcf = fopen(src,"rb");
+        if (!srcf) {
+            fprintf(stderr,"can't open source file %s: errno=%d (%s)\r\n", src, errno, strerror(errno));
+            exit(1);
+        }
+
+        // Open destination file
+        lfs_file_t dstf;
+        if ((ret = lfs_file_open(&lfs, &dstf, path, LFS_O_WRONLY | LFS_O_CREAT)) < 0) {
+            fprintf(stderr,"can't open destination file %s: error=%d\r\n", path, ret);
+            exit(1);
+        }
+
+		char c = fgetc(srcf);
+		while (!feof(srcf)) {
+			ret = lfs_file_write(&lfs, &dstf, &c, 1);
+			if (ret < 0) {
+				fprintf(stderr,"can't write to destination file %s: error=%d\r\n", path, ret);
+				exit(1);
+			}
+			c = fgetc(srcf);
+		}
+
+        // Close destination file
+		ret = lfs_file_close(&lfs, &dstf);
+		if (ret < 0) {
+			fprintf(stderr,"can't close destination file %s: error=%d\r\n", path, ret);
+			exit(1);
+		}
+
+        // Close source file
+        fclose(srcf);
+    }
+}
+
+static void compact(char *src) {
+    DIR *dir;
+    struct dirent *ent;
+    char curr_path[PATH_MAX];
+
+    dir = opendir(src);
+    if (dir) {
+        while ((ent = readdir(dir))) {
+            // Skip . and .. directories
+            if ((strcmp(ent->d_name,".") != 0) && (strcmp(ent->d_name,"..") != 0)) {
+                // Update the current path
+                strcpy(curr_path, src);
+                strcat(curr_path, "/");
+                strcat(curr_path, ent->d_name);
+
+                if (ent->d_type == DT_DIR) {
+                    create_dir(curr_path);
+                    compact(curr_path);
+                } else if (ent->d_type == DT_REG) {
+                    create_file(curr_path);
+                }
+            }
+        }
+
+        closedir(dir);
+    }
+}
+
+void usage() {
+	fprintf(stdout, "usage: mklfs -c <pack-dir> -y <block-cycle> -b <block-size> -r <read-size> -p <prog-size> -s <filesystem-size> -i <image-file-path>\r\n");
+}
+
+static int is_number(const char *s) {
+	const char *c = s;
+
+	while (*c) {
+		if ((*c < '0') || (*c > '9')) {
+			return 0;
+		}
+		c++;
+	}
+
+	return 1;
+}
+
+static int is_hex(const char *s) {
+	const char *c = s;
+
+	if (*c++ != '0') {
+		return 0;
+	}
+
+	if (*c++ != 'x') {
+		return 0;
+	}
+
+	while (*c) {
+		if (((*c < '0') || (*c > '9')) && ((*c < 'A') || (*c > 'F')) && ((*c < 'a') || (*c > 'f'))) {
+			return 0;
+		}
+		c++;
+	}
+
+	return 1;
+}
+
+static int to_int(const char *s) {
+	if (is_number(s)) {
+		return atoi(s);
+	} else if (is_hex(s)) {
+		return (int)strtol(s, NULL, 16);
+	}
+
+	return -1;
+}
+
+int main(int argc, char **argv) {
+    char *src = NULL;   // Source directory
+    char *dst = NULL;   // Destination image
+    int c;              // Current option
+    int block_size = 0; // Block size
+    int read_size = 0;  // Read size
+    int prog_size = 0;  // Prog size
+    int fs_size = 0;    // File system size
+    int block_cyc = 512;  // Block cycles
+
+    int err;
+
+	while ((c = getopt(argc, argv, "y:c:i:b:p:r:s:")) != -1) {
+		switch (c) {
+		case 'c':
+			src = optarg;
+			break;
+
+		case 'i':
+			dst = optarg;
+			break;
+
+		case 'y':
+			block_cyc = to_int(optarg);
+			break;
+
+		case 'b':
+			block_size = to_int(optarg);
+			break;
+
+		case 'p':
+			prog_size = to_int(optarg);
+			break;
+
+		case 'r':
+			read_size = to_int(optarg);
+			break;
+
+		case 's':
+			fs_size = to_int(optarg);
+			break;
+		}
+	}
+
+    if ((src == NULL) || (dst == NULL) || (block_size <= 0) || (prog_size <= 0) ||
+        (read_size <= 0) || (fs_size <= 0)) {
+    		usage();
+        exit(1);
+    }
+
+    // Mount the file system
+    cfg.read  = lfs_read;
+    cfg.prog  = lfs_prog;
+    cfg.erase = lfs_erase;
+    cfg.sync  = lfs_sync;
+
+    cfg.read_size      = read_size;
+    cfg.prog_size      = prog_size;
+    cfg.cache_size     = prog_size;
+    cfg.lookahead_size = prog_size;
+    cfg.block_cycles   = block_cyc;
+    cfg.block_size     = block_size;
+    cfg.block_count    = fs_size / cfg.block_size;
+    cfg.context        = NULL;
+
+	data = calloc(1, fs_size);
+	if (!data) {
+		fprintf(stderr, "no memory for mount\r\n");
+		return -1;
+	}
+
+	err = lfs_format(&lfs, &cfg);
+	if (err < 0) {
+		fprintf(stderr, "format error: error=%d\r\n", err);
+		return -1;
+	}
+
+	err = lfs_mount(&lfs, &cfg);
+	if (err < 0) {
+		fprintf(stderr, "mount error: error=%d\r\n", err);
+		return -1;
+	}
+
+	compact(src);
+
+	FILE *img = fopen(dst, "wb+");
+
+	if (!img) {
+		fprintf(stderr, "can't create image file: errno=%d (%s)\r\n", errno, strerror(errno));
+		return -1;
+	}
+
+	fwrite(data, 1, fs_size, img);
+
+	fclose(img);
+
+	return 0;
+}
diff --git a/src/bach/build.bach/tools/mksquashfs b/src/bach/build.bach/tools/mksquashfs
new file mode 100755
index 0000000..4f70505
--- /dev/null
+++ b/src/bach/build.bach/tools/mksquashfs
Binary files differ
diff --git a/src/bach/build.bach/tools/mkyaffs2 b/src/bach/build.bach/tools/mkyaffs2
new file mode 100755
index 0000000..65bb896
--- /dev/null
+++ b/src/bach/build.bach/tools/mkyaffs2
Binary files differ
diff --git a/src/bach/build.bach/tools/mkyaffs2image b/src/bach/build.bach/tools/mkyaffs2image
new file mode 100755
index 0000000..68eff1e
--- /dev/null
+++ b/src/bach/build.bach/tools/mkyaffs2image
Binary files differ
diff --git a/src/bach/build.bach/tools/mkyaffs2image4k b/src/bach/build.bach/tools/mkyaffs2image4k
new file mode 100755
index 0000000..2086c37
--- /dev/null
+++ b/src/bach/build.bach/tools/mkyaffs2image4k
Binary files differ
diff --git a/src/bach/build.bach/tools/pack_image.sh b/src/bach/build.bach/tools/pack_image.sh
new file mode 100755
index 0000000..341c3d8
--- /dev/null
+++ b/src/bach/build.bach/tools/pack_image.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+OUTDIR=`pwd`
+TARDIR=$OUTDIR
+
+
+if [ -d "$TARDIR" ]; then
+
+	#Create directories and move images into them.
+	mkdir -p $OUTDIR/modem1_image && cp -af  $1  $OUTDIR/modem1_image
+	if echo "$2" | grep -q "modemc2k"; then
+		mkdir -p $OUTDIR/modem3_image && cp -af  $2  $OUTDIR/modem3_image/
+		mkdir -p $OUTDIR/dsp_bin &&  cp -af  $3  $OUTDIR/dsp_bin/
+	else
+		mkdir -p $OUTDIR/dsp_bin &&  cp -af  $2  $OUTDIR/dsp_bin/
+	fi
+fi
+
+
+set -e
+$OUTDIR/tools/make_ext4fs -s -j 1 -l 14M $OUTDIR/modem_sparse.img    $OUTDIR/modem1_image 1> /dev/null 
+$OUTDIR/tools/simg2img $OUTDIR/modem_sparse.img  $TARDIR/modem.img 
+mv $TARDIR/modem.img $1
+
+if echo "$2" | grep -q "modemc2k"; then
+	$OUTDIR/tools/make_ext4fs -s -j 1 -l 8M $OUTDIR/modemc2k_sparse.img $OUTDIR/modem3_image  1> /dev/null
+	$OUTDIR/tools/simg2img $OUTDIR/modemc2k_sparse.img  $TARDIR/modemc2k.img
+	mv $TARDIR/modemc2k.img $2
+
+	$OUTDIR/tools/make_ext4fs -s -j 1 -l 2M  $OUTDIR/dsp_sparse.bin   $OUTDIR/dsp_bin  1> /dev/null
+	$OUTDIR/tools/simg2img $OUTDIR/dsp_sparse.bin  $TARDIR/dsp.bin 
+	mv $TARDIR/dsp.bin $3
+else
+	$OUTDIR/tools/make_ext4fs -s -j 1 -l 2M  $OUTDIR/dsp_sparse.bin    $OUTDIR/dsp_bin  1> /dev/null
+	$OUTDIR/tools/simg2img $OUTDIR/dsp_sparse.bin  $TARDIR/dsp.bin 
+	mv $TARDIR/dsp.bin $2
+fi
+set +e
+
+
+rm -rf  $OUTDIR/modem1_image     $OUTDIR/modem3_image        $OUTDIR/dsp_bin
+rm -f  $OUTDIR/modem_sparse.img $OUTDIR/modemc2k_sparse.img $OUTDIR/dsp_sparse.bin
+
+ 
diff --git a/src/bach/build.bach/tools/pack_one_image.sh b/src/bach/build.bach/tools/pack_one_image.sh
new file mode 100755
index 0000000..f81268c
--- /dev/null
+++ b/src/bach/build.bach/tools/pack_one_image.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+
+usage()
+{
+	printf "The right format is:\n"
+	printf "./pack_one_image.sh <PATH_TO_IMAGE_DIR> <PARTITION_SIZE> <EXT4_IMAGE_NAME> \n"
+	exit
+}
+
+
+
+OUTDIR=`pwd`
+if [ -d "$1" ]; then
+	TARDIR=$OUTDIR/out/
+	if [ $2 ] && [ $3 ]; then
+		set -e
+		$OUTDIR/tools/make_ext4fs -s -j 1 -l $2 $TARDIR/one_sparse.img  $1 1> /dev/null 
+		$OUTDIR/tools/simg2img $TARDIR/one_sparse.img  $TARDIR/$3 
+		set +e
+	else
+		usage
+	fi
+else
+	usage	
+fi
+
+
+rm -f  $TARDIR/one_sparse.img 
+
+
+
diff --git a/src/bach/build.bach/tools/pbp b/src/bach/build.bach/tools/pbp
new file mode 100755
index 0000000..2e2d3ad
--- /dev/null
+++ b/src/bach/build.bach/tools/pbp
Binary files differ
diff --git a/src/bach/build.bach/tools/perl/Crypt/RC4.pm b/src/bach/build.bach/tools/perl/Crypt/RC4.pm
new file mode 100644
index 0000000..acd27a0
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Crypt/RC4.pm
@@ -0,0 +1,165 @@
+#--------------------------------------------------------------------#
+# Crypt::RC4
+#       Date Written:   07-Jun-2000 04:15:55 PM
+#       Last Modified:  13-Dec-2001 03:33:49 PM 
+#       Author:         Kurt Kincaid (sifukurt@yahoo.com)
+#       Copyright (c) 2001, Kurt Kincaid
+#           All Rights Reserved.
+#
+#       This is free software and may be modified and/or
+#       redistributed under the same terms as Perl itself.
+#--------------------------------------------------------------------#
+
+package Crypt::RC4;
+
+use strict;
+use vars qw( $VERSION @ISA @EXPORT $MAX_CHUNK_SIZE );
+
+$MAX_CHUNK_SIZE = 1024 unless $MAX_CHUNK_SIZE;
+
+require Exporter;
+
+@ISA     = qw(Exporter);
+@EXPORT  = qw(RC4);
+$VERSION = '2.02';
+
+sub new {
+    my ( $class, $key )  = @_;
+    my $self = bless {}, $class;
+    $self->{state} = Setup( $key );
+    $self->{x} = 0;
+    $self->{y} = 0;
+    $self;
+}
+
+sub RC4 {
+    my $self;
+    my( @state, $x, $y );
+    if ( ref $_[0] ) {
+        $self = shift;
+    @state = @{ $self->{state} };
+    $x = $self->{x};
+    $y = $self->{y};
+    } else {
+        @state = Setup( shift );
+    $x = $y = 0;
+    }
+    my $message = shift;
+    my $num_pieces = do {
+    my $num = length($message) / $MAX_CHUNK_SIZE;
+    my $int = int $num;
+    $int == $num ? $int : $int+1;
+    };
+    for my $piece ( 0..$num_pieces - 1 ) {
+    my @message = unpack "C*", substr($message, $piece * $MAX_CHUNK_SIZE, $MAX_CHUNK_SIZE);
+    for ( @message ) {
+        $x = 0 if ++$x > 255;
+        $y -= 256 if ($y += $state[$x]) > 255;
+        @state[$x, $y] = @state[$y, $x];
+        $_ ^= $state[( $state[$x] + $state[$y] ) % 256];
+    }
+    substr($message, $piece * $MAX_CHUNK_SIZE, $MAX_CHUNK_SIZE) = pack "C*", @message;
+    }
+    if ($self) {
+    $self->{state} = \@state;
+    $self->{x} = $x;
+    $self->{y} = $y;
+    }
+    $message;
+}
+
+sub Setup {
+    my @k = unpack( 'C*', shift );
+    my @state = 0..255;
+    my $y = 0;
+    for my $x (0..255) {
+    $y = ( $k[$x % @k] + $state[$x] + $y ) % 256;
+    @state[$x, $y] = @state[$y, $x];
+    }
+    wantarray ? @state : \@state;
+}
+
+
+1;
+__END__
+
+=head1 NAME
+
+Crypt::RC4 - Perl implementation of the RC4 encryption algorithm
+
+=head1 SYNOPSIS
+
+# Functional Style
+  use Crypt::RC4;
+  $encrypted = RC4( $passphrase, $plaintext );
+  $decrypt = RC4( $passphrase, $encrypted );
+  
+# OO Style
+  use Crypt::RC4;
+  $ref = Crypt::RC4->new( $passphrase );
+  $encrypted = $ref->RC4( $plaintext );
+
+  $ref2 = Crypt::RC4->new( $passphrase );
+  $decrypted = $ref2->RC4( $encrypted );
+
+# process an entire file, one line at a time
+# (Warning: Encrypted file leaks line lengths.)
+  $ref3 = Crypt::RC4->new( $passphrase );
+  while (<FILE>) {
+      chomp;
+      print $ref3->RC4($_), "\n";
+  }
+
+=head1 DESCRIPTION
+
+A simple implementation of the RC4 algorithm, developed by RSA Security, Inc. Here is the description
+from RSA's website:
+
+RC4 is a stream cipher designed by Rivest for RSA Data Security (now RSA Security). It is a variable
+key-size stream cipher with byte-oriented operations. The algorithm is based on the use of a random
+permutation. Analysis shows that the period of the cipher is overwhelmingly likely to be greater than
+10100. Eight to sixteen machine operations are required per output byte, and the cipher can be
+expected to run very quickly in software. Independent analysts have scrutinized the algorithm and it
+is considered secure.
+
+Based substantially on the "RC4 in 3 lines of perl" found at http://www.cypherspace.org
+
+A major bug in v1.0 was fixed by David Hook (dgh@wumpus.com.au).  Thanks, David.
+
+=head1 AUTHOR
+
+Kurt Kincaid (sifukurt@yahoo.com)
+Ronald Rivest for RSA Security, Inc.
+
+=head1 BUGS
+
+Disclaimer: Strictly speaking, this module uses the "alleged" RC4
+algorithm. The Algorithm known as "RC4" is a trademark of RSA Security
+Inc., and this document makes no claims one way or another that this
+is the correct algorithm, and further, make no claims about the
+quality of the source code nor any licensing requirements for
+commercial use.
+
+There's nothing preventing you from using this module in an insecure
+way which leaks information. For example, encrypting multilple
+messages with the same passphrase may allow an attacker to decode all of
+them with little effort, even though they'll appear to be secured. If
+serious crypto is your goal, be careful. Be very careful.
+
+It's a pure-Perl implementation, so that rating of "Eight
+to sixteen machine operations" is good for nothing but a good laugh.
+If encryption and decryption are a bottleneck for you, please re-write
+this module to use native code wherever practical.
+
+=head1 LICENSE
+
+This is free software and may be modified and/or
+redistributed under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+L<perl>, L<http://www.cypherspace.org>, L<http://www.rsasecurity.com>, 
+L<http://www.achtung.com/crypto/rc4.html>, 
+L<http://www.columbia.edu/~ariel/ssleay/rrc4.html>
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Digest/Perl/MD5.pm b/src/bach/build.bach/tools/perl/Digest/Perl/MD5.pm
new file mode 100644
index 0000000..e81877c
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Digest/Perl/MD5.pm
@@ -0,0 +1,481 @@
+#! /usr/bin/false
+#
+# $Id: MD5.pm,v 1.23 2004/08/27 20:28:25 lackas Exp $
+#
+
+package Digest::Perl::MD5;
+use strict;
+use integer;
+use Exporter;
+use vars qw($VERSION @ISA @EXPORTER @EXPORT_OK);
+
+@EXPORT_OK = qw(md5 md5_hex md5_base64);
+
+@ISA = 'Exporter';
+$VERSION = '1.8';
+
+# I-Vektor
+sub A() { 0x67_45_23_01 }
+sub B() { 0xef_cd_ab_89 }
+sub C() { 0x98_ba_dc_fe }
+sub D() { 0x10_32_54_76 }
+
+# for internal use
+sub MAX() { 0xFFFFFFFF }
+
+# padd a message to a multiple of 64
+sub padding {
+    my $l = length (my $msg = shift() . chr(128));    
+    $msg .= "\0" x (($l%64<=56?56:120)-$l%64);
+    $l = ($l-1)*8;
+    $msg .= pack 'VV', $l & MAX , ($l >> 16 >> 16);
+}
+
+
+sub rotate_left($$) {
+	#$_[0] << $_[1] | $_[0] >> (32 - $_[1]);
+	#my $right = $_[0] >> (32 - $_[1]);
+	#my $rmask = (1 << $_[1]) - 1;
+	($_[0] << $_[1]) | (( $_[0] >> (32 - $_[1])  )  & ((1 << $_[1]) - 1));
+	#$_[0] << $_[1] | (($_[0]>> (32 - $_[1])) & (1 << (32 - $_[1])) - 1);
+}
+
+sub gen_code {
+  # Discard upper 32 bits on 64 bit archs.
+  my $MSK = ((1 << 16) << 16) ? ' & ' . MAX : '';
+#	FF => "X0=rotate_left(((X1&X2)|(~X1&X3))+X0+X4+X6$MSK,X5)+X1$MSK;",
+#	GG => "X0=rotate_left(((X1&X3)|(X2&(~X3)))+X0+X4+X6$MSK,X5)+X1$MSK;",
+  my %f = (
+	FF => "X0=rotate_left((X3^(X1&(X2^X3)))+X0+X4+X6$MSK,X5)+X1$MSK;",
+	GG => "X0=rotate_left((X2^(X3&(X1^X2)))+X0+X4+X6$MSK,X5)+X1$MSK;",
+	HH => "X0=rotate_left((X1^X2^X3)+X0+X4+X6$MSK,X5)+X1$MSK;",
+	II => "X0=rotate_left((X2^(X1|(~X3)))+X0+X4+X6$MSK,X5)+X1$MSK;",
+  );
+  #unless ( (1 << 16) << 16) { %f = %{$CODES{'32bit'}} }
+  #else { %f = %{$CODES{'64bit'}} }
+
+  my %s = (  # shift lengths
+	S11 => 7, S12 => 12, S13 => 17, S14 => 22, S21 => 5, S22 => 9, S23 => 14,
+	S24 => 20, S31 => 4, S32 => 11, S33 => 16, S34 => 23, S41 => 6, S42 => 10,
+	S43 => 15, S44 => 21
+  );
+
+  my $insert = "\n";
+  while(<DATA>) {
+	chomp;
+	next unless /^[FGHI]/;
+	my ($func,@x) = split /,/;
+	my $c = $f{$func};
+	$c =~ s/X(\d)/$x[$1]/g;
+	$c =~ s/(S\d{2})/$s{$1}/;
+	$c =~ s/^(.*)=rotate_left\((.*),(.*)\)\+(.*)$//;
+
+	my $su = 32 - $3;
+	my $sh = (1 << $3) - 1;
+
+	$c = "$1=(((\$r=$2)<<$3)|((\$r>>$su)&$sh))+$4";
+
+	#my $rotate = "(($2 << $3) || (($2 >> (32 - $3)) & (1 << $2) - 1)))"; 
+	# $c = "\$r = $2;
+	# $1 = ((\$r << $3) | ((\$r >> (32 - $3))  & ((1 << $3) - 1))) + $4";
+	$insert .= "\t$c\n";
+  }
+  close DATA;
+  
+  my $dump = '
+  sub round {
+	my ($a,$b,$c,$d) = @_[0 .. 3];
+	my $r;' . $insert . '
+	$_[0]+$a' . $MSK . ', $_[1]+$b ' . $MSK . 
+        ', $_[2]+$c' . $MSK . ', $_[3]+$d' . $MSK . ';
+  }';
+  eval $dump;
+  # print "$dump\n";
+  # exit 0;
+}
+
+gen_code();
+
+#########################################
+# Private output converter functions:
+sub _encode_hex { unpack 'H*', $_[0] }
+sub _encode_base64 {
+	my $res;
+	while ($_[0] =~ /(.{1,45})/gs) {
+		$res .= substr pack('u', $1), 1;
+		chop $res;
+	}
+	$res =~ tr|` -_|AA-Za-z0-9+/|;#`
+	chop $res; chop $res;
+	$res
+}
+
+#########################################
+# OOP interface:
+sub new {
+	my $proto = shift;
+	my $class = ref $proto || $proto;
+	my $self = {};
+	bless $self, $class;
+	$self->reset();
+	$self
+}
+
+sub reset {
+	my $self = shift;
+	delete $self->{_data};
+	$self->{_state} = [A,B,C,D];
+	$self->{_length} = 0;
+	$self
+}
+
+sub add {
+	my $self = shift;
+	$self->{_data} .= join '', @_ if @_;
+	my ($i,$c);
+	for $i (0 .. (length $self->{_data})/64-1) {
+		my @X = unpack 'V16', substr $self->{_data}, $i*64, 64;
+		@{$self->{_state}} = round(@{$self->{_state}},@X);
+		++$c;
+	}
+	if ($c) {
+		substr ($self->{_data}, 0, $c*64) = '';
+		$self->{_length} += $c*64;
+	}
+	$self
+}
+
+sub finalize {
+	my $self = shift;
+	$self->{_data} .= chr(128);
+    my $l = $self->{_length} + length $self->{_data};
+    $self->{_data} .= "\0" x (($l%64<=56?56:120)-$l%64);
+    $l = ($l-1)*8;
+    $self->{_data} .= pack 'VV', $l & MAX , ($l >> 16 >> 16);
+	$self->add();
+	$self
+}
+
+sub addfile {
+  	my ($self,$fh) = @_;
+	if (!ref($fh) && ref(\$fh) ne "GLOB") {
+	    require Symbol;
+	    $fh = Symbol::qualify($fh, scalar caller);
+	}
+	# $self->{_data} .= do{local$/;<$fh>};
+	my $read = 0;
+	my $buffer = '';
+	$self->add($buffer) while $read = read $fh, $buffer, 8192;
+	die __PACKAGE__, " read failed: $!" unless defined $read;
+	$self
+}
+
+sub add_bits {
+	my $self = shift;
+	return $self->add( pack 'B*', shift ) if @_ == 1;
+	my ($b,$n) = @_;
+	die __PACKAGE__, " Invalid number of bits\n" if $n%8;
+	$self->add( substr $b, 0, $n/8 )
+}
+
+sub digest {
+	my $self = shift;
+	$self->finalize();
+	my $res = pack 'V4', @{$self->{_state}};
+	$self->reset();
+	$res
+}
+
+sub hexdigest {
+	_encode_hex($_[0]->digest)
+}
+
+sub b64digest {
+	_encode_base64($_[0]->digest)
+}
+
+sub clone {
+	my $self = shift;
+	my $clone = { 
+		_state => [@{$self->{_state}}],
+		_length => $self->{_length},
+		_data => $self->{_data}
+	};
+	bless $clone, ref $self || $self;
+}
+
+#########################################
+# Procedural interface:
+sub md5 {
+	my $message = padding(join'',@_);
+	my ($a,$b,$c,$d) = (A,B,C,D);
+	my $i;
+	for $i (0 .. (length $message)/64-1) {
+		my @X = unpack 'V16', substr $message,$i*64,64;	
+		($a,$b,$c,$d) = round($a,$b,$c,$d,@X);
+	}
+	pack 'V4',$a,$b,$c,$d;
+}
+sub md5_hex { _encode_hex &md5 } 
+sub md5_base64 { _encode_base64 &md5 }
+
+
+1;
+
+=head1 NAME
+
+Digest::MD5::Perl - Perl implementation of Ron Rivests MD5 Algorithm
+
+=head1 DISCLAIMER
+
+This is B<not> an interface (like C<Digest::MD5>) but a Perl implementation of MD5.
+It is written in perl only and because of this it is slow but it works without C-Code.
+You should use C<Digest::MD5> instead of this module if it is available.
+This module is only usefull for
+
+=over 4
+
+=item
+
+computers where you cannot install C<Digest::MD5> (e.g. lack of a C-Compiler)
+
+=item
+
+encrypting only small amounts of data (less than one million bytes). I use it to
+hash passwords.
+
+=item
+
+educational purposes
+
+=back
+
+=head1 SYNOPSIS
+
+ # Functional style
+ use Digest::MD5  qw(md5 md5_hex md5_base64);
+
+ $hash = md5 $data;
+ $hash = md5_hex $data;
+ $hash = md5_base64 $data;
+    
+
+ # OO style
+ use Digest::MD5;
+
+ $ctx = Digest::MD5->new;
+
+ $ctx->add($data);
+ $ctx->addfile(*FILE);
+
+ $digest = $ctx->digest;
+ $digest = $ctx->hexdigest;
+ $digest = $ctx->b64digest;
+
+=head1 DESCRIPTION
+
+This modules has the same interface as the much faster C<Digest::MD5>. So you can
+easily exchange them, e.g.
+
+	BEGIN {
+	  eval {
+	    require Digest::MD5;
+	    import Digest::MD5 'md5_hex'
+	  };
+	  if ($@) { # ups, no Digest::MD5
+	    require Digest::Perl::MD5;
+	    import Digest::Perl::MD5 'md5_hex'
+	  }		
+	}
+
+If the C<Digest::MD5> module is available it is used and if not you take
+C<Digest::Perl::MD5>.
+
+You can also install the Perl part of Digest::MD5 together with Digest::Perl::MD5
+and use Digest::MD5 as normal, it falls back to Digest::Perl::MD5 if it
+cannot load its object files.
+
+For a detailed Documentation see the C<Digest::MD5> module.
+
+=head1 EXAMPLES
+
+The simplest way to use this library is to import the md5_hex()
+function (or one of its cousins):
+
+    use Digest::Perl::MD5 'md5_hex';
+    print 'Digest is ', md5_hex('foobarbaz'), "\n";
+
+The above example would print out the message
+
+    Digest is 6df23dc03f9b54cc38a0fc1483df6e21
+
+provided that the implementation is working correctly.  The same
+checksum can also be calculated in OO style:
+
+    use Digest::MD5;
+    
+    $md5 = Digest::MD5->new;
+    $md5->add('foo', 'bar');
+    $md5->add('baz');
+    $digest = $md5->hexdigest;
+    
+    print "Digest is $digest\n";
+
+The digest methods are destructive. That means you can only call them
+once and the $md5 objects is reset after use. You can make a copy with clone:
+
+	$md5->clone->hexdigest
+
+=head1 LIMITATIONS
+
+This implementation of the MD5 algorithm has some limitations:
+
+=over 4
+
+=item
+
+It's slow, very slow. I've done my very best but Digest::MD5 is still about 100 times faster.
+You can only encrypt Data up to one million bytes in an acceptable time. But it's very usefull
+for encrypting small amounts of data like passwords.
+
+=item
+
+You can only encrypt up to 2^32 bits = 512 MB on 32bit archs. But You should
+use C<Digest::MD5> for those amounts of data anyway.
+
+=back
+
+=head1 SEE ALSO
+
+L<Digest::MD5>
+
+L<md5(1)>
+
+RFC 1321
+
+tools/md5: a small BSD compatible md5 tool written in pure perl.
+
+=head1 COPYRIGHT
+
+This library is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+ Copyright 2000 Christian Lackas, Imperia Software Solutions
+ Copyright 1998-1999 Gisle Aas.
+ Copyright 1995-1996 Neil Winton.
+ Copyright 1991-1992 RSA Data Security, Inc.
+
+The MD5 algorithm is defined in RFC 1321. The basic C code
+implementing the algorithm is derived from that in the RFC and is
+covered by the following copyright:
+
+=over 4
+
+=item
+
+Copyright (C) 1991-1992, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+
+=back
+
+This copyright does not prohibit distribution of any version of Perl
+containing this extension under the terms of the GNU or Artistic
+licenses.
+
+=head1 AUTHORS
+
+The original MD5 interface was written by Neil Winton
+(<N.Winton (at) axion.bt.co.uk>).
+
+C<Digest::MD5> was made by Gisle Aas <gisle (at) aas.no> (I took his Interface
+and part of the documentation).
+
+Thanks to Guido Flohr for his 'use integer'-hint.
+
+This release was made by Christian Lackas <delta (at) lackas.net>.
+
+=cut
+
+__DATA__
+FF,$a,$b,$c,$d,$_[4],7,0xd76aa478,/* 1 */
+FF,$d,$a,$b,$c,$_[5],12,0xe8c7b756,/* 2 */
+FF,$c,$d,$a,$b,$_[6],17,0x242070db,/* 3 */
+FF,$b,$c,$d,$a,$_[7],22,0xc1bdceee,/* 4 */
+FF,$a,$b,$c,$d,$_[8],7,0xf57c0faf,/* 5 */
+FF,$d,$a,$b,$c,$_[9],12,0x4787c62a,/* 6 */
+FF,$c,$d,$a,$b,$_[10],17,0xa8304613,/* 7 */
+FF,$b,$c,$d,$a,$_[11],22,0xfd469501,/* 8 */
+FF,$a,$b,$c,$d,$_[12],7,0x698098d8,/* 9 */
+FF,$d,$a,$b,$c,$_[13],12,0x8b44f7af,/* 10 */
+FF,$c,$d,$a,$b,$_[14],17,0xffff5bb1,/* 11 */
+FF,$b,$c,$d,$a,$_[15],22,0x895cd7be,/* 12 */
+FF,$a,$b,$c,$d,$_[16],7,0x6b901122,/* 13 */
+FF,$d,$a,$b,$c,$_[17],12,0xfd987193,/* 14 */
+FF,$c,$d,$a,$b,$_[18],17,0xa679438e,/* 15 */
+FF,$b,$c,$d,$a,$_[19],22,0x49b40821,/* 16 */ 
+GG,$a,$b,$c,$d,$_[5],5,0xf61e2562,/* 17 */
+GG,$d,$a,$b,$c,$_[10],9,0xc040b340,/* 18 */
+GG,$c,$d,$a,$b,$_[15],14,0x265e5a51,/* 19 */
+GG,$b,$c,$d,$a,$_[4],20,0xe9b6c7aa,/* 20 */
+GG,$a,$b,$c,$d,$_[9],5,0xd62f105d,/* 21 */
+GG,$d,$a,$b,$c,$_[14],9,0x2441453,/* 22 */
+GG,$c,$d,$a,$b,$_[19],14,0xd8a1e681,/* 23 */
+GG,$b,$c,$d,$a,$_[8],20,0xe7d3fbc8,/* 24 */
+GG,$a,$b,$c,$d,$_[13],5,0x21e1cde6,/* 25 */
+GG,$d,$a,$b,$c,$_[18],9,0xc33707d6,/* 26 */
+GG,$c,$d,$a,$b,$_[7],14,0xf4d50d87,/* 27 */
+GG,$b,$c,$d,$a,$_[12],20,0x455a14ed,/* 28 */
+GG,$a,$b,$c,$d,$_[17],5,0xa9e3e905,/* 29 */
+GG,$d,$a,$b,$c,$_[6],9,0xfcefa3f8,/* 30 */
+GG,$c,$d,$a,$b,$_[11],14,0x676f02d9,/* 31 */
+GG,$b,$c,$d,$a,$_[16],20,0x8d2a4c8a,/* 32 */
+HH,$a,$b,$c,$d,$_[9],4,0xfffa3942,/* 33 */
+HH,$d,$a,$b,$c,$_[12],11,0x8771f681,/* 34 */
+HH,$c,$d,$a,$b,$_[15],16,0x6d9d6122,/* 35 */
+HH,$b,$c,$d,$a,$_[18],23,0xfde5380c,/* 36 */
+HH,$a,$b,$c,$d,$_[5],4,0xa4beea44,/* 37 */
+HH,$d,$a,$b,$c,$_[8],11,0x4bdecfa9,/* 38 */
+HH,$c,$d,$a,$b,$_[11],16,0xf6bb4b60,/* 39 */
+HH,$b,$c,$d,$a,$_[14],23,0xbebfbc70,/* 40 */
+HH,$a,$b,$c,$d,$_[17],4,0x289b7ec6,/* 41 */
+HH,$d,$a,$b,$c,$_[4],11,0xeaa127fa,/* 42 */
+HH,$c,$d,$a,$b,$_[7],16,0xd4ef3085,/* 43 */
+HH,$b,$c,$d,$a,$_[10],23,0x4881d05,/* 44 */
+HH,$a,$b,$c,$d,$_[13],4,0xd9d4d039,/* 45 */
+HH,$d,$a,$b,$c,$_[16],11,0xe6db99e5,/* 46 */
+HH,$c,$d,$a,$b,$_[19],16,0x1fa27cf8,/* 47 */
+HH,$b,$c,$d,$a,$_[6],23,0xc4ac5665,/* 48 */
+II,$a,$b,$c,$d,$_[4],6,0xf4292244,/* 49 */
+II,$d,$a,$b,$c,$_[11],10,0x432aff97,/* 50 */
+II,$c,$d,$a,$b,$_[18],15,0xab9423a7,/* 51 */
+II,$b,$c,$d,$a,$_[9],21,0xfc93a039,/* 52 */
+II,$a,$b,$c,$d,$_[16],6,0x655b59c3,/* 53 */
+II,$d,$a,$b,$c,$_[7],10,0x8f0ccc92,/* 54 */
+II,$c,$d,$a,$b,$_[14],15,0xffeff47d,/* 55 */
+II,$b,$c,$d,$a,$_[5],21,0x85845dd1,/* 56 */
+II,$a,$b,$c,$d,$_[12],6,0x6fa87e4f,/* 57 */
+II,$d,$a,$b,$c,$_[19],10,0xfe2ce6e0,/* 58 */
+II,$c,$d,$a,$b,$_[10],15,0xa3014314,/* 59 */
+II,$b,$c,$d,$a,$_[17],21,0x4e0811a1,/* 60 */
+II,$a,$b,$c,$d,$_[8],6,0xf7537e82,/* 61 */
+II,$d,$a,$b,$c,$_[15],10,0xbd3af235,/* 62 */
+II,$c,$d,$a,$b,$_[6],15,0x2ad7d2bb,/* 63 */
+II,$b,$c,$d,$a,$_[13],21,0xeb86d391,/* 64 */
diff --git a/src/bach/build.bach/tools/perl/OLE/Storage_Lite.pm b/src/bach/build.bach/tools/perl/OLE/Storage_Lite.pm
new file mode 100644
index 0000000..3ea2a57
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/OLE/Storage_Lite.pm
@@ -0,0 +1,1686 @@
+# OLE::Storage_Lite

+#  by Kawai, Takanori (Hippo2000) 2000.11.4, 8, 14

+# This Program is Still ALPHA version.

+#//////////////////////////////////////////////////////////////////////////////

+# OLE::Storage_Lite::PPS Object

+#//////////////////////////////////////////////////////////////////////////////

+#==============================================================================

+# OLE::Storage_Lite::PPS

+#==============================================================================

+package OLE::Storage_Lite::PPS;

+require Exporter;

+use strict;

+use vars qw($VERSION @ISA);

+@ISA = qw(Exporter);

+$VERSION = '0.19';

+

+#------------------------------------------------------------------------------

+# new (OLE::Storage_Lite::PPS)

+#------------------------------------------------------------------------------

+sub new ($$$$$$$$$$;$$) {

+#1. Constructor for General Usage

+  my($sClass, $iNo, $sNm, $iType, $iPrev, $iNext, $iDir,

+     $raTime1st, $raTime2nd, $iStart, $iSize, $sData, $raChild) = @_;

+

+  if($iType == OLE::Storage_Lite::PpsType_File()) { #FILE

+    return OLE::Storage_Lite::PPS::File->_new

+        ($iNo, $sNm, $iType, $iPrev, $iNext, $iDir, $raTime1st, $raTime2nd,

+         $iStart, $iSize, $sData, $raChild);

+  }

+  elsif($iType == OLE::Storage_Lite::PpsType_Dir()) { #DIRECTRY

+    return OLE::Storage_Lite::PPS::Dir->_new

+        ($iNo, $sNm, $iType, $iPrev, $iNext, $iDir, $raTime1st, $raTime2nd,

+         $iStart, $iSize, $sData, $raChild);

+  }

+  elsif($iType == OLE::Storage_Lite::PpsType_Root()) { #ROOT

+    return OLE::Storage_Lite::PPS::Root->_new

+        ($iNo, $sNm, $iType, $iPrev, $iNext, $iDir, $raTime1st, $raTime2nd,

+         $iStart, $iSize, $sData, $raChild);

+  }

+  else {

+    die "Error PPS:$iType $sNm\n";

+  }

+}

+#------------------------------------------------------------------------------

+# _new (OLE::Storage_Lite::PPS)

+#   for OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub _new ($$$$$$$$$$$;$$) {

+  my($sClass, $iNo, $sNm, $iType, $iPrev, $iNext, $iDir,

+        $raTime1st, $raTime2nd, $iStart, $iSize, $sData, $raChild) = @_;

+#1. Constructor for OLE::Storage_Lite

+  my $oThis = {

+    No   => $iNo,

+    Name => $sNm,

+    Type => $iType,

+    PrevPps => $iPrev,

+    NextPps => $iNext,

+    DirPps => $iDir,

+    Time1st => $raTime1st,

+    Time2nd => $raTime2nd,

+    StartBlock => $iStart,

+    Size       => $iSize,

+    Data       => $sData,

+    Child      => $raChild,

+  };

+  bless $oThis, $sClass;

+  return $oThis;

+}

+#------------------------------------------------------------------------------

+# _DataLen (OLE::Storage_Lite::PPS)

+# Check for update

+#------------------------------------------------------------------------------

+sub _DataLen($) {

+    my($oSelf) =@_;

+    return 0 unless(defined($oSelf->{Data}));

+    return ($oSelf->{_PPS_FILE})?

+        ($oSelf->{_PPS_FILE}->stat())[7] : length($oSelf->{Data});

+}

+#------------------------------------------------------------------------------

+# _makeSmallData (OLE::Storage_Lite::PPS)

+#------------------------------------------------------------------------------

+sub _makeSmallData($$$) {

+  my($oThis, $aList, $rhInfo) = @_;

+  my ($sRes);

+  my $FILE = $rhInfo->{_FILEH_};

+  my $iSmBlk = 0;

+

+  foreach my $oPps (@$aList) {

+#1. Make SBD, small data string

+  if($oPps->{Type}==OLE::Storage_Lite::PpsType_File()) {

+    next if($oPps->{Size}<=0);

+    if($oPps->{Size} < $rhInfo->{_SMALL_SIZE}) {

+      my $iSmbCnt = int($oPps->{Size} / $rhInfo->{_SMALL_BLOCK_SIZE})

+                    + (($oPps->{Size} % $rhInfo->{_SMALL_BLOCK_SIZE})? 1: 0);

+      #1.1 Add to SBD

+      for (my $i = 0; $i<($iSmbCnt-1); $i++) {

+            print {$FILE} (pack("V", $i+$iSmBlk+1));

+      }

+      print {$FILE} (pack("V", -2));

+

+      #1.2 Add to Data String(this will be written for RootEntry)

+      #Check for update

+      if($oPps->{_PPS_FILE}) {

+        my $sBuff;

+        $oPps->{_PPS_FILE}->seek(0, 0); #To The Top

+        while($oPps->{_PPS_FILE}->read($sBuff, 4096)) {

+            $sRes .= $sBuff;

+        }

+      }

+      else {

+        $sRes .= $oPps->{Data};

+      }

+      $sRes .= ("\x00" x

+        ($rhInfo->{_SMALL_BLOCK_SIZE} - ($oPps->{Size}% $rhInfo->{_SMALL_BLOCK_SIZE})))

+        if($oPps->{Size}% $rhInfo->{_SMALL_BLOCK_SIZE});

+      #1.3 Set for PPS

+      $oPps->{StartBlock} = $iSmBlk;

+      $iSmBlk += $iSmbCnt;

+    }

+  }

+  }

+  my $iSbCnt = int($rhInfo->{_BIG_BLOCK_SIZE}/ OLE::Storage_Lite::LongIntSize());

+  print {$FILE} (pack("V", -1) x ($iSbCnt - ($iSmBlk % $iSbCnt)))

+    if($iSmBlk  % $iSbCnt);

+#2. Write SBD with adjusting length for block

+  return $sRes;

+}

+#------------------------------------------------------------------------------

+# _savePpsWk (OLE::Storage_Lite::PPS)

+#------------------------------------------------------------------------------

+sub _savePpsWk($$)

+{

+  my($oThis, $rhInfo) = @_;

+#1. Write PPS

+  my $FILE = $rhInfo->{_FILEH_};

+  print {$FILE} (

+            $oThis->{Name}

+            . ("\x00" x (64 - length($oThis->{Name})))  #64

+            , pack("v", length($oThis->{Name}) + 2)     #66

+            , pack("c", $oThis->{Type})         #67

+            , pack("c", 0x00) #UK               #68

+            , pack("V", $oThis->{PrevPps}) #Prev        #72

+            , pack("V", $oThis->{NextPps}) #Next        #76

+            , pack("V", $oThis->{DirPps})  #Dir     #80

+            , "\x00\x09\x02\x00"                #84

+            , "\x00\x00\x00\x00"                #88

+            , "\xc0\x00\x00\x00"                #92

+            , "\x00\x00\x00\x46"                #96

+            , "\x00\x00\x00\x00"                #100

+            , OLE::Storage_Lite::LocalDate2OLE($oThis->{Time1st})       #108

+            , OLE::Storage_Lite::LocalDate2OLE($oThis->{Time2nd})       #116

+            , pack("V", defined($oThis->{StartBlock})?

+                      $oThis->{StartBlock}:0)       #116

+            , pack("V", defined($oThis->{Size})?

+                 $oThis->{Size} : 0)            #124

+            , pack("V", 0),                  #128

+        );

+}

+

+#//////////////////////////////////////////////////////////////////////////////

+# OLE::Storage_Lite::PPS::Root Object

+#//////////////////////////////////////////////////////////////////////////////

+#==============================================================================

+# OLE::Storage_Lite::PPS::Root

+#==============================================================================

+package OLE::Storage_Lite::PPS::Root;

+require Exporter;

+use strict;

+use IO::File;

+use IO::Handle;

+use Fcntl;

+use vars qw($VERSION @ISA);

+@ISA = qw(OLE::Storage_Lite::PPS Exporter);

+$VERSION = '0.19';

+sub _savePpsSetPnt($$$);

+sub _savePpsSetPnt2($$$);

+#------------------------------------------------------------------------------

+# new (OLE::Storage_Lite::PPS::Root)

+#------------------------------------------------------------------------------

+sub new ($;$$$) {

+    my($sClass, $raTime1st, $raTime2nd, $raChild) = @_;

+    OLE::Storage_Lite::PPS::_new(

+        $sClass,

+        undef,

+        OLE::Storage_Lite::Asc2Ucs('Root Entry'),

+        5,

+        undef,

+        undef,

+        undef,

+        $raTime1st,

+        $raTime2nd,

+        undef,

+        undef,

+        undef,

+        $raChild);

+}

+#------------------------------------------------------------------------------

+# save (OLE::Storage_Lite::PPS::Root)

+#------------------------------------------------------------------------------

+sub save($$;$$) {

+  my($oThis, $sFile, $bNoAs, $rhInfo) = @_;

+  #0.Initial Setting for saving

+  $rhInfo = {} unless($rhInfo);

+  $rhInfo->{_BIG_BLOCK_SIZE}  = 2**

+                (($rhInfo->{_BIG_BLOCK_SIZE})?

+                    _adjust2($rhInfo->{_BIG_BLOCK_SIZE})  : 9);

+  $rhInfo->{_SMALL_BLOCK_SIZE}= 2 **

+                (($rhInfo->{_SMALL_BLOCK_SIZE})?

+                    _adjust2($rhInfo->{_SMALL_BLOCK_SIZE}): 6);

+  $rhInfo->{_SMALL_SIZE} = 0x1000;

+  $rhInfo->{_PPS_SIZE} = 0x80;

+

+  my $closeFile = 1;

+

+  #1.Open File

+  #1.1 $sFile is Ref of scalar

+  if(ref($sFile) eq 'SCALAR') {

+    require IO::Scalar;

+    my $oIo = new IO::Scalar $sFile, O_WRONLY;

+    $rhInfo->{_FILEH_} = $oIo;

+  }

+  #1.1.1 $sFile is a IO::Scalar object

+  # Now handled as a filehandle ref below.

+

+  #1.2 $sFile is a IO::Handle object

+  elsif(UNIVERSAL::isa($sFile, 'IO::Handle')) {

+    # Not all filehandles support binmode() so try it in an eval.

+    eval{ binmode $sFile };

+    $rhInfo->{_FILEH_} = $sFile;

+  }

+  #1.3 $sFile is a simple filename string

+  elsif(!ref($sFile)) {

+    if($sFile ne '-') {

+        my $oIo = new IO::File;

+        $oIo->open(">$sFile") || return undef;

+        binmode($oIo);

+        $rhInfo->{_FILEH_} = $oIo;

+    }

+    else {

+        my $oIo = new IO::Handle;

+        $oIo->fdopen(fileno(STDOUT),"w") || return undef;

+        binmode($oIo);

+        $rhInfo->{_FILEH_} = $oIo;

+    }

+  }

+  #1.4 Assume that if $sFile is a ref then it is a valid filehandle

+  else {

+    # Not all filehandles support binmode() so try it in an eval.

+    eval{ binmode $sFile };

+    $rhInfo->{_FILEH_} = $sFile;

+    # Caller controls filehandle closing

+    $closeFile = 0;

+  }

+

+  my $iBlk = 0;

+  #1. Make an array of PPS (for Save)

+  my @aList=();

+  if($bNoAs) {

+    _savePpsSetPnt2([$oThis], \@aList, $rhInfo);

+  }

+  else {

+    _savePpsSetPnt([$oThis], \@aList, $rhInfo);

+  }

+  my ($iSBDcnt, $iBBcnt, $iPPScnt) = $oThis->_calcSize(\@aList, $rhInfo);

+

+  #2.Save Header

+  $oThis->_saveHeader($rhInfo, $iSBDcnt, $iBBcnt, $iPPScnt);

+

+  #3.Make Small Data string (write SBD)

+  my $sSmWk = $oThis->_makeSmallData(\@aList, $rhInfo);

+  $oThis->{Data} = $sSmWk;  #Small Datas become RootEntry Data

+

+  #4. Write BB

+  my $iBBlk = $iSBDcnt;

+  $oThis->_saveBigData(\$iBBlk, \@aList, $rhInfo);

+

+  #5. Write PPS

+  $oThis->_savePps(\@aList, $rhInfo);

+

+  #6. Write BD and BDList and Adding Header informations

+  $oThis->_saveBbd($iSBDcnt, $iBBcnt, $iPPScnt,  $rhInfo);

+

+  #7.Close File

+  return $rhInfo->{_FILEH_}->close if $closeFile;

+}

+#------------------------------------------------------------------------------

+# _calcSize (OLE::Storage_Lite::PPS)

+#------------------------------------------------------------------------------

+sub _calcSize($$)

+{

+  my($oThis, $raList, $rhInfo) = @_;

+

+#0. Calculate Basic Setting

+  my ($iSBDcnt, $iBBcnt, $iPPScnt) = (0,0,0);

+  my $iSmallLen = 0;

+  my $iSBcnt = 0;

+  foreach my $oPps (@$raList) {

+      if($oPps->{Type}==OLE::Storage_Lite::PpsType_File()) {

+        $oPps->{Size} = $oPps->_DataLen();  #Mod

+        if($oPps->{Size} < $rhInfo->{_SMALL_SIZE}) {

+          $iSBcnt += int($oPps->{Size} / $rhInfo->{_SMALL_BLOCK_SIZE})

+                          + (($oPps->{Size} % $rhInfo->{_SMALL_BLOCK_SIZE})? 1: 0);

+        }

+        else {

+          $iBBcnt +=

+            (int($oPps->{Size}/ $rhInfo->{_BIG_BLOCK_SIZE}) +

+                (($oPps->{Size}% $rhInfo->{_BIG_BLOCK_SIZE})? 1: 0));

+        }

+      }

+  }

+  $iSmallLen = $iSBcnt * $rhInfo->{_SMALL_BLOCK_SIZE};

+  my $iSlCnt = int($rhInfo->{_BIG_BLOCK_SIZE}/ OLE::Storage_Lite::LongIntSize());

+  $iSBDcnt = int($iSBcnt / $iSlCnt)+ (($iSBcnt % $iSlCnt)? 1:0);

+  $iBBcnt +=  (int($iSmallLen/ $rhInfo->{_BIG_BLOCK_SIZE}) +

+                (( $iSmallLen% $rhInfo->{_BIG_BLOCK_SIZE})? 1: 0));

+  my $iCnt = scalar(@$raList);

+  my $iBdCnt = $rhInfo->{_BIG_BLOCK_SIZE}/OLE::Storage_Lite::PpsSize();

+  $iPPScnt = (int($iCnt/$iBdCnt) + (($iCnt % $iBdCnt)? 1: 0));

+  return ($iSBDcnt, $iBBcnt, $iPPScnt);

+}

+#------------------------------------------------------------------------------

+# _adjust2 (OLE::Storage_Lite::PPS::Root)

+#------------------------------------------------------------------------------

+sub _adjust2($) {

+  my($i2) = @_;

+  my $iWk;

+  $iWk = log($i2)/log(2);

+  return ($iWk > int($iWk))? int($iWk)+1:$iWk;

+}

+#------------------------------------------------------------------------------

+# _saveHeader (OLE::Storage_Lite::PPS::Root)

+#------------------------------------------------------------------------------

+sub _saveHeader($$$$$) {

+  my($oThis, $rhInfo, $iSBDcnt, $iBBcnt, $iPPScnt) = @_;

+  my $FILE = $rhInfo->{_FILEH_};

+

+#0. Calculate Basic Setting

+  my $iBlCnt = $rhInfo->{_BIG_BLOCK_SIZE} / OLE::Storage_Lite::LongIntSize();

+  my $i1stBdL = int(($rhInfo->{_BIG_BLOCK_SIZE} - 0x4C) / OLE::Storage_Lite::LongIntSize());

+  my $i1stBdMax = $i1stBdL * $iBlCnt  - $i1stBdL;

+  my $iBdExL = 0;

+  my $iAll = $iBBcnt + $iPPScnt + $iSBDcnt;

+  my $iAllW = $iAll;

+  my $iBdCntW = int($iAllW / $iBlCnt) + (($iAllW % $iBlCnt)? 1: 0);

+  my $iBdCnt = int(($iAll + $iBdCntW) / $iBlCnt) + ((($iAllW+$iBdCntW) % $iBlCnt)? 1: 0);

+  my $i;

+

+  if ($iBdCnt > $i1stBdL) {

+    #0.1 Calculate BD count

+    $iBlCnt--; #the BlCnt is reduced in the count of the last sect is used for a pointer the next Bl

+    my $iBBleftover = $iAll - $i1stBdMax;

+

+    if ($iAll >$i1stBdMax) {

+      while(1) {

+        $iBdCnt = int(($iBBleftover) / $iBlCnt) + ((($iBBleftover) % $iBlCnt)? 1: 0);

+        $iBdExL = int(($iBdCnt) / $iBlCnt) + ((($iBdCnt) % $iBlCnt)? 1: 0);

+        $iBBleftover = $iBBleftover + $iBdExL;

+        last if($iBdCnt == (int(($iBBleftover) / $iBlCnt) + ((($iBBleftover) % $iBlCnt)? 1: 0)));

+      }

+    }

+    $iBdCnt += $i1stBdL;

+    #print "iBdCnt = $iBdCnt \n";

+  }

+#1.Save Header

+  print {$FILE} (

+            "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"

+            , "\x00\x00\x00\x00" x 4

+            , pack("v", 0x3b)

+            , pack("v", 0x03)

+            , pack("v", -2)

+            , pack("v", 9)

+            , pack("v", 6)

+            , pack("v", 0)

+            , "\x00\x00\x00\x00" x 2

+            , pack("V", $iBdCnt),

+            , pack("V", $iBBcnt+$iSBDcnt), #ROOT START

+            , pack("V", 0)

+            , pack("V", 0x1000)

+            , pack("V", $iSBDcnt ? 0 : -2)                  #Small Block Depot

+            , pack("V", $iSBDcnt)

+    );

+#2. Extra BDList Start, Count

+  if($iAll <= $i1stBdMax) {

+    print {$FILE} (

+                pack("V", -2),      #Extra BDList Start

+                pack("V", 0),       #Extra BDList Count

+        );

+  }

+  else {

+    print {$FILE} (

+            pack("V", $iAll+$iBdCnt),

+            pack("V", $iBdExL),

+        );

+  }

+

+#3. BDList

+    for($i=0; $i<$i1stBdL and $i < $iBdCnt; $i++) {

+        print {$FILE} (pack("V", $iAll+$i));

+    }

+    print {$FILE} ((pack("V", -1)) x($i1stBdL-$i)) if($i<$i1stBdL);

+}

+#------------------------------------------------------------------------------

+# _saveBigData (OLE::Storage_Lite::PPS)

+#------------------------------------------------------------------------------

+sub _saveBigData($$$$) {

+  my($oThis, $iStBlk, $raList, $rhInfo) = @_;

+  my $iRes = 0;

+  my $FILE = $rhInfo->{_FILEH_};

+

+#1.Write Big (ge 0x1000) Data into Block

+  foreach my $oPps (@$raList) {

+    if($oPps->{Type}!=OLE::Storage_Lite::PpsType_Dir()) {

+#print "PPS: $oPps DEF:", defined($oPps->{Data}), "\n";

+        $oPps->{Size} = $oPps->_DataLen();  #Mod

+        if(($oPps->{Size} >= $rhInfo->{_SMALL_SIZE}) ||

+            (($oPps->{Type} == OLE::Storage_Lite::PpsType_Root()) && defined($oPps->{Data}))) {

+            #1.1 Write Data

+            #Check for update

+            if($oPps->{_PPS_FILE}) {

+                my $sBuff;

+                my $iLen = 0;

+                $oPps->{_PPS_FILE}->seek(0, 0); #To The Top

+                while($oPps->{_PPS_FILE}->read($sBuff, 4096)) {

+                    $iLen += length($sBuff);

+                    print {$FILE} ($sBuff);           #Check for update

+                }

+            }

+            else {

+                print {$FILE} ($oPps->{Data});

+            }

+            print {$FILE} (

+                        "\x00" x

+                        ($rhInfo->{_BIG_BLOCK_SIZE} -

+                            ($oPps->{Size} % $rhInfo->{_BIG_BLOCK_SIZE}))

+                    ) if ($oPps->{Size} % $rhInfo->{_BIG_BLOCK_SIZE});

+            #1.2 Set For PPS

+            $oPps->{StartBlock} = $$iStBlk;

+            $$iStBlk +=

+                    (int($oPps->{Size}/ $rhInfo->{_BIG_BLOCK_SIZE}) +

+                        (($oPps->{Size}% $rhInfo->{_BIG_BLOCK_SIZE})? 1: 0));

+        }

+    }

+  }

+}

+#------------------------------------------------------------------------------

+# _savePps (OLE::Storage_Lite::PPS::Root)

+#------------------------------------------------------------------------------

+sub _savePps($$$)

+{

+  my($oThis, $raList, $rhInfo) = @_;

+#0. Initial

+  my $FILE = $rhInfo->{_FILEH_};

+#2. Save PPS

+  foreach my $oItem (@$raList) {

+      $oItem->_savePpsWk($rhInfo);

+  }

+#3. Adjust for Block

+  my $iCnt = scalar(@$raList);

+  my $iBCnt = $rhInfo->{_BIG_BLOCK_SIZE} / $rhInfo->{_PPS_SIZE};

+  print {$FILE} ("\x00" x (($iBCnt - ($iCnt % $iBCnt)) * $rhInfo->{_PPS_SIZE}))

+        if($iCnt % $iBCnt);

+  return int($iCnt / $iBCnt) + (($iCnt % $iBCnt)? 1: 0);

+}

+#------------------------------------------------------------------------------

+# _savePpsSetPnt2 (OLE::Storage_Lite::PPS::Root)

+#  For Test

+#------------------------------------------------------------------------------

+sub _savePpsSetPnt2($$$)

+{

+  my($aThis, $raList, $rhInfo) = @_;

+#1. make Array as Children-Relations

+#1.1 if No Children

+  if($#$aThis < 0) {

+      return 0xFFFFFFFF;

+  }

+  elsif($#$aThis == 0) {

+#1.2 Just Only one

+      push @$raList, $aThis->[0];

+      $aThis->[0]->{No} = $#$raList;

+      $aThis->[0]->{PrevPps} = 0xFFFFFFFF;

+      $aThis->[0]->{NextPps} = 0xFFFFFFFF;

+      $aThis->[0]->{DirPps} = _savePpsSetPnt2($aThis->[0]->{Child}, $raList, $rhInfo);

+      return $aThis->[0]->{No};

+  }

+  else {

+#1.3 Array

+      my $iCnt = $#$aThis + 1;

+#1.3.1 Define Center

+      my $iPos = 0; #int($iCnt/ 2);     #$iCnt

+

+      my @aWk = @$aThis;

+      my @aPrev = ($#$aThis > 1)? splice(@aWk, 1, 1) : (); #$iPos);

+      my @aNext = splice(@aWk, 1); #, $iCnt - $iPos -1);

+      $aThis->[$iPos]->{PrevPps} = _savePpsSetPnt2(

+            \@aPrev, $raList, $rhInfo);

+      push @$raList, $aThis->[$iPos];

+      $aThis->[$iPos]->{No} = $#$raList;

+

+#1.3.2 Devide a array into Previous,Next

+      $aThis->[$iPos]->{NextPps} = _savePpsSetPnt2(

+            \@aNext, $raList, $rhInfo);

+      $aThis->[$iPos]->{DirPps} = _savePpsSetPnt2($aThis->[$iPos]->{Child}, $raList, $rhInfo);

+      return $aThis->[$iPos]->{No};

+  }

+}

+#------------------------------------------------------------------------------

+# _savePpsSetPnt2 (OLE::Storage_Lite::PPS::Root)

+#  For Test

+#------------------------------------------------------------------------------

+sub _savePpsSetPnt2s($$$)

+{

+  my($aThis, $raList, $rhInfo) = @_;

+#1. make Array as Children-Relations

+#1.1 if No Children

+  if($#$aThis < 0) {

+      return 0xFFFFFFFF;

+  }

+  elsif($#$aThis == 0) {

+#1.2 Just Only one

+      push @$raList, $aThis->[0];

+      $aThis->[0]->{No} = $#$raList;

+      $aThis->[0]->{PrevPps} = 0xFFFFFFFF;

+      $aThis->[0]->{NextPps} = 0xFFFFFFFF;

+      $aThis->[0]->{DirPps} = _savePpsSetPnt2($aThis->[0]->{Child}, $raList, $rhInfo);

+      return $aThis->[0]->{No};

+  }

+  else {

+#1.3 Array

+      my $iCnt = $#$aThis + 1;

+#1.3.1 Define Center

+      my $iPos = 0; #int($iCnt/ 2);     #$iCnt

+      push @$raList, $aThis->[$iPos];

+      $aThis->[$iPos]->{No} = $#$raList;

+      my @aWk = @$aThis;

+#1.3.2 Devide a array into Previous,Next

+      my @aPrev = splice(@aWk, 0, $iPos);

+      my @aNext = splice(@aWk, 1, $iCnt - $iPos -1);

+      $aThis->[$iPos]->{PrevPps} = _savePpsSetPnt2(

+            \@aPrev, $raList, $rhInfo);

+      $aThis->[$iPos]->{NextPps} = _savePpsSetPnt2(

+            \@aNext, $raList, $rhInfo);

+      $aThis->[$iPos]->{DirPps} = _savePpsSetPnt2($aThis->[$iPos]->{Child}, $raList, $rhInfo);

+      return $aThis->[$iPos]->{No};

+  }

+}

+#------------------------------------------------------------------------------

+# _savePpsSetPnt (OLE::Storage_Lite::PPS::Root)

+#------------------------------------------------------------------------------

+sub _savePpsSetPnt($$$)

+{

+  my($aThis, $raList, $rhInfo) = @_;

+#1. make Array as Children-Relations

+#1.1 if No Children

+  if($#$aThis < 0) {

+      return 0xFFFFFFFF;

+  }

+  elsif($#$aThis == 0) {

+#1.2 Just Only one

+      push @$raList, $aThis->[0];

+      $aThis->[0]->{No} = $#$raList;

+      $aThis->[0]->{PrevPps} = 0xFFFFFFFF;

+      $aThis->[0]->{NextPps} = 0xFFFFFFFF;

+      $aThis->[0]->{DirPps} = _savePpsSetPnt($aThis->[0]->{Child}, $raList, $rhInfo);

+      return $aThis->[0]->{No};

+  }

+  else {

+#1.3 Array

+      my $iCnt = $#$aThis + 1;

+#1.3.1 Define Center

+      my $iPos = int($iCnt/ 2);     #$iCnt

+      push @$raList, $aThis->[$iPos];

+      $aThis->[$iPos]->{No} = $#$raList;

+      my @aWk = @$aThis;

+#1.3.2 Devide a array into Previous,Next

+      my @aPrev = splice(@aWk, 0, $iPos);

+      my @aNext = splice(@aWk, 1, $iCnt - $iPos -1);

+      $aThis->[$iPos]->{PrevPps} = _savePpsSetPnt(

+            \@aPrev, $raList, $rhInfo);

+      $aThis->[$iPos]->{NextPps} = _savePpsSetPnt(

+            \@aNext, $raList, $rhInfo);

+      $aThis->[$iPos]->{DirPps} = _savePpsSetPnt($aThis->[$iPos]->{Child}, $raList, $rhInfo);

+      return $aThis->[$iPos]->{No};

+  }

+}

+#------------------------------------------------------------------------------

+# _savePpsSetPnt (OLE::Storage_Lite::PPS::Root)

+#------------------------------------------------------------------------------

+sub _savePpsSetPnt1($$$)

+{

+  my($aThis, $raList, $rhInfo) = @_;

+#1. make Array as Children-Relations

+#1.1 if No Children

+  if($#$aThis < 0) {

+      return 0xFFFFFFFF;

+  }

+  elsif($#$aThis == 0) {

+#1.2 Just Only one

+      push @$raList, $aThis->[0];

+      $aThis->[0]->{No} = $#$raList;

+      $aThis->[0]->{PrevPps} = 0xFFFFFFFF;

+      $aThis->[0]->{NextPps} = 0xFFFFFFFF;

+      $aThis->[0]->{DirPps} = _savePpsSetPnt($aThis->[0]->{Child}, $raList, $rhInfo);

+      return $aThis->[0]->{No};

+  }

+  else {

+#1.3 Array

+      my $iCnt = $#$aThis + 1;

+#1.3.1 Define Center

+      my $iPos = int($iCnt/ 2);     #$iCnt

+      push @$raList, $aThis->[$iPos];

+      $aThis->[$iPos]->{No} = $#$raList;

+      my @aWk = @$aThis;

+#1.3.2 Devide a array into Previous,Next

+      my @aPrev = splice(@aWk, 0, $iPos);

+      my @aNext = splice(@aWk, 1, $iCnt - $iPos -1);

+      $aThis->[$iPos]->{PrevPps} = _savePpsSetPnt(

+            \@aPrev, $raList, $rhInfo);

+      $aThis->[$iPos]->{NextPps} = _savePpsSetPnt(

+            \@aNext, $raList, $rhInfo);

+      $aThis->[$iPos]->{DirPps} = _savePpsSetPnt($aThis->[$iPos]->{Child}, $raList, $rhInfo);

+      return $aThis->[$iPos]->{No};

+  }

+}

+#------------------------------------------------------------------------------

+# _saveBbd (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _saveBbd($$$$)

+{

+  my($oThis, $iSbdSize, $iBsize, $iPpsCnt, $rhInfo) = @_;

+  my $FILE = $rhInfo->{_FILEH_};

+#0. Calculate Basic Setting

+  my $iBbCnt = $rhInfo->{_BIG_BLOCK_SIZE} / OLE::Storage_Lite::LongIntSize();

+  my $iBlCnt = $iBbCnt - 1;

+  my $i1stBdL = int(($rhInfo->{_BIG_BLOCK_SIZE} - 0x4C) / OLE::Storage_Lite::LongIntSize());

+  my $i1stBdMax = $i1stBdL * $iBbCnt  - $i1stBdL;

+  my $iBdExL = 0;

+  my $iAll = $iBsize + $iPpsCnt + $iSbdSize;

+  my $iAllW = $iAll;

+  my $iBdCntW = int($iAllW / $iBbCnt) + (($iAllW % $iBbCnt)? 1: 0);

+  my $iBdCnt = 0;

+  my $i;

+#0.1 Calculate BD count

+  my $iBBleftover = $iAll - $i1stBdMax;

+  if ($iAll >$i1stBdMax) {

+

+    while(1) {

+      $iBdCnt = int(($iBBleftover) / $iBlCnt) + ((($iBBleftover) % $iBlCnt)? 1: 0);

+      $iBdExL = int(($iBdCnt) / $iBlCnt) + ((($iBdCnt) % $iBlCnt)? 1: 0);

+      $iBBleftover = $iBBleftover + $iBdExL;

+      last if($iBdCnt == (int(($iBBleftover) / $iBlCnt) + ((($iBBleftover) % $iBlCnt)? 1: 0)));

+    }

+  }

+  $iAllW += $iBdExL;

+  $iBdCnt += $i1stBdL;

+  #print "iBdCnt = $iBdCnt \n";

+

+#1. Making BD

+#1.1 Set for SBD

+  if($iSbdSize > 0) {

+    for ($i = 0; $i<($iSbdSize-1); $i++) {

+      print {$FILE} (pack("V", $i+1));

+    }

+    print {$FILE} (pack("V", -2));

+  }

+#1.2 Set for B

+  for ($i = 0; $i<($iBsize-1); $i++) {

+      print {$FILE} (pack("V", $i+$iSbdSize+1));

+  }

+  print {$FILE} (pack("V", -2));

+

+#1.3 Set for PPS

+  for ($i = 0; $i<($iPpsCnt-1); $i++) {

+      print {$FILE} (pack("V", $i+$iSbdSize+$iBsize+1));

+  }

+  print {$FILE} (pack("V", -2));

+#1.4 Set for BBD itself ( 0xFFFFFFFD : BBD)

+  for($i=0; $i<$iBdCnt;$i++) {

+    print {$FILE} (pack("V", 0xFFFFFFFD));

+  }

+#1.5 Set for ExtraBDList

+  for($i=0; $i<$iBdExL;$i++) {

+    print {$FILE} (pack("V", 0xFFFFFFFC));

+  }

+#1.6 Adjust for Block

+  print {$FILE} (pack("V", -1) x ($iBbCnt - (($iAllW + $iBdCnt) % $iBbCnt)))

+                if(($iAllW + $iBdCnt) % $iBbCnt);

+#2.Extra BDList

+  if($iBdCnt > $i1stBdL)  {

+    my $iN=0;

+    my $iNb=0;

+    for($i=$i1stBdL;$i<$iBdCnt; $i++, $iN++) {

+      if($iN>=($iBbCnt-1)) {

+          $iN = 0;

+          $iNb++;

+          print {$FILE} (pack("V", $iAll+$iBdCnt+$iNb));

+      }

+      print {$FILE} (pack("V", $iBsize+$iSbdSize+$iPpsCnt+$i));

+    }

+    print {$FILE} (pack("V", -1) x (($iBbCnt-1) - (($iBdCnt-$i1stBdL) % ($iBbCnt-1))))

+        if(($iBdCnt-$i1stBdL) % ($iBbCnt-1));

+    print {$FILE} (pack("V", -2));

+  }

+}

+

+#//////////////////////////////////////////////////////////////////////////////

+# OLE::Storage_Lite::PPS::File Object

+#//////////////////////////////////////////////////////////////////////////////

+#==============================================================================

+# OLE::Storage_Lite::PPS::File

+#==============================================================================

+package OLE::Storage_Lite::PPS::File;

+require Exporter;

+use strict;

+use vars qw($VERSION @ISA);

+@ISA = qw(OLE::Storage_Lite::PPS Exporter);

+$VERSION = '0.19';

+#------------------------------------------------------------------------------

+# new (OLE::Storage_Lite::PPS::File)

+#------------------------------------------------------------------------------

+sub new ($$$) {

+  my($sClass, $sNm, $sData) = @_;

+    OLE::Storage_Lite::PPS::_new(

+        $sClass,

+        undef,

+        $sNm,

+        2,

+        undef,

+        undef,

+        undef,

+        undef,

+        undef,

+        undef,

+        undef,

+        $sData,

+        undef);

+}

+#------------------------------------------------------------------------------

+# newFile (OLE::Storage_Lite::PPS::File)

+#------------------------------------------------------------------------------

+sub newFile ($$;$) {

+    my($sClass, $sNm, $sFile) = @_;

+    my $oSelf =

+    OLE::Storage_Lite::PPS::_new(

+        $sClass,

+        undef,

+        $sNm,

+        2,

+        undef,

+        undef,

+        undef,

+        undef,

+        undef,

+        undef,

+        undef,

+        '',

+        undef);

+#

+    if((!defined($sFile)) or ($sFile eq '')) {

+        $oSelf->{_PPS_FILE} = IO::File->new_tmpfile();

+    }

+    elsif(UNIVERSAL::isa($sFile, 'IO::Handle')) {

+        $oSelf->{_PPS_FILE} = $sFile;

+    }

+    elsif(!ref($sFile)) {

+        #File Name

+        $oSelf->{_PPS_FILE} = new IO::File;

+        return undef unless($oSelf->{_PPS_FILE});

+        $oSelf->{_PPS_FILE}->open("$sFile", "r+") || return undef;

+    }

+    else {

+        return undef;

+    }

+    if($oSelf->{_PPS_FILE}) {

+        $oSelf->{_PPS_FILE}->seek(0, 2);

+        binmode($oSelf->{_PPS_FILE});

+        $oSelf->{_PPS_FILE}->autoflush(1);

+    }

+    return $oSelf;

+}

+#------------------------------------------------------------------------------

+# append (OLE::Storage_Lite::PPS::File)

+#------------------------------------------------------------------------------

+sub append ($$) {

+    my($oSelf, $sData) = @_;

+    if($oSelf->{_PPS_FILE}) {

+        print {$oSelf->{_PPS_FILE}} $sData;

+    }

+    else {

+        $oSelf->{Data} .= $sData;

+    }

+}

+

+#//////////////////////////////////////////////////////////////////////////////

+# OLE::Storage_Lite::PPS::Dir Object

+#//////////////////////////////////////////////////////////////////////////////

+#------------------------------------------------------------------------------

+# new (OLE::Storage_Lite::PPS::Dir)

+#------------------------------------------------------------------------------

+package OLE::Storage_Lite::PPS::Dir;

+require Exporter;

+use strict;

+use vars qw($VERSION @ISA);

+@ISA = qw(OLE::Storage_Lite::PPS Exporter);

+$VERSION = '0.19';

+sub new ($$;$$$) {

+    my($sClass, $sName, $raTime1st, $raTime2nd, $raChild) = @_;

+    OLE::Storage_Lite::PPS::_new(

+        $sClass,

+        undef,

+        $sName,

+        1,

+        undef,

+        undef,

+        undef,

+        $raTime1st,

+        $raTime2nd,

+        undef,

+        undef,

+        undef,

+        $raChild);

+}

+#==============================================================================

+# OLE::Storage_Lite

+#==============================================================================

+package OLE::Storage_Lite;

+require Exporter;

+

+use strict;

+use IO::File;

+use Time::Local 'timegm';

+

+use vars qw($VERSION @ISA @EXPORT);

+@ISA = qw(Exporter);

+$VERSION = '0.19';

+sub _getPpsSearch($$$$$;$);

+sub _getPpsTree($$$;$);

+#------------------------------------------------------------------------------

+# Const for OLE::Storage_Lite

+#------------------------------------------------------------------------------

+#0. Constants

+sub PpsType_Root {5};

+sub PpsType_Dir  {1};

+sub PpsType_File {2};

+sub DataSizeSmall{0x1000};

+sub LongIntSize  {4};

+sub PpsSize      {0x80};

+#------------------------------------------------------------------------------

+# new OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub new($$) {

+  my($sClass, $sFile) = @_;

+  my $oThis = {

+    _FILE => $sFile,

+  };

+  bless $oThis;

+  return $oThis;

+}

+#------------------------------------------------------------------------------

+# getPpsTree: OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub getPpsTree($;$)

+{

+  my($oThis, $bData) = @_;

+#0.Init

+  my $rhInfo = _initParse($oThis->{_FILE});

+  return undef unless($rhInfo);

+#1. Get Data

+  my ($oPps) = _getPpsTree(0, $rhInfo, $bData);

+  close(IN);

+  return $oPps;

+}

+#------------------------------------------------------------------------------

+# getSearch: OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub getPpsSearch($$;$$)

+{

+  my($oThis, $raName, $bData, $iCase) = @_;

+#0.Init

+  my $rhInfo = _initParse($oThis->{_FILE});

+  return undef unless($rhInfo);

+#1. Get Data

+  my @aList = _getPpsSearch(0, $rhInfo, $raName, $bData, $iCase);

+  close(IN);

+  return @aList;

+}

+#------------------------------------------------------------------------------

+# getNthPps: OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub getNthPps($$;$)

+{

+  my($oThis, $iNo, $bData) = @_;

+#0.Init

+  my $rhInfo = _initParse($oThis->{_FILE});

+  return undef unless($rhInfo);

+#1. Get Data

+  my $oPps = _getNthPps($iNo, $rhInfo, $bData);

+  close IN;

+  return $oPps;

+}

+#------------------------------------------------------------------------------

+# _initParse: OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub _initParse($) {

+  my($sFile)=@_;

+  my $oIo;

+  #1. $sFile is Ref of scalar

+  if(ref($sFile) eq 'SCALAR') {

+    require IO::Scalar;

+    $oIo = new IO::Scalar;

+    $oIo->open($sFile);

+  }

+  #2. $sFile is a IO::Handle object

+  elsif(UNIVERSAL::isa($sFile, 'IO::Handle')) {

+    $oIo = $sFile;

+    binmode($oIo);

+  }

+  #3. $sFile is a simple filename string

+  elsif(!ref($sFile)) {

+    $oIo = new IO::File;

+    $oIo->open("<$sFile") || return undef;

+    binmode($oIo);

+  }

+  #4 Assume that if $sFile is a ref then it is a valid filehandle

+  else {

+    $oIo = $sFile;

+    # Not all filehandles support binmode() so try it in an eval.

+    eval{ binmode $oIo };

+  }

+  return _getHeaderInfo($oIo);

+}

+#------------------------------------------------------------------------------

+# _getPpsTree: OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub _getPpsTree($$$;$) {

+  my($iNo, $rhInfo, $bData, $raDone) = @_;

+  if(defined($raDone)) {

+    return () if(grep {$_ ==$iNo} @$raDone);

+  }

+  else {

+    $raDone=[];

+  }

+  push @$raDone, $iNo;

+

+  my $iRootBlock = $rhInfo->{_ROOT_START} ;

+#1. Get Information about itself

+  my $oPps = _getNthPps($iNo, $rhInfo, $bData);

+#2. Child

+  if($oPps->{DirPps} !=  0xFFFFFFFF) {

+    my @aChildL = _getPpsTree($oPps->{DirPps}, $rhInfo, $bData, $raDone);

+    $oPps->{Child} =  \@aChildL;

+  }

+  else {

+    $oPps->{Child} =  undef;

+  }

+#3. Previous,Next PPSs

+  my @aList = ();

+  push @aList, _getPpsTree($oPps->{PrevPps}, $rhInfo, $bData, $raDone)

+                        if($oPps->{PrevPps} != 0xFFFFFFFF);

+  push @aList, $oPps;

+  push @aList, _getPpsTree($oPps->{NextPps}, $rhInfo, $bData, $raDone)

+                if($oPps->{NextPps} != 0xFFFFFFFF);

+  return @aList;

+}

+#------------------------------------------------------------------------------

+# _getPpsSearch: OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub _getPpsSearch($$$$$;$) {

+  my($iNo, $rhInfo, $raName, $bData, $iCase, $raDone) = @_;

+  my $iRootBlock = $rhInfo->{_ROOT_START} ;

+  my @aRes;

+#1. Check it self

+  if(defined($raDone)) {

+    return () if(grep {$_==$iNo} @$raDone);

+  }

+  else {

+    $raDone=[];

+  }

+  push @$raDone, $iNo;

+  my $oPps = _getNthPps($iNo, $rhInfo, undef);

+#  if(grep($_ eq $oPps->{Name}, @$raName)) {

+  if(($iCase && (grep(/^\Q$oPps->{Name}\E$/i, @$raName))) ||

+     (grep($_ eq $oPps->{Name}, @$raName))) {

+    $oPps = _getNthPps($iNo, $rhInfo, $bData) if ($bData);

+    @aRes = ($oPps);

+  }

+  else {

+    @aRes = ();

+  }

+#2. Check Child, Previous, Next PPSs

+  push @aRes, _getPpsSearch($oPps->{DirPps},  $rhInfo, $raName, $bData, $iCase, $raDone)

+        if($oPps->{DirPps} !=  0xFFFFFFFF) ;

+  push @aRes, _getPpsSearch($oPps->{PrevPps}, $rhInfo, $raName, $bData, $iCase, $raDone)

+        if($oPps->{PrevPps} != 0xFFFFFFFF );

+  push @aRes, _getPpsSearch($oPps->{NextPps}, $rhInfo, $raName, $bData, $iCase, $raDone)

+        if($oPps->{NextPps} != 0xFFFFFFFF);

+  return @aRes;

+}

+#===================================================================

+# Get Header Info (BASE Informain about that file)

+#===================================================================

+sub _getHeaderInfo($){

+  my($FILE) = @_;

+  my($iWk);

+  my $rhInfo = {};

+  $rhInfo->{_FILEH_} = $FILE;

+  my $sWk;

+#0. Check ID

+  $rhInfo->{_FILEH_}->seek(0, 0);

+  $rhInfo->{_FILEH_}->read($sWk, 8);

+  return undef unless($sWk eq "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1");

+#BIG BLOCK SIZE

+  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x1E, 2, "v");

+  return undef unless(defined($iWk));

+  $rhInfo->{_BIG_BLOCK_SIZE} = 2 ** $iWk;

+#SMALL BLOCK SIZE

+  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x20, 2, "v");

+  return undef unless(defined($iWk));

+  $rhInfo->{_SMALL_BLOCK_SIZE} = 2 ** $iWk;

+#BDB Count

+  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x2C, 4, "V");

+  return undef unless(defined($iWk));

+  $rhInfo->{_BDB_COUNT} = $iWk;

+#START BLOCK

+  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x30, 4, "V");

+  return undef unless(defined($iWk));

+  $rhInfo->{_ROOT_START} = $iWk;

+#MIN SIZE OF BB

+#  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x38, 4, "V");

+#  return undef unless(defined($iWk));

+#  $rhInfo->{_MIN_SIZE_BB} = $iWk;

+#SMALL BD START

+  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x3C, 4, "V");

+  return undef unless(defined($iWk));

+  $rhInfo->{_SBD_START} = $iWk;

+#SMALL BD COUNT

+  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x40, 4, "V");

+  return undef unless(defined($iWk));

+  $rhInfo->{_SBD_COUNT} = $iWk;

+#EXTRA BBD START

+  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x44, 4, "V");

+  return undef unless(defined($iWk));

+  $rhInfo->{_EXTRA_BBD_START} = $iWk;

+#EXTRA BD COUNT

+  $iWk = _getInfoFromFile($rhInfo->{_FILEH_}, 0x48, 4, "V");

+  return undef unless(defined($iWk));

+  $rhInfo->{_EXTRA_BBD_COUNT} = $iWk;

+#GET BBD INFO

+  $rhInfo->{_BBD_INFO}= _getBbdInfo($rhInfo);

+#GET ROOT PPS

+  my $oRoot = _getNthPps(0, $rhInfo, undef);

+  $rhInfo->{_SB_START} = $oRoot->{StartBlock};

+  $rhInfo->{_SB_SIZE}  = $oRoot->{Size};

+  return $rhInfo;

+}

+#------------------------------------------------------------------------------

+# _getInfoFromFile

+#------------------------------------------------------------------------------

+sub _getInfoFromFile($$$$) {

+  my($FILE, $iPos, $iLen, $sFmt) =@_;

+  my($sWk);

+  return undef unless($FILE);

+  return undef if($FILE->seek($iPos, 0)==0);

+  return undef if($FILE->read($sWk,  $iLen)!=$iLen);

+  return unpack($sFmt, $sWk);

+}

+#------------------------------------------------------------------------------

+# _getBbdInfo

+#------------------------------------------------------------------------------

+sub _getBbdInfo($) {

+  my($rhInfo) =@_;

+  my @aBdList = ();

+  my $iBdbCnt = $rhInfo->{_BDB_COUNT};

+  my $iGetCnt;

+  my $sWk;

+  my $i1stCnt = int(($rhInfo->{_BIG_BLOCK_SIZE} - 0x4C) / OLE::Storage_Lite::LongIntSize());

+  my $iBdlCnt = int($rhInfo->{_BIG_BLOCK_SIZE} / OLE::Storage_Lite::LongIntSize()) - 1;

+

+#1. 1st BDlist

+  $rhInfo->{_FILEH_}->seek(0x4C, 0);

+  $iGetCnt = ($iBdbCnt < $i1stCnt)? $iBdbCnt: $i1stCnt;

+  $rhInfo->{_FILEH_}->read($sWk, OLE::Storage_Lite::LongIntSize()*$iGetCnt);

+  push @aBdList, unpack("V$iGetCnt", $sWk);

+  $iBdbCnt -= $iGetCnt;

+#2. Extra BDList

+  my $iBlock = $rhInfo->{_EXTRA_BBD_START};

+  while(($iBdbCnt> 0) && _isNormalBlock($iBlock)){

+    _setFilePos($iBlock, 0, $rhInfo);

+    $iGetCnt= ($iBdbCnt < $iBdlCnt)? $iBdbCnt: $iBdlCnt;

+    $rhInfo->{_FILEH_}->read($sWk, OLE::Storage_Lite::LongIntSize()*$iGetCnt);

+    push @aBdList, unpack("V$iGetCnt", $sWk);

+    $iBdbCnt -= $iGetCnt;

+    $rhInfo->{_FILEH_}->read($sWk, OLE::Storage_Lite::LongIntSize());

+    $iBlock = unpack("V", $sWk);

+  }

+#3.Get BDs

+  my @aWk;

+  my %hBd;

+  my $iBlkNo = 0;

+  my $iBdL;

+  my $i;

+  my $iBdCnt = int($rhInfo->{_BIG_BLOCK_SIZE} / OLE::Storage_Lite::LongIntSize());

+  foreach $iBdL (@aBdList) {

+    _setFilePos($iBdL, 0, $rhInfo);

+    $rhInfo->{_FILEH_}->read($sWk, $rhInfo->{_BIG_BLOCK_SIZE});

+    @aWk = unpack("V$iBdCnt", $sWk);

+    for($i=0;$i<$iBdCnt;$i++, $iBlkNo++) {

+       if($aWk[$i] != ($iBlkNo+1)){

+            $hBd{$iBlkNo} = $aWk[$i];

+        }

+    }

+  }

+  return \%hBd;

+}

+#------------------------------------------------------------------------------

+# getNthPps (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _getNthPps($$$){

+  my($iPos, $rhInfo, $bData) = @_;

+  my($iPpsStart) = ($rhInfo->{_ROOT_START});

+  my($iPpsBlock, $iPpsPos);

+  my $sWk;

+  my $iBlock;

+

+  my $iBaseCnt = $rhInfo->{_BIG_BLOCK_SIZE} / OLE::Storage_Lite::PpsSize();

+  $iPpsBlock = int($iPos / $iBaseCnt);

+  $iPpsPos   = $iPos % $iBaseCnt;

+

+  $iBlock = _getNthBlockNo($iPpsStart, $iPpsBlock, $rhInfo);

+  return undef unless(defined($iBlock));

+

+  _setFilePos($iBlock, OLE::Storage_Lite::PpsSize()*$iPpsPos, $rhInfo);

+  $rhInfo->{_FILEH_}->read($sWk, OLE::Storage_Lite::PpsSize());

+  return undef unless($sWk);

+  my $iNmSize = unpack("v", substr($sWk, 0x40, 2));

+  $iNmSize = ($iNmSize > 2)? $iNmSize - 2 : $iNmSize;

+  my $sNm= substr($sWk, 0, $iNmSize);

+  my $iType = unpack("C", substr($sWk, 0x42, 2));

+  my $lPpsPrev = unpack("V", substr($sWk, 0x44, OLE::Storage_Lite::LongIntSize()));

+  my $lPpsNext = unpack("V", substr($sWk, 0x48, OLE::Storage_Lite::LongIntSize()));

+  my $lDirPps  = unpack("V", substr($sWk, 0x4C, OLE::Storage_Lite::LongIntSize()));

+  my @raTime1st =

+        (($iType == OLE::Storage_Lite::PpsType_Root()) or ($iType == OLE::Storage_Lite::PpsType_Dir()))?

+            OLEDate2Local(substr($sWk, 0x64, 8)) : undef ,

+  my @raTime2nd =

+        (($iType == OLE::Storage_Lite::PpsType_Root()) or ($iType == OLE::Storage_Lite::PpsType_Dir()))?

+            OLEDate2Local(substr($sWk, 0x6C, 8)) : undef,

+  my($iStart, $iSize) = unpack("VV", substr($sWk, 0x74, 8));

+  if($bData) {

+      my $sData = _getData($iType, $iStart, $iSize, $rhInfo);

+      return OLE::Storage_Lite::PPS->new(

+        $iPos, $sNm, $iType, $lPpsPrev, $lPpsNext, $lDirPps,

+        \@raTime1st, \@raTime2nd, $iStart, $iSize, $sData, undef);

+  }

+  else {

+      return OLE::Storage_Lite::PPS->new(

+        $iPos, $sNm, $iType, $lPpsPrev, $lPpsNext, $lDirPps,

+        \@raTime1st, \@raTime2nd, $iStart, $iSize, undef, undef);

+  }

+}

+#------------------------------------------------------------------------------

+# _setFilePos (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _setFilePos($$$){

+  my($iBlock, $iPos, $rhInfo) = @_;

+  $rhInfo->{_FILEH_}->seek(($iBlock+1)*$rhInfo->{_BIG_BLOCK_SIZE}+$iPos, 0);

+}

+#------------------------------------------------------------------------------

+# _getNthBlockNo (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _getNthBlockNo($$$){

+  my($iStBlock, $iNth, $rhInfo) = @_;

+  my $iSv;

+  my $iNext = $iStBlock;

+  for(my $i =0; $i<$iNth; $i++) {

+    $iSv = $iNext;

+    $iNext = _getNextBlockNo($iSv, $rhInfo);

+    return undef unless _isNormalBlock($iNext);

+  }

+  return $iNext;

+}

+#------------------------------------------------------------------------------

+# _getData (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _getData($$$$)

+{

+  my($iType, $iBlock, $iSize, $rhInfo) = @_;

+  if ($iType == OLE::Storage_Lite::PpsType_File()) {

+    if($iSize < OLE::Storage_Lite::DataSizeSmall()) {

+        return _getSmallData($iBlock, $iSize, $rhInfo);

+    }

+    else {

+        return _getBigData($iBlock, $iSize, $rhInfo);

+    }

+  }

+  elsif($iType == OLE::Storage_Lite::PpsType_Root()) {  #Root

+    return _getBigData($iBlock, $iSize, $rhInfo);

+  }

+  elsif($iType == OLE::Storage_Lite::PpsType_Dir()) {  # Directory

+    return undef;

+  }

+}

+#------------------------------------------------------------------------------

+# _getBigData (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _getBigData($$$)

+{

+  my($iBlock, $iSize, $rhInfo) = @_;

+  my($iRest, $sWk, $sRes);

+

+  return '' unless(_isNormalBlock($iBlock));

+  $iRest = $iSize;

+  my($i, $iGetSize, $iNext);

+  $sRes = '';

+  my @aKeys= sort({$a<=>$b} keys(%{$rhInfo->{_BBD_INFO}}));

+

+  while ($iRest > 0) {

+    my @aRes = grep($_ >= $iBlock, @aKeys);

+    my $iNKey = $aRes[0];

+    $i = $iNKey - $iBlock;

+    $iNext = $rhInfo->{_BBD_INFO}{$iNKey};

+    _setFilePos($iBlock, 0, $rhInfo);

+    my $iGetSize = ($rhInfo->{_BIG_BLOCK_SIZE} * ($i+1));

+    $iGetSize = $iRest if($iRest < $iGetSize);

+    $rhInfo->{_FILEH_}->read( $sWk, $iGetSize);

+    $sRes .= $sWk;

+    $iRest -= $iGetSize;

+    $iBlock= $iNext;

+  }

+  return $sRes;

+}

+#------------------------------------------------------------------------------

+# _getNextBlockNo (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _getNextBlockNo($$){

+  my($iBlockNo, $rhInfo) = @_;

+  my $iRes = $rhInfo->{_BBD_INFO}->{$iBlockNo};

+  return defined($iRes)? $iRes: $iBlockNo+1;

+}

+#------------------------------------------------------------------------------

+# _isNormalBlock (OLE::Storage_Lite)

+# 0xFFFFFFFC : BDList, 0xFFFFFFFD : BBD,

+# 0xFFFFFFFE: End of Chain 0xFFFFFFFF : unused

+#------------------------------------------------------------------------------

+sub _isNormalBlock($){

+  my($iBlock) = @_;

+  return ($iBlock < 0xFFFFFFFC)? 1: undef;

+}

+#------------------------------------------------------------------------------

+# _getSmallData (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _getSmallData($$$)

+{

+  my($iSmBlock, $iSize, $rhInfo) = @_;

+  my($sRes, $sWk);

+  my $iRest = $iSize;

+  $sRes = '';

+  while ($iRest > 0) {

+    _setFilePosSmall($iSmBlock, $rhInfo);

+    $rhInfo->{_FILEH_}->read($sWk,

+        ($iRest >= $rhInfo->{_SMALL_BLOCK_SIZE})?

+            $rhInfo->{_SMALL_BLOCK_SIZE}: $iRest);

+    $sRes .= $sWk;

+    $iRest -= $rhInfo->{_SMALL_BLOCK_SIZE};

+    $iSmBlock= _getNextSmallBlockNo($iSmBlock, $rhInfo);

+  }

+  return $sRes;

+}

+#------------------------------------------------------------------------------

+# _setFilePosSmall(OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _setFilePosSmall($$)

+{

+  my($iSmBlock, $rhInfo) = @_;

+  my $iSmStart = $rhInfo->{_SB_START};

+  my $iBaseCnt = $rhInfo->{_BIG_BLOCK_SIZE} / $rhInfo->{_SMALL_BLOCK_SIZE};

+  my $iNth = int($iSmBlock/$iBaseCnt);

+  my $iPos = $iSmBlock % $iBaseCnt;

+

+  my $iBlk = _getNthBlockNo($iSmStart, $iNth, $rhInfo);

+  _setFilePos($iBlk, $iPos * $rhInfo->{_SMALL_BLOCK_SIZE}, $rhInfo);

+}

+#------------------------------------------------------------------------------

+# _getNextSmallBlockNo (OLE::Storage_Lite)

+#------------------------------------------------------------------------------

+sub _getNextSmallBlockNo($$)

+{

+  my($iSmBlock, $rhInfo) = @_;

+  my($sWk);

+

+  my $iBaseCnt = $rhInfo->{_BIG_BLOCK_SIZE} / OLE::Storage_Lite::LongIntSize();

+  my $iNth = int($iSmBlock/$iBaseCnt);

+  my $iPos = $iSmBlock % $iBaseCnt;

+  my $iBlk = _getNthBlockNo($rhInfo->{_SBD_START}, $iNth, $rhInfo);

+  _setFilePos($iBlk, $iPos * OLE::Storage_Lite::LongIntSize(), $rhInfo);

+  $rhInfo->{_FILEH_}->read($sWk, OLE::Storage_Lite::LongIntSize());

+  return unpack("V", $sWk);

+

+}

+#------------------------------------------------------------------------------

+# Asc2Ucs: OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub Asc2Ucs($)

+{

+  my($sAsc) = @_;

+  return join("\x00", split //, $sAsc) . "\x00";

+}

+#------------------------------------------------------------------------------

+# Ucs2Asc: OLE::Storage_Lite

+#------------------------------------------------------------------------------

+sub Ucs2Asc($)

+{

+  my($sUcs) = @_;

+  return join('', map(pack('c', $_), unpack('v*', $sUcs)));

+}

+

+#------------------------------------------------------------------------------

+# OLEDate2Local()

+#

+# Convert from a Window FILETIME structure to a localtime array. FILETIME is

+# a 64-bit value representing the number of 100-nanosecond intervals since

+# January 1 1601.

+#

+# We first convert the FILETIME to seconds and then subtract the difference

+# between the 1601 epoch and the 1970 Unix epoch.

+#

+sub OLEDate2Local {

+

+    my $oletime = shift;

+

+    # Unpack the FILETIME into high and low longs.

+    my ( $lo, $hi ) = unpack 'V2', $oletime;

+

+    # Convert the longs to a double.

+    my $nanoseconds = $hi * 2**32 + $lo;

+

+    # Convert the 100 nanosecond units into seconds.

+    my $time = $nanoseconds / 1e7;

+

+    # Subtract the number of seconds between the 1601 and 1970 epochs.

+    $time -= 11644473600;

+

+    # Convert to a localtime (actually gmtime) structure.

+    my @localtime = gmtime($time);

+

+    return @localtime;

+}

+

+#------------------------------------------------------------------------------

+# LocalDate2OLE()

+#

+# Convert from a a localtime array to a Window FILETIME structure. FILETIME is

+# a 64-bit value representing the number of 100-nanosecond intervals since

+# January 1 1601.

+#

+# We first convert the localtime (actually gmtime) to seconds and then add the

+# difference between the 1601 epoch and the 1970 Unix epoch. We convert that to

+# 100 nanosecond units, divide it into high and low longs and return it as a

+# packed 64bit structure.

+#

+sub LocalDate2OLE {

+

+    my $localtime = shift;

+

+    return "\x00" x 8 unless $localtime;

+

+    # Convert from localtime (actually gmtime) to seconds.

+    my $time = timegm( @{$localtime} );

+

+    # Add the number of seconds between the 1601 and 1970 epochs.

+    $time += 11644473600;

+

+    # The FILETIME seconds are in units of 100 nanoseconds.

+    my $nanoseconds = $time * 1E7;

+

+use POSIX 'fmod';

+

+    # Pack the total nanoseconds into 64 bits...

+    my $hi = int( $nanoseconds / 2**32 );

+    my $lo = fmod($nanoseconds, 2**32);

+

+    my $oletime = pack "VV", $lo, $hi;

+

+    return $oletime;

+}

+

+1;

+__END__

+

+

+=head1 NAME

+

+OLE::Storage_Lite - Simple Class for OLE document interface.

+

+=head1 SYNOPSIS

+

+    use OLE::Storage_Lite;

+

+    # Initialize.

+

+    # From a file

+    my $oOl = OLE::Storage_Lite->new("some.xls");

+

+    # From a filehandle object

+    use IO::File;

+    my $oIo = new IO::File;

+    $oIo->open("<iofile.xls");

+    binmode($oIo);

+    my $oOl = OLE::Storage_Lite->new($oFile);

+

+    # Read data

+    my $oPps = $oOl->getPpsTree(1);

+

+    # Save Data

+    # To a File

+    $oPps->save("kaba.xls"); #kaba.xls

+    $oPps->save('-');        #STDOUT

+

+    # To a filehandle object

+    my $oIo = new IO::File;

+    $oIo->open(">iofile.xls");

+    bimode($oIo);

+    $oPps->save($oIo);

+

+

+=head1 DESCRIPTION

+

+OLE::Storage_Lite allows you to read and write an OLE structured file.

+

+OLE::Storage_Lite::PPS is a class representing PPS. OLE::Storage_Lite::PPS::Root, OLE::Storage_Lite::PPS::File and OLE::Storage_Lite::PPS::Dir

+are subclasses of OLE::Storage_Lite::PPS.

+

+

+=head2 new()

+

+Constructor.

+

+    $oOle = OLE::Storage_Lite->new($sFile);

+

+Creates a OLE::Storage_Lite object for C<$sFile>. C<$sFile> must be a correct file name.

+

+The C<new()> constructor also accepts a valid filehandle. Remember to C<binmode()> the filehandle first.

+

+

+=head2 getPpsTree()

+

+    $oPpsRoot = $oOle->getPpsTree([$bData]);

+

+Returns PPS as an OLE::Storage_Lite::PPS::Root object.

+Other PPS objects will be included as its children.

+

+If C<$bData> is true, the objects will have data in the file.

+

+

+=head2 getPpsSearch()

+

+    $oPpsRoot = $oOle->getPpsTree($raName [, $bData][, $iCase] );

+

+Returns PPSs as OLE::Storage_Lite::PPS objects that has the name specified in C<$raName> array.

+

+If C<$bData> is true, the objects will have data in the file.

+If C<$iCase> is true, search is case insensitive.

+

+

+=head2 getNthPps()

+

+    $oPpsRoot = $oOle->getNthPps($iNth [, $bData]);

+

+Returns PPS as C<OLE::Storage_Lite::PPS> object specified number C<$iNth>.

+

+If C<$bData> is true, the objects will have data in the file.

+

+

+=head2 Asc2Ucs()

+

+    $sUcs2 = OLE::Storage_Lite::Asc2Ucs($sAsc>);

+

+Utility function. Just adds 0x00 after every characters in C<$sAsc>.

+

+

+=head2 Ucs2Asc()

+

+    $sAsc = OLE::Storage_Lite::Ucs2Asc($sUcs2);

+

+Utility function. Just deletes 0x00 after words in C<$sUcs>.

+

+

+=head1 OLE::Storage_Lite::PPS

+

+OLE::Storage_Lite::PPS has these properties:

+

+=over 4

+

+=item No

+

+Order number in saving.

+

+=item Name

+

+Its name in UCS2 (a.k.a Unicode).

+

+=item Type

+

+Its type (1:Dir, 2:File (Data), 5: Root)

+

+=item PrevPps

+

+Previous pps (as No)

+

+=item NextPps

+

+Next pps (as No)

+

+=item DirPps

+

+Dir pps (as No).

+

+=item Time1st

+

+Timestamp 1st in array ref as similar fomat of localtime.

+

+=item Time2nd

+

+Timestamp 2nd in array ref as similar fomat of localtime.

+

+=item StartBlock

+

+Start block number

+

+=item Size

+

+Size of the pps

+

+=item Data

+

+Its data

+

+=item Child

+

+Its child PPSs in array ref

+

+=back

+

+

+=head1 OLE::Storage_Lite::PPS::Root

+

+OLE::Storage_Lite::PPS::Root has 2 methods.

+

+=head2 new()

+

+    $oRoot = OLE::Storage_Lite::PPS::Root->new(

+                    $raTime1st,

+                    $raTime2nd,

+                    $raChild);

+

+

+Constructor.

+

+C<$raTime1st>, C<$raTime2nd> are array refs with ($iSec, $iMin, $iHour, $iDay, $iMon, $iYear).

+$iSec means seconds, $iMin means minutes. $iHour means hours.

+$iDay means day. $iMon is month -1. $iYear is year - 1900.

+

+C<$raChild> is a array ref of children PPSs.

+

+

+=head2 save()

+

+    $oRoot = $oRoot>->save(

+                    $sFile,

+                    $bNoAs);

+

+

+Saves information into C<$sFile>. If C<$sFile> is '-', this will use STDOUT.

+

+The C<new()> constructor also accepts a valid filehandle. Remember to C<binmode()> the filehandle first.

+

+If C<$bNoAs> is defined, this function will use the No of PPSs for saving order.

+If C<$bNoAs> is undefined, this will calculate PPS saving order.

+

+

+=head1 OLE::Storage_Lite::PPS::Dir

+

+OLE::Storage_Lite::PPS::Dir has 1 method.

+

+=head2 new()

+

+    $oRoot = OLE::Storage_Lite::PPS::Dir->new(

+                    $sName,

+                  [, $raTime1st]

+                  [, $raTime2nd]

+                  [, $raChild>]);

+

+

+Constructor.

+

+C<$sName> is a name of the PPS.

+

+C<$raTime1st>, C<$raTime2nd> is a array ref as

+($iSec, $iMin, $iHour, $iDay, $iMon, $iYear).

+$iSec means seconds, $iMin means minutes. $iHour means hours.

+$iDay means day. $iMon is month -1. $iYear is year - 1900.

+

+C<$raChild> is a array ref of children PPSs.

+

+

+=head1 OLE::Storage_Lite::PPS::File

+

+OLE::Storage_Lite::PPS::File has 3 method.

+

+=head2 new

+

+    $oRoot = OLE::Storage_Lite::PPS::File->new($sName, $sData);

+

+C<$sName> is name of the PPS.

+

+C<$sData> is data of the PPS.

+

+

+=head2 newFile()

+

+    $oRoot = OLE::Storage_Lite::PPS::File->newFile($sName, $sFile);

+

+This function makes to use file handle for geting and storing data.

+

+C<$sName> is name of the PPS.

+

+If C<$sFile> is scalar, it assumes that is a filename.

+If C<$sFile> is an IO::Handle object, it uses that specified handle.

+If C<$sFile> is undef or '', it uses temporary file.

+

+CAUTION: Take care C<$sFile> will be updated by C<append> method.

+So if you want to use IO::Handle and append a data to it,

+you should open the handle with "r+".

+

+

+=head2 append()

+

+    $oRoot = $oPps->append($sData);

+

+appends specified data to that PPS.

+

+C<$sData> is appending data for that PPS.

+

+

+=head1 CAUTION

+

+A saved file with VBA (a.k.a Macros) by this module will not work correctly.

+However modules can get the same information from the file,

+the file occurs a error in application(Word, Excel ...).

+

+

+=head1 DEPRECATED FEATURES

+

+Older version of C<OLE::Storage_Lite> autovivified a scalar ref in the C<new()> constructors into a scalar filehandle. This functionality is still there for backwards compatibility but it is highly recommended that you do not use it. Instead create a filehandle (scalar or otherwise) and pass that in.

+

+

+=head1 COPYRIGHT

+

+The OLE::Storage_Lite module is Copyright (c) 2000,2001 Kawai Takanori. Japan.

+All rights reserved.

+

+You may distribute under the terms of either the GNU General Public

+License or the Artistic License, as specified in the Perl README file.

+

+

+=head1 ACKNOWLEDGEMENTS

+

+First of all, I would like to acknowledge to Martin Schwartz and his module OLE::Storage.

+

+

+=head1 AUTHOR

+

+Kawai Takanori kwitknr@cpan.org

+

+This module is currently maintained by John McNamara jmcnamara@cpan.org

+

+

+=head1 SEE ALSO

+

+OLE::Storage

+

+Documentation for the OLE Compound document has been released by Microsoft under the I<Open Specification Promise>. See http://www.microsoft.com/interop/docs/supportingtechnologies.mspx

+

+The Digital Imaging Group have also detailed the OLE format in the JPEG2000 specification: see Appendix A of http://www.i3a.org/pdf/wg1n1017.pdf

+

+

+=cut

diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel.pm
new file mode 100644
index 0000000..57554aa
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel.pm
@@ -0,0 +1,3323 @@
+package Spreadsheet::ParseExcel;
+
+##############################################################################
+#
+# Spreadsheet::ParseExcel - Extract information from an Excel file.
+#
+# Copyright 2000-2008, Takanori Kawai
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+use 5.008;
+
+use OLE::Storage_Lite;
+use IO::File;
+use Config;
+
+use Crypt::RC4;
+use Digest::Perl::MD5;
+
+our $VERSION = '0.59';
+
+use Spreadsheet::ParseExcel::Workbook;
+use Spreadsheet::ParseExcel::Worksheet;
+use Spreadsheet::ParseExcel::Font;
+use Spreadsheet::ParseExcel::Format;
+use Spreadsheet::ParseExcel::Cell;
+use Spreadsheet::ParseExcel::FmtDefault;
+
+my @aColor = (
+    '000000',    # 0x00
+    'FFFFFF', 'FFFFFF', 'FFFFFF', 'FFFFFF',
+    'FFFFFF', 'FFFFFF', 'FFFFFF', 'FFFFFF',    # 0x08
+    'FFFFFF', 'FF0000', '00FF00', '0000FF',
+    'FFFF00', 'FF00FF', '00FFFF', '800000',    # 0x10
+    '008000', '000080', '808000', '800080',
+    '008080', 'C0C0C0', '808080', '9999FF',    # 0x18
+    '993366', 'FFFFCC', 'CCFFFF', '660066',
+    'FF8080', '0066CC', 'CCCCFF', '000080',    # 0x20
+    'FF00FF', 'FFFF00', '00FFFF', '800080',
+    '800000', '008080', '0000FF', '00CCFF',    # 0x28
+    'CCFFFF', 'CCFFCC', 'FFFF99', '99CCFF',
+    'FF99CC', 'CC99FF', 'FFCC99', '3366FF',    # 0x30
+    '33CCCC', '99CC00', 'FFCC00', 'FF9900',
+    'FF6600', '666699', '969696', '003366',    # 0x38
+    '339966', '003300', '333300', '993300',
+    '993366', '333399', '333333', 'FFFFFF'     # 0x40
+);
+use constant verExcel95 => 0x500;
+use constant verExcel97 => 0x600;
+use constant verBIFF2   => 0x00;
+use constant verBIFF3   => 0x02;
+use constant verBIFF4   => 0x04;
+use constant verBIFF5   => 0x08;
+use constant verBIFF8   => 0x18;
+
+use constant MS_BIFF_CRYPTO_NONE => 0;
+use constant MS_BIFF_CRYPTO_XOR  => 1;
+use constant MS_BIFF_CRYPTO_RC4  => 2;
+
+use constant sizeof_BIFF_8_FILEPASS => ( 6 + 3 * 16 );
+
+use constant REKEY_BLOCK => 0x400;
+
+# Error code for some of the common parsing errors.
+use constant ErrorNone          => 0;
+use constant ErrorNoFile        => 1;
+use constant ErrorNoExcelData   => 2;
+use constant ErrorFileEncrypted => 3;
+
+our %error_strings = (
+    ErrorNone,          '',                               # 0
+    ErrorNoFile,        'File not found',                 # 1
+    ErrorNoExcelData,   'No Excel data found in file',    # 2
+    ErrorFileEncrypted, 'File is encrypted',              # 3
+
+);
+
+
+our %ProcTbl = (
+
+    #Develpers' Kit P291
+    0x14   => \&_subHeader,            # Header
+    0x15   => \&_subFooter,            # Footer
+    0x18   => \&_subName,              # NAME(?)
+    0x1A   => \&_subVPageBreak,        # Vertical Page Break
+    0x1B   => \&_subHPageBreak,        # Horizontal Page Break
+    0x22   => \&_subFlg1904,           # 1904 Flag
+    0x26   => \&_subMargin,            # Left Margin
+    0x27   => \&_subMargin,            # Right Margin
+    0x28   => \&_subMargin,            # Top Margin
+    0x29   => \&_subMargin,            # Bottom Margin
+    0x2A   => \&_subPrintHeaders,      # Print Headers
+    0x2B   => \&_subPrintGridlines,    # Print Gridlines
+    0x3C   => \&_subContinue,          # Continue
+    0x43   => \&_subXF,                # XF for Excel < 4.
+    0x0443 => \&_subXF,                # XF for Excel = 4.
+
+    #Develpers' Kit P292
+    0x55 => \&_subDefColWidth,         # Consider
+    0x5C => \&_subWriteAccess,         # WRITEACCESS
+    0x7D => \&_subColInfo,             # Colinfo
+    0x7E => \&_subRK,                  # RK
+    0x81 => \&_subWSBOOL,              # WSBOOL
+    0x83 => \&_subHcenter,             # HCENTER
+    0x84 => \&_subVcenter,             # VCENTER
+    0x85 => \&_subBoundSheet,          # BoundSheet
+
+    0x92 => \&_subPalette,             # Palette, fgp
+
+    0x99 => \&_subStandardWidth,       # Standard Col
+
+    #Develpers' Kit P293
+    0xA1 => \&_subSETUP,               # SETUP
+    0xBD => \&_subMulRK,               # MULRK
+    0xBE => \&_subMulBlank,            # MULBLANK
+    0xD6 => \&_subRString,             # RString
+
+    #Develpers' Kit P294
+    0xE0 => \&_subXF,                  # ExTended Format
+    0xE5 => \&_subMergeArea,           # MergeArea (Not Documented)
+    0xFC => \&_subSST,                 # Shared String Table
+    0xFD => \&_subLabelSST,            # Label SST
+
+    #Develpers' Kit P295
+    0x201 => \&_subBlank,              # Blank
+
+    0x202 => \&_subInteger,            # Integer(Not Documented)
+    0x203 => \&_subNumber,             # Number
+    0x204 => \&_subLabel,              # Label
+    0x205 => \&_subBoolErr,            # BoolErr
+    0x207 => \&_subString,             # STRING
+    0x208 => \&_subRow,                # RowData
+    0x221 => \&_subArray,              # Array (Consider)
+    0x225 => \&_subDefaultRowHeight,   # Consider
+
+    0x31  => \&_subFont,               # Font
+    0x231 => \&_subFont,               # Font
+
+    0x27E => \&_subRK,                 # RK
+    0x41E => \&_subFormat,             # Format
+
+    0x06  => \&_subFormula,            # Formula
+    0x406 => \&_subFormula,            # Formula
+
+    0x009 => \&_subBOF,                # BOF(BIFF2)
+    0x209 => \&_subBOF,                # BOF(BIFF3)
+    0x409 => \&_subBOF,                # BOF(BIFF4)
+    0x809 => \&_subBOF,                # BOF(BIFF5-8)
+);
+
+our $BIGENDIAN;
+our $PREFUNC;
+our $_CellHandler;
+our $_NotSetCell;
+our $_Object;
+our $_use_perlio;
+
+#------------------------------------------------------------------------------
+# Spreadsheet::ParseExcel->new
+#------------------------------------------------------------------------------
+sub new {
+    my ( $class, %hParam ) = @_;
+
+    if ( not defined $_use_perlio ) {
+        if (   exists $Config{useperlio}
+            && defined $Config{useperlio}
+            && $Config{useperlio} eq "define" )
+        {
+            $_use_perlio = 1;
+        }
+        else {
+            $_use_perlio = 0;
+            require IO::Scalar;
+            import IO::Scalar;
+        }
+    }
+
+    # Check ENDIAN(Little: Intel etc. BIG: Sparc etc)
+    $BIGENDIAN =
+        ( defined $hParam{Endian} ) ? $hParam{Endian}
+      : ( unpack( "H08", pack( "L", 2 ) ) eq '02000000' ) ? 0
+      :                                                     1;
+    my $self = {};
+    bless $self, $class;
+
+    $self->{GetContent} = \&_subGetContent;
+
+    if ( $hParam{EventHandlers} ) {
+        $self->SetEventHandlers( $hParam{EventHandlers} );
+    }
+    else {
+        $self->SetEventHandlers( \%ProcTbl );
+    }
+    if ( $hParam{AddHandlers} ) {
+        foreach my $sKey ( keys( %{ $hParam{AddHandlers} } ) ) {
+            $self->SetEventHandler( $sKey, $hParam{AddHandlers}->{$sKey} );
+        }
+    }
+    $_CellHandler = $hParam{CellHandler} if ( $hParam{CellHandler} );
+    $_NotSetCell  = $hParam{NotSetCell};
+    $_Object      = $hParam{Object};
+
+
+    if ( defined $hParam{Password} ) {
+        $self->{Password} = $hParam{Password};
+    }
+    else {
+        $self->{Password} = 'VelvetSweatshop';
+    }
+
+    $self->{_error_status} = ErrorNone;
+    return $self;
+}
+
+#------------------------------------------------------------------------------
+# Spreadsheet::ParseExcel->SetEventHandler
+#------------------------------------------------------------------------------
+sub SetEventHandler {
+    my ( $self, $key, $sub_ref ) = @_;
+    $self->{FuncTbl}->{$key} = $sub_ref;
+}
+
+#------------------------------------------------------------------------------
+# Spreadsheet::ParseExcel->SetEventHandlers
+#------------------------------------------------------------------------------
+sub SetEventHandlers {
+    my ( $self, $rhTbl ) = @_;
+    $self->{FuncTbl} = undef;
+    foreach my $sKey ( keys %$rhTbl ) {
+        $self->{FuncTbl}->{$sKey} = $rhTbl->{$sKey};
+    }
+}
+
+#------------------------------------------------------------------------------
+# Decryption routines
+# based on sources of gnumeric (ms-biff.c ms-excel-read.c)
+#------------------------------------------------------------------------------
+sub md5state {
+    my ( $md5 ) = @_;
+    my $s = '';
+    for ( my $i = 0 ; $i < 4 ; $i++ ) {
+        my $v = $md5->{_state}[$i];
+        $s .= chr( $v & 0xff );
+        $s .= chr( ( $v >> 8 ) & 0xff );
+        $s .= chr( ( $v >> 16 ) & 0xff );
+        $s .= chr( ( $v >> 24 ) & 0xff );
+    }
+
+    return $s;
+}
+
+sub MakeKey {
+    my ( $block, $key, $valContext ) = @_;
+
+    my $pwarray = "\0" x 64;
+
+    substr( $pwarray, 0, 5 ) = substr( $valContext, 0, 5 );
+
+    substr( $pwarray, 5, 1 ) = chr( $block & 0xff );
+    substr( $pwarray, 6, 1 ) = chr( ( $block >> 8 ) & 0xff );
+    substr( $pwarray, 7, 1 ) = chr( ( $block >> 16 ) & 0xff );
+    substr( $pwarray, 8, 1 ) = chr( ( $block >> 24 ) & 0xff );
+
+    substr( $pwarray, 9,  1 ) = "\x80";
+    substr( $pwarray, 56, 1 ) = "\x48";
+
+    my $md5 = Digest::Perl::MD5->new();
+    $md5->add( $pwarray );
+
+    my $s = md5state( $md5 );
+
+    ${$key} = Crypt::RC4->new( $s );
+}
+
+sub VerifyPassword {
+    my ( $password, $docid, $salt_data, $hashedsalt_data, $valContext ) = @_;
+
+    my $pwarray = "\0" x 64;
+    my $i;
+    my $md5 = Digest::Perl::MD5->new();
+
+    for ( $i = 0 ; $i < length( $password ) ; $i++ ) {
+        my $o = ord( substr( $password, $i, 1 ) );
+        substr( $pwarray, 2 * $i, 1 ) = chr( $o & 0xff );
+        substr( $pwarray, 2 * $i + 1, 1 ) = chr( ( $o >> 8 ) & 0xff );
+    }
+    substr( $pwarray, 2 * $i, 1 ) = chr( 0x80 );
+    substr( $pwarray, 56, 1 ) = chr( ( $i << 4 ) & 0xff );
+
+    $md5->add( $pwarray );
+
+    my $mdContext1 = md5state( $md5 );
+
+    my $offset    = 0;
+    my $keyoffset = 0;
+    my $tocopy    = 5;
+
+    $md5->reset;
+
+    while ( $offset != 16 ) {
+        if ( ( 64 - $offset ) < 5 ) {
+            $tocopy = 64 - $offset;
+        }
+
+        substr( $pwarray, $offset, $tocopy ) =
+          substr( $mdContext1, $keyoffset, $tocopy );
+
+        $offset += $tocopy;
+
+        if ( $offset == 64 ) {
+            $md5->add( $pwarray );
+            $keyoffset = $tocopy;
+            $tocopy    = 5 - $tocopy;
+            $offset    = 0;
+            next;
+        }
+
+        $keyoffset = 0;
+        $tocopy    = 5;
+        substr( $pwarray, $offset, 16 ) = $docid;
+        $offset += 16;
+    }
+
+    substr( $pwarray, 16, 1 )  = "\x80";
+    substr( $pwarray, 17, 47 ) = "\0" x 47;
+    substr( $pwarray, 56, 1 )  = "\x80";
+    substr( $pwarray, 57, 1 )  = "\x0a";
+
+    $md5->add( $pwarray );
+    ${$valContext} = md5state( $md5 );
+
+    my $key;
+
+    MakeKey( 0, \$key, ${$valContext} );
+
+    my $salt       = $key->RC4( $salt_data );
+    my $hashedsalt = $key->RC4( $hashedsalt_data );
+
+    $salt .= "\x80" . "\0" x 47;
+
+    substr( $salt, 56, 1 ) = "\x80";
+
+    $md5->reset;
+    $md5->add( $salt );
+    my $mdContext2 = md5state( $md5 );
+
+    return ( $mdContext2 eq $hashedsalt );
+}
+
+sub SkipBytes {
+    my ( $q, $start, $count ) = @_;
+
+    my $scratch = "\0" x REKEY_BLOCK;
+    my $block;
+
+    $block = int( ( $start + $count ) / REKEY_BLOCK );
+
+    if ( $block != $q->{block} ) {
+        MakeKey( $q->{block} = $block, \$q->{rc4_key}, $q->{md5_ctxt} );
+        $count = ( $start + $count ) % REKEY_BLOCK;
+    }
+
+    $q->{rc4_key}->RC4( substr( $scratch, 0, $count ) );
+
+    return 1;
+}
+
+sub SetDecrypt {
+    my ( $q, $version, $password ) = @_;
+
+    if ( $q->{opcode} != 0x2f ) {
+        return 0;
+    }
+
+    if ( $password eq '' ) {
+        return 0;
+    }
+
+    # TODO old versions decryption
+    #if (version < MS_BIFF_V8 || q->data[0] == 0)
+    #    return ms_biff_pre_biff8_query_set_decrypt (q, password);
+
+    if ( $q->{length} != sizeof_BIFF_8_FILEPASS ) {
+        return 0;
+    }
+
+    unless (
+        VerifyPassword(
+            $password,
+            substr( $q->{data}, 6,  16 ),
+            substr( $q->{data}, 22, 16 ),
+            substr( $q->{data}, 38, 16 ),
+            \$q->{md5_ctxt}
+        )
+      )
+    {
+        return 0;
+    }
+
+    $q->{encryption} = MS_BIFF_CRYPTO_RC4;
+    $q->{block}      = -1;
+
+    # The first record after FILEPASS seems to be unencrypted
+    $q->{dont_decrypt_next_record} = 1;
+
+    # Pretend to decrypt the entire stream up till this point, it was
+    # encrypted, but do it anyway to keep the rc4 state in sync
+
+    SkipBytes( $q, 0, $q->{streamPos} );
+
+    return 1;
+}
+
+sub InitStream {
+    my ( $stream_data ) = @_;
+    my %q;
+
+    $q{opcode} = 0;
+    $q{length} = 0;
+    $q{data}   = '';
+
+    $q{stream}    = $stream_data;              # data stream
+    $q{streamLen} = length( $stream_data );    # stream length
+    $q{streamPos} = 0;                         # stream position
+
+    $q{encryption}               = 0;
+    $q{xor_key}                  = '';
+    $q{rc4_key}                  = '';
+    $q{md5_ctxt}                 = '';
+    $q{block}                    = 0;
+    $q{dont_decrypt_next_record} = 0;
+
+    return \%q;
+}
+
+sub QueryNext {
+    my ( $q ) = @_;
+
+    if ( $q->{streamPos} + 4 >= $q->{streamLen} ) {
+        return 0;
+    }
+
+    my $data = substr( $q->{stream}, $q->{streamPos}, 4 );
+
+    ( $q->{opcode}, $q->{length} ) = unpack( 'v2', $data );
+
+    # No biff record should be larger than around 20,000.
+    if ( $q->{length} >= 20000 ) {
+        return 0;
+    }
+
+    if ( $q->{length} > 0 ) {
+        $q->{data} = substr( $q->{stream}, $q->{streamPos} + 4, $q->{length} );
+    }
+    else {
+        $q->{data}                     = undef;
+        $q->{dont_decrypt_next_record} = 1;
+    }
+
+    if ( $q->{encryption} == MS_BIFF_CRYPTO_RC4 ) {
+        if ( $q->{dont_decrypt_next_record} ) {
+            SkipBytes( $q, $q->{streamPos}, 4 + $q->{length} );
+            $q->{dont_decrypt_next_record} = 0;
+        }
+        else {
+            my $pos  = $q->{streamPos};
+            my $data = $q->{data};
+            my $len  = $q->{length};
+            my $res  = '';
+
+            # Pretend to decrypt header.
+            SkipBytes( $q, $pos, 4 );
+            $pos += 4;
+
+            while ( $q->{block} != int( ( $pos + $len ) / REKEY_BLOCK ) ) {
+                my $step = REKEY_BLOCK - ( $pos % REKEY_BLOCK );
+                $res .= $q->{rc4_key}->RC4( substr( $data, 0, $step ) );
+                $data = substr( $data, $step );
+                $pos += $step;
+                $len -= $step;
+                MakeKey( ++$q->{block}, \$q->{rc4_key}, $q->{md5_ctxt} );
+            }
+
+            $res .= $q->{rc4_key}->RC4( substr( $data, 0, $len ) );
+            $q->{data} = $res;
+        }
+    }
+    elsif ( $q->{encryption} == MS_BIFF_CRYPTO_XOR ) {
+
+        # not implemented
+        return 0;
+    }
+    elsif ( $q->{encryption} == MS_BIFF_CRYPTO_NONE ) {
+
+    }
+
+    $q->{streamPos} += 4 + $q->{length};
+
+    return 1;
+}
+
+###############################################################################
+#
+# Parse()
+#
+# Parse the Excel file and convert it into a tree of objects..
+#
+sub parse {
+
+    my ( $self, $source, $formatter ) = @_;
+
+    my $workbook = Spreadsheet::ParseExcel::Workbook->new();
+    $workbook->{SheetCount} = 0;
+
+    my ( $biff_data, $data_length ) = $self->_get_content( $source, $workbook );
+    return undef if not $biff_data;
+
+    if ( $formatter ) {
+        $workbook->{FmtClass} = $formatter;
+    }
+    else {
+        $workbook->{FmtClass} = Spreadsheet::ParseExcel::FmtDefault->new();
+    }
+
+    # Parse the BIFF data.
+    my $stream = InitStream( $biff_data );
+
+    while ( QueryNext( $stream ) ) {
+
+        my $record        = $stream->{opcode};
+        my $record_length = $stream->{length};
+
+        my $record_header = $stream->{data};
+
+        # If the file contains a FILEPASS record we assume that it is encrypted
+        # and cannot be parsed.
+        if ( $record == 0x002F ) {
+            unless ( SetDecrypt( $stream, '', $self->{Password} ) ) {
+                $self->{_error_status} = ErrorFileEncrypted;
+                return undef;
+            }
+        }
+
+        # Special case of a formula String with no string.
+        if (   $workbook->{_PrevPos}
+            && ( defined $self->{FuncTbl}->{$record} )
+            && ( $record != 0x207 ) )
+        {
+            my $iPos = $workbook->{_PrevPos};
+            $workbook->{_PrevPos} = undef;
+
+            my ( $row, $col, $format_index ) = @$iPos;
+            _NewCell(
+                $workbook, $row, $col,
+                Kind     => 'Formula String',
+                Val      => '',
+                FormatNo => $format_index,
+                Format   => $workbook->{Format}[$format_index],
+                Numeric  => 0,
+                Code     => undef,
+                Book     => $workbook,
+            );
+        }
+
+        # If the BIFF record matches 0x0*09 then it is a BOF record.
+        # We reset the _skip_chart flag to ensure we check the sheet type.
+        if ( ( $record & 0xF0FF ) == 0x09 ) {
+            $workbook->{_skip_chart} = 0;
+        }
+
+        if ( defined $self->{FuncTbl}->{$record} && !$workbook->{_skip_chart} )
+        {
+            $self->{FuncTbl}->{$record}
+              ->( $workbook, $record, $record_length, $record_header );
+        }
+
+        $PREFUNC = $record if ( $record != 0x3C );    #Not Continue
+
+        return $workbook if defined $workbook->{_ParseAbort};
+    }
+
+    return $workbook;
+}
+
+###############################################################################
+#
+# _get_content()
+#
+# Get the Excel BIFF content from the file or filehandle.
+#
+sub _get_content {
+
+    my ( $self, $source, $workbook ) = @_;
+    my ( $biff_data, $data_length );
+
+    # Reset the error status in case method is called more than once.
+    $self->{_error_status} = ErrorNone;
+
+    if ( ref( $source ) eq "SCALAR" ) {
+
+        # Specified by a scalar buffer.
+        ( $biff_data, $data_length ) = $self->{GetContent}->( $source );
+
+    }
+    elsif ( ( ref( $source ) =~ /GLOB/ ) || ( ref( $source ) eq 'Fh' ) ) {
+
+        # For CGI.pm (Light FileHandle)
+        binmode( $source );
+        my $sWk;
+        my $sBuff = '';
+
+        while ( read( $source, $sWk, 4096 ) ) {
+            $sBuff .= $sWk;
+        }
+
+        ( $biff_data, $data_length ) = $self->{GetContent}->( \$sBuff );
+
+    }
+    elsif ( ref( $source ) eq 'ARRAY' ) {
+
+        # Specified by file content
+        $workbook->{File} = undef;
+        my $sData = join( '', @$source );
+        ( $biff_data, $data_length ) = $self->{GetContent}->( \$sData );
+    }
+    else {
+
+        # Specified by filename .
+        $workbook->{File} = $source;
+
+        if ( !-e $source ) {
+            $self->{_error_status} = ErrorNoFile;
+            return undef;
+        }
+
+        ( $biff_data, $data_length ) = $self->{GetContent}->( $source );
+    }
+
+    # If the read was successful return the data.
+    if ( $data_length ) {
+        return ( $biff_data, $data_length );
+    }
+    else {
+        $self->{_error_status} = ErrorNoExcelData;
+        return undef;
+    }
+
+}
+
+#------------------------------------------------------------------------------
+# _subGetContent (for Spreadsheet::ParseExcel)
+#------------------------------------------------------------------------------
+sub _subGetContent {
+    my ( $sFile ) = @_;
+
+    my $oOl = OLE::Storage_Lite->new( $sFile );
+    return ( undef, undef ) unless ( $oOl );
+    my @aRes = $oOl->getPpsSearch(
+        [
+            OLE::Storage_Lite::Asc2Ucs( 'Book' ),
+            OLE::Storage_Lite::Asc2Ucs( 'Workbook' )
+        ],
+        1, 1
+    );
+    return ( undef, undef ) if ( $#aRes < 0 );
+
+    #Hack from Herbert
+    if ( $aRes[0]->{Data} ) {
+        return ( $aRes[0]->{Data}, length( $aRes[0]->{Data} ) );
+    }
+
+    #Same as OLE::Storage_Lite
+    my $oIo;
+
+    #1. $sFile is Ref of scalar
+    if ( ref( $sFile ) eq 'SCALAR' ) {
+        if ( $_use_perlio ) {
+            open $oIo, "<", \$sFile;
+        }
+        else {
+            $oIo = IO::Scalar->new;
+            $oIo->open( $sFile );
+        }
+    }
+
+    #2. $sFile is a IO::Handle object
+    elsif ( UNIVERSAL::isa( $sFile, 'IO::Handle' ) ) {
+        $oIo = $sFile;
+        binmode( $oIo );
+    }
+
+    #3. $sFile is a simple filename string
+    elsif ( !ref( $sFile ) ) {
+        $oIo = IO::File->new;
+        $oIo->open( "<$sFile" ) || return undef;
+        binmode( $oIo );
+    }
+    my $sWk;
+    my $sBuff = '';
+
+    while ( $oIo->read( $sWk, 4096 ) ) {    #4_096 has no special meanings
+        $sBuff .= $sWk;
+    }
+    $oIo->close();
+
+    #Not Excel file (simple method)
+    return ( undef, undef ) if ( substr( $sBuff, 0, 1 ) ne "\x09" );
+    return ( $sBuff, length( $sBuff ) );
+}
+
+#------------------------------------------------------------------------------
+# _subBOF (for Spreadsheet::ParseExcel) Developers' Kit : P303
+#------------------------------------------------------------------------------
+sub _subBOF {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iVer, $iDt ) = unpack( "v2", $sWk );
+
+    #Workbook Global
+    if ( $iDt == 0x0005 ) {
+        $oBook->{Version} = unpack( "v", $sWk );
+        $oBook->{BIFFVersion} =
+          ( $oBook->{Version} == verExcel95 ) ? verBIFF5 : verBIFF8;
+        $oBook->{_CurSheet}  = undef;
+        $oBook->{_CurSheet_} = -1;
+    }
+
+    #Worksheet or Dialogsheet
+    elsif ( $iDt != 0x0020 ) {    #if($iDt == 0x0010)
+        if ( defined $oBook->{_CurSheet_} ) {
+            $oBook->{_CurSheet} = $oBook->{_CurSheet_} + 1;
+            $oBook->{_CurSheet_}++;
+
+            (
+                $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{SheetVersion},
+                $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{SheetType},
+              )
+              = unpack( "v2", $sWk )
+              if ( length( $sWk ) > 4 );
+        }
+        else {
+            $oBook->{BIFFVersion} = int( $bOp / 0x100 );
+            if (   ( $oBook->{BIFFVersion} == verBIFF2 )
+                || ( $oBook->{BIFFVersion} == verBIFF3 )
+                || ( $oBook->{BIFFVersion} == verBIFF4 ) )
+            {
+                $oBook->{Version}   = $oBook->{BIFFVersion};
+                $oBook->{_CurSheet} = 0;
+                $oBook->{Worksheet}[ $oBook->{SheetCount} ] =
+                  Spreadsheet::ParseExcel::Worksheet->new(
+                    _Name    => '',
+                    Name     => '',
+                    _Book    => $oBook,
+                    _SheetNo => $oBook->{SheetCount},
+                  );
+                $oBook->{SheetCount}++;
+            }
+        }
+    }
+    else {
+
+        # Set flag to ignore all chart records until we reach another BOF.
+        $oBook->{_skip_chart} = 1;
+    }
+}
+
+#------------------------------------------------------------------------------
+# _subBlank (for Spreadsheet::ParseExcel) DK:P303
+#------------------------------------------------------------------------------
+sub _subBlank {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iR, $iC, $iF ) = unpack( "v3", $sWk );
+    _NewCell(
+        $oBook, $iR, $iC,
+        Kind     => 'BLANK',
+        Val      => '',
+        FormatNo => $iF,
+        Format   => $oBook->{Format}[$iF],
+        Numeric  => 0,
+        Code     => undef,
+        Book     => $oBook,
+    );
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+#------------------------------------------------------------------------------
+# _subInteger (for Spreadsheet::ParseExcel) Not in DK
+#------------------------------------------------------------------------------
+sub _subInteger {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iR, $iC, $iF, $sTxt, $sDum );
+
+    ( $iR, $iC, $iF, $sDum, $sTxt ) = unpack( "v3cv", $sWk );
+    _NewCell(
+        $oBook, $iR, $iC,
+        Kind     => 'INTEGER',
+        Val      => $sTxt,
+        FormatNo => $iF,
+        Format   => $oBook->{Format}[$iF],
+        Numeric  => 0,
+        Code     => undef,
+        Book     => $oBook,
+    );
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+#------------------------------------------------------------------------------
+# _subNumber (for Spreadsheet::ParseExcel)  : DK: P354
+#------------------------------------------------------------------------------
+sub _subNumber {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+
+    my ( $iR, $iC, $iF ) = unpack( "v3", $sWk );
+    my $dVal = _convDval( substr( $sWk, 6, 8 ) );
+    _NewCell(
+        $oBook, $iR, $iC,
+        Kind     => 'Number',
+        Val      => $dVal,
+        FormatNo => $iF,
+        Format   => $oBook->{Format}[$iF],
+        Numeric  => 1,
+        Code     => undef,
+        Book     => $oBook,
+    );
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+#------------------------------------------------------------------------------
+# _convDval (for Spreadsheet::ParseExcel)
+#------------------------------------------------------------------------------
+sub _convDval {
+    my ( $sWk ) = @_;
+    return
+      unpack( "d",
+        ( $BIGENDIAN ) ? pack( "c8", reverse( unpack( "c8", $sWk ) ) ) : $sWk );
+}
+
+#------------------------------------------------------------------------------
+# _subRString (for Spreadsheet::ParseExcel) DK:P405
+#------------------------------------------------------------------------------
+sub _subRString {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iR, $iC, $iF, $iL, $sTxt );
+    ( $iR, $iC, $iF, $iL ) = unpack( "v4", $sWk );
+    $sTxt = substr( $sWk, 8, $iL );
+
+    #Has STRUN
+    if ( length( $sWk ) > ( 8 + $iL ) ) {
+        _NewCell(
+            $oBook, $iR, $iC,
+            Kind     => 'RString',
+            Val      => $sTxt,
+            FormatNo => $iF,
+            Format   => $oBook->{Format}[$iF],
+            Numeric  => 0,
+            Code     => '_native_',                        #undef,
+            Book     => $oBook,
+            Rich     => substr( $sWk, ( 8 + $iL ) + 1 ),
+        );
+    }
+    else {
+        _NewCell(
+            $oBook, $iR, $iC,
+            Kind     => 'RString',
+            Val      => $sTxt,
+            FormatNo => $iF,
+            Format   => $oBook->{Format}[$iF],
+            Numeric  => 0,
+            Code     => '_native_',
+            Book     => $oBook,
+        );
+    }
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+#------------------------------------------------------------------------------
+# _subBoolErr (for Spreadsheet::ParseExcel) DK:P306
+#------------------------------------------------------------------------------
+sub _subBoolErr {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iR, $iC, $iF ) = unpack( "v3", $sWk );
+    my ( $iVal, $iFlg ) = unpack( "cc", substr( $sWk, 6, 2 ) );
+    my $sTxt = DecodeBoolErr( $iVal, $iFlg );
+
+    _NewCell(
+        $oBook, $iR, $iC,
+        Kind     => 'BoolError',
+        Val      => $sTxt,
+        FormatNo => $iF,
+        Format   => $oBook->{Format}[$iF],
+        Numeric  => 0,
+        Code     => undef,
+        Book     => $oBook,
+    );
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+###############################################################################
+#
+# _subRK()
+#
+# Decode the RK BIFF record.
+#
+sub _subRK {
+
+    my ( $workbook, $biff_number, $length, $data ) = @_;
+
+    my ( $row, $col, $format_index, $rk_number ) = unpack( 'vvvV', $data );
+
+    my $number = _decode_rk_number( $rk_number );
+
+    _NewCell(
+        $workbook, $row, $col,
+        Kind     => 'RK',
+        Val      => $number,
+        FormatNo => $format_index,
+        Format   => $workbook->{Format}->[$format_index],
+        Numeric  => 1,
+        Code     => undef,
+        Book     => $workbook,
+    );
+
+    # Store the max and min row/col values.
+    _SetDimension( $workbook, $row, $col, $col );
+}
+
+#------------------------------------------------------------------------------
+# _subArray (for Spreadsheet::ParseExcel)   DK:P297
+#------------------------------------------------------------------------------
+sub _subArray {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iBR, $iER, $iBC, $iEC ) = unpack( "v2c2", $sWk );
+
+}
+
+#------------------------------------------------------------------------------
+# _subFormula (for Spreadsheet::ParseExcel) DK:P336
+#------------------------------------------------------------------------------
+sub _subFormula {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iR, $iC, $iF ) = unpack( "v3", $sWk );
+
+    my ( $iFlg ) = unpack( "v", substr( $sWk, 12, 2 ) );
+    if ( $iFlg == 0xFFFF ) {
+        my ( $iKind ) = unpack( "c", substr( $sWk, 6, 1 ) );
+        my ( $iVal )  = unpack( "c", substr( $sWk, 8, 1 ) );
+
+        if ( ( $iKind == 1 ) or ( $iKind == 2 ) ) {
+            my $sTxt =
+              ( $iKind == 1 )
+              ? DecodeBoolErr( $iVal, 0 )
+              : DecodeBoolErr( $iVal, 1 );
+            _NewCell(
+                $oBook, $iR, $iC,
+                Kind     => 'Formula Bool',
+                Val      => $sTxt,
+                FormatNo => $iF,
+                Format   => $oBook->{Format}[$iF],
+                Numeric  => 0,
+                Code     => undef,
+                Book     => $oBook,
+            );
+        }
+        else {    # Result (Reserve Only)
+            $oBook->{_PrevPos} = [ $iR, $iC, $iF ];
+        }
+    }
+    else {
+        my $dVal = _convDval( substr( $sWk, 6, 8 ) );
+        _NewCell(
+            $oBook, $iR, $iC,
+            Kind     => 'Formula Number',
+            Val      => $dVal,
+            FormatNo => $iF,
+            Format   => $oBook->{Format}[$iF],
+            Numeric  => 1,
+            Code     => undef,
+            Book     => $oBook,
+        );
+    }
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+#------------------------------------------------------------------------------
+# _subString (for Spreadsheet::ParseExcel)  DK:P414
+#------------------------------------------------------------------------------
+sub _subString {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+
+    #Position (not enough for ARRAY)
+
+    my $iPos = $oBook->{_PrevPos};
+    return undef unless ( $iPos );
+    $oBook->{_PrevPos} = undef;
+    my ( $iR, $iC, $iF ) = @$iPos;
+
+    my ( $iLen, $sTxt, $sCode );
+    if ( $oBook->{BIFFVersion} == verBIFF8 ) {
+        my ( $raBuff, $iLen ) = _convBIFF8String( $oBook, $sWk, 1 );
+        $sTxt = $raBuff->[0];
+        $sCode = ( $raBuff->[1] ) ? 'ucs2' : undef;
+    }
+    elsif ( $oBook->{BIFFVersion} == verBIFF5 ) {
+        $sCode = '_native_';
+        $iLen  = unpack( "v", $sWk );
+        $sTxt  = substr( $sWk, 2, $iLen );
+    }
+    else {
+        $sCode = '_native_';
+        $iLen  = unpack( "c", $sWk );
+        $sTxt  = substr( $sWk, 1, $iLen );
+    }
+    _NewCell(
+        $oBook, $iR, $iC,
+        Kind     => 'String',
+        Val      => $sTxt,
+        FormatNo => $iF,
+        Format   => $oBook->{Format}[$iF],
+        Numeric  => 0,
+        Code     => $sCode,
+        Book     => $oBook,
+    );
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+#------------------------------------------------------------------------------
+# _subLabel (for Spreadsheet::ParseExcel)   DK:P344
+#------------------------------------------------------------------------------
+sub _subLabel {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iR, $iC, $iF ) = unpack( "v3", $sWk );
+    my ( $sLbl, $sCode );
+
+    #BIFF8
+    if ( $oBook->{BIFFVersion} >= verBIFF8 ) {
+        my ( $raBuff, $iLen, $iStPos, $iLenS ) =
+          _convBIFF8String( $oBook, substr( $sWk, 6 ), 1 );
+        $sLbl = $raBuff->[0];
+        $sCode = ( $raBuff->[1] ) ? 'ucs2' : undef;
+    }
+
+    #Before BIFF8
+    else {
+        $sLbl = substr( $sWk, 8 );
+        $sCode = '_native_';
+    }
+    _NewCell(
+        $oBook, $iR, $iC,
+        Kind     => 'Label',
+        Val      => $sLbl,
+        FormatNo => $iF,
+        Format   => $oBook->{Format}[$iF],
+        Numeric  => 0,
+        Code     => $sCode,
+        Book     => $oBook,
+    );
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+###############################################################################
+#
+# _subMulRK()
+#
+# Decode the Multiple RK BIFF record.
+#
+sub _subMulRK {
+
+    my ( $workbook, $biff_number, $length, $data ) = @_;
+
+    # JMN: I don't know why this is here.
+    return if $workbook->{SheetCount} <= 0;
+
+    my ( $row, $first_col ) = unpack( "v2", $data );
+    my $last_col = unpack( "v", substr( $data, length( $data ) - 2, 2 ) );
+
+    # Iterate over the RK array and decode the data.
+    my $pos = 4;
+    for my $col ( $first_col .. $last_col ) {
+
+        my $data = substr( $data, $pos, 6 );
+        my ( $format_index, $rk_number ) = unpack 'vV', $data;
+        my $number = _decode_rk_number( $rk_number );
+
+        _NewCell(
+            $workbook, $row, $col,
+            Kind     => 'MulRK',
+            Val      => $number,
+            FormatNo => $format_index,
+            Format   => $workbook->{Format}->[$format_index],
+            Numeric  => 1,
+            Code     => undef,
+            Book     => $workbook,
+        );
+        $pos += 6;
+    }
+
+    # Store the max and min row/col values.
+    _SetDimension( $workbook, $row, $first_col, $last_col );
+}
+
+#------------------------------------------------------------------------------
+# _subMulBlank (for Spreadsheet::ParseExcel) DK:P349
+#------------------------------------------------------------------------------
+sub _subMulBlank {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iR, $iSc ) = unpack( "v2", $sWk );
+    my $iEc = unpack( "v", substr( $sWk, length( $sWk ) - 2, 2 ) );
+    my $iPos = 4;
+    for ( my $iC = $iSc ; $iC <= $iEc ; $iC++ ) {
+        my $iF = unpack( 'v', substr( $sWk, $iPos, 2 ) );
+        _NewCell(
+            $oBook, $iR, $iC,
+            Kind     => 'MulBlank',
+            Val      => '',
+            FormatNo => $iF,
+            Format   => $oBook->{Format}[$iF],
+            Numeric  => 0,
+            Code     => undef,
+            Book     => $oBook,
+        );
+        $iPos += 2;
+    }
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iSc, $iEc );
+}
+
+#------------------------------------------------------------------------------
+# _subLabelSST (for Spreadsheet::ParseExcel) DK: P345
+#------------------------------------------------------------------------------
+sub _subLabelSST {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iR, $iC, $iF, $iIdx ) = unpack( 'v3V', $sWk );
+
+    _NewCell(
+        $oBook, $iR, $iC,
+        Kind     => 'PackedIdx',
+        Val      => $oBook->{PkgStr}[$iIdx]->{Text},
+        FormatNo => $iF,
+        Format   => $oBook->{Format}[$iF],
+        Numeric  => 0,
+        Code     => ( $oBook->{PkgStr}[$iIdx]->{Unicode} ) ? 'ucs2' : undef,
+        Book     => $oBook,
+        Rich     => $oBook->{PkgStr}[$iIdx]->{Rich},
+    );
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iC, $iC );
+}
+
+#------------------------------------------------------------------------------
+# _subFlg1904 (for Spreadsheet::ParseExcel) DK:P296
+#------------------------------------------------------------------------------
+sub _subFlg1904 {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    $oBook->{Flg1904} = unpack( "v", $sWk );
+}
+
+#------------------------------------------------------------------------------
+# _subRow (for Spreadsheet::ParseExcel) DK:P403
+#------------------------------------------------------------------------------
+sub _subRow {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    #0. Get Worksheet info (MaxRow, MaxCol, MinRow, MinCol)
+    my ( $iR, $iSc, $iEc, $iHght, $undef1, $undef2, $iGr, $iXf ) =
+      unpack( "v8", $sWk );
+    $iEc--;
+
+    # TODO. we need to handle hidden rows:
+    # $iGr & 0x20
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{RowHeight}[$iR] = $iHght / 20;
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    _SetDimension( $oBook, $iR, $iSc, $iEc );
+}
+
+#------------------------------------------------------------------------------
+# _SetDimension (for Spreadsheet::ParseExcel)
+#------------------------------------------------------------------------------
+sub _SetDimension {
+    my ( $oBook, $iR, $iSc, $iEc ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    #2.MaxRow, MaxCol, MinRow, MinCol
+    #2.1 MinRow
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MinRow} = $iR
+      unless ( defined $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MinRow} )
+      and ( $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MinRow} <= $iR );
+
+    #2.2 MaxRow
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MaxRow} = $iR
+      unless ( defined $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MaxRow} )
+      and ( $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MaxRow} > $iR );
+
+    #2.3 MinCol
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MinCol} = $iSc
+      unless ( defined $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MinCol} )
+      and ( $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MinCol} <= $iSc );
+
+    #2.4 MaxCol
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MaxCol} = $iEc
+      unless ( defined $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MaxCol} )
+      and ( $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{MaxCol} > $iEc );
+
+}
+
+#------------------------------------------------------------------------------
+# _subDefaultRowHeight (for Spreadsheet::ParseExcel)    DK: P318
+#------------------------------------------------------------------------------
+sub _subDefaultRowHeight {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    #1. RowHeight
+    my ( $iDum, $iHght ) = unpack( "v2", $sWk );
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{DefRowHeight} = $iHght / 20;
+
+}
+
+#------------------------------------------------------------------------------
+# _subStandardWidth(for Spreadsheet::ParseExcel)    DK:P413
+#------------------------------------------------------------------------------
+sub _subStandardWidth {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my $iW = unpack( "v", $sWk );
+    $oBook->{StandardWidth} = _convert_col_width( $oBook, $iW );
+}
+
+###############################################################################
+#
+# _subDefColWidth()
+#
+# Read the DEFCOLWIDTH Biff record. This gives the width in terms of chars
+# and is different from the width in the COLINFO record.
+#
+sub _subDefColWidth {
+
+    my ( $self, $record, $length, $data ) = @_;
+
+    my $width = unpack 'v', $data;
+
+    # Adjustment for default Arial 10 width.
+    $width = 8.43 if $width == 8;
+
+    $self->{Worksheet}->[ $self->{_CurSheet} ]->{DefColWidth} = $width;
+}
+
+###############################################################################
+#
+# _convert_col_width()
+#
+# Converts from the internal Excel column width units to user units seen in the
+# interface. It is first necessary to convert the internal width to pixels and
+# then to user units. The conversion is specific to a default font of Arial 10.
+# TODO, the conversion should be extended to other fonts and sizes.
+#
+sub _convert_col_width {
+
+    my $self        = shift;
+    my $excel_width = shift;
+
+    # Convert from Excel units to pixels (rounded up).
+    my $pixels = int( 0.5 + $excel_width * 7 / 256 );
+
+    # Convert from pixels to user units.
+    # The conversion is different for columns <= 1 user unit (12 pixels).
+    my $user_width;
+    if ( $pixels <= 12 ) {
+        $user_width = $pixels / 12;
+    }
+    else {
+        $user_width = ( $pixels - 5 ) / 7;
+    }
+
+    # Round up to 2 decimal places.
+    $user_width = int( $user_width * 100 + 0.5 ) / 100;
+
+    return $user_width;
+}
+
+#------------------------------------------------------------------------------
+# _subColInfo (for Spreadsheet::ParseExcel) DK:P309
+#------------------------------------------------------------------------------
+sub _subColInfo {
+
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+
+    return undef unless defined $oBook->{_CurSheet};
+
+    my ( $iSc, $iEc, $iW, $iXF, $iGr ) = unpack( "v5", $sWk );
+
+    for ( my $i = $iSc ; $i <= $iEc ; $i++ ) {
+
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{ColWidth}[$i] =
+          _convert_col_width( $oBook, $iW );
+
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{ColFmtNo}[$i] = $iXF;
+
+        # TODO. we need to handle hidden cols: $iGr & 0x01.
+    }
+}
+
+#------------------------------------------------------------------------------
+# _subSST (for Spreadsheet::ParseExcel) DK:P413
+#------------------------------------------------------------------------------
+sub _subSST {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    _subStrWk( $oBook, substr( $sWk, 8 ) );
+}
+
+#------------------------------------------------------------------------------
+# _subContinue (for Spreadsheet::ParseExcel)    DK:P311
+#------------------------------------------------------------------------------
+sub _subContinue {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+
+    #if(defined $self->{FuncTbl}->{$bOp}) {
+    #    $self->{FuncTbl}->{$PREFUNC}->($oBook, $bOp, $bLen, $sWk);
+    #}
+
+    _subStrWk( $oBook, $sWk, 1 ) if ( $PREFUNC == 0xFC );
+}
+
+#------------------------------------------------------------------------------
+# _subWriteAccess (for Spreadsheet::ParseExcel) DK:P451
+#------------------------------------------------------------------------------
+sub _subWriteAccess {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return if ( defined $oBook->{_Author} );
+
+    #BIFF8
+    if ( $oBook->{BIFFVersion} >= verBIFF8 ) {
+        $oBook->{Author} = _convBIFF8String( $oBook, $sWk );
+    }
+
+    #Before BIFF8
+    else {
+        my ( $iLen ) = unpack( "c", $sWk );
+        $oBook->{Author} =
+          $oBook->{FmtClass}->TextFmt( substr( $sWk, 1, $iLen ), '_native_' );
+    }
+}
+
+#------------------------------------------------------------------------------
+# _convBIFF8String (for Spreadsheet::ParseExcel)
+#------------------------------------------------------------------------------
+sub _convBIFF8String {
+    my ( $oBook, $sWk, $iCnvFlg ) = @_;
+    my ( $iLen, $iFlg ) = unpack( "vc", $sWk );
+    my ( $iHigh, $iExt, $iRich ) = ( $iFlg & 0x01, $iFlg & 0x04, $iFlg & 0x08 );
+    my ( $iStPos, $iExtCnt, $iRichCnt, $sStr );
+
+    #2. Rich and Ext
+    if ( $iRich && $iExt ) {
+        $iStPos = 9;
+        ( $iRichCnt, $iExtCnt ) = unpack( 'vV', substr( $sWk, 3, 6 ) );
+    }
+    elsif ( $iRich ) {    #Only Rich
+        $iStPos   = 5;
+        $iRichCnt = unpack( 'v', substr( $sWk, 3, 2 ) );
+        $iExtCnt  = 0;
+    }
+    elsif ( $iExt ) {     #Only Ext
+        $iStPos   = 7;
+        $iRichCnt = 0;
+        $iExtCnt  = unpack( 'V', substr( $sWk, 3, 4 ) );
+    }
+    else {                #Nothing Special
+        $iStPos   = 3;
+        $iExtCnt  = 0;
+        $iRichCnt = 0;
+    }
+
+    #3.Get String
+    if ( $iHigh ) {       #Compressed
+        $iLen *= 2;
+        $sStr = substr( $sWk, $iStPos, $iLen );
+        _SwapForUnicode( \$sStr );
+        $sStr = $oBook->{FmtClass}->TextFmt( $sStr, 'ucs2' )
+          unless ( $iCnvFlg );
+    }
+    else {                #Not Compressed
+        $sStr = substr( $sWk, $iStPos, $iLen );
+        $sStr = $oBook->{FmtClass}->TextFmt( $sStr, undef ) unless ( $iCnvFlg );
+    }
+
+    #4. return
+    if ( wantarray ) {
+
+        #4.1 Get Rich and Ext
+        if ( length( $sWk ) < $iStPos + $iLen + $iRichCnt * 4 + $iExtCnt ) {
+            return (
+                [ undef, $iHigh, undef, undef ],
+                $iStPos + $iLen + $iRichCnt * 4 + $iExtCnt,
+                $iStPos, $iLen
+            );
+        }
+        else {
+            return (
+                [
+                    $sStr,
+                    $iHigh,
+                    substr( $sWk, $iStPos + $iLen, $iRichCnt * 4 ),
+                    substr( $sWk, $iStPos + $iLen + $iRichCnt * 4, $iExtCnt )
+                ],
+                $iStPos + $iLen + $iRichCnt * 4 + $iExtCnt,
+                $iStPos, $iLen
+            );
+        }
+    }
+    else {
+        return $sStr;
+    }
+}
+
+#------------------------------------------------------------------------------
+# _subXF (for Spreadsheet::ParseExcel)     DK:P453
+#------------------------------------------------------------------------------
+sub _subXF {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+
+    my ( $iFnt, $iIdx );
+    my (
+        $iLock,    $iHidden, $iStyle,  $i123,   $iAlH,    $iWrap,
+        $iAlV,     $iJustL,  $iRotate, $iInd,   $iShrink, $iMerge,
+        $iReadDir, $iBdrD,   $iBdrSL,  $iBdrSR, $iBdrST,  $iBdrSB,
+        $iBdrSD,   $iBdrCL,  $iBdrCR,  $iBdrCT, $iBdrCB,  $iBdrCD,
+        $iFillP,   $iFillCF, $iFillCB
+    );
+
+
+    if ( $oBook->{BIFFVersion} == verBIFF4 ) {
+
+        # Minimal support for Excel 4. We just get the font and format indices
+        # so that the cell data value can be formatted.
+        ( $iFnt, $iIdx, ) = unpack( "CC", $sWk );
+    }
+    elsif ( $oBook->{BIFFVersion} == verBIFF8 ) {
+        my ( $iGen, $iAlign, $iGen2, $iBdr1, $iBdr2, $iBdr3, $iPtn );
+
+        ( $iFnt, $iIdx, $iGen, $iAlign, $iGen2, $iBdr1, $iBdr2, $iBdr3, $iPtn )
+          = unpack( "v7Vv", $sWk );
+        $iLock   = ( $iGen & 0x01 )   ? 1 : 0;
+        $iHidden = ( $iGen & 0x02 )   ? 1 : 0;
+        $iStyle  = ( $iGen & 0x04 )   ? 1 : 0;
+        $i123    = ( $iGen & 0x08 )   ? 1 : 0;
+        $iAlH    = ( $iAlign & 0x07 );
+        $iWrap   = ( $iAlign & 0x08 ) ? 1 : 0;
+        $iAlV    = ( $iAlign & 0x70 ) / 0x10;
+        $iJustL  = ( $iAlign & 0x80 ) ? 1 : 0;
+
+        $iRotate = ( ( $iAlign & 0xFF00 ) / 0x100 ) & 0x00FF;
+        $iRotate = 90            if ( $iRotate == 255 );
+        $iRotate = 90 - $iRotate if ( $iRotate > 90 );
+
+        $iInd     = ( $iGen2 & 0x0F );
+        $iShrink  = ( $iGen2 & 0x10 ) ? 1 : 0;
+        $iMerge   = ( $iGen2 & 0x20 ) ? 1 : 0;
+        $iReadDir = ( ( $iGen2 & 0xC0 ) / 0x40 ) & 0x03;
+        $iBdrSL   = $iBdr1 & 0x0F;
+        $iBdrSR   = ( ( $iBdr1 & 0xF0 ) / 0x10 ) & 0x0F;
+        $iBdrST   = ( ( $iBdr1 & 0xF00 ) / 0x100 ) & 0x0F;
+        $iBdrSB   = ( ( $iBdr1 & 0xF000 ) / 0x1000 ) & 0x0F;
+
+        $iBdrCL = ( ( $iBdr2 & 0x7F ) ) & 0x7F;
+        $iBdrCR = ( ( $iBdr2 & 0x3F80 ) / 0x80 ) & 0x7F;
+        $iBdrD  = ( ( $iBdr2 & 0xC000 ) / 0x4000 ) & 0x3;
+
+        $iBdrCT = ( ( $iBdr3 & 0x7F ) ) & 0x7F;
+        $iBdrCB = ( ( $iBdr3 & 0x3F80 ) / 0x80 ) & 0x7F;
+        $iBdrCD = ( ( $iBdr3 & 0x1FC000 ) / 0x4000 ) & 0x7F;
+        $iBdrSD = ( ( $iBdr3 & 0x1E00000 ) / 0x200000 ) & 0xF;
+        $iFillP = ( ( $iBdr3 & 0xFC000000 ) / 0x4000000 ) & 0x3F;
+
+        $iFillCF = ( $iPtn & 0x7F );
+        $iFillCB = ( ( $iPtn & 0x3F80 ) / 0x80 ) & 0x7F;
+    }
+    else {
+        my ( $iGen, $iAlign, $iPtn, $iPtn2, $iBdr1, $iBdr2 );
+
+        ( $iFnt, $iIdx, $iGen, $iAlign, $iPtn, $iPtn2, $iBdr1, $iBdr2 ) =
+          unpack( "v8", $sWk );
+        $iLock   = ( $iGen & 0x01 ) ? 1 : 0;
+        $iHidden = ( $iGen & 0x02 ) ? 1 : 0;
+        $iStyle  = ( $iGen & 0x04 ) ? 1 : 0;
+        $i123    = ( $iGen & 0x08 ) ? 1 : 0;
+
+        $iAlH   = ( $iAlign & 0x07 );
+        $iWrap  = ( $iAlign & 0x08 ) ? 1 : 0;
+        $iAlV   = ( $iAlign & 0x70 ) / 0x10;
+        $iJustL = ( $iAlign & 0x80 ) ? 1 : 0;
+
+        $iRotate = ( ( $iAlign & 0x300 ) / 0x100 ) & 0x3;
+
+        $iFillCF = ( $iPtn & 0x7F );
+        $iFillCB = ( ( $iPtn & 0x1F80 ) / 0x80 ) & 0x7F;
+
+        $iFillP = ( $iPtn2 & 0x3F );
+        $iBdrSB = ( ( $iPtn2 & 0x1C0 ) / 0x40 ) & 0x7;
+        $iBdrCB = ( ( $iPtn2 & 0xFE00 ) / 0x200 ) & 0x7F;
+
+        $iBdrST = ( $iBdr1 & 0x07 );
+        $iBdrSL = ( ( $iBdr1 & 0x38 ) / 0x8 ) & 0x07;
+        $iBdrSR = ( ( $iBdr1 & 0x1C0 ) / 0x40 ) & 0x07;
+        $iBdrCT = ( ( $iBdr1 & 0xFE00 ) / 0x200 ) & 0x7F;
+
+        $iBdrCL = ( $iBdr2 & 0x7F ) & 0x7F;
+        $iBdrCR = ( ( $iBdr2 & 0x3F80 ) / 0x80 ) & 0x7F;
+    }
+
+    push @{ $oBook->{Format} }, Spreadsheet::ParseExcel::Format->new(
+        FontNo => $iFnt,
+        Font   => $oBook->{Font}[$iFnt],
+        FmtIdx => $iIdx,
+
+        Lock     => $iLock,
+        Hidden   => $iHidden,
+        Style    => $iStyle,
+        Key123   => $i123,
+        AlignH   => $iAlH,
+        Wrap     => $iWrap,
+        AlignV   => $iAlV,
+        JustLast => $iJustL,
+        Rotate   => $iRotate,
+
+        Indent  => $iInd,
+        Shrink  => $iShrink,
+        Merge   => $iMerge,
+        ReadDir => $iReadDir,
+
+        BdrStyle => [ $iBdrSL, $iBdrSR,  $iBdrST, $iBdrSB ],
+        BdrColor => [ $iBdrCL, $iBdrCR,  $iBdrCT, $iBdrCB ],
+        BdrDiag  => [ $iBdrD,  $iBdrSD,  $iBdrCD ],
+        Fill     => [ $iFillP, $iFillCF, $iFillCB ],
+    );
+}
+
+#------------------------------------------------------------------------------
+# _subFormat (for Spreadsheet::ParseExcel)  DK: P336
+#------------------------------------------------------------------------------
+sub _subFormat {
+
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my $sFmt;
+
+    if ( $oBook->{BIFFVersion} <= verBIFF5 ) {
+        $sFmt = substr( $sWk, 3, unpack( 'c', substr( $sWk, 2, 1 ) ) );
+        $sFmt = $oBook->{FmtClass}->TextFmt( $sFmt, '_native_' );
+    }
+    else {
+        $sFmt = _convBIFF8String( $oBook, substr( $sWk, 2 ) );
+    }
+
+    my $format_index = unpack( 'v', substr( $sWk, 0, 2 ) );
+
+    # Excel 4 and earlier used an index of 0 to indicate that a built-in format
+    # that was stored implicitly.
+    if ( $oBook->{BIFFVersion} <= verBIFF4 && $format_index == 0 ) {
+        $format_index = keys %{ $oBook->{FormatStr} };
+    }
+
+    $oBook->{FormatStr}->{$format_index} = $sFmt;
+}
+
+#------------------------------------------------------------------------------
+# _subPalette (for Spreadsheet::ParseExcel) DK: P393
+#------------------------------------------------------------------------------
+sub _subPalette {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    for ( my $i = 0 ; $i < unpack( 'v', $sWk ) ; $i++ ) {
+
+        #        push @aColor, unpack('H6', substr($sWk, $i*4+2));
+        $aColor[ $i + 8 ] = unpack( 'H6', substr( $sWk, $i * 4 + 2 ) );
+    }
+}
+
+#------------------------------------------------------------------------------
+# _subFont (for Spreadsheet::ParseExcel) DK:P333
+#------------------------------------------------------------------------------
+sub _subFont {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iHeight, $iAttr, $iCIdx, $iBold, $iSuper, $iUnderline, $sFntName );
+    my ( $bBold, $bItalic, $bUnderline, $bStrikeout );
+
+    if ( $oBook->{BIFFVersion} == verBIFF8 ) {
+        ( $iHeight, $iAttr, $iCIdx, $iBold, $iSuper, $iUnderline ) =
+          unpack( "v5c", $sWk );
+        my ( $iSize, $iHigh ) = unpack( 'cc', substr( $sWk, 14, 2 ) );
+        if ( $iHigh ) {
+            $sFntName = substr( $sWk, 16, $iSize * 2 );
+            _SwapForUnicode( \$sFntName );
+            $sFntName = $oBook->{FmtClass}->TextFmt( $sFntName, 'ucs2' );
+        }
+        else {
+            $sFntName = substr( $sWk, 16, $iSize );
+            $sFntName = $oBook->{FmtClass}->TextFmt( $sFntName, '_native_' );
+        }
+        $bBold      = ( $iBold >= 0x2BC ) ? 1 : 0;
+        $bItalic    = ( $iAttr & 0x02 )   ? 1 : 0;
+        $bStrikeout = ( $iAttr & 0x08 )   ? 1 : 0;
+        $bUnderline = ( $iUnderline )     ? 1 : 0;
+    }
+    elsif ( $oBook->{BIFFVersion} == verBIFF5 ) {
+        ( $iHeight, $iAttr, $iCIdx, $iBold, $iSuper, $iUnderline ) =
+          unpack( "v5c", $sWk );
+        $sFntName =
+          $oBook->{FmtClass}
+          ->TextFmt( substr( $sWk, 15, unpack( "c", substr( $sWk, 14, 1 ) ) ),
+            '_native_' );
+        $bBold      = ( $iBold >= 0x2BC ) ? 1 : 0;
+        $bItalic    = ( $iAttr & 0x02 )   ? 1 : 0;
+        $bStrikeout = ( $iAttr & 0x08 )   ? 1 : 0;
+        $bUnderline = ( $iUnderline )     ? 1 : 0;
+    }
+    else {
+        ( $iHeight, $iAttr ) = unpack( "v2", $sWk );
+        $iCIdx  = undef;
+        $iSuper = 0;
+
+        $bBold      = ( $iAttr & 0x01 ) ? 1 : 0;
+        $bItalic    = ( $iAttr & 0x02 ) ? 1 : 0;
+        $bUnderline = ( $iAttr & 0x04 ) ? 1 : 0;
+        $bStrikeout = ( $iAttr & 0x08 ) ? 1 : 0;
+
+        $sFntName = substr( $sWk, 5, unpack( "c", substr( $sWk, 4, 1 ) ) );
+    }
+    push @{ $oBook->{Font} }, Spreadsheet::ParseExcel::Font->new(
+        Height         => $iHeight / 20.0,
+        Attr           => $iAttr,
+        Color          => $iCIdx,
+        Super          => $iSuper,
+        UnderlineStyle => $iUnderline,
+        Name           => $sFntName,
+
+        Bold      => $bBold,
+        Italic    => $bItalic,
+        Underline => $bUnderline,
+        Strikeout => $bStrikeout,
+    );
+
+    #Skip Font[4]
+    push @{ $oBook->{Font} }, {} if ( scalar( @{ $oBook->{Font} } ) == 4 );
+
+}
+
+#------------------------------------------------------------------------------
+# _subBoundSheet (for Spreadsheet::ParseExcel): DK: P307
+#------------------------------------------------------------------------------
+sub _subBoundSheet {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my ( $iPos, $iGr, $iKind ) = unpack( "Lc2", $sWk );
+    $iKind &= 0x0F;
+    return if ( ( $iKind != 0x00 ) && ( $iKind != 0x01 ) );
+
+    if ( $oBook->{BIFFVersion} >= verBIFF8 ) {
+        my ( $iSize, $iUni ) = unpack( "cc", substr( $sWk, 6, 2 ) );
+        my $sWsName = substr( $sWk, 8 );
+        if ( $iUni & 0x01 ) {
+            _SwapForUnicode( \$sWsName );
+            $sWsName = $oBook->{FmtClass}->TextFmt( $sWsName, 'ucs2' );
+        }
+        $oBook->{Worksheet}[ $oBook->{SheetCount} ] =
+          Spreadsheet::ParseExcel::Worksheet->new(
+            Name     => $sWsName,
+            Kind     => $iKind,
+            _Pos     => $iPos,
+            _Book    => $oBook,
+            _SheetNo => $oBook->{SheetCount},
+          );
+    }
+    else {
+        $oBook->{Worksheet}[ $oBook->{SheetCount} ] =
+          Spreadsheet::ParseExcel::Worksheet->new(
+            Name =>
+              $oBook->{FmtClass}->TextFmt( substr( $sWk, 7 ), '_native_' ),
+            Kind     => $iKind,
+            _Pos     => $iPos,
+            _Book    => $oBook,
+            _SheetNo => $oBook->{SheetCount},
+          );
+    }
+    $oBook->{SheetCount}++;
+}
+
+#------------------------------------------------------------------------------
+# _subHeader (for Spreadsheet::ParseExcel) DK: P340
+#------------------------------------------------------------------------------
+sub _subHeader {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+    my $sW;
+
+    if ( !defined $sWk ) {
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{Header} = undef;
+        return;
+    }
+
+    #BIFF8
+    if ( $oBook->{BIFFVersion} >= verBIFF8 ) {
+        $sW = _convBIFF8String( $oBook, $sWk );
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{Header} =
+          ( $sW eq "\x00" ) ? undef : $sW;
+    }
+
+    #Before BIFF8
+    else {
+        my ( $iLen ) = unpack( "c", $sWk );
+        $sW =
+          $oBook->{FmtClass}->TextFmt( substr( $sWk, 1, $iLen ), '_native_' );
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{Header} =
+          ( $sW eq "\x00\x00\x00" ) ? undef : $sW;
+    }
+}
+
+#------------------------------------------------------------------------------
+# _subFooter (for Spreadsheet::ParseExcel) DK: P335
+#------------------------------------------------------------------------------
+sub _subFooter {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+    my $sW;
+
+    if ( !defined $sWk ) {
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{Footer} = undef;
+        return;
+    }
+
+    #BIFF8
+    if ( $oBook->{BIFFVersion} >= verBIFF8 ) {
+        $sW = _convBIFF8String( $oBook, $sWk );
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{Footer} =
+          ( $sW eq "\x00" ) ? undef : $sW;
+    }
+
+    #Before BIFF8
+    else {
+        my ( $iLen ) = unpack( "c", $sWk );
+        $sW =
+          $oBook->{FmtClass}->TextFmt( substr( $sWk, 1, $iLen ), '_native_' );
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{Footer} =
+          ( $sW eq "\x00\x00\x00" ) ? undef : $sW;
+    }
+}
+
+#------------------------------------------------------------------------------
+# _subHPageBreak (for Spreadsheet::ParseExcel) DK: P341
+#------------------------------------------------------------------------------
+sub _subHPageBreak {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my @aBreak;
+    my $iCnt = unpack( "v", $sWk );
+
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    #BIFF8
+    if ( $oBook->{BIFFVersion} >= verBIFF8 ) {
+        for ( my $i = 0 ; $i < $iCnt ; $i++ ) {
+            my ( $iRow, $iColB, $iColE ) =
+              unpack( 'v3', substr( $sWk, 2 + $i * 6, 6 ) );
+
+            #            push @aBreak, [$iRow, $iColB, $iColE];
+            push @aBreak, $iRow;
+        }
+    }
+
+    #Before BIFF8
+    else {
+        for ( my $i = 0 ; $i < $iCnt ; $i++ ) {
+            my ( $iRow ) = unpack( 'v', substr( $sWk, 2 + $i * 2, 2 ) );
+            push @aBreak, $iRow;
+
+            #            push @aBreak, [$iRow, 0, 255];
+        }
+    }
+    @aBreak = sort { $a <=> $b } @aBreak;
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{HPageBreak} = \@aBreak;
+}
+
+#------------------------------------------------------------------------------
+# _subVPageBreak (for Spreadsheet::ParseExcel) DK: P447
+#------------------------------------------------------------------------------
+sub _subVPageBreak {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    my @aBreak;
+    my $iCnt = unpack( "v", $sWk );
+
+    #BIFF8
+    if ( $oBook->{BIFFVersion} >= verBIFF8 ) {
+        for ( my $i = 0 ; $i < $iCnt ; $i++ ) {
+            my ( $iCol, $iRowB, $iRowE ) =
+              unpack( 'v3', substr( $sWk, 2 + $i * 6, 6 ) );
+            push @aBreak, $iCol;
+
+            #            push @aBreak, [$iCol, $iRowB, $iRowE];
+        }
+    }
+
+    #Before BIFF8
+    else {
+        for ( my $i = 0 ; $i < $iCnt ; $i++ ) {
+            my ( $iCol ) = unpack( 'v', substr( $sWk, 2 + $i * 2, 2 ) );
+            push @aBreak, $iCol;
+
+            #            push @aBreak, [$iCol, 0, 65535];
+        }
+    }
+    @aBreak = sort { $a <=> $b } @aBreak;
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{VPageBreak} = \@aBreak;
+}
+
+#------------------------------------------------------------------------------
+# _subMargin (for Spreadsheet::ParseExcel) DK: P306, 345, 400, 440
+#------------------------------------------------------------------------------
+sub _subMargin {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    # The "Mergin" options are a workaround for a backward compatible typo.
+
+    my $dWk = _convDval( substr( $sWk, 0, 8 ) );
+    if ( $bOp == 0x26 ) {
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{LeftMergin} = $dWk;
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{LeftMargin} = $dWk;
+    }
+    elsif ( $bOp == 0x27 ) {
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{RightMergin} = $dWk;
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{RightMargin} = $dWk;
+    }
+    elsif ( $bOp == 0x28 ) {
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{TopMergin} = $dWk;
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{TopMargin} = $dWk;
+    }
+    elsif ( $bOp == 0x29 ) {
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{BottomMergin} = $dWk;
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{BottomMargin} = $dWk;
+    }
+}
+
+#------------------------------------------------------------------------------
+# _subHcenter (for Spreadsheet::ParseExcel) DK: P340
+#------------------------------------------------------------------------------
+sub _subHcenter {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    my $iWk = unpack( "v", $sWk );
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{HCenter} = $iWk;
+
+}
+
+#------------------------------------------------------------------------------
+# _subVcenter (for Spreadsheet::ParseExcel) DK: P447
+#------------------------------------------------------------------------------
+sub _subVcenter {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    my $iWk = unpack( "v", $sWk );
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{VCenter} = $iWk;
+}
+
+#------------------------------------------------------------------------------
+# _subPrintGridlines (for Spreadsheet::ParseExcel) DK: P397
+#------------------------------------------------------------------------------
+sub _subPrintGridlines {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    my $iWk = unpack( "v", $sWk );
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{PrintGrid} = $iWk;
+
+}
+
+#------------------------------------------------------------------------------
+# _subPrintHeaders (for Spreadsheet::ParseExcel) DK: P397
+#------------------------------------------------------------------------------
+sub _subPrintHeaders {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    my $iWk = unpack( "v", $sWk );
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{PrintHeaders} = $iWk;
+}
+
+#------------------------------------------------------------------------------
+# _subSETUP (for Spreadsheet::ParseExcel) DK: P409
+#------------------------------------------------------------------------------
+sub _subSETUP {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    # Workaround for some apps and older Excels that don't write a
+    # complete SETUP record.
+    return undef if $bLen != 34;
+
+    my $oWkS = $oBook->{Worksheet}[ $oBook->{_CurSheet} ];
+    my $iGrBit;
+
+    (
+        $oWkS->{PaperSize}, $oWkS->{Scale},     $oWkS->{PageStart},
+        $oWkS->{FitWidth},  $oWkS->{FitHeight}, $iGrBit,
+        $oWkS->{Res},       $oWkS->{VRes},
+    ) = unpack( 'v8', $sWk );
+
+    $oWkS->{HeaderMargin} = _convDval( substr( $sWk, 16, 8 ) );
+    $oWkS->{FooterMargin} = _convDval( substr( $sWk, 24, 8 ) );
+    $oWkS->{Copis} = unpack( 'v2', substr( $sWk, 32, 2 ) );
+    $oWkS->{LeftToRight} = ( ( $iGrBit & 0x01 ) ? 1 : 0 );
+    $oWkS->{Landscape}   = ( ( $iGrBit & 0x02 ) ? 1 : 0 );
+    $oWkS->{NoPls}       = ( ( $iGrBit & 0x04 ) ? 1 : 0 );
+    $oWkS->{NoColor}     = ( ( $iGrBit & 0x08 ) ? 1 : 0 );
+    $oWkS->{Draft}       = ( ( $iGrBit & 0x10 ) ? 1 : 0 );
+    $oWkS->{Notes}       = ( ( $iGrBit & 0x20 ) ? 1 : 0 );
+    $oWkS->{NoOrient}    = ( ( $iGrBit & 0x40 ) ? 1 : 0 );
+    $oWkS->{UsePage}     = ( ( $iGrBit & 0x80 ) ? 1 : 0 );
+
+    # The NoPls flag indicates that the values have not been taken from an
+    # actual printer and thus may not be accurate.
+
+    # Set default scale if NoPls otherwise it may be an invalid value of 0XFF.
+    $oWkS->{Scale} = 100 if $oWkS->{NoPls};
+
+    # Workaround for a backward compatible typo.
+    $oWkS->{HeaderMergin} = $oWkS->{HeaderMargin};
+    $oWkS->{FooterMergin} = $oWkS->{FooterMargin};
+
+}
+
+#------------------------------------------------------------------------------
+# _subName (for Spreadsheet::ParseExcel) DK: P350
+#------------------------------------------------------------------------------
+sub _subName {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    my (
+        $iGrBit, $cKey,    $cCh,    $iCce,   $ixAls,
+        $iTab,   $cchCust, $cchDsc, $cchHep, $cchStatus
+    ) = unpack( 'vc2v3c4', $sWk );
+
+    #Builtin Name + Length == 1
+    if ( ( $iGrBit & 0x20 ) && ( $cCh == 1 ) ) {
+
+        #BIFF8
+        if ( $oBook->{BIFFVersion} >= verBIFF8 ) {
+            my $iName  = unpack( 'n', substr( $sWk, 14 ) );
+            my $iSheet = unpack( 'v', substr( $sWk, 8 ) ) - 1;
+
+            # Workaround for mal-formed Excel workbooks where Print_Title is
+            # set as Global (i.e. itab = 0). Note, this will have to be
+            # treated differently when we get around to handling global names.
+            return undef if $iSheet == -1;
+
+            if ( $iName == 6 ) {    #PrintArea
+                my ( $iSheetW, $raArea ) = _ParseNameArea( substr( $sWk, 16 ) );
+                $oBook->{PrintArea}[$iSheet] = $raArea;
+            }
+            elsif ( $iName == 7 ) {    #Title
+                my ( $iSheetW, $raArea ) = _ParseNameArea( substr( $sWk, 16 ) );
+                my @aTtlR = ();
+                my @aTtlC = ();
+                foreach my $raI ( @$raArea ) {
+                    if ( $raI->[3] == 0xFF ) {    #Row Title
+                        push @aTtlR, [ $raI->[0], $raI->[2] ];
+                    }
+                    else {                        #Col Title
+                        push @aTtlC, [ $raI->[1], $raI->[3] ];
+                    }
+                }
+                $oBook->{PrintTitle}[$iSheet] =
+                  { Row => \@aTtlR, Column => \@aTtlC };
+            }
+        }
+        else {
+            my $iName = unpack( 'c', substr( $sWk, 14 ) );
+            if ( $iName == 6 ) {                  #PrintArea
+                my ( $iSheet, $raArea ) =
+                  _ParseNameArea95( substr( $sWk, 15 ) );
+                $oBook->{PrintArea}[$iSheet] = $raArea;
+            }
+            elsif ( $iName == 7 ) {               #Title
+                my ( $iSheet, $raArea ) =
+                  _ParseNameArea95( substr( $sWk, 15 ) );
+                my @aTtlR = ();
+                my @aTtlC = ();
+                foreach my $raI ( @$raArea ) {
+                    if ( $raI->[3] == 0xFF ) {    #Row Title
+                        push @aTtlR, [ $raI->[0], $raI->[2] ];
+                    }
+                    else {                        #Col Title
+                        push @aTtlC, [ $raI->[1], $raI->[3] ];
+                    }
+                }
+                $oBook->{PrintTitle}[$iSheet] =
+                  { Row => \@aTtlR, Column => \@aTtlC };
+            }
+        }
+    }
+}
+
+#------------------------------------------------------------------------------
+# ParseNameArea (for Spreadsheet::ParseExcel) DK: 494 (ptgAread3d)
+#------------------------------------------------------------------------------
+sub _ParseNameArea {
+    my ( $sObj ) = @_;
+    my ( $iOp );
+    my @aRes = ();
+    $iOp = unpack( 'C', $sObj );
+    my $iSheet;
+    if ( $iOp == 0x3b ) {
+        my ( $iWkS, $iRs, $iRe, $iCs, $iCe ) =
+          unpack( 'v5', substr( $sObj, 1 ) );
+        $iSheet = $iWkS;
+        push @aRes, [ $iRs, $iCs, $iRe, $iCe ];
+    }
+    elsif ( $iOp == 0x29 ) {
+        my $iLen = unpack( 'v', substr( $sObj, 1, 2 ) );
+        my $iSt = 0;
+        while ( $iSt < $iLen ) {
+            my ( $iOpW, $iWkS, $iRs, $iRe, $iCs, $iCe ) =
+              unpack( 'cv5', substr( $sObj, $iSt + 3, 11 ) );
+
+            if ( $iOpW == 0x3b ) {
+                $iSheet = $iWkS;
+                push @aRes, [ $iRs, $iCs, $iRe, $iCe ];
+            }
+
+            if ( $iSt == 0 ) {
+                $iSt += 11;
+            }
+            else {
+                $iSt += 12;    #Skip 1 byte;
+            }
+        }
+    }
+    return ( $iSheet, \@aRes );
+}
+
+#------------------------------------------------------------------------------
+# ParseNameArea95 (for Spreadsheet::ParseExcel) DK: 494 (ptgAread3d)
+#------------------------------------------------------------------------------
+sub _ParseNameArea95 {
+    my ( $sObj ) = @_;
+    my ( $iOp );
+    my @aRes = ();
+    $iOp = unpack( 'C', $sObj );
+    my $iSheet;
+    if ( $iOp == 0x3b ) {
+        $iSheet = unpack( 'v', substr( $sObj, 11, 2 ) );
+        my ( $iRs, $iRe, $iCs, $iCe ) =
+          unpack( 'v2C2', substr( $sObj, 15, 6 ) );
+        push @aRes, [ $iRs, $iCs, $iRe, $iCe ];
+    }
+    elsif ( $iOp == 0x29 ) {
+        my $iLen = unpack( 'v', substr( $sObj, 1, 2 ) );
+        my $iSt = 0;
+        while ( $iSt < $iLen ) {
+            my $iOpW = unpack( 'c', substr( $sObj, $iSt + 3, 6 ) );
+            $iSheet = unpack( 'v', substr( $sObj, $iSt + 14, 2 ) );
+            my ( $iRs, $iRe, $iCs, $iCe ) =
+              unpack( 'v2C2', substr( $sObj, $iSt + 18, 6 ) );
+            push @aRes, [ $iRs, $iCs, $iRe, $iCe ] if ( $iOpW == 0x3b );
+
+            if ( $iSt == 0 ) {
+                $iSt += 21;
+            }
+            else {
+                $iSt += 22;    #Skip 1 byte;
+            }
+        }
+    }
+    return ( $iSheet, \@aRes );
+}
+
+#------------------------------------------------------------------------------
+# _subBOOL (for Spreadsheet::ParseExcel) DK: P452
+#------------------------------------------------------------------------------
+sub _subWSBOOL {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{PageFit} =
+      ( ( unpack( 'v', $sWk ) & 0x100 ) ? 1 : 0 );
+}
+
+#------------------------------------------------------------------------------
+# _subMergeArea (for Spreadsheet::ParseExcel) DK: (Not)
+#------------------------------------------------------------------------------
+sub _subMergeArea {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    my $iCnt = unpack( "v", $sWk );
+    my $oWkS = $oBook->{Worksheet}[ $oBook->{_CurSheet} ];
+    $oWkS->{MergedArea} = [] unless ( defined $oWkS->{MergedArea} );
+    for ( my $i = 0 ; $i < $iCnt ; $i++ ) {
+        my ( $iRs, $iRe, $iCs, $iCe ) =
+          unpack( 'v4', substr( $sWk, $i * 8 + 2, 8 ) );
+        for ( my $iR = $iRs ; $iR <= $iRe ; $iR++ ) {
+            for ( my $iC = $iCs ; $iC <= $iCe ; $iC++ ) {
+                $oWkS->{Cells}[$iR][$iC]->{Merged} = 1
+                  if ( defined $oWkS->{Cells}[$iR][$iC] );
+            }
+        }
+        push @{ $oWkS->{MergedArea} }, [ $iRs, $iCs, $iRe, $iCe ];
+    }
+}
+
+#------------------------------------------------------------------------------
+# DecodeBoolErr (for Spreadsheet::ParseExcel) DK: P306
+#------------------------------------------------------------------------------
+sub DecodeBoolErr {
+    my ( $iVal, $iFlg ) = @_;
+    if ( $iFlg ) {    # ERROR
+        if ( $iVal == 0x00 ) {
+            return "#NULL!";
+        }
+        elsif ( $iVal == 0x07 ) {
+            return "#DIV/0!";
+        }
+        elsif ( $iVal == 0x0F ) {
+            return "#VALUE!";
+        }
+        elsif ( $iVal == 0x17 ) {
+            return "#REF!";
+        }
+        elsif ( $iVal == 0x1D ) {
+            return "#NAME?";
+        }
+        elsif ( $iVal == 0x24 ) {
+            return "#NUM!";
+        }
+        elsif ( $iVal == 0x2A ) {
+            return "#N/A!";
+        }
+        else {
+            return "#ERR";
+        }
+    }
+    else {
+        return ( $iVal ) ? "TRUE" : "FALSE";
+    }
+}
+
+###############################################################################
+#
+# _decode_rk_number()
+#
+# Convert an encoded RK number into a real number. The RK encoding is
+# explained in some detail in the MS docs. It is a way of storing applicable
+# ints and doubles in 32bits (30 data + 2 info bits) in order to save space.
+#
+sub _decode_rk_number {
+
+    my $rk_number = shift;
+    my $number;
+
+    # Check the main RK type.
+    if ( $rk_number & 0x02 ) {
+
+        # RK Type 2 and 4, a packed integer.
+
+        # Shift off the info bits.
+        $number = $rk_number >> 2;
+
+        # Convert from unsigned to signed if required.
+        $number -= 0x40000000 if $number & 0x20000000;
+    }
+    else {
+
+        # RK Type 1 and 3, a truncated IEEE Double.
+
+        # Pack the RK number into the high 30 bits of an IEEE double.
+        $number = pack "VV", 0x0000, $rk_number & 0xFFFFFFFC;
+
+        # Reverse the packed IEEE double on big-endian machines.
+        $number = reverse $number if $BIGENDIAN;
+
+        # Unpack the number.
+        $number = unpack "d", $number;
+    }
+
+    # RK Types 3 and 4 were multiplied by 100 prior to encoding.
+    $number /= 100 if $rk_number & 0x01;
+
+    return $number;
+}
+
+###############################################################################
+#
+# _subStrWk()
+#
+# Extract the workbook strings from the SST (Shared String Table) record and
+# any following CONTINUE records.
+#
+# The workbook strings are initially contained in the SST block but may also
+# occupy one or more CONTINUE blocks. Reading the CONTINUE blocks is made a
+# little tricky by the fact that they can contain an additional initial byte
+# if a string is continued from a previous block.
+#
+# Parsing is further complicated by the fact that the continued section of the
+# string may have a different encoding (ASCII or UTF-8) from the previous
+# section. Excel does this to save space.
+#
+sub _subStrWk {
+
+    my ( $self, $biff_data, $is_continue ) = @_;
+
+    if ( $is_continue ) {
+
+        # We are reading a CONTINUE record.
+
+        if ( $self->{_buffer} eq '' ) {
+
+            # A CONTINUE block with no previous SST.
+            $self->{_buffer} .= $biff_data;
+        }
+        elsif ( !defined $self->{_string_continued} ) {
+
+            # The CONTINUE block starts with a new (non-continued) string.
+
+            # Strip the Grbit byte and store the string data.
+            $self->{_buffer} .= substr $biff_data, 1;
+        }
+        else {
+
+            # A CONTINUE block that starts with a continued string.
+
+            # The first byte (Grbit) of the CONTINUE record indicates if (0)
+            # the continued string section is single bytes or (1) double bytes.
+            my $grbit = ord $biff_data;
+
+            my ( $str_position, $str_length ) = @{ $self->{_previous_info} };
+            my $buff_length = length $self->{_buffer};
+
+            if ( $buff_length >= ( $str_position + $str_length ) ) {
+
+                # Not in a string.
+                $self->{_buffer} .= $biff_data;
+            }
+            elsif ( ( $self->{_string_continued} & 0x01 ) == ( $grbit & 0x01 ) )
+            {
+
+                # Same encoding as the previous block of the string.
+                $self->{_buffer} .= substr( $biff_data, 1 );
+            }
+            else {
+
+                # Different encoding to the previous block of the string.
+                if ( $grbit & 0x01 ) {
+
+                    # Current block is UTF-16, previous was ASCII.
+                    my ( undef, $cch ) = unpack 'vc', $self->{_buffer};
+                    substr( $self->{_buffer}, 2, 1 ) = pack( 'C', $cch | 0x01 );
+
+                    # Convert the previous ASCII, single character, portion of
+                    # the string into a double character UTF-16 string by
+                    # inserting zero bytes.
+                    for (
+                        my $i = ( $buff_length - $str_position ) ;
+                        $i >= 1 ;
+                        $i--
+                      )
+                    {
+                        substr( $self->{_buffer}, $str_position + $i, 0 ) =
+                          "\x00";
+                    }
+
+                }
+                else {
+
+                    # Current block is ASCII, previous was UTF-16.
+
+                    # Convert the current ASCII, single character, portion of
+                    # the string into a double character UTF-16 string by
+                    # inserting null bytes.
+                    my $change_length =
+                      ( $str_position + $str_length ) - $buff_length;
+
+                    # Length of the current CONTINUE record data.
+                    my $biff_length = length $biff_data;
+
+                    # Restrict the portion to be changed to the current block
+                    # if the string extends over more than one block.
+                    if ( $change_length > ( $biff_length - 1 ) * 2 ) {
+                        $change_length = ( $biff_length - 1 ) * 2;
+                    }
+
+                    # Insert the null bytes.
+                    for ( my $i = ( $change_length / 2 ) ; $i >= 1 ; $i-- ) {
+                        substr( $biff_data, $i + 1, 0 ) = "\x00";
+                    }
+
+                }
+
+                # Strip the Grbit byte and store the string data.
+                $self->{_buffer} .= substr $biff_data, 1;
+            }
+        }
+    }
+    else {
+
+        # Not a CONTINUE block therefore an SST block.
+        $self->{_buffer} .= $biff_data;
+    }
+
+    # Reset the state variables.
+    $self->{_string_continued} = undef;
+    $self->{_previous_info}    = undef;
+
+    # Extract out any full strings from the current buffer leaving behind a
+    # partial string that is continued into the next block, or an empty
+    # buffer is no string is continued.
+    while ( length $self->{_buffer} >= 4 ) {
+        my ( $str_info, $length, $str_position, $str_length ) =
+          _convBIFF8String( $self, $self->{_buffer}, 1 );
+
+        if ( defined $str_info->[0] ) {
+            push @{ $self->{PkgStr} },
+              {
+                Text    => $str_info->[0],
+                Unicode => $str_info->[1],
+                Rich    => $str_info->[2],
+                Ext     => $str_info->[3],
+              };
+            $self->{_buffer} = substr( $self->{_buffer}, $length );
+        }
+        else {
+            $self->{_string_continued} = $str_info->[1];
+            $self->{_previous_info} = [ $str_position, $str_length ];
+            last;
+        }
+    }
+}
+
+#------------------------------------------------------------------------------
+# _SwapForUnicode (for Spreadsheet::ParseExcel)
+#------------------------------------------------------------------------------
+sub _SwapForUnicode {
+    my ( $sObj ) = @_;
+
+    #    for(my $i = 0; $i<length($$sObj); $i+=2){
+    for ( my $i = 0 ; $i < ( int( length( $$sObj ) / 2 ) * 2 ) ; $i += 2 ) {
+        my $sIt = substr( $$sObj, $i, 1 );
+        substr( $$sObj, $i, 1 ) = substr( $$sObj, $i + 1, 1 );
+        substr( $$sObj, $i + 1, 1 ) = $sIt;
+    }
+}
+
+#------------------------------------------------------------------------------
+# _NewCell (for Spreadsheet::ParseExcel)
+#------------------------------------------------------------------------------
+sub _NewCell {
+    my ( $oBook, $iR, $iC, %rhKey ) = @_;
+    my ( $sWk, $iLen );
+    return undef unless ( defined $oBook->{_CurSheet} );
+
+    my $FmtClass = $oBook->{FmtClass};
+    $rhKey{Type} =
+      $FmtClass->ChkType( $rhKey{Numeric}, $rhKey{Format}{FmtIdx} );
+    my $FmtStr = $oBook->{FormatStr}{ $rhKey{Format}{FmtIdx} };
+
+    # Set "Date" type if required for numbers in a MulRK BIFF block.
+    if ( defined $FmtStr && $rhKey{Type} eq "Numeric" ) {
+
+        # Match a range of possible date formats. Note: this isn't important
+        # except for reporting. The number will still be converted to a date
+        # by ExcelFmt() even if 'Type' isn't set to 'Date'.
+        if ( $FmtStr =~ m{^[dmy][-\\/dmy]*$}i ) {
+            $rhKey{Type} = "Date";
+        }
+    }
+
+    my $oCell = Spreadsheet::ParseExcel::Cell->new(
+        Val      => $rhKey{Val},
+        FormatNo => $rhKey{FormatNo},
+        Format   => $rhKey{Format},
+        Code     => $rhKey{Code},
+        Type     => $rhKey{Type},
+    );
+    $oCell->{_Kind} = $rhKey{Kind};
+    $oCell->{_Value} = $FmtClass->ValFmt( $oCell, $oBook );
+    if ( $rhKey{Rich} ) {
+        my @aRich = ();
+        my $sRich = $rhKey{Rich};
+        for ( my $iWk = 0 ; $iWk < length( $sRich ) ; $iWk += 4 ) {
+            my ( $iPos, $iFnt ) = unpack( 'v2', substr( $sRich, $iWk ) );
+            push @aRich, [ $iPos, $oBook->{Font}[$iFnt] ];
+        }
+        $oCell->{Rich} = \@aRich;
+    }
+
+    if ( defined $_CellHandler ) {
+        if ( defined $_Object ) {
+            no strict;
+            ref( $_CellHandler ) eq "CODE"
+              ? $_CellHandler->(
+                $_Object, $oBook, $oBook->{_CurSheet}, $iR, $iC, $oCell
+              )
+              : $_CellHandler->callback( $_Object, $oBook, $oBook->{_CurSheet},
+                $iR, $iC, $oCell );
+        }
+        else {
+            $_CellHandler->( $oBook, $oBook->{_CurSheet}, $iR, $iC, $oCell );
+        }
+    }
+    unless ( $_NotSetCell ) {
+        $oBook->{Worksheet}[ $oBook->{_CurSheet} ]->{Cells}[$iR][$iC] = $oCell;
+    }
+    return $oCell;
+}
+
+#------------------------------------------------------------------------------
+# ColorIdxToRGB (for Spreadsheet::ParseExcel)
+#
+# TODO JMN Make this a Workbook method and re-document.
+#
+#------------------------------------------------------------------------------
+sub ColorIdxToRGB {
+    my ( $sPkg, $iIdx ) = @_;
+    return ( ( defined $aColor[$iIdx] ) ? $aColor[$iIdx] : $aColor[0] );
+}
+
+
+###############################################################################
+#
+# error().
+#
+# Return an error string for a failed parse().
+#
+sub error {
+
+    my $self = shift;
+
+    my $parse_error = $self->{_error_status};
+
+    if ( exists $error_strings{$parse_error} ) {
+        return $error_strings{$parse_error};
+    }
+    else {
+        return 'Unknown parse error';
+    }
+}
+
+
+###############################################################################
+#
+# error_code().
+#
+# Return an error code for a failed parse().
+#
+sub error_code {
+
+    my $self = shift;
+
+    return $self->{_error_status};
+}
+
+
+###############################################################################
+#
+# Mapping between legacy method names and new names.
+#
+{
+    no warnings;    # Ignore warnings about variables used only once.
+    *Parse = *parse;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Spreadsheet::ParseExcel - Read information from an Excel file.
+
+=head1 SYNOPSIS
+
+    #!/usr/bin/perl -w
+
+    use strict;
+    use Spreadsheet::ParseExcel;
+
+    my $parser   = Spreadsheet::ParseExcel->new();
+    my $workbook = $parser->parse('Book1.xls');
+
+    if ( !defined $workbook ) {
+        die $parser->error(), ".\n";
+    }
+
+    for my $worksheet ( $workbook->worksheets() ) {
+
+        my ( $row_min, $row_max ) = $worksheet->row_range();
+        my ( $col_min, $col_max ) = $worksheet->col_range();
+
+        for my $row ( $row_min .. $row_max ) {
+            for my $col ( $col_min .. $col_max ) {
+
+                my $cell = $worksheet->get_cell( $row, $col );
+                next unless $cell;
+
+                print "Row, Col    = ($row, $col)\n";
+                print "Value       = ", $cell->value(),       "\n";
+                print "Unformatted = ", $cell->unformatted(), "\n";
+                print "\n";
+            }
+        }
+    }
+
+
+=head1 DESCRIPTION
+
+The Spreadsheet::ParseExcel module can be used to read information from Excel 95-2003 binary files.
+
+The module cannot read files in the Excel 2007 Open XML XLSX format. See the L<Spreadsheet::XLSX> module instead.
+
+=head1 Parser
+
+=head2 new()
+
+The C<new()> method is used to create a new C<Spreadsheet::ParseExcel> parser object.
+
+    my $parser = Spreadsheet::ParseExcel->new();
+
+It it possible to pass a password to decrypt an encrypted file:
+
+    $parser = Spreadsheet::ParseExcel->new( Password => 'secret' );
+
+Only the default Excel encryption scheme is currently supported. See L</Decryption>.
+
+As an advanced feature it is also possible to pass a call-back handler to the parser to control the parsing of the spreadsheet.
+
+    $parser = Spreadsheet::ParseExcel->new(
+        CellHandler => \&cell_handler,
+        NotSetCell  => 1,
+    );
+
+The call-back can be used to ignore certain cells or to reduce memory usage. See the section L<Reducing the memory usage of Spreadsheet::ParseExcel> for more information.
+
+
+=head2 parse($filename, $formatter)
+
+The Parser C<parse()> method returns a L</Workbook> object.
+
+    my $parser   = Spreadsheet::ParseExcel->new();
+    my $workbook = $parser->parse('Book1.xls');
+
+If an error occurs C<parse()> returns C<undef>. In general, programs should contain a test for failed parsing as follows:
+
+    my $parser   = Spreadsheet::ParseExcel->new();
+    my $workbook = $parser->parse('Book1.xls');
+
+    if ( !defined $workbook ) {
+        die $parser->error(), ".\n";
+    }
+
+The C<$filename> parameter is generally the file to be parsed. However, it can also be a filehandle or a scalar reference.
+
+The optional C<$formatter> parameter can be an reference to a L</Formatter Class> to format the value of cells. This is useful for parsing workbooks with Unicode or Asian characters:
+
+    my $parser    = Spreadsheet::ParseExcel->new();
+    my $formatter = Spreadsheet::ParseExcel::FmtJapan->new();
+    my $workbook  = $parser->parse( 'Book1.xls', $formatter );
+
+The L<Spreadsheet::ParseExcel::FmtJapan> formatter also supports Unicode. If you encounter any encoding problems with the default formatter try that instead.
+
+
+=head2 error()
+
+The Parser C<error()> method returns an error string if a C<parse()> fails:
+
+    my $parser   = Spreadsheet::ParseExcel->new();
+    my $workbook = $parser->parse('Book1.xls');
+
+    if ( !defined $workbook ) {
+        die $parser->error(), ".\n";
+    }
+
+If you wish to generate you own error string you can use the C<error_code()> method instead (see below). The C<error()> and C<error_code()> values are as follows:
+
+    error()                         error_code()
+    =======                         ============
+    ''                              0
+    'File not found'                1
+    'No Excel data found in file'   2
+    'File is encrypted'             3
+
+
+The C<error_code()> method is explained below.
+
+Spreadsheet::ParseExcel will try to decrypt an encrypted Excel file using the default password or a user supplied password passed to C<new()>, see above. If these fail the module will return the C<'File is encrypted'> error. Only the default Excel encryption scheme is currently supported, see L</Decryption>.
+
+
+=head2 error_code()
+
+The Parser C<error_code()> method returns an error code if a C<parse()> fails:
+
+    my $parser   = Spreadsheet::ParseExcel->new();
+    my $workbook = $parser->parse('Book1.xls');
+
+    if ( !defined $workbook ) {
+        die "Got error code ", $parser->error_code, ".\n";
+    }
+
+This can be useful if you wish to employ you own error strings or error handling methods.
+
+
+=head1 Workbook
+
+A C<Spreadsheet::ParseExcel::Workbook> is created via the C<Spreadsheet::ParseExcel> C<parse()> method:
+
+    my $parser   = Spreadsheet::ParseExcel->new();
+    my $workbook = $parser->parse('Book1.xls');
+
+The main methods of the Workbook class are:
+
+    $workbook->worksheets()
+    $workbook->worksheet()
+    $workbook->worksheet_count()
+    $workbook->get_filename()
+
+These more commonly used methods of the Workbook class are outlined below. The other, less commonly used, methods are documented in L<Spreadsheet::ParseExcel::Worksheet>.
+
+
+=head2 worksheets()
+
+Returns an array of L</Worksheet> objects. This was most commonly used to iterate over the worksheets in a workbook:
+
+    for my $worksheet ( $workbook->worksheets() ) {
+        ...
+    }
+
+
+=head2 worksheet()
+
+The C<worksheet()> method returns a single C<Worksheet> object using either its name or index:
+
+    $worksheet = $workbook->worksheet('Sheet1');
+    $worksheet = $workbook->worksheet(0);
+
+Returns C<undef> if the sheet name or index doesn't exist.
+
+
+=head2 worksheet_count()
+
+The C<worksheet_count()> method returns the number of Worksheet objects in the Workbook.
+
+    my $worksheet_count = $workbook->worksheet_count();
+
+
+=head2 get_filename()
+
+The C<get_filename()> method returns the name of the Excel file of C<undef> if the data was read from a filehandle rather than a file.
+
+    my $filename = $workbook->get_filename();
+
+
+=head2 Other Workbook Methods
+
+For full documentation of the methods available via a Workbook object see L<Spreadsheet::ParseExcel::Workbook>.
+
+=head1 Worksheet
+
+The C<Spreadsheet::ParseExcel::Worksheet> class encapsulates the properties of an Excel worksheet.
+
+A Worksheet object is obtained via the L</worksheets()> or L</worksheet()> methods.
+
+    for my $worksheet ( $workbook->worksheets() ) {
+        ...
+    }
+
+    # Or:
+
+    $worksheet = $workbook->worksheet('Sheet1');
+    $worksheet = $workbook->worksheet(0);
+
+The most commonly used methods of the Worksheet class are:
+
+    $worksheet->get_cell()
+    $worksheet->row_range()
+    $worksheet->col_range()
+    $worksheet->get_name()
+
+The Spreadsheet::ParseExcel::Worksheet class exposes a lot of methods but in general very few are required unless you are writing an advanced filter.
+
+The most commonly used methods are detailed below. The others are documented in L<Spreadsheet::ParseExcel::Worksheet>.
+
+=head2 get_cell($row, $col)
+
+Return the L</Cell> object at row C<$row> and column C<$col> if it is defined. Otherwise returns undef.
+
+    my $cell = $worksheet->get_cell($row, $col);
+
+
+=head2 row_range()
+
+Returns a two-element list C<($min, $max)> containing the minimum and maximum defined rows in the worksheet. If there is no row defined C<$max> is smaller than C<$min>.
+
+    my ( $row_min, $row_max ) = $worksheet->row_range();
+
+
+=head2 col_range()
+
+Returns a two-element list C<($min, $max)> containing the minimum and maximum of defined columns in the worksheet. If there is no column defined C<$max> is smaller than C<$min>.
+
+    my ( $col_min, $col_max ) = $worksheet->col_range();
+
+
+=head2 get_name()
+
+The C<get_name()> method returns the name of the worksheet, such as 'Sheet1'.
+
+    my $name = $worksheet->get_name();
+
+=head2 Other Worksheet Methods
+
+For other, less commonly used, Worksheet methods see L<Spreadsheet::ParseExcel::Worksheet>.
+
+=head1 Cell
+
+The C<Spreadsheet::ParseExcel::Cell> class has the following main methods.
+
+    $cell->value()
+    $cell->unformatted()
+
+=head2 value()
+
+The C<value()> method returns the formatted value of the cell.
+
+    my $value = $cell->value();
+
+Formatted in this sense refers to the numeric format of the cell value. For example a number such as 40177 might be formatted as 40,117, 40117.000 or even as the date 2009/12/30.
+
+If the cell doesn't contain a numeric format then the formatted and unformatted cell values are the same, see the C<unformatted()> method below.
+
+For a defined C<$cell> the C<value()> method will always return a value.
+
+In the case of a cell with formatting but no numeric or string contents the method will return the empty string C<''>.
+
+
+=head2 unformatted()
+
+The C<unformatted()> method returns the unformatted value of the cell.
+
+    my $unformatted = $cell->unformatted();
+
+Returns the cell value without a numeric format. See the C<value()> method above.
+
+=head2 Other Cell Methods
+
+For other, less commonly used, Worksheet methods see L<Spreadsheet::ParseExcel::Cell>.
+
+
+=head1 Format
+
+The C<Spreadsheet::ParseExcel::Format> class has the following properties:
+
+=head2 Format properties
+
+    $format->{Font}
+    $format->{AlignH}
+    $format->{AlignV}
+    $format->{Indent}
+    $format->{Wrap}
+    $format->{Shrink}
+    $format->{Rotate}
+    $format->{JustLast}
+    $format->{ReadDir}
+    $format->{BdrStyle}
+    $format->{BdrColor}
+    $format->{BdrDiag}
+    $format->{Fill}
+    $format->{Lock}
+    $format->{Hidden}
+    $format->{Style}
+
+These properties are generally only of interest to advanced users. Casual users can skip this section.
+
+=head2 $format->{Font}
+
+Returns the L</Font> object for the Format.
+
+=head2 $format->{AlignH}
+
+Returns the horizontal alignment of the format where the value has the following meaning:
+
+    0 => No alignment
+    1 => Left
+    2 => Center
+    3 => Right
+    4 => Fill
+    5 => Justify
+    6 => Center across
+    7 => Distributed/Equal spaced
+
+=head2 $format->{AlignV}
+
+Returns the vertical alignment of the format where the value has the following meaning:
+
+    0 => Top
+    1 => Center
+    2 => Bottom
+    3 => Justify
+    4 => Distributed/Equal spaced
+
+=head2 $format->{Indent}
+
+Returns the indent level of the C<Left> horizontal alignment.
+
+=head2 $format->{Wrap}
+
+Returns true if textwrap is on.
+
+=head2 $format->{Shrink}
+
+Returns true if "Shrink to fit" is set for the format.
+
+=head2 $format->{Rotate}
+
+Returns the text rotation. In Excel97+, it returns the angle in degrees of the text rotation.
+
+In Excel95 or earlier it returns a value as follows:
+
+    0 => No rotation
+    1 => Top down
+    2 => 90 degrees anti-clockwise,
+    3 => 90 clockwise
+
+=head2 $format->{JustLast}
+
+Return true if the "justify last" property is set for the format.
+
+=head2 $format->{ReadDir}
+
+Returns the direction that the text is read from.
+
+=head2 $format->{BdrStyle}
+
+Returns an array ref of border styles as follows:
+
+    [ $left, $right, $top, $bottom ]
+
+=head2 $format->{BdrColor}
+
+Returns an array ref of border color indexes as follows:
+
+    [ $left, $right, $top, $bottom ]
+
+=head2 $format->{BdrDiag}
+
+Returns an array ref of diagonal border kind, style and color index as follows:
+
+    [$kind, $style, $color ]
+
+Where kind is:
+
+    0 => None
+    1 => Right-Down
+    2 => Right-Up
+    3 => Both
+
+=head2 $format->{Fill}
+
+Returns an array ref of fill pattern and color indexes as follows:
+
+    [ $pattern, $front_color, $back_color ]
+
+=head2 $format->{Lock}
+
+Returns true if the cell is locked.
+
+=head2 $format->{Hidden}
+
+Returns true if the cell is Hidden.
+
+=head2 $format->{Style}
+
+Returns true if the format is a Style format.
+
+
+
+
+=head1 Font
+
+I<Spreadsheet::ParseExcel::Font>
+
+Format class has these properties:
+
+=head1 Font Properties
+
+    $font->{Name}
+    $font->{Bold}
+    $font->{Italic}
+    $font->{Height}
+    $font->{Underline}
+    $font->{UnderlineStyle}
+    $font->{Color}
+    $font->{Strikeout}
+    $font->{Super}
+
+=head2 $font->{Name}
+
+Returns the name of the font, for example 'Arial'.
+
+=head2 $font->{Bold}
+
+Returns true if the font is bold.
+
+=head2 $font->{Italic}
+
+Returns true if the font is italic.
+
+=head2 $font->{Height}
+
+Returns the size (height) of the font.
+
+=head2 $font->{Underline}
+
+Returns true if the font in underlined.
+
+=head2 $font->{UnderlineStyle}
+
+Returns the style of an underlined font where the value has the following meaning:
+
+     0 => None
+     1 => Single
+     2 => Double
+    33 => Single accounting
+    34 => Double accounting
+
+=head2 $font->{Color}
+
+Returns the color index for the font. The index can be converted to a RGB string using the C<ColorIdxToRGB()> Parser method.
+
+=head2 $font->{Strikeout}
+
+Returns true if the font has the strikeout property set.
+
+=head2 $font->{Super}
+
+Returns one of the following values if the superscript or subscript property of the font is set:
+
+    0 => None
+    1 => Superscript
+    2 => Subscript
+
+=head1 Formatter Class
+
+Formatters can be passed to the C<parse()> method to deal with Unicode or Asian formatting.
+
+Spreadsheet::ParseExcel includes 2 formatter classes. C<FmtDefault> and C<FmtJapanese>. It is also possible to create a user defined formatting class.
+
+The formatter class C<Spreadsheet::ParseExcel::Fmt*> should provide the following functions:
+
+
+=head2 ChkType($self, $is_numeric, $format_index)
+
+Method to check the type of data in the cell. Should return C<Date>, C<Numeric> or C<Text>. It is passed the following parameters:
+
+=over
+
+=item $self
+
+A scalar reference to the Formatter object.
+
+=item $is_numeric
+
+If true, the value seems to be number.
+
+=item $format_index
+
+The index number for the cell Format object.
+
+=back
+
+=head2 TextFmt($self, $string_data, $string_encoding)
+
+Converts the string data in the cell into the correct encoding.  It is passed the following parameters:
+
+=over
+
+=item $self
+
+A scalar reference to the Formatter object.
+
+=item $string_data
+
+The original string/text data.
+
+=item $string_encoding
+
+The character encoding of original string/text.
+
+=back
+
+=head2 ValFmt($self, $cell, $workbook)
+
+Convert the original unformatted cell value into the appropriate formatted value. For instance turn a number into a formatted date.  It is passed the following parameters:
+
+=over
+
+=item $self
+
+A scalar reference to the Formatter object.
+
+=item $cell
+
+A scalar reference to the Cell object.
+
+=item $workbook
+
+A scalar reference to the Workbook object.
+
+=back
+
+
+=head2 FmtString($self, $cell, $workbook)
+
+Get the format string for the Cell.  It is passed the following parameters:
+
+=over
+
+=item $self
+
+A scalar reference to the Formatter object.
+
+=item $cell
+
+A scalar reference to the Cell object.
+
+=item $workbook
+
+A scalar reference to the Workbook object.
+
+=back
+
+
+=head1 Reducing the memory usage of Spreadsheet::ParseExcel
+
+In some cases a C<Spreadsheet::ParseExcel> application may consume a lot of memory when processing a large Excel file and, as a result, may fail to complete. The following explains why this can occur and how to resolve it.
+
+C<Spreadsheet::ParseExcel> processes an Excel file in two stages. In the first stage it extracts the Excel binary stream from the OLE container file using C<OLE::Storage_Lite>. In the second stage it parses the binary stream to read workbook, worksheet and cell data which it then stores in memory. The majority of the memory usage is required for storing cell data.
+
+The reason for this is that as the Excel file is parsed and each cell is encountered a cell handling function creates a relatively large nested cell object that contains the cell value and all of the data that relates to the cell formatting. For large files (a 10MB Excel file on a 256MB system) this overhead can cause the system to grind to a halt.
+
+However, in a lot of cases when an Excel file is being processed the only information that is required are the cell values. In these cases it is possible to avoid most of the memory overhead by specifying your own cell handling function and by telling Spreadsheet::ParseExcel not to store the parsed cell data. This is achieved by passing a cell handler function to C<new()> when creating the parse object. Here is an example.
+
+    #!/usr/bin/perl -w
+
+    use strict;
+    use Spreadsheet::ParseExcel;
+
+    my $parser = Spreadsheet::ParseExcel->new(
+        CellHandler => \&cell_handler,
+        NotSetCell  => 1
+    );
+
+    my $workbook = $parser->parse('file.xls');
+
+    sub cell_handler {
+
+        my $workbook    = $_[0];
+        my $sheet_index = $_[1];
+        my $row         = $_[2];
+        my $col         = $_[3];
+        my $cell        = $_[4];
+
+        # Do something useful with the formatted cell value
+        print $cell->value(), "\n";
+
+    }
+
+
+The user specified cell handler is passed as a code reference to C<new()> along with the parameter C<NotSetCell> which tells Spreadsheet::ParseExcel not to store the parsed cell. Note, you don't have to iterate over the rows and columns, this happens automatically as part of the parsing.
+
+The cell handler is passed 5 arguments. The first, C<$workbook>, is a reference to the C<Spreadsheet::ParseExcel::Workbook> object that represent the parsed workbook. This can be used to access any of the C<Spreadsheet::ParseExcel::Workbook> methods, see L</Workbook>. The second C<$sheet_index> is the zero-based index of the worksheet being parsed. The third and fourth, C<$row> and C<$col>, are the zero-based row and column number of the cell. The fifth, C<$cell>, is a reference to the C<Spreadsheet::ParseExcel::Cell> object. This is used to extract the data from the cell. See L</Cell> for more information.
+
+This technique can be useful if you are writing an Excel to database filter since you can put your DB calls in the cell handler.
+
+If you don't want all of the data in the spreadsheet you can add some control logic to the cell handler. For example we can extend the previous example so that it only prints the first 10 rows of the first two worksheets in the parsed workbook by adding some C<if()> statements to the cell handler:
+
+    #!/usr/bin/perl -w
+
+    use strict;
+    use Spreadsheet::ParseExcel;
+
+    my $parser = Spreadsheet::ParseExcel->new(
+        CellHandler => \&cell_handler,
+        NotSetCell  => 1
+    );
+
+    my $workbook = $parser->parse('file.xls');
+
+    sub cell_handler {
+
+        my $workbook    = $_[0];
+        my $sheet_index = $_[1];
+        my $row         = $_[2];
+        my $col         = $_[3];
+        my $cell        = $_[4];
+
+        # Skip some worksheets and rows (inefficiently).
+        return if $sheet_index >= 3;
+        return if $row >= 10;
+
+        # Do something with the formatted cell value
+        print $cell->value(), "\n";
+
+    }
+
+
+However, this still processes the entire workbook. If you wish to save some additional processing time you can abort the parsing after you have read the data that you want, using the workbook C<ParseAbort> method:
+
+    #!/usr/bin/perl -w
+
+    use strict;
+    use Spreadsheet::ParseExcel;
+
+    my $parser = Spreadsheet::ParseExcel->new(
+        CellHandler => \&cell_handler,
+        NotSetCell  => 1
+    );
+
+    my $workbook = $parser->parse('file.xls');
+
+    sub cell_handler {
+
+        my $workbook    = $_[0];
+        my $sheet_index = $_[1];
+        my $row         = $_[2];
+        my $col         = $_[3];
+        my $cell        = $_[4];
+
+        # Skip some worksheets and rows (more efficiently).
+        if ( $sheet_index >= 1 and $row >= 10 ) {
+            $workbook->ParseAbort(1);
+            return;
+        }
+
+        # Do something with the formatted cell value
+        print $cell->value(), "\n";
+
+    }
+
+=head1 Decryption
+
+If a workbook is "protected" then Excel will encrypt the file whether a password is supplied or not. As of version 0.59 Spreadsheet::ParseExcel supports decrypting Excel workbooks using a default or user supplied password. However, only the following encryption scheme is supported:
+
+    Office 97/2000 Compatible encryption
+
+The following encryption methods are not supported:
+
+    Weak Encryption (XOR)
+    RC4, Microsoft Base Cryptographic Provider v1.0
+    RC4, Microsoft Base DSS and Diffie-Hellman Cryptographic Provider
+    RC4, Microsoft DH SChannel Cryptographic Provider
+    RC4, Microsoft Enhanced Cryptographic Provider v1.0
+    RC4, Microsoft Enhanced DSS and Diffie-Hellman Cryptographic Provider
+    RC4, Microsoft Enhanced RSA and AES Cryptographic Provider
+    RC4, Microsoft RSA SChannel Cryptographic Provider
+    RC4, Microsoft Strong Cryptographic Provider
+
+See the following for more information on Excel encryption: L<http://office.microsoft.com/en-us/office-2003-resource-kit/important-aspects-of-password-and-encryption-protection-HA001140311.aspx>.
+
+
+
+=head1 KNOWN PROBLEMS
+
+=over
+
+=item * Issues reported by users: L<http://rt.cpan.org/Public/Dist/Display.html?Name=Spreadsheet-ParseExcel>
+
+=item * This module cannot read the values of formulas from files created with Spreadsheet::WriteExcel unless the user specified the values when creating the file (which is generally not the case). The reason for this is that Spreadsheet::WriteExcel writes the formula but not the formula result since it isn't in a position to calculate arbitrary Excel formulas without access to Excel's formula engine.
+
+=item * If Excel has date fields where the specified format is equal to the system-default for the short-date locale, Excel does not store the format, but defaults to an internal format which is system dependent. In these cases ParseExcel uses the date format 'yyyy-mm-dd'.
+
+=back
+
+
+
+
+=head1 REPORTING A BUG
+
+Bugs can be reported via rt.cpan.org. See the following for instructions on bug reporting for Spreadsheet::ParseExcel
+
+L<http://rt.cpan.org/Public/Dist/Display.html?Name=Spreadsheet-ParseExcel>
+
+
+
+
+=head1 SEE ALSO
+
+=over
+
+=item * xls2csv by Ken Prows L<http://search.cpan.org/~ken/xls2csv-1.06/script/xls2csv>.
+
+=item * xls2csv and xlscat by H.Merijn Brand (these utilities are part of Spreadsheet::Read, see below).
+
+=item * excel2txt by Ken Youens-Clark, L<http://search.cpan.org/~kclark/excel2txt/excel2txt>. This is an excellent example of an Excel filter using Spreadsheet::ParseExcel. It can produce CSV, Tab delimited, Html, XML and Yaml.
+
+=item * XLSperl by Jon Allen L<http://search.cpan.org/~jonallen/XLSperl/bin/XLSperl>. This application allows you to use Perl "one-liners" with Microsoft Excel files.
+
+=item * Spreadsheet::XLSX L<http://search.cpan.org/~dmow/Spreadsheet-XLSX/lib/Spreadsheet/XLSX.pm> by Dmitry Ovsyanko. A module with a similar interface to Spreadsheet::ParseExcel for parsing Excel 2007 XLSX OpenXML files.
+
+=item * Spreadsheet::Read L<http://search.cpan.org/~hmbrand/Spreadsheet-Read/Read.pm> by H.Merijn Brand. A single interface for reading several different spreadsheet formats.
+
+=item * Spreadsheet::WriteExcel L<http://search.cpan.org/~jmcnamara/Spreadsheet-WriteExcel/lib/Spreadsheet/WriteExcel.pm>. A perl module for creating new Excel files.
+
+=item * Spreadsheet::ParseExcel::SaveParser L<http://search.cpan.org/~jmcnamara/Spreadsheet-ParseExcel/lib/Spreadsheet/ParseExcel/SaveParser.pm>. This is a combination of Spreadsheet::ParseExcel and Spreadsheet::WriteExcel and it allows you to "rewrite" an Excel file. See the following example L<http://search.cpan.org/~jmcnamara/Spreadsheet-WriteExcel/lib/Spreadsheet/WriteExcel.pm#MODIFYING_AND_REWRITING_EXCEL_FILES>. It is part of the Spreadsheet::ParseExcel distro.
+
+=item * Text::CSV_XS L<http://search.cpan.org/~hmbrand/Text-CSV_XS/CSV_XS.pm> by H.Merijn Brand. A fast and rigorous module for reading and writing CSV data. Don't consider rolling your own CSV handling, use this module instead.
+
+=back
+
+
+
+
+=head1 MAILING LIST
+
+There is a Google group for discussing and asking questions about Spreadsheet::ParseExcel. This is a good place to search to see if your question has been asked before:  L<http://groups-beta.google.com/group/spreadsheet-parseexcel/>
+
+
+
+
+=head1 DONATIONS
+
+If you'd care to donate to the Spreadsheet::ParseExcel project, you can do so via PayPal: L<http://tinyurl.com/7ayes>
+
+
+
+
+=head1 TODO
+
+=over
+
+=item * The current maintenance work is directed towards making the documentation more useful, improving and simplifying the API, and improving the maintainability of the code base. After that new features will be added.
+
+=item * Fix open bugs and documentation for SaveParser.
+
+=item * Add Formula support, Hyperlink support, Named Range support.
+
+=item * Improve Spreadsheet::ParseExcel::SaveParser compatibility with Spreadsheet::WriteExcel.
+
+=item * Improve Unicode and other encoding support. This will probably require dropping support for perls prior to 5.8+.
+
+=back
+
+
+
+=head1 ACKNOWLEDGEMENTS
+
+From Kawai Takanori:
+
+First of all, I would like to acknowledge the following valuable programs and modules:
+XHTML, OLE::Storage and Spreadsheet::WriteExcel.
+
+In no particular order: Yamaji Haruna, Simamoto Takesi, Noguchi Harumi, Ikezawa Kazuhiro, Suwazono Shugo, Hirofumi Morisada, Michael Edwards, Kim Namusk, Slaven Rezic, Grant Stevens, H.Merijn Brand and many many people + Kawai Mikako.
+
+Alexey Mazurin added the decryption facility.
+
+
+
+=head1 DISCLAIMER OF WARRANTY
+
+Because this software is licensed free of charge, there is no warranty for the software, to the extent permitted by applicable law. Except when otherwise stated in writing the copyright holders and/or other parties provide the software "as is" without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the software is with you. Should the software prove defective, you assume the cost of all necessary servicing, repair, or correction.
+
+In no event unless required by applicable law or agreed to in writing will any copyright holder, or any other party who may modify and/or redistribute the software as permitted by the above licence, be liable to you for damages, including any general, special, incidental, or consequential damages arising out of the use or inability to use the software (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties or a failure of the software to operate with any other software), even if such holder or other party has been advised of the possibility of such damages.
+
+
+
+
+=head1 LICENSE
+
+Either the Perl Artistic Licence L<http://dev.perl.org/licenses/artistic.html> or the GPL L<http://www.opensource.org/licenses/gpl-license.php>
+
+
+
+
+=head1 AUTHOR
+
+Current maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori (Hippo2000) kwitknr@cpan.org
+
+
+
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2011 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved. This is free software. You may distribute under the terms of either the GNU General Public License or the Artistic License.
+
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Cell.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Cell.pm
new file mode 100644
index 0000000..2527075
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Cell.pm
@@ -0,0 +1,314 @@
+package Spreadsheet::ParseExcel::Cell;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::Cell - A class for Cell data and formatting.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+our $VERSION = '0.59';
+
+###############################################################################
+#
+# new()
+#
+# Constructor.
+#
+sub new {
+    my ( $package, %properties ) = @_;
+    my $self = \%properties;
+
+    bless $self, $package;
+}
+
+###############################################################################
+#
+# value()
+#
+# Returns the formatted value of the cell.
+#
+sub value {
+
+    my $self = shift;
+
+    return $self->{_Value};
+}
+
+###############################################################################
+#
+# unformatted()
+#
+# Returns the unformatted value of the cell.
+#
+sub unformatted {
+
+    my $self = shift;
+
+    return $self->{Val};
+}
+
+###############################################################################
+#
+# get_format()
+#
+# Returns the Format object for the cell.
+#
+sub get_format {
+
+    my $self = shift;
+
+    return $self->{Format};
+}
+
+###############################################################################
+#
+# type()
+#
+# Returns the type of cell such as Text, Numeric or Date.
+#
+sub type {
+
+    my $self = shift;
+
+    return $self->{Type};
+}
+
+###############################################################################
+#
+# encoding()
+#
+# Returns the character encoding of the cell.
+#
+sub encoding {
+
+    my $self = shift;
+
+    if ( !defined $self->{Code} ) {
+        return 1;
+    }
+    elsif ( $self->{Code} eq 'ucs2' ) {
+        return 2;
+    }
+    elsif ( $self->{Code} eq '_native_' ) {
+        return 3;
+    }
+    else {
+        return 0;
+    }
+
+    return $self->{Code};
+}
+
+###############################################################################
+#
+# is_merged()
+#
+# Returns true if the cell is merged.
+#
+sub is_merged {
+
+    my $self = shift;
+
+    return $self->{Merged};
+}
+
+###############################################################################
+#
+# get_rich_text()
+#
+# Returns an array ref of font information about each string block in a "rich",
+# i.e. multi-format, string.
+#
+sub get_rich_text {
+
+    my $self = shift;
+
+    return $self->{Rich};
+}
+
+###############################################################################
+#
+# Mapping between legacy method names and new names.
+#
+{
+    no warnings;    # Ignore warnings about variables used only once.
+    *Value = *value;
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::Cell - A class for Cell data and formatting.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 Methods
+
+The following Cell methods are available:
+
+    $cell->value()
+    $cell->unformatted()
+    $cell->get_format()
+    $cell->type()
+    $cell->encoding()
+    $cell->is_merged()
+    $cell->get_rich_text()
+
+
+=head2 value()
+
+The C<value()> method returns the formatted value of the cell.
+
+    my $value = $cell->value();
+
+Formatted in this sense refers to the numeric format of the cell value. For example a number such as 40177 might be formatted as 40,117, 40117.000 or even as the date 2009/12/30.
+
+If the cell doesn't contain a numeric format then the formatted and unformatted cell values are the same, see the C<unformatted()> method below.
+
+For a defined C<$cell> the C<value()> method will always return a value.
+
+In the case of a cell with formatting but no numeric or string contents the method will return the empty string C<''>.
+
+
+=head2 unformatted()
+
+The C<unformatted()> method returns the unformatted value of the cell.
+
+    my $unformatted = $cell->unformatted();
+
+Returns the cell value without a numeric format. See the C<value()> method above.
+
+
+=head2 get_format()
+
+The C<get_format()> method returns the L<Spreadsheet::ParseExcel::Format> object for the cell.
+
+    my $format = $cell->get_format();
+
+If a user defined format hasn't been applied to the cell then the default cell format is returned.
+
+
+=head2 type()
+
+The C<type()> method returns the type of cell such as Text, Numeric or Date. If the type was detected as Numeric, and the Cell Format matches C<m{^[dmy][-\\/dmy]*$}i>, it will be treated as a Date type.
+
+    my $type = $cell->type();
+
+See also L<Dates and Time in Excel>.
+
+
+=head2 encoding()
+
+The C<encoding()> method returns the character encoding of the cell.
+
+    my $encoding = $cell->encoding();
+
+This method is only of interest to developers. In general Spreadsheet::ParseExcel will return all character strings in UTF-8 regardless of the encoding used by Excel.
+
+The C<encoding()> method returns one of the following values:
+
+=over
+
+=item * 0: Unknown format. This shouldn't happen. In the default case the format should be 1.
+
+=item * 1: 8bit ASCII or single byte UTF-16. This indicates that the characters are encoded in a single byte. In Excel 95 and earlier This usually meant ASCII or an international variant. In Excel 97 it refers to a compressed UTF-16 character string where all of the high order bytes are 0 and are omitted to save space.
+
+=item * 2: UTF-16BE.
+
+=item * 3: Native encoding. In Excel 95 and earlier this encoding was used to represent multi-byte character encodings such as SJIS.
+
+=back
+
+
+=head2 is_merged()
+
+The C<is_merged()> method returns true if the cell is merged.
+
+    my $is_merged = $cell->is_merged();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_rich_text()
+
+The C<get_rich_text()> method returns an array ref of font information about each string block in a "rich", i.e. multi-format, string.
+
+    my $rich_text = $cell->get_rich_text();
+
+The return value is an arrayref of arrayrefs in the form:
+
+    [
+        [ $start_position, $font_object ],
+         ...,
+    ]
+
+Returns undef if the property isn't set.
+
+
+=head1 Dates and Time in Excel
+
+Dates and times in Excel are represented by real numbers, for example "Jan 1 2001 12:30 PM" is represented by the number 36892.521.
+
+The integer part of the number stores the number of days since the epoch and the fractional part stores the percentage of the day.
+
+A date or time in Excel is just like any other number. The way in which it is displayed is controlled by the number format:
+
+    Number format               $cell->value()            $cell->unformatted()
+    =============               ==============            ==============
+    'dd/mm/yy'                  '28/02/08'                39506.5
+    'mm/dd/yy'                  '02/28/08'                39506.5
+    'd-m-yyyy'                  '28-2-2008'               39506.5
+    'dd/mm/yy hh:mm'            '28/02/08 12:00'          39506.5
+    'd mmm yyyy'                '28 Feb 2008'             39506.5
+    'mmm d yyyy hh:mm AM/PM'    'Feb 28 2008 12:00 PM'    39506.5
+
+
+The L<Spreadsheet::ParseExcel::Utility> module contains a function called C<ExcelLocaltime> which will convert between an unformatted Excel date/time number and a C<localtime()> like array.
+
+For date conversions using the CPAN C<DateTime> framework see L<DateTime::Format::Excel> http://search.cpan.org/search?dist=DateTime-Format-Excel
+
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Dump.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Dump.pm
new file mode 100644
index 0000000..5586db2
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Dump.pm
@@ -0,0 +1,355 @@
+package Spreadsheet::ParseExcel::Dump;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::Dump - A class for dumping Excel records.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+our $VERSION = '0.59';
+
+my %NameTbl = (
+
+    #P291
+    0x0A => 'EOF',
+    0x0C => 'CALCCOUNT',
+    0x0D => 'CALCMODE',
+    0x0E => 'PRECISION',
+    0x0F => 'REFMODE',
+    0x10 => 'DELTA',
+    0x11 => 'ITERATION',
+    0x12 => 'PROTECT',
+    0x13 => 'PASSWORD',
+    0x14 => 'HEADER',
+
+    0x15 => 'FOOTER',
+    0x16 => 'EXTERNCOUNT',
+    0x17 => 'EXTERNSHEET',
+    0x19 => 'WINDOWPROTECT',
+    0x1A => 'VERTICALPAGEBREAKS',
+    0x1B => 'HORIZONTALPAGEBREAKS',
+    0x1C => 'NOTE',
+    0x1D => 'SELECTION',
+    0x22 => '1904',
+    0x26 => 'LEFTMARGIN',
+
+    0x27 => 'RIGHTMARGIN',
+    0x28 => 'TOPMARGIN',
+    0x29 => 'BOTTOMMARGIN',
+    0x2A => 'PRINTHEADERS',
+    0x2B => 'PRINTGRIDLINES',
+    0x2F => 'FILEPASS',
+    0x3C => 'COUNTINUE',
+    0x3D => 'WINDOW1',
+    0x40 => 'BACKUP',
+    0x41 => 'PANE',
+
+    0x42 => 'CODEPAGE',
+    0x4D => 'PLS',
+    0x50 => 'DCON',
+    0x51 => 'DCONREF',
+
+    #P292
+    0x52 => 'DCONNAME',
+    0x55 => 'DEFCOLWIDTH',
+    0x59 => 'XCT',
+    0x5A => 'CRN',
+    0x5B => 'FILESHARING',
+    0x5C => 'WRITEACCES',
+    0x5D => 'OBJ',
+    0x5E => 'UNCALCED',
+    0x5F => 'SAVERECALC',
+    0x60 => 'TEMPLATE',
+
+    0x63 => 'OBJPROTECT',
+    0x7D => 'COLINFO',
+    0x7E => 'RK',
+    0x7F => 'IMDATA',
+    0x80 => 'GUTS',
+    0x81 => 'WSBOOL',
+    0x82 => 'GRIDSET',
+    0x83 => 'HCENTER',
+    0x84 => 'VCENTER',
+    0x85 => 'BOUNDSHEET',
+
+    0x86 => 'WRITEPROT',
+    0x87 => 'ADDIN',
+    0x88 => 'EDG',
+    0x89 => 'PUB',
+    0x8C => 'COUNTRY',
+    0x8D => 'HIDEOBJ',
+    0x90 => 'SORT',
+    0x91 => 'SUB',
+    0x92 => 'PALETTE',
+    0x94 => 'LHRECORD',
+
+    0x95 => 'LHNGRAPH',
+    0x96 => 'SOUND',
+    0x98 => 'LPR',
+    0x99 => 'STANDARDWIDTH',
+    0x9A => 'FNGROUPNAME',
+    0x9B => 'FILTERMODE',
+    0x9C => 'FNGROUPCOUNT',
+
+    #P293
+    0x9D => 'AUTOFILTERINFO',
+    0x9E => 'AUTOFILTER',
+    0xA0 => 'SCL',
+    0xA1 => 'SETUP',
+    0xA9 => 'COORDLIST',
+    0xAB => 'GCW',
+    0xAE => 'SCENMAN',
+    0xAF => 'SCENARIO',
+    0xB0 => 'SXVIEW',
+    0xB1 => 'SXVD',
+
+    0xB2 => 'SXV',
+    0xB4 => 'SXIVD',
+    0xB5 => 'SXLI',
+    0xB6 => 'SXPI',
+    0xB8 => 'DOCROUTE',
+    0xB9 => 'RECIPNAME',
+    0xBC => 'SHRFMLA',
+    0xBD => 'MULRK',
+    0xBE => 'MULBLANK',
+    0xBF => 'TOOLBARHDR',
+    0xC0 => 'TOOLBAREND',
+    0xC1 => 'MMS',
+
+    0xC2 => 'ADDMENU',
+    0xC3 => 'DELMENU',
+    0xC5 => 'SXDI',
+    0xC6 => 'SXDB',
+    0xCD => 'SXSTRING',
+    0xD0 => 'SXTBL',
+    0xD1 => 'SXTBRGIITM',
+    0xD2 => 'SXTBPG',
+    0xD3 => 'OBPROJ',
+    0xD5 => 'SXISDTM',
+
+    0xD6 => 'RSTRING',
+    0xD7 => 'DBCELL',
+    0xDA => 'BOOKBOOL',
+    0xDC => 'PARAMQRY',
+    0xDC => 'SXEXT',
+    0xDD => 'SCENPROTECT',
+    0xDE => 'OLESIZE',
+
+    #P294
+    0xDF => 'UDDESC',
+    0xE0 => 'XF',
+    0xE1 => 'INTERFACEHDR',
+    0xE2 => 'INTERFACEEND',
+    0xE3 => 'SXVS',
+    0xEA => 'TABIDCONF',
+    0xEB => 'MSODRAWINGGROUP',
+    0xEC => 'MSODRAWING',
+    0xED => 'MSODRAWINGSELECTION',
+    0xEF => 'PHONETICINFO',
+    0xF0 => 'SXRULE',
+
+    0xF1 => 'SXEXT',
+    0xF2 => 'SXFILT',
+    0xF6 => 'SXNAME',
+    0xF7 => 'SXSELECT',
+    0xF8 => 'SXPAIR',
+    0xF9 => 'SXFMLA',
+    0xFB => 'SXFORMAT',
+    0xFC => 'SST',
+    0xFD => 'LABELSST',
+    0xFF => 'EXTSST',
+
+    0x100 => 'SXVDEX',
+    0x103 => 'SXFORMULA',
+    0x122 => 'SXDBEX',
+    0x13D => 'TABID',
+    0x160 => 'USESELFS',
+    0x161 => 'DSF',
+    0x162 => 'XL5MODIFY',
+    0x1A5 => 'FILESHARING2',
+    0x1A9 => 'USERBVIEW',
+    0x1AA => 'USERVIEWBEGIN',
+
+    0x1AB => 'USERSVIEWEND',
+    0x1AD => 'QSI',
+    0x1AE => 'SUPBOOK',
+    0x1AF => 'PROT4REV',
+    0x1B0 => 'CONDFMT',
+    0x1B1 => 'CF',
+    0x1B2 => 'DVAL',
+
+    #P295
+    0x1B5 => 'DCONBIN',
+    0x1B6 => 'TXO',
+    0x1B7 => 'REFRESHALL',
+    0x1B8 => 'HLINK',
+    0x1BA => 'CODENAME',
+    0x1BB => 'SXFDBTYPE',
+    0x1BC => 'PROT4REVPASS',
+    0x1BE => 'DV',
+    0x200 => 'DIMENSIONS',
+    0x201 => 'BLANK',
+
+    0x202 => 'Integer',            #Not Documented
+    0x203 => 'NUMBER',
+    0x204 => 'LABEL',
+    0x205 => 'BOOLERR',
+    0x207 => 'STRING',
+    0x208 => 'ROW',
+    0x20B => 'INDEX',
+    0x218 => 'NAME',
+    0x221 => 'ARRAY',
+    0x223 => 'EXTERNNAME',
+    0x225 => 'DEFAULTROWHEIGHT',
+
+    0x231 => 'FONT',
+    0x236 => 'TABLE',
+    0x23E => 'WINDOW2',
+    0x293 => 'STYLE',
+    0x406 => 'FORMULA',
+    0x41E => 'FORMAT',
+
+    0x18 => 'NAME',
+
+    0x06 => 'FORMULA',
+
+    0x09  => 'BOF(BIFF2)',
+    0x209 => 'BOF(BIFF3)',
+    0x409 => 'BOF(BIFF4)',
+    0x809 => 'BOF(BIFF5-7)',
+
+    0x31 => 'FONT', 0x27E => 'RK',
+
+    #Chart/Graph
+    0x1001 => 'UNITS',
+    0x1002 => 'CHART',
+    0x1003 => 'SERISES',
+    0x1006 => 'DATAFORMAT',
+    0x1007 => 'LINEFORMAT',
+    0x1009 => 'MAKERFORMAT',
+    0x100A => 'AREAFORMAT',
+    0x100B => 'PIEFORMAT',
+    0x100C => 'ATTACHEDLABEL',
+    0x100D => 'SERIESTEXT',
+    0x1014 => 'CHARTFORMAT',
+    0x1015 => 'LEGEND',
+    0x1016 => 'SERIESLIST',
+    0x1017 => 'BAR',
+    0x1018 => 'LINE',
+    0x1019 => 'PIE',
+    0x101A => 'AREA',
+    0x101B => 'SCATTER',
+    0x101C => 'CHARTLINE',
+    0x101D => 'AXIS',
+    0x101E => 'TICK',
+    0x101F => 'VALUERANGE',
+    0x1020 => 'CATSERRANGE',
+    0x1021 => 'AXISLINEFORMAT',
+    0x1022 => 'CHARTFORMATLINK',
+    0x1024 => 'DEFAULTTEXT',
+    0x1025 => 'TEXT',
+    0x1026 => 'FONTX',
+    0x1027 => 'OBJECTLINK',
+    0x1032 => 'FRAME',
+    0x1033 => 'BEGIN',
+    0x1034 => 'END',
+    0x1035 => 'PLOTAREA',
+    0x103A => '3D',
+    0x103C => 'PICF',
+    0x103D => 'DROPBAR',
+    0x103E => 'RADAR',
+    0x103F => 'SURFACE',
+    0x1040 => 'RADARAREA',
+    0x1041 => 'AXISPARENT',
+    0x1043 => 'LEGENDXN',
+    0x1044 => 'SHTPROPS',
+    0x1045 => 'SERTOCRT',
+    0x1046 => 'AXESUSED',
+    0x1048 => 'SBASEREF',
+    0x104A => 'SERPARENT',
+    0x104B => 'SERAUXTREND',
+    0x104E => 'IFMT',
+    0x104F => 'POS',
+    0x1050 => 'ALRUNS',
+    0x1051 => 'AI',
+    0x105B => 'SERAUXERRBAR',
+    0x105D => 'SERFMT',
+    0x1060 => 'FBI',
+    0x1061 => 'BOPPOP',
+    0x1062 => 'AXCEXT',
+    0x1063 => 'DAT',
+    0x1064 => 'PLOTGROWTH',
+    0x1065 => 'SINDEX',
+    0x1066 => 'GELFRAME',
+    0x1067 => 'BPOPPOPCUSTOM',
+);
+
+#------------------------------------------------------------------------------
+# subDUMP (for Spreadsheet::ParseExcel)
+#------------------------------------------------------------------------------
+sub subDUMP {
+    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
+    printf "%04X:%-23s (Len:%3d) : %s\n",
+      $bOp, OpName($bOp), $bLen, unpack( "H40", $sWk );
+}
+
+#------------------------------------------------------------------------------
+# Spreadsheet::ParseExcel->OpName
+#------------------------------------------------------------------------------
+sub OpName {
+    my ($bOp) = @_;
+    return ( defined $NameTbl{$bOp} ) ? $NameTbl{$bOp} : 'undef';
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::Dump - A class for dumping Excel records.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
+
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtDefault.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtDefault.pm
new file mode 100644
index 0000000..416866f
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtDefault.pm
@@ -0,0 +1,221 @@
+package Spreadsheet::ParseExcel::FmtDefault;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::FmtDefault - A class for Cell formats.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+use Spreadsheet::ParseExcel::Utility qw(ExcelFmt);
+our $VERSION = '0.59';
+
+my %hFmtDefault = (
+    0x00 => '@',
+    0x01 => '0',
+    0x02 => '0.00',
+    0x03 => '#,##0',
+    0x04 => '#,##0.00',
+    0x05 => '($#,##0_);($#,##0)',
+    0x06 => '($#,##0_);[RED]($#,##0)',
+    0x07 => '($#,##0.00_);($#,##0.00_)',
+    0x08 => '($#,##0.00_);[RED]($#,##0.00_)',
+    0x09 => '0%',
+    0x0A => '0.00%',
+    0x0B => '0.00E+00',
+    0x0C => '# ?/?',
+    0x0D => '# ??/??',
+    0x0E => 'yyyy-mm-dd',      # Was 'm-d-yy', which is bad as system default
+    0x0F => 'd-mmm-yy',
+    0x10 => 'd-mmm',
+    0x11 => 'mmm-yy',
+    0x12 => 'h:mm AM/PM',
+    0x13 => 'h:mm:ss AM/PM',
+    0x14 => 'h:mm',
+    0x15 => 'h:mm:ss',
+    0x16 => 'm-d-yy h:mm',
+
+    #0x17-0x24 -- Differs in Natinal
+    0x25 => '(#,##0_);(#,##0)',
+    0x26 => '(#,##0_);[RED](#,##0)',
+    0x27 => '(#,##0.00);(#,##0.00)',
+    0x28 => '(#,##0.00);[RED](#,##0.00)',
+    0x29 => '_(*#,##0_);_(*(#,##0);_(*"-"_);_(@_)',
+    0x2A => '_($*#,##0_);_($*(#,##0);_(*"-"_);_(@_)',
+    0x2B => '_(*#,##0.00_);_(*(#,##0.00);_(*"-"??_);_(@_)',
+    0x2C => '_($*#,##0.00_);_($*(#,##0.00);_(*"-"??_);_(@_)',
+    0x2D => 'mm:ss',
+    0x2E => '[h]:mm:ss',
+    0x2F => 'mm:ss.0',
+    0x30 => '##0.0E+0',
+    0x31 => '@',
+);
+
+#------------------------------------------------------------------------------
+# new (for Spreadsheet::ParseExcel::FmtDefault)
+#------------------------------------------------------------------------------
+sub new {
+    my ( $sPkg, %hKey ) = @_;
+    my $oThis = {};
+    bless $oThis;
+    return $oThis;
+}
+
+#------------------------------------------------------------------------------
+# TextFmt (for Spreadsheet::ParseExcel::FmtDefault)
+#------------------------------------------------------------------------------
+sub TextFmt {
+    my ( $oThis, $sTxt, $sCode ) = @_;
+    return $sTxt if ( ( !defined($sCode) ) || ( $sCode eq '_native_' ) );
+    return pack( 'U*', unpack( 'n*', $sTxt ) );
+}
+
+#------------------------------------------------------------------------------
+# FmtStringDef (for Spreadsheet::ParseExcel::FmtDefault)
+#------------------------------------------------------------------------------
+sub FmtStringDef {
+    my ( $oThis, $iFmtIdx, $oBook, $rhFmt ) = @_;
+    my $sFmtStr = $oBook->{FormatStr}->{$iFmtIdx};
+
+    if ( !( defined($sFmtStr) ) && defined($rhFmt) ) {
+        $sFmtStr = $rhFmt->{$iFmtIdx};
+    }
+    $sFmtStr = $hFmtDefault{$iFmtIdx} unless ($sFmtStr);
+    return $sFmtStr;
+}
+
+#------------------------------------------------------------------------------
+# FmtString (for Spreadsheet::ParseExcel::FmtDefault)
+#------------------------------------------------------------------------------
+sub FmtString {
+    my ( $oThis, $oCell, $oBook ) = @_;
+
+    my $sFmtStr =
+      $oThis->FmtStringDef( $oBook->{Format}[ $oCell->{FormatNo} ]->{FmtIdx},
+        $oBook );
+
+    # Special case for cells that use Lotus123 style leading
+    # apostrophe to designate text formatting.
+    if ( $oBook->{Format}[ $oCell->{FormatNo} ]->{Key123} ) {
+        $sFmtStr = '@';
+    }
+
+    unless ( defined($sFmtStr) ) {
+        if ( $oCell->{Type} eq 'Numeric' ) {
+            if ( int( $oCell->{Val} ) != $oCell->{Val} ) {
+                $sFmtStr = '0.00';
+            }
+            else {
+                $sFmtStr = '0';
+            }
+        }
+        elsif ( $oCell->{Type} eq 'Date' ) {
+            if ( int( $oCell->{Val} ) <= 0 ) {
+                $sFmtStr = 'h:mm:ss';
+            }
+            else {
+                $sFmtStr = 'yyyy-mm-dd';
+            }
+        }
+        else {
+            $sFmtStr = '@';
+        }
+    }
+    return $sFmtStr;
+}
+
+#------------------------------------------------------------------------------
+# ValFmt (for Spreadsheet::ParseExcel::FmtDefault)
+#------------------------------------------------------------------------------
+sub ValFmt {
+    my ( $oThis, $oCell, $oBook ) = @_;
+
+    my ( $Dt, $iFmtIdx, $iNumeric, $Flg1904 );
+
+    if ( $oCell->{Type} eq 'Text' ) {
+        $Dt =
+          ( ( defined $oCell->{Val} ) && ( $oCell->{Val} ne '' ) )
+          ? $oThis->TextFmt( $oCell->{Val}, $oCell->{Code} )
+          : '';
+
+        return $Dt;
+    }
+    else {
+        $Dt      = $oCell->{Val};
+        $Flg1904 = $oBook->{Flg1904};
+        my $sFmtStr = $oThis->FmtString( $oCell, $oBook );
+
+        return ExcelFmt( $sFmtStr, $Dt, $Flg1904, $oCell->{Type} );
+    }
+}
+
+#------------------------------------------------------------------------------
+# ChkType (for Spreadsheet::ParseExcel::FmtDefault)
+#------------------------------------------------------------------------------
+sub ChkType {
+    my ( $oPkg, $iNumeric, $iFmtIdx ) = @_;
+    if ($iNumeric) {
+        if (   ( ( $iFmtIdx >= 0x0E ) && ( $iFmtIdx <= 0x16 ) )
+            || ( ( $iFmtIdx >= 0x2D ) && ( $iFmtIdx <= 0x2F ) ) )
+        {
+            return "Date";
+        }
+        else {
+            return "Numeric";
+        }
+    }
+    else {
+        return "Text";
+    }
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::FmtDefault - A class for Cell formats.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtJapan.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtJapan.pm
new file mode 100644
index 0000000..71f2b16
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtJapan.pm
@@ -0,0 +1,210 @@
+package Spreadsheet::ParseExcel::FmtJapan;
+use utf8;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::FmtJapan - A class for Cell formats.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+use Encode qw(find_encoding decode);
+use base 'Spreadsheet::ParseExcel::FmtDefault';
+our $VERSION = '0.59';
+
+my %FormatTable = (
+    0x00 => '@',
+    0x01 => '0',
+    0x02 => '0.00',
+    0x03 => '#,##0',
+    0x04 => '#,##0.00',
+    0x05 => '(\\#,##0_);(\\#,##0)',
+    0x06 => '(\\#,##0_);[RED](\\#,##0)',
+    0x07 => '(\\#,##0.00_);(\\#,##0.00_)',
+    0x08 => '(\\#,##0.00_);[RED](\\#,##0.00_)',
+    0x09 => '0%',
+    0x0A => '0.00%',
+    0x0B => '0.00E+00',
+    0x0C => '# ?/?',
+    0x0D => '# ??/??',
+
+    #    0x0E => 'm/d/yy',
+    0x0E => 'yyyy/m/d',
+    0x0F => 'd-mmm-yy',
+    0x10 => 'd-mmm',
+    0x11 => 'mmm-yy',
+    0x12 => 'h:mm AM/PM',
+    0x13 => 'h:mm:ss AM/PM',
+    0x14 => 'h:mm',
+    0x15 => 'h:mm:ss',
+
+    #    0x16 => 'm/d/yy h:mm',
+    0x16 => 'yyyy/m/d h:mm',
+
+    #0x17-0x24 -- Differs in Natinal
+    0x1E => 'm/d/yy',
+    0x1F => 'yyyy"年"m"月"d"日"',
+    0x20 => 'h"時"mm"分"',
+    0x21 => 'h"時"mm"分"ss"秒"',
+
+    #0x17-0x24 -- Differs in Natinal
+    0x25 => '(#,##0_);(#,##0)',
+    0x26 => '(#,##0_);[RED](#,##0)',
+    0x27 => '(#,##0.00);(#,##0.00)',
+    0x28 => '(#,##0.00);[RED](#,##0.00)',
+    0x29 => '_(*#,##0_);_(*(#,##0);_(*"-"_);_(@_)',
+    0x2A => '_(\\*#,##0_);_(\\*(#,##0);_(*"-"_);_(@_)',
+    0x2B => '_(*#,##0.00_);_(*(#,##0.00);_(*"-"??_);_(@_)',
+    0x2C => '_(\\*#,##0.00_);_(\\*(#,##0.00);_(*"-"??_);_(@_)',
+    0x2D => 'mm:ss',
+    0x2E => '[h]:mm:ss',
+    0x2F => 'mm:ss.0',
+    0x30 => '##0.0E+0',
+    0x31 => '@',
+
+    0x37 => 'yyyy"年"m"月"',
+    0x38 => 'm"月"d"日"',
+    0x39 => 'ge.m.d',
+    0x3A => 'ggge"年"m"月"d"日"',
+);
+
+#------------------------------------------------------------------------------
+# new (for Spreadsheet::ParseExcel::FmtJapan)
+#------------------------------------------------------------------------------
+sub new {
+    my ( $class, %args ) = @_;
+    my $encoding = $args{Code} || $args{encoding};
+    my $self = { Code => $encoding };
+    if($encoding){
+        $self->{encoding} = find_encoding($encoding eq 'sjis' ? 'cp932' : $encoding)
+            or do{
+                require Carp;
+                Carp::croak(qq{Unknown encoding '$encoding'});
+            };
+    }
+    return bless $self, $class;
+}
+
+#------------------------------------------------------------------------------
+# TextFmt (for Spreadsheet::ParseExcel::FmtJapan)
+#------------------------------------------------------------------------------
+sub TextFmt {
+    my ( $self, $text, $input_encoding ) = @_;
+    if(!defined $input_encoding){
+        $input_encoding = 'utf8';
+    }
+    elsif($input_encoding eq '_native_'){
+        $input_encoding = 'cp932'; # Shift_JIS in Microsoft products
+    }
+    $text = decode($input_encoding, $text);
+    return $self->{Code} ? $self->{encoding}->encode($text) : $text;
+}
+#------------------------------------------------------------------------------
+# FmtStringDef (for Spreadsheet::ParseExcel::FmtJapan)
+#------------------------------------------------------------------------------
+sub FmtStringDef {
+    my ( $self, $format_index, $book ) = @_;
+    return $self->SUPER::FmtStringDef( $format_index, $book, \%FormatTable );
+}
+
+#------------------------------------------------------------------------------
+# CnvNengo (for Spreadsheet::ParseExcel::FmtJapan)
+#------------------------------------------------------------------------------
+
+# Convert A.D. into Japanese Nengo (aka Gengo)
+
+my @Nengo = (
+	{
+		name      => '平成', # Heisei
+		abbr_name => 'H',
+
+		base      => 1988,
+		start     => 19890108,
+	},
+	{
+		name      => '昭和', # Showa
+		abbr_name => 'S',
+
+		base      => 1925,
+		start     => 19261225,
+	},
+	{
+		name      => '大正', # Taisho
+		abbr_name => 'T',
+
+		base      => 1911,
+		start     => 19120730,
+	},
+	{
+		name      => '明治', # Meiji
+		abbr_name => 'M',
+
+		base      => 1867,
+		start     => 18680908,
+	},
+);
+
+# Usage: CnvNengo(name => @tm) or CnvNeng(abbr_name => @tm)
+sub CnvNengo {
+    my ( $kind, @tm ) = @_;
+    my $year = $tm[5] + 1900;
+    my $wk = ($year * 10000) + ($tm[4] * 100) + ($tm[3] * 1);
+    #my $wk = sprintf( '%04d%02d%02d', $year, $tm[4], $tm[3] );
+    foreach my $nengo(@Nengo){
+        if( $wk >= $nengo->{start} ){
+            return $nengo->{$kind} . ($year - $nengo->{base});
+        }
+    }
+    return $year;
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::FmtJapan - A class for Cell formats.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtJapan2.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtJapan2.pm
new file mode 100644
index 0000000..318cb01
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtJapan2.pm
@@ -0,0 +1,103 @@
+package Spreadsheet::ParseExcel::FmtJapan2;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::FmtJapan2 - A class for Cell formats.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+use Jcode;
+use Unicode::Map;
+use base 'Spreadsheet::ParseExcel::FmtJapan';
+our $VERSION = '0.59';
+
+#------------------------------------------------------------------------------
+# new (for Spreadsheet::ParseExcel::FmtJapan2)
+#------------------------------------------------------------------------------
+sub new {
+    my ( $sPkg, %hKey ) = @_;
+    my $oMap = Unicode::Map->new('CP932Excel');
+    die "NO MAP FILE CP932Excel!!"
+      unless ( -r Unicode::Map->mapping("CP932Excel") );
+
+    my $oThis = {
+        Code    => $hKey{Code},
+        _UniMap => $oMap,
+    };
+    bless $oThis;
+    $oThis->SUPER::new(%hKey);
+    return $oThis;
+}
+
+#------------------------------------------------------------------------------
+# TextFmt (for Spreadsheet::ParseExcel::FmtJapan2)
+#------------------------------------------------------------------------------
+sub TextFmt {
+    my ( $oThis, $sTxt, $sCode ) = @_;
+
+    #    $sCode = 'sjis' if((! defined($sCode)) || ($sCode eq '_native_'));
+    if ( $oThis->{Code} ) {
+        if ( !defined($sCode) ) {
+            $sTxt =~ s/(.)/\x00$1/sg;
+            $sTxt = $oThis->{_UniMap}->from_unicode($sTxt);
+        }
+        elsif ( $sCode eq 'ucs2' ) {
+            $sTxt = $oThis->{_UniMap}->from_unicode($sTxt);
+        }
+        return Jcode::convert( $sTxt, $oThis->{Code}, 'sjis' );
+    }
+    else {
+        return $sTxt;
+    }
+}
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::FmtJapan2 - A class for Cell formats.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtUnicode.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtUnicode.pm
new file mode 100644
index 0000000..f0368bc
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/FmtUnicode.pm
@@ -0,0 +1,104 @@
+package Spreadsheet::ParseExcel::FmtUnicode;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::FmtUnicode - A class for Cell formats.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+use Unicode::Map;
+use base 'Spreadsheet::ParseExcel::FmtDefault';
+
+our $VERSION = '0.59';
+
+#------------------------------------------------------------------------------
+# new (for Spreadsheet::ParseExcel::FmtUnicode)
+#------------------------------------------------------------------------------
+sub new {
+    my ( $sPkg, %hKey ) = @_;
+    my $sMap = $hKey{Unicode_Map};
+    my $oMap;
+    $oMap = Unicode::Map->new($sMap) if $sMap;
+    my $oThis = {
+        Unicode_Map => $sMap,
+        _UniMap     => $oMap,
+    };
+    bless $oThis;
+    return $oThis;
+}
+
+#------------------------------------------------------------------------------
+# TextFmt (for Spreadsheet::ParseExcel::FmtUnicode)
+#------------------------------------------------------------------------------
+sub TextFmt {
+    my ( $oThis, $sTxt, $sCode ) = @_;
+    if ( $oThis->{_UniMap} ) {
+        if ( !defined($sCode) ) {
+            my $sSv = $sTxt;
+            $sTxt =~ s/(.)/\x00$1/sg;
+            $sTxt = $oThis->{_UniMap}->from_unicode($sTxt);
+            $sTxt = $sSv unless ($sTxt);
+        }
+        elsif ( $sCode eq 'ucs2' ) {
+            $sTxt = $oThis->{_UniMap}->from_unicode($sTxt);
+        }
+
+        #        $sTxt = $oThis->{_UniMap}->from_unicode($sTxt)
+        #                     if(defined($sCode) && $sCode eq 'ucs2');
+        return $sTxt;
+    }
+    else {
+        return $sTxt;
+    }
+}
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::FmtUnicode - A class for Cell formats.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Font.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Font.pm
new file mode 100644
index 0000000..eb3b1fd
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Font.pm
@@ -0,0 +1,69 @@
+package Spreadsheet::ParseExcel::Font;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::Font - A class for Cell fonts.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+our $VERSION = '0.59';
+
+sub new {
+    my ( $class, %rhIni ) = @_;
+    my $self = \%rhIni;
+
+    bless $self, $class;
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::Font - A class for Cell fonts.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
+
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Format.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Format.pm
new file mode 100644
index 0000000..18b762f
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Format.pm
@@ -0,0 +1,68 @@
+package Spreadsheet::ParseExcel::Format;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::Format - A class for Cell formats.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+our $VERSION = '0.59';
+
+sub new {
+    my ( $class, %rhIni ) = @_;
+    my $self = \%rhIni;
+
+    bless $self, $class;
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::Format - A class for Cell formats.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/SaveParser.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/SaveParser.pm
new file mode 100644
index 0000000..3d2cf9c
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/SaveParser.pm
@@ -0,0 +1,310 @@
+package Spreadsheet::ParseExcel::SaveParser;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::SaveParser - Rewrite an existing Excel file.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+use Spreadsheet::ParseExcel;
+use Spreadsheet::ParseExcel::SaveParser::Workbook;
+use Spreadsheet::ParseExcel::SaveParser::Worksheet;
+use Spreadsheet::WriteExcel;
+use base 'Spreadsheet::ParseExcel';
+
+our $VERSION = '0.59';
+
+###############################################################################
+#
+# new()
+#
+sub new {
+
+    my ( $package, %params ) = @_;
+    $package->SUPER::new(%params);
+}
+
+###############################################################################
+#
+# Create()
+#
+sub Create {
+
+    my ( $self, $formatter ) = @_;
+
+    #0. New $workbook
+    my $workbook = Spreadsheet::ParseExcel::Workbook->new();
+    $workbook->{SheetCount} = 0;
+
+    # User specified formater class.
+    if ($formatter) {
+        $workbook->{FmtClass} = $formatter;
+    }
+    else {
+        $workbook->{FmtClass} = Spreadsheet::ParseExcel::FmtDefault->new();
+    }
+
+    return Spreadsheet::ParseExcel::SaveParser::Workbook->new($workbook);
+}
+
+###############################################################################
+#
+# Parse()
+#
+sub Parse {
+
+    my ( $self, $sFile, $formatter ) = @_;
+
+    my $workbook = $self->SUPER::Parse( $sFile, $formatter );
+
+    return undef unless defined $workbook;
+    return Spreadsheet::ParseExcel::SaveParser::Workbook->new($workbook);
+}
+
+###############################################################################
+#
+# SaveAs()
+#
+sub SaveAs {
+
+    my ( $self, $workbook, $filename ) = @_;
+
+    $workbook->SaveAs($filename);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::SaveParser - Rewrite an existing Excel file.
+
+=head1 SYNOPSIS
+
+
+
+Say we start with an Excel file that looks like this:
+
+    -----------------------------------------------------
+   |   |      A      |      B      |      C      |
+    -----------------------------------------------------
+   | 1 | Hello       | ...         | ...         |  ...
+   | 2 | World       | ...         | ...         |  ...
+   | 3 | *Bold text* | ...         | ...         |  ...
+   | 4 | ...         | ...         | ...         |  ...
+   | 5 | ...         | ...         | ...         |  ...
+
+
+Then we process it with the following program:
+
+    #!/usr/bin/perl
+
+    use strict;
+    use warnings;
+
+    use Spreadsheet::ParseExcel;
+    use Spreadsheet::ParseExcel::SaveParser;
+
+
+    # Open an existing file with SaveParser
+    my $parser   = Spreadsheet::ParseExcel::SaveParser->new();
+    my $template = $parser->Parse('template.xls');
+
+
+    # Get the first worksheet.
+    my $worksheet = $template->worksheet(0);
+    my $row  = 0;
+    my $col  = 0;
+
+
+    # Overwrite the string in cell A1
+    $worksheet->AddCell( $row, $col, 'New string' );
+
+
+    # Add a new string in cell B1
+    $worksheet->AddCell( $row, $col + 1, 'Newer' );
+
+
+    # Add a new string in cell C1 with the format from cell A3.
+    my $cell = $worksheet->get_cell( $row + 2, $col );
+    my $format_number = $cell->{FormatNo};
+
+    $worksheet->AddCell( $row, $col + 2, 'Newest', $format_number );
+
+
+    # Write over the existing file or write a new file.
+    $template->SaveAs('newfile.xls');
+
+
+We should now have an Excel file that looks like this:
+
+    -----------------------------------------------------
+   |   |      A      |      B      |      C      |
+    -----------------------------------------------------
+   | 1 | New string  | Newer       | *Newest*    |  ...
+   | 2 | World       | ...         | ...         |  ...
+   | 3 | *Bold text* | ...         | ...         |  ...
+   | 4 | ...         | ...         | ...         |  ...
+   | 5 | ...         | ...         | ...         |  ...
+
+
+
+=head1 DESCRIPTION
+
+The C<Spreadsheet::ParseExcel::SaveParser> module rewrite an existing Excel file by reading it with C<Spreadsheet::ParseExcel> and rewriting it with C<Spreadsheet::WriteExcel>.
+
+=head1 METHODS
+
+=head1 Parser
+
+=head2 new()
+
+    $parse = new Spreadsheet::ParseExcel::SaveParser();
+
+Constructor.
+
+=head2 Parse()
+
+    $workbook = $parse->Parse($sFileName);
+
+    $workbook = $parse->Parse($sFileName , $formatter);
+
+Returns a L</Workbook> object. If an error occurs, returns undef.
+
+The optional C<$formatter> is a Formatter Class to format the value of cells.
+
+
+=head1 Workbook
+
+The C<Parse()> method returns a C<Spreadsheet::ParseExcel::SaveParser::Workbook> object.
+
+This is a subclass of the L<Spreadsheet::ParseExcel::Workbook> and has the following methods:
+
+=head2 worksheets()
+
+Returns an array of L</Worksheet> objects. This was most commonly used to iterate over the worksheets in a workbook:
+
+    for my $worksheet ( $workbook->worksheets() ) {
+        ...
+    }
+
+=head2 worksheet()
+
+The C<worksheet()> method returns a single C<Worksheet> object using either its name or index:
+
+    $worksheet = $workbook->worksheet('Sheet1');
+    $worksheet = $workbook->worksheet(0);
+
+Returns C<undef> if the sheet name or index doesn't exist.
+
+
+=head2 AddWorksheet()
+
+    $workbook = $workbook->AddWorksheet($name, %properties);
+
+Create a new Worksheet object of type C<Spreadsheet::ParseExcel::Worksheet>.
+
+The C<%properties> hash contains the properties of new Worksheet.
+
+
+=head2 AddFont
+
+    $workbook = $workbook->AddFont(%properties);
+
+Create new Font object of type C<Spreadsheet::ParseExcel::Font>.
+
+The C<%properties> hash contains the properties of new Font.
+
+
+=head2 AddFormat
+
+    $workbook = $workbook->AddFormat(%properties);
+
+The C<%properties> hash contains the properties of new Font.
+
+
+=head1 Worksheet
+
+Spreadsheet::ParseExcel::SaveParser::Worksheet
+
+Worksheet is a subclass of Spreadsheet::ParseExcel::Worksheet.
+And has these methods :
+
+
+The C<Worksbook::worksheet()> method returns a C<Spreadsheet::ParseExcel::SaveParser::Worksheet> object.
+
+This is a subclass of the L<Spreadsheet::ParseExcel::Worksheet> and has the following methods:
+
+
+=head1 AddCell
+
+    $workbook = $worksheet->AddCell($row, $col, $value, $format [$encoding]);
+
+Create new Cell object of type C<Spreadsheet::ParseExcel::Cell>.
+
+The C<$format> parameter is the format number rather than a full format object.
+
+To specify just same as another cell,
+you can set it like below:
+
+    $row            = 0;
+    $col            = 0;
+    $worksheet      = $template->worksheet(0);
+    $cell           = $worksheet->get_cell( $row, $col );
+    $format_number  = $cell->{FormatNo};
+
+    $worksheet->AddCell($row +1, $coll, 'New data', $format_number);
+
+
+
+
+=head1 TODO
+
+Please note that this module is currently (versions 0.50-0.60) undergoing a major
+restructuring and rewriting.
+
+=head1 Known Problems
+
+
+You can only rewrite the features that Spreadsheet::WriteExcel supports so
+macros, graphs and some other features in the original Excel file will be lost.
+Also, formulas aren't rewritten, only the result of a formula is written.
+
+Only last print area will remain. (Others will be removed)
+
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2002 Kawai Takanori and Nippon-RAD Co. OP Division
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Utility.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Utility.pm
new file mode 100644
index 0000000..2eb09e7
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Utility.pm
@@ -0,0 +1,1615 @@
+package Spreadsheet::ParseExcel::Utility;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::Utility - Utility functions for ParseExcel.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+require Exporter;
+use vars qw(@ISA @EXPORT_OK);
+@ISA       = qw(Exporter);
+@EXPORT_OK = qw(ExcelFmt LocaltimeExcel ExcelLocaltime
+  col2int int2col sheetRef xls2csv);
+
+our $VERSION = '0.59';
+
+my $qrNUMBER = qr/(^[+-]?\d+(\.\d+)?$)|(^[+-]?\d+\.?(\d*)[eE][+-](\d+))$/;
+
+###############################################################################
+#
+# ExcelFmt()
+#
+# This function takes an Excel style number format and converts a number into
+# that format. for example: 'hh:mm:ss AM/PM' + 0.01023148 = '12:14:44 AM'.
+#
+# It does this with a type of templating mechanism. The format string is parsed
+# to identify tokens that need to be replaced and their position within the
+# string is recorded. These can be thought of as placeholders. The number is
+# then converted to the required formats and substituted into the placeholders.
+#
+# Interested parties should refer to the Excel documentation on cell formats for
+# more information: http://office.microsoft.com/en-us/excel/HP051995001033.aspx
+# The Microsoft documentation for the Excel Binary File Format, [MS-XLS].pdf,
+# also contains a ABNF grammar for number format strings.
+#
+# Maintainers notes:
+# ==================
+#
+# Note on format subsections:
+# A format string can contain 4 possible sub-sections separated by semi-colons:
+# Positive numbers, negative numbers, zero values, and text.
+# For example: _(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)
+#
+# Note on conditional formats.
+# A number format in Excel can have a conditional expression such as:
+#     [>9999999](000)000-0000;000-0000
+# This is equivalent to the following in Perl:
+#     $format = $number > 9999999 ? '(000)000-0000' : '000-0000';
+# Nested conditionals are also possible but we don't support them.
+#
+# Efficiency: The excessive use of substr() isn't very efficient. However,
+# it probably doesn't merit rewriting this function with a parser or regular
+# expressions and \G.
+#
+# TODO: I think the single quote handling may not be required. Check.
+#
+sub ExcelFmt {
+
+    my ( $format_str, $number, $is_1904, $number_type, $want_subformats ) = @_;
+
+    # Return text strings without further formatting.
+    return $number unless $number =~ $qrNUMBER;
+
+    # Handle OpenOffice.org GENERAL format.
+    $format_str = '@' if uc($format_str) eq "GENERAL";
+
+    # Check for a conditional at the start of the format. See notes above.
+    my $conditional;
+    if ( $format_str =~ /^\[([<>=][^\]]+)\](.*)$/ ) {
+        $conditional = $1;
+        $format_str  = $2;
+    }
+
+    # Ignore the underscore token which is used to indicate a padding space.
+    $format_str =~ s/_/ /g;
+
+    # Split the format string into 4 possible sub-sections: positive numbers,
+    # negative numbers, zero values, and text. See notes above.
+    my @formats;
+    my $section      = 0;
+    my $double_quote = 0;
+    my $single_quote = 0;
+
+    # Initial parsing of the format string to remove escape characters. This
+    # also handles quoted strings. See note about single quotes above.
+  CHARACTER:
+    for my $char ( split //, $format_str ) {
+
+        if ( $double_quote or $single_quote ) {
+            $formats[$section] .= $char;
+            $double_quote = 0 if $char eq '"';
+            $single_quote = 0;
+            next CHARACTER;
+        }
+
+        if ( $char eq ';' ) {
+            $section++;
+            next CHARACTER;
+        }
+        elsif ( $char eq '"' ) {
+            $double_quote = 1;
+        }
+        elsif ( $char eq '!' ) {
+            $single_quote = 1;
+        }
+        elsif ( $char eq '\\' ) {
+            $single_quote = 1;
+        }
+        elsif ( $char eq '(' ) {
+            next CHARACTER;    # Ignore.
+        }
+        elsif ( $char eq ')' ) {
+            next CHARACTER;    # Ignore.
+        }
+
+        # Convert upper case OpenOffice.org date/time formats to lowercase..
+        $char = lc($char) if $char =~ /[DMYHS]/;
+
+        $formats[$section] .= $char;
+    }
+
+    # Select the appropriate format from the 4 possible sub-sections:
+    # positive numbers, negative numbers, zero values, and text.
+    # We ignore the Text section since non-numeric values are returned
+    # unformatted at the start of the function.
+    my $format;
+    $section = 0;
+
+    if ( @formats == 1 ) {
+        $section = 0;
+    }
+    elsif ( @formats == 2 ) {
+        if ( $number < 0 ) {
+            $section = 1;
+        }
+        else {
+            $section = 0;
+        }
+    }
+    elsif ( @formats == 3 ) {
+        if ( $number == 0 ) {
+            $section = 2;
+        }
+        elsif ( $number < 0 ) {
+            $section = 1;
+        }
+        else {
+            $section = 0;
+        }
+    }
+    else {
+        $section = 0;
+    }
+
+    # Override the previous choice if the format is conditional.
+    if ($conditional) {
+
+        # TODO. Replace string eval with a function.
+        $section = eval "$number $conditional" ? 0 : 1;
+    }
+
+    # We now have the required format.
+    $format = $formats[$section];
+
+    # The format string can contain one of the following colours:
+    # [Black] [Blue] [Cyan] [Green] [Magenta] [Red] [White] [Yellow]
+    # or the string [ColorX] where x is a colour index from 1 to 56.
+    # We don't use the colour but we return it to the caller.
+    #
+    my $color = '';
+    if ( $format =~ s/^(\[[A-Z][a-z]{2,}(\d{1,2})?\])// ) {
+        $color = $1;
+    }
+
+    # Remove the locale, such as [$-409], from the format string.
+    my $locale = '';
+    if ( $format =~ s/^(\[\$?-\d+\])// ) {
+        $locale = $1;
+    }
+
+    # Replace currency locale, such as [$$-409], with $ in the format string.
+    # See the RT#60547 test cases in 21_number_format_user.t.
+    if ( $format =~ s/(\[\$([^-]+)(-\d+)?\])/$2/s ) {
+        $locale = $1;
+    }
+
+
+    # Remove leading # from '# ?/?', '# ??/??' fraction formats.
+    $format =~ s{# \?}{?}g;
+
+    # Parse the format string and create an AoA of placeholders that contain
+    # the parts of the string to be replaced. The format of the information
+    # stored is: [ $token, $start_pos, $end_pos, $option_info ].
+    #
+    my $format_mode  = '';    # Either: '', 'number', 'date'
+    my $pos          = 0;     # Character position within format string.
+    my @placeholders = ();    # Arefs with parts of the format to be replaced.
+    my $token        = '';    # The actual format extracted from the total str.
+    my $start_pos;            # A position variable. Initial parser position.
+    my $token_start = -1;     # A position variable.
+    my $decimal_pos = -1;     # Position of the punctuation char "." or ",".
+    my $comma_count = 0;      # Count of the commas in the format.
+    my $is_fraction = 0;      # Number format is a fraction.
+    my $is_currency = 0;      # Number format is a currency.
+    my $is_percent  = 0;      # Number format is a percentage.
+    my $is_12_hour  = 0;      # Time format is using 12 hour clock.
+    my $seen_dot    = 0;      # Treat only the first "." as the decimal point.
+
+    # Parse the format.
+  PARSER:
+    while ( $pos < length $format ) {
+        $start_pos = $pos;
+        my $char = substr( $format, $pos, 1 );
+
+        # Ignore control format characters such as '#0+-.?eE,%'. However,
+        # only ignore '.' if it is the first one encountered. RT 45502.
+        if ( ( !$seen_dot && $char !~ /[#0\+\-\.\?eE\,\%]/ )
+            || $char !~ /[#0\+\-\?eE\,\%]/ )
+        {
+
+            if ( $token_start != -1 ) {
+                push @placeholders,
+                  [
+                    substr( $format, $token_start, $pos - $token_start ),
+                    $decimal_pos, $pos - $token_start
+                  ];
+                $token_start = -1;
+            }
+        }
+
+        # Processing for quoted strings within the format. See notes above.
+        if ( $char eq '"' ) {
+            $double_quote = $double_quote ? 0 : 1;
+            $pos++;
+            next PARSER;
+        }
+        elsif ( $char eq '!' ) {
+            $single_quote = 1;
+            $pos++;
+            next PARSER;
+        }
+        elsif ( $char eq '\\' ) {
+            if ( $single_quote != 1 ) {
+                $single_quote = 1;
+                $pos++;
+                next PARSER;
+            }
+        }
+
+        if (   ( defined($double_quote) and ($double_quote) )
+            or ( defined($single_quote) and ($single_quote) )
+            or ( $seen_dot && $char eq '.' ) )
+        {
+            $single_quote = 0;
+            if (
+                ( $format_mode ne 'date' )
+                and (  ( substr( $format, $pos, 2 ) eq "\x81\xA2" )
+                    || ( substr( $format, $pos, 2 ) eq "\x81\xA3" )
+                    || ( substr( $format, $pos, 2 ) eq "\xA2\xA4" )
+                    || ( substr( $format, $pos, 2 ) eq "\xA2\xA5" ) )
+              )
+            {
+
+                # The above matches are currency symbols.
+                push @placeholders,
+                  [ substr( $format, $pos, 2 ), length($token), 2 ];
+                $is_currency = 1;
+                $pos += 2;
+            }
+            else {
+                $pos++;
+            }
+        }
+        elsif (
+            ( $char =~ /[#0\+\.\?eE\,\%]/ )
+            || (    ( $format_mode ne 'date' )
+                and ( ( $char eq '-' ) || ( $char eq '(' ) || ( $char eq ')' ) )
+            )
+          )
+        {
+            $format_mode = 'number' unless $format_mode;
+            if ( substr( $format, $pos, 1 ) =~ /[#0]/ ) {
+                if (
+                    substr( $format, $pos ) =~
+                    /^([#0]+[\.]?[0#]*[eE][\+\-][0#]+)/ )
+                {
+                    push @placeholders, [ $1, $pos, length($1) ];
+                    $pos += length($1);
+                }
+                else {
+                    if ( $token_start == -1 ) {
+                        $token_start = $pos;
+                        $decimal_pos = length($token);
+                    }
+                }
+            }
+            elsif ( substr( $format, $pos, 1 ) eq '?' ) {
+
+                # Look for a fraction format like ?/? or ??/??
+                if ( $token_start != -1 ) {
+                    push @placeholders,
+                      [
+                        substr(
+                            $format, $token_start, $pos - $token_start + 1
+                        ),
+                        $decimal_pos,
+                        $pos - $token_start + 1
+                      ];
+                }
+                $token_start = $pos;
+
+                # Find the end of the fraction format.
+              FRACTION:
+                while ( $pos < length($format) ) {
+                    if ( substr( $format, $pos, 1 ) eq '/' ) {
+                        $is_fraction = 1;
+                    }
+                    elsif ( substr( $format, $pos, 1 ) eq '?' ) {
+                        $pos++;
+                        next FRACTION;
+                    }
+                    else {
+                        if ( $is_fraction
+                            && ( substr( $format, $pos, 1 ) =~ /[0-9]/ ) )
+                        {
+
+                            # TODO: Could invert if() logic and remove this.
+                            $pos++;
+                            next FRACTION;
+                        }
+                        else {
+                            last FRACTION;
+                        }
+                    }
+                    $pos++;
+                }
+                $pos--;
+
+                push @placeholders,
+                  [
+                    substr( $format, $token_start, $pos - $token_start + 1 ),
+                    length($token), $pos - $token_start + 1
+                  ];
+                $token_start = -1;
+            }
+            elsif ( substr( $format, $pos, 3 ) =~ /^[eE][\+\-][0#]$/ ) {
+                if ( substr( $format, $pos ) =~ /([eE][\+\-][0#]+)/ ) {
+                    push @placeholders, [ $1, $pos, length($1) ];
+                    $pos += length($1);
+                }
+                $token_start = -1;
+            }
+            else {
+                if ( $token_start != -1 ) {
+                    push @placeholders,
+                      [
+                        substr( $format, $token_start, $pos - $token_start ),
+                        $decimal_pos, $pos - $token_start
+                      ];
+                    $token_start = -1;
+                }
+                if ( substr( $format, $pos, 1 ) =~ /[\+\-]/ ) {
+                    push @placeholders,
+                      [ substr( $format, $pos, 1 ), length($token), 1 ];
+                    $is_currency = 1;
+                }
+                elsif ( substr( $format, $pos, 1 ) eq '.' ) {
+                    push @placeholders,
+                      [ substr( $format, $pos, 1 ), length($token), 1 ];
+                    $seen_dot = 1;
+                }
+                elsif ( substr( $format, $pos, 1 ) eq ',' ) {
+                    $comma_count++;
+                    push @placeholders,
+                      [ substr( $format, $pos, 1 ), length($token), 1 ];
+                }
+                elsif ( substr( $format, $pos, 1 ) eq '%' ) {
+                    $is_percent = 1;
+                }
+                elsif (( substr( $format, $pos, 1 ) eq '(' )
+                    || ( substr( $format, $pos, 1 ) eq ')' ) )
+                {
+                    push @placeholders,
+                      [ substr( $format, $pos, 1 ), length($token), 1 ];
+                    $is_currency = 1;
+                }
+            }
+            $pos++;
+        }
+        elsif ( $char =~ /[ymdhsapg]/i ) {
+            $format_mode = 'date' unless $format_mode;
+            if ( substr( $format, $pos, 5 ) =~ /am\/pm/i ) {
+                push @placeholders, [ 'am/pm', length($token), 5 ];
+                $is_12_hour = 1;
+                $pos += 5;
+            }
+            elsif ( substr( $format, $pos, 3 ) =~ /a\/p/i ) {
+                push @placeholders, [ 'a/p', length($token), 3 ];
+                $is_12_hour = 1;
+                $pos += 3;
+            }
+            elsif ( substr( $format, $pos, 5 ) eq 'mmmmm' ) {
+                push @placeholders, [ 'mmmmm', length($token), 5 ];
+                $pos += 5;
+            }
+            elsif (( substr( $format, $pos, 4 ) eq 'mmmm' )
+                || ( substr( $format, $pos, 4 ) eq 'dddd' )
+                || ( substr( $format, $pos, 4 ) eq 'yyyy' )
+                || ( substr( $format, $pos, 4 ) eq 'ggge' ) )
+            {
+                push @placeholders,
+                  [ substr( $format, $pos, 4 ), length($token), 4 ];
+                $pos += 4;
+            }
+            elsif (( substr( $format, $pos, 3 ) eq 'ddd' )
+                || ( substr( $format, $pos, 3 ) eq 'mmm' )
+                || ( substr( $format, $pos, 3 ) eq 'yyy' ) )
+            {
+                push @placeholders,
+                  [ substr( $format, $pos, 3 ), length($token), 3 ];
+                $pos += 3;
+            }
+            elsif (( substr( $format, $pos, 2 ) eq 'yy' )
+                || ( substr( $format, $pos, 2 ) eq 'mm' )
+                || ( substr( $format, $pos, 2 ) eq 'dd' )
+                || ( substr( $format, $pos, 2 ) eq 'hh' )
+                || ( substr( $format, $pos, 2 ) eq 'ss' )
+                || ( substr( $format, $pos, 2 ) eq 'ge' ) )
+            {
+                if (
+                       ( substr( $format, $pos, 2 ) eq 'mm' )
+                    && (@placeholders)
+                    && (   ( $placeholders[-1]->[0] eq 'h' )
+                        or ( $placeholders[-1]->[0] eq 'hh' ) )
+                  )
+                {
+
+                    # For this case 'm' is minutes not months.
+                    push @placeholders, [ 'mm', length($token), 2, 'minutes' ];
+                }
+                else {
+                    push @placeholders,
+                      [ substr( $format, $pos, 2 ), length($token), 2 ];
+                }
+                if (   ( substr( $format, $pos, 2 ) eq 'ss' )
+                    && ( @placeholders > 1 ) )
+                {
+                    if (   ( $placeholders[-2]->[0] eq 'm' )
+                        || ( $placeholders[-2]->[0] eq 'mm' ) )
+                    {
+
+                        # For this case 'm' is minutes not months.
+                        push( @{ $placeholders[-2] }, 'minutes' );
+                    }
+                }
+                $pos += 2;
+            }
+            elsif (( substr( $format, $pos, 1 ) eq 'm' )
+                || ( substr( $format, $pos, 1 ) eq 'd' )
+                || ( substr( $format, $pos, 1 ) eq 'h' )
+                || ( substr( $format, $pos, 1 ) eq 's' ) )
+            {
+                if (
+                       ( substr( $format, $pos, 1 ) eq 'm' )
+                    && (@placeholders)
+                    && (   ( $placeholders[-1]->[0] eq 'h' )
+                        or ( $placeholders[-1]->[0] eq 'hh' ) )
+                  )
+                {
+
+                    # For this case 'm' is minutes not months.
+                    push @placeholders, [ 'm', length($token), 1, 'minutes' ];
+                }
+                else {
+                    push @placeholders,
+                      [ substr( $format, $pos, 1 ), length($token), 1 ];
+                }
+                if (   ( substr( $format, $pos, 1 ) eq 's' )
+                    && ( @placeholders > 1 ) )
+                {
+                    if (   ( $placeholders[-2]->[0] eq 'm' )
+                        || ( $placeholders[-2]->[0] eq 'mm' ) )
+                    {
+
+                        # For this case 'm' is minutes not months.
+                        push( @{ $placeholders[-2] }, 'minutes' );
+                    }
+                }
+                $pos += 1;
+            }
+        }
+        elsif ( ( substr( $format, $pos, 3 ) eq '[h]' ) ) {
+            $format_mode = 'date' unless $format_mode;
+            push @placeholders, [ '[h]', length($token), 3 ];
+            $pos += 3;
+        }
+        elsif ( ( substr( $format, $pos, 4 ) eq '[mm]' ) ) {
+            $format_mode = 'date' unless $format_mode;
+            push @placeholders, [ '[mm]', length($token), 4 ];
+            $pos += 4;
+        }
+        elsif ( $char eq '@' ) {
+            push @placeholders, [ '@', length($token), 1 ];
+            $pos++;
+        }
+        elsif ( $char eq '*' ) {
+            push @placeholders,
+              [ substr( $format, $pos, 1 ), length($token), 1 ];
+        }
+        else {
+            $pos++;
+        }
+        $pos++ if ( $pos == $start_pos );    #No Format match
+        $token .= substr( $format, $start_pos, $pos - $start_pos );
+
+    }    # End of parsing.
+
+    # Copy the located format string to a result string that we will perform
+    # the substitutions on and return to the user.
+    my $result = $token;
+
+    # Add a placeholder between the decimal/comma and end of the token, if any.
+    if ( $token_start != -1 ) {
+        push @placeholders,
+          [
+            substr( $format, $token_start, $pos - $token_start + 1 ),
+            $decimal_pos, $pos - $token_start + 1
+          ];
+    }
+
+    #
+    # In the next sections we process date, number and text formats. We take a
+    # format such as yyyy/mm/dd and replace it with something like 2008/12/25.
+    #
+    if ( ( $format_mode eq 'date' ) && ( $number =~ $qrNUMBER ) ) {
+
+        # The maximum allowable date in Excel is 9999-12-31T23:59:59.000 which
+        # equates to 2958465.999+ in the 1900 epoch and 2957003.999+ in the
+        # 1904 epoch. We use 0 as the minimum in both epochs. The 1904 system
+        # actually supports negative numbers but that isn't worth the effort.
+        my $min_date = 0;
+        my $max_date = 2958466;
+        $max_date = 2957004 if $is_1904;
+
+        if ( $number < $min_date || $number >= $max_date ) {
+            return $number;    # Return unformatted number.
+        }
+
+        # Process date formats.
+        my @time = ExcelLocaltime( $number, $is_1904 );
+
+        #    0     1     2      3     4       5      6      7
+        my ( $sec, $min, $hour, $day, $month, $year, $wday, $msec ) = @time;
+
+        $month++;              # localtime() zero indexed month.
+        $year += 1900;         # localtime() year.
+
+        my @full_month_name = qw(
+          None January February March April May June July
+          August September October November December
+        );
+        my @short_month_name = qw(
+          None Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+        );
+        my @full_day_name = qw(
+          Sunday Monday Tuesday Wednesday Thursday Friday Saturday
+        );
+        my @short_day_name = qw(
+          Sun Mon Tue Wed Thu Fri Sat
+        );
+
+        # Replace the placeholders in the template such as yyyy mm dd with
+        # actual numbers or strings.
+        my $replacement;
+        for ( my $i = @placeholders - 1 ; $i >= 0 ; $i-- ) {
+            my $placeholder = $placeholders[$i];
+
+            if ( $placeholder->[-1] eq 'minutes' ) {
+
+                # For this case 'm/mm' is minutes not months.
+                if ( $placeholder->[0] eq 'mm' ) {
+                    $replacement = sprintf( "%02d", $min );
+                }
+                else {
+                    $replacement = sprintf( "%d", $min );
+                }
+            }
+            elsif ( $placeholder->[0] eq 'yyyy' ) {
+
+                # 4 digit Year. 2000 -> 2000.
+                $replacement = sprintf( '%04d', $year );
+            }
+            elsif ( $placeholder->[0] eq 'yy' ) {
+
+                # 2 digit Year. 2000 -> 00.
+                $replacement = sprintf( '%02d', $year % 100 );
+            }
+            elsif ( $placeholder->[0] eq 'mmmmm' ) {
+
+                # First character of the month name. 1 -> J.
+                $replacement = substr( $short_month_name[$month], 0, 1 );
+            }
+            elsif ( $placeholder->[0] eq 'mmmm' ) {
+
+                # Full month name. 1 -> January.
+                $replacement = $full_month_name[$month];
+            }
+            elsif ( $placeholder->[0] eq 'mmm' ) {
+
+                # Short month name. 1 -> Jan.
+                $replacement = $short_month_name[$month];
+            }
+            elsif ( $placeholder->[0] eq 'mm' ) {
+
+                # 2 digit month. 1 -> 01.
+                $replacement = sprintf( '%02d', $month );
+            }
+            elsif ( $placeholder->[0] eq 'm' ) {
+
+                # 1 digit month. 1 -> 1.
+                $replacement = sprintf( '%d', $month );
+            }
+            elsif ( $placeholder->[0] eq 'dddd' ) {
+
+                # Full day name. Wednesday (for example.)
+                $replacement = $full_day_name[$wday];
+            }
+            elsif ( $placeholder->[0] eq 'ddd' ) {
+
+                # Short day name. Wed (for example.)
+                $replacement = $short_day_name[$wday];
+            }
+            elsif ( $placeholder->[0] eq 'dd' ) {
+
+                # 2 digit day. 1 -> 01.
+                $replacement = sprintf( '%02d', $day );
+            }
+            elsif ( $placeholder->[0] eq 'd' ) {
+
+                # 1 digit day. 1 -> 1.
+                $replacement = sprintf( '%d', $day );
+            }
+            elsif ( $placeholder->[0] eq 'hh' ) {
+
+                # 2 digit hour.
+                if ($is_12_hour) {
+                    my $hour_tmp = $hour % 12;
+                    $hour_tmp = 12 if $hour % 12 == 0;
+                    $replacement = sprintf( '%d', $hour_tmp );
+                }
+                else {
+                    $replacement = sprintf( '%02d', $hour );
+                }
+            }
+            elsif ( $placeholder->[0] eq 'h' ) {
+
+                # 1 digit hour.
+                if ($is_12_hour) {
+                    my $hour_tmp = $hour % 12;
+                    $hour_tmp = 12 if $hour % 12 == 0;
+                    $replacement = sprintf( '%2d', $hour_tmp );
+                }
+                else {
+                    $replacement = sprintf( '%d', $hour );
+                }
+            }
+            elsif ( $placeholder->[0] eq 'ss' ) {
+
+                # 2 digit seconds.
+                $replacement = sprintf( '%02d', $sec );
+            }
+            elsif ( $placeholder->[0] eq 's' ) {
+
+                # 1 digit seconds.
+                $replacement = sprintf( '%d', $sec );
+            }
+            elsif ( $placeholder->[0] eq 'am/pm' ) {
+
+                # AM/PM.
+                $replacement = ( $hour >= 12 ) ? 'PM' : 'AM';
+            }
+            elsif ( $placeholder->[0] eq 'a/p' ) {
+
+                # AM/PM.
+                $replacement = ( $hour >= 12 ) ? 'P' : 'A';
+            }
+            elsif ( $placeholder->[0] eq '.' ) {
+
+                # Decimal point for seconds.
+                $replacement = '.';
+            }
+            elsif ( $placeholder->[0] =~ /(^0+$)/ ) {
+
+                # Milliseconds. For example h:ss.000.
+                my $length = length($1);
+                $replacement =
+                  substr( sprintf( "%.${length}f", $msec / 1000 ), 2, $length );
+            }
+            elsif ( $placeholder->[0] eq '[h]' ) {
+
+                # Hours modulus 24. 25 displays as 25 not as 1.
+                $replacement = sprintf( '%d', int($number) * 24 + $hour );
+            }
+            elsif ( $placeholder->[0] eq '[mm]' ) {
+
+                # Mins modulus 60. 72 displays as 72 not as 12.
+                $replacement =
+                  sprintf( '%d', ( int($number) * 24 + $hour ) * 60 + $min );
+            }
+            elsif ( $placeholder->[0] eq 'ge' ) {
+                require Spreadsheet::ParseExcel::FmtJapan;
+                # Japanese Nengo (aka Gengo) in initialism (abbr. name)
+                 $replacement =
+                  Spreadsheet::ParseExcel::FmtJapan::CnvNengo( abbr_name => @time );
+            }
+            elsif ( $placeholder->[0] eq 'ggge' ) {
+                require Spreadsheet::ParseExcel::FmtJapan;
+                # Japanese Nengo (aka Gengo) in Kanji (full name)
+                 $replacement =
+                  Spreadsheet::ParseExcel::FmtJapan::CnvNengo( name => @time );
+            }
+            elsif ( $placeholder->[0] eq '@' ) {
+
+                # Text format.
+                $replacement = $number;
+            }
+
+            # Substitute the replacement string back into the template.
+            substr( $result, $placeholder->[1], $placeholder->[2],
+                $replacement );
+        }
+    }
+    elsif ( ( $format_mode eq 'number' ) && ( $number =~ $qrNUMBER ) ) {
+
+        # Process non date formats.
+        if (@placeholders) {
+            while ( $placeholders[-1]->[0] eq ',' ) {
+                $comma_count--;
+                substr(
+                    $result,
+                    $placeholders[-1]->[1],
+                    $placeholders[-1]->[2], ''
+                );
+                $number /= 1000;
+                pop @placeholders;
+            }
+
+            my $number_format = join( '', map { $_->[0] } @placeholders );
+            my $number_result;
+            my $str_length    = 0;
+            my $engineering   = 0;
+            my $is_decimal    = 0;
+            my $is_integer    = 0;
+            my $after_decimal = undef;
+
+            for my $token ( split //, $number_format ) {
+                if ( $token eq '.' ) {
+                    $str_length++;
+                    $is_decimal = 1;
+                }
+                elsif ( ( $token eq 'E' ) || ( $token eq 'e' ) ) {
+                    $engineering = 1;
+                }
+                elsif ( $token eq '0' ) {
+                    $str_length++;
+                    $after_decimal++ if $is_decimal;
+                    $is_integer = 1;
+                }
+                elsif ( $token eq '#' ) {
+                    $after_decimal++ if $is_decimal;
+                    $is_integer = 1;
+                }
+                elsif ( $token eq '?' ) {
+                    $after_decimal++ if $is_decimal;
+                }
+            }
+
+            $number *= 100.0 if $is_percent;
+
+            my $data = ($is_currency) ? abs($number) : $number + 0;
+
+            if ($is_fraction) {
+                $number_result = sprintf( "%0${str_length}d", int($data) );
+            }
+            else {
+                if ($is_decimal) {
+
+                    if ( defined $after_decimal ) {
+                        $number_result =
+                          sprintf "%0${str_length}.${after_decimal}f", $data;
+                    }
+                    else {
+                        $number_result = sprintf "%0${str_length}f", $data;
+                    }
+
+                    # Fix for Perl and sprintf not rounding up like Excel.
+                    # http://rt.cpan.org/Public/Bug/Display.html?id=45626
+                    if ( $data =~ /^${number_result}5/ ) {
+                        $number_result =
+                          sprintf "%0${str_length}.${after_decimal}f",
+                          $data . '1';
+                    }
+                }
+                else {
+                    $number_result = sprintf( "%0${str_length}.0f", $data );
+                }
+            }
+
+            $number_result = AddComma($number_result) if $comma_count > 0;
+
+            my $number_length = length($number_result);
+            my $decimal_pos   = -1;
+            my $replacement;
+
+            for ( my $i = @placeholders - 1 ; $i >= 0 ; $i-- ) {
+                my $placeholder = $placeholders[$i];
+
+                if ( $placeholder->[0] =~
+                    /([#0]*)([\.]?)([0#]*)([eE])([\+\-])([0#]+)/ )
+                {
+                    substr( $result, $placeholder->[1], $placeholder->[2],
+                        MakeE( $placeholder->[0], $number ) );
+                }
+                elsif ( $placeholder->[0] =~ /\// ) {
+                    substr( $result, $placeholder->[1], $placeholder->[2],
+                        MakeFraction( $placeholder->[0], $number, $is_integer )
+                    );
+                }
+                elsif ( $placeholder->[0] eq '.' ) {
+                    $number_length--;
+                    $decimal_pos = $number_length;
+                }
+                elsif ( $placeholder->[0] eq '+' ) {
+                    substr( $result, $placeholder->[1], $placeholder->[2],
+                        ( $number > 0 )
+                        ? '+'
+                        : ( ( $number == 0 ) ? '+' : '-' ) );
+                }
+                elsif ( $placeholder->[0] eq '-' ) {
+                    substr( $result, $placeholder->[1], $placeholder->[2],
+                        ( $number > 0 )
+                        ? ''
+                        : ( ( $number == 0 ) ? '' : '-' ) );
+                }
+                elsif ( $placeholder->[0] eq '@' ) {
+                    substr( $result, $placeholder->[1], $placeholder->[2],
+                        $number );
+                }
+                elsif ( $placeholder->[0] eq '*' ) {
+                    substr( $result, $placeholder->[1], $placeholder->[2], '' );
+                }
+                elsif (( $placeholder->[0] eq "\xA2\xA4" )
+                    or ( $placeholder->[0] eq "\xA2\xA5" )
+                    or ( $placeholder->[0] eq "\x81\xA2" )
+                    or ( $placeholder->[0] eq "\x81\xA3" ) )
+                {
+                    substr(
+                        $result,           $placeholder->[1],
+                        $placeholder->[2], $placeholder->[0]
+                    );
+                }
+                elsif (( $placeholder->[0] eq '(' )
+                    or ( $placeholder->[0] eq ')' ) )
+                {
+                    substr(
+                        $result,           $placeholder->[1],
+                        $placeholder->[2], $placeholder->[0]
+                    );
+                }
+                else {
+                    if ( $number_length > 0 ) {
+                        if ( $i <= 0 ) {
+                            $replacement =
+                              substr( $number_result, 0, $number_length );
+                            $number_length = 0;
+                        }
+                        else {
+                            my $real_part_length = length( $placeholder->[0] );
+                            if ( $decimal_pos >= 0 ) {
+                                my $format = $placeholder->[0];
+                                $format =~ s/^#+//;
+                                $real_part_length = length $format;
+                                $real_part_length =
+                                  ( $number_length <= $real_part_length )
+                                  ? $number_length
+                                  : $real_part_length;
+                            }
+                            else {
+                                $real_part_length =
+                                  ( $number_length <= $real_part_length )
+                                  ? $number_length
+                                  : $real_part_length;
+                            }
+                            $replacement =
+                              substr( $number_result,
+                                $number_length - $real_part_length,
+                                $real_part_length );
+                            $number_length -= $real_part_length;
+                        }
+                    }
+                    else {
+                        $replacement = '';
+                    }
+                    substr( $result, $placeholder->[1], $placeholder->[2],
+                        "\x00" . $replacement );
+                }
+            }
+            $replacement =
+              ( $number_length > 0 )
+              ? substr( $number_result, 0, $number_length )
+              : '';
+            $result =~ s/\x00/$replacement/;
+            $result =~ s/\x00//g;
+        }
+    }
+    else {
+
+        # Process text formats
+        my $is_text = 0;
+        for ( my $i = @placeholders - 1 ; $i >= 0 ; $i-- ) {
+            my $placeholder = $placeholders[$i];
+            if ( $placeholder->[0] eq '@' ) {
+                substr( $result, $placeholder->[1], $placeholder->[2],
+                    $number );
+                $is_text++;
+            }
+            else {
+                substr( $result, $placeholder->[1], $placeholder->[2], '' );
+            }
+        }
+
+        $result = $number unless $is_text;
+
+    }    # End of placeholder substitutions.
+
+    # Trim the leading and trailing whitespace from the results.
+    $result =~ s/^\s+//;
+    $result =~ s/\s+$//;
+
+    # Fix for negative currency.
+    $result =~ s/^\$\-/\-\$/;
+    $result =~ s/^\$ \-/\-\$ /;
+
+    # Return color and locale strings if required.
+    if ($want_subformats) {
+        return ( $result, $color, $locale );
+    }
+    else {
+        return $result;
+    }
+}
+
+#------------------------------------------------------------------------------
+# AddComma (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+sub AddComma {
+    my ($sNum) = @_;
+
+    if ( $sNum =~ /^([^\d]*)(\d\d\d\d+)(\.*.*)$/ ) {
+        my ( $sPre, $sObj, $sAft ) = ( $1, $2, $3 );
+        for ( my $i = length($sObj) - 3 ; $i > 0 ; $i -= 3 ) {
+            substr( $sObj, $i, 0, ',' );
+        }
+        return $sPre . $sObj . $sAft;
+    }
+    else {
+        return $sNum;
+    }
+}
+
+#------------------------------------------------------------------------------
+# MakeFraction (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+sub MakeFraction {
+    my ( $sFmt, $iData, $iFlg ) = @_;
+    my $iBunbo;
+    my $iShou;
+
+    #1. Init
+    # print "FLG: $iFlg\n";
+    if ($iFlg) {
+        $iShou = $iData - int($iData);
+        return '' if ( $iShou == 0 );
+    }
+    else {
+        $iShou = $iData;
+    }
+    $iShou = abs($iShou);
+    my $sSWk;
+
+    #2.Calc BUNBO
+    #2.1 BUNBO defined
+    if ( $sFmt =~ /\/(\d+)$/ ) {
+        $iBunbo = $1;
+        return sprintf( "%d/%d", $iShou * $iBunbo, $iBunbo );
+    }
+    else {
+
+        #2.2 Calc BUNBO
+        $sFmt =~ /\/(\?+)$/;
+        my $iKeta = length($1);
+        my $iSWk  = 1;
+        my $sSWk  = '';
+        my $iBunsi;
+        for ( my $iBunbo = 2 ; $iBunbo < 10**$iKeta ; $iBunbo++ ) {
+            $iBunsi = int( $iShou * $iBunbo + 0.5 );
+            my $iCmp = abs( $iShou - ( $iBunsi / $iBunbo ) );
+            if ( $iCmp < $iSWk ) {
+                $iSWk = $iCmp;
+                $sSWk = sprintf( "%d/%d", $iBunsi, $iBunbo );
+                last if ( $iSWk == 0 );
+            }
+        }
+        return $sSWk;
+    }
+}
+
+#------------------------------------------------------------------------------
+# MakeE (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+sub MakeE {
+    my ( $sFmt, $iData ) = @_;
+
+    $sFmt =~ /(([#0]*)[\.]?[#0]*)([eE])([\+\-][0#]+)/;
+    my ( $sKari, $iKeta, $sE, $sSisu ) = ( $1, length($2), $3, $4 );
+    $iKeta = 1 if ( $iKeta <= 0 );
+
+    my $iLog10 = 0;
+    $iLog10 = ( $iData == 0 ) ? 0 : ( log( abs($iData) ) / log(10) );
+    $iLog10 = (
+        int( $iLog10 / $iKeta ) +
+          ( ( ( $iLog10 - int( $iLog10 / $iKeta ) ) < 0 ) ? -1 : 0 ) ) * $iKeta;
+
+    my $sUe = ExcelFmt( $sKari, $iData * ( 10**( $iLog10 * -1 ) ), 0 );
+    my $sShita = ExcelFmt( $sSisu, $iLog10, 0 );
+    return $sUe . $sE . $sShita;
+}
+
+#------------------------------------------------------------------------------
+# LeapYear (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+sub LeapYear {
+    my ($iYear) = @_;
+    return 1 if ( $iYear == 1900 );    #Special for Excel
+    return ( ( ( $iYear % 4 ) == 0 )
+          && ( ( $iYear % 100 ) || ( $iYear % 400 ) == 0 ) )
+      ? 1
+      : 0;
+}
+
+#------------------------------------------------------------------------------
+# LocaltimeExcel (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+sub LocaltimeExcel {
+    my ( $iSec, $iMin, $iHour, $iDay, $iMon, $iYear, $iwDay, $iMSec, $flg1904 )
+      = @_;
+
+    #0. Init
+    $iMon++;
+    $iYear += 1900;
+
+    #1. Calc Time
+    my $iTime;
+    $iTime = $iHour;
+    $iTime *= 60;
+    $iTime += $iMin;
+    $iTime *= 60;
+    $iTime += $iSec;
+    $iTime += $iMSec / 1000.0 if ( defined($iMSec) );
+    $iTime /= 86400.0;    #3600*24(1day in seconds)
+    my $iY;
+    my $iYDays;
+
+    #2. Calc Days
+    if ($flg1904) {
+        $iY = 1904;
+        $iTime--;         #Start from Jan 1st
+        $iYDays = 366;
+    }
+    else {
+        $iY     = 1900;
+        $iYDays = 366;    #In Excel 1900 is leap year (That's not TRUE!)
+    }
+    while ( $iY < $iYear ) {
+        $iTime += $iYDays;
+        $iY++;
+        $iYDays = ( LeapYear($iY) ) ? 366 : 365;
+    }
+    for ( my $iM = 1 ; $iM < $iMon ; $iM++ ) {
+        if (   $iM == 1
+            || $iM == 3
+            || $iM == 5
+            || $iM == 7
+            || $iM == 8
+            || $iM == 10
+            || $iM == 12 )
+        {
+            $iTime += 31;
+        }
+        elsif ( $iM == 4 || $iM == 6 || $iM == 9 || $iM == 11 ) {
+            $iTime += 30;
+        }
+        elsif ( $iM == 2 ) {
+            $iTime += ( LeapYear($iYear) ) ? 29 : 28;
+        }
+    }
+    $iTime += $iDay;
+    return $iTime;
+}
+
+#------------------------------------------------------------------------------
+# ExcelLocaltime (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+sub ExcelLocaltime {
+
+    my ( $dObj, $flg1904 ) = @_;
+    my ( $iSec, $iMin, $iHour, $iDay, $iMon, $iYear, $iwDay, $iMSec );
+    my ( $iDt, $iTime, $iYDays );
+
+    $iDt   = int($dObj);
+    $iTime = $dObj - $iDt;
+
+    #1. Calc Days
+    if ($flg1904) {
+        $iYear = 1904;
+        $iDt++;    #Start from Jan 1st
+        $iYDays = 366;
+        $iwDay = ( ( $iDt + 4 ) % 7 );
+    }
+    else {
+        $iYear  = 1900;
+        $iYDays = 366;    #In Excel 1900 is leap year (That's not TRUE!)
+        $iwDay = ( ( $iDt + 6 ) % 7 );
+    }
+    while ( $iDt > $iYDays ) {
+        $iDt -= $iYDays;
+        $iYear++;
+        $iYDays =
+          (      ( ( $iYear % 4 ) == 0 )
+              && ( ( $iYear % 100 ) || ( $iYear % 400 ) == 0 ) ) ? 366 : 365;
+    }
+    $iYear -= 1900;       # Localtime year is relative to 1900.
+
+    for ( $iMon = 1 ; $iMon < 12 ; $iMon++ ) {
+        my $iMD;
+        if (   $iMon == 1
+            || $iMon == 3
+            || $iMon == 5
+            || $iMon == 7
+            || $iMon == 8
+            || $iMon == 10
+            || $iMon == 12 )
+        {
+            $iMD = 31;
+        }
+        elsif ( $iMon == 4 || $iMon == 6 || $iMon == 9 || $iMon == 11 ) {
+            $iMD = 30;
+        }
+        elsif ( $iMon == 2 ) {
+            $iMD = ( ( $iYear % 4 ) == 0 ) ? 29 : 28;
+        }
+        last if ( $iDt <= $iMD );
+        $iDt -= $iMD;
+    }
+
+    $iMon -= 1;    # Localtime month is 0 based.
+
+    #2. Calc Time
+    $iDay = $iDt;
+    $iTime += ( 0.0005 / 86400.0 );
+    $iTime *= 24.0;
+    $iHour = int($iTime);
+    $iTime -= $iHour;
+    $iTime *= 60.0;
+    $iMin = int($iTime);
+    $iTime -= $iMin;
+    $iTime *= 60.0;
+    $iSec = int($iTime);
+    $iTime -= $iSec;
+    $iTime *= 1000.0;
+    $iMSec = int($iTime);
+
+    return ( $iSec, $iMin, $iHour, $iDay, $iMon, $iYear, $iwDay, $iMSec );
+}
+
+# -----------------------------------------------------------------------------
+# col2int (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+# converts a excel row letter into an int for use in an array
+sub col2int {
+    my $result = 0;
+    my $str    = shift;
+    my $incr   = 0;
+
+    for ( my $i = length($str) ; $i > 0 ; $i-- ) {
+        my $char = substr( $str, $i - 1 );
+        my $curr += ord( lc($char) ) - ord('a') + 1;
+        $curr *= $incr if ($incr);
+        $result += $curr;
+        $incr   += 26;
+    }
+
+    # this is one out as we range 0..x-1 not 1..x
+    $result--;
+
+    return $result;
+}
+
+# -----------------------------------------------------------------------------
+# int2col (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+### int2col
+# convert a column number into column letters
+# @note this is quite a brute force coarse method
+#   does not manage values over 701 (ZZ)
+# @arg number, to convert
+# @returns string, column name
+#
+sub int2col {
+    my $out = "";
+    my $val = shift;
+
+    do {
+        $out .= chr( ( $val % 26 ) + ord('A') );
+        $val = int( $val / 26 ) - 1;
+    } while ( $val >= 0 );
+
+    return scalar reverse $out;
+}
+
+# -----------------------------------------------------------------------------
+# sheetRef (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+### sheetRef
+# convert an excel letter-number address into a useful array address
+# @note that also Excel uses X-Y notation, we normally use Y-X in arrays
+# @args $str, excel coord eg. A2
+# @returns an array - 2 elements - column, row, or undefined
+#
+sub sheetRef {
+    my $str = shift;
+    my @ret;
+
+    $str =~ m/^(\D+)(\d+)$/;
+
+    if ( $1 && $2 ) {
+        push( @ret, $2 - 1, col2int($1) );
+    }
+    if ( $ret[0] < 0 ) {
+        undef @ret;
+    }
+
+    return @ret;
+}
+
+# -----------------------------------------------------------------------------
+# xls2csv (for Spreadsheet::ParseExcel::Utility)
+#------------------------------------------------------------------------------
+### xls2csv
+# convert a chunk of an excel file into csv text chunk
+# @args $param, sheet-colrow:colrow (1-A1:B2 or A1:B2 for sheet 1
+# @args $rotate, 0 or 1 decides if output should be rotated or not
+# @returns string containing a chunk of csv
+#
+sub xls2csv {
+    my ( $filename, $regions, $rotate ) = @_;
+    my $sheet = 0;
+
+    # We need Text::CSV_XS for proper CSV handling.
+    require Text::CSV_XS;
+
+    # extract any sheet number from the region string
+    $regions =~ m/^(\d+)-(.*)/;
+
+    if ($2) {
+        $sheet   = $1 - 1;
+        $regions = $2;
+    }
+
+    # now extract the start and end regions
+    $regions =~ m/(.*):(.*)/;
+
+    if ( !$1 || !$2 ) {
+        print STDERR "Bad Params";
+        return "";
+    }
+
+    my @start = sheetRef($1);
+    my @end   = sheetRef($2);
+    if ( !@start ) {
+        print STDERR "Bad coorinates - $1";
+        return "";
+    }
+    if ( !@end ) {
+        print STDERR "Bad coorinates - $2";
+        return "";
+    }
+
+    if ( $start[1] > $end[1] ) {
+        print STDERR "Bad COLUMN ordering\n";
+        print STDERR "Start column " . int2col( $start[1] );
+        print STDERR " after end column " . int2col( $end[1] ) . "\n";
+        return "";
+    }
+    if ( $start[0] > $end[0] ) {
+        print STDERR "Bad ROW ordering\n";
+        print STDERR "Start row " . ( $start[0] + 1 );
+        print STDERR " after end row " . ( $end[0] + 1 ) . "\n";
+        exit;
+    }
+
+    # start the excel object now
+    my $oExcel = new Spreadsheet::ParseExcel;
+    my $oBook  = $oExcel->Parse($filename);
+
+    # open the sheet
+    my $oWkS = $oBook->{Worksheet}[$sheet];
+
+    # now check that the region exists in the file
+    # if not truncate to the possible region
+    # output a warning msg
+    if ( $start[1] < $oWkS->{MinCol} ) {
+        print STDERR int2col( $start[1] )
+          . " < min col "
+          . int2col( $oWkS->{MinCol} )
+          . " Resetting\n";
+        $start[1] = $oWkS->{MinCol};
+    }
+    if ( $end[1] > $oWkS->{MaxCol} ) {
+        print STDERR int2col( $end[1] )
+          . " > max col "
+          . int2col( $oWkS->{MaxCol} )
+          . " Resetting\n";
+        $end[1] = $oWkS->{MaxCol};
+    }
+    if ( $start[0] < $oWkS->{MinRow} ) {
+        print STDERR ""
+          . ( $start[0] + 1 )
+          . " < min row "
+          . ( $oWkS->{MinRow} + 1 )
+          . " Resetting\n";
+        $start[0] = $oWkS->{MinCol};
+    }
+    if ( $end[0] > $oWkS->{MaxRow} ) {
+        print STDERR ""
+          . ( $end[0] + 1 )
+          . " > max row "
+          . ( $oWkS->{MaxRow} + 1 )
+          . " Resetting\n";
+        $end[0] = $oWkS->{MaxRow};
+
+    }
+
+    my $x1 = $start[1];
+    my $y1 = $start[0];
+    my $x2 = $end[1];
+    my $y2 = $end[0];
+
+    my @cell_data;
+    my $row = 0;
+
+    if ( !$rotate ) {
+        for ( my $y = $y1 ; $y <= $y2 ; $y++ ) {
+            for ( my $x = $x1 ; $x <= $x2 ; $x++ ) {
+                my $cell = $oWkS->{Cells}[$y][$x];
+
+                my $value;
+                if ( defined $cell ) {
+                    $value .= $cell->value();
+                }
+                else {
+                    $value = '';
+                }
+
+                push @{ $cell_data[$row] }, $value;
+            }
+            $row++;
+        }
+    }
+    else {
+        for ( my $x = $x1 ; $x <= $x2 ; $x++ ) {
+            for ( my $y = $y1 ; $y <= $y2 ; $y++ ) {
+                my $cell = $oWkS->{Cells}[$y][$x];
+
+                my $value;
+                if ( defined $cell ) {
+                    $value .= $cell->value();
+                }
+                else {
+                    $value = '';
+                }
+
+                push @{ $cell_data[$row] }, $value;
+            }
+            $row++;
+        }
+    }
+
+    # Create the CSV output string.
+    my $csv = Text::CSV_XS->new( { binary => 1, eol => $/ } );
+    my $output = "";
+
+    for my $row (@cell_data) {
+        $csv->combine(@$row);
+        $output .= $csv->string();
+    }
+
+    return $output;
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::Utility - Utility functions for Spreadsheet::ParseExcel.
+
+=head1 SYNOPSIS
+
+    use Spreadsheet::ParseExcel::Utility qw(ExcelFmt ExcelLocaltime LocaltimeExcel);
+
+    # Convert localtime to Excel time
+    my $datetime = LocaltimeExcel(11, 10, 12, 23, 2, 64); # 1964-3-23 12:10:11
+
+    print $datetime, "\n"; # 23459.5070717593 (Excel date/time format)
+
+    # Convert Excel Time to localtime
+    my @time = ExcelLocaltime($datetime);
+    print join(":", @time), "\n";   # 11:10:12:23:2:64:1:0
+
+    # Formatting
+    print ExcelFmt('yyyy-mm-dd', $datetime), "\n"; # 1964-3-23
+    print ExcelFmt('m-d-yy',     $datetime), "\n"; # 3-23-64
+    print ExcelFmt('#,##0',      $datetime), "\n"; # 23,460
+    print ExcelFmt('#,##0.00',   $datetime), "\n"; # 23,459.51
+
+=head1 DESCRIPTION
+
+The C<Spreadsheet::ParseExcel::Utility> module provides utility functions for working with ParseExcel and Excel data.
+
+=head1 Functions
+
+C<Spreadsheet::ParseExcel::Utility> can export the following functions:
+
+    ExcelFmt
+    ExcelLocaltime
+    LocaltimeExcel
+    col2int
+    int2col
+    sheetRef
+    xls2csv
+
+These functions must be imported implicitly:
+
+    # Just one function.
+    use Spreadsheet::ParseExcel::Utility 'col2int';
+
+    # More than one.
+    use Spreadsheet::ParseExcel::Utility qw(ExcelFmt ExcelLocaltime LocaltimeExcel);
+
+
+=head2 ExcelFmt($format_string, $number, $is_1904)
+
+Excel stores data such as dates and currency values as numbers. The way these numbers are displayed is controlled by the number format string for the cell. For example a cell with a number format of C<'$#,##0.00'> for currency and a value of 1234.567 would be displayed as follows:
+
+    '$#,##0.00' + 1234.567 = '$1,234.57'.
+
+The C<ExcelFmt()> function tries to emulate this formatting so that the user can convert raw numbers returned by C<Spreadsheet::ParseExel> to a desired format. For example:
+
+    print ExcelFmt('$#,##0.00', 1234.567); # $1,234.57.
+
+The syntax of the function is:
+
+    my $text = ExcelFmt($format_string, $number, $is_1904);
+
+Where C<$format_string> is an Excel number format string, C<$number> is a real or integer number and C<is_1904> is an optional flag to indicate that dates should use Excel's 1904 epoch instead of the default 1900 epoch.
+
+C<ExcelFmt()> is also used internally to convert numbers returned by the C<Cell::unformatted()> method to the formatted value returned by the C<Cell::value()> method:
+
+
+    my $cell = $worksheet->get_cell( 0, 0 );
+
+    print $cell->unformatted(), "\n"; # 1234.567
+    print $cell->value(),       "\n"; # $1,234.57
+
+The most common usage for C<ExcelFmt> is to convert numbers to dates. Dates and times in Excel are represented by real numbers, for example "1 Jan 2001 12:30 PM" is represented by the number 36892.521. The integer part of the number stores the number of days since the epoch and the fractional part stores the percentage of the day. By applying an Excel number format the number is converted to the desired string representation:
+
+    print ExcelFmt('d mmm yyyy h:mm AM/PM', 36892.521);  # 1 Jan 2001 12:30 PM
+
+C<$is_1904> is an optional flag to indicate that dates should use Excel's 1904 epoch instead of the default 1900 epoch. Excel for Windows generally uses 1900 and Excel for Mac OS uses 1904. The C<$is1904> flag isn't required very often by a casual user and can usually be ignored.
+
+
+=head2 ExcelLocaltime($excel_datetime, $is_1904)
+
+The C<ExcelLocaltime()> function converts from an Excel date/time number to a C<localtime()>-like array of values:
+
+        my @time = ExcelLocaltime($excel_datetime);
+
+        #    0     1     2      3     4       5      6      7
+        my ( $sec, $min, $hour, $day, $month, $year, $wday, $msec ) = @time;
+
+The array elements from C<(0 .. 6)> are the same as Perl's C<localtime()>. The last element C<$msec> is milliseconds. In particular it should be noted that, in common with C<localtime()>, the month is zero indexed and the year is the number of years since 1900. This means that you will usually need to do the following:
+
+        $month++;
+        $year += 1900;
+
+See also Perl's documentation for L<localtime()|perlfunc>:
+
+The C<$is_1904> flag is an optional. It is used to indicate that dates should use Excel's 1904 epoch instead of the default 1900 epoch.
+
+=head2 LocaltimeExcel($sec, $min, $hour, $day, $month, $year, $wday, $msec, $is_1904)
+
+The C<LocaltimeExcel()> function converts from a C<localtime()>-like array of values to an Excel date/time number:
+
+    $excel_datetime = LocaltimeExcel($sec, $min, $hour, $day, $month, $year, $wday, $msec);
+
+The array elements from C<(0 .. 6)> are the same as Perl's C<localtime()>. The last element C<$msec> is milliseconds. In particular it should be noted that, in common with C<localtime()>, the month is zero indexed and the year is the number of years since 1900. See also Perl's documentation for L<localtime()|perlfunc>:
+
+The C<$wday> and C<$msec> elements are usually optional. This time elements can also be zeroed if they aren't of interest:
+
+                                    # sec, min, hour, day, month, year
+    $excel_datetime = LocaltimeExcel( 0,   0,   0,    1,   0,     101 );
+
+    print ExcelFmt('d mmm yyyy', $excel_datetime);  # 1 Jan 2001
+
+The C<$is_1904> flag is also optional. It is used to indicate that dates should use Excel's 1904 epoch instead of the default 1900 epoch.
+
+
+=head2 col2int($column)
+
+The C<col2int()> function converts an Excel column letter to an zero-indexed column number:
+
+    print col2int('A');  # 0
+    print col2int('AA'); # 26
+
+This function was contributed by Kevin Mulholland.
+
+
+=head2 int2col($column_number)
+
+The C<int2col()> function converts an zero-indexed Excel column number to a column letter:
+
+    print int2col(0);  # 'A'
+    print int2col(26); # 'AA'
+
+This function was contributed by Kevin Mulholland.
+
+
+=head2 sheetRef($cell_string)
+
+The C<sheetRef()> function converts an Excel cell reference in 'A1' notation to a zero-indexed C<(row, col)> pair.
+
+    my ($row, $col) = sheetRef('A1'); # ( 0, 0 )
+    my ($row, $col) = sheetRef('C2'); # ( 1, 2 )
+
+This function was contributed by Kevin Mulholland.
+
+
+=head2 xls2csv($filename, $region, $rotate)
+
+The C<xls2csv()> function converts a section of an Excel file into a CSV text string.
+
+    $csv_text = xls2csv($filename, $region, $rotate);
+
+Where:
+
+    $region = "sheet-colrow:colrow"
+    For example '1-A1:B2' means 'A1:B2' for sheet 1.
+
+    and
+
+    $rotate  = 0 or 1 (output is rotated/transposed or not)
+
+This function requires C<Text::CSV_XS> to be installed. It was contributed by Kevin Mulholland along with the C<xls2csv> script in the C<sample> directory of the distro.
+
+See also the following xls2csv utilities: Ken Prows' C<xls2csv>: http://search.cpan.org/~ken/xls2csv/script/xls2csv and H.Merijn Brand's C<xls2csv> (which is part of Spreadsheet::Read): http://search.cpan.org/~hmbrand/Spreadsheet-Read/
+
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Workbook.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Workbook.pm
new file mode 100644
index 0000000..d8a03c0
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Workbook.pm
@@ -0,0 +1,297 @@
+package Spreadsheet::ParseExcel::Workbook;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::Workbook - A class for Workbooks.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+
+our $VERSION = '0.59';
+
+###############################################################################
+#
+# new()
+#
+# Constructor.
+#
+sub new {
+    my ($class) = @_;
+    my $self = {};
+    bless $self, $class;
+}
+
+###############################################################################
+#
+# worksheet()
+#
+# This method returns a single Worksheet object using either its name or index.
+#
+sub worksheet {
+    my ( $oBook, $sName ) = @_;
+    my $oWkS;
+    foreach $oWkS ( @{ $oBook->{Worksheet} } ) {
+        return $oWkS if ( $oWkS->{Name} eq $sName );
+    }
+    if ( $sName =~ /^\d+$/ ) {
+        return $oBook->{Worksheet}->[$sName];
+    }
+    return undef;
+}
+
+###############################################################################
+#
+# worksheets()
+#
+# Returns an array ofWorksheet objects.
+#
+sub worksheets {
+    my $self = shift;
+
+    return @{ $self->{Worksheet} };
+}
+
+###############################################################################
+#
+# worksheet_count()
+#
+# Returns the number Woksheet objects in the Workbook.
+#
+sub worksheet_count {
+
+    my $self = shift;
+
+    return $self->{SheetCount};
+}
+
+###############################################################################
+#
+# get_filename()
+#
+# Returns the name of the Excel file of C<undef> if the data was read from a filehandle rather than a file.
+#
+sub get_filename {
+
+    my $self = shift;
+
+    return $self->{File};
+}
+
+###############################################################################
+#
+# get_print_areas()
+#
+# Returns an array ref of print areas.
+#
+# TODO. This should really be a Worksheet method.
+#
+sub get_print_areas {
+
+    my $self = shift;
+
+    return $self->{PrintArea};
+}
+
+###############################################################################
+#
+# get_print_titles()
+#
+# Returns an array ref of print title hash refs.
+#
+# TODO. This should really be a Worksheet method.
+#
+sub get_print_titles {
+
+    my $self = shift;
+
+    return $self->{PrintTitle};
+}
+
+###############################################################################
+#
+# using_1904_date()
+#
+# Returns true if the Excel file is using the 1904 date epoch.
+#
+sub using_1904_date {
+
+    my $self = shift;
+
+    return $self->{Flg1904};
+}
+
+###############################################################################
+#
+# ParseAbort()
+#
+# Todo
+#
+sub ParseAbort {
+    my ( $self, $val ) = @_;
+    $self->{_ParseAbort} = $val;
+}
+
+###############################################################################
+#
+# Parse(). Deprecated.
+#
+# Syntactic wrapper around Spreadsheet::ParseExcel::Parse().
+# This method is *deprecated* since it doesn't conform to the the current
+# error handling in the S::PE Parse() method.
+#
+sub Parse {
+
+    my ( $class, $source, $formatter ) = @_;
+    my $excel = Spreadsheet::ParseExcel->new();
+    my $workbook = $excel->Parse( $source, $formatter );
+    $workbook->{_Excel} = $excel;
+    return $workbook;
+}
+
+###############################################################################
+#
+# Mapping between legacy method names and new names.
+#
+{
+    no warnings;    # Ignore warnings about variables used only once.
+    *Worksheet = *worksheet;
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::Workbook - A class for Workbooks.
+
+=head1 SYNOPSIS
+
+See the documentation for Spreadsheet::ParseExcel.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for L<Spreadsheet::ParseExcel>.
+
+
+=head1 Methods
+
+The following Workbook methods are available:
+
+    $workbook->worksheets()
+    $workbook->worksheet()
+    $workbook->worksheet_count()
+    $workbook->get_filename()
+    $workbook->get_print_areas()
+    $workbook->get_print_titles()
+    $workbook->using_1904_date()
+
+
+=head2 worksheets()
+
+The C<worksheets()> method returns an array of Worksheet objects. This was most commonly used to iterate over the worksheets in a workbook:
+
+    for my $worksheet ( $workbook->worksheets() ) {
+        ...
+    }
+
+
+=head2 worksheet()
+
+The C<worksheet()> method returns a single C<Worksheet> object using either its name or index:
+
+    $worksheet = $workbook->worksheet('Sheet1');
+    $worksheet = $workbook->worksheet(0);
+
+Returns C<undef> if the sheet name or index doesn't exist.
+
+
+=head2 worksheet_count()
+
+The C<worksheet_count()> method returns the number of Woksheet objects in the Workbook.
+
+    my $worksheet_count = $workbook->worksheet_count();
+
+
+=head2 get_filename()
+
+The C<get_filename()> method returns the name of the Excel file of C<undef> if the data was read from a filehandle rather than a file.
+
+    my $filename = $workbook->get_filename();
+
+
+=head2 get_print_areas()
+
+The C<get_print_areas()> method returns an array ref of print areas.
+
+    my $print_areas = $workbook->get_print_areas();
+
+Each print area is as follows:
+
+    [ $start_row, $start_col, $end_row, $end_col ]
+
+Returns undef if there are no print areas.
+
+
+=head2 get_print_titles()
+
+The C<get_print_titles()> method returns an array ref of print title hash refs.
+
+    my $print_titles = $workbook->get_print_titles();
+
+Each print title array ref is as follows:
+
+    {
+        Row    => [ $start_row, $end_row ],
+        Column => [ $start_col, $end_col ],
+    }
+
+
+Returns undef if there are no print titles.
+
+
+=head2 using_1904_date()
+
+The C<using_1904_date()> method returns true if the Excel file is using the 1904 date epoch instead of the 1900 epoch.
+
+    my $using_1904_date = $workbook->using_1904_date();
+
+ The Windows version of Excel generally uses the 1900 epoch while the Mac version of Excel generally uses the 1904 epoch.
+
+Returns 0 if the 1900 epoch is in use.
+
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Worksheet.pm b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Worksheet.pm
new file mode 100644
index 0000000..b9d32d7
--- /dev/null
+++ b/src/bach/build.bach/tools/perl/Spreadsheet/ParseExcel/Worksheet.pm
@@ -0,0 +1,955 @@
+package Spreadsheet::ParseExcel::Worksheet;
+
+###############################################################################
+#
+# Spreadsheet::ParseExcel::Worksheet - A class for Worksheets.
+#
+# Used in conjunction with Spreadsheet::ParseExcel.
+#
+# Copyright (c) 2009      John McNamara
+# Copyright (c) 2006-2008 Gabor Szabo
+# Copyright (c) 2000-2006 Kawai Takanori
+#
+# perltidy with standard settings.
+#
+# Documentation after __END__
+#
+
+use strict;
+use warnings;
+use Scalar::Util qw(weaken);
+
+our $VERSION = '0.59';
+
+###############################################################################
+#
+# new()
+#
+sub new {
+
+    my ( $class, %properties ) = @_;
+
+    my $self = \%properties;
+
+    weaken $self->{_Book};
+
+    $self->{Cells}       = undef;
+    $self->{DefColWidth} = 8.43;
+
+    return bless $self, $class;
+}
+
+###############################################################################
+#
+# get_cell( $row, $col )
+#
+# Returns the Cell object at row $row and column $col, if defined.
+#
+sub get_cell {
+
+    my ( $self, $row, $col ) = @_;
+
+    if (   !defined $row
+        || !defined $col
+        || !defined $self->{MaxRow}
+        || !defined $self->{MaxCol} )
+    {
+
+        # Return undef if no arguments are given or if no cells are defined.
+        return undef;
+    }
+    elsif ($row < $self->{MinRow}
+        || $row > $self->{MaxRow}
+        || $col < $self->{MinCol}
+        || $col > $self->{MaxCol} )
+    {
+
+        # Return undef if outside allowable row/col range.
+        return undef;
+    }
+    else {
+
+        # Return the Cell object.
+        return $self->{Cells}->[$row]->[$col];
+    }
+}
+
+###############################################################################
+#
+# row_range()
+#
+# Returns a two-element list ($min, $max) containing the minimum and maximum
+# defined rows in the worksheet.
+#
+# If there is no row defined $max is smaller than $min.
+#
+sub row_range {
+
+    my $self = shift;
+
+    my $min = $self->{MinRow} || 0;
+    my $max = defined( $self->{MaxRow} ) ? $self->{MaxRow} : ( $min - 1 );
+
+    return ( $min, $max );
+}
+
+###############################################################################
+#
+# col_range()
+#
+# Returns a two-element list ($min, $max) containing the minimum and maximum
+# defined cols in the worksheet.
+#
+# If there is no column defined $max is smaller than $min.
+#
+sub col_range {
+
+    my $self = shift;
+
+    my $min = $self->{MinCol} || 0;
+    my $max = defined( $self->{MaxCol} ) ? $self->{MaxCol} : ( $min - 1 );
+
+    return ( $min, $max );
+}
+
+###############################################################################
+#
+# get_name()
+#
+# Returns the name of the worksheet.
+#
+sub get_name {
+
+    my $self = shift;
+
+    return $self->{Name};
+}
+
+###############################################################################
+#
+# sheet_num()
+#
+sub sheet_num {
+
+    my $self = shift;
+
+    return $self->{_SheetNo};
+}
+
+###############################################################################
+#
+# get_h_pagebreaks()
+#
+# Returns an array ref of row numbers where a horizontal page break occurs.
+#
+sub get_h_pagebreaks {
+
+    my $self = shift;
+
+    return $self->{HPageBreak};
+}
+
+###############################################################################
+#
+# get_v_pagebreaks()
+#
+# Returns an array ref of column numbers where a vertical page break occurs.
+#
+sub get_v_pagebreaks {
+
+    my $self = shift;
+
+    return $self->{VPageBreak};
+}
+
+###############################################################################
+#
+# get_merged_areas()
+#
+# Returns an array ref of cells that are merged.
+#
+sub get_merged_areas {
+
+    my $self = shift;
+
+    return $self->{MergedArea};
+}
+
+###############################################################################
+#
+# get_row_heights()
+#
+# Returns an array_ref of row heights.
+#
+sub get_row_heights {
+
+    my $self = shift;
+
+    return @{ $self->{RowHeight} };
+}
+
+###############################################################################
+#
+# get_col_widths()
+#
+# Returns an array_ref of column widths.
+#
+sub get_col_widths {
+
+    my $self = shift;
+
+    return @{ $self->{ColWidth} };
+}
+
+###############################################################################
+#
+# get_default_row_height()
+#
+# Returns the default row height for the worksheet. Generally 12.75.
+#
+sub get_default_row_height {
+
+    my $self = shift;
+
+    return $self->{DefRowHeight};
+}
+
+###############################################################################
+#
+# get_default_col_width()
+#
+# Returns the default column width for the worksheet. Generally 8.43.
+#
+sub get_default_col_width {
+
+    my $self = shift;
+
+    return $self->{DefColWidth};
+}
+
+###############################################################################
+#
+# _get_row_properties()
+#
+# Returns an array_ref of row properties.
+# TODO. This is a placeholder for a future method.
+#
+sub _get_row_properties {
+
+    my $self = shift;
+
+    return $self->{RowProperties};
+}
+
+###############################################################################
+#
+# _get_col_properties()
+#
+# Returns an array_ref of column properties.
+# TODO. This is a placeholder for a future method.
+#
+sub _get_col_properties {
+
+    my $self = shift;
+
+    return $self->{ColProperties};
+}
+
+###############################################################################
+#
+# get_header()
+#
+# Returns the worksheet header string.
+#
+sub get_header {
+
+    my $self = shift;
+
+    return $self->{Header};
+}
+
+###############################################################################
+#
+# get_footer()
+#
+# Returns the worksheet footer string.
+#
+sub get_footer {
+
+    my $self = shift;
+
+    return $self->{Footer};
+}
+
+###############################################################################
+#
+# get_margin_left()
+#
+# Returns the left margin of the worksheet in inches.
+#
+sub get_margin_left {
+
+    my $self = shift;
+
+    return $self->{LeftMargin};
+}
+
+###############################################################################
+#
+# get_margin_right()
+#
+# Returns the right margin of the worksheet in inches.
+#
+sub get_margin_right {
+
+    my $self = shift;
+
+    return $self->{RightMargin};
+}
+
+###############################################################################
+#
+# get_margin_top()
+#
+# Returns the top margin of the worksheet in inches.
+#
+sub get_margin_top {
+
+    my $self = shift;
+
+    return $self->{TopMargin};
+}
+
+###############################################################################
+#
+# get_margin_bottom()
+#
+# Returns the bottom margin of the worksheet in inches.
+#
+sub get_margin_bottom {
+
+    my $self = shift;
+
+    return $self->{BottomMargin};
+}
+
+###############################################################################
+#
+# get_margin_header()
+#
+# Returns the header margin of the worksheet in inches.
+#
+sub get_margin_header {
+
+    my $self = shift;
+
+    return $self->{HeaderMargin};
+}
+
+###############################################################################
+#
+# get_margin_footer()
+#
+# Returns the footer margin of the worksheet in inches.
+#
+sub get_margin_footer {
+
+    my $self = shift;
+
+    return $self->{FooterMargin};
+}
+
+###############################################################################
+#
+# get_paper()
+#
+# Returns the printer paper size.
+#
+sub get_paper {
+
+    my $self = shift;
+
+    return $self->{PaperSize};
+}
+
+###############################################################################
+#
+# get_start_page()
+#
+# Returns the page number that printing will start from.
+#
+sub get_start_page {
+
+    my $self = shift;
+
+    # Only return the page number if the "First page number" option is set.
+    if ( $self->{UsePage} ) {
+        return $self->{PageStart};
+    }
+    else {
+        return 0;
+    }
+}
+
+###############################################################################
+#
+# get_print_order()
+#
+# Returns the Worksheet page printing order.
+#
+sub get_print_order {
+
+    my $self = shift;
+
+    return $self->{LeftToRight};
+}
+
+###############################################################################
+#
+# get_print_scale()
+#
+# Returns the workbook scale for printing.
+#
+sub get_print_scale {
+
+    my $self = shift;
+
+    return $self->{Scale};
+}
+
+###############################################################################
+#
+# get_fit_to_pages()
+#
+# Returns the number of pages wide and high that the printed worksheet page
+# will fit to.
+#
+sub get_fit_to_pages {
+
+    my $self = shift;
+
+    if ( !$self->{PageFit} ) {
+        return ( 0, 0 );
+    }
+    else {
+        return ( $self->{FitWidth}, $self->{FitHeight} );
+    }
+}
+
+###############################################################################
+#
+# is_portrait()
+#
+# Returns true if the worksheet has been set for printing in portrait mode.
+#
+sub is_portrait {
+
+    my $self = shift;
+
+    return $self->{Landscape};
+}
+
+###############################################################################
+#
+# is_centered_horizontally()
+#
+# Returns true if the worksheet has been centered horizontally for printing.
+#
+sub is_centered_horizontally {
+
+    my $self = shift;
+
+    return $self->{HCenter};
+}
+
+###############################################################################
+#
+# is_centered_vertically()
+#
+# Returns true if the worksheet has been centered vertically for printing.
+#
+sub is_centered_vertically {
+
+    my $self = shift;
+
+    return $self->{HCenter};
+}
+
+###############################################################################
+#
+# is_print_gridlines()
+#
+# Returns true if the worksheet print "gridlines" option is turned on.
+#
+sub is_print_gridlines {
+
+    my $self = shift;
+
+    return $self->{PrintGrid};
+}
+
+###############################################################################
+#
+# is_print_row_col_headers()
+#
+# Returns true if the worksheet print "row and column headings" option is on.
+#
+sub is_print_row_col_headers {
+
+    my $self = shift;
+
+    return $self->{PrintHeaders};
+}
+
+###############################################################################
+#
+# is_print_black_and_white()
+#
+# Returns true if the worksheet print "black and white" option is turned on.
+#
+sub is_print_black_and_white {
+
+    my $self = shift;
+
+    return $self->{NoColor};
+}
+
+###############################################################################
+#
+# is_print_draft()
+#
+# Returns true if the worksheet print "draft" option is turned on.
+#
+sub is_print_draft {
+
+    my $self = shift;
+
+    return $self->{Draft};
+}
+
+###############################################################################
+#
+# is_print_comments()
+#
+# Returns true if the worksheet print "comments" option is turned on.
+#
+sub is_print_comments {
+
+    my $self = shift;
+
+    return $self->{Notes};
+}
+
+###############################################################################
+#
+# Mapping between legacy method names and new names.
+#
+{
+    no warnings;    # Ignore warnings about variables used only once.
+    *sheetNo  = *sheet_num;
+    *Cell     = *get_cell;
+    *RowRange = *row_range;
+    *ColRange = *col_range;
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Spreadsheet::ParseExcel::Worksheet - A class for Worksheets.
+
+=head1 SYNOPSIS
+
+See the documentation for L<Spreadsheet::ParseExcel>.
+
+=head1 DESCRIPTION
+
+This module is used in conjunction with Spreadsheet::ParseExcel. See the documentation for Spreadsheet::ParseExcel.
+
+=head1 Methods
+
+The C<Spreadsheet::ParseExcel::Worksheet> class encapsulates the properties of an Excel worksheet. It has the following methods:
+
+    $worksheet->get_cell()
+    $worksheet->row_range()
+    $worksheet->col_range()
+    $worksheet->get_name()
+    $worksheet->get_h_pagebreaks()
+    $worksheet->get_v_pagebreaks()
+    $worksheet->get_merged_areas()
+    $worksheet->get_row_heights()
+    $worksheet->get_col_widths()
+    $worksheet->get_default_row_height()
+    $worksheet->get_default_col_width()
+    $worksheet->get_header()
+    $worksheet->get_footer()
+    $worksheet->get_margin_left()
+    $worksheet->get_margin_right()
+    $worksheet->get_margin_top()
+    $worksheet->get_margin_bottom()
+    $worksheet->get_margin_header()
+    $worksheet->get_margin_footer()
+    $worksheet->get_paper()
+    $worksheet->get_start_page()
+    $worksheet->get_print_order()
+    $worksheet->get_print_scale()
+    $worksheet->get_fit_to_pages()
+    $worksheet->is_portrait()
+    $worksheet->is_centered_horizontally()
+    $worksheet->is_centered_vertically()
+    $worksheet->is_print_gridlines()
+    $worksheet->is_print_row_col_headers()
+    $worksheet->is_print_black_and_white()
+    $worksheet->is_print_draft()
+    $worksheet->is_print_comments()
+
+
+=head2 get_cell($row, $col)
+
+Return the L</Cell> object at row C<$row> and column C<$col> if it is defined. Otherwise returns undef.
+
+    my $cell = $worksheet->get_cell($row, $col);
+
+=head2 row_range()
+
+Returns a two-element list C<($min, $max)> containing the minimum and maximum defined rows in the worksheet. If there is no row defined C<$max> is smaller than C<$min>.
+
+    my ( $row_min, $row_max ) = $worksheet->row_range();
+
+=head2 col_range()
+
+Returns a two-element list C<($min, $max)> containing the minimum and maximum of defined columns in the worksheet. If there is no column defined C<$max> is smaller than C<$min>.
+
+    my ( $col_min, $col_max ) = $worksheet->col_range();
+
+
+=head2 get_name()
+
+The C<get_name()> method returns the name of the worksheet.
+
+    my $name = $worksheet->get_name();
+
+
+=head2 get_h_pagebreaks()
+
+The C<get_h_pagebreaks()> method returns an array ref of row numbers where a horizontal page break occurs.
+
+    my $h_pagebreaks = $worksheet->get_h_pagebreaks();
+
+Returns C<undef> if there are no pagebreaks.
+
+
+=head2 get_v_pagebreaks()
+
+The C<get_v_pagebreaks()> method returns an array ref of column numbers where a vertical page break occurs.
+
+    my $v_pagebreaks = $worksheet->get_v_pagebreaks();
+
+Returns C<undef> if there are no pagebreaks.
+
+
+=head2 get_merged_areas()
+
+The C<get_merged_areas()> method returns an array ref of cells that are merged.
+
+    my $merged_areas = $worksheet->get_merged_areas();
+
+Each merged area is represented as follows:
+
+    [ $start_row, $start_col, $end_row, $end_col]
+
+Returns C<undef> if there are no merged areas.
+
+
+=head2 get_row_heights()
+
+The C<get_row_heights()> method returns an array_ref of row heights.
+
+    my $row_heights = $worksheet->get_row_heights();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_col_widths()
+
+The C<get_col_widths()> method returns an array_ref of column widths.
+
+    my $col_widths = $worksheet->get_col_widths();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_default_row_height()
+
+The C<get_default_row_height()> method returns the default row height for the worksheet. Generally 12.75.
+
+    my $default_row_height = $worksheet->get_default_row_height();
+
+
+=head2 get_default_col_width()
+
+The C<get_default_col_width()> method returns the default column width for the worksheet. Generally 8.43.
+
+    my $default_col_width = $worksheet->get_default_col_width();
+
+
+=head2 get_header()
+
+The C<get_header()> method returns the worksheet header string. This string can contain control codes for alignment and font properties. Refer to the Excel on-line help on headers and footers or to the Spreadsheet::WriteExcel documentation for set_header().
+
+    my $header = $worksheet->get_header();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_footer()
+
+The C<get_footer()> method returns the worksheet footer string. This string can contain control codes for alignment and font properties. Refer to the Excel on-line help on headers and footers or to the Spreadsheet::WriteExcel documentation for set_header().
+
+    my $footer = $worksheet->get_footer();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_margin_left()
+
+The C<get_margin_left()> method returns the left margin of the worksheet in inches.
+
+    my $margin_left = $worksheet->get_margin_left();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_margin_right()
+
+The C<get_margin_right()> method returns the right margin of the worksheet in inches.
+
+    my $margin_right = $worksheet->get_margin_right();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_margin_top()
+
+The C<get_margin_top()> method returns the top margin of the worksheet in inches.
+
+    my $margin_top = $worksheet->get_margin_top();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_margin_bottom()
+
+The C<get_margin_bottom()> method returns the bottom margin of the worksheet in inches.
+
+    my $margin_bottom = $worksheet->get_margin_bottom();
+
+Returns C<undef> if the property isn't set.
+
+
+=head2 get_margin_header()
+
+The C<get_margin_header()> method returns the header margin of the worksheet in inches.
+
+    my $margin_header = $worksheet->get_margin_header();
+
+Returns a default value of 0.5 if not set.
+
+
+=head2 get_margin_footer()
+
+The C<get_margin_footer()> method returns the footer margin of the worksheet in inches.
+
+    my $margin_footer = $worksheet->get_margin_footer();
+
+Returns a default value of 0.5 if not set.
+
+
+=head2 get_paper()
+
+The C<get_paper()> method returns the printer paper size.
+
+    my $paper = $worksheet->get_paper();
+
+The value corresponds to the formats shown below:
+
+    Index   Paper format            Paper size
+    =====   ============            ==========
+      0     Printer default         -
+      1     Letter                  8 1/2 x 11 in
+      2     Letter Small            8 1/2 x 11 in
+      3     Tabloid                 11 x 17 in
+      4     Ledger                  17 x 11 in
+      5     Legal                   8 1/2 x 14 in
+      6     Statement               5 1/2 x 8 1/2 in
+      7     Executive               7 1/4 x 10 1/2 in
+      8     A3                      297 x 420 mm
+      9     A4                      210 x 297 mm
+     10     A4 Small                210 x 297 mm
+     11     A5                      148 x 210 mm
+     12     B4                      250 x 354 mm
+     13     B5                      182 x 257 mm
+     14     Folio                   8 1/2 x 13 in
+     15     Quarto                  215 x 275 mm
+     16     -                       10x14 in
+     17     -                       11x17 in
+     18     Note                    8 1/2 x 11 in
+     19     Envelope  9             3 7/8 x 8 7/8
+     20     Envelope 10             4 1/8 x 9 1/2
+     21     Envelope 11             4 1/2 x 10 3/8
+     22     Envelope 12             4 3/4 x 11
+     23     Envelope 14             5 x 11 1/2
+     24     C size sheet            -
+     25     D size sheet            -
+     26     E size sheet            -
+     27     Envelope DL             110 x 220 mm
+     28     Envelope C3             324 x 458 mm
+     29     Envelope C4             229 x 324 mm
+     30     Envelope C5             162 x 229 mm
+     31     Envelope C6             114 x 162 mm
+     32     Envelope C65            114 x 229 mm
+     33     Envelope B4             250 x 353 mm
+     34     Envelope B5             176 x 250 mm
+     35     Envelope B6             176 x 125 mm
+     36     Envelope                110 x 230 mm
+     37     Monarch                 3.875 x 7.5 in
+     38     Envelope                3 5/8 x 6 1/2 in
+     39     Fanfold                 14 7/8 x 11 in
+     40     German Std Fanfold      8 1/2 x 12 in
+     41     German Legal Fanfold    8 1/2 x 13 in
+     256    User defined
+
+The two most common paper sizes are C<1 = "US Letter"> and C<9 = A4>. Returns 9 by default.
+
+
+=head2 get_start_page()
+
+The C<get_start_page()> method returns the page number that printing will start from.
+
+    my $start_page = $worksheet->get_start_page();
+
+Returns 0 if the property isn't set.
+
+
+=head2 get_print_order()
+
+The C<get_print_order()> method returns 0 if the worksheet print "page order" is "Down then over" (the default) or 1 if it is "Over then down".
+
+    my $print_order = $worksheet->get_print_order();
+
+
+=head2 get_print_scale()
+
+The C<get_print_scale()> method returns the workbook scale for printing. The print scale fctor can be in the range 10 .. 400.
+
+    my $print_scale = $worksheet->get_print_scale();
+
+Returns 100 by default.
+
+
+=head2 get_fit_to_pages()
+
+The C<get_fit_to_pages()> method returns the number of pages wide and high that the printed worksheet page will fit to.
+
+    my ($pages_wide, $pages_high) = $worksheet->get_fit_to_pages();
+
+Returns (0, 0) if the property isn't set.
+
+
+=head2 is_portrait()
+
+The C<is_portrait()> method returns true if the worksheet has been set for printing in portrait mode.
+
+    my $is_portrait = $worksheet->is_portrait();
+
+Returns 0 if the worksheet has been set for printing in horizontal mode.
+
+
+=head2 is_centered_horizontally()
+
+The C<is_centered_horizontally()> method returns true if the worksheet has been centered horizontally for printing.
+
+    my $is_centered_horizontally = $worksheet->is_centered_horizontally();
+
+Returns 0 if the property isn't set.
+
+
+=head2 is_centered_vertically()
+
+The C<is_centered_vertically()> method returns true if the worksheet has been centered vertically for printing.
+
+    my $is_centered_vertically = $worksheet->is_centered_vertically();
+
+Returns 0 if the property isn't set.
+
+
+=head2 is_print_gridlines()
+
+The C<is_print_gridlines()> method returns true if the worksheet print "gridlines" option is turned on.
+
+    my $is_print_gridlines = $worksheet->is_print_gridlines();
+
+Returns 0 if the property isn't set.
+
+
+=head2 is_print_row_col_headers()
+
+The C<is_print_row_col_headers()> method returns true if the worksheet print "row and column headings" option is turned on.
+
+    my $is_print_row_col_headers = $worksheet->is_print_row_col_headers();
+
+Returns 0 if the property isn't set.
+
+
+=head2 is_print_black_and_white()
+
+The C<is_print_black_and_white()> method returns true if the worksheet print "black and white" option is turned on.
+
+    my $is_print_black_and_white = $worksheet->is_print_black_and_white();
+
+Returns 0 if the property isn't set.
+
+
+=head2 is_print_draft()
+
+The C<is_print_draft()> method returns true if the worksheet print "draft" option is turned on.
+
+    my $is_print_draft = $worksheet->is_print_draft();
+
+Returns 0 if the property isn't set.
+
+
+=head2 is_print_comments()
+
+The C<is_print_comments()> method returns true if the worksheet print "comments" option is turned on.
+
+    my $is_print_comments = $worksheet->is_print_comments();
+
+Returns 0 if the property isn't set.
+
+
+=head1 AUTHOR
+
+Maintainer 0.40+: John McNamara jmcnamara@cpan.org
+
+Maintainer 0.27-0.33: Gabor Szabo szabgab@cpan.org
+
+Original author: Kawai Takanori kwitknr@cpan.org
+
+=head1 COPYRIGHT
+
+Copyright (c) 2009-2010 John McNamara
+
+Copyright (c) 2006-2008 Gabor Szabo
+
+Copyright (c) 2000-2006 Kawai Takanori
+
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
+
+=cut
diff --git a/src/bach/build.bach/tools/preCfgGen.pl b/src/bach/build.bach/tools/preCfgGen.pl
new file mode 100644
index 0000000..2e0ee25
--- /dev/null
+++ b/src/bach/build.bach/tools/preCfgGen.pl
@@ -0,0 +1,292 @@
+#!/usr/bin/perl
+#
+#  Copyright Statement:
+#  --------------------
+#  This software is protected by Copyright and the information contained
+#  herein is confidential. The software may not be copied and the information
+#  contained herein may not be used or disclosed except with the written
+#  permission of MediaTek Inc. (C) 2006
+#
+#  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+#  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+#  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON
+#  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+#  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+#  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+#  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH
+#  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO
+#  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S
+#  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.
+#
+#  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE
+#  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+#  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+#  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO
+#  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+#
+#  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE
+#  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF
+#  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND
+#  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER
+#  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).
+#
+#*****************************************************************************
+#*
+#* Filename:
+#* ---------
+#*   cfgGen.pl
+#*
+#* Project:
+#* --------
+#*
+#*
+#* Description:
+#* ------------
+#*   This script generates the CFG file for flash tool 
+#*
+#* Author:
+#* -------
+#*   Cindy Tu
+#*
+#****************************************************************************/
+
+#****************************************************************************
+# Included Modules
+#****************************************************************************
+use strict;
+use Cwd qw(realpath);
+use File::Basename qw(dirname);
+#****************************************************************************
+# Constants
+#****************************************************************************
+my $CFGGEN_VERNO     = " m0.03";
+                      #  v0.03 , Update to support emigen
+                      #  v0.02 , Update for ASIC
+                      #  v0.01 , Update bootloader naming rule
+                      #  v0.00 , initial version 
+
+#****************************************************************************
+# Input Parameters and Global Variables
+#****************************************************************************
+my $BUILD_PATH    = $ARGV[0];
+my $BL_BIN        = $ARGV[1];
+my $MAKEFILE      = $ARGV[2];
+my $BB_PATH       = $ARGV[3];
+my $CC_CMD        = $ARGV[4];
+my $VIA_CMD       = $ARGV[5];
+my $OPTION_TMP    = $ARGV[6];
+my $TMP_FOLDER    = $ARGV[7];
+my $PRELOAD_BIN   = $ARGV[8];
+#my $NET_PATH      = $ARGV[8];
+#my $ACT_SIGN      = $ARGV[9];
+
+my $DebugPrint = 1;
+
+print "preCfggen: BUILD_PATH: $BUILD_PATH, BL_BIN: $BL_BIN, MAKEFILE: $MAKEFILE, BB_PATH: $BB_PATH, CC_CMD=$CC_CMD, VIA_CMD=$VIA_CMD, OPTION_TMP=$OPTION_TMP, Temp folder=$TMP_FOLDER\n" if ($DebugPrint == 1);
+
+#****************************************************************************
+# 1 >>> parse Project Make File
+#****************************************************************************
+my %MAKEFILE_OPTIONS;
+&Parse_Makefile();
+my $PLATFORM = $MAKEFILE_OPTIONS{'platform'};
+my $BOARD_VER = $ENV{"BOARD_VER"};
+my $BOARD_TYPE = $PLATFORM . "_" . $BOARD_VER;
+
+#****************************************************************************
+# 2 >>> generate the output cfg file for SV5
+#****************************************************************************
+my $CFG_FILE = $BUILD_PATH . "/" . $PLATFORM . "\.cfg";
+
+my $FILEPATH = realpath dirname $0;
+print "preCfggen: perl file path:$FILEPATH\n" if ($DebugPrint == 1);
+require "$FILEPATH/cfgGen_EMI.pl";
+
+### Bootloader naming
+
+if ($BL_BIN eq "")
+{
+	print "BL_BIN is NULL, set to default naming\n";
+	# Set default bootloader name
+	$BL_BIN = "bootloader.bin";
+} 
+
+open (CFG_FILE, ">$CFG_FILE") or &error_handler("$CFG_FILE: file error!", __FILE__, __LINE__);
+
+print CFG_FILE &gen_general_setting();
+print CFG_FILE &gen_boot_region_setting($BL_BIN, $PRELOAD_BIN);
+print CFG_FILE &gen_control_block_region_setting();
+print CFG_FILE &gen_main_region_setting();
+print CFG_FILE &gen_fs_region_setting();
+print CFG_FILE &gen_external_memory_setting($BB_PATH, $BOARD_TYPE, $PLATFORM, $CC_CMD, $VIA_CMD, $OPTION_TMP, $TMP_FOLDER, \%MAKEFILE_OPTIONS);
+print "preCfggen:$CFG_FILE is generated\n";
+
+### Sign
+# if ($ACT_SIGN eq "-sign")
+# {
+    # my $CFG_FILE_NET_PATH = $CFG_FILE;
+    # $CFG_FILE_NET_PATH =~ s/^\.\///;
+    # print "CFG_FILE_NET_PATH = $NET_PATH/$CFG_FILE_NET_PATH\n";
+    # if($^O eq "MSWin32")
+    # {
+        # system("\\\\mbjsap101\\Guardian_Programs\\Guardian\\bin\\Debug\\Guardian.exe $NET_PATH\\$CFG_FILE_NET_PATH");
+    # }
+    # else
+    # {
+        # system("/proj/wcp1sm/Guardian $NET_PATH/$CFG_FILE_NET_PATH");
+    # }
+# }
+
+
+exit;
+
+#****************************************************************************
+# parse Project Makefile
+#****************************************************************************
+sub Parse_Makefile
+{
+    my @filelist = ($MAKEFILE, undef);
+    my $keyname;
+
+    foreach my $filename (@filelist) {
+        print " filename: $filename\n" if ($DebugPrint == 1);
+        &error_handler("$filename: NOT exist!", __FILE__, __LINE__) if (!-e $filename);
+
+        open (FILE_HANDLE, "<$filename") or &error_handler("$filename: file error!", __FILE__, __LINE__);
+        while (<FILE_HANDLE>) {
+            if (/^(\w+)\s*=\s*(\S+)/) {
+                $keyname = lc($1);
+                defined($MAKEFILE_OPTIONS{$keyname}) && warn "$1 redefined in $MAKEFILE!\n";
+                $MAKEFILE_OPTIONS{$keyname} = $2; #uc($2);
+                if ($keyname eq 'internal_feature_option') {
+                    $filelist[1] = $2;
+                    $filelist[1] =~ s/\$WORKDIR/$ENV{WORKDIR}/;
+                }
+                if ($DebugPrint == 1) {
+                   print "$1:$2\n";
+                }
+            }
+        }
+        close (FILE_HANDLE);
+    }
+
+    $MAKEFILE_OPTIONS{'platform'} = $MAKEFILE_OPTIONS{'platform_chip'} or "MT6291";
+}
+
+#****************************************************************************
+# subroutine:  gen_general_setting
+# return:      General Setting
+#****************************************************************************
+sub gen_general_setting
+{
+    my $template = <<"__TEMPLATE";
+############################################################################################################
+#
+#  General Setting 
+#    
+############################################################################################################
+
+general:
+    config_version : alpha # config file version ("alpha", "beta" is used before SQC done.)
+                           # After SQC done, the version should be "1" for the first release version.
+    platform: $PLATFORM       # It is used for tool to identify the right setting for specific target
+
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  gen_boot_region_setting
+# return:      Boot Region Setting
+#****************************************************************************
+sub gen_boot_region_setting
+{   
+    (my $bl_bin, my $preload_bin) = @_;   	
+    my $template = <<"__TEMPLATE";
+############################################################################################################
+#
+#  Boot Region Setting
+#
+############################################################################################################
+
+boot_region:
+  alignment: block         # block[default], page(NAND:2K/512B, NOR: 1KB, eMMC: 512B, SF: 256B)
+  rom:
+    - file: $bl_bin
+    - file: $preload_bin
+
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  gen_control_block_region_setting
+# return:      Control Block Region Setting
+#****************************************************************************
+sub gen_control_block_region_setting
+{
+    my $template = <<"__TEMPLATE";
+############################################################################################################
+#
+#  Control Block Region Setting
+#
+############################################################################################################          
+
+control_block_region:
+  rom:
+
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  gen_main_region_setting
+# return:      Main Region Setting
+#****************************************************************************
+sub gen_main_region_setting
+{
+    my $template = <<"__TEMPLATE";
+############################################################################################################
+#
+#  Main Region Setting
+#
+############################################################################################################
+
+main_region:
+  alignment: block         # block[default], page(NAND:2K/512B, NOR: 1KB, eMMC: 512B, SF: 256B)
+  rom:
+
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  gen_fs_region_setting
+# return:      File System Region Setting
+#****************************************************************************
+sub gen_fs_region_setting
+{
+    my $template = <<"__TEMPLATE";
+############################################################################################################
+#
+#  File System Region Setting
+#
+############################################################################################################
+
+file_system_region:
+  rom:
+
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  error_handler
+# input:       $error_msg:     error message
+#****************************************************************************
+sub error_handler
+{
+    my ($error_msg, $file, $line_no) = @_;
+    
+    my $final_error_msg = "CFGGEN ERROR: $error_msg at $file line $line_no\n";
+    print $final_error_msg;
+    die $final_error_msg;
+}
+
diff --git a/src/bach/build.bach/tools/preCfgGen_sp.pl b/src/bach/build.bach/tools/preCfgGen_sp.pl
new file mode 100644
index 0000000..31659e6
--- /dev/null
+++ b/src/bach/build.bach/tools/preCfgGen_sp.pl
@@ -0,0 +1,123 @@
+#!/usr/bin/perl
+#
+#  Copyright Statement:
+#  --------------------
+#  This software is protected by Copyright and the information contained
+#  herein is confidential. The software may not be copied and the information
+#  contained herein may not be used or disclosed except with the written
+#  permission of MediaTek Inc. (C) 2006
+#
+#  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+#  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+#  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON
+#  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+#  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+#  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+#  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH
+#  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO
+#  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S
+#  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.
+#
+#  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE
+#  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+#  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+#  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO
+#  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+#
+#  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE
+#  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF
+#  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND
+#  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER
+#  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).
+#
+#*****************************************************************************
+#*
+#* Filename:
+#* ---------
+#*   cfgGen.pl
+#*
+#* Project:
+#* --------
+#*
+#*
+#* Description:
+#* ------------
+#*   This script generates the CFG file for flash tool 
+#*
+#* Author:
+#* -------
+#*   Cindy Tu
+#*
+#****************************************************************************/
+
+#****************************************************************************
+# Included Modules
+#****************************************************************************
+use strict;
+#****************************************************************************
+# Constants
+#****************************************************************************
+my $CFGGEN_VERNO     = " m0.01";
+
+#****************************************************************************
+# Input Parameters and Global Variables
+#****************************************************************************
+my $BUILD_PATH      = $ARGV[0];
+my $PROJECT_FLAVOR  = $ARGV[1];
+
+my $DebugPrint = 1;
+
+#****************************************************************************
+# 1 >>> parse Project Make File
+#****************************************************************************
+my $PLATFORM = "MT6735";
+my $BOARD_VER = $ENV{"BOARD_VER"};
+#****************************************************************************
+# 2 >>> generate the output cfg file for SV5
+#****************************************************************************
+my $CFG_FILE = $BUILD_PATH . "/" . $PLATFORM . "\.txt";
+
+open (CFG_FILE, ">$CFG_FILE") or &error_handler("$CFG_FILE: file error!", __FILE__, __LINE__);
+
+print CFG_FILE &gen_general_setting();
+
+exit;
+
+#****************************************************************************
+# subroutine:  gen_general_setting
+# return:      General Setting
+#****************************************************************************
+sub gen_general_setting
+{
+    my $template = <<"__TEMPLATE";
+############################################################################################################
+#
+#  General Setting 
+#    
+############################################################################################################
+- general: MTK_PLATFORM_CFG
+  info: 
+    - config_version: V1.1.2
+      platform: $PLATFORM
+      project: $PROJECT_FLAVOR
+      storage: EMMC
+      boot_channel: MSDC_0
+      block_size: 0x20000
+__TEMPLATE
+}
+
+#****************************************************************************
+# subroutine:  error_handler
+# input:       $error_msg:     error message
+#****************************************************************************
+sub error_handler
+{
+    my ($error_msg, $file, $line_no) = @_;
+    
+    my $final_error_msg = "CFGGEN ERROR: $error_msg at $file line $line_no\n";
+    print $final_error_msg;
+    die $final_error_msg;
+}
+
diff --git a/src/bach/build.bach/tools/project/fo_example.txt b/src/bach/build.bach/tools/project/fo_example.txt
new file mode 100644
index 0000000..fbde0e4
--- /dev/null
+++ b/src/bach/build.bach/tools/project/fo_example.txt
@@ -0,0 +1,13 @@
+
+# ----------------------------------------------------------------------------
+# # [NEW_FEATURE_NAME]                                   {#l1_new_feature_option}
+# ## description
+# > [NEW_FEATURE_NAME] is used to [FILL_IN_THE_FUNCTION].   \n
+#
+# value     | definition
+# :-------- | :--------------
+# VAL1      | DEF1
+# VAL2      | DEF2
+#
+# ## present value
+[NEW_FEATURE_NAME] = VAL1
diff --git a/src/bach/build.bach/tools/project/insert_paras.sh b/src/bach/build.bach/tools/project/insert_paras.sh
new file mode 100755
index 0000000..6201dbb
--- /dev/null
+++ b/src/bach/build.bach/tools/project/insert_paras.sh
@@ -0,0 +1,28 @@
+#/bin/bash
+
+insert_place=$1
+feature_info=$2
+
+function usage {
+	printf "./insert_paras.sh [INSERT_PLACE] [TXT_FILE_TO_INSERT] \n"
+	printf "[INSERT_PLACE]: The feature option that you want to insert after. \n"
+	printf "[TXT_FILE_TO_INSERT]: The paragraph that you want to insert for\
+	the new feature option.\n"
+}
+
+echo "Insert place: $1"
+echo "Inserted file: $2"
+
+if [ $# -ne 2 ]; then
+	echo "Wrong usage, it should be:"
+	usage
+	exit 1
+fi
+
+for entry in config/*
+do
+echo "Folder: $entry"
+sed -i "/$insert_place =/r $2" $entry/internal_feature_option
+done
+
+
diff --git a/src/bach/build.bach/tools/project/prj_crt.sh b/src/bach/build.bach/tools/project/prj_crt.sh
new file mode 100755
index 0000000..7588227
--- /dev/null
+++ b/src/bach/build.bach/tools/project/prj_crt.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+
+# usage: ./tools/backtrace/prj_crt.sh clone_prj new_prj"
+CLONE_PRJ=$1
+NEW_PRJ=$2
+# $3 is address list
+
+if [ "$#" -lt "2" ]; then
+    echo "Usage: $0 <clone project> <new project>"
+    exit
+fi
+
+RED_PAT="\033[37;41m"
+BLUE_PAT="\033[37;44m"
+YELLOW_PAT="\033[30;43m"
+RESET_PAT="\033[0m"
+
+# check firstly
+if [ ! -f "make/projects/${CLONE_PRJ}.prj" ] || [ ! -d "work/custom/${CLONE_PRJ}" ]; then
+    echo -e "${RED_PAT} Cannot find ${CLONE_PRJ} in project list!${RESET_PAT}"
+    exit
+fi
+
+# start to create project ...
+echo ""
+echo -e "Start to clone ${CLONE_PRJ} to ${NEW_PRJ}..."
+echo -e "Copy *.prj ..."
+rm -f make/projects/${NEW_PRJ}.prj
+cp -f make/projects/${CLONE_PRJ}.prj make/projects/${NEW_PRJ}.prj
+
+echo -e "Check rel mapping ..."
+relmap=`cat rel/mapping/prj_map | grep "${NEW_PRJ};"`
+if [ "$relmap" != "" ]; then
+    echo -e " ${BLUE_PAT}> Find in project mapping, no need to add new ... $result${RESET_PAT}"
+else
+    echo -e " ${RED_PAT}> Cannot find ${NEW_PRJ} in project mapping!${RESET_PAT}"
+    result=`cat rel/mapping/prj_map | grep "${CLONE_PRJ};"`
+    if [ "$result" != "" ]; then
+        echo -e " > try to clone one from ${CLONE_PRJ}"
+        relmap=`echo $result | sed "s/${CLONE_PRJ};/${NEW_PRJ};/g"`
+        echo -e " ${BLUE_PAT}> $relmap${RESET_PAT}"
+        echo $relmap >> rel/mapping/prj_map
+    else
+        echo -e " ${RED_PAT}> Still cannot find similar setting, please add manaually!${RESET_PAT}"
+        exit
+    fi
+fi
+
+echo -e "Clone rel folders ..."
+rm -fr rel/filter/${NEW_PRJ}
+cp -fr rel/filter/${CLONE_PRJ} rel/filter/${NEW_PRJ} 
+
+echo -e "Check internal feature option ..."
+BUILD_FDR=`cat work/custom/${CLONE_PRJ}/build/customer_feature_option | grep "INTERNAL_FEATURE_OPTION.*=" | grep "INTERNAL_FEATURE_OPTION.*=" | sed 's/.*\/\(build\..*\)\/config.*/\1/'`
+if [ "$BUILD_FDR" != "" ]; then
+    echo -e " ${BLUE_PAT}> Find $BUILD_FDR in cloned project ${CLOSE_PRJ}${RESET_PAT}"
+else
+    echo -e " ${RED_PAT}> Cannot find build.xxxx in cloned project!${RESET_PAT}"
+    exit
+fi
+
+echo -e "Clone internal feature option ..."
+rm -fr work/${BUILD_FDR}/config/${NEW_PRJ}
+cp -fr work/${BUILD_FDR}/config/${CLONE_PRJ} work/${BUILD_FDR}/config/${NEW_PRJ}
+
+echo -e "Clone custom folder ..."
+rm -fr work/custom/${NEW_PRJ}
+cp -fr work/custom/${CLONE_PRJ} work/custom/${NEW_PRJ}
+# replace the INTERNAL_FEATURE_OPTION
+sed -i "s/\/${CLONE_PRJ}\//\/${NEW_PRJ}\//g" work/custom/${NEW_PRJ}/build/customer_feature_option
+# check GPIO
+GPIO_FDR=work/custom/${NEW_PRJ}/gpio
+if [ -f "${GPIO_FDR}/${CLONE_PRJ}.csv" ]; then
+    # find GPIO file
+    echo -e " ${BLUE_PAT}> rename ${GPIO_FDR}/${CLONE_PRJ}.csv to ${GPIO_FDR}/${NEW_PRJ}.csv${RESET_PAT}"
+    mv ${GPIO_FDR}/${CLONE_PRJ}.csv ${GPIO_FDR}/${NEW_PRJ}.csv
+fi
+
+CFG=`echo $relmap | awk -F ';' '{print $2}'`
+SDK=`echo $relmap | awk -F ';' '{print $3}'`
+echo ""
+echo "----------------------------------------------------------"
+echo -e "${BLUE_PAT}${NEW_PRJ}${RESET_PAT} is cloned from ${CLONE_PRJ} done!"
+echo "Please check/modify and upload following files/folders/configurations ..."
+echo -e " ${YELLOW_PAT}> Config: ${CFG}"
+echo -e " ${YELLOW_PAT}> SDK: ${SDK}"
+echo -e " ${YELLOW_PAT}> make/${RESET_PAT}"
+mtk_repo status make
+echo -e " ${YELLOW_PAT}> rel/${RESET_PAT}"
+mtk_repo status rel
+echo -e " ${YELLOW_PAT}> work/${BUILD_FDR}/${RESET_PAT}"
+mtk_repo status work/${BUILD_FDR}
+echo -e " ${YELLOW_PAT}> work/custom/${RESET_PAT}"
+mtk_repo status work/custom
+echo "----------------------------------------------------------"
+echo ""
diff --git a/src/bach/build.bach/tools/project/replace_keyword.sh b/src/bach/build.bach/tools/project/replace_keyword.sh
new file mode 100644
index 0000000..68f8f99
--- /dev/null
+++ b/src/bach/build.bach/tools/project/replace_keyword.sh
@@ -0,0 +1,74 @@
+#usage: bash replace_keyword.sh [PROJECTS...], postfix 'prj' not needed
+#example: bash replace_keyword.sh MERCURY_FPGA_HOSTED
+cd ../../
+cp tools/project/replace_keyword.sh .
+
+declare -A rename_list replace_list 
+#module_list=(config make/projects tools build)
+#module_list=(work/slk work/lk work/linux-4.4 config work/base-files make/projects work/custom tools build work/bootloader_lte)
+#rename_list=([mercury]=mt6298 [MERCURY]=MT6298)
+#replace_list=([mercury]=mt6298 [MERCURY]=MT6298)
+#module_list+=(work/slk work/lk work/linux-4.4 config work/base-files make/projects work/custom tools build work/bootloader_lte work/busybox work/slk)
+module_list=(make work/mloader)
+rename_list=([mercury]=mt6298 [MERCURY]=MT6298)
+replace_list=([mercury]=mt6298 [MERCURY]=MT6298 [gen97p]=gen98 [GEN97P]=GEN98)
+
+# rename folders under $1 with naming $2 to $3
+function rename_folder {
+    retry=0
+    ret=0
+    echo -e "\033[0;32m"rename folders under $1 from $2 to $3 "\033[0m"
+    #retry for multiple matches, e.g., AAA/mercury/BBB/mercury/
+    while [ "$retry" != "5" ]; do 
+        find $1 -execdir rename "s/$2/$3/" '{}' \+
+        ret=$?
+        if [ "$ret" == "0" ]; then
+            break
+        fi
+        retry=$((retry + 1))
+        echo ret=$ret, retry=$retry
+    done
+}
+
+# replace all content under folder $1 from $2 to $3
+function replace_keyword {
+    echo -e "\033[0;32m"replace keyword $2 to $3 under folder $1"\033[0m"
+    grep -rl --exclude-dir=".git" $2 $1 | xargs sed -i "s/$2/$3/g"
+}
+
+function parse_module {
+    echo parse make/projects/$1.prj
+    module_list+=(`grep "p4:" make/projects/$1.prj | awk '{print "work/"$1}'`)
+}
+
+for proj in $@ ; do
+    parse_module "${proj}"
+done
+
+echo remove duplicate elements...
+sorted_module_list=($(echo "${module_list[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
+for module in "${!sorted_module_list[@]}"; do
+    for key in "${!rename_list[@]}"; do
+        rename_folder ${sorted_module_list[$module]} $key ${rename_list[$key]}
+    done
+    for key in "${!replace_list[@]}"; do
+        replace_keyword ${sorted_module_list[$module]} $key ${replace_list[$key]}
+    done
+done
+
+#uncomment to rollback
+: '
+for module in "${!sorted_module_list[@]}"; do
+    for key in "${!rename_list[@]}"; do
+        rename_folder ${sorted_module_list[$module]} ${rename_list[$key]} $key 
+    done
+    for key in "${!replace_list[@]}"; do
+        replace_keyword ${sorted_module_list[$module]} ${replace_list[$key]} $key 
+    done
+done
+'
+
+#replace old build script with modified ones
+cp build/* .
+#restore this script since it may also be modified
+mv replace_keyword.sh tools/project/.
diff --git a/src/bach/build.bach/tools/python/gen_gpio_code.py b/src/bach/build.bach/tools/python/gen_gpio_code.py
new file mode 100755
index 0000000..9df53e0
--- /dev/null
+++ b/src/bach/build.bach/tools/python/gen_gpio_code.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+#-*- coding: UTF-8 -*-
+
+import os.path
+import sys
+import argparse
+import re
+
+
+
+inputfile = 'V:\gpio\MT6297_EVB_MTK1353_Golden_Setting.csv'
+outpath = 'V:\gpio\\'
+final_setting = []
+label = {'number':0, 'name':1, 'usage':2, 'mode':3, 'drv':4, 'pullen':5, 'pullsel':6, 'dir':7, 'out':8, 'smt':9, 'ies':10, 'user':11, 'app':12}
+
+def setting_entry_process(setting):
+
+    entry = re.split(',', setting.upper())
+    #gpio number
+    temp_list = ['GPIO', entry[label['number']]]
+    entry[label['number']] = ''.join(temp_list)
+
+    #Use as EINT: 1 Use as GPIO: 0
+    if entry[label['usage']] == 'N':
+        entry[label['usage']] = 'USE_AS_GPIO'
+    else:
+        entry[label['usage']] = 'USE_AS_EINT'
+
+    #mode
+    #if isinstance(entry[label['mode']], int) is False:
+    temp_list = ['GPIO_MODE_0', entry[label['mode']]]
+    entry[label['mode']] = ''.join(temp_list)
+
+    #drive strength, default as 0
+    if entry[label['drv']] == 'DEFAULT':
+        entry[label['drv']] = 'DRV_DEFAULT_SETTING'
+    else:
+        temp_list = ['GPIO_DRV', entry[label['drv']]]
+        entry[label['drv']] = ''.join(temp_list)
+
+    #pull enable
+    if entry[label['pullen']] == 'Y':
+        entry[label['pullen']] = 'GPIO_PULL_ENABLE'
+    else:
+        entry[label['pullen']] = 'GPIO_PULL_DISABLE'
+    #pull high:1  pull low:0
+    if entry[label['pullsel']] == 'Y':
+        entry[label['pullsel']] = 'GPIO_PULL_UP'
+    else:
+        entry[label['pullsel']] = 'GPIO_PULL_DOWN'
+
+    #IO dir out:1  in:0
+    if entry[label['dir']] == 'OUT':
+        entry[label['dir']] = 'GPIO_DIR_OUT'
+    elif entry[label['dir']] == 'IN':
+        entry[label['dir']] = 'GPIO_DIR_IN'
+
+    #output high:1  low:0
+    if entry[label['out']] == 'NC':
+        entry[label['out']] = 'OUT_DEFAULT_SETTING'
+    elif entry[label['out']] == 'HIGH':
+        entry[label['out']] = 'GPIO_OUT_ONE'
+    elif entry[label['out']] == 'LOW':
+        entry[label['out']] = 'GPIO_OUT_ZERO'
+
+    #smt
+    if entry[label['smt']] == 'Y':
+        entry[label['smt']] = 'SMT_DEFAULT_SETTING'
+    else:
+        entry[label['smt']] = 'GPIO_SMT_DISABLE'
+
+    #ies
+    if entry[label['ies']] == 'Y':
+        entry[label['ies']] = 'IES_DEFAULT_SETTING'
+    else:
+        entry[label['ies']] = 'GPIO_IES_DISABLE'
+
+    #print entry
+    #print ''
+    #discard GPIO name, user, Application info
+    entry = entry[0:len(label)-2]
+    entry.pop(label['name'])
+    final_setting.append(entry)
+
+def gen_gpio_code(setting, outfile):
+    with open(os.path.abspath(outfile), 'w') as fw:
+
+        fw.write("#ifndef __GPIO_SETTING_H_\n")
+        fw.write("#define __GPIO_SETTING_H_\n\n")
+
+        fw.write("#define USE_AS_GPIO\t\t\t\t(1)\n")
+        fw.write("#define USE_AS_EINT\t\t\t\t(0)\n\n")
+
+        fw.write("#define DEFAULT_SETTING\t\t\t\t\t(-1)\n")
+        fw.write("#define DRV_DEFAULT_SETTING\t\t\t\t(DEFAULT_SETTING)\n")
+        fw.write("#define PULL_EN_DEFAULT_SETTING\t\t\t(DEFAULT_SETTING)\n")
+        fw.write("#define PULL_SEL_DEFAULT_SETTING\t\t(DEFAULT_SETTING)\n")
+        fw.write("#define OUT_DEFAULT_SETTING\t\t\t\t(DEFAULT_SETTING)\n")
+        fw.write("#define SMT_DEFAULT_SETTING\t\t\t\t(DEFAULT_SETTING)\n")
+        fw.write("#define IES_DEFAULT_SETTING\t\t\t\t(DEFAULT_SETTING)\n\n")
+
+        fw.write("struct gpio_setting {\n")
+        fw.write("\tuint32_t number;\n")
+        fw.write("\tuint32_t usage;\n")
+        fw.write("\tuint32_t mode;\n")
+        fw.write("\tuint32_t drv;\n")
+        fw.write("\tuint32_t pull_en;\n")
+        fw.write("\tuint32_t pull_sel;\n")
+        fw.write("\tuint32_t dir;\n")
+        fw.write("\tuint32_t out;\n")
+        fw.write("\tuint32_t smt;\n")
+        fw.write("\tuint32_t ies;\n")
+        fw.write("};\n\n")
+
+        fw.write("struct gpio_setting gpio_golden_table[] = {\n")
+        for entry in setting:
+            fw.write("\t{")
+            for val in entry:
+                fw.write("{0}, ".format(val))
+
+            fw.write("},\n")
+        fw.write("};\n")
+
+        fw.write("#endif")
+
+def main():
+    print("Apollo GPIO code generator:v0.1")
+    parser = argparse.ArgumentParser(description='Apollo GPIO generator')
+
+    parser.add_argument('--input', '-i', required=True,
+                        help='Provide the input file')
+    parser.add_argument('--output', '-o', required=False,
+                        help='Provide the output path')
+
+    args = parser.parse_args()
+
+    if not os.path.isfile(args.input):
+        #args.input = 'V:\gpio\MT6297_EVB_MTK1353_Golden_Setting.csv'
+        print ("Input file not exist.")
+        sys.exit(0)
+
+    if args.output is None or not os.path.isdir(args.output):
+        outpath = os.path.dirname(os.path.abspath(__file__))
+#         print "Output path set as:" ,outpath
+    else:
+        outpath = os.path.abspath(args.output)
+#         print "args.output:",args.output
+#         print "outpath:", outpath
+
+    with open(os.path.abspath(args.input), 'r') as fr:
+        print ("input file:",args.input)
+        for line in fr:
+            MatchObj = re.match('(.*):[ ]*(\w+)', line)
+            if MatchObj is not None:
+                filename = MatchObj.group(2)
+                print ("Golden setting table:", filename)
+                filename = 'gpio_golden'
+                temp = [os.path.join(outpath, filename), '.h']
+                outfile = ''.join(temp)
+                print ("output file:", outfile)
+            MatchObject = re.search('(?<=\d,)\w+', line)
+            if MatchObject is not None:
+                line = line.strip()
+                setting_entry_process(line)
+            else:
+                continue
+
+    gen_gpio_code(final_setting, outfile)
+
+if __name__ == '__main__':
+    main()
diff --git a/src/bach/build.bach/tools/qemu/Makefile b/src/bach/build.bach/tools/qemu/Makefile
new file mode 100644
index 0000000..eafcfdb
--- /dev/null
+++ b/src/bach/build.bach/tools/qemu/Makefile
@@ -0,0 +1,50 @@
+NAME	= qemu
+
+all: src patch config build install
+
+NUM_PROCESSORS=`cat /proc/cpuinfo | grep processor | wc -l`
+
+VER=2.11.1
+QEMUDIR=qemu-${VER}
+OUTDIR=bin-qemu-${VER}
+SRCPKG=${QEMUDIR}.tar.xz
+LKQEMUBIN=qemu-system-arm
+LXQEMUBIN=qemu-system-aarch64
+
+LKQEMUARGS="-machine virt -cpu cortex-a15 -m 64"
+LXQEMUARGS="-machine virt -cpu cortex-a53 -m 64"
+
+.PHONY: patch
+
+src:
+	@sh -c "if [ ! -d ${QEMUDIR} ]; then \
+		tar Jxvf ${SRCPKG}; \
+	fi"
+
+patch:
+
+config:
+	@sh -c "if [ -d ${QEMUDIR} ]; then \
+		cd ${QEMUDIR} && ./configure --target-list=arm-softmmu,aarch64-softmmu --disable-gtk --disable-tpm; \
+	fi"
+
+build:
+	@sh -c "if [ -d ${QEMUDIR} ]; then \
+		cd ${QEMUDIR} && molyq make -j${NUM_PROCESSORS}; \
+	fi"
+
+install:
+	@sh -c "if [ -d ${QEMUDIR} ]; then \
+		mkdir -p ${OUTDIR}; \
+		cp ${QEMUDIR}/arm-softmmu/qemu-system-arm ${OUTDIR}/; \
+		cp ${QEMUDIR}/aarch64-softmmu/qemu-system-aarch64 ${OUTDIR}/; \
+	fi"
+
+clean:
+	rm -fr ${QEMUDIR} config-temp config.log
+
+runlk:
+	mosesq `pwd`/${OUTDIR}/${LKQEMUBIN} ${LKQEMUARGS} -kernel ../../out/qemu/lk.bin -nographic
+
+runlinux:
+	mosesq `pwd`/${OUTDIR}/${LXQEMUBIN} ${LXQEMUARGS} -kernel ../../out/qemu/Image -initrd ../../out/qemu/initramfs.bin -nographic -append \"root=/dev/ramdisk console=ttyAMA0\"
diff --git a/src/bach/build.bach/tools/qemu/bin-qemu-2.11.1/qemu-system-aarch64 b/src/bach/build.bach/tools/qemu/bin-qemu-2.11.1/qemu-system-aarch64
new file mode 100755
index 0000000..85b4954
--- /dev/null
+++ b/src/bach/build.bach/tools/qemu/bin-qemu-2.11.1/qemu-system-aarch64
Binary files differ
diff --git a/src/bach/build.bach/tools/qemu/bin-qemu-2.11.1/qemu-system-arm b/src/bach/build.bach/tools/qemu/bin-qemu-2.11.1/qemu-system-arm
new file mode 100755
index 0000000..afb8f9e
--- /dev/null
+++ b/src/bach/build.bach/tools/qemu/bin-qemu-2.11.1/qemu-system-arm
Binary files differ
diff --git a/src/bach/build.bach/tools/qemu/efi-virtio.rom b/src/bach/build.bach/tools/qemu/efi-virtio.rom
new file mode 100644
index 0000000..f4de595
--- /dev/null
+++ b/src/bach/build.bach/tools/qemu/efi-virtio.rom
Binary files differ
diff --git a/src/bach/build.bach/tools/qemu/qemu-2.11.1.tar.xz b/src/bach/build.bach/tools/qemu/qemu-2.11.1.tar.xz
new file mode 100755
index 0000000..2631480
--- /dev/null
+++ b/src/bach/build.bach/tools/qemu/qemu-2.11.1.tar.xz
Binary files differ
diff --git a/src/bach/build.bach/tools/simg2img b/src/bach/build.bach/tools/simg2img
new file mode 100755
index 0000000..2f9530a
--- /dev/null
+++ b/src/bach/build.bach/tools/simg2img
Binary files differ
diff --git a/src/bach/build.bach/tools/ubiimgGen/mkfs.ubifs b/src/bach/build.bach/tools/ubiimgGen/mkfs.ubifs
new file mode 100755
index 0000000..7ac9ebe
--- /dev/null
+++ b/src/bach/build.bach/tools/ubiimgGen/mkfs.ubifs
Binary files differ
diff --git a/src/bach/build.bach/tools/ubiimgGen/ubiimgGen.pl b/src/bach/build.bach/tools/ubiimgGen/ubiimgGen.pl
new file mode 100644
index 0000000..f2498a2
--- /dev/null
+++ b/src/bach/build.bach/tools/ubiimgGen/ubiimgGen.pl
@@ -0,0 +1,95 @@
+#!/usr/bin/perl -w
+
+#use strict;
+use lib "tools/perl";
+use Spreadsheet::ParseExcel;
+
+# UBI tool
+my $MKUBIFS = "tools/ubiimgGen/mkfs.ubifs";
+my $UBINIZE = "tools/ubiimgGen/ubinize";
+
+#------------------------------------------------------------
+# global definition
+#------------------------------------------------------------
+my $CUSTOMDIR =   "$ENV{'WORKDIR'}/custom/$ENV{'PRJ_FILENAME'}";
+my $UBIDIR      = "$CUSTOMDIR/build/ubifs";
+my $OUTDIR      = "$ENV{'OUTDIR'}";
+my $UBIFSCFGDIR = "$OUTDIR/tmp/ubifs";
+my $IMGDIR      = "$ARGV[0]";
+my $PART_NAME   = "$ARGV[1]";
+my $BLK_KBSIZE  = $ARGV[2];
+my $PAGE_SIZE   = $ARGV[3] * 1024;
+# use zlib for compression
+my $COMP  = "zlib";
+
+print("$CUSTOMDIR\n");
+print("$UBIDIR\n");
+print("$OUTDIR\n");
+print("$IMGDIR\n");
+print("$PART_NAME\n");
+
+my $FileName  = "$CUSTOMDIR/build/flash_partition.xls";
+my $parser    = Spreadsheet::ParseExcel->new();
+my $workbook  = $parser->parse($FileName);
+die $parser->error(), ".\n" if ( !defined $workbook );
+my $find = 0;
+
+#------------------------------------------------------------
+# functions
+#------------------------------------------------------------
+sub system_cmd
+{
+    my $cmd = $_[0];
+    printf("$cmd\n");
+    system($cmd);
+}
+
+#------------------------------------------------------------
+# main function
+#------------------------------------------------------------
+# Iterate through all worksheets
+for my $worksheet ( $workbook->worksheets() ) {
+	# Find out the worksheet ranges
+	my ( $row_min, $row_max ) = $worksheet->row_range();
+	my ( $col_min, $col_max ) = $worksheet->col_range();
+
+    # Scan flash partition
+	for my $row ( $row_min .. $row_max ) {
+		next if ($row == 0);
+
+		# Return the cell object at $row and $col
+    	my $name = $worksheet->get_cell($row, 0)->value();
+       
+        # check partition 
+        if ("$name" eq "$PART_NAME") {
+    	    my $file = $worksheet->get_cell($row, 1)->value();
+    	    my $len  = $worksheet->get_cell($row, 2)->value();
+            printf("find ---> partition $name, $file\n");
+
+            # compute number of blocks in partition
+            my $ubi_blk_size = $BLK_KBSIZE * 1024 - 2 * $PAGE_SIZE;
+            my $nr_blk = ($len * 1024) / $BLK_KBSIZE;
+            system_cmd("$MKUBIFS -F -m $PAGE_SIZE -e $ubi_blk_size -c $nr_blk -r $IMGDIR -o $OUTDIR/${file}_raw.img -x $COMP");
+
+            # generate cfg and flashed image
+            if (!-d  "$UBIFSCFGDIR") {
+                printf("create output folder --> $UBIFSCFGDIR\n");
+                system_cmd("mkdir -p $UBIFSCFGDIR");
+            }
+            system_cmd("rm -f $UBIFSCFGDIR/$name.ubicfg");
+            system_cmd("cp -f $UBIDIR/ubifs_template.ubicfg $UBIFSCFGDIR/$name.ubicfg");
+            system_cmd("sed -i \"s|IMG_NAME|$OUTDIR\/${file}_raw.img|g\" $UBIFSCFGDIR/$name.ubicfg");
+            system_cmd("sed -i \"s|VOL_NAME|$name|g\" $UBIFSCFGDIR/$name.ubicfg");
+            system_cmd("${UBINIZE} -o $OUTDIR/$file -m $PAGE_SIZE -p ${BLK_KBSIZE}KiB -s $PAGE_SIZE -O $PAGE_SIZE $UBIFSCFGDIR/$name.ubicfg");
+            system_cmd("rm -f $OUTDIR/${file}_raw.img");
+
+            # we only need to generate one config file
+            $find = 1;
+            last;
+        }
+	}
+
+    if ($find == 1) {
+        last;
+    }
+}
diff --git a/src/bach/build.bach/tools/ubiimgGen/ubinize b/src/bach/build.bach/tools/ubiimgGen/ubinize
new file mode 100755
index 0000000..f51418b
--- /dev/null
+++ b/src/bach/build.bach/tools/ubiimgGen/ubinize
Binary files differ
diff --git a/src/bach/build.bach/tools/unyaffs2 b/src/bach/build.bach/tools/unyaffs2
new file mode 100755
index 0000000..eef696c
--- /dev/null
+++ b/src/bach/build.bach/tools/unyaffs2
Binary files differ