Mercurial > hg > sidinfo
view sidinfo.c @ 19:16cfbdf20eaf
Handle multiple files.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 25 Sep 2014 02:49:24 +0300 |
parents | c30fe2b4251f |
children | 6058339ffe0e |
line wrap: on
line source
/* * 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_FILENAME = 0x10000000, PSF_ALL = 0xffffffff, }; typedef struct { 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_FILENAME , "Filename" , NULL }, { PSF_ALL , "All" , NULL }, }; const int noptPSFlags = sizeof(optPSFlags) / sizeof(optPSFlags[0]); /* Options */ BOOL optParsable = FALSE, optNoNamePrefix = FALSE, optHexadecimal = FALSE; uint32_t optFields = PSF_ALL; int optNFiles = 0; /* 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 }, { 5, 'n', "noprefix", "Output without field name prefix", 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; } } printf("Example: %s -x -p -f hash,copyright somesidfile.sid\n", th_prog_name); } 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; case 5: optNoNamePrefix = TRUE; break; default: THERR("Unknown option '%s'.\n", currArg); 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 playAddress, // Play one frame nSongs, // Number of subsongs startSong; // Default starting song uint32_t speed; // Speed 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 uint8_t startPage, pageLength; uint16_t reserved; // Extra data BOOL isRSID; size_t dataSize; // Total size of data - header th_md5hash_t hash; // Songlength database hash } PSIDHeader; 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; int index, ret = -1; size_t read; BOOL first; memset(psid, 0, sizeof(*psid)); if ((fileData = (uint8_t *) th_malloc(PSID_BUFFER_SIZE)) == NULL) { THERR("Error allocating temporary data buffer of %d bytes.\n", PSID_BUFFER_SIZE); goto error; } // Read PSID header in 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) || !th_fread_be16(inFile, &psid->initAddress) || !th_fread_be16(inFile, &psid->playAddress) || !th_fread_be16(inFile, &psid->nSongs) || !th_fread_be16(inFile, &psid->startSong) || !th_fread_be32(inFile, &psid->speed)) { THERR("Could not read PSID/RSID header.\n"); 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) { THERR("Not a supported PSID or RSID file.\n"); goto error; } psid->isRSID = psid->magic[0] == 'R'; 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) { // Yes, we need to if (!th_fread_be16(inFile, &psid->flags) || !th_fread_byte(inFile, &psid->startPage) || !th_fread_byte(inFile, &psid->pageLength) || !th_fread_be16(inFile, &psid->reserved)) { THERR("Error reading PSID/RSID v2+ extra header data.\n"); goto error; } } // Initialize MD5-hash calculation th_md5_init(&state); // Process actual data psid->dataSize = 0; first = TRUE; do { read = fread(fileData, sizeof(uint8_t), PSID_BUFFER_SIZE, inFile); psid->dataSize += read; if (first && psid->loadAddress == 0) { if (read < 4) { THERR("Error reading song data, unexpectedly small file.\n"); goto error; } // Strip load address (2 first bytes) th_md5_append(&state, &fileData[2], read - 2); first = FALSE; } else if (read > 0) { // Append "as is" th_md5_append(&state, fileData, read); } } while (read > 0 && !feof(inFile)); // Append header data to hash siAppendHash16(&state, psid->initAddress); siAppendHash16(&state, psid->playAddress); siAppendHash16(&state, psid->nSongs); // Append song speed data to hash tmp8 = psid->isRSID ? 60 : 0; for (index = 0; index < psid->nSongs && index < 32; index++) { if (psid->isRSID) tmp8 = 60; else tmp8 = (psid->speed & (1 << index)) ? 60 : 0; th_md5_append(&state, &tmp8, sizeof(tmp8)); } // Rest of songs (more than 32) for (index = 32; index < psid->nSongs; index++) th_md5_append(&state, &tmp8, sizeof(tmp8)); // PSIDv2NG specific if (psid->version >= 2) { // REFER TO SIDPLAY HEADERS FOR MORE INFORMATION tmp8 = (psid->flags >> 2) & 3; if (tmp8 == 2) th_md5_append(&state, &tmp8, sizeof(tmp8)); } // Calculate the hash th_md5_finish(&state, psid->hash); ret = 0; error: // Free buffer th_free(fileData); return ret; } static void siPrintLinePrefix(FILE *outFile, const BOOL parsable, const char *name) { if (!optNoNamePrefix) fprintf(outFile, parsable ? "%s=" : "%-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 char *filename, const PSIDHeader *psid) { PR(13, "%s", NULL, filename); 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"); } } BOOL argHandleFile(char *filename) { static PSIDHeader psid; static FILE *inFile = NULL; optNFiles++; if ((inFile = fopen(filename, "rb")) == NULL) { THERR("Could not open file '%s'.\n", filename); return FALSE; } // Read PSID data if (siReadPSIDFile(inFile, &psid) != 0) goto error; // Output siPrintPSIDInformation(stdout, optParsable, optHexadecimal, optFields, filename, &psid); // Shutdown error: if (inFile != NULL) fclose(inFile); return TRUE; } int main(int argc, char *argv[]) { // Initialize th_init("SIDInfo", "PSID/RSID information displayer", "0.2", NULL, NULL); th_verbosityLevel = 0; // Parse command line arguments if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, FALSE)) return -1; if (optNFiles == 0) { argShowHelp(); THERR("No filename(s) specified.\n"); } return 0; }