content: package org.flashtool.flashsystem;
  
  import java.io.ByteArrayInputStream;
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.time.ZonedDateTime;
  import java.time.format.DateTimeFormatter;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Properties;
  import java.util.Vector;
  import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
  import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.widgets.Shell;
  import org.flashtool.flashsystem.io.USBFlash;
  import org.flashtool.gui.tools.WidgetTask;
  import org.flashtool.gui.tools.XMLBootConfig;
  import org.flashtool.gui.tools.XMLBootDelivery;
  import org.flashtool.gui.tools.XMLPartitionDelivery;
  import org.flashtool.logger.LogProgress;
  import org.flashtool.parsers.sin.SinFile;
  import org.flashtool.parsers.sin.SinFileException;
  import org.flashtool.parsers.ta.TAFileParseException;
  import org.flashtool.parsers.ta.TAFileParser;
  import org.flashtool.parsers.ta.TAUnit;
  import org.flashtool.system.DeviceEntry;
  import org.flashtool.system.Devices;
  import org.flashtool.system.OS;
  import org.flashtool.system.TextFile;
  import org.flashtool.util.BytesUtil;
  import org.flashtool.util.CircularByteBuffer;
  import org.flashtool.util.HexDump;
  import org.rauschig.jarchivelib.IOUtils;
  
  import com.igormaznitsa.jbbp.JBBPParser;
  import com.igormaznitsa.jbbp.io.JBBPBitInputStream;
  import com.igormaznitsa.jbbp.io.JBBPByteOrder;
  import com.igormaznitsa.jbbp.mapper.Bin;
  
  import lombok.extern.slf4j.Slf4j;
  
  @Slf4j
  public class CommandFlasher implements Flasher {
  
  	String version = "v2";
  	
  	public class Lun {
  		
          @Bin public byte lunlength;
          @Bin public byte reserved1;
          @Bin public byte lunid;
          @Bin public byte[] reserved2;
          @Bin public int length;
          @Bin public byte [] lundata;
  	
  	}
  
  	public class UfsInfos {
  
  		@Bin public byte headerlen;
  		@Bin public byte[] ufs_header;
  		@Bin public Lun [] luns;
  	
  		long sectorSize=0;
  		
  		public void setSectorSize(long sectorsize) {
  			sectorSize=sectorsize;
  		}
  	
  		public long getLunSize(int lun) {
  			return (long)luns[lun].length*sectorSize/1024L;
  		}
  		
  	    public Object newInstance(Class<?> klazz){
  	       return klazz == Lun.class ? new Lun() : null;
  	    }
  	
  	}
  
  	public class EmmcInfos {
  
  		 @Bin public byte[] not_used;
  		 @Bin public byte[] crc;
  		 @Bin public byte[] ecc;
  		 @Bin public byte[] file_format;
  		 @Bin public byte[] tmp_write_protect;
  		 @Bin public byte[] perm_write_protect;
  		 @Bin public byte[] copy;
  		 @Bin public byte[] file_format_group;
  		 @Bin public byte[] content_prop_app;
  		 @Bin public byte[] reserved1;
  		 @Bin public byte[] write_bl_partial;
  		 @Bin public byte[] write_bl_length;
  		 @Bin public byte[] r2w_factor;
  		 @Bin public byte[] default_ecc;
  		 @Bin public byte[] wp_grp_enable;
  		 @Bin public byte[] wp_grp_size;
  		 @Bin public byte[] erase_grp_mult;
  		 @Bin public byte[] erase_grp_size;
  		 @Bin public byte[] c_size_mult;
  		 @Bin public byte[] wdd_w_curr_max;
  		 @Bin public byte[] wdd_w_curr_min;
  		 @Bin public byte[] wdd_r_curr_max;
  		 @Bin public byte[] wdd_r_curr_min;
  		 @Bin public byte[] c_size;
  		 @Bin public byte[] reserved2;
  		 @Bin public byte[] dsr_imp;
  		 @Bin public byte[] read_blk_misalign;
  		 @Bin public byte[] write_blk_misalign;
  		 @Bin public byte[] read_blk_partial;
  		 @Bin public byte[] read_bl_length;
  		 @Bin public byte[] ccc;
  		 @Bin public byte[] tran_speed;
  		 @Bin public byte[] nsac;
  		 @Bin public byte[] taac;
  		 @Bin public byte[] reserved3;
  		 @Bin public byte[] spec_vers;
  		 @Bin public byte[] csd_structure;
  		 @Bin public byte[] reserved4;
  		 @Bin public byte[] sec_bad_blk_mgmnt;
  		 @Bin public byte[] reserved5;
  		 @Bin public byte[] enh_start_addr;
  		 @Bin public byte[] enh_size_mult;
  		 @Bin public byte[] gp_size_mult;
  		 @Bin public byte[] PARTITION_SETTING_COMPLETED;
  		 @Bin public byte[] PARTITIONS_ATTRIBUTE;
  		 @Bin public byte[] MAX_ENH_SIZE_MULT;
  		 @Bin public byte[] PARTITIONING_SUPPORT;
  		 @Bin public byte[] HPI_MGMT;
  		 @Bin public byte[] RST_n_FUNCTION;
  		 @Bin public byte[] BKOPS_EN;
  		 @Bin public byte[] BKOPS_START;
  		 @Bin public byte[] RESERVED6;
  		 @Bin public byte[] WR_REL_PARAM;
  		 @Bin public byte[] WR_REL_SET;
  		 @Bin public byte[] RPMB_SIZE_MULT;
  		 @Bin public byte[] FW_CONFIG;
  		 @Bin public byte[] RESERVED7;
  		 @Bin public byte[] USER_WP;
  		 @Bin public byte[] RESERVED8;
  		 @Bin public byte[] BOOT_WP;
  		 @Bin public byte[] ERASE_GROUP_DEF;
  		 @Bin public byte[] RESERVED9;
  		 @Bin public byte[] BOOT_BUS_WIDTH;
  		 @Bin public byte[] BOOT_CONFIG_PROT;
  		 @Bin public byte[] PARTITION_CONFIG;
  		 @Bin public byte[] RESERVED10;
  		 @Bin public byte[] ERASED_MEM_CONT;
  		 @Bin public byte[] RESERVED11;
  		 @Bin public byte[] BUS_WIDTH;
  		 @Bin public byte[] RESERVED12;
  		 @Bin public byte[] HS_TIMING;
  		 @Bin public byte[] RESERVED13;
  		 @Bin public byte[] POWER_CLASS;
  		 @Bin public byte[] RESERVED14;
  		 @Bin public byte[] CMD_SET_REV;
  		 @Bin public byte[] RESERVED15;
  		 @Bin public byte[] CMD_SET;
  		 @Bin public byte[] RESERVED16;
  		 @Bin public byte[] EXT_CSD_REV;
  		 @Bin public byte[] RESERVED17;
  		 @Bin public byte[] CSD_STRUCTURE1;
  		 @Bin public byte[] RESERVED18;
  		 @Bin public byte[] CARD_TYPE;
  		 @Bin public byte[] RESERVED19;
  		 @Bin public byte[] OUT_OF_INTERRUPT_TIME;
  		 @Bin public byte[] PARTITION_SWITCH_TIME;
  		 @Bin public byte[] PWR_CL_52_195;
  		 @Bin public byte[] PWR_CL_26_195;
  		 @Bin public byte[] PWR_CL_52_360;
  		 @Bin public byte[] PWR_CL_26_360;
  		 @Bin public byte[] RESERVED20;
  		 @Bin public byte[] MIN_PERF_R_4_26;
  		 @Bin public byte[] MIN_PERF_W_4_26;
  		 @Bin public byte[] MIN_PERF_R_8_26_4_52;
  		 @Bin public byte[] MIN_PERF_W_8_26_4_52;
  		 @Bin public byte[] MIN_PERF_R_8_52;
  		 @Bin public byte[] MIN_PERF_W_8_52;
  		 @Bin public byte[] RESERVED21;
  		 @Bin public int SEC_COUNT;
  		 @Bin public byte[] RESERVED22;
  		 @Bin public byte[] S_A_TIMEOUT;
  		 @Bin public byte[] RESERVED23;
  		 @Bin public byte[] S_C_VCCQ;
  		 @Bin public byte[] S_C_VCC;
  		 @Bin public byte[] HC_WP_GRP_SIZE;
  		 @Bin public byte[] REL_WR_SEC_C;
  		 @Bin public byte[] ERASE_TIMEOUT_MULT;
  		 @Bin public byte[] HC_ERASE_GRP_SIZE;
  		 @Bin public byte[] ACC_SIZE;
  		 @Bin public byte[] BOOT_SIZE_MULT;
  		 @Bin public byte[] RESERVED24;
  		 @Bin public byte[] BOOT_INFO;
  		 @Bin public byte[] SEC_TRIM_MULT;
  		 @Bin public byte[] SEC_ERASE_MULT;
  		 @Bin public byte[] SEC_FEATURE_SUPPORT;
  		 @Bin public byte[] TRIM_MULT;
  		 @Bin public byte[] RESERVED25;
  		 @Bin public byte[] MIN_PERF_DDR_R_8_52;
  		 @Bin public byte[] MIN_PERF_DDR_W_8_52;
  		 @Bin public byte[] RESERVED26;
  		 @Bin public byte[] PWR_CL_DDR_52_195;
  		 @Bin public byte[] PWR_CL_DDR_52_360;
  		 @Bin public byte[] RESERVED27;
  		 @Bin public byte[] INI_TIMEOUT_AP;
  		 @Bin public byte[] CORRECTLY_PRG_SECTORS_NUM;
  		 @Bin public byte[] BKOPS_STATUS;
  		 @Bin public byte[] RESERVED28;
  		 @Bin public byte[] BKOPS_SUPPORT;
  		 @Bin public byte[] HPI_FEATURES;
  		 @Bin public byte[] S_CMD_SET;
  		 @Bin public byte[] RESERVED29;
  	
  		long sectorSize=0;
  		
  		public void setSectorSize(long sectorsize) {
  			sectorSize=sectorsize;
  		}
  	
  		public long getDiskSize() {
  			return (long)SEC_COUNT*sectorSize/1024L;
  		}
  		
  	    public Object newInstance(Class<?> klazz){
  	       return klazz == Lun.class ? new Lun() : null;
  	    }
  	
  	}
  
  	private Bundle _bundle;
      private String currentdevice = "";
      private String serial = "";
      private Shell _curshell;
      private HashMap<Long,TAUnit> TaPartition2 = new HashMap<Long,TAUnit>();
      private XMLBootConfig bc=null;
      private XMLPartitionDelivery pd=null;
      private Properties  phoneprops = null;
      private UfsInfos ufs_infos = null;
      private EmmcInfos emmc_infos = null;
      
      public CommandFlasher(Bundle bundle, Shell shell) {
      	_bundle=bundle;
      	_curshell = shell;
      }
  
      public boolean flashmode() {
      	boolean found = false;
      	try {
  			Thread.sleep(500);
  			found = Devices.getLastConnected(false).getPid().equals("B00B");
  		}
  		catch (Exception e) {
  	    	found = false;
  		}
      	return found;
      }
  
  
      public void loadProperties() {
      	log.info("Reading phone properties");
      	phoneprops = new Properties();
      	try {
      		getVar("product");
  	    	phoneprops.setProperty("swrelease", new String(readTA(2, 2202).getUnitData()));
  	    	phoneprops.setProperty("customization", new String(readTA(2, 2205).getUnitData()));
  	    	phoneprops.setProperty("model",new String(readTA(2, 2210).getUnitData()));
  	    	phoneprops.setProperty("serial", new String(readTA(2, 4900).getUnitData()));
  	    	phoneprops.setProperty("lastflashdate",new String(readTA(2, 10021).getUnitData()));
  	    	getRootKeyHash();
      	} catch (Exception e) {
      	}    	
      }
  
      public boolean open(boolean simulate) {
      	USBFlash.setUSBBufferSize(512*1024);
      	if (_bundle.getMaxBuffer()==0) {
      		USBFlash.setUSBBufferSize(4096*1024);
      		log.info("USB buffer size set to 4096K");
      	}
      	if (_bundle.getMaxBuffer()==1) {
      		USBFlash.setUSBBufferSize(2048*1024);
      		log.info("USB buffer size set to 2048K");
      	}
      	if (_bundle.getMaxBuffer()==2) {
      		USBFlash.setUSBBufferSize(1024*1024);
  	    	log.info("USB buffer size set to 1024K");
      	}
      	if (_bundle.getMaxBuffer()==3) {
      		USBFlash.setUSBBufferSize(512*1024);
  	    	log.info("USB buffer size set to 512K");
      	}
      	if (_bundle.getMaxBuffer()==4) {
      		USBFlash.setUSBBufferSize(256*1024);
      		log.info("USB buffer size set to 256K");
      	}
      	if (_bundle.getMaxBuffer()==5) {
      		USBFlash.setUSBBufferSize(128*1024);
      		log.info("USB buffer size set to 128K");
      	}
      	if (_bundle.getMaxBuffer()==6) {
      		USBFlash.setUSBBufferSize(64*1024);
      		log.info("USB buffer size set to 64K");
      	}
      	if (_bundle.getMaxBuffer()==7) {
      		USBFlash.setUSBBufferSize(32*1024);
      		log.info("USB buffer size set to 32K");
      	}
      	boolean found=false;
      	try {
      		USBFlash.open("B00B");
      		try {
  				log.info("Reading device information");
  				loadProperties();
  				currentdevice=getPhoneProperty("product");
  				log.info("Connected device : "+currentdevice+" / SW release "+phoneprops.getProperty("swrelease")+" / Customization "+phoneprops.getProperty("customization"));
  				log.info("Last flash date : "+phoneprops.getProperty("lastflashdate"));
      		}
      		catch (Exception e) {
      			log.info(e.getMessage());
      			log.info("Unable to read from phone after having opened it.");
      			log.info("trying to continue anyway");
      		}
      	    log.info("Phone ready for flashmode operations.");
  			if (_bundle.getDevice()!=null) {
  				if (_bundle.getDevice().length()>0 && !currentdevice.equals(_bundle.getDevice())) {
  						log.error("The bundle does not match the connected device");
  						close();
  						found = false;
  				}
  				else found=true;
  			}
  			else
  				found = true;
      	}
      	catch (Exception e){
      		e.printStackTrace();
      		found=false;
      	}
      	return found;
      }
  
      public boolean open() {
      	return open(_bundle.simulate());
      }
  
      public String getFlashScript() {
      	try {
      		if (_bundle.hasFsc()) {
      			return _bundle.getFsc().getAbsolutePath();
      		}
      	} catch (Exception e) {
      	}
      	DeviceEntry dev = Devices.getDeviceFromVariant(getCurrentDevice());
      	return dev.getFlashScript(_bundle.getVersion(), getCurrentDevice());
      }
  
      public boolean checkScript() {
      	try {
      		Vector<String> ignored = new Vector<String>();
      		FlashScript flashscript = new FlashScript(getFlashScript());
      		flashscript.setBootConfig(bc);
      		flashscript.setPartitionDelivery(pd);
      		Iterator<Category> icategs = _bundle.getMeta().getAllEntries(true).iterator();
      		while (icategs.hasNext()) {
      			Category cat = icategs.next();
      			if (!flashscript.hasCategory(cat)) {
      				if (!cat.getId().equals("FSCONFIG") ) {
      					ignored.add(cat.getId());
      				}
      			}
      		}
      		if (ignored.size()>0) {
      			Enumeration eignored = ignored.elements();
      			String dynmsg = "";
      			while (eignored.hasMoreElements()) {
      				dynmsg=dynmsg+eignored.nextElement();
      				if (eignored.hasMoreElements()) dynmsg = dynmsg + ",";
      			}
      			String result = WidgetTask.openYESNOBox(_curshell, "Those data are not in the FSC script and will be skipped : \n"+dynmsg+".\n Do you want to continue ?");
      			if (Integer.parseInt(result) == SWT.YES) {
      				return true;
      			}
      			return false;
      		}
      		return true;
  
      	} catch (Exception e) {
      		return false;
      	}
      }
  
      public boolean hasScript() {
      	File fsc=null;
      	try {
      		fsc=new File(getFlashScript());
      	}
      	catch (Exception e) {
      		fsc=null;
      	}
      	if (fsc!=null) {
      		if (fsc.exists()) {
      			if (_bundle.hasFsc()) return true;
      			String result = WidgetTask.openYESNOBox(_curshell, "A FSC script is found : "+fsc.getName()+". Do you want to use it ?");
      			return Integer.parseInt(result)==SWT.YES;
      		}
      		else return false;
      	}
      	return false;
      }
  
      public void runScript() {
      	try {
      		TextFile tf = new TextFile(getFlashScript(),"ISO8859-1");
      		log.info("Found a template session. Using it : "+tf.getFileName());
      		Map<Integer,String> map =  tf.getMap();
      		Iterator<Integer> keys = map.keySet().iterator();
      		setFlashState(true);
      		while (keys.hasNext()) {
      			String param1="";
      			String param2="";
      			String line = map.get(keys.next());
      			String[] parsed = line.split(":");
      			String action = parsed[0];
      			if (parsed.length>1)
      				param1 = parsed[1];
      			if (parsed.length>2)
      				param2 = parsed[2];
      			if (action.equals("flash")) {
      				BundleEntry b = _bundle.searchEntry(param2);
      				if (b!=null) {
      					SinFile sin =new SinFile(new File(b.getAbsolutePath()));
      					flashImage(sin,param1);
      				}
      				else {
      					if (bc!=null) {
      						String file = bc.getMatchingFile(param2);
      						if (file!=null) {
      	    					SinFile sin =new SinFile(new File(file));
      	    					flashImage(sin,param1);
      						}
          					else {
          						log.warn(param2 + " is excluded from bundle");
          					}
      					}
      					else {
      						log.warn(param2 + " is excluded from bundle");
      					}
      				}
      			}
      			else if (action.equals("Repartition")) {
     					if (pd!=null) {
  						String file = pd.getMatchingFile(SinFile.getShortName(param2), _curshell);
  						if (file!=null) {
  	    					SinFile sin =new SinFile(new File(file));
  	    					repartition(sin,Integer.parseInt(param1));
  						}
      					else {
      						log.warn(param2 + " is excluded from bundle");
      					}
  					}
  					else {
  						BundleEntry b = _bundle.searchEntry(param2);
  	    				if (b!=null) {
  	    					SinFile sin =new SinFile(new File(b.getAbsolutePath()));
  	    					repartition(sin,Integer.parseInt(param1));
  	    				}
  	    				else
  	    					log.warn(param2 + " is excluded from bundle");
  					}
      			}
      			else if (action.equals("Write-TA")) {
      				if (Integer.parseInt(param1) == 2)
      					if (TaPartition2.get(Long.parseLong(param2))!=null) {
      						writeTA(2,TaPartition2.get(Long.parseLong(param2)));
      					}
      					else {
      						if (!param2.equals("10100") && !param2.equals("10021"))
      							log.warn("TA Unit "+param2 + " is excluded from bundle");
      					}
      			}
      			else if (action.equals("set_active")) {
      				setActive(param1);
      			}
      			else if (action.equals("Get-ufs-info")) {
      				getUfsInfo();
      				if (pd != null)
      					pd.setUfsInfos(ufs_infos);
      			}
      			else if (action.equals("Get-emmc-info")) {
      				getEmmcInfo();
         				if (pd != null)
          				pd.setEmmcInfos(emmc_infos);
      			}
      			else if (action.equals("Get-gpt-info")) {
      				GetGptInfo(Integer.parseInt(param1));
      			}
      			else if (action.equals("getvar")) {
      				getVar(param1);
      			}
      		}
      		setFlashState(false);
      	} catch (Exception e) {e.printStackTrace();}
      }
  
      
  	public void flash() throws X10FlashException, IOException {
  		try {
  			//WidgetTask.openOKBox(_curshell, "This device protocol is not yet supported");
  			log.info("Start Flashing");
  			bc = getBootConfig();
  			pd = _bundle.getXMLPartitionDelivery();
  			if (pd!=null) {
  				pd.setFolder(_bundle.getPartitionDelivery().getFolder());
  			}
  			else {
  				String result = WidgetTask.openYESNOBox(_curshell, "No partition delivery included.\nMaybe a bundle created with a previous release of Flashtool.\nDo you want to continue ?");
  				if (Integer.parseInt(result)!=SWT.YES) throw new X10FlashException("No partition delivery");
  			}
  			loadTAFiles();
  		    if (hasScript()) {
  		    	if (checkScript())
  		    		runScript();
  		    }
  		    else {
  		    	log.error("No flash script found. Flash script is mandatory");
  		    }
  		} catch (Exception e) {
  			log.error(e.getMessage());
  		}
  		LogProgress.initProgress(0);
  		close();
  	}
  
  	public XMLBootConfig getBootConfig() throws FileNotFoundException, IOException,X10FlashException, TAFileParseException, BootDeliveryException  {
  		if (!_bundle.hasBootDelivery()) {
  			log.info("No boot delivery into the bundle");
  			return null;
  		}
  		log.info("Parsing boot delivery");
  		XMLBootDelivery xml = _bundle.getXMLBootDelivery();
  		Vector<XMLBootConfig> found = new Vector<XMLBootConfig>();
      		Enumeration<XMLBootConfig> e = xml.getBootConfigs();
      		while (e.hasMoreElements()) {
      			// We get matching bootconfig from all configs
      			XMLBootConfig bc=e.nextElement();
      			bc.addMatcher("PLF_ROOT_HASH", phoneprops.getProperty("root-key-hash"));
      			if (bc.matchAttributes()) {
      				found.add(bc);
      			}
      		}
  		if (found.size()==0)
  			throw new BootDeliveryException ("Found no matching boot config. Aborting");
  		// if found more thant 1 config
  		boolean same = true;
  		if (found.size()>1) {
  			// Check if all found configs have the same fileset
  			Iterator<XMLBootConfig> masterlist = found.iterator();
  			while (masterlist.hasNext()) {
  				XMLBootConfig masterconfig = masterlist.next();
  				Iterator<XMLBootConfig> slavelist = found.iterator();
  				while (slavelist.hasNext()) {
  					XMLBootConfig slaveconfig = slavelist.next();
  					if (slaveconfig.compare(masterconfig)==2)
  						throw new BootDeliveryException ("Cannot decide among found configurations. Skipping boot delivery");
  				}
  			}
  		}
  		found.get(found.size()-1).setFolder(_bundle.getBootDelivery().getFolder());
  		log.info("Found a boot delivery");
  		return found.get(found.size()-1);
      }
  
  	public void close() {
  		try {
  			sync();
  			powerDown();
  		} catch (Exception e) {
  			e.printStackTrace();
  		}
  		USBFlash.close();
  	}
  
  	public String getPhoneProperty(String property) {
  		return phoneprops.getProperty(property);
  	}
  
  	public Bundle getBundle() {
  		return _bundle;
  	}
  
  	public void getUfsInfo()  throws IOException,X10FlashException {
      	log.info("Sending Get-ufs-info");
      		String command = "Get-ufs-info";
      		USBFlash.write(command.getBytes());
      		CommandPacket reply = USBFlash.readCommandReply(true);
      		log.info("   Get-ufs-info status : "+reply.getResponse());
  
      		JBBPParser ufs_parser = JBBPParser.prepare(
      			    "byte headerlen;"
                    + "byte[headerlen-1] ufs_header;"
                    + "luns [_] { "
                    + "   byte lunlength; "
                    + "   byte reserved1; "
                    + "   byte lunid; "
                    + "   byte[12] reserved2; "
                    + "   int length; "
                    + "   byte[lunlength-19] lundata; "
                    + "}"
               );
  
      		try {
      			JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(reply.getDataArray()));
      			ufs_infos = ufs_parser.parse(stream).mapTo(new UfsInfos());
      			ufs_infos.setSectorSize(Integer.parseInt(getPhoneProperty("Sector-size")));
      			try {
      			   stream.close();
      			} catch (Exception streamclose ) {}
      		}
      		catch (Exception e)   {
      			log.error("Error parsing Get-ufs-info reply");
      			ufs_infos=null;
      		}
  
  	}
  
  	public void getEmmcInfo()  throws IOException,X10FlashException {
      	log.info("Sending Get-emmc-info");
      		String command = "Get-emmc-info";
      		USBFlash.write(command.getBytes());
      		CommandPacket reply = USBFlash.readCommandReply(true);
      		log.info("   Get-emmc-info status : "+reply.getResponse());
  
      		JBBPParser emmc_parser = JBBPParser.prepare(
      				 "byte[1] not_used;"
      		      + "byte[7] crc;"
      			  + "byte[2] ecc;"
      			  + "byte[2] file_format;"
      			  + "byte[1] tmp_write_protect;"
      			  + "byte[1] perm_write_protect;"
      			  + "byte[1] copy;"
      			  + "byte[1] file_format_group;"
      			  + "byte[1] content_prop_app;"
      			  + "byte[4] reserved1;"
      			  + "byte[1] write_bl_partial;"
      			  + "byte[4] write_bl_length;"
      			  + "byte[3] r2w_factor;"
      			  + "byte[2] default_ecc;"
      			  + "byte[1] wp_grp_enable;"
      			  + "byte[5] wp_grp_size;"
      			  + "byte[5] erase_grp_mult;"
      			  + "byte[5] erase_grp_size;"
      			  + "byte[3] c_size_mult;"
      			  + "byte[3] wdd_w_curr_max;"
      			  + "byte[3] wdd_w_curr_min;"
      			  + "byte[3] wdd_r_curr_max;"
      			  + "byte[3] wdd_r_curr_min;"
      			  + "byte[12] c_size;"
      			  + "byte[2] reserved2;"
      			  + "byte[1] dsr_imp;"
      			  + "byte[1] read_blk_misalign;"
      			  + "byte[1] write_blk_misalign;"
      			  + "byte[1] read_blk_partial;"
      			  + "byte[4] read_bl_length;"
      			  + "byte[12] ccc;"
      			  + "byte[8] tran_speed;"
      			  + "byte[8] nsac;"
      			  + "byte[8] taac;"
      			  + "byte[2] reserved3;"
      			  + "byte[4] spec_vers;"
      			  + "byte[2] csd_structure;"
      			  + "byte[6] reserved4;"
      			  + "byte[1] sec_bad_blk_mgmnt;"
      			  + "byte[1] reserved5;"
      			  + "byte[4] enh_start_addr;"
      			  + "byte[3] enh_size_mult;"
      			  + "byte[12] gp_size_mult;"
      			  + "byte[1] PARTITION_SETTING_COMPLETED;"
      			  + "byte[1] PARTITIONS_ATTRIBUTE;"
      			  + "byte[3] MAX_ENH_SIZE_MULT;"
      			  + "byte[1] PARTITIONING_SUPPORT;"
      			  + "byte[1] HPI_MGMT;"
      			  + "byte[1] RST_n_FUNCTION;"
      			  + "byte[1] BKOPS_EN;"
      			  + "byte[1] BKOPS_START;"
      			  + "byte[1] RESERVED6;"
      			  + "byte[1] WR_REL_PARAM;"
      			  + "byte[1] WR_REL_SET;"
      			  + "byte[1] RPMB_SIZE_MULT;"
      			  + "byte[1] FW_CONFIG;"
      			  + "byte[1] RESERVED7;"
      			  + "byte[1] USER_WP;"
      			  + "byte[1] RESERVED8;"
      			  + "byte[1] BOOT_WP;"
      			  + "byte[1] ERASE_GROUP_DEF;"
      			  + "byte[1] RESERVED9;"
      			  + "byte[1] BOOT_BUS_WIDTH;"
      			  + "byte[1] BOOT_CONFIG_PROT;"
      			  + "byte[1] PARTITION_CONFIG;"
      			  + "byte[1] RESERVED10;"
      			  + "byte[1] ERASED_MEM_CONT;"
      			  + "byte[1] RESERVED11;"
      			  + "byte[1] BUS_WIDTH;"
      			  + "byte[1] RESERVED12;"
      			  + "byte[1] HS_TIMING;"
      			  + "byte[1] RESERVED13;"
      			  + "byte[1] POWER_CLASS;"
      			  + "byte[1] RESERVED14;"
      			  + "byte[1] CMD_SET_REV;"
      			  + "byte[1] RESERVED15;"
      			  + "byte[1] CMD_SET;"
      			  + "byte[1] RESERVED16;"
      			  + "byte[1] EXT_CSD_REV;"
      			  + "byte[1] RESERVED17;"
      			  + "byte[1] CSD_STRUCTURE1;"
      			  + "byte[1] RESERVED18;"
      			  + "byte[1] CARD_TYPE;"
      			  + "byte[1] RESERVED19;"
      			  + "byte[1] OUT_OF_INTERRUPT_TIME;"
      			  + "byte[1] PARTITION_SWITCH_TIME;"
      			  + "byte[1] PWR_CL_52_195;"
      			  + "byte[1] PWR_CL_26_195;"
      			  + "byte[1] PWR_CL_52_360;"
      			  + "byte[1] PWR_CL_26_360;"
      			  + "byte[1] RESERVED20;"
      			  + "byte[1] MIN_PERF_R_4_26;"
      			  + "byte[1] MIN_PERF_W_4_26;"
      			  + "byte[1] MIN_PERF_R_8_26_4_52;"
      			  + "byte[1] MIN_PERF_W_8_26_4_52;"
      			  + "byte[1] MIN_PERF_R_8_52;"
      			  + "byte[1] MIN_PERF_W_8_52;"
      			  + "byte[1] RESERVED21;"
                    + "<int SEC_COUNT;"
      			  + "byte[1] RESERVED22;"
      			  + "byte[1] S_A_TIMEOUT;"
      			  + "byte[1] RESERVED23;"
      			  + "byte[1] S_C_VCCQ;"
      			  + "byte[1] S_C_VCC;"
      			  + "byte[1] HC_WP_GRP_SIZE;"
      			  + "byte[1] REL_WR_SEC_C;"
      			  + "byte[1] ERASE_TIMEOUT_MULT;"
      			  + "byte[1] HC_ERASE_GRP_SIZE;"
      			  + "byte[1] ACC_SIZE;"
      			  + "byte[1] BOOT_SIZE_MULT;"
      			  + "byte[1] RESERVED24;"
      			  + "byte[1] BOOT_INFO;"
      			  + "byte[1] SEC_TRIM_MULT;"
      			  + "byte[1] SEC_ERASE_MULT;"
      			  + "byte[1] SEC_FEATURE_SUPPORT;"
      			  + "byte[1] TRIM_MULT;"
      			  + "byte[1] RESERVED25;"
      			  + "byte[1] MIN_PERF_DDR_R_8_52;"
      			  + "byte[1] MIN_PERF_DDR_W_8_52;"
      			  + "byte[2] RESERVED26;"
      			  + "byte[1] PWR_CL_DDR_52_195;"
      			  + "byte[1] PWR_CL_DDR_52_360;"
      			  + "byte[1] RESERVED27;"
      			  + "byte[1] INI_TIMEOUT_AP;"
      			  + "byte[4] CORRECTLY_PRG_SECTORS_NUM;"
      			  + "byte[1] BKOPS_STATUS;"
      			  + "byte[255] RESERVED28;"
      			  + "byte[1] BKOPS_SUPPORT;"
      			  + "byte[1] HPI_FEATURES;"
      			  + "byte[1] S_CMD_SET;"
      			  + "byte[7] RESERVED29;"
               );
  
      		try {
      			JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(reply.getDataArray()));
      			emmc_infos = emmc_parser.parse(stream).mapTo(new EmmcInfos());
      			emmc_infos.setSectorSize(Integer.parseInt(getPhoneProperty("Sector-size")));
      			try {
      			   stream.close();
      			} catch (Exception streamclose ) {}
      		}
          	catch (Exception e)   {
          			log.error("Error parsing Emmc-info reply");
          			emmc_infos=null;
          	}  
  	}
  
  	public void GetGptInfo(int partnumber)  throws IOException,X10FlashException {
      	log.info("Sending Get-gpt-info:"+partnumber);
      	String command = "Get-gpt-info:"+partnumber;
      	USBFlash.write(command.getBytes());
      	CommandPacket reply = USBFlash.readCommandReply(true);
      	log.info("   Get-gpt-info status : "+reply.getResponse());
  	}
  
  	public void setActive(String name)  throws IOException,X10FlashException {
      	log.info("Sending set_active:"+name);
      	if (!_bundle.simulate()) {
      		String command = "set_active:"+name;
      		USBFlash.write(command.getBytes());
      		CommandPacket reply = USBFlash.readCommandReply(true);
      		log.info("   set_active status : "+reply.getResponse());
      	}
  	}
  	
  	public void setFlashState(boolean ongoing) throws IOException,X10FlashException {
  		if (!_bundle.simulate()) {
  			if (ongoing) {
  	    		writeTA(2,new TAUnit(10100,new byte[] {0x01}));
  			}
  			else {
  	    	  	String result = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
  	    	  	TAUnit tau = new TAUnit(10021, BytesUtil.concatAll(result.getBytes(), new byte[] {0x00}));
  	    	  	writeTA(2,tau);
  	    		writeTA(2,new TAUnit(10100,new byte[] {0x00}));
  			}
  		}
  	}
  
  	public String getLog() throws X10FlashException, IOException {
  		log.info("Sending Getlog");
  		String command = "Getlog";
  		USBFlash.write(command.getBytes());
  		CommandPacket reply = USBFlash.readCommandReply(true);
  		log.info("   Getlog status : "+reply.getResponse());
  		return reply.getMessage();
  	}
  
  	public TAUnit readTA(int partition, int unit) throws X10FlashException, IOException {
  		return readTA(partition, unit, true);
  	}
  
  	public TAUnit readTA(int partition, int unit, boolean withlog) throws X10FlashException, IOException {
  			String command = "Read-TA:"+partition+":"+unit;
  			if (withlog)
  				log.info("Sending "+command);
  			USBFlash.write(command.getBytes());
  			CommandPacket reply = USBFlash.readCommandReply(true);
  			if (withlog)
  				log.info("   Read-TA status : "+reply.getResponse());
  			if (reply.getResponse().equals("OKAY")) {
  				TAUnit taunit = new TAUnit(unit,reply.getDataArray());
  				return taunit;
  			}
  			else {
  				if (withlog)
  					log.warn("   "+reply.getMessage()+" ( Hex unit value "+HexDump.toHex(unit)+" )");
  				return null;
  			}
  	}
  
  	public void writeTA(int partition, TAUnit unit) throws X10FlashException, IOException {
  		try {
  			//wrotedata=true;
  			log.info("Writing TA unit "+HexDump.toHex((int)unit.getUnitNumber())+" to partition "+partition);
  			if (!_bundle.simulate()) {
  				log.info("   download:"+HexDump.toHex(unit.getDataLength()));
  				String command = "download:"+HexDump.toHex(unit.getDataLength());
  				USBFlash.write(command.getBytes());
  				CommandPacket p = USBFlash.readCommandReply(false);
  				if (unit.getDataLength()>0) {
  					USBFlash.write(unit.getUnitData());
  				}
  				p = USBFlash.readCommandReply(true);
  				log.info("   download status : "+p.getResponse());
  				log.info("   Write-TA:"+partition+":"+unit.getUnitNumber());
  				command = "Write-TA:"+partition+":"+unit.getUnitNumber();
  				USBFlash.write(command.getBytes());
  				p = USBFlash.readCommandReply(true);
  				log.info("   Write-TA status : "+p.getResponse());
  			}
  		} 
  		catch (Exception e) {
  		}
  	}
  
  	public void getVar(String key) throws X10FlashException, IOException {
  		String command = "getvar:"+key;
  		log.info("Sending "+command);
  		USBFlash.write(command.getBytes());
  		CommandPacket reply = USBFlash.readCommandReply(true);
  		log.info("   getvar status : "+reply.getResponse());
  		if (reply.getResponse().equals("FAIL")) {
  			log.warn("   "+reply.getMessage());
  		}
  		phoneprops.setProperty(key,reply.getMessage());
  	}
  
  	public void sync() throws IOException, X10FlashException {
      	log.info("Sending Sync");
      	if (!_bundle.simulate()) {
      		USBFlash.write(("Sync").getBytes());
      		CommandPacket p = USBFlash.readCommandReply(true);
      		log.info("   Sync status : "+p.getResponse());
      	}
      }
  
      public void powerDown() throws IOException, X10FlashException {
      	log.info("Sending powerdown");
  		USBFlash.write(("powerdown").getBytes());
  		CommandPacket p = USBFlash.readCommandReply(true);
  		log.info("   powerdown status : "+p.getResponse());
      }
  
      public void getRootKeyHash() throws IOException, X10FlashException {
      	log.info("Sending Get-root-key-hash");
  		USBFlash.write("Get-root-key-hash".getBytes());
  		CommandPacket p = USBFlash.readCommandReply(true);
  		log.info("   Get-root-key-hash status : "+p.getResponse());
  		phoneprops.setProperty("root-key-hash", HexDump.toHex(p.getDataArray()).replaceAll(" ", ""));
      }
  
  	public void repartition(SinFile sin, int partnumber) throws IOException, X10FlashException {
  		//wrotedata=true;
  		String command="";
  		CommandPacket p;
  		log.info("processing "+sin.getName());
  		command = "download:"+HexDump.toHex(sin.getHeader().length);
  		log.info("   "+command);
  		if (!_bundle.simulate()) {
  			USBFlash.write(command.getBytes());
  			p = USBFlash.readCommandReply(false);
  			USBFlash.write(sin.getHeader());
  			p = USBFlash.readCommandReply(true);
  			log.info("   download status : "+p.getResponse());
  			command = "signature";
  			log.info("   "+command);
  			USBFlash.write(command.getBytes());
  			p = USBFlash.readCommandReply(true);
  			log.info("   signature status : "+p.getResponse());
  		}
  		
  		TarArchiveInputStream tarIn = sin.getTarInputStream();
  		TarArchiveEntry entry=null;
  		while ((entry = tarIn.getNextTarEntry()) != null) {
  			if (!entry.getName().endsWith("cms")) {
  				log.info("   sending "+entry.getName());
  				if (!_bundle.simulate()) {
  					command = "download:"+HexDump.toHex((int)entry.getSize());
  					log.info("      "+command);
  					USBFlash.write(command.getBytes());
  					p = USBFlash.readCommandReply(false);
  					//log.info("      Download reply : "+p.getResponse());
  				}
  				CircularByteBuffer cb = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
  				IOUtils.copy(tarIn, cb.getOutputStream());
  				while (cb.getAvailable()>0) {
  					byte[] buffer = new byte[cb.getAvailable()>=USBFlash.getUSBBufferSize()?USBFlash.getUSBBufferSize():cb.getAvailable()];
  					cb.getInputStream().read(buffer);
  					if (!_bundle.simulate()) {
  						USBFlash.write(buffer);
  					}
  				}
  				p=null;
  				if (!_bundle.simulate()) {
  					p = USBFlash.readCommandReply(true);
  					log.info("      download status : "+p.getResponse());
  				}
  				command="Repartition:"+partnumber;
  				log.info("   "+command);
  				if (!_bundle.simulate()) {
  					USBFlash.write(command.getBytes());
  					p = USBFlash.readCommandReply(true);
  					log.info("   Repartition status : "+p.getResponse());
  				}
  			}
  		}
  		tarIn.close();
  		
      }
  
  	public void flashImage(SinFile sin,String partitionname) throws X10FlashException, IOException {
  		//wrotedata=true;
  		String command="";
  		log.info("processing "+sin.getName());
  		command = "download:"+HexDump.toHex(sin.getHeader().length);
  		CommandPacket p=null;
  		if (!_bundle.simulate()) {
  			log.info("      "+command);
  			USBFlash.write(command.getBytes());
  			p = USBFlash.readCommandReply(false);
  			USBFlash.write(sin.getHeader());
  			p = USBFlash.readCommandReply(true);
  			log.info("      download status : "+p.getResponse());
  			command="signature";
  			log.info("      "+command);
  			USBFlash.write(command.getBytes());
  			p = USBFlash.readCommandReply(true);
  			log.info("   signature status : "+p.getResponse());
  		}
  		command="erase:"+partitionname;
  		log.info("   "+command);
  		if (!_bundle.simulate()) {
  			USBFlash.write(command.getBytes());
  			p = USBFlash.readCommandReply(true);
  			log.info("   erase status : "+p.getResponse());
  		}
  		TarArchiveInputStream tarIn = sin.getTarInputStream();
  		TarArchiveEntry entry=null;
  		while ((entry = tarIn.getNextTarEntry()) != null) {
  			if (!entry.getName().endsWith("cms")) {
  				log.info("   sending "+entry.getName());
  				if (!_bundle.simulate()) {
  					command = "download:"+HexDump.toHex((int)entry.getSize());
  					log.info("      "+command);
  					USBFlash.write(command.getBytes());
  					p = USBFlash.readCommandReply(false);
  				}
  				CircularByteBuffer cb = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
  				IOUtils.copy(tarIn, cb.getOutputStream());
  				LogProgress.initProgress(cb.getAvailable()/USBFlash.getUSBBufferSize()+1);
  				while (cb.getAvailable()>0) {
  					byte[] buffer = new byte[cb.getAvailable()>=USBFlash.getUSBBufferSize()?USBFlash.getUSBBufferSize():cb.getAvailable()];
  					cb.getInputStream().read(buffer);
  					LogProgress.updateProgress();
  					if (!_bundle.simulate()) {
  						USBFlash.write(buffer);
  					}
  					else {
  						try {
  							Thread.sleep(10);} catch (Exception e) {}
  					}
  				}
  				if (!_bundle.simulate()) {
  					p = USBFlash.readCommandReply(true);
  					log.info("      download status : "+p.getResponse());
  				}
  				command="flash:"+partitionname;
  				log.info("      "+command);
  				if (!_bundle.simulate()) {
  					USBFlash.write(command.getBytes());
  					p = USBFlash.readCommandReply(true);
  					log.info("      flash status : "+p.getResponse());
  				}
  				LogProgress.initProgress(0);
  			}
  		}
  		
  		tarIn.close();
  	}
  
  	
  	public String getIMEI() {
  		return phoneprops.getProperty("Phone-id").split(":")[1];
  	}
  	
  	public String getRootingStatus() {
  		return phoneprops.getProperty("Rooting-status");
  	}
  
  	public void sendLoader() throws FileNotFoundException, IOException, X10FlashException, SinFileException {
  		
  	}
  	
  	public void backupTA() {
      	log.info("Making a TA backup");
      	String timeStamp = OS.getTimeStamp();
      	LogProgress.initProgress(24000);
      	try {
      		backupTA(1, timeStamp);
      	} catch (Exception e) {}
      	try {
      		backupTA(2, timeStamp);
      	} catch (Exception e) {}
      	LogProgress.initProgress(0);
  	}
  	
  	private void backupTA(int partition, String timestamp) {
  		log.info("Saving TA partition "+partition);
      	String folder = OS.getFolderRegisteredDevices()+File.separator+getPhoneProperty("serialno")+File.separator+"s1ta"+File.separator+timestamp;
      	new File(folder).mkdirs();
      	TextFile tazone = new TextFile(folder+File.separator+partition+".ta","ISO8859-1");
      	try {
      		tazone.open(false);
      	} catch (Exception e1) {
      		log.error("Unable to create backup file");
      		return;
      	}
      	try {
      		tazone.writeln(HexDump.toHex((byte)partition));
      		for (int unit = 0 ; unit < 12000; unit++) {
      			LogProgress.updateProgress();
      			try {
      				TAUnit taunit = this.readTA(partition, unit, false);
      				if (taunit != null) 
      					tazone.writeln(taunit.toString());
      			} catch (Exception e3) {
      				e3.printStackTrace();
      			}
      		}
      		tazone.close();
  	        log.info("TA partition "+partition+" saved to "+folder+File.separator+partition+".ta");
      	} catch (Exception e) {
      		e.printStackTrace();
      	}
  	}
  	
  	public String getCurrentDevice() {
  		return currentdevice;
  	}
  
  	public String getSerial() {
  		return serial;
  	}
  
  	public void loadTAFiles() throws FileNotFoundException, IOException,X10FlashException {
  		Iterator<Category> entries = _bundle.getMeta().getTAEntries(true).iterator();
  			while (entries.hasNext()) {
  				Category categ = entries.next();
  				Iterator<BundleEntry> icateg = categ.getEntries().iterator();
  				while (icateg.hasNext()) {
  					BundleEntry bent = icateg.next();
  					if (bent.getName().toUpperCase().endsWith(".TA")) {
  						if (!bent.getName().toUpperCase().contains("SIMLOCK")) {
  							try {
  								TAFileParser ta = new TAFileParser(new File(bent.getAbsolutePath()));
  								Iterator<TAUnit> i = ta.entries().iterator();
  								while (i.hasNext()) {
  									TAUnit ent = i.next();
  									if  (ent.getUnitNumber()!=2010)
  										TaPartition2.put(ent.getUnitNumber(),ent);
  									else
  										log.warn("Unit "+ent.getUnitNumber()+" is ignored");
  								}
  							}
  							catch (TAFileParseException tae) {
  								log.error("Error parsing TA file. Skipping");
  							}
  						}
  						else {
  							log.warn("File "+bent.getName()+" is ignored");
  						}
  					}
  				}
  			}
  		try {
  			if (bc!=null) {
  				TAFileParser taf = new TAFileParser(new File(bc.getTA()));
  				Iterator<TAUnit> i = taf.entries().iterator();
  				while (i.hasNext()) {
  					TAUnit ent = i.next();
  					TaPartition2.put(ent.getUnitNumber(),ent);
  				}
  			}
  		} catch (Exception e) {
  			e.printStackTrace();
  		}
      }
  }
  