changeset 2:ffb795582a91

More work.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 25 Sep 2014 01:46:05 +0300
parents 25a3a142e909
children dbe35d78e621
files sidinfo.c
diffstat 1 files changed, 349 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/sidinfo.c	Wed Sep 24 22:15:55 2014 +0300
+++ b/sidinfo.c	Thu Sep 25 01:46:05 2014 +0300
@@ -1,14 +1,236 @@
+/*
+ * SIDInfo - PSID/RSID information displayer
+ * Written by Matti 'ccr' Hämäläinen
+ * (C) Copyright 2014 Tecnic Software productions (TNSP)
+ */
 #include "th_args.h"
 #include "th_endian.h"
+#include "th_string.h"
 #include "th_crypto.h"
 
+#define PSID_MAGIC_LEN    4
+#define PSID_STR_LEN      32
 #define PSID_BUFFER_SIZE  (1024 * 16)
 
 
+enum
+{
+    PSF_NONE	       = 0,
+
+    PSF_TYPE	       = 0x00000001,
+    PSF_VERSION        = 0x00000002,
+    PSF_DATA_OFFS      = 0x00000004,
+    PSF_LOAD_ADDR      = 0x00000008,
+    PSF_INIT_ADDR      = 0x00000010,
+    PSF_PLAY_ADDR      = 0x00000020,
+    PSF_SONGS          = 0x00000040,
+    PSF_START_SONG     = 0x00000080,
+    PSF_SPEEDS         = 0x00000100,
+    PSF_SID_NAME       = 0x00000200,
+    PSF_SID_AUTHOR     = 0x00000400,
+    PSF_SID_COPYRIGHT  = 0x00000800,
+
+    PSF_DATA_SIZE      = 0x00100000,
+    PSF_HASH           = 0x00200000,
+
+    PSF_ALL            = 0xffffffff,
+};
+
+
 typedef struct
 {
-    char magic[4];       // "PSID" / "RSID" magic identifier
-    uint16_t version,    // Version number
+    uint32_t flag;
+    char *name;
+    char *lname;
+} PSFOption;
+
+
+const PSFOption optPSFlags[] =
+{
+    { PSF_TYPE           , "Type"       , NULL },
+    { PSF_VERSION        , "Version"    , NULL },
+    { PSF_DATA_OFFS      , "DataOffs"   , "Data offset" },
+    { PSF_DATA_SIZE      , "DataSize"   , "Data size" },
+    { PSF_LOAD_ADDR      , "LoadAddr"   , "Load address" },
+    { PSF_INIT_ADDR      , "InitAddr"   , "Init address" },
+    { PSF_PLAY_ADDR      , "PlayAddr"   , "Play address" },
+    { PSF_SONGS          , "Songs"      , "Songs" },
+    { PSF_START_SONG     , "StartSong"  , "Start song" },
+    { PSF_SID_NAME       , "Name"       , NULL },
+    { PSF_SID_AUTHOR     , "Author"     , NULL },
+    { PSF_SID_COPYRIGHT  , "Copyright"  , NULL },
+    { PSF_HASH           , "Hash"       , NULL },
+    { PSF_ALL            , "All"        , NULL },
+};
+
+const int noptPSFlags = sizeof(optPSFlags) / sizeof(optPSFlags[0]);
+
+
+/* Options
+ */
+char *	optInFilename = NULL;
+BOOL	optParsable = FALSE,
+        optHexadecimal = FALSE;
+uint32_t optFields = PSF_ALL;
+
+
+/* Arguments
+ */
+static optarg_t optList[] =
+{
+    { 0, '?', "help",       "Show this help", OPT_NONE },
+    { 1, 'v', "verbose",    "Be more verbose", OPT_NONE },
+    { 2, 'p', "parsable",   "Output in script-parsable format", OPT_NONE },
+    { 3, 'f', "fields",     "Show only specified field(s)", OPT_ARGREQ },
+    { 4, 'x', "hex",        "Use hexadecimal values", OPT_NONE },
+};
+
+static const int optListN = (sizeof(optList) / sizeof(optList[0]));
+
+
+void argShowHelp(void)
+{
+    int index, n;
+
+    th_print_banner(stdout, th_prog_name, "[options] <sid filename>");
+    th_args_help(stdout, optList, optListN);
+    printf(
+        "\n"
+        "Available fields:\n");
+    
+    for (index = n = 0; index < noptPSFlags; index++)
+    {
+        const PSFOption *opt = &optPSFlags[index];
+        printf("%s%s", opt->name, (index < noptPSFlags - 1) ? ", " : "\n\n");
+        if (++n > 5)
+        {
+            printf("\n");
+            n = 0;
+        }
+    }
+}
+
+
+int argMatchPSField(const char *field)
+{
+    int index, found = -1;
+    size_t len = strlen(field);
+    for (index = 0; index < noptPSFlags; index++)
+    {
+        const PSFOption *opt = &optPSFlags[index];
+        if (strncasecmp(opt->name, field, len) == 0)
+        {
+            if (found >= 0)
+                return -2;
+            found = index;
+        }
+    }
+
+    return found;
+}
+
+
+BOOL argParsePSField(char *opt, char *end, uint32_t *fields)
+{
+    // Trim whitespace
+    if (end != NULL)
+    {
+        *end = 0;
+        for (end--; end > opt && *end && th_isspace(*end); end--)
+            *end = 0;
+    }
+    while (*opt && th_isspace(*opt)) opt++;
+
+    // Match field name
+    int found = argMatchPSField(opt);
+    switch (found)
+    {
+        case -1:
+            THERR("No such flag '%s'.\n", opt);
+            return FALSE;
+
+        case -2:
+            THERR("Flag '%s' is ambiguous.\n", opt);
+            return FALSE;
+        
+        default:
+            *fields |= optPSFlags[found].flag;
+            return TRUE;
+    }
+}
+
+
+BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    switch (optN)
+    {
+    case 0:
+        argShowHelp();
+        exit(0);
+        break;
+
+    case 1:
+        th_verbosityLevel++;
+        break;
+
+    case 2:
+        optParsable = TRUE;
+        break;
+
+    case 3:
+        {
+            char *start = optArg;
+            optFields = PSF_NONE;
+
+            while (*start)
+            {
+                char *end = strchr(start, ',');
+
+                if (!argParsePSField(start, end, &optFields))
+                    return FALSE;
+
+                if (!end)
+                    break;
+
+                start = end + 1;
+            }
+            
+            //fprintf(stderr, "%08x\n", optFields);
+        }
+        break;
+
+    case 4:
+        optHexadecimal = TRUE;
+        break;
+
+    default:
+        THERR("Unknown option '%s'.\n", currArg);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+BOOL argHandleFile(char *currArg)
+{
+    if (!optInFilename)
+        optInFilename = currArg;
+    else
+    {
+        THERR("Filename already specified on commandline!\n");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+typedef struct
+{
+    char magic[PSID_MAGIC_LEN + 1]; // "PSID" / "RSID" magic identifier
+    uint16_t
+        version,         // Version number
         dataOffset,      // Start of actual c64 data in file
         loadAddress,     // Loading address
         initAddress,     // Initialization address
@@ -16,9 +238,9 @@
         nSongs,          // Number of subsongs
         startSong;       // Default starting song
     uint32_t speed;      // Speed
-    char sidName[32];    // Descriptive text-fields, ASCIIZ
-    char sidAuthor[32];
-    char sidCopyright[32];
+    char sidName[PSID_STR_LEN + 1];    // Descriptive text-fields, ASCIIZ
+    char sidAuthor[PSID_STR_LEN + 1];
+    char sidCopyright[PSID_STR_LEN + 1];
 
     // PSIDv2 data
     uint16_t flags;      // Flags
@@ -33,7 +255,16 @@
 } PSIDHeader;
 
 
-int si_read_sid_file(FILE *inFile, PSIDHeader *psid)
+static void siAppendHash16(th_md5state_t *state, uint16_t data)
+{
+    uint8_t ib8[2];
+    ib8[0] = data & 0xff;
+    ib8[1] = data >> 8;
+    th_md5_append(state, (uint8_t *) &ib8, sizeof(ib8));
+}
+
+
+int siReadPSIDFile(FILE *inFile, PSIDHeader *psid)
 {
     th_md5state_t state;
     uint8_t tmp8, *fileData = NULL;
@@ -50,7 +281,7 @@
     }
     
     // Read PSID header in
-    if (!th_fread_str(inFile, (uint8_t *) psid->magic, sizeof(psid->magic)) ||
+    if (!th_fread_str(inFile, (uint8_t *) psid->magic, PSID_MAGIC_LEN) ||
         !th_fread_be16(inFile, &psid->version) ||
         !th_fread_be16(inFile, &psid->dataOffset) ||
         !th_fread_be16(inFile, &psid->loadAddress) ||
@@ -64,6 +295,8 @@
         goto error;
     }
 
+    psid->magic[PSID_MAGIC_LEN] = 0;
+
     if ((psid->magic[0] != 'R' && psid->magic[0] != 'P') ||
         psid->magic[1] != 'S' || psid->magic[2] != 'I' || psid->magic[3] != 'D' ||
         psid->version < 1 || psid->version > 3)
@@ -74,14 +307,18 @@
 
     psid->isRSID = psid->magic[0] == 'R';
 
-    if (!th_fread_str(inFile, (uint8_t *)psid->sidName, sizeof(psid->sidName)) ||
-        !th_fread_str(inFile, (uint8_t *)psid->sidAuthor, sizeof(psid->sidAuthor)) ||
-        !th_fread_str(inFile, (uint8_t *)psid->sidCopyright, sizeof(psid->sidCopyright)))
+    if (!th_fread_str(inFile, (uint8_t *)psid->sidName, PSID_STR_LEN) ||
+        !th_fread_str(inFile, (uint8_t *)psid->sidAuthor, PSID_STR_LEN) ||
+        !th_fread_str(inFile, (uint8_t *)psid->sidCopyright, PSID_STR_LEN))
     {
         THERR("Error reading SID file header.\n");
         goto error;
     }
 
+    psid->sidName[PSID_STR_LEN] = 0;
+    psid->sidAuthor[PSID_STR_LEN] = 0;
+    psid->sidCopyright[PSID_STR_LEN] = 0;
+
     // Check if we need to load PSIDv2NG header ...
     if (psid->version >= 2)
     {
@@ -102,7 +339,8 @@
     // Process actual data
     psid->dataSize = 0;
     first = TRUE;
-    do {
+    do
+    {
         read = fread(fileData, sizeof(uint8_t), PSID_BUFFER_SIZE, inFile);
         psid->dataSize += read;
         if (read < 16)
@@ -125,17 +363,9 @@
     } while (read > 0 && !feof(inFile));
 
     // Append header data to hash
-#define THADDHASH(QDATAB) do {			\
-        uint8_t ib8[2];				\
-        ib8[0] = (QDATAB & 0xff);		\
-        ib8[1] = (QDATAB >> 8);			\
-        th_md5_append(&state, (uint8_t *) &ib8, sizeof(ib8));    \
-    } while (0)
-
-    THADDHASH(psid->initAddress);
-    THADDHASH(psid->playAddress);
-    THADDHASH(psid->nSongs);
-#undef THADDHASH
+    siAppendHash16(&state, psid->initAddress);
+    siAppendHash16(&state, psid->playAddress);
+    siAppendHash16(&state, psid->nSongs);
 
     // Append song speed data to hash
     tmp8 = psid->isRSID ? 60 : 0;
@@ -173,17 +403,110 @@
 }
 
 
+static void siPrintLinePrefix(FILE *outFile, const BOOL parsable, const char *name)
+{
+    if (parsable)
+        fprintf(outFile, "%s=", name);
+    else
+        fprintf(outFile, "%-20s : ", name);
+}
+
+
+static void siPrintPSIDInfoLine(FILE *outFile, const BOOL parsable, const BOOL hex,
+    const uint32_t flags, const int xindex, const char *xfmt, const char *xaltfmt, ...)
+{
+    const PSFOption *opt = &optPSFlags[xindex];
+    if (flags & opt->flag)
+    {
+        va_list ap;
+        const char *fmt = hex ? (xaltfmt != NULL ? xaltfmt : xfmt) : xfmt;
+
+        siPrintLinePrefix(outFile, parsable, (parsable || opt->lname == NULL) ? opt->name : opt->lname);
+
+        va_start(ap, xaltfmt);
+
+        if (parsable)
+            vfprintf(outFile, fmt, ap);
+        else
+            vfprintf(outFile, fmt, ap);
+
+        va_end(ap);
+        
+        fprintf(outFile, "\n");
+    }
+}
+
+#define PR(xindex, xfmt, xaltfmt, ...) siPrintPSIDInfoLine(outFile, parsable, hex, flags, xindex, xfmt, xaltfmt, __VA_ARGS__ )
+
+
+void siPrintPSIDInformation(FILE *outFile, const BOOL parsable, const BOOL hex, const uint32_t flags, const PSIDHeader *psid)
+{
+    PR( 0, "%s", NULL, psid->magic);
+    PR( 1, "%d.%d", NULL, (psid->version & 0xff), (psid->version >> 8));
+    PR( 2, "%d", "$%08x", psid->dataOffset);
+    PR( 3, "%d", "$%08x", psid->dataSize);
+    PR( 4, "%d", "$%04x", psid->loadAddress);
+    PR( 5, "%d", "$%04x", psid->initAddress);
+    PR( 6, "%d", "$%04x", psid->playAddress);
+    PR( 7, "%d", "$%04x", psid->nSongs);
+    PR( 8, "%d", "$%04x", psid->startSong);
+
+    PR( 9, "%s", NULL, psid->sidName);
+    PR(10, "%s", NULL, psid->sidAuthor);
+    PR(11, "%s", NULL, psid->sidCopyright); 
+
+    if (flags & PSF_HASH)
+    {
+        siPrintLinePrefix(outFile, parsable, "Hash");
+        th_md5_print(outFile, psid->hash);
+        fprintf(outFile, "\n");
+    }
+}
+
 
 int main(int argc, char *argv[])
 {
+    FILE *inFile = NULL;
+    int ret = -1;
+    PSIDHeader psid;
+
     // Initialize
     th_init("SIDInfo", "PSID/RSID information displayer", "0.1", NULL, NULL);
     th_verbosityLevel = 0;
 
-    // Try to open the file
-//    if ((inFile = fopen(inFilename, "rb")) == NULL)
-//        goto error;
+    // Parse command line arguments
+    if (!th_args_process(argc, argv, optList, optListN,
+                         argHandleOpt, argHandleFile, FALSE))
+        return -1;
+
+    if (optInFilename == NULL)
+    {
+        argShowHelp();
+        THERR("No filename specified.\n");
+        goto error;
+    }
 
+    // Try to open the file
+    if ((inFile = fopen(optInFilename, "rb")) == NULL)
+    {
+        THERR("Could not open file '%s'.\n", optInFilename);
+        goto error;
+    }
 
-    return 0;
+    // Read PSID data
+    if ((ret = siReadPSIDFile(inFile, &psid)) != 0)
+        goto error;
+
+    // Output
+    fprintf(stdout, "%s\n", optInFilename);
+    siPrintPSIDInformation(stdout, optParsable, optHexadecimal, optFields, &psid);
+
+    ret = 0;
+
+    // Shutdown
+error:
+    if (inFile != NULL)
+        fclose(inFile);
+
+    return ret;
 }