2020-01-05 00:50:02 +01:00
|
|
|
#include <cstring>
|
2019-10-25 22:30:12 +02:00
|
|
|
#include <QDataStream>
|
|
|
|
#include <QTextCodec>
|
|
|
|
#include <QtEndian>
|
|
|
|
#include <QUrl>
|
2019-11-06 23:23:05 +01:00
|
|
|
#include <QIODevice>
|
|
|
|
#include <QApplication>
|
2019-11-01 19:07:21 +01:00
|
|
|
#include <QBuffer>
|
2019-11-06 23:23:05 +01:00
|
|
|
#include <QImageReader>
|
2019-11-07 23:23:25 +01:00
|
|
|
#include <QCryptographicHash>
|
2019-11-08 01:04:17 +01:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
2019-11-06 23:23:05 +01:00
|
|
|
#include <QTemporaryDir>
|
2019-11-08 01:04:17 +01:00
|
|
|
#endif // QT_VERSION >= 5
|
2019-11-10 16:46:31 +01:00
|
|
|
#include "common/garmin.h"
|
2019-10-25 22:30:12 +02:00
|
|
|
#include "gpiparser.h"
|
|
|
|
|
|
|
|
|
|
|
|
struct RecordHeader {
|
|
|
|
quint16 type;
|
|
|
|
quint16 flags;
|
|
|
|
quint32 size;
|
|
|
|
quint32 extra;
|
|
|
|
};
|
|
|
|
|
|
|
|
class TranslatedString {
|
|
|
|
public:
|
|
|
|
TranslatedString() {}
|
|
|
|
TranslatedString(const QString &lang, const QString &str)
|
|
|
|
: _lang(lang), _str(str) {}
|
|
|
|
|
|
|
|
const QString &str() const {return _str;}
|
|
|
|
const QString &lang() const {return _lang;}
|
|
|
|
|
|
|
|
private:
|
|
|
|
QString _lang;
|
|
|
|
QString _str;
|
|
|
|
};
|
|
|
|
|
2019-11-06 23:23:05 +01:00
|
|
|
class CryptDevice : public QIODevice
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CryptDevice(QIODevice *device, quint32 key, quint32 blockSize,
|
|
|
|
QObject *parent = 0);
|
|
|
|
bool isSequential() const {return true;}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
qint64 readData(char *data, qint64 maxSize);
|
|
|
|
qint64 writeData(const char *, qint64) {return -1;}
|
2019-11-01 19:07:21 +01:00
|
|
|
|
2019-11-06 23:23:05 +01:00
|
|
|
private:
|
|
|
|
QIODevice *_device;
|
|
|
|
quint32 _key;
|
|
|
|
QByteArray _block;
|
|
|
|
int _available;
|
|
|
|
};
|
2019-11-01 19:07:21 +01:00
|
|
|
|
2019-11-06 23:23:05 +01:00
|
|
|
static void demangle(quint8 *data, quint32 size, quint32 key)
|
2019-11-01 19:07:21 +01:00
|
|
|
{
|
|
|
|
static const unsigned char shuf[] = {
|
|
|
|
0xb, 0xc, 0xa, 0x0,
|
|
|
|
0x8, 0xf, 0x2, 0x1,
|
|
|
|
0x6, 0x4, 0x9, 0x3,
|
|
|
|
0xd, 0x5, 0x7, 0xe
|
|
|
|
};
|
|
|
|
|
|
|
|
int hiCnt = 0, loCnt;
|
2019-11-05 19:31:52 +01:00
|
|
|
quint8 sum = shuf[((key >> 24) + (key >> 16) + (key >> 8) + key) & 0xf];
|
2019-11-01 19:07:21 +01:00
|
|
|
|
|
|
|
for (quint32 i = 0; i < size; i++) {
|
|
|
|
quint8 hiAdd = shuf[key >> (hiCnt << 2) & 0xf] + sum;
|
|
|
|
loCnt = (hiCnt > 6) ? 0 : hiCnt + 1;
|
|
|
|
quint8 loAdd = shuf[key >> (loCnt << 2) & 0xf] + sum;
|
2019-11-05 19:31:52 +01:00
|
|
|
quint8 hi = data[i] - (hiAdd << 4);
|
2019-11-01 19:07:21 +01:00
|
|
|
quint8 lo = data[i] - loAdd;
|
|
|
|
data[i] = (hi & 0xf0) | (lo & 0x0f);
|
|
|
|
hiCnt = (loCnt > 6) ? 0 : loCnt + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-06 23:23:05 +01:00
|
|
|
CryptDevice::CryptDevice(QIODevice *device, quint32 key, quint32 blockSize,
|
|
|
|
QObject *parent) : QIODevice(parent), _device(device), _key(key), _available(0)
|
|
|
|
{
|
|
|
|
_block.resize(blockSize);
|
|
|
|
setOpenMode(_device->openMode());
|
|
|
|
}
|
|
|
|
|
|
|
|
qint64 CryptDevice::readData(char *data, qint64 maxSize)
|
|
|
|
{
|
|
|
|
qint64 rs, ts = 0;
|
|
|
|
int cs;
|
|
|
|
|
|
|
|
if (_available) {
|
|
|
|
cs = qMin(maxSize, (qint64)_available);
|
|
|
|
memcpy(data, _block.constData() + _block.size() - _available, cs);
|
|
|
|
_available -= cs;
|
|
|
|
maxSize -= cs;
|
|
|
|
ts = cs;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (maxSize) {
|
|
|
|
if ((rs = _device->read(_block.data(), _block.size())) < 0)
|
|
|
|
return -1;
|
|
|
|
else if (!rs)
|
|
|
|
break;
|
|
|
|
_available = rs;
|
|
|
|
demangle((quint8*)_block.data(), _available, _key);
|
|
|
|
cs = qMin(maxSize, (qint64)_available);
|
|
|
|
memcpy(data + ts, _block.constData(), cs);
|
|
|
|
_available -= cs;
|
|
|
|
maxSize -= cs;
|
|
|
|
ts += cs;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ts;
|
|
|
|
}
|
|
|
|
|
2019-11-10 16:46:31 +01:00
|
|
|
|
|
|
|
static qint32 readInt24(QDataStream &stream)
|
2019-10-25 22:30:12 +02:00
|
|
|
{
|
2019-11-10 16:46:31 +01:00
|
|
|
unsigned char data[3];
|
|
|
|
quint32 val;
|
|
|
|
|
|
|
|
stream.readRawData((char*)data, sizeof(data));
|
|
|
|
val = data[0] | ((quint32)data[1]) << 8 | ((quint32)data[2]) << 16;
|
|
|
|
return (val > 0x7FFFFF) ? (val & 0x7FFFFF) - 0x800000 : val;
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static quint16 nextHeaderType(QDataStream &stream)
|
|
|
|
{
|
|
|
|
quint16 type = 0;
|
2019-11-07 23:23:25 +01:00
|
|
|
|
|
|
|
if (stream.device()->peek((char*)&type, sizeof(type))
|
|
|
|
< (qint64)sizeof(type)) {
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
return 0xFFFF;
|
|
|
|
} else
|
|
|
|
return qFromLittleEndian(type);
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static quint8 readRecordHeader(QDataStream &stream, RecordHeader &hdr)
|
|
|
|
{
|
|
|
|
stream >> hdr.type >> hdr.flags >> hdr.size;
|
|
|
|
if (hdr.flags & 0xA)
|
|
|
|
stream >> hdr.extra;
|
|
|
|
return (hdr.flags & 0xA) ? 12 : 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint32 skipRecord(QDataStream &stream)
|
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint8 rs = readRecordHeader(stream, rh);
|
|
|
|
stream.skipRawData(rh.size);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint16 readString(QDataStream &stream, QTextCodec *codec, QString &str)
|
|
|
|
{
|
|
|
|
quint16 len;
|
|
|
|
stream >> len;
|
|
|
|
QByteArray ba;
|
|
|
|
ba.resize(len);
|
|
|
|
stream.readRawData(ba.data(), len);
|
|
|
|
str = codec ? codec->toUnicode(ba) : QString::fromLatin1(ba);
|
|
|
|
|
|
|
|
return len + 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint32 readTranslatedObjects(QDataStream &stream, QTextCodec *codec,
|
|
|
|
QList<TranslatedString> &objects)
|
|
|
|
{
|
|
|
|
qint32 size = 0, ret;
|
2020-01-05 00:50:02 +01:00
|
|
|
char lang[3];
|
2019-10-25 22:30:12 +02:00
|
|
|
|
2020-01-05 00:50:02 +01:00
|
|
|
memset(lang, 0, sizeof(lang));
|
2019-10-25 22:30:12 +02:00
|
|
|
objects.clear();
|
|
|
|
|
|
|
|
stream >> size;
|
|
|
|
ret = size + 4;
|
2019-11-07 23:23:25 +01:00
|
|
|
while (stream.status() == QDataStream::Ok && size > 0) {
|
2019-10-25 22:30:12 +02:00
|
|
|
QString str;
|
2020-01-05 00:50:02 +01:00
|
|
|
stream.readRawData(lang, sizeof(lang) - 1);
|
2019-10-25 22:30:12 +02:00
|
|
|
size -= readString(stream, codec, str) + 2;
|
|
|
|
objects.append(TranslatedString(lang, str));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size < 0)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
static quint32 readFprsRecord(QDataStream &stream)
|
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint16 s1;
|
|
|
|
quint8 rs, s2, s3, s4;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
|
|
|
stream >> s1 >> s2 >> s3 >> s4;
|
|
|
|
|
|
|
|
return rs + 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint32 readFileDataRecord(QDataStream &stream, QTextCodec *codec)
|
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint32 ds, s1;
|
|
|
|
quint16 s2, s3;
|
|
|
|
quint8 rs;
|
|
|
|
QList<TranslatedString> obj;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
|
|
|
stream >> s1 >> s2 >> s3;
|
|
|
|
ds = 8;
|
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
|
|
|
|
|
|
|
if (s1 & 0x10) {
|
|
|
|
quint8 ss1, ss2;
|
|
|
|
quint16 ss3;
|
|
|
|
stream >> ss1 >> ss2 >> ss3;
|
|
|
|
ds += 4;
|
|
|
|
}
|
|
|
|
if (s1 & 0x100) {
|
|
|
|
quint32 ss1;
|
|
|
|
stream >> ss1;
|
|
|
|
if (ss1)
|
|
|
|
stream.skipRawData(ss1);
|
|
|
|
ds += ss1 + 4;
|
|
|
|
}
|
|
|
|
if (s1 & 0x400) {
|
|
|
|
QString str;
|
|
|
|
ds += readString(stream, codec, str);
|
|
|
|
}
|
|
|
|
if (s1 & 0x400000) {
|
|
|
|
quint16 ss1;
|
|
|
|
stream >> ss1;
|
|
|
|
if (ss1)
|
|
|
|
stream.skipRawData(ss1);
|
|
|
|
ds += ss1 + 2;
|
|
|
|
}
|
|
|
|
// structure of higher fields not known
|
|
|
|
|
|
|
|
if (ds > rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
else if (ds < rh.size)
|
|
|
|
// skip remaining unknown fields
|
|
|
|
stream.skipRawData(rh.size - ds);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
2019-10-25 22:30:12 +02:00
|
|
|
static quint32 readDescription(QDataStream &stream, QTextCodec *codec,
|
|
|
|
Waypoint &waypoint)
|
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint8 rs;
|
|
|
|
quint32 ds;
|
|
|
|
QList<TranslatedString> obj;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
|
|
|
ds = readTranslatedObjects(stream, codec, obj);
|
|
|
|
if (!obj.isEmpty())
|
|
|
|
waypoint.setDescription(obj.first().str());
|
|
|
|
|
|
|
|
if (ds != rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint32 readNotes(QDataStream &stream, QTextCodec *codec,
|
|
|
|
Waypoint &waypoint)
|
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint8 rs, s1;
|
|
|
|
quint32 ds = 1;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
|
|
|
stream >> s1;
|
|
|
|
if (s1 & 0x1) {
|
|
|
|
QList<TranslatedString> obj;
|
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
2020-03-09 20:04:13 +01:00
|
|
|
if (!obj.isEmpty())
|
|
|
|
waypoint.setComment(obj.first().str());
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|
|
|
|
if (s1 & 0x2) {
|
|
|
|
QString str;
|
|
|
|
ds += readString(stream, codec, str);
|
2020-03-09 20:04:13 +01:00
|
|
|
if (!str.isEmpty())
|
|
|
|
waypoint.setComment(str);
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ds != rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint32 readContact(QDataStream &stream, QTextCodec *codec,
|
|
|
|
Waypoint &waypoint)
|
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint8 rs;
|
2019-11-08 21:00:59 +01:00
|
|
|
quint16 flags;
|
2019-10-25 22:30:12 +02:00
|
|
|
quint32 ds = 2;
|
|
|
|
QString str;
|
|
|
|
QList<TranslatedString> obj;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
2019-11-08 21:00:59 +01:00
|
|
|
stream >> flags;
|
2019-10-25 22:30:12 +02:00
|
|
|
|
2019-11-08 21:00:59 +01:00
|
|
|
if (flags & 0x1) // phone
|
2019-10-25 22:30:12 +02:00
|
|
|
ds += readString(stream, codec, str);
|
2019-11-08 21:00:59 +01:00
|
|
|
if (flags & 0x2) // phone2
|
2019-10-25 22:30:12 +02:00
|
|
|
ds += readString(stream, codec, str);
|
2019-11-08 21:00:59 +01:00
|
|
|
if (flags & 0x4) // fax
|
2019-10-25 22:30:12 +02:00
|
|
|
ds += readString(stream, codec, str);
|
2019-11-08 21:00:59 +01:00
|
|
|
if (flags & 0x8) // mail
|
2019-10-25 22:30:12 +02:00
|
|
|
ds += readString(stream, codec, str);
|
2019-11-08 21:00:59 +01:00
|
|
|
if (flags & 0x10) { // web
|
2019-10-25 22:30:12 +02:00
|
|
|
ds += readString(stream, codec, str);
|
|
|
|
QUrl url(str);
|
|
|
|
waypoint.addLink(Link(url.scheme().isEmpty()
|
|
|
|
? "http://" + str : str, str));
|
|
|
|
}
|
2019-11-08 21:00:59 +01:00
|
|
|
if (flags & 0x20) // unknown
|
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
|
|
|
|
|
|
|
if (ds != rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint32 readAddress(QDataStream &stream, QTextCodec *codec,
|
|
|
|
Waypoint &waypoint)
|
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint8 rs;
|
|
|
|
quint16 flags;
|
|
|
|
quint32 ds = 2;
|
|
|
|
QList<TranslatedString> obj;
|
|
|
|
QString str;
|
|
|
|
Address addr;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
|
|
|
stream >> flags;
|
|
|
|
|
|
|
|
if (flags & 0x1) {
|
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
|
|
|
if (!obj.isEmpty())
|
|
|
|
addr.setCity(obj.first().str());
|
|
|
|
}
|
|
|
|
if (flags & 0x2) {
|
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
|
|
|
if (!obj.isEmpty())
|
|
|
|
addr.setCountry(obj.first().str());
|
|
|
|
}
|
|
|
|
if (flags & 0x4) {
|
2019-10-25 22:30:12 +02:00
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
2019-11-08 21:00:59 +01:00
|
|
|
if (!obj.isEmpty())
|
|
|
|
addr.setState(obj.first().str());
|
|
|
|
}
|
|
|
|
if (flags & 0x8) {
|
|
|
|
ds += readString(stream, codec, str);
|
|
|
|
addr.setPostalCode(str);
|
|
|
|
}
|
|
|
|
if (flags & 0x10) {
|
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
|
|
|
if (!obj.isEmpty())
|
|
|
|
addr.setStreet(obj.first().str());
|
|
|
|
}
|
|
|
|
if (flags & 0x20) // unknown
|
|
|
|
ds += readString(stream, codec, str);
|
|
|
|
|
|
|
|
waypoint.setAddress(addr);
|
2019-10-25 22:30:12 +02:00
|
|
|
|
|
|
|
if (ds != rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
2019-11-08 01:04:17 +01:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
|
|
|
static const QTemporaryDir &tempDir()
|
|
|
|
{
|
|
|
|
static QTemporaryDir dir;
|
|
|
|
return dir;
|
|
|
|
}
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
static quint32 readImageInfo(QDataStream &stream, Waypoint &waypoint,
|
|
|
|
const QString &fileName, int &imgId)
|
2019-11-06 23:23:05 +01:00
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint8 rs, s1;
|
|
|
|
quint32 size;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
|
|
|
stream >> s1 >> size;
|
|
|
|
|
|
|
|
QByteArray ba;
|
|
|
|
ba.resize(size);
|
|
|
|
stream.readRawData(ba.data(), ba.size());
|
|
|
|
|
2019-11-15 23:41:30 +01:00
|
|
|
if (tempDir().isValid()) {
|
|
|
|
QBuffer buf(&ba);
|
|
|
|
QImageReader ir(&buf);
|
|
|
|
|
|
|
|
QByteArray id(fileName.toUtf8() + QByteArray::number(imgId++));
|
|
|
|
QFile imgFile(tempDir().path() + "/" + QString("%0.%1").arg(
|
|
|
|
QCryptographicHash::hash(id, QCryptographicHash::Sha1).toHex(),
|
|
|
|
QString(ir.format())));
|
|
|
|
imgFile.open(QIODevice::WriteOnly);
|
|
|
|
imgFile.write(ba);
|
|
|
|
imgFile.close();
|
|
|
|
|
|
|
|
waypoint.addImage(ImageInfo(imgFile.fileName(), ir.size()));
|
|
|
|
}
|
2019-11-06 23:23:05 +01:00
|
|
|
|
|
|
|
if (size + 5 != rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
2019-11-08 01:04:17 +01:00
|
|
|
#endif // QT_VERSION >= 5
|
2019-11-06 23:23:05 +01:00
|
|
|
|
2019-11-10 16:46:31 +01:00
|
|
|
static int speed(quint8 flags)
|
|
|
|
{
|
|
|
|
switch (flags >> 4) {
|
|
|
|
case 0x8:
|
|
|
|
return 40;
|
|
|
|
case 0x9:
|
|
|
|
return 30;
|
|
|
|
case 0xA:
|
|
|
|
return 50;
|
|
|
|
case 0xB:
|
|
|
|
return 70;
|
|
|
|
case 0xC:
|
|
|
|
return 80;
|
|
|
|
case 0xD:
|
|
|
|
return 90;
|
|
|
|
case 0xE:
|
|
|
|
return 100;
|
2019-11-13 19:21:41 +01:00
|
|
|
case 0xF:
|
|
|
|
return 120;
|
2019-11-10 16:46:31 +01:00
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint32 readCamera(QDataStream &stream, QVector<Waypoint> &waypoints,
|
|
|
|
QList<Area> &polygons)
|
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint8 flags, type, s7, rs;
|
2019-11-14 22:55:06 +01:00
|
|
|
qint32 top, right, bottom, left, lat, lon;
|
2019-11-10 19:20:25 +01:00
|
|
|
quint32 ds = 15;
|
2019-11-10 16:46:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
|
|
|
top = readInt24(stream);
|
|
|
|
right = readInt24(stream);
|
|
|
|
bottom = readInt24(stream);
|
|
|
|
left = readInt24(stream);
|
|
|
|
stream >> flags >> type >> s7;
|
|
|
|
|
|
|
|
if (s7) {
|
2019-11-10 19:20:25 +01:00
|
|
|
quint32 skip = s7 + 2 + s7/4;
|
2019-11-10 16:46:31 +01:00
|
|
|
stream.skipRawData(skip);
|
2019-11-14 22:55:06 +01:00
|
|
|
lat = readInt24(stream);
|
|
|
|
lon = readInt24(stream);
|
2019-11-10 19:20:25 +01:00
|
|
|
ds += skip + 6;
|
2019-11-14 22:55:06 +01:00
|
|
|
} else {
|
|
|
|
quint8 s8;
|
|
|
|
stream.skipRawData(9);
|
|
|
|
stream >> s8;
|
|
|
|
quint32 skip = 3 + s8 + s8/4;
|
|
|
|
stream.skipRawData(skip);
|
|
|
|
lat = readInt24(stream);
|
|
|
|
lon = readInt24(stream);
|
|
|
|
ds += skip + 16;
|
2019-11-10 19:20:25 +01:00
|
|
|
}
|
2019-11-10 16:46:31 +01:00
|
|
|
|
2019-11-14 22:55:06 +01:00
|
|
|
waypoints.append(Coordinates(toWGS24(lon), toWGS24(lat)));
|
|
|
|
|
2020-12-02 23:58:11 +01:00
|
|
|
Area area(RectC(Coordinates(toWGS24(left), toWGS24(top)),
|
|
|
|
Coordinates(toWGS24(right), toWGS24(bottom))));
|
2019-11-10 16:46:31 +01:00
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 8:
|
|
|
|
area.setDescription(QString("%1 mi/h")
|
|
|
|
.arg(speed(flags)));
|
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
area.setDescription(QString("%1 km/h")
|
|
|
|
.arg(speed(flags)));
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
case 11:
|
|
|
|
area.setDescription("Red light camera");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
polygons.append(area);
|
|
|
|
|
2019-11-10 19:20:25 +01:00
|
|
|
if (ds > rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
else if (ds < rh.size)
|
|
|
|
stream.skipRawData(rh.size - ds);
|
|
|
|
|
2019-11-10 16:46:31 +01:00
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
2019-10-25 22:30:12 +02:00
|
|
|
static quint32 readPOI(QDataStream &stream, QTextCodec *codec,
|
2019-11-07 23:23:25 +01:00
|
|
|
QVector<Waypoint> &waypoints, const QString &fileName, int &imgId)
|
2019-10-25 22:30:12 +02:00
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint8 rs;
|
|
|
|
quint32 ds;
|
2019-11-09 21:43:07 +01:00
|
|
|
qint32 lat, lon;
|
2019-10-25 22:30:12 +02:00
|
|
|
quint16 s3;
|
|
|
|
QList<TranslatedString> obj;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
2019-11-09 21:43:07 +01:00
|
|
|
stream >> lat >> lon >> s3;
|
2019-10-25 22:30:12 +02:00
|
|
|
stream.skipRawData(s3);
|
|
|
|
ds = 10 + s3;
|
|
|
|
ds += readTranslatedObjects(stream, codec, obj);
|
|
|
|
|
2019-11-10 16:46:31 +01:00
|
|
|
waypoints.append(Waypoint(Coordinates(toWGS32(lon), toWGS32(lat))));
|
2019-10-25 22:30:12 +02:00
|
|
|
if (!obj.isEmpty())
|
|
|
|
waypoints.last().setName(obj.first().str());
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
while (stream.status() == QDataStream::Ok && ds < rh.size) {
|
2019-10-25 22:30:12 +02:00
|
|
|
switch(nextHeaderType(stream)) {
|
|
|
|
case 10:
|
|
|
|
ds += readDescription(stream, codec, waypoints.last());
|
|
|
|
break;
|
2019-11-08 21:00:59 +01:00
|
|
|
case 11:
|
|
|
|
ds += readAddress(stream, codec, waypoints.last());
|
|
|
|
break;
|
2019-10-25 22:30:12 +02:00
|
|
|
case 12:
|
|
|
|
ds += readContact(stream, codec, waypoints.last());
|
|
|
|
break;
|
2019-11-08 01:04:17 +01:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
2019-11-06 23:23:05 +01:00
|
|
|
case 13:
|
2019-11-07 23:23:25 +01:00
|
|
|
ds += readImageInfo(stream, waypoints.last(), fileName, imgId);
|
2019-11-06 23:23:05 +01:00
|
|
|
break;
|
2019-11-08 01:04:17 +01:00
|
|
|
#endif // QT_VERSION >= 5
|
2019-10-25 22:30:12 +02:00
|
|
|
case 14:
|
|
|
|
ds += readNotes(stream, codec, waypoints.last());
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ds += skipRecord(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ds != rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static quint32 readSpatialIndex(QDataStream &stream, QTextCodec *codec,
|
2019-11-10 16:46:31 +01:00
|
|
|
QVector<Waypoint> &waypoints, QList<Area> &polygons, const QString &fileName,
|
|
|
|
int &imgId)
|
2019-10-25 22:30:12 +02:00
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
quint32 ds, s5;
|
|
|
|
qint32 top, right, bottom, left;
|
|
|
|
quint16 s6;
|
|
|
|
quint8 rs;
|
|
|
|
|
|
|
|
rs = readRecordHeader(stream, rh);
|
|
|
|
stream >> top >> right >> bottom >> left >> s5 >> s6;
|
|
|
|
stream.skipRawData(s6);
|
|
|
|
ds = 22 + s6;
|
|
|
|
if (rh.flags & 0x8) {
|
2019-11-07 23:23:25 +01:00
|
|
|
while (stream.status() == QDataStream::Ok && ds < rh.size) {
|
2019-10-25 22:30:12 +02:00
|
|
|
switch(nextHeaderType(stream)) {
|
|
|
|
case 2:
|
2019-11-07 23:23:25 +01:00
|
|
|
ds += readPOI(stream, codec, waypoints, fileName, imgId);
|
2019-10-25 22:30:12 +02:00
|
|
|
break;
|
|
|
|
case 8:
|
2019-11-10 16:46:31 +01:00
|
|
|
ds += readSpatialIndex(stream, codec, waypoints, polygons,
|
|
|
|
fileName, imgId);
|
|
|
|
break;
|
|
|
|
case 19:
|
|
|
|
ds += readCamera(stream, waypoints, polygons);
|
2019-10-25 22:30:12 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ds += skipRecord(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ds != rh.size)
|
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
|
|
|
|
return rs + rh.size;
|
|
|
|
}
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
static void readPOIDatabase(QDataStream &stream, QTextCodec *codec,
|
2019-11-10 16:46:31 +01:00
|
|
|
QVector<Waypoint> &waypoints, QList<Area> &polygons, const QString &fileName,
|
|
|
|
int &imgId)
|
2019-10-25 22:30:12 +02:00
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
QList<TranslatedString> obj;
|
2019-11-07 23:23:25 +01:00
|
|
|
quint32 ds;
|
2019-10-25 22:30:12 +02:00
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
readRecordHeader(stream, rh);
|
|
|
|
ds = readTranslatedObjects(stream, codec, obj);
|
2019-11-10 16:46:31 +01:00
|
|
|
ds += readSpatialIndex(stream, codec, waypoints, polygons, fileName, imgId);
|
2019-11-07 23:23:25 +01:00
|
|
|
if (rh.flags & 0x8) {
|
|
|
|
while (stream.status() == QDataStream::Ok && ds < rh.size) {
|
|
|
|
switch(nextHeaderType(stream)) {
|
|
|
|
case 5: // symbol
|
|
|
|
case 7: // category
|
|
|
|
default:
|
|
|
|
ds += skipRecord(stream);
|
|
|
|
}
|
|
|
|
}
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
if (ds != rh.size)
|
2019-10-25 22:30:12 +02:00
|
|
|
stream.setStatus(QDataStream::ReadCorruptData);
|
|
|
|
}
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
bool GPIParser::readData(QDataStream &stream, QTextCodec *codec,
|
2019-11-26 08:15:01 +01:00
|
|
|
QVector<Waypoint> &waypoints, QList<Area> &polygons, const QString &fileName)
|
2019-10-25 22:30:12 +02:00
|
|
|
{
|
2019-11-26 08:15:01 +01:00
|
|
|
int imgId = 0;
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
while (stream.status() == QDataStream::Ok) {
|
|
|
|
switch (nextHeaderType(stream)) {
|
|
|
|
case 0x09: // POI database
|
2019-11-10 16:46:31 +01:00
|
|
|
readPOIDatabase(stream, codec, waypoints, polygons, fileName,
|
|
|
|
imgId);
|
2019-11-07 23:23:25 +01:00
|
|
|
break;
|
|
|
|
case 0xffff: // EOF
|
|
|
|
skipRecord(stream);
|
|
|
|
if (stream.status() == QDataStream::Ok)
|
|
|
|
return true;
|
|
|
|
break;
|
|
|
|
case 0x16: // route
|
|
|
|
case 0x15: // info header
|
|
|
|
default:
|
|
|
|
skipRecord(stream);
|
|
|
|
}
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
_errorString = "Invalid/corrupted GPI data";
|
2019-10-25 22:30:12 +02:00
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
return false;
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 09:54:11 +01:00
|
|
|
bool GPIParser::readGPIHeader(QDataStream &stream, QTextCodec **codec)
|
2019-10-25 22:30:12 +02:00
|
|
|
{
|
|
|
|
RecordHeader rh;
|
|
|
|
char m1[6], m2[2];
|
|
|
|
quint16 codepage = 0;
|
|
|
|
quint8 s2, s3;
|
|
|
|
quint32 ds;
|
|
|
|
|
|
|
|
readRecordHeader(stream, rh);
|
|
|
|
stream.readRawData(m1, sizeof(m1));
|
|
|
|
stream.readRawData(m2, sizeof(m2));
|
|
|
|
stream >> codepage >> s2 >> s3;
|
|
|
|
ds = sizeof(m1) + sizeof(m2) + 4;
|
|
|
|
|
|
|
|
if (codepage == 65001)
|
2019-11-02 09:54:11 +01:00
|
|
|
*codec = QTextCodec::codecForName("UTF-8");
|
2019-10-25 22:30:12 +02:00
|
|
|
else if (codepage == 0)
|
2019-11-02 09:54:11 +01:00
|
|
|
*codec = 0;
|
2019-10-25 22:30:12 +02:00
|
|
|
else
|
2019-11-02 09:54:11 +01:00
|
|
|
*codec = QTextCodec::codecForName(QString("CP%1").arg(codepage)
|
2019-10-25 22:30:12 +02:00
|
|
|
.toLatin1());
|
|
|
|
|
|
|
|
if (s2 & 0x10)
|
2019-11-02 09:54:11 +01:00
|
|
|
ds += readFileDataRecord(stream, *codec);
|
2019-10-25 22:30:12 +02:00
|
|
|
|
|
|
|
if (stream.status() != QDataStream::Ok || ds != rh.size) {
|
|
|
|
_errorString = "Invalid GPI header";
|
|
|
|
return false;
|
|
|
|
} else
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
bool GPIParser::readFileHeader(QDataStream &stream, quint32 &ebs)
|
2019-10-25 22:30:12 +02:00
|
|
|
{
|
|
|
|
RecordHeader rh;
|
2019-11-07 23:23:25 +01:00
|
|
|
quint32 ds, s7;
|
|
|
|
quint16 s10;
|
|
|
|
quint8 s5, s6, s8, s9;
|
|
|
|
char magic[6];
|
2019-10-25 22:30:12 +02:00
|
|
|
|
|
|
|
readRecordHeader(stream, rh);
|
2019-11-07 23:23:25 +01:00
|
|
|
stream.readRawData(magic, sizeof(magic));
|
|
|
|
if (memcmp(magic, "GRMREC", sizeof(magic))) {
|
|
|
|
_errorString = "Not a GPI file";
|
|
|
|
return false;
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|
2019-11-07 23:23:25 +01:00
|
|
|
stream >> s5 >> s6 >> s7 >> s8 >> s9 >> s10;
|
|
|
|
stream.skipRawData(s10);
|
|
|
|
ds = sizeof(magic) + 10 + s10;
|
|
|
|
if (rh.flags & 8)
|
|
|
|
ds += readFprsRecord(stream);
|
2019-10-25 22:30:12 +02:00
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
ebs = (s8 & 0x4) ? s9 * 8 + 8 : 0;
|
2019-10-27 16:34:53 +01:00
|
|
|
|
2019-11-07 23:23:25 +01:00
|
|
|
if (stream.status() != QDataStream::Ok || ds != rh.size) {
|
|
|
|
_errorString = "Invalid file header";
|
|
|
|
return false;
|
|
|
|
} else
|
|
|
|
return true;
|
2019-10-27 16:34:53 +01:00
|
|
|
}
|
|
|
|
|
2019-10-25 22:30:12 +02:00
|
|
|
bool GPIParser::parse(QFile *file, QList<TrackData> &tracks,
|
|
|
|
QList<RouteData> &routes, QList<Area> &polygons, QVector<Waypoint> &waypoints)
|
|
|
|
{
|
|
|
|
Q_UNUSED(tracks);
|
|
|
|
Q_UNUSED(routes);
|
|
|
|
QDataStream stream(file);
|
|
|
|
QTextCodec *codec = 0;
|
2019-11-01 19:07:21 +01:00
|
|
|
quint32 ebs;
|
2019-10-25 22:30:12 +02:00
|
|
|
|
|
|
|
stream.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
|
2019-11-02 09:54:11 +01:00
|
|
|
if (!readFileHeader(stream, ebs) || !readGPIHeader(stream, &codec))
|
2019-10-25 22:30:12 +02:00
|
|
|
return false;
|
|
|
|
|
2019-11-01 19:07:21 +01:00
|
|
|
if (ebs) {
|
2019-11-06 23:23:05 +01:00
|
|
|
CryptDevice dev(stream.device(), 0xf870b5, ebs);
|
|
|
|
QDataStream cryptStream(&dev);
|
|
|
|
cryptStream.setByteOrder(QDataStream::LittleEndian);
|
2019-11-26 08:15:01 +01:00
|
|
|
return readData(cryptStream, codec, waypoints, polygons,
|
|
|
|
file->fileName());
|
2019-10-25 22:30:12 +02:00
|
|
|
} else
|
2019-11-26 08:15:01 +01:00
|
|
|
return readData(stream, codec, waypoints, polygons, file->fileName());
|
2019-10-25 22:30:12 +02:00
|
|
|
}
|