content: package org.flashtool.parsers.sin.v1;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.RandomAccessFile;
  
  import org.flashtool.logger.LogProgress;
  import org.flashtool.parsers.sin.v2.HashBlock;
  import org.flashtool.parsers.sin.v2.HashBlocks;
  import org.flashtool.system.OS;
  import org.flashtool.util.BytesUtil;
  
  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 SinParser {
  	  @Bin public byte multipleHeaders;
  	  @Bin public int headerLen;
  	  @Bin public byte payloadType;
  	  @Bin public short unknown;
  	  @Bin public byte memId;
  	  @Bin public byte compression;
  	  @Bin public int hashLen;
  	  public HashBlocks blocks;
  	  public PartitionInfo parti;
  	  public int certLen;
  	  public byte[] cert;
  	  public byte[] partitioninfo = null;
  	  private File sinfile;
  	  private long size;
  	  private long dataSize=0L;
  	  String dataType;
  
  	  public void parseHash(JBBPBitInputStream sinStream) throws IOException {
  		  JBBPParser hashBlocksV2 = JBBPParser.prepare(
  		            "block[_] {int offset;"
  		                    + "int length;"
  		                    + "byte hashLen;"
  		                    + "byte[hashLen] crc;}"
  		  );
  		  if (hashLen>0) {
  			  byte[] hashBlocks = sinStream.readByteArray(hashLen);
  			  blocks = hashBlocksV2.parse(hashBlocks).mapTo(new org.flashtool.parsers.sin.v2.HashBlocks());
  			  certLen = sinStream.readInt(JBBPByteOrder.BIG_ENDIAN);
  			  cert = sinStream.readByteArray(certLen);
  			  if (blocks.block.length==1 && blocks.block[0].offset!=0) blocks.block[0].offset=0;
  			  if (blocks.block[0].length==16) {
  				  partitioninfo = sinStream.readByteArray(16);
  				  JBBPParser partInfo = JBBPParser.prepare(
  				            "<int mot1;"
  				          + "<int mot2;"
  				          + "<int offset;"
  				          + "<int blockcount;"
  				  );
  				  parti = partInfo.parse(partitioninfo).mapTo(new org.flashtool.parsers.sin.v1.PartitionInfo());
  				  if (blocks.block.length>1)
  					  dataSize=parti.blockcount*blocks.block[1].length;
  			  }
  			  blocks.setSpare(this.payloadType);
  		  }
  		  dataType=getDataTypePriv();
  	  }
  
  	  public void setFile(File f) {
  		  sinfile = f;
  	  }
  
  	  public void setLength(long s) {
  		  size=s;
  	  }
  
  	  public String getDataType() {
  		  return dataType;
  	  }
  	  
  	  public long getDataSize() {
  		  return dataSize;
  	  }
  	  
  	  public byte[] getHeader()  throws IOException {
  			RandomAccessFile fin = new RandomAccessFile(sinfile,"r");
  			byte[] buff = new byte[headerLen];
  			fin.read(buff);
  			fin.close();
  			return buff;
  	  }
  
  	  public boolean hasPartitionInfo() {
  		  return partitioninfo!=null;
  	  }
  
  	  public byte[] getPartitionInfo() throws IOException {
  		    return partitioninfo;
  	  }
  
  		public String getDataTypePriv(byte[] res) throws IOException {
  			if (BytesUtil.startsWith(res, new byte[] {0x7F,0x45,0x4C,0x46})) return "elf";
  			if (BytesUtil.startsWith(res, new byte[] {0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, (byte)0xFF, (byte)0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})) return "yaffs2";
  			int pos = BytesUtil.indexOf(res, new byte[]{(byte)0x53,(byte)0xEF,(byte)0x01,(byte)0x00});
  			if (pos==-1) return "unknown";
  			pos = pos - 56;
  			byte[] header = new byte[58];
  			System.arraycopy(res, pos, header, 0, header.length);
  			byte[] bcount = new byte[4];
  			System.arraycopy(header, 4, bcount, 0, bcount.length);
  			BytesUtil.revert(bcount);
  			long blockcount = BytesUtil.getInt(bcount);
  			dataSize = blockcount*4L*1024L;
  			return "ext4";
  		}
  
  		public String getDataTypePriv() throws IOException {
  			RandomAccessFile fin = new RandomAccessFile(sinfile,"r");
  			byte[] res=null;
  			fin.seek(headerLen);
  			if (hashLen==0) {
  				res = new byte[(int)size-headerLen];
  				fin.read(res);
  			}
  			else {
  				int i=0;
  				while (i < blocks.block.length && blocks.block[i].offset==0 ) {
  					res = new byte[blocks.block[i].length];
  					fin.read(res);
  					i++;
  				}
  			}
  			fin.close();
  			return getDataTypePriv(res);
  		}
  
  		public void dumpImage() throws IOException {
  				try {
  					RandomAccessFile fin = new RandomAccessFile(sinfile,"r");
  					log.info("Generating container file");
  					String ext = "."+getDataType();
  					String foutname = sinfile.getAbsolutePath().substring(0, sinfile.getAbsolutePath().length()-4)+ext;
  					RandomAccessFile fout = OS.generateEmptyFile(foutname, dataSize, (byte)0xFF);
  					log.info("Finished Generating container file");
  					// Positionning in files
  					log.info("Extracting data into container");
  					fin.seek(headerLen);
  					LogProgress.initProgress(blocks.block.length);
  					for (int i=0;i<blocks.block.length;i++) {
  						HashBlock b = blocks.block[i];
  						byte[] data = new byte[b.length];
  						fin.read(data);
  						if (!b.validate(data)) throw new Exception("Corrupted data");
  						fout.seek(blocks.block.length==1?0:b.offset);
  						fout.write(data);
  						LogProgress.updateProgress();
  					}
  					LogProgress.initProgress(0);
  					fout.close();
  					log.info("Extraction finished to "+foutname);
  				}
  				catch (Exception e) {
  					log.error("Error while extracting data : "+e.getMessage());
  					LogProgress.initProgress(0);
  					e.printStackTrace();
  				}
  			}
  
  }