1 #include <QMap>
2 #include <QtEndian>
3 #include "vectortile.h"
4 #include "img.h"
5 
6 
7 typedef QMap<QByteArray, VectorTile*> TileMap;
8 
tileType(const char str[3])9 static SubFile::Type tileType(const char str[3])
10 {
11 	if (!memcmp(str, "TRE", 3))
12 		return SubFile::TRE;
13 	else if (!memcmp(str, "RGN", 3))
14 		return SubFile::RGN;
15 	else if (!memcmp(str, "LBL", 3))
16 		return SubFile::LBL;
17 	else if (!memcmp(str, "TYP", 3))
18 		return SubFile::TYP;
19 	else if (!memcmp(str, "GMP", 3))
20 		return SubFile::GMP;
21 	else if (!memcmp(str, "NET", 3))
22 		return SubFile::NET;
23 	else
24 		return SubFile::Unknown;
25 }
26 
IMG(const QString & fileName)27 IMG::IMG(const QString &fileName) : _file(fileName)
28 {
29 #define CHECK(condition) \
30 	if (!(condition)) { \
31 		_errorString = "Unsupported or invalid IMG file"; \
32 		qDeleteAll(tileMap); \
33 		return; \
34 	}
35 
36 	TileMap tileMap;
37 	QByteArray typFile;
38 
39 	if (!_file.open(QFile::ReadOnly)) {
40 		_errorString = _file.errorString();
41 		return;
42 	}
43 
44 	// Read IMG header
45 	char signature[7], identifier[7];
46 	_file.read((char*)&_key, 1) && _file.seek(0x10)
47 	  && read(signature, sizeof(signature)) && _file.seek(0x41)
48 	  && read(identifier, sizeof(identifier));
49 	if (memcmp(signature, "DSKIMG", sizeof(signature))
50 	  || memcmp(identifier, "GARMIN", sizeof(identifier))) {
51 		_errorString = "Not a Garmin IMG file";
52 		return;
53 	}
54 	char d1[20], d2[31];
55 	quint8 e1, e2;
56 	CHECK(_file.seek(0x49) && read(d1, sizeof(d1)) && _file.seek(0x61)
57 	  && readValue(e1) && readValue(e2) && _file.seek(0x65)
58 	  && read(d2, sizeof(d2)));
59 
60 	QByteArray nba(QByteArray(d1, sizeof(d1)) + QByteArray(d2, sizeof(d2)));
61 	_name = QString::fromLatin1(nba.constData(), nba.size()-1).trimmed();
62 	_blockBits = e1 + e2;
63 
64 	// Read the FAT table
65 	quint8 flag;
66 	quint64 offset = 0x200;
67 	// Skip unused FAT blocks if any
68 	while (true) {
69 		CHECK(_file.seek(offset) && readValue(flag));
70 		if (flag)
71 			break;
72 		offset += 512;
73 	}
74 
75 	// Read first FAT block with FAT table size
76 	char name[8], type[3];
77 	quint32 size;
78 	quint16 part;
79 	CHECK(_file.seek(offset + 12) && readValue(size));
80 	offset += 512;
81 	int cnt = (size - offset) / 512;
82 
83 	// Read FAT blocks describing the IMG sub-files
84 	for (int i = 0; i < cnt; i++) {
85 		quint16 block;
86 		CHECK(_file.seek(offset) && readValue(flag) && read(name, sizeof(name))
87 		  && read(type, sizeof(type)) && readValue(size) && readValue(part));
88 		SubFile::Type tt = tileType(type);
89 
90 		QByteArray fn(name, sizeof(name));
91 		if (VectorTile::isTileFile(tt)) {
92 			VectorTile *tile;
93 			TileMap::const_iterator it = tileMap.find(fn);
94 			if (it == tileMap.constEnd()) {
95 				tile = new VectorTile();
96 				tileMap.insert(fn, tile);
97 			} else
98 				tile = *it;
99 
100 			SubFile *file = part ? tile->file(tt)
101 			  : tile->addFile(this, tt);
102 			CHECK(file);
103 
104 			_file.seek(offset + 0x20);
105 			for (int i = 0; i < 240; i++) {
106 				CHECK(readValue(block));
107 				if (block == 0xFFFF)
108 					break;
109 				file->addBlock(block);
110 			}
111 		} else if (tt == SubFile::TYP) {
112 			SubFile *typ = 0;
113 			if (typFile.isNull()) {
114 				_typ = new SubFile(this);
115 				typ = _typ;
116 				typFile = fn;
117 			} else if (fn == typFile)
118 				typ = _typ;
119 
120 			if (typ) {
121 				_file.seek(offset + 0x20);
122 				for (int i = 0; i < 240; i++) {
123 					CHECK(readValue(block));
124 					if (block == 0xFFFF)
125 						break;
126 					typ->addBlock(block);
127 				}
128 			}
129 		}
130 
131 		offset += 512;
132 	}
133 
134 	// Create tile tree
135 	int minMapZoom = 24;
136 	for (TileMap::const_iterator it = tileMap.constBegin();
137 	  it != tileMap.constEnd(); ++it) {
138 		VectorTile *tile = it.value();
139 
140 		if (!tile->init()) {
141 			qWarning("%s: %s: Invalid map tile", qPrintable(_file.fileName()),
142 			  qPrintable(it.key()));
143 			delete tile;
144 			continue;
145 		}
146 
147 		double min[2], max[2];
148 		min[0] = tile->bounds().left();
149 		min[1] = tile->bounds().bottom();
150 		max[0] = tile->bounds().right();
151 		max[1] = tile->bounds().top();
152 		_tileTree.Insert(min, max, tile);
153 
154 		_bounds |= tile->bounds();
155 		if (tile->zooms().min() < _zooms.min())
156 			_zooms.setMin(tile->zooms().min());
157 		if (tile->zooms().min() < minMapZoom)
158 			minMapZoom = tile->zooms().min();
159 	}
160 
161 	// Detect and mark basemap
162 	TileTree::Iterator it;
163 	for (_tileTree.GetFirst(it); !_tileTree.IsNull(it); _tileTree.GetNext(it)) {
164 		VectorTile *tile = _tileTree.GetAt(it);
165 		if (tile->zooms().min() > minMapZoom)
166 			_baseMap = true;
167 		if (tile->zooms().min() == minMapZoom)
168 			tile->markAsBasemap();
169 	}
170 	// Allow some extra zoom out on maps without basemaps, but not too much as
171 	// this would kill the rendering performance
172 	if (!_baseMap)
173 		_zooms.setMin(_zooms.min() - 2);
174 
175 	if (!_tileTree.Count())
176 		_errorString = "No usable map tile found";
177 	else
178 		_valid = true;
179 }
180 
read(char * data,qint64 maxSize)181 qint64 IMG::read(char *data, qint64 maxSize)
182 {
183 	qint64 ret = _file.read(data, maxSize);
184 	if (_key)
185 		for (int i = 0; i < ret; i++)
186 			data[i] ^= _key;
187 	return ret;
188 }
189 
readValue(T & val)190 template<class T> bool IMG::readValue(T &val)
191 {
192 	T data;
193 
194 	if (read((char*)&data, sizeof(T)) < (qint64)sizeof(T))
195 		return false;
196 
197 	val = qFromLittleEndian(data);
198 
199 	return true;
200 }
201 
readBlock(int blockNum,char * data)202 bool IMG::readBlock(int blockNum, char *data)
203 {
204 	if (!_file.seek((quint64)blockNum << _blockBits))
205 		return false;
206 	if (read(data, 1U<<_blockBits) < 1U<<_blockBits)
207 		return false;
208 
209 	return true;
210 }
211