
/*****  P A C K H E X . C  ************************************************
 *
 *   Packhex is a hex-file compaction utility.  It attempts to concatenate
 *   hex records to produce more size-efficient packaging.
 *
 *   Limitations: Input files must be correctly formatted.  This utility
 *                is not robust enough to detect hex-record formatting
 *                errors.
 *
 *   Published:   5/93 Embedded Systems magazine
 *
 *   Compiler:    Microsoft C 6.0
 *                cl /F 1000 packhex.c
 *
 *
 *   $Id$
 *
 **************************************************************************/


/* #define SMALLER_RECORDS */
#ifdef SMALLER_RECORDS
#define MAX_LEN_S1_RECS 128
#define MAX_LEN_S2_RECS 128
#define MAX_LEN_S3_RECS 128
#else
#define MAX_LEN_S1_RECS 252
#define MAX_LEN_S2_RECS 251
#define MAX_LEN_S3_RECS 250
#endif


/*--------------------------------- includes ---------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"

#ifndef VMS
#ifndef HAVE_STRERROR
extern int sys_nerr;
extern char *sys_errlist[];

#define strerror( _err ) \
  ((_err) < sys_nerr) ? sys_errlist [(_err)] : "unknown error"

#else   /* HAVE_STRERROR */
char *strerror ();
#endif
#else   /* VMS */
char *strerror (int,...);
#endif

#if defined(__unix__) && !defined(EXIT_FAILURE)
#define EXIT_FAILURE -1
#define EXIT_SUCCESS  0
#endif

/*--------------------------------- defines ----------------------------------*/

#define YES                   1
#define MAX_LINE_SIZE       600
#define EOS                 '\0'


/*---------------------------------- macros ----------------------------------*/

/* Convert ASCII hexadecimal digit to value. */

#define HEX_DIGIT( C )   ( ( ( ( C ) > '9' ) ? ( C ) + 25 : ( C ) ) & 0xF )


/*--------------------------------- typedefs ---------------------------------*/

typedef unsigned char   Boolean;
typedef unsigned char   Uchar;
typedef unsigned int    Uint;
typedef unsigned long   Ulong;

typedef struct   /* Functions and constant returning Hex-record vital stats. */
{
    Boolean    ( *is_data_record  )( char *              );
    Ulong      ( *get_address     )( char *              );
    Uint       ( *get_data_count  )( char *              );
    const Uint    max_data_count;
    char      *( *get_data_start  )( char *              );
    void       ( *put_data_record )( Uint, Ulong, char * );
} Rec_vitals;


/*--------------------------- function prototypes ----------------------------*/

Rec_vitals  * identify_first_data_record( char * );
Ulong         get_ndigit_hex( char *, int );


/*----------------------------- Intel Hex format -----------------------------*/

/*
 *    Intel Hex data-record layout
 *
 *    :aabbbbccd...dee
 *
 *    :      - header character
 *    aa     - record data byte count, a 2-digit hex value
 *    bbbb   - record address, a 4-digit hex value
 *    cc     - record type, a 2-digit hex value:
 *              "00" is a data record
 *              "01" is an end-of-data record
 *              "02" is an extended-address record
 *              "03" is a start record
 *    d...d  - data (always an even number of chars)
 *    ee     - record checksum, a 2-digit hex value
 *             checksum = 2's complement
 *                 [ (sum of bytes: aabbbbccd...d) modulo 256 ]
 */


Boolean is_intel_data_rec( char * rec_str )
{
    return( ( rec_str[ 0 ] == ':' )  &&  ( rec_str[ 8 ] == '0' ) );
}

Uint get_intel_rec_data_count( char * rec_str )
{
    return( ( Uint ) get_ndigit_hex( rec_str + 1, 2 ) );
}

Ulong get_intel_rec_address( char * rec_str )
{
    return( get_ndigit_hex( rec_str + 3, 4 ) );
}

char * get_intel_rec_data_start( char * rec_str )
{
    return( rec_str + 9 );
}

void put_intel_data_rec( Uint count, Ulong address, char * data_str )
{
    char    *ptr;
    Uint    sum = count + ( address >> 8 & 0xff ) + ( address & 0xff );

    for ( ptr = data_str ; *ptr != EOS ; ptr += 2 )
        sum += ( Uint ) get_ndigit_hex( ptr, 2 );

    printf(
      ":%02X%04lX00%s%02X\n", count, address, data_str, (~sum + 1) & 0xff
    );
}


Rec_vitals  intel_hex =
{
    is_intel_data_rec,
    get_intel_rec_address,
    get_intel_rec_data_count,
    255,                        /* Maximum data bytes in a record. */
    get_intel_rec_data_start,
    put_intel_data_rec
};


/*------------------------- Motorola S1-record format ------------------------*/

/*
 *    Motorola S-record data-record layout
 *
 *    Sabbc...cd...dee
 *
 *    S      - header character
 *    a      - record type, a 1-digit value:
 *               "0" is a header record
 *               "1" is a 2-byte-address data record
 *               "2" is a 3-byte-address data record
 *               "3" is a 4-byte-address data record
 *               "7" is a 4-byte-address end-of-data record
 *               "8" is a 3-byte-address end-of-data record
 *               "9" is a 2-byte-address end-of-data record
 *    bb     - record length in bytes, a 2-digit hex value
 *             (record length doesn't count the header/type
 *              chars and checksum byte)
 *    c...c  - record address, a 4-, 6-, or 8-digit value,
 *               depending on record type
 *    d...d  - data (always an even number of chars)
 *    ee     - record checksum, a 2-digit hex value
 *             checksum = 1's complement
 *             [ (sum of all bytes: bbc..cd...d) modulo 256 ]
 */

#define S1_COUNT_OFFSET         3


Boolean is_moto_s1_data_rec( char * rec_str )
{
    return ( ( rec_str[ 0 ] == 'S' )  &&  ( rec_str[ 1 ] == '1' ) );
}

Uint get_moto_s1_rec_data_count( char * rec_str )
{
    return( ( Uint ) get_ndigit_hex( rec_str + 2, 2 ) - S1_COUNT_OFFSET );
}

Ulong get_moto_s1_rec_address( char * rec_str )
{
    return( get_ndigit_hex( rec_str + 4, 4 ) );
}

char * get_moto_s1_rec_data_start( char * rec_str )
{
    return( rec_str + 8 );
}

void put_moto_s1_data_rec( Uint count, Ulong address, char * data_str )
{
    char   *ptr;
    Uint    sum = S1_COUNT_OFFSET + count +
                    ( address >> 8 & 0xff ) + ( address & 0xff );

    for ( ptr = data_str ; *ptr != EOS ; ptr += 2 )
        sum += ( Uint ) get_ndigit_hex( ptr, 2 );

    printf(
      "S1%02X%04lX%s%02X\n",
      count + S1_COUNT_OFFSET, address, data_str, ~sum & 0xff
    );
}


Rec_vitals  motorola_s1_rec =
{
    is_moto_s1_data_rec,
    get_moto_s1_rec_address,
    get_moto_s1_rec_data_count,
    MAX_LEN_S1_RECS,            /* Maximum data bytes in a record. */
    get_moto_s1_rec_data_start,
    put_moto_s1_data_rec
};


/*------------------------- Motorola S2-record format ------------------------*/

#define S2_COUNT_OFFSET         4

Boolean is_moto_s2_data_rec( char * rec_str )
{
    return ( ( rec_str[ 0 ] == 'S' )  &&  ( rec_str[ 1 ] == '2' ) );
}

Uint get_moto_s2_rec_data_count( char * rec_str )
{
    return( ( Uint ) get_ndigit_hex( rec_str + 2, 2 ) - S2_COUNT_OFFSET );
}

Ulong get_moto_s2_rec_address( char * rec_str )
{
    return( get_ndigit_hex( rec_str + 4, 6 ) );
}

char * get_moto_s2_rec_data_start( char * rec_str )
{
    return( rec_str + 10 );
}

void put_moto_s2_data_rec( Uint count, Ulong address, char * data_str )
{
    char    *ptr;
    Uint    sum = S2_COUNT_OFFSET + count + ( address >> 16 & 0xff ) +
                                            ( address >>  8 & 0xff ) +
                                            ( address       & 0xff );

    for ( ptr = data_str ; *ptr != EOS ; ptr += 2 )
        sum += ( Uint ) get_ndigit_hex( ptr, 2 );

    printf(
      "S2%02X%06lX%s%02X\n",
      count + S2_COUNT_OFFSET, address, data_str, ~sum & 0xff
    );
}


Rec_vitals  motorola_s2_rec =
{
    is_moto_s2_data_rec,
    get_moto_s2_rec_address,
    get_moto_s2_rec_data_count,
    MAX_LEN_S2_RECS,            /* Maximum data bytes in a record. */
    get_moto_s2_rec_data_start,
    put_moto_s2_data_rec
};


/*------------------------- Motorola S3-record format ------------------------*/

#define S3_COUNT_OFFSET         5

Boolean is_moto_s3_data_rec( char * rec_str )
{
    return ( ( rec_str[ 0 ] == 'S' )  &&  ( rec_str[ 1 ] == '3' ) );
}

Uint get_moto_s3_rec_data_count( char * rec_str )
{
    return( ( Uint ) get_ndigit_hex( rec_str + 2, 2 ) - S3_COUNT_OFFSET );
}

Ulong get_moto_s3_rec_address( char * rec_str )
{
    return( get_ndigit_hex( rec_str + 4, 8 ) );
}

char * get_moto_s3_rec_data_start( char * rec_str )
{
    return( rec_str + 12 );
}

void put_moto_s3_data_rec( Uint count, Ulong address, char * data_str )
{
    char    *ptr;
    Uint     sum = S3_COUNT_OFFSET + count + ( address >> 24 & 0xff ) +
                                             ( address >> 16 & 0xff ) +
                                             ( address >>  8 & 0xff ) +
                                             ( address       & 0xff );

    for ( ptr = data_str ; *ptr != EOS ; ptr += 2 )
        sum += ( Uint ) get_ndigit_hex( ptr, 2 );

    printf(
      "S3%02X%08lX%s%02X\n",
      count + S3_COUNT_OFFSET, address, data_str, ~sum & 0xff
   );
}


Rec_vitals  motorola_s3_rec =
{
    is_moto_s3_data_rec,
    get_moto_s3_rec_address,
    get_moto_s3_rec_data_count,
    MAX_LEN_S3_RECS,            /* Maximum data bytes in a record. */
    get_moto_s3_rec_data_start,
    put_moto_s3_data_rec
};


/*-------------------- Put your favorite hex format here ---------------------*/

/*
 *    * * *  The following is a template for an additional hex format:  * * *
 *
 *
 *    Boolean is_X_data_rec( char * rec_str ) {}
 *
 *    Uint get_X_rec_data_count( char * rec_str ) {}
 *
 *    Ulong get_X_rec_address( char * rec_str ) {}
 *
 *    char * get_X_rec_data_start( char * rec_str ) {}
 *
 *    void put_X_data_rec( Uint count, Ulong address, char * data_str ) {}
 *
 *    Rec_vitals  X_rec =
 *    {
 *        is_X_data_rec,
 *        get_X_rec_address,
 *        get_X_rec_data_count,
 *        MAXIMUM DATA BYTES IN A RECORD,
 *        get_X_rec_data_start,
 *        put_X_data_rec
 *    };
 *
 */

/*----------------------------------------------------------------------------*/


/*
 *   Put address of additional Rec_vitals structures
 *   in this array, before the NULL entry.
 */

Rec_vitals  *formats[] =
{
    &intel_hex,
    &motorola_s1_rec,
    &motorola_s2_rec,
    &motorola_s3_rec,
    ( Rec_vitals * ) NULL
};


/****   main   *****************************************************************
*
*
*       Expects: Nothing (no command-line parameters).
*
*       Returns: Exit status (EXIT_SUCCESS or EXIT_FAILURE).
*
*       Reads hex records on the standard input and attempts to
*       splice adjacent data fields together.  Results appear on
*       the standard output.
*
*******************************************************************************/

int main(
  int argc,
  char **argv
)
{

    char       inbuff[ MAX_LINE_SIZE ], outbuff[ MAX_LINE_SIZE ];
    char       *in_dptr, *out_dptr;
    int        d_total, d_count, d_excess, n;
    Ulong      in_rec_addr, out_rec_addr = 0;
    Rec_vitals *rptr;


    /* Sift through file until first hex record is identified.    */
    if ( ( rptr = identify_first_data_record( inbuff ) ) == NULL )
    {
        fputs( "No hex records found.\n", stderr );
        exit( EXIT_FAILURE );
    }


    /* Attempt data-record splicing until end-of-file is reached. */
    d_total = 0;
    do
    {
        if ( rptr->is_data_record( inbuff ) == YES )
        { /* Input record is a data record. */
            d_count     = rptr->get_data_count( inbuff );
            in_rec_addr = rptr->get_address( inbuff );
            in_dptr     = rptr->get_data_start( inbuff );

            if ( d_total == 0  ||  in_rec_addr != out_rec_addr + d_total )
            { /* Begin a new output record. */
                if ( d_total != 0 )
                    rptr->put_data_record( d_total, out_rec_addr, outbuff );
                out_dptr     = outbuff;
                n = d_total  = d_count;
                out_rec_addr = in_rec_addr;
            }
            else if
              ( ( d_excess = d_total + d_count - rptr->max_data_count ) > 0 )
            { /* Output a maximum-length record, then start a new record. */
                strncat( outbuff, in_dptr, 2 * ( d_count - d_excess ) );
                rptr->put_data_record(
                  rptr->max_data_count, out_rec_addr, outbuff
                );
                in_dptr      += 2 * ( d_count - d_excess );
                out_dptr      = outbuff;
                n = d_total   = d_excess;
                out_rec_addr += rptr->max_data_count;
            }
            else
            { /* Append input record's data field with accumulated data. */
                out_dptr = outbuff + ( 2 * d_total );
                d_total += n = d_count;
            }
            strncpy( out_dptr, in_dptr, 2 * n );
            out_dptr[ 2 * n ] = EOS;
        }
        else
        { /* Not a data record;
           * flush accumulated data then echo non-data record.
           */
            if ( d_total != 0 )
            {
                rptr->put_data_record( d_total, out_rec_addr, outbuff );
                d_total = 0;
            }
            puts( inbuff );
        }
    } while ( gets( inbuff ) != NULL );


    return ( EXIT_SUCCESS );

}


/****   identify_first_data_record   *******************************************
*
*       Expects: Pointer to hex-record line buffer.
*
*       Returns: Pointer to hex-record structure (NULL if no match found).
*
*       Reads the standard input, line by line, searching for a valid
*       record header character.  If a valid header is found, a pointer
*       to the hex-record's type structure is returned, otherwise NULL.
*
*       The input-stream pointer is left pointing to the first valid hex record.
*
*******************************************************************************/

Rec_vitals * identify_first_data_record( char * buff_ptr )
{
    Rec_vitals  ** ptr;

    while ( gets( buff_ptr ) != NULL )
    {
        for ( ptr = formats ; *ptr != ( Rec_vitals * ) NULL ; ptr++ )
            if ( ( *ptr )->is_data_record( buff_ptr ) == YES )
                return( *ptr );        /* Successful return.        */

        puts( buff_ptr );              /* Echo non-hex-record line. */
    }

    return( ( Rec_vitals * ) NULL );   /* Unsuccessful return.      */
}


/****   get_ndigit_hex   *******************************************************
*
*       Expects: Pointer to first ASCII hexadecimal digit, number of digits.
*
*       Returns: Value of hexadecimal string as an unsigned long.
*
*******************************************************************************/

Ulong get_ndigit_hex( char * cptr, int digits )
{
    Ulong    value;

    for ( value = 0 ; --digits >= 0 ; cptr++ )
        value = ( value * 16L ) + HEX_DIGIT( *cptr );

    return( value );
}
