#!/usr/bin/python
#===========================================================================
     
#  This script parses "partition.xml" and creates numerous output files
#  specifically, partition.bin, rawprogram.xml

# REFERENCES

#  $Header: //source/qcom/qct/core/storage/tools/emmc/src/jsdcc/partition_load_pt/msp.py#18 $
#  $DateTime: 2011/08/09 20:02:16 $ 
#  $Author: ahughes $

# when          who     what, where, why 
# --------      ---     ------------------------------------------------------- 
# 2011-08-09    ah      -t option specifies output directory for files
# 2011-08-01    ah      Bug fix whhen patching DISK
# 2011-07-26    ah      Much better error messages
# 2011-07-21    ah      Major changes, now using getopt(), patching/writing can be all one step
# 2011-07-13    ah      Better error messages for user experience
# 2011-06-01    ah      Improved singleimage support for GPT
# 2011-05-26    ah      Handles sparse files and "zeroout" tag
# 2011-05-17    ah      Removed auto-patching MBR - must manually patch just like in GPT
# 2011-05-02    ah      No longer querying for /proc/partitions by default (user must ask now)
# 2011-03-22    ah      Enforcing that msp can't patch/write to partitions other than USER
# 2011-03-22    ah      Fixed memory bug with large file writing, fixed bug with SINGLEIMAGE creation (i.e. append)
# 2011-03-16    ah      Fixed memory bug with large file patching and read/write permissions for 
#                       SCons and singleimage.bin, Now need user input when patching DISK, "sync" command for linux
# 2011-03-09    ah      Replaces msp.exe - compatible with Linux and Windows
#                       does not need cygwin, cleaner code, more error checking
# 2011-02-14    ah      Changed 'dd' to 'cat' to avoid error happening on Win7
# 2010-10-26    ah      Now "patches" partition table on the fly, adds warnings before nuking drive
# 2010-10-22    ah      Used to replace msp.exe - can program cards and singleimages.bin

# Copyright (c) 2007-2010
# Qualcomm Incorporated.
# All Rights Reserved.
# Qualcomm Confidential and Proprietary
# ===========================================================================*/

import struct, os, sys, getopt
import math
import re
import codecs
from types import *
import time
import subprocess as sub

from xml.etree import ElementTree as ET
#from elementtree.ElementTree import ElementTree
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
from xml.dom import minidom

DiskSectors         =      0
SingleImageSize     =      0

BLOCK_SIZE          =      0x200
TABLE_ENTRY_0       =      0x1BE
TABLE_ENTRY_1       =      0x1CE
TABLE_ENTRY_2       =      0x1DE
TABLE_ENTRY_3       =      0x1EE

EMMCBLD_MIN_DISK_SIZE   = (4*1024*1024) # 4MB

MAX_FILE_SIZE_BEFORE_SPLIT = (10*1024*1024)
#MAX_FILE_SIZE_BEFORE_SPLIT = (2048) # testing purposes

ExtendedPartitionBegins = 0
bytes_read              = 0
FileNotFoundShowWarning = 0

print "\nmsp.py is running from CWD: ", os.getcwd(), "\n"


SECTOR_SIZE = 512
disk_size = None

def ReadSectors(opfile,NumSectors):
    try:
        return opfile.read(NumSectors*512)
    except:
        print "ERROR: Could not complete the read"
        sys.exit()

def EnsureDirectoryExists(filename):
    dir = os.path.dirname(filename)

    try:
        os.stat(dir)
    except:
        os.makedirs(dir)

def PrintBigError(sz):
    print "\t _________________ ___________ "
    print "\t|  ___| ___ \\ ___ \\  _  | ___ \\"
    print "\t| |__ | |_/ / |_/ / | | | |_/ /"
    print "\t|  __||    /|    /| | | |    / "
    print "\t| |___| |\\ \\| |\\ \\\\ \\_/ / |\\ \\ "
    print "\t\\____/\\_| \\_\\_| \\_|\\___/\\_| \\_|\n"

    if len(sz)>0:
        print sz
        sys.exit(1)
		
def PrettyPrintArray(bytes_read):
    Bytes = struct.unpack("%dB" % len(bytes_read),bytes_read)

    for k in range(len(Bytes)/512):
        print "-"*78
        for j in range(32):
            for i in range(16):
                sys.stdout.write("%.2X " % Bytes[i+j*16])
    
            sys.stdout.write("\t")
    
            for i in range(16):
                sys.stdout.write("%c" % Bytes[i+j*16])
            print " "
    print " "

def Zerout(disk_name,start_sector,num_sectors):
    global verbose
    

    verbose = True

    if verbose:
        print "\nIn Zeroout"
        print "Attempting to open %s" % disk_name
    try:
        opfile = open(disk_name, "r+b")
    except:
        print "ERROR: (Zerout) Could not open %s, cwd=%s" % (disk_name,os.getcwd() )

        if sys.platform.startswith("linux"):
            print "Did you remember to use SUDO?\n"
        else:
            print "\t  ___      _           _       _     _             _                ___  "
            print "\t / _ \\    | |         (_)     (_)   | |           | |              |__ \\ "
            print "\t/ /_\\ \\ __| |_ __ ___  _ _ __  _ ___| |_ _ __ __ _| |_  ___  _ __     ) |"
            print "\t|  _  |/ _` | '_ ` _ \\| | '_ \\| / __| __| '__/ _` | __|/ _ \\| '__|   / / "
            print "\t| | | | (_| | | | | | | | | | | \\__ \\ |_| | | (_| | |_| (_) | |     |_|  "
            print "\t\\_| |_/\\__,_|_| |_| |_|_|_| |_|_|___/\\__|_|  \\__,_|\\__|\\___/|_|     (_)  \n\n"
                                                                         
            print "Did you remember to be in Administrator Mode for Win7?\n"
        sys.exit()


    if start_sector<0:
        # Means do this NUM_DISK_SECTORS - start_sector
        start_sector = (AvailablePartitions[disk_name]/512) + start_sector # remember start_sector is negative, so add it

    if verbose:
        print "Moving to sector %i" % start_sector
    try:
        opfile.seek(int(start_sector*512))
    except:
        print "ERROR: Could not move to sector %d in %s" % (start_sector,disk_name)
        sys.exit()

    temp = [0]*(512*num_sectors)
    bytes_read = struct.pack("%iB"%(512*num_sectors),*temp)
    if verbose:
        print "Writing %i zeros" % len(temp)
    try:
        opfile.write(bytes_read)
    except:
        print "ERROR: Could not write at sector %d in %s" % (Zeroout['start_sector'],disk_name)
        sys.exit()

    try:
        opfile.close()
    except:
        print "\tWARNING: Could not close %s" % disk_name
        pass


def external_call(command, capture_output=True):
    errors = None
    output = None
    try:
        if capture_output:
            p = sub.Popen(command, stdout=sub.PIPE, stderr=sub.PIPE)
            output, errors = p.communicate()
        else:
            os.system(command)
    except Exception, e:
        print "Error executing command %s. %s" % (str(command), e)
        #clean_up()
        sys.exit(1)
    finally:
        #if not output is None:
        #    print "Result: %s" % output
        if (not errors is None) and (not errors == ""):
            print "Process stderr: %s" % errors
    return output

def ParseXML(xml_filename):
    global PartitionArray, PatchArray, ZeroOutArray,MinDiskSize

    root = ET.parse( xml_filename )
    #Create an iterator
    iter = root.getiterator()
    for element in iter:
        #print "\nElement:" , element.tag
        # Parse out include files
        if element.tag=="program":
            #print "\nFound a partition to program"

            if element.keys():
                ##print "\tAttributes:"
                Partition = {}
                for name, value in element.items():
                    #print "\t\tName: '%s'=>'%s' " % (name,value)
                    Partition[name]=value

            ##print "Partition['start_sector']=",Partition['start_sector']

            m = re.search("NUM_DISK_SECTORS-(\d+)", Partition['start_sector'])
            if type(m) is not NoneType:
                if DiskSize > 0 :
                    Partition['start_sector']           = int((DiskSize/512)-int(m.group(1)))
                else:
                    Partition['start_sector']           = int((EMMCBLD_MIN_DISK_SIZE/512)-int(m.group(1)))
            else:
                Partition['start_sector']           = int(Partition['start_sector'])

            if 'num_sectors' in Partition:
                Partition['num_sectors']            = int(Partition['num_sectors'])
            else:
                Partition['num_sectors']            = int(Partition['num_partition_sectors'])

            if 'offset' in Partition:
                Partition['offset']                 = int(Partition['offset'])
            else:
                Partition['offset']                 = int(Partition['file_sector_offset'])

            Partition['physical_partition_number']  = int(Partition['physical_partition_number'])

            PartitionArray.append( Partition )

        elif element.tag=="patch":
            #print "\nFound a PATCH to program"

            if element.keys():
                #print "\n\tAttributes:"
                Patch = {}
                for name, value in element.items():
                    #print "\t\tName: '%s'=>'%s' " % (name,value)
                    Patch[name]=value

                Patch['function']           = "none"
                Patch['arg0']               = 0
                Patch['arg1']               = 0


                m = re.search("NUM_DISK_SECTORS-(\d+)", Patch['start_sector'])
                if type(m) is not NoneType:
                    Patch['start_sector'] = Patch['start_sector'].replace("NUM_DISK_SECTORS-%s"%m.group(1),str(int((DiskSize/512)-int(m.group(1)))))

                m = re.search("NUM_DISK_SECTORS-(\d+)", Patch['value'])
                if type(m) is not NoneType:
                    # if we are patching, then at this point NUM_DISK_SECTORS could have been zero
                    # In that case, set to EMMCBLD_MIN_DISK_SIZE
                    if DiskSize>0:
                        Patch['value'] = Patch['value'].replace("NUM_DISK_SECTORS-%s"%m.group(1),str(int((DiskSize/512)-int(m.group(1)))))
                    else:
                        Patch['value'] = Patch['value'].replace("NUM_DISK_SECTORS-%s"%m.group(1),str(int(EMMCBLD_MIN_DISK_SIZE/512)-int(m.group(1))))

                Patch['start_sector']   = int(float(Patch['start_sector']))

                m = re.search("CRC32\((\d+).?,(\d+).?\)", Patch['value'])
                if type(m) is not NoneType:
                    Patch['value']          = 0
                    Patch['function']       = "CRC32"
                    Patch['arg0']           = int(float(m.group(1)))   # start_sector
                    Patch['arg1']           = int(float(m.group(2)))   # len_in_bytes

                ##print Patch['value']

                Patch['value']              = int(float(Patch['value']))
                Patch['byte_offset']        = int(float(Patch['byte_offset']))
                Patch['size_in_bytes']      = int(float(Patch['size_in_bytes']))
                Patch['byte_offset']        = int(float(Patch['byte_offset']))

                Patch['physical_partition_number']  = int(float(Patch['physical_partition_number']))

                if Patching=="DISK":
                    if Patch['filename']=="DISK":
                        PatchArray.append( Patch )
                if Patching=="FILES":
                    if Patch['filename']!="DISK":
                        PatchArray.append( Patch )

        elif element.tag=="zeroout":
            #print "\nFound a zeroout to program"

            if element.keys():
                #print "\n\tAttributes:"
                Zeroout = {}
                for name, value in element.items():
                    #print "\t\tName: '%s'=>'%s' " % (name,value)
                    Zeroout[name]=value

                    # <zeroout label="Wipe_GPT_Backup" num_partition_sectors="1" physical_partition_number="0" start_sector="NUM_DISK_SECTORS-1."/>

                m = re.search("NUM_DISK_SECTORS-(\d+)", Zeroout['start_sector'])
                if type(m) is not NoneType:
                    Zeroout['start_sector'] = Zeroout['start_sector'].replace("NUM_DISK_SECTORS-%s"%m.group(1),str(int((DiskSize/512)-int(m.group(1)))))

                Zeroout['start_sector']   = int(float(Zeroout['start_sector']))
                Zeroout['physical_partition_number']  = int(float(Zeroout['physical_partition_number']))

                ZeroOutArray.append( Zeroout )
                #print Zeroout
                #print "\n"


def ReturnArrayFromCommaSeparatedList(sz):
    temp = re.sub("\s+|\n"," ",sz)
    temp = re.sub("^\s+","",temp)
    temp = re.sub("\s+$","",temp)
    return temp.split(',')

def find_file(filename, search_paths):
    print "\n\n\tLooking for",filename
    print "\t"+"-"*40
    for x in search_paths:
        print "\tSearching ",x
        temp = os.path.join(x, filename)
        if os.path.exists(temp):
            print "\n\t**Found %s (%i bytes)" % (temp,os.path.getsize(temp))
            return temp

    ## search cwd last
    print "\tSearching ",os.getcwd()
    if os.path.exists(filename):
        print "\n\t**Found %s (%i bytes)" % (filename,os.path.getsize(filename))
        return filename

    print "\tCound't find file\n"
    return None


def DoubleCheckDiskSize():

    if os.path.basename(Filename)=="singleimage.bin":
        return

    if sys.platform.startswith("win"):
        print "\n\nTesting of OS detected disk size correctly...\n"
    
        Size = AvailablePartitions[Filename]
    
        TrueSize = Size
    
        count = 0
        # Windows workaround to get the correct number of sectors
        fp = open(Filename, 'rb')
        fp.seek(int(Size))
        try:
            while True:
                fp.read(SECTOR_SIZE)

                if count % 128 == 0:
                    sys.stdout.write(".")

                count += 1

        except:
            TrueSize = fp.tell()
        fp.close()
    
        if TrueSize != Size and Size<=(64*1024*1024*1024):
            print "\n\t                          _             "
            print "\t                         (_)            "
            print "\t__      ____ _ _ __ _ __  _ _ __   __ _ "
            print "\t\\ \\ /\\ / / _` | '__| '_ \\| | '_ \\ / _` |"
            print "\t \\ V  V / (_| | |  | | | | | | | | (_| |"
            print "\t  \\_/\\_/ \\__,_|_|  |_| |_|_|_| |_|\\__, |"
            print "\t                                   __/ |"
            print "\t                                  |___/ \n\n"
            print "NOTE: This OS has *not* detected the correct size of the disk"
            print "\nSECTORS: Size=%i, TrueSize=%i, Difference=%i sectors (%s)" % (Size/512,TrueSize/512,(TrueSize-Size)/512,ReturnSizeString(TrueSize-Size))
            print "This means the backup GPT header will *not* be located at the true last sector"
            print "This is only an issue if you care :) It will be off by %s" % ReturnSizeString(TrueSize-Size)
            print "\nNOTE: This program *can't* write to the end of the disk, OS limitation"
        else:
            print "\n\nAll is well\n"
    


def PerformWrite():
    global PartitionSorted, LoadSubsetOfFiles, search_paths, interactive, SingleImageSize, Filename

    print "\t                                                     _            "
    print "\t                                                    (_)            "
    print "\t _ __  _ __ ___   __ _ _ __ __ _ _ __ ___  _ __ ___  _ _ __   __ _ "
    print "\t| '_ \\| '__/ _ \\ / _` | '__/ _` | '_ ` _ \\| '_ ` _ \\| | '_ \\ / _` |"
    print "\t| |_) | | | (_) | (_| | | | (_| | | | | | | | | | | | | | | | (_| |"
    print "\t| .__/|_|  \\___/ \\__, |_|  \\__,_|_| |_| |_|_| |_| |_|_|_| |_|\\__, |"
    print "\t| |               __/ |                                       __/ |"
    print "\t|_|              |___/                                       |___/ "

    for Partition in PartitionSorted:   # Here Partition is a *sorted* entry from rawprogram.xml
        if Partition['physical_partition_number']!=0:   ## msp tool can only write to PHY partition 0
            print "Skipping '%s' for physical_partition_number=%d (only 0 is accessible)" % (Partition['filename'],Partition['physical_partition_number'])
        else:

            if len(Partition['filename'])==0:
                continue

            if LoadSubsetOfFiles is True:
                # To be here means user only wants some of the files loaded from rawprogram0.xml
                if Partition['filename'] in file_list:
                    #print "LOAD: '%s' was specified to be programmed" % Partition['filename']
                    pass
                else:
                    #print "SKIPPING: '%s', it was not specified to be programmed" % Partition['filename']
                    continue


            print "\n"+"="*78
            print "="*78
            FileWithPath = find_file(Partition['filename'], search_paths)

            size=0
            if FileWithPath is not None:
                size = os.path.getsize(FileWithPath)


            # to be here the rawprogram.xml file had to have a "filename" entry
            print "\n'%s' (%s) to partition '%s' at sector %d (at %s)\n" % (Partition['filename'],ReturnSizeString(size),Partition['label'],Partition['start_sector'],ReturnSizeString(Partition['start_sector']*512))

            if interactive is True:
                loadfile = raw_input("Do you want to load this file? (Y|n|q)")
                if loadfile=='Y' or loadfile=='y' or loadfile=='':
                    pass
                elif loadfile=='q' or loadfile=='Q':
                    sys.exit()
                else:
                    continue

            while FileWithPath is None:
                FileNotFoundShowWarning = 1

                print "\t______      _   _        ___  "
                print "\t| ___ \\    | | | |      |__ \\ "
                print "\t| |_/ /__ _| |_| |__       ) |"
                print "\t|  __// _` | __| '_ \\     / / "
                print "\t| |  | (_| | |_| | | |   |_|  "
                print "\t\\_|   \\__,_|\\__|_| |_|   (_)  \n\n"

                print "ERROR: '%s' listed in '%s' not found\n" % (Partition['filename'],rawprogram_filename)

                print "Please provide a path for this file"
                print "Ex. \\\\somepath\\folder OR c:\\somepath\\folder\n"
                temppath = raw_input("Enter PATH or Q to quit? ")
                if temppath=='Q' or temppath=='q' or temppath=='':
                    sys.exit()

                print "\n"

                FileWithPath = find_file(Partition['filename'], [temppath])

                size=0
                if FileWithPath is not None:
                    size = os.path.getsize(FileWithPath)

                    temp = raw_input("\nShould this path be used to find other files? (Y|n|q)")
                    if temp=='Q' or temp=='q':
                        sys.exit()
                    elif temp=='Y' or temp=='y' or temp=='':
                        search_paths.append(temppath)
                    print "\n"

            if size==0:
                loadfile = raw_input("WARNING: This file is 0 bytes, do you want to load this file? (y|N|q)")
                if loadfile=='N' or loadfile=='n' or loadfile=='':
                    continue
                elif loadfile=='q' or loadfile=='Q':
                    sys.exit()
                else:
                    pass


            if Partition['num_sectors']==0:
                Partition['num_sectors'] = int(size/512)
                if size%512 != 0:
                    Partition['num_sectors']+=1 # not an even multiple of 512, so ++

            ##print "At start_sector %i (%.2fKB) write %i sectors" % (Partition['start_sector'],Partition['start_sector']/2.0,Partition['num_sectors'])
            ##print "\tsize of \"%s\" is %i bytes" % (Partition['filename'],size)
            ##print "\tsize of partition listed in in \"%s\" is %i bytes" % (rawprogram_filename,Partition['num_sectors']*512)

            ## This below happens on files like partition0.bin, where they hold the entire partition table, 
            ## but, only MBR is meant to be written, thus partition0.bin is 9 sectors but MBR is only 1 sector

            if size > (Partition['num_sectors']*512):
                size = Partition['num_sectors']*512

            ##print "\tAttempting to read %i bytes from \n\t\"%s\" at offset %i" % (size,Partition['filename'],Partition['offset'])
            #os.getcwd()+"\\"+

            try:
                ipfile = open(FileWithPath, "rb")
            except:
                print "ERROR: Could not open FileWithPath=%s, cwd=%s" % (Partition['filename'], os.getcwd() )
                sys.exit()


            print "\tAttempting to move to sector %i (file offset) in %s" % (Partition['offset'],Partition['filename'])
            try:
                ipfile.seek(int(Partition['offset']*512))
            except:
                print "ERROR: Could not move to sector %d in %s" % (Partition['offset'],Partition['filename'])
                sys.exit()

            print "\tAttempting to read %i bytes" % (size)
            try:
                if size<MAX_FILE_SIZE_BEFORE_SPLIT:
                    bytes_read = ipfile.read(size)
                else:
                    print "File is too large to read all at once, must be broken up"
            except:
                print "ERROR: Could not read %d bytes in %s" % (size,Partition['filename'])
                sys.exit()

            if size<MAX_FILE_SIZE_BEFORE_SPLIT:
                ipfile.close()

            if size<MAX_FILE_SIZE_BEFORE_SPLIT:
                print "\tSuccessfully read %d bytes of %d bytes and closed %s" % (len(bytes_read),size,Partition['filename'])

                Remainder = len(bytes_read)%512

                if Remainder != 0:
                    print "\tbytes_read is not a multiple of 512, appending zeros"

                    Bytes = struct.unpack("%dB" % len(bytes_read),bytes_read)    ## unpack returns list, so get index 0

                    Temp         = list(Bytes) + [0x00]*(512-Remainder)   # concat
                    TotalSize = len(Temp)

                    bytes_read = struct.pack("%dB" % TotalSize, *Temp)
                    print "\tNow len(bytes_read)=%d" % len(bytes_read)

                # At this point bytes_read is a multiple of 512

            #print "\t\tNeed to move to location %i (%i bytes) in %s" % (Partition['start_sector'],(Partition['start_sector']*512),Filename)
            #print "\t\tPartition['start_sector']*512 = %i" % (Partition['start_sector']*512)

            if (2*DiskSize)<Partition['start_sector'] and os.path.basename(Filename)!="singleimage.bin":
                print "2*DiskSize=%d" % (2*DiskSize)
                print "Partition['start_sector']=",Partition['start_sector']
                print "\n\nERROR: Attempting to move to sector %i (%.2f MB) and only %i sectors (%.2f MB) exist (%i difference (%.2f MB) )" % (Partition['start_sector'],Partition['start_sector']/2048.0,2*DiskSize,DiskSize/1024.0,Partition['start_sector']-(2*DiskSize),(Partition['start_sector']-(2*DiskSize))/2048.0)
                sys.exit()

            try:
                if os.path.basename(Filename)=="singleimage.bin":
                    Filename = OutputFolder+os.path.basename(Filename)

                opfile = open(Filename, "r+b")  ## Filename = '\\.\PHYSICALDRIVE1'
                #print "Opened '%s', cwd=%s" % (Filename, os.getcwd() )
            except:
                print "ERROR: Could not open Filename=%s, cwd=%s" % (Filename, os.getcwd() )

                if sys.platform.startswith("linux"):
                    print "\tDon't forget you need SUDO with this program"
                    print "\tsudo python msp.py partition.xml /dev/sdx (where x is the device node)"
                else:
                    print "\t  ___      _           _       _     _             _                ___  "
                    print "\t / _ \\    | |         (_)     (_)   | |           | |              |__ \\ "
                    print "\t/ /_\\ \\ __| |_ __ ___  _ _ __  _ ___| |_ _ __ __ _| |_  ___  _ __     ) |"
                    print "\t|  _  |/ _` | '_ ` _ \\| | '_ \\| / __| __| '__/ _` | __|/ _ \\| '__|   / / "
                    print "\t| | | | (_| | | | | | | | | | | \\__ \\ |_| | | (_| | |_| (_) | |     |_|  "
                    print "\t\\_| |_/\\__,_|_| |_| |_|_|_| |_|_|___/\\__|_|  \\__,_|\\__|\\___/|_|     (_)  \n\n"

                    print "\n"+"-"*78
                    print "\tThis program needs to be run as Administrator!!"
                    print "-"*78+"\n"
                    print "-"*78
                    print "\tTo fix, you must open a CMD prompt with \"Run as administrator\""
                    print "-"*78+"\n"

                sys.exit()

            print "\n\tOpened %s" % Filename


            if Partition['start_sector'] < 0:
                print "start sector is less than 0 - skipping this instruction, most likely for GPT HACK"
                continue

            if Partition['start_sector'] > int(DiskSize/512):
                print "\nERROR: Start sector is BIGGER than the disk"
                print "\nERROR: Your device is TOO SMALL to handle this partition info"
                sys.exit(1)

            try:
                opfile.seek(int(Partition['start_sector']*512))
            except:
                print "ERROR: Could not move to sector %d on %s" % (Partition['start_sector'],Filename)
                sys.exit()

            print "\tMoved to sector %d on %s" % (Partition['start_sector'],Filename)

            ##print "size=",size
            ##print "MAX_FILE_SIZE_BEFORE_SPLIT=",MAX_FILE_SIZE_BEFORE_SPLIT

            if size<MAX_FILE_SIZE_BEFORE_SPLIT:
                print "\tFile can be written completely."
                try:
                    opfile.write(bytes_read)
                except:
                    print "ERROR: Could not write to %s" % (Filename)
                    print "\nPlease try removing the medium and re-inserting it"
                    print "Strangly this helps sometimes after writing *new* partition tables\n\n"
                    sys.exit()

            else:
                ## To be here means I need to break up the file
                ##print "I need to break up this file"

                TempSize = size
                NumLoop = int(TempSize/MAX_FILE_SIZE_BEFORE_SPLIT)
                Remainder = size%MAX_FILE_SIZE_BEFORE_SPLIT
                if Remainder>0:
                    Remainder=1

                print "\nNeed to break up this file, will loop %d times writing %i bytes each time" % (NumLoop+Remainder,MAX_FILE_SIZE_BEFORE_SPLIT)
                TempSize=0
                for a in range(NumLoop):
                    #print "read %i bytes" % MAX_FILE_SIZE_BEFORE_SPLIT
                    try:
                        bytes_read = ipfile.read(MAX_FILE_SIZE_BEFORE_SPLIT)
                    except:
                        print "ERROR: Could not read from %s" % (FileWithPath)
                        sys.exit()

                    ##print "\n\t%i) Packing %i Bytes [%i:%i]" % (a+1,MAX_FILE_SIZE_BEFORE_SPLIT,TempSize,TempSize+MAX_FILE_SIZE_BEFORE_SPLIT)
                    ##bytes_read = struct.pack("%dB" % MAX_FILE_SIZE_BEFORE_SPLIT, *Bytes[TempSize:(TempSize+MAX_FILE_SIZE_BEFORE_SPLIT)])
                    print "\t%.2i) Writing %i Bytes [%i:%i]" % (a+1,MAX_FILE_SIZE_BEFORE_SPLIT,TempSize,TempSize+MAX_FILE_SIZE_BEFORE_SPLIT)
                    try:
                        opfile.write(bytes_read)
                    except:
                        print "ERROR: Could not write to %s" % (Filename)
                        sys.exit()

                    TempSize += MAX_FILE_SIZE_BEFORE_SPLIT
                ##print "Out of loop"
                a+=1    

                if Remainder == 1:
                    # Need to PAD the file to be a multiple of 512 bytes too
    
                    #print "\n\t%i) Packing %i Bytes [%i:%i]" % (a,(len(Bytes)-TempSize),TempSize,len(Bytes))
                    #bytes_read = struct.pack("%dB" % (len(Bytes)-TempSize), *Bytes[TempSize:len(Bytes)])
    
                    print "\t%.2i) Writing %i Bytes [%i:%i]" % (a+1,(size-TempSize),TempSize,size)
                    try:
                        bytes_read = ipfile.read(size-TempSize)
                    except:
                        print "ERROR: Could not read from %s" % (FileWithPath)
                        sys.exit()
    
                    ##print "len(bytes_read)=",len(bytes_read)
    
                    Remainder = len(bytes_read)%512
    
                    if Remainder != 0:
                        print "\tbytes_read is not a multiple of 512, appending zeros"
    
                        Bytes = struct.unpack("%dB" % len(bytes_read),bytes_read)    ## unpack returns list, so get index 0
    
                        Temp         = list(Bytes) + [0x00]*(512-Remainder)   # concat
                        TotalSize = len(Temp)
    
                        bytes_read = struct.pack("%dB" % TotalSize, *Temp)
                        print "\tNow len(bytes_read)=%d" % len(bytes_read)
    
                    # At this point bytes_read is a multiple of 512
    
                    #print "This is the final write"
                    try:
                        opfile.write(bytes_read)
                    except:
                        print "ERROR: Could not write to %s" % (Filename)
                        sys.exit()

                ipfile.close()

            if os.path.basename(Filename)=="singleimage.bin":
                print "\tSingleImageSize before = ", SingleImageSize
                SingleImageSize+=int(len(bytes_read)/512)
                print "\tSingleImageSize after = ", SingleImageSize

            #print "\tWrote %d bytes at sector %d on %s" % (len(bytes_read),Partition['start_sector'],Filename)

            try:
                ##print opfile
                opfile.close()
            except:
                print "\tWARNING: Can't close the file?"
                #sys.exit()
                pass


            print "\n\tWritten with"
            print "\tpython dd.py --if=%s --bs=512 --count=%i --seek=%i --of=%s" % (FileWithPath,int((size-1)/SECTOR_SIZE)+1,Partition['start_sector'],Filename)
            print "\n\tVerify with"
            print "\tpython dd.py --if=%s --bs=512 --count=%i --skip=%i --of=dump.bin" % (Filename,int((size-1)/SECTOR_SIZE)+1,Partition['start_sector'])

            print "\n\tSuccessfully wrote \"%s\" (%s payload) to %s" % (Partition['filename'],ReturnSizeString(size),Filename)

            #raw_input("Enter something: ")

            #if Partition['filename']=="sbl3.mbn":
            #    sys.exit()
    print "\nDone Writing Files\n"

def GetPartitions():
    global Devices,AvailablePartitions
    if sys.platform.startswith("linux"):
        print "This is a linux system since sys.platform='%s'" % sys.platform

        print "-"*78    
        print "\tRemember - DON'T FORGET SUDO"
        print "\tRemember - DON'T FORGET SUDO"
        print "\tRemember - DON'T FORGET SUDO"
        print "-"*78+"\n"

        os.system("cat /proc/partitions > temp_partitions.txt")
        IN = open("temp_partitions.txt")
        output = IN.readlines()
        for line in output:
            #print line

            m = re.search("(\d+) (sd[a-z])$", line)
            if type(m) is not NoneType:
                Size    = int(m.group(1))
                Device  = "/dev/"+m.group(2)
                #print "%s\tSize=%d,%.1fMB (%.2fGB) (%iKB)" % (Device,Size,int(Size)/1024.0,int(Size)/(1024.0*1024.0),int(Size))
                AvailablePartitions[Device] = Size*1024.0    # linux reports in terms of 1024, 


    else:
        ##print "This is a windows system since sys.platform='%s'" % sys.platform

        print "\t  ___      _           _       _     _             _                ___  "
        print "\t / _ \\    | |         (_)     (_)   | |           | |              |__ \\ "
        print "\t/ /_\\ \\ __| |_ __ ___  _ _ __  _ ___| |_ _ __ __ _| |_  ___  _ __     ) |"
        print "\t|  _  |/ _` | '_ ` _ \\| | '_ \\| / __| __| '__/ _` | __|/ _ \\| '__|   / / "
        print "\t| | | | (_| | | | | | | | | | | \\__ \\ |_| | | (_| | |_| (_) | |     |_|  "
        print "\t\\_| |_/\\__,_|_| |_| |_|_|_| |_|_|___/\\__|_|  \\__,_|\\__|\\___/|_|     (_)  \n\n"

        print "-"*78    
        print "\tRemember - Under Win7 you must run this as Administrator"
        print "\tRemember - Under Win7 you must run this as Administrator"
        print "\tRemember - Under Win7 you must run this as Administrator"
        print "-"*78

        response = external_call('wmic DISKDRIVE get DeviceID, MediaType, Model, Size')
        m = re.search("Access is denied", response)
        if type(m) is not NoneType:
            print "\nERROR: This computer does not have correct privileges, you need administrator group privilege\n"
            sys.exit(1)

        response = response.replace('\r', '').strip("\n").split("\n")[1:]
        for line in response:
            m = re.search("(PHYSICALDRIVE\d+).+ (\d+) ", line)
            if type(m) is not NoneType:
                Size    = int(m.group(2))       # size in sectors
                Device  = "\\\\.\\"+m.group(1)  # \\.\PHYSICALDRIVE1

                AvailablePartitions[Device] = Size


    Devices = AvailablePartitions.keys()
    Devices.sort()

    print "\n\n"+"-"*78
    print "--------------------------------Partitions Detected--------------------------------------"
    print "-"*78+"\n"
    for device in Devices:
        value = AvailablePartitions[device]

        if value/(1024.0*1024.0*1024.0) > 31.0:
            print "%s   %s\tsectors:%i <--- Not likely an SD card, careful!" % (device,ReturnSizeString(value),value/512) 
        else:
            print "%s   %s\tsectors:%i" % (device,ReturnSizeString(value),value/512)

    print "\n"+"-"*78+"\n"

def PerformPatching():
    global PatchArray,Patching

    print "\t             _        _     _             "
    print "\t            | |      | |   (_)            "
    print "\t _ __   __ _| |_  ___| |__  _ _ __   __ _ "
    print "\t| '_ \\ / _` | __|/ __| '_ \\| | '_ \\ / _` |"
    print "\t| |_) | (_| | |_| (__| | | | | | | | (_| |"
    print "\t| .__/ \\__,_|\\__|\\___|_| |_|_|_| |_|\\__, |"
    print "\t| |                                  __/ |"
    print "\t|_|                                 |___/ "
    
    var = 'Y'
    if Patching == "DISK":
        var = 'N'           ## user must authorize this

    ## PATCHING HAPPENS HERE - PATCHING HAPPENS HERE - PATCHING HAPPENS HERE

    for Patch in PatchArray:

        if Patch['physical_partition_number']!=0:   ## msp tool can only write to PHY partition 0
            print "Skipping '%s' for physical_partition_number=%d (only 0 is accessible)" % (Patch['filename'],Patch['physical_partition_number'])
        else:

            print "\n" + "-"*78
            print "PATCH: (%s) %s" % (Patch['filename'],Patch['what'])

            FileToOpen =  Patch['filename']

            if nopromt is True:
                # means don't bug them, i.e. automation
                pass
            else:
                if var=='N' or var=='n':
                    var = raw_input("\nWARNING: Are you sure you want to PATCH to '%s' of size %s (y|N) " % (Filename,ReturnSizeString(DiskSize)))
                    if var=='Y' or var=='y':
                        pass
                    else:
                        sys.exit(1)

            if Patching=="DISK":
                FileWithPath = Filename
            else:
                FileWithPath = find_file(FileToOpen, search_paths)

            while FileWithPath is None:
                FileNotFoundShowWarning = 1

                print "\t______      _   _        ___  "
                print "\t| ___ \\    | | | |      |__ \\ "
                print "\t| |_/ /__ _| |_| |__       ) |"
                print "\t|  __// _` | __| '_ \\     / / "
                print "\t| |  | (_| | |_| | | |   |_|  "
                print "\t\\_|   \\__,_|\\__|_| |_|   (_)  \n\n"

                if rawprogram_filename is None:
                    print "ERROR: '%s' listed in '%s' not found\n" % (FileToOpen,patch_filename)
                else:
                    print "ERROR: '%s' listed in '%s' not found\n" % (FileToOpen,rawprogram_filename)

                print "Please provide a path for this file"
                print "Ex. \\\\somepath\\folder OR c:\\somepath\\folder\n"
                temppath = raw_input("Enter PATH or Q to quit? ")
                if temppath=='Q' or temppath=='q' or temppath=='':
                    sys.exit()

                print "\n"

                FileWithPath = find_file(Partition['filename'], [temppath])

                size=0
                if FileWithPath is not None:
                    size = os.path.getsize(FileWithPath)

                    temp = raw_input("\nShould this path be used to find other files? (Y|n|q)")
                    if temp=='Q' or temp=='q':
                        sys.exit()
                    elif temp=='Y' or temp=='y' or temp=='':
                        search_paths.append(temppath)
                    print "\n"            
				
            try:
                opfile = open(FileWithPath, "r+b")
                print "Opened %s, cwd=%s" % (FileWithPath,os.getcwd() )
            except:
                print "ERROR: Could not open %s, cwd=%s" % (FileWithPath,os.getcwd() )
                sys.exit()

            if Patch['function']=="CRC32":
                print "\t%s(%u,%u) requested " % (Patch['function'],Patch['arg0'],Patch['arg1'])

                PStartSector = int(Patch['arg0'])
                PNumSectors  = int(Patch['arg1']/512)
                if PNumSectors==0:
                    PNumSectors+=1

                try:
                    if Patch['arg0']>64:
                        print "\tPatch['arg0']=%d" % Patch['arg0']
                        print "\tPatch['arg1']=%d" % Patch['arg1']

                        print "\tmoving to sector %d-%d (%d)" % (PStartSector,(64-PNumSectors),PStartSector-(64-PNumSectors))
                        opfile.seek( int( (PStartSector-(64-PNumSectors) )*512 ))
                    else:
                        print "moving to sector %d (byte location %d)" % (Patch['arg0'],Patch['arg0']*512)
                        opfile.seek( int(Patch['arg0']*512))

                except:
                    print "ERROR: Could not complete move in %s" % (FileWithPath)
                    sys.exit()

                print "\tMove Successful "

                try:
                    if Patch['arg0']>64:
                        print "\tTrying to read %d bytes in %s" % (64*512,FileWithPath)
                        bytes_read = opfile.read(64*512)
                    else:
                        print "\tTrying to read %d bytes in %s" % (Patch['arg1'],FileWithPath)
                        bytes_read = opfile.read(Patch['arg1'])
                except:
                    print "ERROR: Could not read in %s" % (FileWithPath)
                    sys.exit()

                print "\tlen(bytes_read)=",len(bytes_read)

                if Patch['arg0']>64:
                    Bytes = struct.unpack("%dB" % (64*512),bytes_read)    ## unpack returns list, so get index 0
                else:
                    Bytes = struct.unpack("%dB" % Patch['arg1'],bytes_read)    ## unpack returns list, so get index 0



                if Patch['arg0']>64:
                    #PStartSector
                    #PNumSectors
                    StartByte = (64-PNumSectors)*512
                    print "\tStartByte=",StartByte

                    Patch['value'] = CalcCRC32(Bytes[StartByte:],Patch['arg1'])
                else:
                    Patch['value'] = CalcCRC32(Bytes,Patch['arg1'])

                print "\tCRC32=0x%.8X" % Patch['value']

            # ELSE - patch is not a CRC
            try:
                if Patch['start_sector']>64:
                    #print "moving to %d-63=%d" % (Patch['start_sector'],Patch['start_sector']-63)
                    opfile.seek( int((Patch['start_sector']-63)*512))
                else:
                    #print "moving to sector %d (byte location %d)" % (Patch['start_sector'],Patch['start_sector']*512)
                    opfile.seek(int(Patch['start_sector']*512))
            except:
                print "ERROR: Could not move to sector %d in %s" % (Patch['start_sector'],FileWithPath)
                sys.exit()

            try:
                if Patch['start_sector']>64:
                    bytes_read = opfile.read(64*512)
                    if len(bytes_read) != (64*512):
                        PrintBigError("Didn't get the read size 64*512");
                else:
                    bytes_read = opfile.read(512)
                    if len(bytes_read) != (512):
                        PrintBigError("Didn't get the read size 512");
            except:
                print "ERROR: Could not read sector %d in %s" % (Patch['start_sector'],FileWithPath)
                sys.exit()


            print "success was able to read len(bytes_read)=%d" % (len(bytes_read))

            # Move back to do the write
            try:
                if Patch['start_sector']>64:
                    #print "moving to %d-63=%d" % (Patch['start_sector'],Patch['start_sector']-63)
                    opfile.seek(int((Patch['start_sector']-63)*512))
                else:
                    #print "moving to sector %d (byte location %d)" % (Patch['start_sector'],Patch['start_sector']*512)
                    opfile.seek(int(Patch['start_sector']*512))
            except:
                print "ERROR: Could not move to sector %d in %s" % (Patch['start_sector'],FileWithPath)
                sys.exit()


            if Patch['value'] < 0:
                print "ERROR: Patch value was negative. This means your DISK size is too small"
                sys.exit()

            print "At sector %d (0x%X) offset %d (0x%X) in %s with %d (0x%X)" % (Patch['start_sector'],Patch['start_sector']*512,Patch['byte_offset'],Patch['byte_offset'],FileWithPath,Patch['value'],Patch['value'])

            #Patch['byte_offset']
            if Patch['start_sector']>64:
                ValueList = list(struct.unpack("32768B",bytes_read))
            else:
                ValueList = list(struct.unpack("512B",bytes_read))

            #print "\nBefore"
            #j=0
            #sys.stdout.write("%.4X\t "%j)
            #for b in ValueList[-512:]:
            #    sys.stdout.write("%.2X "%b)
            #    j+=1
            #    #if j>64:
            #    #    break
            #    if j%16==0:
            #        print " "
            #        sys.stdout.write("%.4X\t "%j)


            print "Patch value:",Patch['value'],"\t"

            for j in range(Patch['size_in_bytes']):
                if Patch['start_sector']>64:
                    #print "Applying patch at %d+%d=%d value=0x%.2X" % (63*512,Patch['byte_offset']+j,63*512+j+Patch['byte_offset'],(Patch['value']>>(j*8))&0xFF)
                    ValueList[63*512+Patch['byte_offset']+j ] = (Patch['value']>>(j*8))&0xFF
                    sys.stdout.write("%.2X " % (int(Patch['value']>>(j*8)) & 0xFF))
                else:
                    ValueList[Patch['byte_offset']+j ] = (Patch['value']>>(j*8))&0xFF
                    #import pdb; pdb.set_trace()
                    sys.stdout.write("%.2X " % (int(Patch['value']>>(j*8)) & 0xFF))

            
            #for b in ValueList:
            #    sys.stdout.write("%.2X "%b)


            #print "\nAfter"
            #j=0
            #sys.stdout.write("%.4X\t "%j)
            #for b in ValueList[-512:]:
            #    sys.stdout.write("%.2X "%b)
            #    j+=1
            #    #if j>64:
            #    #    break
            #    if j%16==0:
            #        print " "
            #        sys.stdout.write("%.4X\t "%j)

            #print " "

            if Patch['start_sector']>64:
                bytes_read = struct.pack("32768B",*ValueList)
            else:
                bytes_read = struct.pack("512B",*ValueList)

            print "(little endian)"
            print "committing patch"
            try:
                opfile.write(bytes_read)
            except:
                print "ERROR: Could not write %d bytes to %s" % (len(bytes_read),FileWithPath)
                sys.exit()

                #WriteValue(fd, RawProgramInfo.start_sector, RawProgramInfo.byte_offset, RawProgramInfo.value, RawProgramInfo.size_in_bytes);

            try:
                opfile.close()
            except:
                #print "\tWARNING: Could not close %s" % FileWithPath
                pass


            if sys.platform.startswith("linux"):
                os.system("sync")

            print "PATCH:" , Patch['what']
            print "DONE\n"

            #if Patch['what']== "Update Backup Header with Partition Array Location.":
            #    sys.exit(0)

    print "Done patching"

def Usage():
    print "\nUsage: Mass Storage Programmer - destructively writes data to disks!!\n"

    sudo = ""
    drive= ""
    if sys.platform.startswith("linux"):
        sudo = "sudo"
        drive= "/dev/sdb"
    else:
        print "\tYou must run as Administrator under Win7"
        print "\tYou must run as Administrator under Win7"
        print "\tYou must run as Administrator under Win7\n"
        drive="\\\\.\\PHYSICALDRIVE1"

    print "%-40s\t python msp.py" % "Display this info"
    print "%-40s\t%s python msp.py -w -d %s" % ("Wipe partition info (-w)",sudo,drive)
    print "%-40s\t%s python msp.py -e -d %s" % ("Erase entire card (-e)",sudo,drive)
    print "\n%-40s\t%s python msp.py -r rawprogram0.xml -d %s" % ("Program a device (-r)",sudo,drive)
    print "%-40s\t%s python msp.py -r rawprogram0.xml -d 0" % ("Create a singleimage.bin (-r)",sudo)
    print "%-40s\t%s python msp.py -r rawprogram0.xml -d 0 -t c:\\temp" % ("singleimage.bin stored to c:\\temp (-r)",sudo)
    print "%-40s\t%s python msp.py -r rawprogram0.xml -d 8192" % ("Create an 4MB singleimage.bin (-r)",sudo)
    print "%-40s\t%s python msp.py -r rawprogram0.xml -d 16777216" % ("Create an 8GB singleimage.bin (-r)",sudo)
    print "\n%-40s\t%s python msp.py -n -r rawprogram.xml -d %s" % ("no prompts (-n) i.e. automation",sudo,drive)
    print "%-40s\t%s python msp.py -i -r rawprogram.xml -d %s " % ("Interactively choose files (-i)",sudo,drive)
    print "\n%-40s\t%s python msp.py -f sbl1.mbn -f sbl2.mbn -r rawprogram.xml -d %s " % ("Specify files from rawprogram.xml (-f)",sudo,drive)
    print "%-40s\t%s python msp.py -f sbl1.mbn,sbl2.mbn -r rawprogram.xml -d %s " % ("Specify files from rawprogram.xml (-f)",sudo,drive)
    print "\n%-40s\t%s python msp.py -s c:\windows -s d:\ -r rawprogram.xml -d %s " % ("Search this path for files (-s)",sudo,drive)
    print "%-40s\t%s python msp.py -s c:\windows,d:\ -r rawprogram.xml -d %s " % ("Search this path for files (-s)",sudo,drive)
    print "\n%-40s\t%s python msp.py -p patch0.xml -d %s" % ("Patch a device (-p)",sudo,drive)
    print "%-40s\t%s python msp.py -p patch0.xml -d 0" % ("Patch files to default size (-p)",sudo)
    print "%-40s\t%s python msp.py -p patch0.xml -d 16384" % ("Patch files to 8MB disk size (-p)",sudo)
    print "\n%-40s\t%s python msp.py -r rawprogram0.xml -p patch0.xml -d %s" % ("ALL IN ONE STEP",sudo,drive)



def ReturnSizeString(size):
    if size>(1024*1024*1024):
        return "%.2f GB" % (size/(1024.0*1024.0*1024.0))
    elif size>(1024*1024):
        return "%.2f MB" % (size/(1024.0*1024.0))
    elif size>(1024):
        return "%.2f KB" % (size/(1024.0))
    else:
        return "%i B" % (size)


# A8h reflected is 15h, i.e. 10101000 <--> 00010101
def reflect(data,nBits):

    reflection = 0x00000000
    bit = 0

    for bit in range(nBits):
        if(data & 0x01):
            reflection |= (1 << ((nBits - 1) - bit))
        data = (data >> 1);

    return reflection


def CalcCRC32(array,len):
   k        = 8;            # length of unit (i.e. byte)
   MSB      = 0;
   gx	    = 0x04C11DB7;   # IEEE 32bit polynomial
   regs     = 0xFFFFFFFF;   # init to all ones
   regsMask = 0xFFFFFFFF;   # ensure only 32 bit answer

   for i in range(len):
      DataByte = array[i]
      DataByte = reflect( DataByte, 8 );

      for j in range(k):
        MSB  = DataByte>>(k-1)  ## get MSB
        MSB &= 1                ## ensure just 1 bit

        regsMSB = (regs>>31) & 1

        regs = regs<<1          ## shift regs for CRC-CCITT

        if regsMSB ^ MSB:       ## MSB is a 1
            regs = regs ^ gx    ## XOR with generator poly

        regs = regs & regsMask; ## Mask off excess upper bits

        DataByte <<= 1          ## get to next bit


   regs          = regs & regsMask ## Mask off excess upper bits
   ReflectedRegs = reflect(regs,32) ^ 0xFFFFFFFF;

   return ReflectedRegs


## ==============================================================================================
## ==============================================================================================
## ==============================================================================================
## =====main()===================================================================================
## ==============================================================================================
## ==============================================================================================
## ==============================================================================================

AvailablePartitions = {}

try:
    opts, args = getopt.getopt(sys.argv[1:], "r:p:d:iewns:f:t:v", ["rawprogram=", "patch=", "dest=","nopromt=", "interactive=", "erase=",  "wipe=", "search_path=","file=","location=","verbose="])
except getopt.GetoptError, err:
    # print help information and exit:
    print str(err) # will print something like "option -a not recognized"
    Usage()
    sys.exit(1)

check_sparse            = None
dd                      = None
xml_filename            = None
rawprogram_filename     = None
patch_filename          = None
disk_name               = None
search_paths            = []
file_list               = []
LoadSubsetOfFiles       = False
interactive             = False
erase_device            = False
wipe_device             = False
nopromt                 = False
verbose                 = False

Operation               = 0
OPERATION_PROGRAM       = 2
OPERATION_PATCH         = 4
OPERATION_ERASE         = 8
OPERATION_WIPE          = 16
Patching                = "FILES"
OutputFolder            = ""

Filename                = "singleimage.bin"
DiskSize                = 0
MinDiskSize             = 0

GetPartitions()
Usage()

for o, a in opts:
    if o in ("-r", "--rawprogram"):
        rawprogram_filename = a
        Operation |= OPERATION_PROGRAM
    elif o in ("-t", "--location"):
        OutputFolder = a
        OutputFolder = re.sub(r"\\$","",OutputFolder)    # remove final slash if it exists
        OutputFolder = re.sub(r"/$","",OutputFolder)     # remove final slash if it exists

        OutputFolder += "/"     # slashes will all be corrected below

        if sys.platform.startswith("linux"):
            OutputFolder = re.sub(r"\\","/",OutputFolder)   # correct slashes
        else:
            OutputFolder = re.sub(r"/","\\\\",OutputFolder) # correct slashes

        print "OutputFolder=",OutputFolder
        EnsureDirectoryExists(OutputFolder) # only need to call once

        Filename = OutputFolder + Filename

    elif o in ("-p", "--patch"):
        patch_filename = a
        Operation |= OPERATION_PATCH
    elif o in ("-d", "--dest"):
        disk_name = a

        # if a number was specified, it's size in sectoras and we're making a singleimage.bin
        # otherwise, a drive /dev/sdb or something was specified

        m = re.search("^(\d+)$", disk_name)
        if type(m) is not NoneType:

            ## they specified a number must be making a single image or patching files
            Patching = "FILES"
            Filename = OutputFolder + "singleimage.bin"
            DiskSize = int(int(m.group(1))*512) # comes in as sectors

        else:

            ## to be here means they didn't specify a number, thus a device
            Filename        = disk_name
            Patching        = "DISK"
            ValidDiskName   = False

            # User can also specify the singleimage.bin name, and I can treat that like it's the disk

            if sys.platform.startswith("linux"):
                m = re.search("/dev/sd[a-z]", disk_name)
                if type(m) is not NoneType:
                    ValidDiskName   = True
            else:
                m = re.search("(PHYSICALDRIVE\d+)", disk_name)
                if type(m) is not NoneType:
                    ValidDiskName   = True
                    Filename = "\\\\.\\"+m.group(1)

            if ValidDiskName:
                if Filename not in Devices:
                    print "ERROR: %s does not exist" % Filename
                    Usage()
                    sys.exit()
                DiskSize = AvailablePartitions[Filename]
            else:
                # To be here means user did this possibly "-d singleimage.bin"
                try:
                    DiskSize = os.path.getsize(disk_name)
                    if DiskSize < EMMCBLD_MIN_DISK_SIZE:
                        DiskSize = EMMCBLD_MIN_DISK_SIZE
                except:
                    print "ERROR: Can't get size of %s" % Filename
                    Usage()
                    sys.exit()

    elif o in ("-i", "--interactive"):
        interactive = True
    elif o in ("-e", "--erase"):
        Operation = OPERATION_ERASE
        erase_device = True
    elif o in ("-n", "--nopromt"):
        nopromt = True
    elif o in ("-v", "--verbose"):
        verbose = True
    elif o in ("-w", "--wipe"):
        Operation = OPERATION_WIPE
        wipe_device = True
    elif o in ("-s", "--search_path"):
        ## also allow seperating commas
        for x in a.strip("\n").split(","):
            search_paths.append(x)
    elif o in ("-f", "--file_list"):
        ## also allow seperating files by commas, i.e. -f sbl1.mbn,slb2.mbn,etc
        LoadSubsetOfFiles = True
        for x in a.strip("\n").split(","):
            file_list.append(x)
    else:
        assert False, "unhandled option"

if Operation==0:
    sys.exit(1)



print "\nSearch Paths"
print search_paths
print "\nFiles"
print file_list

if (Operation & OPERATION_PROGRAM) > 0:
    if rawprogram_filename is None:
        print "Error: You must specify an \"rawprogram\" XML file for option -r"
        sys.exit(1)
    else:
        rawprogram_filename = find_file(rawprogram_filename, search_paths)
        if rawprogram_filename is None:
            print "Error: You must specify an \"rawprogram\" XML file for option -r"
            sys.exit(1)

if (Operation & OPERATION_PATCH) > 0:
    if patch_filename is None:
        print "Error: You must specify an \"patch\" XML file for option -p"
        sys.exit(1)
    else:
        patch_filename = find_file(patch_filename, search_paths)
        if patch_filename is None:
            print "Error: You must specify an \"patch\" XML file for option -p"
            sys.exit(1)

elif Operation==OPERATION_ERASE or Operation==OPERATION_WIPE:
    if disk_name is None:
        print "Error: You must specify a DISK, option -d"
        sys.exit(1)
    
        
if (Operation & OPERATION_WIPE) > 0:
    if nopromt is True:
        # means don't bug them, i.e. automation
        pass
    else:
        print "\n\nWARNING: Destructive Operation Requested"
        print "\n\nWARNING: Wiping out the MBR/GPT information means you must re-partition and format after!!"
        var = raw_input("\nAre you sure you want to ZERO OUT the MBR/GPT information for %s of %s (y|N) " % (disk_name,ReturnSizeString(AvailablePartitions[disk_name])))
        if var=='Y' or var=='y':
            pass
        else:
            sys.exit()

    print "\n\nZero out first 2 sectors on card"
    Zerout(disk_name,0 ,2)  ## Sector 0, wipe out 2 sectors

    print "\n\nZero out last sector on card"
    Zerout(disk_name,-1,1)  ## Last Sector, wipe out 1 sector

    print "\n'%s' no longer has partition information\n" % disk_name
    sys.exit(1)



if (Operation & OPERATION_PROGRAM) > 0:

    # Can we test for sparse images?
    check_sparse = find_file("checksparse.py", search_paths)

    if check_sparse is not None:
        sz = "python %s -i %s " % (check_sparse,rawprogram_filename)
        for path in search_paths:
            sz += "-s %s " % path

        print "\n"+"-"*78
        print "-"*78

        print "Checking for sparse files"
        print "-"*78
        print "-"*78
        print sz
        os.system(sz)

    if os.path.basename(Filename)=="singleimage.bin":
        ## Wipe out any old singleimage
        SingleImageSize = 0
        opfile = open(Filename, "wb")
        opfile.close()

        print "\nProgramming %s of size %s" % (Filename,ReturnSizeString(DiskSize))

        if nopromt is False:
            if (DiskSize/(1024.0*1024.0))>100:
                var = raw_input("\nThis will be a LARGE singleimage.bin, it will take a long time, Do you want to continue? (Y|n)")
                if var=='Y' or var=='y' or var=='':
                    pass
                else:
                    sys.exit()
    else:
        if nopromt is True:
            # means don't bug them, i.e. automation
            pass
        else:
            var = raw_input("\nWARNING: Are you sure you want to write to '%s' of size %s (y|N) " % (Filename,ReturnSizeString(DiskSize)))
            if var=='Y' or var=='y':
                pass
            else:
                sys.exit()


NumPhyPartitions        = 0
PartitionArray          = []
PatchArray              = []
ZeroOutArray            = []
PhyPartition            = {}       # Main HASH that holds all the partition info

if rawprogram_filename is not None:
    ParseXML(rawprogram_filename)

if patch_filename is not None:
    ParseXML(patch_filename)

if DiskSize>0:
    if DiskSize<int(MinDiskSize):
        print "\nERROR: Current eMMC/SD card is too small to program these partitions"
        print "       Need at least %s" % ReturnSizeString(int(MinDiskSize))
        sys.exit(1)

if DiskSize==0:
    DiskSize = int(MinDiskSize)
    if DiskSize == 0:
        DiskSize = EMMCBLD_MIN_DISK_SIZE
    print "\nDiskSize was set to 0, DiskSize will be %s (%d sectors)" % (ReturnSizeString(DiskSize),int(DiskSize/512))

#print PartitionArray

## rawprogram0.xml is random in terms of start_sector. It's important for
## making the single image that we first write at sector 0, then 100, then 200 etc
## i.e. writing 200, then going back to write 100 is more complicated, so SORT

## Step 1. Sort all the start sectors where there's a file to program
PartitionStartSector = []

for Partition in PartitionArray:

    if Partition['filename']=="":
        #print "no filename for label ",Partition['label']
        continue

    if 'sparse' in Partition:
        m = re.search("true", Partition['sparse'],re.IGNORECASE)
        if type(m) is not NoneType:
            print """
            \nERROR: Found sparse image, can't continue\n
            You must first run \"python checksparse.py -i rawprogram0.xml -s C:\path1 -s C:\path2\"
            This will modify rawprogram0.xml to be able to handle the sparse image(s)\n
            Also, you *must* specify the various paths to the sparse images and they need to be
            opened and parsed. Then you can run msp.py again to program.
            """
            sys.exit(1)

    PartitionStartSector.append( Partition["start_sector"] )

PartitionStartSector.sort()

## Step 2. Create a "PartitionSorted" based on PartitionStartSector
PartitionSorted = []
Count = 0
while 1 and (Operation & OPERATION_PROGRAM) > 0:
    for Partition in PartitionArray:
        ##print "---------------- with start_sector = %i and PartitionStartSector[%i]=%i" % (Partition["start_sector"],Count,PartitionStartSector[Count])

        if Partition['filename']=="":
            continue

        if Partition["start_sector"] == PartitionStartSector[Count]:
            ##print "FOUND, len(PartitionStartSector)=%d and len(PartitionSorted)=%d" % (len(PartitionStartSector),len(PartitionSorted))
            # To be here means I found the *next* start_sector in order, i.e. 0,100,200 etc
            PartitionSorted.append( Partition )
            Count += 1  # now go to next sorted entry
            if len(PartitionSorted) == len(PartitionStartSector):
                break   # we're done, both arrays are the same size

    if len(PartitionSorted) == len(PartitionStartSector):
        break   # we're done, both arrays are the same size


#for Partition in PartitionSorted:
#    print "----------------------"
#    print Partition

# Possibilities, 
# 1. programming DISK, then patching DISK
# 2. patching FILES, programming singleimage
# 3. programming DISK
# 4. programming singleimage
# 5. patching DISK
# 6. patching FILES

if (Operation & OPERATION_PROGRAM) > 0 and (Operation & OPERATION_PATCH) > 0:
    ## Have info to do both write and patch

    if Filename!="singleimage.bin":
        # This is a DISK and we need to zero out sectors already there

        print "\n\nZero out first 34 sectors on card"
        Zerout(Filename,0,34)           # First 34 sectors wipes MBR/GPT

        print "\n\nZero out last 33 sectors on card, DiskSize=",DiskSize
        Zerout(Filename,(DiskSize/512)-34,33) # Last 33 wipes backup GPT

        # User could have specified more zero out ranges
        for Zeroout in ZeroOutArray:
            print "\n======================================================================"
            print "ZERO-OUT: '%s'" % Zeroout['label']
            Zerout(Filename,Zeroout['start_sector'],int(Zeroout['num_partition_sectors']))

        PerformWrite()
        PerformPatching()
        DoubleCheckDiskSize()
    else:
        PerformPatching()
        PerformWrite()
        DoubleCheckDiskSize()

elif (Operation & OPERATION_PROGRAM) > 0:
    PerformWrite()   
    print "\n"+"-"*78
    print "If you wrote any partition table information (MBR0.bin, gpt_main0.bin, etc)"
    print " "
    print "         _             _ _      __                     _   "
    print "        | |           ( ) |    / _|                   | |  "
    print "      __| | ___  _ __ |/| |_  | |_ ___  _ __ __ _  ___| |_ "
    print "     / _` |/ _ \\| '_ \\  | __| |  _/ _ \\| '__/ _` |/ _ \\ __|"
    print "    | (_| | (_) | | | | | |_  | || (_) | | | (_| |  __/ |_ "
    print "     \\__,_|\\___/|_| |_|  \\__| |_| \\___/|_|  \\__, |\\___|\\__|"
    print "                                             __/ |         "
    print "                                            |___/          "
    print "             _                       _        _     "
    print "            | |                     | |      | |    "
    print "            | |_  ___    _ __   __ _| |_  ___| |__  "
    print "            | __|/ _ \\  | '_ \\ / _` | __|/ __| '_ \\ "
    print "            | |_| (_) | | |_) | (_| | |_| (__| | | |"
    print "             \\__|\\___/  | .__/ \\__,_|\\__|\\___|_| |_|"
    print "                        | |                         "
    print "                        |_|                         "
    print "\n"

    sudo=""
    if sys.platform.startswith("linux"):
        sudo="sudo "

    print "\tEx: %spython msp.py -p patch0.xml -d %s " % (sudo,Filename)
    print "\n\tOr, do it all in one step\n"
    print "\tEx: %spython msp.py -r rawprogram.xml -d %s -p patch0.xml" % (sudo,Filename)
    print "\n"+"-"*78

    DoubleCheckDiskSize()

elif (Operation & OPERATION_PATCH) > 0:
    PerformPatching()   
    DoubleCheckDiskSize()
        
if (Operation & OPERATION_PROGRAM) > 0:
    
    if os.path.basename(Filename)=="singleimage.bin":   
        print "\nNOTE: This program does *not* pad the last partition, therefore"
        print "      singleimage.bin might be smaller than %d sectors " % int(DiskSize/512)

        print "\n\nSUCCESS - %s created" % Filename
        print "SUCCESS - %s created" % Filename
        print "SUCCESS - %s created\n" % Filename
        
        if FileNotFoundShowWarning==1:
            print "\nWARNING: 1 or more files were *not* found, your singleimage.bin is *NOT* complete"
            print "\nWARNING: 1 or more files were *not* found, your singleimage.bin is *NOT* complete"
            print "\nWARNING: 1 or more files were *not* found, your singleimage.bin is *NOT* complete\n\n"



print "\n"
