6 #include <QApplication>
8 #include <QImageReader>
9 #include <QCryptographicHash>
10 #include <QTemporaryDir>
11 #include "common/garmin.h"
12 #include "common/textcodec.h"
13 #include "common/color.h"
14 #include "common/util.h"
16 #include "gpiparser.h"
18 using namespace Garmin
;
27 class TranslatedString
{
30 TranslatedString(const QString
&lang
, const QString
&str
)
31 : _lang(lang
), _str(str
) {}
33 const QString
&str() const {return _str
;}
34 const QString
&lang() const {return _lang
;}
42 QDebug
operator<<(QDebug dbg
, const TranslatedString
&ts
)
44 dbg
.nospace() << "TS(" << ts
.lang() << ", " << ts
.str() << ")";
49 class CryptDevice
: public QIODevice
52 CryptDevice(QIODevice
*device
, quint32 key
, quint32 blockSize
,
54 bool isSequential() const {return true;}
57 qint64
readData(char *data
, qint64 maxSize
);
58 qint64
writeData(const char *, qint64
) {return -1;}
67 static void demangle(quint8
*data
, quint32 size
, quint32 key
)
69 static const unsigned char shuf
[] = {
77 quint8 sum
= shuf
[((key
>> 24) + (key
>> 16) + (key
>> 8) + key
) & 0xf];
79 for (quint32 i
= 0; i
< size
; i
++) {
80 quint8 hiAdd
= shuf
[key
>> (hiCnt
<< 2) & 0xf] + sum
;
81 loCnt
= (hiCnt
> 6) ? 0 : hiCnt
+ 1;
82 quint8 loAdd
= shuf
[key
>> (loCnt
<< 2) & 0xf] + sum
;
83 quint8 hi
= data
[i
] - (hiAdd
<< 4);
84 quint8 lo
= data
[i
] - loAdd
;
85 data
[i
] = (hi
& 0xf0) | (lo
& 0x0f);
86 hiCnt
= (loCnt
> 6) ? 0 : loCnt
+ 1;
90 CryptDevice::CryptDevice(QIODevice
*device
, quint32 key
, quint32 blockSize
,
91 QObject
*parent
) : QIODevice(parent
), _device(device
), _key(key
), _available(0)
93 _block
.resize(blockSize
);
94 setOpenMode(_device
->openMode());
97 qint64
CryptDevice::readData(char *data
, qint64 maxSize
)
103 cs
= qMin(maxSize
, (qint64
)_available
);
104 memcpy(data
, _block
.constData() + _block
.size() - _available
, cs
);
111 if ((rs
= _device
->read(_block
.data(), _block
.size())) < 0)
116 demangle((quint8
*)_block
.data(), _available
, _key
);
117 cs
= qMin(maxSize
, (qint64
)_available
);
118 memcpy(data
+ ts
, _block
.constData(), cs
);
128 class DataStream
: public QDataStream
131 DataStream(QIODevice
*d
) : QDataStream(d
) {}
133 void setCodepage(quint16 codepage
) {_codec
= TextCodec(codepage
);}
135 quint16
readString(QString
&str
);
137 quint8
readRecordHeader(RecordHeader
&hdr
);
138 quint32
readTranslatedObjects(QList
<TranslatedString
> &objects
);
140 quint32
skipRecord();
141 quint16
nextHeaderType();
147 quint16
DataStream::readString(QString
&str
)
153 readRawData(ba
.data(), len
);
154 str
= _codec
.toString(ba
);
159 qint32
DataStream::readInt24()
161 unsigned char data
[3];
164 readRawData((char*)data
, sizeof(data
));
165 val
= data
[0] | ((quint32
)data
[1]) << 8 | ((quint32
)data
[2]) << 16;
166 return (val
> 0x7FFFFF) ? (val
& 0x7FFFFF) - 0x800000 : val
;
169 quint16
DataStream::nextHeaderType()
173 if (device()->peek((char*)&type
, sizeof(type
)) < (qint64
)sizeof(type
)) {
174 setStatus(QDataStream::ReadCorruptData
);
177 return qFromLittleEndian(type
);
180 quint8
DataStream::readRecordHeader(RecordHeader
&hdr
)
182 *this >> hdr
.type
>> hdr
.flags
>> hdr
.size
;
185 return (hdr
.flags
& 0xA) ? 12 : 8;
188 quint32
DataStream::skipRecord()
191 quint8 rs
= readRecordHeader(rh
);
192 skipRawData(rh
.size
);
197 quint32
DataStream::readTranslatedObjects(QList
<TranslatedString
> &objects
)
199 qint32 size
= 0, ret
;
202 memset(lang
, 0, sizeof(lang
));
207 while (status() == QDataStream::Ok
&& size
> 0) {
209 readRawData(lang
, sizeof(lang
) - 1);
210 size
-= readString(str
) + 2;
211 objects
.append(TranslatedString(lang
, str
));
215 setStatus(QDataStream::ReadCorruptData
);
221 static quint32
readFileDataRecord(DataStream
&stream
)
227 QList
<TranslatedString
> obj
;
229 rs
= stream
.readRecordHeader(rh
);
230 stream
>> flags
>> s2
>> s3
;
233 ds
+= stream
.readTranslatedObjects(obj
);
235 ds
+= stream
.readTranslatedObjects(obj
);
236 // additional stuff depending on flags
240 stream
.setStatus(QDataStream::ReadCorruptData
);
241 else if (ds
< rh
.size
)
242 stream
.skipRawData(rh
.size
- ds
);
247 static quint32
readDescription(DataStream
&stream
, Waypoint
&waypoint
)
252 QList
<TranslatedString
> obj
;
254 rs
= stream
.readRecordHeader(rh
);
255 ds
= stream
.readTranslatedObjects(obj
);
257 waypoint
.setDescription(obj
.first().str());
260 stream
.setStatus(QDataStream::ReadCorruptData
);
265 static quint32
readNotes(DataStream
&stream
, Waypoint
&waypoint
)
271 rs
= stream
.readRecordHeader(rh
);
274 QList
<TranslatedString
> obj
;
275 ds
+= stream
.readTranslatedObjects(obj
);
277 waypoint
.setComment(obj
.first().str());
281 ds
+= stream
.readString(str
);
283 waypoint
.setComment(str
);
287 stream
.setStatus(QDataStream::ReadCorruptData
);
292 static quint32
readContact(DataStream
&stream
, Waypoint
&waypoint
)
299 QList
<TranslatedString
> obj
;
301 rs
= stream
.readRecordHeader(rh
);
305 ds
+= stream
.readString(str
);
306 waypoint
.setPhone(str
);
308 if (flags
& 0x2) // phone2
309 ds
+= stream
.readString(str
);
310 if (flags
& 0x4) // fax
311 ds
+= stream
.readString(str
);
313 ds
+= stream
.readString(str
);
314 waypoint
.addLink(Link("mailto:" + str
, str
));
317 ds
+= stream
.readString(str
);
319 waypoint
.addLink(Link(url
.scheme().isEmpty()
320 ? "http://" + str
: str
, str
));
322 if (flags
& 0x20) // unknown
323 ds
+= stream
.readTranslatedObjects(obj
);
326 stream
.setStatus(QDataStream::ReadCorruptData
);
331 static quint32
readAddress(DataStream
&stream
, Waypoint
&waypoint
)
337 QList
<TranslatedString
> obj
;
341 rs
= stream
.readRecordHeader(rh
);
345 ds
+= stream
.readTranslatedObjects(obj
);
347 addr
.setCity(obj
.first().str());
350 ds
+= stream
.readTranslatedObjects(obj
);
352 addr
.setCountry(obj
.first().str());
355 ds
+= stream
.readTranslatedObjects(obj
);
357 addr
.setState(obj
.first().str());
360 ds
+= stream
.readString(str
);
361 addr
.setPostalCode(str
);
364 ds
+= stream
.readTranslatedObjects(obj
);
366 addr
.setStreet(obj
.first().str());
368 if (flags
& 0x20) // unknown
369 ds
+= stream
.readString(str
);
372 waypoint
.setAddress(addr
.address());
375 stream
.setStatus(QDataStream::ReadCorruptData
);
380 static quint32
readImageInfo(DataStream
&stream
, Waypoint
&waypoint
,
381 const QString
&fileName
, int &imgId
)
387 rs
= stream
.readRecordHeader(rh
);
388 stream
>> s1
>> size
;
392 stream
.readRawData(ba
.data(), ba
.size());
394 if (Util::tempDir().isValid()) {
396 QImageReader
ir(&buf
);
398 QByteArray
id(fileName
.toUtf8() + QByteArray::number(imgId
++));
399 QFile
imgFile(Util::tempDir().path() + "/" + QString("%0.%1").arg(
400 QCryptographicHash::hash(id
, QCryptographicHash::Sha1
).toHex(),
401 QString(ir
.format())));
402 imgFile
.open(QIODevice::WriteOnly
);
406 waypoint
.addImage(imgFile
.fileName());
409 if (size
+ 5 != rh
.size
)
410 stream
.setStatus(QDataStream::ReadCorruptData
);
415 static int speed(quint16 flags
)
417 return (((flags
>> 3) & 0x0F) * 10) + (((flags
>> 2) & 1) * 5);
420 static QString
units(quint16 flags
)
422 return (flags
& (1<<8)) ? "km/h" : "mi/h";
425 static bool portable(quint16 flags
)
430 static bool redLight(quint16 flags
)
432 return (flags
& (1<<9));
435 static QString
cameraDesc(quint16 flags
)
438 return "Red light camera";
440 QString
desc(QString::number(speed(flags
)) + " " + units(flags
));
441 return portable(flags
) ? "<i>" + desc
+ "<i>" : desc
;
445 static quint32
readCamera(DataStream
&stream
, QVector
<Waypoint
> &waypoints
,
446 QList
<Area
> &polygons
)
451 qint32 top
, right
, bottom
, left
, lat
, lon
;
455 rs
= stream
.readRecordHeader(rh
);
456 top
= stream
.readInt24();
457 right
= stream
.readInt24();
458 bottom
= stream
.readInt24();
459 left
= stream
.readInt24();
460 stream
>> flags
>> s7
;
463 quint32 skip
= s7
+ 2 + s7
/4;
464 stream
.skipRawData(skip
);
465 lat
= stream
.readInt24();
466 lon
= stream
.readInt24();
470 stream
.skipRawData(9);
472 quint32 skip
= 3 + s8
+ s8
/4;
473 stream
.skipRawData(skip
);
474 lat
= stream
.readInt24();
475 lon
= stream
.readInt24();
479 Area
area(RectC(Coordinates(toWGS24(left
), toWGS24(top
)),
480 Coordinates(toWGS24(right
), toWGS24(bottom
))));
481 area
.setDescription(cameraDesc(flags
));
483 waypoints
.append(Coordinates(toWGS24(lon
), toWGS24(lat
)));
484 polygons
.append(area
);
487 stream
.setStatus(QDataStream::ReadCorruptData
);
488 else if (ds
< rh
.size
)
489 stream
.skipRawData(rh
.size
- ds
);
494 static quint32
readIconId(DataStream
&stream
, quint16
&id
)
499 rs
= stream
.readRecordHeader(rh
);
503 stream
.setStatus(QDataStream::ReadCorruptData
);
508 static quint32
readPOI(DataStream
&stream
, QVector
<Waypoint
> &waypoints
,
509 const QString
&fileName
, int &imgId
, QVector
<QPair
<int, quint16
> > &icons
)
515 quint16 s3
, iconId
= 0;
516 QList
<TranslatedString
> obj
;
518 rs
= stream
.readRecordHeader(rh
);
519 stream
>> lat
>> lon
>> s3
;
520 stream
.skipRawData(s3
);
522 ds
+= stream
.readTranslatedObjects(obj
);
524 waypoints
.append(Waypoint(Coordinates(toWGS32(lon
), toWGS32(lat
))));
526 waypoints
.last().setName(obj
.first().str());
528 while (stream
.status() == QDataStream::Ok
&& ds
< rh
.size
) {
529 switch (stream
.nextHeaderType()) {
531 ds
+= readIconId(stream
, iconId
);
534 ds
+= readDescription(stream
, waypoints
.last());
537 ds
+= readAddress(stream
, waypoints
.last());
540 ds
+= readContact(stream
, waypoints
.last());
543 ds
+= readImageInfo(stream
, waypoints
.last(), fileName
, imgId
);
546 ds
+= readNotes(stream
, waypoints
.last());
549 ds
+= stream
.skipRecord();
553 icons
.append(QPair
<int, quint16
>(waypoints
.size() - 1, iconId
));
556 stream
.setStatus(QDataStream::ReadCorruptData
);
561 static quint32
readSpatialIndex(DataStream
&stream
, QVector
<Waypoint
> &waypoints
,
562 QList
<Area
> &polygons
, const QString
&fileName
, int &imgId
,
563 QVector
<QPair
<int, quint16
> > &icons
)
567 qint32 top
, right
, bottom
, left
;
571 rs
= stream
.readRecordHeader(rh
);
572 stream
>> top
>> right
>> bottom
>> left
>> s5
>> s6
;
573 stream
.skipRawData(s6
);
575 if (rh
.flags
& 0x8) {
576 while (stream
.status() == QDataStream::Ok
&& ds
< rh
.size
) {
577 switch (stream
.nextHeaderType()) {
579 ds
+= readPOI(stream
, waypoints
, fileName
, imgId
, icons
);
582 ds
+= readSpatialIndex(stream
, waypoints
, polygons
,
583 fileName
, imgId
, icons
);
586 ds
+= readCamera(stream
, waypoints
, polygons
);
589 ds
+= stream
.skipRecord();
595 stream
.setStatus(QDataStream::ReadCorruptData
);
600 static quint32
readSymbol(DataStream
&stream
, QPixmap
&pixmap
)
603 quint8 rs
, u8
, bpp
, transparent
;
604 quint32 ds
= 36, u32
, imageSize
, paletteSize
, bgColor
;
605 quint16 id
, height
, width
, lineSize
;
607 QVector
<QRgb
> palette
;
610 rs
= stream
.readRecordHeader(rh
);
611 stream
>> id
>> height
>> width
>> lineSize
>> bpp
>> u8
>> u8
>> u8
612 >> imageSize
>> u32
>> paletteSize
>> bgColor
>> transparent
>> u8
>> u8
615 data
.resize(imageSize
);
616 stream
.readRawData(data
.data(), data
.size());
621 palette
.resize(paletteSize
);
622 for (quint32 i
= 0; i
< paletteSize
; i
++) {
624 palette
[i
] = (transparent
&& rgb
== bgColor
)
626 : Color::rgb((rgb
& 0x000000FF), (rgb
& 0x0000FF00) >> 8,
627 (rgb
& 0x00FF0000) >> 16);
629 ds
+= paletteSize
* 4;
632 if (data
.size() >= lineSize
* height
) {
634 img
= QImage((uchar
*)data
.data(), width
, height
, lineSize
,
635 QImage::Format_Indexed8
);
636 img
.setColorTable(palette
);
638 img
= QImage((uchar
*)data
.data(), width
, height
, lineSize
,
639 QImage::Format_RGBX8888
).rgbSwapped();
641 pixmap
= QPixmap::fromImage(img
);
643 /* There should be no more data left in the record, but broken GPI files
644 generated by pinns.co.uk tools exist in the wild so we read out
645 the record as a workaround for such files. */
647 stream
.setStatus(QDataStream::ReadCorruptData
);
648 else if (ds
< rh
.size
)
649 stream
.skipRawData(rh
.size
- ds
);
654 static void readPOIDatabase(DataStream
&stream
, QVector
<Waypoint
> &waypoints
,
655 QList
<Area
> &polygons
, const QString
&fileName
, int &imgId
)
658 QList
<TranslatedString
> obj
;
660 QVector
<QPair
<int, quint16
> > il
;
661 QVector
<QPixmap
> icons
;
663 stream
.readRecordHeader(rh
);
664 ds
= stream
.readTranslatedObjects(obj
);
665 ds
+= readSpatialIndex(stream
, waypoints
, polygons
, fileName
, imgId
, il
);
666 if (rh
.flags
& 0x8) {
667 while (stream
.status() == QDataStream::Ok
&& ds
< rh
.size
) {
668 switch (stream
.nextHeaderType()) {
670 icons
.append(QPixmap());
671 ds
+= readSymbol(stream
, icons
.last());
674 ds
+= readSpatialIndex(stream
, waypoints
, polygons
,
675 fileName
, imgId
, il
);
678 ds
+= stream
.skipRecord();
683 for (int i
= 0; i
< il
.size(); i
++) {
684 const QPair
<int, quint16
> &e
= il
.at(i
);
685 if (e
.second
< icons
.size())
686 waypoints
[e
.first
].setStyle(icons
.at(e
.second
));
690 stream
.setStatus(QDataStream::ReadCorruptData
);
693 bool GPIParser::readData(DataStream
&stream
, QVector
<Waypoint
> &waypoints
,
694 QList
<Area
> &polygons
, const QString
&fileName
)
698 while (stream
.status() == QDataStream::Ok
) {
699 switch (stream
.nextHeaderType()) {
701 readPOIDatabase(stream
, waypoints
, polygons
, fileName
,
706 if (stream
.status() == QDataStream::Ok
)
714 _errorString
= "Invalid/corrupted GPI data";
719 bool GPIParser::readGPIHeader(DataStream
&stream
)
723 quint16 codepage
= 0;
727 stream
.readRecordHeader(rh
);
728 stream
.readRawData(m1
, sizeof(m1
));
729 stream
.readRawData(m2
, sizeof(m2
));
730 stream
>> codepage
>> s2
>> s3
;
731 ds
= sizeof(m1
) + sizeof(m2
) + 4;
733 stream
.setCodepage(codepage
);
736 ds
+= readFileDataRecord(stream
);
738 if (stream
.status() != QDataStream::Ok
|| ds
!= rh
.size
) {
739 _errorString
= "Invalid GPI header";
745 bool GPIParser::readFileHeader(DataStream
&stream
, quint32
&ebs
)
750 quint8 s5
, s6
, s8
, s9
;
753 stream
.readRecordHeader(rh
);
754 stream
.readRawData(magic
, sizeof(magic
));
755 if (memcmp(magic
, "GRMREC", sizeof(magic
))) {
756 _errorString
= "Not a GPI file";
759 stream
>> s5
>> s6
>> s7
>> s8
>> s9
>> s10
;
760 stream
.skipRawData(s10
);
761 ds
= sizeof(magic
) + 10 + s10
;
763 ebs
= (s8
& 0x4) ? s9
* 8 + 8 : 0;
766 stream
.setStatus(QDataStream::ReadCorruptData
);
767 else if (ds
< rh
.size
)
768 stream
.skipRawData(rh
.size
- ds
);
770 if (stream
.status() != QDataStream::Ok
) {
771 _errorString
= "Invalid file header";
777 bool GPIParser::parse(QFile
*file
, QList
<TrackData
> &tracks
,
778 QList
<RouteData
> &routes
, QList
<Area
> &polygons
, QVector
<Waypoint
> &waypoints
)
782 DataStream
stream(file
);
785 stream
.setByteOrder(QDataStream::LittleEndian
);
787 if (!readFileHeader(stream
, ebs
) || !readGPIHeader(stream
))
791 CryptDevice
dev(stream
.device(), 0xf870b5, ebs
);
792 DataStream
cryptStream(&dev
);
793 cryptStream
.setByteOrder(QDataStream::LittleEndian
);
794 return readData(cryptStream
, waypoints
, polygons
, file
->fileName());
796 return readData(stream
, waypoints
, polygons
, file
->fileName());