1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-11-24 11:45:53 +01:00
GPXSee/src/data/gpiparser.cpp

751 lines
16 KiB
C++
Raw Normal View History

2020-01-05 00:50:02 +01:00
#include <cstring>
2019-10-25 22:30:12 +02:00
#include <QDataStream>
#include <QtEndian>
#include <QUrl>
#include <QIODevice>
#include <QApplication>
2019-11-01 19:07:21 +01:00
#include <QBuffer>
#include <QImageReader>
#include <QCryptographicHash>
#include <QTemporaryDir>
#include "common/garmin.h"
#include "common/textcodec.h"
#include "address.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;
};
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
private:
QIODevice *_device;
quint32 _key;
QByteArray _block;
int _available;
};
2019-11-01 19:07:21 +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;
}
}
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;
}
class DataStream : public QDataStream
{
public:
DataStream(QIODevice *d) : QDataStream(d) {}
void setCodepage(quint16 codepage) {_codec = TextCodec(codepage);}
quint16 readString(QString &str);
qint32 readInt24();
quint8 readRecordHeader(RecordHeader &hdr);
quint32 readTranslatedObjects(QList<TranslatedString> &objects);
quint32 skipRecord();
quint16 nextHeaderType();
private:
TextCodec _codec;
};
quint16 DataStream::readString(QString &str)
{
quint16 len;
*this >> len;
QByteArray ba;
ba.resize(len);
readRawData(ba.data(), len);
str = _codec.toString(ba);
return len + 2;
}
qint32 DataStream::readInt24()
2019-10-25 22:30:12 +02:00
{
unsigned char data[3];
quint32 val;
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
}
quint16 DataStream::nextHeaderType()
2019-10-25 22:30:12 +02:00
{
quint16 type = 0;
if (device()->peek((char*)&type, sizeof(type)) < (qint64)sizeof(type)) {
setStatus(QDataStream::ReadCorruptData);
return 0xFFFF;
} else
return qFromLittleEndian(type);
2019-10-25 22:30:12 +02:00
}
quint8 DataStream::readRecordHeader(RecordHeader &hdr)
2019-10-25 22:30:12 +02:00
{
*this >> hdr.type >> hdr.flags >> hdr.size;
2019-10-25 22:30:12 +02:00
if (hdr.flags & 0xA)
*this >> hdr.extra;
2019-10-25 22:30:12 +02:00
return (hdr.flags & 0xA) ? 12 : 8;
}
quint32 DataStream::skipRecord()
2019-10-25 22:30:12 +02:00
{
RecordHeader rh;
quint8 rs = readRecordHeader(rh);
skipRawData(rh.size);
2019-10-25 22:30:12 +02:00
return rs + rh.size;
}
quint32 DataStream::readTranslatedObjects(QList<TranslatedString> &objects)
2019-10-25 22:30:12 +02:00
{
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();
*this >> size;
2019-10-25 22:30:12 +02:00
ret = size + 4;
while (status() == QDataStream::Ok && size > 0) {
2019-10-25 22:30:12 +02:00
QString str;
readRawData(lang, sizeof(lang) - 1);
size -= readString(str) + 2;
2019-10-25 22:30:12 +02:00
objects.append(TranslatedString(lang, str));
}
if (size < 0)
setStatus(QDataStream::ReadCorruptData);
2019-10-25 22:30:12 +02:00
return ret;
}
static quint32 readFprsRecord(DataStream &stream)
{
RecordHeader rh;
quint16 s1;
quint8 rs, s2, s3, s4;
rs = stream.readRecordHeader(rh);
stream >> s1 >> s2 >> s3 >> s4;
return rs + 5;
}
static quint32 readFileDataRecord(DataStream &stream)
{
RecordHeader rh;
quint32 ds, s1;
quint16 s2, s3;
quint8 rs;
QList<TranslatedString> obj;
rs = stream.readRecordHeader(rh);
stream >> s1 >> s2 >> s3;
ds = 8;
ds += stream.readTranslatedObjects(obj);
ds += stream.readTranslatedObjects(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 += stream.readString(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;
}
static quint32 readDescription(DataStream &stream, Waypoint &waypoint)
2019-10-25 22:30:12 +02:00
{
RecordHeader rh;
quint8 rs;
quint32 ds;
QList<TranslatedString> obj;
rs = stream.readRecordHeader(rh);
ds = stream.readTranslatedObjects(obj);
2019-10-25 22:30:12 +02:00
if (!obj.isEmpty())
waypoint.setDescription(obj.first().str());
if (ds != rh.size)
stream.setStatus(QDataStream::ReadCorruptData);
return rs + rh.size;
}
static quint32 readNotes(DataStream &stream, Waypoint &waypoint)
2019-10-25 22:30:12 +02:00
{
RecordHeader rh;
quint8 rs, s1;
quint32 ds = 1;
rs = stream.readRecordHeader(rh);
2019-10-25 22:30:12 +02:00
stream >> s1;
if (s1 & 0x1) {
QList<TranslatedString> obj;
ds += stream.readTranslatedObjects(obj);
if (!obj.isEmpty())
waypoint.setComment(obj.first().str());
2019-10-25 22:30:12 +02:00
}
if (s1 & 0x2) {
QString str;
ds += stream.readString(str);
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(DataStream &stream, Waypoint &waypoint)
2019-10-25 22:30:12 +02:00
{
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 = stream.readRecordHeader(rh);
2019-11-08 21:00:59 +01:00
stream >> flags;
2019-10-25 22:30:12 +02:00
if (flags & 0x1) {
ds += stream.readString(str);
waypoint.setPhone(str);
}
2019-11-08 21:00:59 +01:00
if (flags & 0x2) // phone2
ds += stream.readString(str);
2019-11-08 21:00:59 +01:00
if (flags & 0x4) // fax
ds += stream.readString(str);
if (flags & 0x8) {
ds += stream.readString(str);
waypoint.addLink(Link("mailto:" + str, str));
}
if (flags & 0x10) {
ds += stream.readString(str);
2019-10-25 22:30:12 +02:00
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 += stream.readTranslatedObjects(obj);
2019-11-08 21:00:59 +01:00
if (ds != rh.size)
stream.setStatus(QDataStream::ReadCorruptData);
return rs + rh.size;
}
static quint32 readAddress(DataStream &stream, Waypoint &waypoint)
2019-11-08 21:00:59 +01:00
{
RecordHeader rh;
quint8 rs;
quint16 flags;
quint32 ds = 2;
QList<TranslatedString> obj;
QString str;
Address addr;
rs = stream.readRecordHeader(rh);
2019-11-08 21:00:59 +01:00
stream >> flags;
if (flags & 0x1) {
ds += stream.readTranslatedObjects(obj);
2019-11-08 21:00:59 +01:00
if (!obj.isEmpty())
addr.setCity(obj.first().str());
}
if (flags & 0x2) {
ds += stream.readTranslatedObjects(obj);
2019-11-08 21:00:59 +01:00
if (!obj.isEmpty())
addr.setCountry(obj.first().str());
}
if (flags & 0x4) {
ds += stream.readTranslatedObjects(obj);
2019-11-08 21:00:59 +01:00
if (!obj.isEmpty())
addr.setState(obj.first().str());
}
if (flags & 0x8) {
ds += stream.readString(str);
2019-11-08 21:00:59 +01:00
addr.setPostalCode(str);
}
if (flags & 0x10) {
ds += stream.readTranslatedObjects(obj);
2019-11-08 21:00:59 +01:00
if (!obj.isEmpty())
addr.setStreet(obj.first().str());
}
if (flags & 0x20) // unknown
ds += stream.readString(str);
2019-11-08 21:00:59 +01:00
if (addr.isValid())
waypoint.setAddress(addr.address());
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
static const QTemporaryDir &tempDir()
{
static QTemporaryDir dir;
return dir;
}
static quint32 readImageInfo(DataStream &stream, Waypoint &waypoint,
const QString &fileName, int &imgId)
{
RecordHeader rh;
quint8 rs, s1;
quint32 size;
rs = stream.readRecordHeader(rh);
stream >> s1 >> size;
QByteArray ba;
ba.resize(size);
stream.readRawData(ba.data(), ba.size());
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()));
}
if (size + 5 != rh.size)
stream.setStatus(QDataStream::ReadCorruptData);
return rs + rh.size;
}
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;
default:
return 0;
}
}
static quint32 readCamera(DataStream &stream, QVector<Waypoint> &waypoints,
QList<Area> &polygons)
{
RecordHeader rh;
quint8 flags, type, s7, rs;
qint32 top, right, bottom, left, lat, lon;
2019-11-10 19:20:25 +01:00
quint32 ds = 15;
rs = stream.readRecordHeader(rh);
top = stream.readInt24();
right = stream.readInt24();
bottom = stream.readInt24();
left = stream.readInt24();
stream >> flags >> type >> s7;
if (s7) {
2019-11-10 19:20:25 +01:00
quint32 skip = s7 + 2 + s7/4;
stream.skipRawData(skip);
lat = stream.readInt24();
lon = stream.readInt24();
2019-11-10 19:20:25 +01:00
ds += skip + 6;
} else {
quint8 s8;
stream.skipRawData(9);
stream >> s8;
quint32 skip = 3 + s8 + s8/4;
stream.skipRawData(skip);
lat = stream.readInt24();
lon = stream.readInt24();
ds += skip + 16;
2019-11-10 19:20:25 +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))));
switch (type) {
case 8:
area.setDescription(QString("%1&nbsp;mi/h")
.arg(speed(flags)));
break;
case 9:
area.setDescription(QString("%1&nbsp;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);
return rs + rh.size;
}
static quint32 readPOI(DataStream &stream, 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 = stream.readRecordHeader(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 += stream.readTranslatedObjects(obj);
2019-10-25 22:30:12 +02: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());
while (stream.status() == QDataStream::Ok && ds < rh.size) {
switch(stream.nextHeaderType()) {
2019-10-25 22:30:12 +02:00
case 10:
ds += readDescription(stream, waypoints.last());
2019-10-25 22:30:12 +02:00
break;
2019-11-08 21:00:59 +01:00
case 11:
ds += readAddress(stream, waypoints.last());
2019-11-08 21:00:59 +01:00
break;
2019-10-25 22:30:12 +02:00
case 12:
ds += readContact(stream, waypoints.last());
2019-10-25 22:30:12 +02:00
break;
case 13:
ds += readImageInfo(stream, waypoints.last(), fileName, imgId);
break;
2019-10-25 22:30:12 +02:00
case 14:
ds += readNotes(stream, waypoints.last());
2019-10-25 22:30:12 +02:00
break;
default:
ds += stream.skipRecord();
2019-10-25 22:30:12 +02:00
}
}
if (ds != rh.size)
stream.setStatus(QDataStream::ReadCorruptData);
return rs + rh.size;
}
static quint32 readSpatialIndex(DataStream &stream, 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 = stream.readRecordHeader(rh);
2019-10-25 22:30:12 +02:00
stream >> top >> right >> bottom >> left >> s5 >> s6;
stream.skipRawData(s6);
ds = 22 + s6;
if (rh.flags & 0x8) {
while (stream.status() == QDataStream::Ok && ds < rh.size) {
switch(stream.nextHeaderType()) {
2019-10-25 22:30:12 +02:00
case 2:
ds += readPOI(stream, waypoints, fileName, imgId);
2019-10-25 22:30:12 +02:00
break;
case 8:
ds += readSpatialIndex(stream, waypoints, polygons,
fileName, imgId);
break;
case 19:
ds += readCamera(stream, waypoints, polygons);
2019-10-25 22:30:12 +02:00
break;
default:
ds += stream.skipRecord();
2019-10-25 22:30:12 +02:00
}
}
}
if (ds != rh.size)
stream.setStatus(QDataStream::ReadCorruptData);
return rs + rh.size;
}
static void readPOIDatabase(DataStream &stream, QVector<Waypoint> &waypoints,
QList<Area> &polygons, const QString &fileName, int &imgId)
2019-10-25 22:30:12 +02:00
{
RecordHeader rh;
QList<TranslatedString> obj;
quint32 ds;
2019-10-25 22:30:12 +02:00
stream.readRecordHeader(rh);
ds = stream.readTranslatedObjects(obj);
ds += readSpatialIndex(stream, waypoints, polygons, fileName, imgId);
if (rh.flags & 0x8) {
while (stream.status() == QDataStream::Ok && ds < rh.size) {
switch(stream.nextHeaderType()) {
case 8:
ds += readSpatialIndex(stream, waypoints, polygons,
fileName, imgId);
break;
case 5: // symbol
case 7: // category
default:
ds += stream.skipRecord();
}
}
2019-10-25 22:30:12 +02:00
}
if (ds != rh.size)
2019-10-25 22:30:12 +02:00
stream.setStatus(QDataStream::ReadCorruptData);
}
bool GPIParser::readData(DataStream &stream, 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;
while (stream.status() == QDataStream::Ok) {
switch (stream.nextHeaderType()) {
case 0x09: // POI database
readPOIDatabase(stream, waypoints, polygons, fileName,
imgId);
break;
case 0xffff: // EOF
stream.skipRecord();
if (stream.status() == QDataStream::Ok)
return true;
break;
case 0x16: // route
case 0x15: // info header
default:
stream.skipRecord();
}
2019-10-25 22:30:12 +02:00
}
_errorString = "Invalid/corrupted GPI data";
2019-10-25 22:30:12 +02:00
return false;
2019-10-25 22:30:12 +02:00
}
bool GPIParser::readGPIHeader(DataStream &stream)
2019-10-25 22:30:12 +02:00
{
RecordHeader rh;
char m1[6], m2[2];
quint16 codepage = 0;
quint8 s2, s3;
quint32 ds;
stream.readRecordHeader(rh);
2019-10-25 22:30:12 +02:00
stream.readRawData(m1, sizeof(m1));
stream.readRawData(m2, sizeof(m2));
stream >> codepage >> s2 >> s3;
ds = sizeof(m1) + sizeof(m2) + 4;
stream.setCodepage(codepage);
2019-10-25 22:30:12 +02:00
if (s2 & 0x10)
ds += readFileDataRecord(stream);
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;
}
bool GPIParser::readFileHeader(DataStream &stream, quint32 &ebs)
2019-10-25 22:30:12 +02:00
{
RecordHeader rh;
quint32 ds, s7;
quint16 s10;
quint8 s5, s6, s8, s9;
char magic[6];
2019-10-25 22:30:12 +02:00
stream.readRecordHeader(rh);
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
}
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
ebs = (s8 & 0x4) ? s9 * 8 + 8 : 0;
2019-10-27 16:34:53 +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);
DataStream stream(file);
2019-11-01 19:07:21 +01:00
quint32 ebs;
2019-10-25 22:30:12 +02:00
stream.setByteOrder(QDataStream::LittleEndian);
if (!readFileHeader(stream, ebs) || !readGPIHeader(stream))
2019-10-25 22:30:12 +02:00
return false;
2019-11-01 19:07:21 +01:00
if (ebs) {
CryptDevice dev(stream.device(), 0xf870b5, ebs);
DataStream cryptStream(&dev);
cryptStream.setByteOrder(QDataStream::LittleEndian);
return readData(cryptStream, waypoints, polygons, file->fileName());
2019-10-25 22:30:12 +02:00
} else
return readData(stream, waypoints, polygons, file->fileName());
2019-10-25 22:30:12 +02:00
}