view HexFile.cpp @ 1:0b3630a29ad8

Initial version based on previous repository. Project was ported to QT6 and in now cmake based.
author Ideenmodellierer <tiefenrauscher@web.de>
date Thu, 27 Nov 2025 18:40:28 +0100
parents
children
line wrap: on
line source

/////////////////////////////////////////////////////////////////////////////
/// \file   HexFile.cpp
/// \brief  Read .hex file in "Intel HEX" format.
/// \author JD Gascuel.
///
/// \copyright (c) 2012-2016, JD Gascuel. All rights reserved.
/// $Id$
//////////////////////////////////////////////////////////////////////////////
//
// BSD 2-Clause License:
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////

#include "HexFile.h"

#include "Utils/Log.h"
#include "Utils/ProgressEvent.h"

#include <QCoreApplication>
#include <QTextStream>
#include <QtDebug>

//////////////////////////////////////////////////////////////////////////////
// Crypto++ objects needed:

typedef unsigned char byte;

#include "AES/rijndael.h"

///////////////////////////////////////////////////////////////////////////////

HexFile::HexFile()
: _memSize(0),
#ifdef FROG_MASTER
  _baseAddress(0),
#endif
  _buffer(0)
{}

HexFile::~HexFile()
{
    delete[] _buffer; _buffer = 0;
}

///////////////////////////////////////////////////////////////////////////////

bool HexFile::allocate(size_t memSize, unsigned char fill)
{
    delete[] _buffer;
    _buffer = new unsigned char[_memSize = memSize];
    Q_CHECK_PTR(_buffer);

    memset(_buffer, fill, memSize);
    return true;
}

bool HexFile::sqwiz(size_t newMemSize)
{
    if( newMemSize >= _memSize ) {
        LOG_THROW( "Squiz failed" );
    }
    _memSize = newMemSize;
    return true;
}

///////////////////////////////////////////////////////////////////////////////

static size_t bytes = 0;

void HexFile::readLine()
{
    QByteArray line = _file.readLine();
    if( line[0] != ':' )
        LOG_THROW( "Bad HEX format" );

    unsigned char checksum = 0;
    bool ok = true;
    for(int i=1; ok && i<line.length(); i+=2) {
        if( line[i]=='\r' || line[i]=='\n' )
            break;
        checksum += line.mid(i,2).toInt(&ok,16);
    }
    if( ! ok )
        LOG_THROW( "Bad HEX header" );
    if( checksum != 0 )
        LOG_THROW( "Bad HEX checksum" );

    int len  = line.mid(1,2).toInt(0, 16);
    int addr = line.mid(3,2).toInt(0, 16) << 8
             | line.mid(5,2).toInt(0, 16);
    int type = line.mid(7,2).toInt(0, 16);

    switch( type )
    {
    case 00: //---- Data record ----------------------------------------------
        if( _baseAddress == 0x300000 )  // Skip configuration bits.
            return;
        if( _baseAddress == 0xF00000 )  // Skip internal prom reset.
            return;

        for(int i=0; i<len; i++, ++addr)
        {
            size_t a = _baseAddress + addr;
            if( a >= _memSize )
                LOG_THROW( "BAD HEX address" );

            if( _buffer[a] != 0xFF )
                LOG_THROW( "Double write" );

            _buffer[a] = line.mid(9+i*2,2).toInt(&ok,16);
            if( !ok )
                LOG_THROW( "Bad HEX byte" );
            bytes++;
        }
        break;

    case 01: //---- END OF FILE record ---------------------------------------
        _file.seek(-1); // Force to end of file.
        break;

    case 02: //---- Segment address record -----------------------------------
        if( len != 2 || addr != 0 )
            LOG_THROW( "Bad HEX Segment Address record" );
        _baseAddress = line.mid(9,4).toInt(0,16) << 4;
        break;

    case 04: //---- Extended Linear Address Record ---------------------------
        if( len != 2 || addr != 0 )
            LOG_THROW( "Bad HEX Extended Linear Address Record" );
        _baseAddress = line.mid( 9,2).toInt(0,16) << 20
                     | line.mid(11,2).toInt(0,16) << 16;
        break;

    default:
        LOG_THROW("Bad HEX subtype");
    }
}

///////////////////////////////////////////////////////////////////////////////

void HexFile::load(const QString& fileName)
{
    Q_ASSERT(_buffer);

    _file.setFileName(fileName);
    if( ! _file.open(QIODevice::ReadOnly) )
        LOG_THROW("Can't open HEX file");

    bytes = 0;
    while( ! _file.atEnd() )
    {
        PROGRESS(_file.pos(), _file.size());
        readLine();
    }
    PROGRESS_RESET();

    _file.close();

    LOG_TRACE( int(bytes/1024.0f) << "KB loaded ("
            << int(bytes * 100.0f / _memSize) << "% of firmware area).");
}

///////////////////////////////////////////////////////////////////////////////

const unsigned char* HexFile::data() const
{
    Q_ASSERT(_buffer);
    return _buffer;
}

///////////////////////////////////////////////////////////////////////////////

unsigned int HexFile::checksum() const
{
    Q_ASSERT(_buffer);

    unsigned short low = 0;
    unsigned short high = 0;
    for(size_t i=0; i < _memSize; ++i)
    {
        low  += _buffer[i];
        high += low;
    }
    return (((unsigned int)high) << 16) + low;
}

///////////////////////////////////////////////////////////////////////////////

#ifndef FROG_MASTER
void HexFile::saveEncrypted(const QString&, byte [16])
{
    LOG_THROW( "No encryption" );
}
#else
void HexFile::saveEncrypted(const QString& fileName, byte secretKey[16])
{
    _file.setFileName(fileName);
    if( ! _file.open(QIODevice::WriteOnly|QIODevice::Truncate) )
        LOG_THROW( "Can't save to encrypted file" );

    QTextStream out(&_file);
    out.setIntegerBase(16);
    out.setPadChar('0');

    //---- Generates 128 bits of random initialization vector ----------------
    Rijndael::CFB<128>::IV iv = {0};
    Rijndael::ECB<128> PRNG; PRNG.setupEncrypt(secretKey);
    for(int i=0; i<sizeof iv; ++i)
        iv[i] = PRNG.get_random() % 256;

    size_t bytes = 0;   // encrypted fake address
    for(int i0 = 0; i0 < sizeof iv; i0+=0x10)
    {
        out << qSetFieldWidth(1) << ":"
            << qSetFieldWidth(6) << bytes
            << qSetFieldWidth(2);
        for(int i=0; i<0x10; i++)
            out << iv[i0+i];
        out << qSetFieldWidth(1) << "\n";
        bytes += 0x10;
    }

    //---- Create stream encryptor -------------------------------------------
    Rijndael::CFB<128> enc(secretKey, iv);

    //---- Process data ------------------------------------------------------
    PROGRESS(0, _memSize);

    byte encrypted[32];
    for(size_t addr = 0; addr < _memSize; addr += 0x10)
    {
        PROGRESS(addr, _memSize);

        enc.encrypt(_buffer + addr, encrypted);
        out << qSetFieldWidth(1) << ":"
            << qSetFieldWidth(6) << bytes
            << qSetFieldWidth(2);
        for(int i=0; i<16; i++)
            out << encrypted[i];
        out << qSetFieldWidth(1) << "\n";
        bytes += 16;
    }

    //---- Process data ------------------------------------------------------
    unsigned int sum = checksum(); // 33.29.BD.1D
    out << qSetFieldWidth(1) << ":"
        << qSetFieldWidth(6) << bytes
        << qSetFieldWidth(2)
        << ((sum    ) & 0xff)
        << ((sum>> 8) & 0xff)
        << ((sum>>16) & 0xff)
        << ((sum>>24) & 0xff)
        << qSetFieldWidth(1) << "\n"
        ;

    qDebug().nospace() << int( bytes/1024.0f) << "KB saved into "
                       << fileName.section('/', -1);

    PROGRESS_RESET();
}
#endif

///////////////////////////////////////////////////////////////////////////////

void HexFile::loadEncrypted(const QString& fileName, unsigned char secretKey[16])
{
    Q_ASSERT(_buffer);

    _file.setFileName(fileName);
    if( ! _file.open(QIODevice::ReadOnly) )
        LOG_THROW( "Cannot open HEX file "  << fileName );

    //---- Read 128 bits of initialization vector ----------------------------
    Rijndael::CFB<128>::IV iv = {0};

    unsigned int bytes = 0;
    bool ok = true;
    for(size_t i0 = 0; i0 < sizeof iv; i0+=0x10)
    {
        QByteArray line = _file.readLine();
        if( line[0] != ':' )
            LOG_THROW( "Bad HEX line" );

        unsigned int readAddr = line.mid(1,6).toInt(&ok, 16);
        if( !ok || readAddr != bytes )
            LOG_THROW( "Bad HEX address" );

        for(int i=0; i<0x10; i++)
            iv[i0+i] = line.mid(2*i+7,2).toInt(&ok, 16);
        if( !ok )
            LOG_THROW( "Bad HEX file format" );

        bytes += 0x10;
    }

    //---- Create stream decryptor -------------------------------------------
    Rijndael::CFB<128> dec(secretKey, iv);

    //---- Process data ------------------------------------------------------
    PROGRESS(0, (int)_memSize);

    Rijndael::Block encrypted;
    for(size_t addr = 0; addr < _memSize; addr += 0x10)
    {
        PROGRESS((int)addr, (int)_memSize);

        QByteArray line = _file.readLine();
        if( line[0] != ':' )
            LOG_THROW( "Bad HEX line" );

        unsigned int readAddr = line.mid(1,6).toInt(&ok, 16);
        if( !ok || readAddr != bytes )
            LOG_THROW( "Bad HEX address" );

        for(int i=0; i<0x10; i++)
            encrypted[i] = line.mid(2*i+7,2).toInt(&ok, 16);
        if( !ok )
            LOG_THROW( "Bad HEX file format" );
        bytes += 0x10;

        dec.decrypt(encrypted, *(Rijndael::Block*)(_buffer+addr));
    }
    QByteArray line = _file.readLine();
    if( bytes != (unsigned int)line.mid(1,6).toInt(&ok, 16) || !ok )
        LOG_THROW( "Bad HEX address" );

    unsigned int sum =  line.mid( 7,2).toInt(&ok, 16)
                     + (line.mid( 9,2).toInt(&ok, 16) << 8 )
                     + (line.mid(11,2).toInt(&ok, 16) << 16)
                     + (line.mid(13,2).toInt(&ok, 16) << 24);
    if( sum != checksum() )
        LOG_THROW( "Bad HEX checksum" );

    PROGRESS_RESET();
}