Mercurial > hg > sidinfo
view sidinfo.c @ 142:da29f147afcb
Better check for number of songlengths.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 05 Jun 2017 09:31:57 +0300 |
parents | 71725be55ed1 |
children | e481947fd051 |
line wrap: on
line source
/* * SIDInfo - PSID/RSID information displayer * Written by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2014-2017 Tecnic Software productions (TNSP) */ #include "th_args.h" #include "th_string.h" #include "th_file.h" #include "sidlib.h" #ifdef HAVE_ICONV #include <iconv.h> #endif // Some constants #define SET_DEF_CHARSET "utf-8" #define SET_SLDB_FILENAME "Songlengths.txt" enum { OTYPE_OTHER = 0, OTYPE_STR = 1, OTYPE_INT = 2, }; typedef struct { int cmd; char *str; char chr; int flags; } PSFStackItem; typedef struct { int nitems, nallocated; PSFStackItem *items; } PSFStack; typedef struct { char *name; char *lname; int type; } PSFOption; static const PSFOption optPSOptions[] = { { "Filename" , NULL , OTYPE_STR }, { "Type" , NULL , OTYPE_STR }, { "Version" , NULL , OTYPE_STR }, { "PlayerType" , "Player type" , OTYPE_STR }, { "PlayerCompat" , "Player compatibility" , OTYPE_STR }, { "VideoClock" , "Video clock speed" , OTYPE_STR }, { "SIDModel" , "SID model" , OTYPE_STR }, { "DataOffs" , "Data offset" , OTYPE_INT }, { "DataSize" , "Data size" , OTYPE_INT }, { "LoadAddr" , "Load address" , OTYPE_INT }, { "InitAddr" , "Init address" , OTYPE_INT }, { "PlayAddr" , "Play address" , OTYPE_INT }, { "Songs" , "Songs" , OTYPE_INT }, { "StartSong" , "Start song" , OTYPE_INT }, { "SID2Model" , "2nd SID model" , OTYPE_INT }, { "SID3Model" , "3rd SID model" , OTYPE_INT }, { "SID2Addr" , "2nd SID address" , OTYPE_INT }, { "SID3Addr" , "3rd SID address" , OTYPE_INT }, { "Name" , NULL , OTYPE_STR }, { "Author" , NULL , OTYPE_STR }, { "Copyright" , NULL , OTYPE_STR }, { "Hash" , NULL , OTYPE_OTHER }, { "Songlengths" , "Song lengths" , OTYPE_OTHER }, }; static const int noptPSOptions = sizeof(optPSOptions) / sizeof(optPSOptions[0]); // Option variables char *setHVSCPath = NULL, *setSLDBPath = NULL; BOOL optParsable = FALSE, optNoNamePrefix = FALSE, optHexadecimal = FALSE, optOneLine = FALSE, optFieldOutput = TRUE; char *optFieldSep = NULL; int optNFiles = 0; PSFStack optFormat; SIDLibSLDB *sidSLDB = NULL; #ifdef HAVE_ICONV BOOL setUseChConv; iconv_t setChConv; #endif // Define option arguments static const th_optarg 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 }, { 6, 'l', "line", "Output in one line format, -l <field separator>", OPT_ARGREQ }, { 3, 'f', "fields", "Show only specified field(s)", OPT_ARGREQ }, { 4, 'x', "hex", "Use hexadecimal values", OPT_NONE }, { 7, 'F', "format", "Use given format string (see below)", OPT_ARGREQ }, { 8, 'H', "hvsc", "Specify path to HVSC documents directory", OPT_ARGREQ }, { 9, 'S', "sldb", "Specify Songlengths.txt file (use -H if possible)", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp(void) { int index, len; th_print_banner(stdout, th_prog_name, "[options] <sid filename> [sid filename #2 ..]"); th_args_help(stdout, optList, optListN, 0); printf( "\n" "Available fields:\n"); for (len = index = 0; index < noptPSOptions; index++) { const PSFOption *opt = &optPSOptions[index]; len += strlen(opt->name) + 3; if (len >= 72) { printf("\n"); len = 0; } printf("%s%s", opt->name, (index < noptPSOptions - 1) ? ", " : "\n\n"); } printf( "Example: %s -x -p -f hash,copyright somesidfile.sid\n" "\n" "Format strings for '-F' option are composed of @fields@ that\n" "are expanded to their value. Also, escape sequences \\r, \\n and \\t\n" "can be used: -F \"hash=@hash@\\ncopy=@copyright@\\n\"\n" "\n" "When specifying HVSC path, it is preferable to use -H/--hvsc option,\n" "as STIL.txt and Songlengths.txt will be automatically used from there.\n" , th_prog_name); } int argMatchPSField(const char *field) { int index, found = -1; for (index = 0; index < noptPSOptions; index++) { const PSFOption *opt = &optPSOptions[index]; if (th_strcasecmp(opt->name, field) == 0) { if (found >= 0) return -2; found = index; } } return found; } int argMatchPSFieldError(const char *field) { int found = argMatchPSField(field); switch (found) { case -1: THERR("No such field '%s'.\n", field); break; case -2: THERR("Field '%s' is ambiguous.\n", field); break; } return found; } BOOL siStackAddItem(PSFStack *stack, const PSFStackItem *item) { if (stack->items == NULL || stack->nitems + 1 >= stack->nallocated) { stack->nallocated += 16; if ((stack->items = th_realloc(stack->items, stack->nallocated * sizeof(PSFStackItem))) == NULL) { THERR("Could not allocate memory for format item stack.\n"); return FALSE; } } memcpy(stack->items + stack->nitems, item, sizeof(PSFStackItem)); stack->nitems++; return TRUE; } void siClearStack(PSFStack *stack) { if (stack != NULL) { if (stack->nitems > 0 && stack->items != NULL) { int n; for (n = 0; n < stack->nitems; n++) { if (stack->items[n].cmd == -1) th_free(stack->items[n].str); } th_free(stack->items); } memset(stack, 0, sizeof(PSFStack)); } } BOOL argParsePSFields(PSFStack *stack, const char *fmt) { const char *start = fmt; siClearStack(stack); while (*start) { PSFStackItem item; const char *end = strchr(start, ','); char *field = (end != NULL) ? th_strndup_trim(start, end - start, TH_TRIM_BOTH) : th_strdup_trim(start, TH_TRIM_BOTH); if (field != NULL) { int found = argMatchPSFieldError(field); th_free(field); if (found < 0) return FALSE; item.cmd = found; item.str = NULL; if (!siStackAddItem(stack, &item)) return FALSE; } if (!end) break; start = end + 1; } return TRUE; } // // Parse a format string into a PSFStack structure // BOOL argParsePSFormatStr(PSFStack *stack, const char *fmt) { PSFStackItem item; const char *start = NULL; int mode = 0; BOOL rval = TRUE; siClearStack(stack); while (mode != -1) switch (mode) { case 0: if (*fmt == '@') { start = fmt + 1; mode = 1; } else { start = fmt; mode = 2; } fmt++; break; case 1: if (*fmt == '@') { if (fmt - start == 0) { item.cmd = -2; item.str = NULL; item.chr = '@'; if (!siStackAddItem(stack, &item)) return FALSE; } else { char *field = th_strndup_trim(start, fmt - start, TH_TRIM_BOTH); int ret = argMatchPSFieldError(field); th_free(field); if (ret >= 0) { item.cmd = ret; item.str = NULL; if (!siStackAddItem(stack, &item)) return FALSE; } else rval = FALSE; } mode = 0; } else if (*fmt == 0) mode = -1; fmt++; break; case 2: if (*fmt == 0 || *fmt == '@') { item.cmd = -1; item.str = th_strndup(start, fmt - start); if (!siStackAddItem(stack, &item)) return FALSE; mode = (*fmt == 0) ? -1 : 0; } else fmt++; break; } return rval; } 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: if (!argParsePSFields(&optFormat, optArg)) return FALSE; break; case 4: optHexadecimal = TRUE; break; case 5: optNoNamePrefix = TRUE; break; case 6: optOneLine = TRUE; optFieldSep = optArg; break; case 7: optFieldOutput = FALSE; if (!argParsePSFormatStr(&optFormat, optArg)) return FALSE; break; case 8: setHVSCPath = th_strdup(optArg); break; case 9: setSLDBPath = th_strdup(optArg); break; default: THERR("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } static void siPrintStrEscapes(FILE *outFile, const char *str) { while (*str) { if (*str == '\\') switch (*(++str)) { case 'n': fputc('\n', outFile); break; case 'r': fputc('\r', outFile); break; case 't': fputc('\r', outFile); break; case '\\': fputc('\\', outFile); break; default: fputc(*str, outFile); break; } else fputc(*str, outFile); str++; } } static void siPrintFieldPrefix(FILE *outFile, const PSFOption *opt) { const char *name = (optParsable || opt->lname == NULL) ? opt->name : opt->lname; if (!optNoNamePrefix && optFieldOutput) fprintf(outFile, optParsable ? "%s=" : "%-20s : ", name); } static void siPrintFieldSeparator(FILE *outFile) { if (optFieldOutput) fputs(optOneLine ? optFieldSep : "\n", outFile); } static void siPrintPSIDInfoLine(FILE *outFile, BOOL *shown, const PSFOption *opt, const char *xfmt, const char *xaltfmt, ...) { va_list ap; const char *fmt = optHexadecimal ? (xaltfmt != NULL ? xaltfmt : xfmt) : xfmt; siPrintFieldPrefix(outFile, opt); va_start(ap, xaltfmt); vfprintf(outFile, fmt, ap); va_end(ap); siPrintFieldSeparator(outFile); *shown = TRUE; } #define PR(xfmt, xaltfmt, ...) siPrintPSIDInfoLine(outFile, shown, opt, xfmt, xaltfmt, __VA_ARGS__ ) static void siPrintPSIDInformationField(FILE *outFile, const char *filename, const PSIDHeader *psid, BOOL *shown, const PSFStackItem *item) { const PSFOption *opt = &optPSOptions[item->cmd]; switch (item->cmd) { case 0: PR("%s", NULL, filename); break; case 1: PR("%s", NULL, psid->magic); break; case 2: PR("%d.%d", NULL, (psid->version & 0xff), (psid->version >> 8)); break; case 3: PR("%s", NULL, (psid->flags & PSF_PLAYER_TYPE) ? "Compute! SIDPlayer MUS" : "Normal built-in"); break; case 4: if (psid->version >= 2) PR("%s", NULL, (psid->flags & PSF_PLAYSID_TUNE) ? "PlaySID" : "C64 compatible"); break; case 5: if (psid->version >= 2) PR("%s", NULL, si_get_sid_clock_str((psid->flags >> 2) & PSF_CLOCK_MASK)); break; case 6: if (psid->version >= 2) PR("%s", NULL, si_get_sid_model_str((psid->flags >> 4) & PSF_MODEL_MASK)); break; case 7: PR("%d", "$%08x", psid->dataOffset); break; case 8: PR("%d", "$%08x", psid->dataSize); break; case 9: PR("%d", "$%04x", psid->loadAddress); break; case 10: PR("%d", "$%04x", psid->initAddress); break; case 11: PR("%d", "$%04x", psid->playAddress); break; case 12: PR("%d", "$%04x", psid->nSongs); break; case 13: PR("%d", "$%04x", psid->startSong); break; case 14: if (psid->version >= 3) { int flags = (psid->flags >> 6) & PSF_MODEL_MASK; if (flags == PSF_MODEL_UNKNOWN) flags = (psid->flags >> 4) & PSF_MODEL_MASK; PR("%s", NULL, si_get_sid_model_str(flags)); } break; case 15: if (psid->version >= 4) { int flags = (psid->flags >> 8) & PSF_MODEL_MASK; if (flags == PSF_MODEL_UNKNOWN) flags = (psid->flags >> 4) & PSF_MODEL_MASK; PR("%s", NULL, si_get_sid_model_str(flags)); } break; case 16: if (psid->version >= 3) PR("%d", "$%04x", 0xD000 | (psid->sid2Addr << 4)); break; case 17: if (psid->version >= 4) PR("%d", "$%04x", 0xD000 | (psid->sid3Addr << 4)); break; case 18: PR("%s", NULL, psid->sidName); break; case 19: PR("%s", NULL, psid->sidAuthor); break; case 20: PR("%s", NULL, psid->sidCopyright); break; case 21: { siPrintFieldPrefix(outFile, opt); th_md5_print(outFile, psid->hash); siPrintFieldSeparator(outFile); } break; case 22: if (psid->lengths != NULL && psid->lengths->nlengths > 0) { int i; siPrintFieldPrefix(outFile, opt); for (i = 0; i < psid->lengths->nlengths; i++) { int len = psid->lengths->lengths[i]; fprintf(outFile, "%d:%d%s", len / 60, len % 60, (i < psid->lengths->nlengths - 1) ? " " : ""); } siPrintFieldSeparator(outFile); } break; } } BOOL argHandleFile(char *filename) { PSIDHeader *psid = NULL; th_ioctx *inFile = NULL; FILE *outFile; int index; BOOL shown = FALSE; optNFiles++; outFile = stdout; if ((inFile = th_io_fopen(&th_stdio_io_ops, filename, "rb")) == NULL) { THERR("Could not open file '%s'.\n", filename); goto error; } // Read PSID data if (si_read_sid_file(inFile, &psid) != 0) { THERR("Error reading %s\n", filename); goto error; } // Get songlength information, if any if (sidSLDB != NULL) psid->lengths = si_sldb_get_by_hash(sidSLDB, psid->hash); // Output for (index = 0; index < optFormat.nitems; index++) { PSFStackItem *item = &optFormat.items[index]; switch (item->cmd) { case -1: siPrintStrEscapes(outFile, item->str); break; case -2: fputc(item->chr, outFile); break; default: siPrintPSIDInformationField(outFile, filename, psid, &shown, item); break; } } if (optFieldOutput) { if (shown && optOneLine) fprintf(outFile, "\n"); } // Shutdown error: si_free_sid_file(psid); th_io_free(inFile); return TRUE; } int main(int argc, char *argv[]) { // Initialize th_init("SIDInfo", "PSID/RSID information displayer", "0.6.2", NULL, NULL); th_verbosityLevel = 0; memset(&optFormat, 0, sizeof(optFormat)); // Initialize iconv, check if we have language/charset #ifdef HAVE_ICONV char *setLang = th_strdup(getenv("LANG")); if (setLang != NULL) { char *ptr = strchr(setLang, '.'); if (ptr != NULL) strcpy(setLang, ptr + 1); } setChConv = iconv_open(setLang != NULL ? setLang : SET_DEF_CHARSET, "iso88591"); setUseChConv = setChConv != (iconv_t) -1; th_free(setLang); #endif // Parse command line arguments if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_ONLY_OPTS)) return -1; if (optOneLine) { optParsable = FALSE; optNoNamePrefix = TRUE; } if (optFieldOutput && !optFormat.nitems) { PSFStackItem item; int i; memset(&item, 0, sizeof(item)); siClearStack(&optFormat); for (i = 0; i < noptPSOptions; i++) { item.cmd = i; siStackAddItem(&optFormat, &item); } } // Check paths if (setHVSCPath != NULL) { if (setSLDBPath == NULL) setSLDBPath = th_strdup_printf("%s%c%s", setHVSCPath, TH_DIR_SEPARATOR, SET_SLDB_FILENAME); } if (setSLDBPath != NULL) { // Initialize SLDB int ret = THERR_OK; th_ioctx *inFile = NULL; if ((inFile = th_io_fopen(&th_stdio_io_ops, setSLDBPath, "r")) == NULL) { THERR("Could not open SLDB '%s'.\n", setSLDBPath); goto err; } THMSG(1, "Reading SLDB.\n"); if ((sidSLDB = si_sldb_new()) == NULL) { THERR("Could not allocate SLDB structure!\n"); goto err; } if ((ret = si_sldb_read(inFile, sidSLDB)) != THERR_OK) { THERR("Error parsing SLDB: %d, %s\n", ret, th_error_str(ret)); goto err; } th_io_close(inFile); if ((ret = si_sldb_build_index(sidSLDB)) != THERR_OK) { THERR("Error building SLDB index: %d, %s.\n", ret, th_error_str(ret)); goto err; } err: th_io_close(inFile); } // Process files if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_ONLY_OTHER)) goto out; if (optNFiles == 0) { THERR("No filename(s) specified. Try --help.\n"); } out: #ifdef HAVE_ICONV if (setUseChConv) iconv_close(setChConv); #endif th_free(setHVSCPath); th_free(setSLDBPath); si_sldb_free(sidSLDB); return 0; }