Mercurial > hg > sidinfo
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; }