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