view tools/data2inc.c @ 2298:b5abfff07ca9

Add new DMGrowBuf helper functions dmGrowBufCopyOffsSize() and dmGrowBufConstCopyOffsSize().
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 04 Jul 2019 10:54:16 +0300
parents e771185db600
children b7cd5dd0b82e
line wrap: on
line source

/*
 * data2inc - Convert binary data to "C"-source or XA-compatible include file
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2003,2009-2019 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "dmtool.h"
#include "dmlib.h"
#include "dmargs.h"
#include "dmmutex.h"


#define SET_DEF_LINELEN  16
#define SET_MAX_FEXTS    16


//
// Types etc
//
typedef struct
{
    char *name;
    char *desc;
    char *fexts[SET_MAX_FEXTS];

    char *defDataType;

    void (*writeHeader) (FILE *fh, const char *name);
    void (*writeDecl) (FILE *fh, const size_t len, const char *name);
    void (*writeData) (FILE *fh, const Uint8 *buf, const size_t len);
    void (*writeFooter) (FILE *fh, const size_t len, const char *name);
} DMOutputFormat;


//
// Options
//
char    *optInFilename = NULL,
        *optOutFilename = NULL,
        *optObjName = "default_object",
        *optDataType = NULL,
        *optAddLine = NULL;

const DMOutputFormat *setFormat = NULL;
int     optIndentation = -1,
        optLineLen = SET_DEF_LINELEN;
BOOL    optHexMode = FALSE,
        optQuiet = FALSE,
        optExtraData = FALSE,
        optFormatting = TRUE;


static const DMOptArg optList[] =
{
    {  0, '?', "help",           "Show this help", OPT_NONE },
    {  1, 'n', "name",           "Set object name", OPT_ARGREQ },
    {  2, 't', "type",           "Set datatype (unsigned char/byte)", OPT_ARGREQ },
    {  3, 'f', "format",         "Set output format (see list below)", OPT_ARGREQ },
    {  4, 'a', "add-line",       "Add this line to start of file", OPT_ARGREQ },
    {  5, 'l', "line-items",     "Set number of items per line", OPT_ARGREQ },
    {  6, 'x', "hexadecimal",    "Use hexadecimal output", OPT_NONE },
    {  7, 'q', "quiet",          "Do not add comments", OPT_NONE },
    {  8, 'N', "no-formatting",  "Disable additional output formatting", OPT_NONE },
    {  9, 'i', "indentation",    "Set indentation (negative value = tab)", OPT_ARGREQ },
    { 10, 'e', "extra-data",     "Add object end labels and size in asm output", OPT_NONE },
};

static const int optListN = sizeof(optList) / sizeof(optList[0]);
static const DMOutputFormat dmFormatList[];
static const int ndmFormatList;


void argShowHelp()
{
    dmPrintBanner(stdout, dmProgName,
    "[options] [sourcefile] [destfile]");

    dmArgsPrintHelp(stdout, optList, optListN, 0);

    fprintf(stdout,
    "\n"
    "Available output formats (-f <frmt>):\n"
    " frmt | Description (filename extensions)\n"
    "------+------------------------------------------\n");

    for (int i = 0; i < ndmFormatList; i++)
    {
        const DMOutputFormat *fmt = &dmFormatList[i];
        fprintf(stdout, "%-5s | %s (",
            fmt->name, fmt->desc);

        for (int n = 0; n < SET_MAX_FEXTS && fmt->fexts[n] != NULL; n++)
        {
            fprintf(stdout, ".%s%s",
                fmt->fexts[n],
                (n + 1 < SET_MAX_FEXTS &&
                fmt->fexts[n + 1] != NULL) ? " " : "");
        }
        fprintf(stdout, ")\n");
    }

    fprintf(stdout,
    "\n"
    "To convert a data file to a C structure using 'Uint8' as type:\n"
    "$ data2inc -n variable_name -t Uint8 input.bin output.h\n"
    "\n"
    );
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN)
    {
        case 0:
            argShowHelp();
            exit(0);
            break;

        case 1:
            optObjName = optArg;
            break;

        case 2:
            optDataType = optArg;
            break;

        case 3:
            for (int i = 0; i < ndmFormatList; i++)
            {
                const DMOutputFormat *fmt = &dmFormatList[i];
                if (strcasecmp(fmt->name, optArg) == 0)
                {
                    setFormat = fmt;
                    return TRUE;
                }
            }
            dmErrorMsg("Invalid format name '%s'.\n",
                optArg);
            return FALSE;

        case 4:
            optAddLine = optArg;
            break;

        case 5:
            optLineLen = atoi(optArg);
            if (optLineLen < 1)
            {
                dmErrorMsg("Invalid line length / number of items per line '%s'.\n",
                    optArg);
                return FALSE;
            }
            break;


        case 6:
            optHexMode = TRUE;
            break;

        case 7:
            optQuiet = TRUE;
            break;

        case 8:
            optFormatting = FALSE;
            break;

        case 9:
            optIndentation = atoi(optArg);
            break;

        case 10:
            optExtraData = TRUE;
            break;

        default:
            dmErrorMsg("Unimplemented option argument '%s'.\n", currArg);
            return FALSE;
    }

    return TRUE;
}


BOOL argHandleFile(char * currArg)
{
    if (optInFilename == NULL)
        optInFilename = currArg;
    else
    if (optOutFilename == NULL)
        optOutFilename = currArg;
    else
        dmErrorMsg("Source and destination filenames already specified, extraneous argument '%s'.\n", currArg);

    return TRUE;
}


/* Assembler include data output functions
 */
void writeHeader_ASM(FILE *fh, const char *name)
{
    if (name)
        fprintf(fh, "; '%s'", name);
    else
        fprintf(fh, "; Generated");

    fprintf(fh, " by %s v%s\n",
        dmProgName, dmProgVersion);
}


void writeDecl_ASM(FILE *fh, const size_t len, const char *name)
{
    if (optExtraData)
        fprintf(fh, "%s_size = %" DM_PRIu_SIZE_T "\n", name, len);

    fprintf(fh, "%s:\n", name);
}


void writeData_ASM(FILE *fh, const Uint8 * buf, const size_t len)
{
    fprintf(fh, "%s ", optDataType);

    for (size_t i = 0; i < len; i++)
    {
        if (optFormatting)
        {
            if (optHexMode)
                fprintf(fh, "$%.2x", buf[i]);
            else
                fprintf(fh, "%3d", buf[i]);
        }
        else
        {
            if (optHexMode)
                fprintf(fh, "$%x", buf[i]);
            else
                fprintf(fh, "%d", buf[i]);
        }

        if (i + 1 < len)
            fprintf(fh, optFormatting ? ", " : ",");
    }
}


void writeFooter_ASM(FILE *fh, const size_t len, const char *name)
{
    (void) len;
    if (optExtraData)
        fprintf(fh, "%s_end: \n", name);
    else
        fprintf(fh, "\n");
}


/* ANSI-C include data output functions
 */
void writeHeader_C(FILE *fh, const char *name)
{
    if (name)
        fprintf(fh, "/* '%s' generated", name);
    else
        fprintf(fh, "/* Generated");

    fprintf(fh, " by %s v%s\n" " */\n",
        dmProgName, dmProgVersion);
}


void writeDecl_C(FILE *fh, const size_t len, const char *name)
{
    fprintf(fh, "%s %s[%" DM_PRIu_SIZE_T "] = {\n",
        optDataType, name, len);

    printf("extern %s %s[%" DM_PRIu_SIZE_T "];\n",
        optDataType, name, len);
}


void writeData_C(FILE *fh, const Uint8 *buf, const size_t len)
{
    for (size_t i = 0; i < len; i++)
    {
        if (optFormatting)
        {
            if (optHexMode)
                fprintf(fh, "0x%.2x", buf[i]);
            else
                fprintf(fh, "%3d", buf[i]);
        }
        else
        {
            if (optHexMode)
                fprintf(fh, "0x%x", buf[i]);
            else
                fprintf(fh, "%d", buf[i]);
        }

        fprintf(fh, optFormatting ? ", " : ",");
    }
}


void writeFooter_C(FILE *fh, const size_t len, const char *name)
{
    (void) len;
    (void) name;

    fprintf(fh, "};\n");
}


static const DMOutputFormat dmFormatList[] =
{
    {
        "asm",
        "XA65 compatible assembler",
        { "s", "asm", NULL },
        ".byte",

        writeHeader_ASM,
        writeDecl_ASM,
        writeData_ASM,
        writeFooter_ASM,
    },

    {
        "c",
        "ANSI C array",
        { "c", "h", "cc", "cpp", "hpp", "c++", NULL },

        "unsigned char",

        writeHeader_C,
        writeDecl_C,
        writeData_C,
        writeFooter_C,
    },
};

static const int ndmFormatList = sizeof(dmFormatList) / sizeof(dmFormatList[0]);


off_t dmGetFileSize(FILE *fh)
{
    off_t len, pos = ftello(fh);
    fseeko(fh, 0, SEEK_END);
    len = ftello(fh);
    fseeko(fh, pos, SEEK_SET);
    return len;
}


const DMOutputFormat *dmGuessFormatFromName(const char *filename)
{
    const char *fext;

    if ((fext = strrchr(filename, '.')) == NULL)
        return NULL;

    for (int i = 0; i < ndmFormatList; i++)
    {
        const DMOutputFormat *fmt = &dmFormatList[i];

        for (int n = 0; n < SET_MAX_FEXTS && fmt->fexts[n] != NULL; n++)
        {
            if (strcasecmp(fext + 1, fmt->fexts[n]) == 0)
                return fmt;
        }
    }

    return NULL;
}


int main(int argc, char *argv[])
{
    FILE *inFile = NULL, *outFile = NULL;
    Uint8 *dataBuf = NULL;
    size_t dataSize;
    off_t totalSize;
    int res;

    // Initialize
    dmInitProg("data2inc", "Data to include converter", "0.7", NULL, NULL);
    dmVerbosity = 0;

    // Parse arguments
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile, OPTH_BAILOUT))
        exit(1);

    // Determine output type, if not specified
    if (setFormat == NULL)
    {
        if (optOutFilename == NULL)
        {
            dmErrorMsg("Output format not specified and no output filename given (try --help)\n");
            goto exit;
        }

        if ((setFormat = dmGuessFormatFromName(optOutFilename)) == NULL)
        {
            dmErrorMsg("Could not guess output format from filename '%s'.\n",
                optOutFilename);
            goto exit;
        }

        dmMsg(0, "Guessed output format: %s (%s)\n",
            setFormat->desc, setFormat->name);
    }

    // Set some option defaults
    if (optDataType == NULL)
        optDataType = setFormat->defDataType;

    // Open the files
    if (optInFilename == NULL)
        inFile = stdin;
    else
    if ((inFile = fopen(optInFilename, "rb")) == NULL)
    {
        res = dmGetErrno();
        dmErrorMsg("Error opening input file '%s'. (%s)\n",
            optInFilename, dmErrorStr(res));
        goto exit;
    }

    if (optOutFilename == NULL)
        outFile = stdout;
    else
    if ((outFile = fopen(optOutFilename, "wa")) == NULL)
    {
        res = dmGetErrno();
        dmErrorMsg("Error creating output file '%s'. (%s)\n",
            optOutFilename, dmErrorStr(res));
        goto exit;
    }

    // Allocate linebuffer
    dataSize = optLineLen * sizeof(Uint8);
    if ((dataBuf = dmMalloc(dataSize)) == NULL)
    {
        dmErrorMsg("Could not allocate %" DM_PRIu_SIZE_T " byte buffer.\n",
            dataSize);
        goto exit;
    }

    // Get sourcefile size
    totalSize = dmGetFileSize(inFile);

    // Output header
    if (!optQuiet)
        setFormat->writeHeader(outFile, optOutFilename);

    if (optAddLine)
        fprintf(outFile, "%s\n", optAddLine);

    // Output declaration
    setFormat->writeDecl(outFile, totalSize, optObjName);

    // Output data
    while (!feof(inFile))
    {
        res = fread(dataBuf, 1, dataSize, inFile);
        if (res > 0)
        {
            if (optIndentation < 0)
            {
                for (int i = 0; i < -optIndentation; i++)
                    fputs("\t", outFile);
            }
            else
            if (optIndentation > 0)
            {
                for (int i = 0; i < optIndentation; i++)
                    fputs(" ", outFile);
            }

            setFormat->writeData(outFile, dataBuf, res);

            fprintf(outFile, "\n");
        }
    }

    // Output footer
    setFormat->writeFooter(outFile, totalSize, optObjName);

exit:
    // Cleanup
    if (inFile != NULL)
        fclose(inFile);

    if (outFile != NULL)
        fclose(outFile);

    dmFree(dataBuf);

    return 0;
}