package gps.garmin.img;
import gps.garmin.img.structure.IMG;
import gps.garmin.img.structure.data.MapLevel;
import gps.garmin.img.structure.data.ObjectType;
import gps.garmin.img.structure.data.Point;
import gps.garmin.img.structure.data.PointSubtype;
import gps.garmin.img.structure.data.PointType;
import gps.garmin.img.structure.data.Polygon;
import gps.garmin.img.structure.data.PolygonType;
import gps.garmin.img.structure.data.Polyline;
import gps.garmin.img.structure.data.PolylineType;
import gps.garmin.img.structure.data.Subdivision;
import gps.garmin.img.structure.data.Subfile;
import gps.garmin.img.structure.data.SubfileHeaderTRE;
import gps.garmin.img.structure.data.SubfileLBL;
import gps.garmin.img.structure.data.SubfileMDR;
import gps.garmin.img.structure.data.SubfileNET;
import gps.garmin.img.structure.data.SubfileNOD;
import gps.garmin.img.structure.data.SubfileNone;
import gps.garmin.img.structure.data.SubfileRGN;
import gps.garmin.img.structure.data.SubfileTRE;
import gps.garmin.img.structure.fat.FATBlock;
import gps.garmin.img.structure.fat.FATBlockType;
import gps.garmin.img.structure.Header;
import gps.garmin.img.structure.fat.PartitionTable;
import gps.garmin.img.structure.data.SubfileCommonHeader;
import gps.garmin.img.structure.data.SubfileLockStatus;
import gps.garmin.img.structure.data.SubfileType;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
public abstract class Parser {
protected IMG img;
protected boolean isXorEd;
protected int xorByte;
protected int currentByte;
protected int[] currentBytes;
protected int currentByteQuantity;
private HashMap<SubfileType, FATBlock> fatBlocks;
private boolean alreadyParsed;
protected Parser(String name) {
img = new IMG();
img.setName(name);
fatBlocks = new HashMap<SubfileType, FATBlock>();
alreadyParsed = false;
}
public void parse() throws ParseException {
try {
if (alreadyParsed) {
throw new ParseException("IMG file already parsed");
}
parseHeader();
parseTRESubfile();
alreadyParsed = true;
} catch (ParseException e) {
System.out.println("Current byte quantity: " + currentByteQuantity);
System.out.println("Current byte: " + currentByte);
System.out.print("Current bytes:");
for (int b : currentBytes) {
System.out.print(" " + b);
}
System.out.println("");
System.out.println("Current byte pointer: " + getBytePointer());
throw e;
} catch (RuntimeException e) {
System.out.println("Current byte quantity: " + currentByteQuantity);
System.out.println("Current byte: " + currentByte);
System.out.print("Current bytes:");
for (int b : currentBytes) {
System.out.print(" " + b);
}
System.out.println("");
System.out.println("Current byte pointer: " + getBytePointer());
throw e;
}
}
public IMG getIMG() throws ParseException {
if (!alreadyParsed) {
throw new ParseException("IMG file not parsed yet");
}
return img;
}
protected abstract int nextByte() throws ParseException;
protected abstract void nextBytes(int quantity) throws ParseException;
protected abstract long getBytePointer() throws ParseException;
protected abstract void seek(long offset) throws ParseException;
protected abstract void skipBytes(int length) throws ParseException;
protected void parseHeader() throws ParseException {
Header imgHeader = new Header();
img.setImgHeader(imgHeader);
parseXorByte(imgHeader);
// 0x00 x9
skipBytes(9);
// Update month
imgHeader.setUpdateMonth(nextByte());
// Update year
nextByte();
if (currentByte <= 0x62) {
imgHeader.setUpdateYear(currentByte + 2000);
} else { // >= 0x63
imgHeader.setUpdateYear(currentByte + 1900);
}
// 0x00 x3
skipBytes(3);
// Checksum
imgHeader.setChecksum(nextByte());
// Signature
imgHeader.setSignature(parseString(7));
// 0x02 x1
skipBytes(1);
// Sector? x2
skipBytes(2);
// Heads? x2
skipBytes(2);
// Cylinders? x2
skipBytes(2);
// 0x00 x2
skipBytes(2);
// 0x00 x25
skipBytes(25);
// Creation date
imgHeader.setCreationDate(parseDate());
// 0x02 x1
skipBytes(1);
// Map file identifier
imgHeader.setMapFileIdentifier(parseString(7));
// 0x00 x1
skipBytes(1);
// Description (first)
imgHeader.setDescription(parseString(20));
// Heads? x2
skipBytes(2);
// Sectors? x2
skipBytes(2);
// Exponent1
imgHeader.setBlockSizeExponent1(nextByte());
// Exponent2
imgHeader.setBlockSizeExponent2(nextByte());
// FAT block size
imgHeader.setFatBlockSize(
(int)
Math.pow(
2, imgHeader.getBlockSizeExponent1() + imgHeader.getBlockSizeExponent2()));
// ???
skipBytes(2);
// Description (second)
imgHeader.setDescription(imgHeader.getDescription() + parseString(31));
// 0x00 x314
skipBytes(314);
// Partition table
PartitionTable imgPartitionTable = new PartitionTable();
imgHeader.setPartitionTable(imgPartitionTable);
// Boot
imgPartitionTable.setBoot(nextByte());
// Start head
imgPartitionTable.setStartHead(nextByte());
// Start sector
imgPartitionTable.setStartSector(nextByte());
// Start cylinder
imgPartitionTable.setStartCylinder(nextByte());
// System type
imgPartitionTable.setSystemType(nextByte());
// End head
imgPartitionTable.setEndHead(nextByte());
// End sector
imgPartitionTable.setEndSector(nextByte());
// End cylinder
imgPartitionTable.setEndCylinder(nextByte());
// Rel sectors
imgPartitionTable.setRelSectors(getLittleEndianDWord(4));
// Number of sectors
imgPartitionTable.setNumberOfSectors(getLittleEndianDWord(4));
// 0x00 x48
skipBytes(48);
// 0x55
if (nextByte() != 0x55) {
throw new IllegalStateException(); // fixme
}
// 0xAA
if (nextByte() != 0xAA) {
throw new IllegalStateException(); // fixme
}
// End of partition table
// 0x00 x512
skipBytes(512);
// 0x01 x1
skipBytes(1);
// 0x20 x11
skipBytes(11);
// First subfile offset
imgHeader.setFirstSubfileOffset(getLittleEndianWord(4));
// 0x03 x 1
skipBytes(1);
// 0x00 x 15
skipBytes(15);
// Block sequence numbers
int blockSequenceCounter = 0;
for (int i = 0; i < 240; i++) {
int blockSequence = getLittleEndianWord(2);
if (blockSequence == blockSequenceCounter) {
imgHeader.getBlockSequence().add(blockSequence);
blockSequenceCounter++;
} else {
// Fill block sequence
// fixme, que debo hacer acá?
}
}
// Header size
imgHeader.setHeaderSize(
imgHeader.getFatBlockSize() * imgHeader.getBlockSequence().size());
// FAT blocks total size within header
imgHeader.setFatBlocksTotalSize(imgHeader.getFirstSubfileOffset() - 0x600);
// FAT Blocks
while (getBytePointer() < imgHeader.getFirstSubfileOffset()) {
FATBlock fatBlock = new FATBlock();
imgHeader.getFatBlocks().add(fatBlock);
// Block type
if (nextByte() == 0x01) {
fatBlock.setType(FATBlockType.TRUE);
} else {
fatBlock.setType(FATBlockType.DUMMY);
}
if (fatBlock.getType() == FATBlockType.TRUE) {
// Subfile name
fatBlock.setSubfileName(parseString(8));
// Subfile type
fatBlock.setSubfileType(SubfileType.valueOf(parseString(3)));
// Subfile size
fatBlock.setSubfileSizeInBytes(getLittleEndianWord(4));
// Subfile part
fatBlock.setSubfilePart(getLittleEndianDWord(2));
// Subfile offset
if (fatBlock.getSubfilePart() == 0) {
fatBlock.setSubfileOffset(
blockSequenceCounter * imgHeader.getFatBlockSize());
}
// 0x00 x14
skipBytes(14);
// Block sequence numbers
for (int i = 0; i < 240; i++) {
int blockSequence = getLittleEndianWord(2);
if (blockSequence == blockSequenceCounter) {
fatBlock.getBlockSequence().add(blockSequence);
blockSequenceCounter++;
} else {
// Fill block sequence
// fixme, debo hacer algo acá?
}
}
} else {
skipBytes(8 + 3 + 4 + 2 + 14 + 480); // Total 511
fatBlock.setSubfileType(SubfileType.NONE);
}
if (fatBlock.getSubfilePart() == 0) {
fatBlocks.put(fatBlock.getSubfileType(), fatBlock);
} else {
FATBlock firstFatBlock = fatBlocks.get(fatBlock.getSubfileType());
while (firstFatBlock.getNextFatBlock() != null) {
firstFatBlock = firstFatBlock.getNextFatBlock();
}
firstFatBlock.setNextFatBlock(fatBlock);
}
}
}
protected void parseXorByte(final Header imgHeader) throws ParseException {
// XOR'ed?
nextByte();
if (currentByte != 0) {
xorByte = currentByte;
imgHeader.setXorEd(true);
imgHeader.setXorByte(xorByte);
isXorEd = true;
} else {
imgHeader.setXorEd(false);
isXorEd = false;
}
}
protected void parseTRESubfile() throws ParseException {
FATBlock treFatBlock = fatBlocks.get(SubfileType.TRE);
seek(treFatBlock.getSubfileOffset());
SubfileTRE subfile =
(SubfileTRE) parseSubfileCommonHeader(treFatBlock.getSubfileOffset());
SubfileHeaderTRE treHeader = subfile.getHeader();
// North boundary
treHeader.setNorthBoundary(Utils.convertMapUnitsToDegrees(getSignedInteger(3)));
// East boundary
treHeader.setEastBoundary(Utils.convertMapUnitsToDegrees(getSignedInteger(3)));
// South boundary
treHeader.setSouthBoundary(Utils.convertMapUnitsToDegrees(getSignedInteger(3)));
// West boundary
treHeader.setWestBoundary(Utils.convertMapUnitsToDegrees(getSignedInteger(3)));
// Map levels section offset
treHeader.setMapLevelsSectionOffset(getLittleEndianWord(4));
// Map levels section size
treHeader.setMapLevelsSectionSize(getLittleEndianWord(4));
// Subdivisions section offset
treHeader.setSubdivisionsSectionOffset(getLittleEndianWord(4));
// Subdivisions section size
treHeader.setSubdivisionsSectionSize(getLittleEndianWord(4));
// Copyright section offset
treHeader.setCopyrightSectionOffset(getLittleEndianWord(4));
// Copyright section size
treHeader.setCopyrightSectionSize(getLittleEndianWord(4));
// Copyright record size
treHeader.setCopyrightRecordSize(getLittleEndianWord(2));
// 0x00 x4
skipBytes(4);
// POI display flags
nextByte();
treHeader.setTransparentMap((currentByte & 1) == 1);
treHeader.setShowStreetBeforeStreetNumber((currentByte & 2) == 2);
treHeader.setShowZipBeforeCity((currentByte & 4) == 4);
skipBytes(3);
skipBytes(4);
// 0x0001
skipBytes(2);
// 0x00
skipBytes(1);
// Polyline overview section offset
treHeader.setPolylineOverviewSectionOffset(getLittleEndianWord(4));
// Polyline overview section length
treHeader.setPolylineOverviewSectionLength(getLittleEndianWord(4));
// Polyline overview records size
treHeader.setPolylineOverviewRecordsSize(getLittleEndianWord(2));
skipBytes(2);
// 0x0000
skipBytes(2);
// Polygon overview section offset
treHeader.setPolygonOverviewSectionOffset(getLittleEndianWord(4));
// Polygon overview section length
treHeader.setPolygonOverviewSectionLength(getLittleEndianWord(4));
// Polygon overview records size
treHeader.setPolygonOverviewRecordsSize(getLittleEndianWord(2));
skipBytes(2);
// 0x0000
skipBytes(2);
// Point overview section offset
treHeader.setPointOverviewSectionOffset(getLittleEndianWord(4));
// Point overview section length
treHeader.setPointOverviewSectionLength(getLittleEndianWord(4));
// Point overview records size
treHeader.setPointOverviewRecordsSize(getLittleEndianWord(2));
skipBytes(2);
// 0x0000
skipBytes(2);
// Header longer than 116 bytes
if (subfile.getCommonHeader().getHeadersLength() > SubfileHeaderTRE.LENGTH_116) {
// Map ID
treHeader.setMapID(String.valueOf(getLittleEndianWord(4)));
// Header longer than 120 bytes
if (subfile.getCommonHeader().getHeadersLength() > SubfileHeaderTRE.LENGTH_120) {
// 0x00000000
skipBytes(4);
// TRE7 section offset
treHeader.setTre7SectionOffset(getLittleEndianWord(4));
// TRE7 section length
treHeader.setTre7SectionLength(getLittleEndianWord(4));
// TRE7 records size
treHeader.setTre7RecordsSize(getLittleEndianWord(2));
skipBytes(4);
// TRE8 section offset
treHeader.setTre8SectionOffset(getLittleEndianWord(4));
// TRE8 secion length
treHeader.setTre8SectionLength(getLittleEndianWord(4));
skipBytes(4);
// 0x0000
skipBytes(4);
// Header longer than 154 bytes
if (subfile.getCommonHeader().getHeadersLength() > SubfileHeaderTRE.LENGTH_154) {
// Map level encryption key
treHeader.setMapLevelsSectionEncryptionKey(parseString(20));
// TRE9 section offset
treHeader.setTre9SectionOffset(getLittleEndianWord(4));
// TRE9 section length
treHeader.setTre9SectionLength(getLittleEndianWord(4));
// TRE9 records size
treHeader.setTre9RecordsSize(getLittleEndianWord(2));
// 0x00000000
skipBytes(4);
}
}
}
// Map descriptor
String mapDescriptor = "";
nextByte();
while (currentByte != 0) {
mapDescriptor += (char) currentByte;
nextByte();
}
treHeader.setMapDescriptor(mapDescriptor);
// Copyright section
seek(treFatBlock.getSubfileOffset() + treHeader.getCopyrightSectionOffset());
int numberOfRecords =
treHeader.getCopyrightSectionSize() / treHeader.getCopyrightRecordSize();
for (int i = 0; i < numberOfRecords; i++) {
subfile.getCopyrights().add(getLittleEndianWord(treHeader.getCopyrightRecordSize()));
}
// Map levels section
seek(treFatBlock.getSubfileOffset() + treHeader.getMapLevelsSectionOffset());
int numberOfLevels = treHeader.getMapLevelsSectionSize() / 4; // fixme, porque 4?
for (int i = 0; i < numberOfLevels; i++) {
MapLevel mapLevel = new MapLevel();
mapLevel.setNumber(numberOfLevels - i - 1);
// Inherited
nextByte();
mapLevel.setInherited((currentByte & 128) == 128);
// Zoom level
mapLevel.setZoomLevel(currentByte & 15);
// Bits per coord
mapLevel.setBitsPerCoordinate(nextByte());
// Subdivisions
mapLevel.setQuantityOfSubdivisions(getLittleEndianWord(2));
subfile.getMapLevels().add(mapLevel);
}
// Subdivisions section
seek(treFatBlock.getSubfileOffset() + treHeader.getSubdivisionsSectionOffset());
boolean firstSubdivision = true;
int subdivisionCounter = 1;
Subdivision contiguousPreviousSubdivision = null;
Collection<Subdivision> subdivisionsTemp = new ArrayList<Subdivision>();
for (MapLevel mapLevel : subfile.getMapLevels()) {
for (int i = 0; i < mapLevel.getQuantityOfSubdivisions(); i++) {
Subdivision subdivision = new Subdivision();
subdivision.setLevel(mapLevel);
subdivision.setNumber(subdivisionCounter++);
// Offset in RGN subfile
subdivision.setOffsetInRGNSubfile(getLittleEndianWord(3));
// Object types
subdivision.setObjectTypes(getTRESubfileObjectTypes());
// Longitude center
subdivision.setLongitudeCenter(
Utils.convertMapUnitsToDegrees(getSignedInteger(3)));
// Latitude center
subdivision.setLatitudeCenter(
Utils.convertMapUnitsToDegrees(getSignedInteger(3)));
// Width
int value = getLittleEndianWord(2);
subdivision.setWidth(value & 32767);
subdivision.setTotalWidth((subdivision.getWidth() * 2) + 1);
subdivision.setTerminatingFlag((value & 32768) == 32768);
if (contiguousPreviousSubdivision != null) {
contiguousPreviousSubdivision.setContiguousSubdivision(subdivision);
}
if (subdivision.isTerminatingFlag()) {
contiguousPreviousSubdivision = null;
} else {
contiguousPreviousSubdivision = subdivision;
}
// Height
subdivision.setHeight(getLittleEndianWord(2));
subdivision.setTotalHeight((subdivision.getHeight() * 2) + 1);
if (mapLevel.getNumber() != 0) {
// Next level subdivision
int nextSubdivisionNumber = getLittleEndianWord(2);
if (nextSubdivisionNumber != 0) {
Subdivision subdivisionTemp = new Subdivision();
subdivisionTemp.setNumber(nextSubdivisionNumber);
subdivision.setNextLevelSubdivision(subdivisionTemp);
}
}
if (firstSubdivision) {
subfile.setFirstSubdivision(subdivision);
firstSubdivision = false;
}
for (Subdivision subdivisionTemp : subdivisionsTemp) {
if (subdivisionTemp.getNextLevelSubdivision() != null) {
if (subdivisionTemp.getNextLevelSubdivision().getNumber() == subdivision.getNumber()) {
subdivisionTemp.setNextLevelSubdivision(subdivision);
}
}
}
subdivisionsTemp.add(subdivision);
}
}
// Polyline section
seek(treFatBlock.getSubfileOffset() + treHeader.getPolylineOverviewSectionOffset());
int numberOfPolylines =
treHeader.getPolylineOverviewSectionLength() /
treHeader.getPolylineOverviewRecordsSize();
for (int i = 0; i < numberOfPolylines; i++) {
Polyline polyline = new Polyline();
polyline.setNumber(i + 1);
// Polyline type
nextByte(); // fixme, siempre es por default? no se usa el byte?
polyline.setType(PolylineType.DEFAULT);
// Maximum level
polyline.setMaximumLevelWherePresent(nextByte());
if (treHeader.getPolylineOverviewRecordsSize() == 3) {
skipBytes(1);
}
subfile.getPolylines().add(polyline);
}
// Polygon section
seek(treFatBlock.getSubfileOffset() + treHeader.getPolygonOverviewSectionOffset());
int numberOfPolygons =
treHeader.getPolygonOverviewSectionLength() /
treHeader.getPolygonOverviewRecordsSize();
for (int i = 0; i < numberOfPolygons; i++) {
Polygon polygon = new Polygon();
polygon.setNumber(i + 1);
// Polygon type
nextByte(); // fixme, siempre es por default? no se usa el byte?
polygon.setType(PolygonType.DEFAULT);
// Maximum level
polygon.setMaximumLevelWherePresent(nextByte());
if (treHeader.getPolygonOverviewRecordsSize() == 3) {
skipBytes(1);
}
subfile.getPolygons().add(polygon);
}
// Point section
seek(treFatBlock.getSubfileOffset() + treHeader.getPointOverviewSectionOffset());
int numberOfPoints =
treHeader.getPointOverviewSectionLength() /
treHeader.getPointOverviewRecordsSize();
for (int i = 0; i < numberOfPoints; i++) {
Point point = new Point();
point.setNumber(i + 1);
// Point type
nextByte(); // fixme, siempre es por default? no se usa el byte?
point.setType(PointType.DEFAULT);
// Maximum level
point.setMaximumLevelWherePresent(nextByte());
// Point subtype
nextByte(); // fixme, siempre es por default? no se usa el byte?
point.setSubtype(PointSubtype.DEFAULT);
subfile.getPoints().add(point);
}
img.getSubfiles().add(subfile);
}
protected final Subfile parseSubfileCommonHeader(int subfileOffset) throws ParseException {
Subfile subfile = null;
// Subfile common header
SubfileCommonHeader commonHeader = new SubfileCommonHeader();
// Subfile headers length
commonHeader.setHeadersLength(getLittleEndianWord(2));
// Subfile type
commonHeader.setType(SubfileType.getType(parseString(10)));
switch (commonHeader.getType()) {
case RGN: subfile = new SubfileRGN(); break;
case TRE: subfile = new SubfileTRE(); break;
case LBL: subfile = new SubfileLBL(); break;
case NOD: subfile = new SubfileNOD(); break;
case NET: subfile = new SubfileNET(); break;
case MDR: subfile = new SubfileMDR(); break;
default: subfile = new SubfileNone(); break;
}
subfile.setOffset(subfileOffset);
subfile.setCommonHeader(commonHeader);
// 0x01 x1
skipBytes(1);
// Subfile lock status
if (nextByte() == 0) {
commonHeader.setLockStatus(SubfileLockStatus.NOT_LOCKED);
} else {
commonHeader.setLockStatus(SubfileLockStatus.LOCKED);
}
// Subfile creation date
commonHeader.setCreationDate(parseDate());
return subfile;
}
protected final Collection<ObjectType> getTRESubfileObjectTypes() throws ParseException {
Collection<ObjectType> result = new ArrayList<ObjectType>();
int value = nextByte();
while (value > 0) {
if (value >= 0x80) {
result.add(ObjectType.POLYGON);
value -= 0x80;
} else if (value >= 0x40) {
result.add(ObjectType.POLYLINE);
value -= 0x40;
} else if (value >= 0x20) {
result.add(ObjectType.INDEXED_POINT);
value -= 0x20;
} else {
result.add(ObjectType.POINT);
value -= 0x10;
}
}
return result;
}
protected final Date parseDate() throws ParseException {
Calendar calendar = Calendar.getInstance();
calendar.setLenient(false);
calendar.set(Calendar.MILLISECOND, 0);
// Year
calendar.set(Calendar.YEAR, getLittleEndianWord(2));
// Month
calendar.set(Calendar.MONTH, nextByte() - 1);
// Day
calendar.set(Calendar.DAY_OF_MONTH, nextByte());
// Hour
calendar.set(Calendar.HOUR_OF_DAY, nextByte());
// Minute
calendar.set(Calendar.MINUTE, nextByte());
// Second
calendar.set(Calendar.SECOND, nextByte());
return calendar.getTime();
}
protected final String parseString(int length) throws ParseException {
nextBytes(length);
return new String(currentBytes, 0, length);
}
protected final int getLittleEndianWord(int length) throws ParseException {
nextBytes(length);
int result = 0;
for (int i = currentByteQuantity - 1; i >= 0; i--) {
result += (currentBytes[i] << (8 * i));
}
return result;
}
protected final int getLittleEndianDWord(int length) throws ParseException {
nextBytes(length);
int result = 0;
for (int i = currentByteQuantity - 1; i >= 0; i = i - 2) {
result += (currentBytes[i - 1] << (8 * i));
result += (currentBytes[i] << (8 * (i - 1)));
}
return result;
}
protected final int getSignedInteger(int length) throws ParseException {
int value = getLittleEndianWord(length);
int positiveMaximum = 0x7F;
int negativeMaximum = 0xFF;
for (int i = 0; i < currentByteQuantity - 1; i++) {
positiveMaximum = (positiveMaximum << 8) + 0xFF;
negativeMaximum = (negativeMaximum << 8) + 0xFF;
}
if (value > positiveMaximum) {
value = value - negativeMaximum;
}
return value;
}
}