Logo Search packages:      
Sourcecode: qlandkarte version File versions  Download package

gmapsupp.cpp

/******************************************************************************
    Copyright (C) 2006, 2007 Oliver Eichler oliver.eichler@gmx.de
    Copyright (C) 2007 Leon van Dommelen dommelen@eng.fsu.edu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA

  Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd.
  or one of its subsidiaries.

-------------------------------------------------------------------------------

  gmapsupp.cpp: see gmapsupp.h for general info.

  Edits by lvd 12/29/07, 1/x/08: bulk rewrite.

******************************************************************************/

#include "gmapsupp.h"
#include "GarminTypedef.h"
#include "Platform.h"

#include <iostream>
#include <QtCore>
#include <QtGui>

#define DEBUG_SHOW_SECT_DESC

// description of the TRE, RGN, ... parts of GMAPSUPP.IMG subfiles
struct gms_subfile_part_t
{
    gms_subfile_part_t() : offset(0), size(0){}
    /// file offset of subfile part
    quint32 offset;
    /// size of the subfile part
    quint32 size;
};

// GMAPSUPP.IMG IMG subfiles (i.e. the selected tile IMG files)
struct gms_subfile_desc_t
{
    gms_subfile_desc_t() : pRawData(0) {};
    gms_subfile_desc_t(quint8 * pRawData) : pRawData(pRawData) {}
    /// internal filename
    QString name;
    /// location information of all parts
    QMap<QString,gms_subfile_part_t> parts;
    /// pointer to file buffer
    quint8 * pRawData;
};

#pragma pack(1)

//         Special IMG fileheader of a GMAPSUPP.IMG file.
//
// The first two bytes in 0x1CA-0x1CB hold a second block number nBlocks2.
// The header extends over 8*512 bytes, the fields beyond 0x40C,
// data_offset, being unidentified.
//
// The FAT blocks for the header are written behind the header, not in it.
// Maybe to allow a header of more than 240 blocks?
//
// Note that the individual subfiles of GMAPSUPP.IMG must by the definition
// of the FAT blocks, as well as by their pointers, be less than 4GB in size.
// However, there does not seem an obvious reason for GMAPSUPP.IMG itself to
// be restricted to any such limit.
//
// Maybe Garmin is foreseeing GPS units with large amounts of maps loaded.
// That might explain why the header FATs are no longer in the header.
//
struct gms_imghdr_t
{
    quint8  xorByte;             ///< 0x00000000
    quint8  byte0x00000001_0x00000009[9];
    quint8  upMonth;             ///< 0x0000000A
    quint8  upYear;              ///< 0x0000000B
    quint8  byte0x0000000C_0x0000000F[4];
    char    signature[7];        ///< 0x00000010 .. 0x00000016
    quint8  byte0x00000017_0x00000040[42];
    char    identifier[7];       ///< 0x00000041 .. 0x00000047
    quint8  byte0x00000048;
    char    desc1[20];           ///< 0x00000049 .. 0x0000005C
    quint8  byte0x0000005D_0x00000060[4];
    quint8  e1;                  ///< 0x00000061
    quint8  e2;                  ///< 0x00000062
    quint16 nBlocks1;            ///< 0x00000063 .. 0x00000064
    char    desc2[31];           ///< 0x00000065 .. 0x00000083
    quint8  byte0x00000084_0x000001C9[0x146];
    quint16 nBlocks2;            ///< 0x000001CA .. 0x000001CB // NEVER SET???
    quint8  byte0x0000001CC_0x000001FD[0x32];
    quint16 terminator;          ///< 0x000001FE .. 0x000001FF
    quint8  byte0x00000200_0x0000040B[0x20C];
    quint32 dataoffset;          ///< 0x0000040C .. 0x0000040F
    quint8  byte0x00000410_0x00000FFF[0xBF0];

    quint32 blocksize(){return 1 << (e1 + e2);}
};

// MAPSOURC.MPS header?   This is not being used anywhere??
struct gms_mps_map_t
{
    quint8  tok;                 ///< should be 0x4c
    quint16 size;                ///< total entry size is size+sizeof(tok)+sizeof(size)
    quint16 product;             ///< product code as found in the registry
    quint16 dummy;               ///< country / char. set?
    quint32 mapId;               ///< same id as in tdb and map filename
    char    name1[];
    /*
      There are several 0 terminated strings:
      "map name"
      "tile name"
    */
};
/*
  quint32 ???                 ///< the map id as it is used in the FAT table
  quint32 ???                 ///< always 0x00000000 (end token?)
*/

#pragma pack(0)

// initialize a Garmin IMG-file FAT block, without the part (FAT block
// number), file size, or block sequence numbers.
void initFATBlock(FATblock_t * pFAT)
{
    pFAT->flag = 0x01;
    memset(pFAT->name,0x20,sizeof(pFAT->name));
    memset(pFAT->type,0x20,sizeof(pFAT->type));
    memset(pFAT->byte0x00000012_0x0000001F,0x00,
        sizeof(pFAT->byte0x00000012_0x0000001F));
}


// the function that actually creates GMAPSUPP.IMG
void mkGMapSupp(
// input: selected IMG map tiles
QList<gms_map_def_t>& files,
// input: map sets involved
const QSet<QString>& maps,
// input: keys for the map sets
const QSet<QString>& keys,
// input: GMAPSUPP.IMG file size limit
quint64 size_limit,
// input: GMAPSUPP.IMG map tile limit
quint32 maps_limit,
// input: whether to disallow duplicate internal tile IDs
quint32 no_duplicates,
// output: GMAPSUPP.IMG as a byte string
QByteArray& result,
// output: size of the GMAPSUPP.IMG file
quint64& file_size,
// output: number of tiles in the file
quint32& subfile_cnt,
// output: skipped tiles (invalid or GPS limits exceeded)
quint32& skipped_cnt,
// output: invalid tiles (having duplicated internal IDs)
quint32& invalid_cnt
)
{
    // data of all subfiles to combine
    QVector<QByteArray> inputData;
    // all subfile descriptors
    QMap<quint32,gms_subfile_desc_t> subfiles;
    // tile names versus internal file names
    QMap<QString,QString> tilenames;
    // subfile number
    quint32 subfile_no;
    // part number of the subfile
    quint16 part_no;
    // largest block size found in the subfiles
    quint32 largestBlocksize = 0;

    // GMAPSUPP.IMG file size if the current map tile is accepted
    quint64 file_size_new;
    // data offset of the GMAPSUPP.IMG file
    quint32 dataoffset;
    // optimum GMAPSUPP.IMG block size
    quint32 optBlocksize, optBlocksize_new;
    // GMAPSUPP.IMG block size index
    int e2;
    // potential GMAPSUPP.IMG block sizes in the form 2^(9+e2)
    quint32 block_size[23];
    // number of GMAPSUPP.IMG blocks using that block size
    quint64 block_cnt[23], block_cnt_new[23];
    // number of GMAPSUPP.IMG FAT blocks
    quint64 FAT_block_cnt[23], FAT_block_cnt_new[23];
    // GMAPSUPP.IMG part size in bytes, blocks, and FAT blocks
    quint64 part_size, part_blocks, part_FAT_blocks;
    // GMAPSUPP.IMG header size in bytes, blocks, and FAT blocks
    quint64 header_size, header_blocks, header_FAT_blocks;
    // GMAPSUPP.IMG total blocks
    quint64 total_blocks;
    // count of blocks actually written
    quint16 blockcnt = 0;
    // count of FAT blocks actually written
    quint16 FATblockcnt = 0;

    // the MAPSOURC.MPS section of GMAPSUPP.IMG as a byte string
    QByteArray mps;
    // size of the file
    quint64 mps_size, mps_size_new;

    // maximum number of blocks in an IMG file
    const quint32 blocks_limit = 0xFFFF;

    // whether we warned for reaching the size limit
    int warned_for_size = 0;

    // map index
    QSet<QString>::const_iterator map;

    // key index
    QSet<QString>::const_iterator key;

    // subfile part index
    QMap<QString,gms_subfile_part_t>::const_iterator part;

    // temporary string
    char tmpstr[64];

    // initialize

    qDebug() << "Limits: " << size_limit << maps_limit;

    // initialize GMAPSUPP.IMG file size
    file_size = 0;

    // set the potential block sizes
    block_size[0]=512;
    for (e2=1; e2<23; e2++) block_size[e2]=2*block_size[e2-1];
    // zero the block counts
    for (e2=0; e2<23; e2++) {
        block_cnt[e2] = 0;
        FAT_block_cnt[e2] = 0;
    }

    // initialize the optimum block size
    optBlocksize = 0;

    // initialize the size of the MAPSOURC.MPS section
    mps_size = 0;
    // add the bytes needed for the keys of locked maps
    key = keys.begin();
    while(key != keys.end()) {
        mps_size += 1 + 2 + 26;
        ++key;
    }
    // add the bytes needed for the map set descriptors
    map = maps.begin();
    while(map != maps.end()) {
        mps_size += 1 + 2 + 4 + map->size() + 1;
        ++map;
    }

    // initialize the subfile count
    subfile_cnt = 0;

    // initialize the number of tiles skipped
    skipped_cnt = 0;

    // initialize the number of invalid tiles
    invalid_cnt = 0;

    // clear the results buffer
    result.clear();

    // initialize the MAPSOURC.MPS section
    QDataStream s(&mps,QIODevice::WriteOnly);
    s.setByteOrder(QDataStream::LittleEndian);

    // Analyze the selected map tiles (i.e. the subfiles of GMAPSUPP.IMG)

    // loop over the selected tiles giving first priority to the last
    QList<gms_map_def_t>::iterator file = files.end();
    do {
        // next file
        --file;

        // skip the file if the maps limit has been reached
        if (subfile_cnt == maps_limit) {
            file->disabled = 1;
            ++skipped_cnt;
            continue;
        }

        // read file data as one big chunk
        QFile f(file->filename);
        if(!f.open(QIODevice::ReadOnly)) {
            throw QObject::tr("Failed to open ") + file->filename;
        }
        inputData << f.readAll();
        // get the actual file size
        const quint64 fsize = f.size();
        f.close();

        // setup base pointer to the subfile bytes
        quint8 * const pRawData = (quint8*)inputData.last().data();

        // descramble data, if necessary
        if(*pRawData != '\0') {
            quint8 hash = *pRawData;
            quint8 *p   =  pRawData;
            quint64 cnt = 0;

            do {
                *p = (*p) ^ hash;
                ++p;++cnt;
            } while(cnt != fsize);
        }

        // test for garmin map file
        hdr_img_t * imghdr = (hdr_img_t *)pRawData;
        if(strncmp(imghdr->signature,"DSKIMG",7) != 0) {
            throw QObject::tr("Wrong file format: ") + file->filename;
        }
        if(strncmp(imghdr->identifier,"GARMIN",7) != 0) {
            throw QObject::tr("Bad file format: ") + file->filename;
        }

        // next subfile
        subfile_no = subfile_cnt++;
        subfiles[subfile_no] = gms_subfile_desc_t(pRawData);

        // create a reference alias for the current subfile descriptor
        gms_subfile_desc_t& subfile = subfiles[subfile_no];

        // find the largest blocksize of all subfiles, just because.
        const quint32 blocksize = imghdr->blocksize();
        if(largestBlocksize < blocksize) largestBlocksize = blocksize;

        // initialize the new GMAPSUPP.IMG file dimensions

        // gee, why does it not want to set it to 0xFFFFFFFFFFFFFFFF???
        file_size_new = 0xFFFFFFFF;
        file_size_new = (file_size_new+1)*file_size_new+file_size_new;
        // qDebug() << "file_size: " << file_size;

        // new block counts and FAT block counts
        for (e2=0; e2<23; e2++) {
            block_cnt_new[e2] = block_cnt[e2];
            FAT_block_cnt_new[e2] = FAT_block_cnt[e2];
        }

        // new optimum block size
        optBlocksize_new = 0;

        // new MAPSOURC.MPS size
        mps_size_new = mps_size + 1 + 2 + 16
            + 2 * (file->mapname.size() + 1)
            + file->tilename.size() + 1;

        // initialize the number of parts
        part_no=0;

        // total header size, including FAT
        const quint32 fhsize = gar_endian(uint32_t, imghdr->dataoffset);
        if(fhsize <= sizeof(hdr_img_t) || fhsize >= fsize) {
            throw QObject::tr("Bad file data offset: ") + file->filename;
        }

        // create a pointer to the first FAT block
        const FATblock_t * pFAT =
            (const FATblock_t *)(pRawData + sizeof(hdr_img_t));

        // byte-position in the file
        quint32 FAToffset = sizeof(hdr_img_t);

        // skip dummy FAT blocks at the beginning
        while(FAToffset < fhsize) {
            if(pFAT->flag != 0x00) {
                break;
            }
            FAToffset += sizeof(FATblock_t);
            ++pFAT;
        }

        // read the FAT blocks after the header to find the parts (subsubfiles)
        /*
          It is taken for granted that the single subfile parts are not
          fragmented within the file. Thus it is not really neccessary to store
          and handle all block sequence numbers. Just the first one will give
          us the offset. This also implies that it is not necessary to care
          about FAT blocks with a non-zero part number.
        */
        while(FAToffset < fhsize) {

            // terminate on an invalid pointer
            if(pFAT->flag != 0x01) {
                break;
            }

            // start of new subfile part FAT blocks
            if(pFAT->part == 0) {

                // next part
                part_no++;

                // put the subfile part filename into tmpstr
                memcpy(tmpstr,pFAT->name,sizeof(pFAT->name));
                tmpstr[sizeof(pFAT->name)] = 0;

                // store the common parts filename as subfile internal one
                if (part_no == 1) {
                    if (no_duplicates && tilenames.contains(tmpstr)) {
                        invalid_cnt++;
                        qDebug() << "*** duplicated internal filename:"
                            << tmpstr
                            << tilenames[tmpstr].toAscii()
                            << file->tilename.toAscii();
                        qDebug() << file->filename.toAscii()
                            << file->id
                            << file->mapname.toAscii();
                        // removable user interaction
                        if (invalid_cnt <= 4) {
                            int res;
                            if (invalid_cnt <= 3) {
                                res = QMessageBox::question(0,QObject::tr("Duplicate internal ID"),QObject::tr("Two tiles have the same ID: " + tilenames[tmpstr].toAscii() + " and " + file->tilename.toAscii() + ".  The data of the latter will be inoperable.  Proceed?"),QMessageBox::Yes|QMessageBox::No,QMessageBox::No);
                            }
                            else {
                                res = QMessageBox::question(0,QObject::tr("More duplicate internal IDs"),QObject::tr("Still more tiles have the same ID.  Drop them all from now on?"),QMessageBox::Yes|QMessageBox::No,QMessageBox::No);
                            }
                            if(res == QMessageBox::No) {
                                throw QObject::tr("Upload aborted because of duplicate IDs.");
                            }
                            // end removable user interaction
                        }
                        file->disabled = 1;
                        mps_size_new = mps_size;
                        break;
                    }
                    tilenames[tmpstr] = file->tilename;
                    file->intname = tmpstr;
                    subfile.name = tmpstr;
                }

                // put the subfile TRE, RGN, ... file type into tmpstr
                memcpy(tmpstr,pFAT->type,sizeof(pFAT->type));
                tmpstr[sizeof(pFAT->type)] = 0;

                // add it to the subfile parts list, and create an alias to it
                gms_subfile_part_t& part = subfile.parts[tmpstr];

                // set the size in bytes in the descriptor
                part.size   = gar_endian(uint32_t, pFAT->size);

                // set the offset of the part in the subfile in the descriptor
                part.offset = gar_endian(uint16_t, pFAT->blocks[0]) * blocksize;
                // qDebug() << tmpstr << pFAT->size << part.size;

                // add the blocks needed for the part to the totals
                for (e2=0; e2<23; e2++) {

                    // move the part size in an overflow resistant location
                    part_size = part.size;

                    // number of blocks needed for the part
                    part_blocks =
                        (part_size + block_size[e2] - 1)/block_size[e2];

                    // number of FAT blocks needed for the part
                    part_FAT_blocks = (part_blocks + 239)/240;

                    // increment the totals
                    block_cnt_new[e2] += part_blocks;
                    FAT_block_cnt_new[e2] += part_FAT_blocks;

                    // no more than 255 FAT blocks are countable
                    if (part_FAT_blocks > 255) block_cnt_new[e2] = blocks_limit;
                }

            }

            FAToffset += sizeof(FATblock_t);
            ++pFAT;
        }

        // compute the new GMAPSUPP.IMG file size
        for (e2=0; e2<23; e2++) {

            // number of blocks needed for MAPSOURC.MPS
            part_blocks = (mps_size_new + block_size[e2] - 1)/block_size[e2];

            // number of FAT blocks needed for MAPSOURC.MPS
            part_FAT_blocks = (part_blocks + 239)/240;

            // no more than 255 FAT blocks are countable
            if (part_FAT_blocks > 255) continue;

            // skip if too many blocks already
            if (block_cnt_new[e2] + part_blocks + 1 > blocks_limit) continue;

            // guess one FAT block for the header and iterate on the value
            header_blocks = 1;
            for (int iter=1; iter<10; iter++) {
                header_FAT_blocks = (header_blocks + 239)/240;
                header_size = sizeof(gms_imghdr_t) +
                    (FAT_block_cnt_new[e2] + part_FAT_blocks +
                    header_FAT_blocks) * sizeof(FATblock_t);
                header_blocks =
                    (header_size + block_size[e2] - 1)/block_size[e2];
            }

            // give up if still not right (but it will be)
            if (header_FAT_blocks != (header_blocks + 239)/240) {
                qDebug() << "Unable to find header FAT blocks for E2=" << e2;
                continue;
            }

            // no more than 255 FAT blocks are countable
            if (header_FAT_blocks > 255) continue;

            // total blocks
            total_blocks = block_cnt_new[e2] + part_blocks + header_blocks;

            // skip if too many blocks
            if (total_blocks > blocks_limit) continue;

            // the data offset *must* be a 32 bit integer (but it will be)
            if (header_blocks * block_size[e2] > 0xFFFFFFFF) continue;

            // skip if we have a smaller file size already
            if (total_blocks * block_size[e2] > file_size_new) continue;

            // save as the optimum value
            optBlocksize_new = block_size[e2];

            // file size at this block size
            file_size_new = total_blocks * block_size[e2];
        }

        // there should be an acceptable block size!
        if (optBlocksize_new == 0) {
            throw QObject::tr("Unable to find an acceptable block size!??");
        }

        // check for file size exceeded
        if (file_size_new > size_limit) {
            // removable user interaction
            if (!warned_for_size) {
                warned_for_size = 1;
                int res = QMessageBox::question(0,QObject::tr("Upload file size too large"),QObject::tr("Upload file size exceeds the limit of ") + QString::number(size_limit) + QObject::tr(" bytes.  Try to squeeze in as many maps as fit?"),QMessageBox::Yes|QMessageBox::No,QMessageBox::No);
                if(res == QMessageBox::No) {
                    throw QObject::tr("Upload aborted because of excessive file size.");
                }
            }
            // end removable user interaction
            file->disabled = 1;
        }

        // abandon the file on failure
        if (file->disabled) {
            --subfile_cnt;
            ++skipped_cnt;
            continue;
        }

        // else finish up

        // check for trouble
        if ((FAToffset == sizeof(imghdr)) || (FAToffset > fhsize)) {
            throw QObject::tr("Failed to read file structure: ")
                + file->filename;
        }
        if (!part_no) {
            throw QObject::tr("File is empty: ") + file->filename;
        }

        // update the GMAPSUPP.IMG statistics for the added subfile
        file_size = file_size_new;
        for (e2=0; e2<23; e2++) {
            FAT_block_cnt[e2] = FAT_block_cnt_new[e2];
            block_cnt[e2] = block_cnt_new[e2];
        }
        optBlocksize = optBlocksize_new;
        mps_size = mps_size_new;

        // add the file to MAPSOURC.MPS with an L identifier
        s << (quint8)'L';
        // size of the next data
        s << (quint16)(16 + ((file->mapname.size() + 1)<<1)
            + file->tilename.size() + 1);
        // 2 byte product code as in the registry and country/character set?
        s << (quint32)0x02180001;
        // file ID, map name, tile name
        s << (quint32)file->id;
        s.writeRawData(file->mapname.toAscii(),file->mapname.size() + 1);
        s.writeRawData(file->tilename.toAscii(),file->tilename.size() + 1);
        s.writeRawData(file->mapname.toAscii(),file->mapname.size() + 1);
        // ??? wow. :-/ write the number in the internal name
        if(file->intname[0].isDigit()) {
            s << (quint32)file->intname.toInt(0);
        }
        else {
            s << (quint32)file->intname.mid(1).toInt(0,16);
        }
        // terminator?
        s << (quint32)0;

#ifdef DEBUG_SHOW_SECT_DESC
        qDebug() << file->filename.toAscii()
            << subfile.name
            << file->id
            << file->mapname.toAscii()
            << file->tilename.toAscii();
        part = subfile.parts.begin();
        while(part != subfile.parts.end()) {
            qDebug() << part.key() << hex << part->offset << part->size;
            ++part;
        }
#endif                   //DEBUG_SHOW_SECT_DESC

    } while(file != files.begin());

    // if no files to upload, return
    if(!subfile_cnt) {
        if(skipped_cnt) {
            throw QObject::tr("No maps to upload left after skipping invalid maps.");
        }
        else {
            throw QObject::tr("No maps to upload; select them first.");
        }
    }

    // finish the MAPSOURCE.MPS section

    // add the keys for locked maps
    key = keys.begin();
    while(key != keys.end()) {
        s << (quint8)'U' << (quint16)26;
        s.writeRawData(key->toAscii(),26);
        ++key;
    }

    // add the used maps
    map = maps.begin();
    while(map != maps.end()) {
        s << (quint8)'F';
        s << (quint16)(4 + map->size() + 1);
        // I suspect this should really be the basic file name of the .img set:
        s << (quint32)0x02180001;// ???
        s.writeRawData(map->toAscii(),map->size() + 1);
        ++map;
    }

    // check the actual MAPSOURC.MPS size with the computed one
    if ((quint64) mps.size() != mps_size) {
        throw QObject::tr("Program error or MAPSOURC.MPS too large.  Size: ")
            + QString::number(mps_size);
    }

    // Find the GMAPSUPP.IMG physical characteristics

    // compute the E2 value for the optimum blocksize
    quint32 bs = optBlocksize >> 9;
    e2=0;
    while(bs > 1) {
        ++e2;
        bs >>= 1;
    }
    if (e2 > 22) {
        throw QObject::tr("Program error: invalid upload block size.");
    }
    if (optBlocksize != block_size[e2]) {
        throw QObject::tr("Program error: wrong block size.");
    }

    // total blocks in the file
    total_blocks = file_size / optBlocksize;

    // blocks for the MAPSOURC.MPS part
    part_blocks = (mps_size + optBlocksize - 1)/optBlocksize;
    part_FAT_blocks = (part_blocks + 239)/240;

    // header blocks
    header_blocks = total_blocks - block_cnt[e2] - part_blocks;
    header_FAT_blocks = (header_blocks + 239)/240;

    // header size
    header_size = sizeof(gms_imghdr_t) + sizeof(FATblock_t) *
        (FAT_block_cnt[e2] + part_FAT_blocks + header_FAT_blocks);
    if ((header_size + optBlocksize - 1)/optBlocksize != header_blocks) {
        throw QObject::tr("Program error: wrong header blocks.");
    }

    // compute the data offset
    dataoffset = header_blocks * optBlocksize;

    // store total number of blocks
    block_cnt[e2] = total_blocks;

    // store total number of FAT blocks
    FAT_block_cnt[e2] += header_FAT_blocks + part_FAT_blocks;

    // list key statistics for debugging purposes
    qDebug() << "GMAPSUPP.IMG file size:"
        << file_size
        << "characteristics:"
        << optBlocksize
        << dataoffset
        << block_cnt[e2]
        << FAT_block_cnt[e2]
        << "tiles:"
        << subfile_cnt;

    // Create the GMAPSUPP.IMG file header

    // define and zero it
    gms_imghdr_t gms_imghdr;
    memset(&gms_imghdr,0,sizeof(gms_imghdr_t));

    // get the date
    QDateTime date = QDateTime::currentDateTime();

    // space-fill the descriptor strings
    memset(&gms_imghdr.desc1,0x20,sizeof(gms_imghdr.desc1));
    memset(&gms_imghdr.desc2,0x20,sizeof(gms_imghdr.desc2) - 1);

    // put in current month and year
    gms_imghdr.upMonth    = date.date().month();
    gms_imghdr.upYear     = date.date().year() - 1900;

    // copy the signature to the 7 byte string
    strncpy(gms_imghdr.signature,"DSKIMG",7);

    // copy the identifier to the 7 byte string
    strncpy(gms_imghdr.identifier,"GARMIN",7);

    // identify creator in the map description
    memcpy(gms_imghdr.desc1,"QLandkarte",10);

    // as far as is known, E1 is always 9, to force a minimum 512 block size
    gms_imghdr.e1 = 0x09;
    // the E2 corresponding to the optimum block size
    gms_imghdr.e2 = e2;

    // set the number of file blocks in the named field (note: unaligned!)
    gar_store(uint16_t, gms_imghdr.nBlocks1, (quint16) total_blocks);

    // add the "partition table" terminator
    gms_imghdr.terminator = gar_endian(uint16_t, 0xAA55);

    // add the data offset
    gms_imghdr.dataoffset = gar_endian(uint32_t, dataoffset);

    // create a pointer to set various unnamed fields
    quint8 * p = (quint8*)&gms_imghdr;

    // various - watch out for unaligned destinations
                                 // non-standard?
    *(p + 0x0E)               = 0x01;
                                 // standard value
    *(p + 0x17)               = 0x02;
                                 // standard is 0x0004
    *(quint16*)(p + 0x18)     = gar_endian(uint16_t, 0x0020);
                                 // standard is 0x0010
    *(quint16*)(p + 0x1A)     = gar_endian(uint16_t, 0x0020);
                                 // standard is 0x0020
    *(quint16*)(p + 0x1C)     = gar_endian(uint16_t, 0x03C7);
                                 // copies (0x1a)
    gar_ptr_store(uint16_t, p + 0x5D, 0x0020);
                                 // copies (0x18)
    gar_ptr_store(uint16_t, p + 0x5F, 0x0020);

    // date stuff
    gar_ptr_store(uint16_t, p + 0x39, date.date().year());
    *(p + 0x3B)               = date.date().month();
    *(p + 0x3C)               = date.date().day();
    *(p + 0x3D)               = date.time().hour();
    *(p + 0x3E)               = date.time().minute();
    *(p + 0x3F)               = date.time().second();
                                 // 0x02 standard. bit for copied map set?
    *(p + 0x40)               = 0x08;

    // more
                                 // copies (0x18) to a *2* byte field
    *(quint16*)(p + 0x1C4)    = gar_endian(uint16_t, 0x0020);
    /*  // not really needed ???
     *(p + 0x1C0)               = 0x01; // standard value
     *(p + 0x1C3)               = 0x15; // normal, but *not* (0x1A) - 1
     *(p + 0x1C4)               = 0x10; // value above
     *(p + 0x1C5)               = 0x00;
     */

    // set the number of file blocks at 0x1CA
    *(quint16*)(p + 0x1CA) = gar_endian(uint16_t, (quint16) total_blocks);

    // create the GMAPSUPP.IMG file in the result byte string
    // FIXME BELOW!!!

    // initialize the string to 0xFF (not-a-block-number in FAT blocks)
    result.fill(0xFF,file_size);

    // write the file header
    memcpy(result.data(), &gms_imghdr, sizeof(gms_imghdr_t));

    // create a pointer to the start of the FAT blocks section
    FATblock_t * pFAT   = (FATblock_t*)(result.data() + sizeof(gms_imghdr_t));

    // create a pointer to the block list section of a FAT
    quint16 * pBlock    = 0;

    // create a pointer to the source subfile location
    quint8 * pSrc       = 0;

    // create a pointer to the target location in GMAPSUP.IMG
    quint8 * pTar       = 0;

    // counter of written characters
    quint32 written = 0;

    // part FAT block number
    quint16 partno;

    // numbers of block sequence numbers written to the current FAT block
    quint16 blockcnt_;

    // initialize the file header FAT block, no name or type
    initFATBlock(pFAT);          // default initialization
                                 // put in the size of the header
    pFAT->size = gar_endian(uint32_t, header_size);
                                 // a nonsensical IMG FAT block number, should be 0??
    pFAT->part = gar_endian(uint16_t, 3);
    FATblockcnt++;               // one more FAT block written

    // write the block sequence numbers to FAT, but *not* to 0x420
    partno            = 0;       // 3 does NOT work here, normal numbering resumes
    blockcnt_         = 0;       // no block numbers written to this FAT block
    pBlock = pFAT->blocks;       // pointer to the block number location
    written           = 0;       // number of bytes written
    do {
                                 // add the next block number to the FAT block
        *pBlock++ = gar_endian(uint16_t, blockcnt);
        blockcnt++;
        written += optBlocksize; // the amount of data written in this block
        ++blockcnt_;             // one more block number in this FAT block
        // switch to the next FAT block every 240 blocks
        if(blockcnt_ == 240 && written < header_size) {
            initFATBlock(++pFAT);// initialize next FAT block
            pFAT->size  = 0;     // the size is not repeated
            ++partno;
                                 // TODO: this shift smells fishy???
            pFAT->part  = gar_endian(uint16_t, partno << 8);
                                 // reset pointer to block number
            pBlock      = pFAT->blocks;
            blockcnt_   = 0;     // no block numbers yet
            FATblockcnt++;       // one more FAT block written
        }
    } while(written < header_size);

    // check the number of blocks written for the header
    if (dataoffset != blockcnt * optBlocksize) {
        throw QObject::tr("Program error: wrong number of header blocks.");
    }

    // copy the parts of all subfiles to the data and FAT block areas
    for (subfile_no=0; subfile_no<subfile_cnt; subfile_no++) {

        // loop over the parts
        part = subfiles[subfile_no].parts.begin();
        while(part != subfiles[subfile_no].parts.end()) {

            // copy the file to the data area of GMAPSUPP.IMG
            pSrc = subfiles[subfile_no].pRawData + part->offset;
            pTar = (quint8*)result.data() + blockcnt * optBlocksize;
            memcpy(pTar,pSrc,part->size);

            // initialize the first FAT block of this part
            initFATBlock(++pFAT);// default initialization
            memcpy(pFAT->name,
                subfiles[subfile_no].name.toLatin1(),
                sizeof(pFAT->name));
            memcpy(pFAT->type,part.key().toLatin1(),sizeof(pFAT->type));
                                 // put in the size of the part
            pFAT->size = gar_endian(uint32_t, part->size);
            pFAT->part = 0;      // FAT block number of this part
            FATblockcnt++;       // one more FAT block written

            // write the block sequence numbers to FAT
                                 // first FAT block of the part
            partno            = 0;
                                 // block numbers written to this FAT block
            blockcnt_         = 0;
                                 // pointer to the block number location
            pBlock = pFAT->blocks;
                                 // number of bytes written
            written           = 0;
            do {
                                 // next block number in the FAT block
                *pBlock++ = gar_endian(uint16_t, blockcnt);
                blockcnt++;
                                 // amount of data written in this block
                written += optBlocksize;
                ++blockcnt_;     // one more block number
                // switch to the next FAT block every 240 blocks
                if(blockcnt_ == 240 && written < part->size) {
                                 // initialize next FAT block
                    initFATBlock(++pFAT);
                    memcpy(pFAT->name,
                        subfiles[subfile_no].name.toLatin1(),
                        sizeof(pFAT->name));
                    memcpy(pFAT->type,
                        part.key().toLatin1(),
                        sizeof(pFAT->type));
                                 // the size is not repeated
                    pFAT->size  = 0;
                    ++partno;
                                 // TODO: this shift is fishy
                    pFAT->part  = gar_endian(uint16_t, partno << 8);
                                 // pointer to block number
                    pBlock      = pFAT->blocks;
                                 // no block numbers yet
                    blockcnt_   = 0;
                                 // one more FAT block written
                    FATblockcnt++;
                }
            } while(written < part->size);

            ++part;
        }
    }

    // write the MAPSOURCE.MPS section
    pSrc = (quint8*)mps.data();
    pTar = (quint8*)result.data() + blockcnt * optBlocksize;
    memcpy(pTar,pSrc,mps.size());

    // initialize its first FAT block
    initFATBlock(++pFAT);        // default initialization
    memcpy(pFAT->name,"MAPSOURC",sizeof(pFAT->name));
    memcpy(pFAT->type,"MPS",sizeof(pFAT->type));
    pFAT->size = mps.size();     // the size of the MAPSOURC.SRC section
    pFAT->part = 0;              // first FAT block of MAPSOURC.SRC
    FATblockcnt++;               // one more FAT block written

    // write the block sequence numbers to FAT
    partno            = 0;       // first FAT block
    blockcnt_         = 0;       // no block numbers written to this FAT block
    pBlock = pFAT->blocks;       // pointer to the block number location
    written           = 0;       // number of bytes written
    do {
        *pBlock++ = blockcnt++;  // add the next block number to the FAT block
        written += optBlocksize; // the amount of data written in this block
        ++blockcnt_;             // one more block number in this FAT block
        // switch to next FAT block every 240 blocks
        if(blockcnt_ == 240 && written < (unsigned)mps.size()) {
            initFATBlock(++pFAT);// initialize next FAT block
            memcpy(pFAT->name,"MAPSOURC",sizeof(pFAT->name));
            memcpy(pFAT->type,"MPS",sizeof(pFAT->type));
                                 // TODO: the size IS repeated??
            pFAT->size  = mps.size();
                                 // TODO: this shift smells fishy
            pFAT->part  = (++partno << 8);
                                 // reset pointer to block number
            pBlock      = pFAT->blocks;
            blockcnt_   = 0;     // no block numbers yet
            FATblockcnt++;       // one more FAT block written
        }
    } while(written < (unsigned)mps.size());

    // mark empty FAT blocks until start of data section as dummy
    while((char*)(++pFAT) < (result.data() + dataoffset)) {
        pFAT->flag = 0;
    }

    // check the number of blocks actually written
    if (blockcnt * optBlocksize != file_size) {
        qDebug() << "file size: " << file_size
            << " should be: " << blockcnt * optBlocksize;
        qDebug() << "blockcnt: "  << blockcnt;
        qDebug() << "optBlocksize: "  << optBlocksize;
        throw QObject::tr("Program error: wrong number of blocks written.");
    }

    // check the number of FAT blocks actually written
    if (FATblockcnt != FAT_block_cnt[e2]) {
        qDebug() << "FATblockcnt: "  << FATblockcnt;
        qDebug() << "FAT_block_cnt[e2]: "  << FAT_block_cnt[e2];
        throw QObject::tr("Program error: wrong number of FAT blocks.");
    }
}

Generated by  Doxygen 1.6.0   Back to index