Mercurial > hg > sidinfo
view sidinfo.c @ 152:44e1c1555d87
Update documentation a bit.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 25 Oct 2017 23:40:21 +0300 |
parents | 0288c4d138f7 |
children | e3be2ae6120e |
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 { OFMT_FORMAT = 0x0002, }; enum { OTYPE_OTHER = 0, OTYPE_STR = 1, OTYPE_INT = 2, }; typedef struct { int cmd; char *str; char chr; int flags; char *fmt; } 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_STR }, { "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" "The -F fields can be further formatted via printf-style specifiers:\n" "-F \"@copyright:'%%-30s'@\"\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; } int siItemFormatStrPutInt(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const int value, const int f_radix, int f_flags, int f_width, int f_prec, const BOOL f_unsig, char *(f_alt)(const char *buf, const size_t blen, const int vret, const int flags)) { char buf[64]; int f_len = 0, vret; BOOL f_neg = FALSE; vret = th_vprintf_buf_int(buf, sizeof(buf), &f_len, value, f_radix, f_flags & TH_PF_UPCASE, f_unsig, &f_neg); if (vret == EOF) return 0; return th_vprintf_put_int_format(ctx, vputch, buf, f_flags, f_width, f_prec, f_len, vret, f_neg, f_unsig, f_alt); } int siItemFormatStrPrintDo(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const char *fmt, const PSFOption *opt, const char *d_str, const int d_int) { int ret = 0; while (*fmt) { if (*fmt != '%') { if ((ret = vputch(ctx, *fmt)) == EOF) goto out; } else { int f_width = -1, f_prec = -1, f_flags = 0; BOOL end = FALSE; fmt++; // Check for flags while (!end) { switch (*fmt) { case '#': f_flags |= TH_PF_ALT; break; case '+': f_flags |= TH_PF_SIGN; break; case '0': f_flags |= TH_PF_ZERO; break; case '-': f_flags |= TH_PF_LEFT; break; case ' ': f_flags |= TH_PF_SPACE; break; case '\'': f_flags |= TH_PF_GROUP; break; default: end = TRUE; break; } if (!end) fmt++; } // Get field width if (*fmt == '*') { return -101; } else { f_width = 0; while (th_isdigit(*fmt)) f_width = f_width * 10 + (*fmt++ - '0'); } // Check for field precision if (*fmt == '.') { fmt++; if (*fmt == '*') { return -102; } else { // If no digit after '.', precision is to be 0 f_prec = 0; while (th_isdigit(*fmt)) f_prec = f_prec * 10 + (*fmt++ - '0'); } } // Check for length modifiers (only some are supported currently) switch (*fmt) { case 0: return -104; case 'o': if (opt->type != OTYPE_INT) return -120; if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 8, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_oct)) == EOF) goto out; break; case 'u': case 'i': case 'd': if (opt->type != OTYPE_INT) return -120; if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 10, f_flags, f_width, f_prec, *fmt == 'u', NULL)) == EOF) goto out; break; case 'x': case 'X': if (opt->type != OTYPE_INT) return -120; if (*fmt == 'X') f_flags |= TH_PF_UPCASE; if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 16, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_hex)) == EOF) goto out; break; case 's': if (opt->type != OTYPE_STR) return -121; if ((ret = th_vprintf_put_str(ctx, vputch, d_str, f_flags, f_width, f_prec)) == EOF) goto out; break; //case '%': default: if ((ret = vputch(ctx, *fmt)) == EOF) goto out; break; } } fmt++; } out: return ret == EOF ? ret : ctx->ipos; } static int siItemFormatStrPutCH(th_vprintf_ctx *ctx, const char ch) { if (ctx->pos + 1 >= ctx->size) { ctx->size += 64; if ((ctx->buf = th_realloc(ctx->buf, ctx->size)) == NULL) return EOF; } ctx->buf[ctx->pos] = ch; ctx->pos++; ctx->ipos++; return ch; } char * siItemFormatStrPrint(const char *fmt, const PSFOption *opt, const char *d_str, const int d_int) { th_vprintf_ctx ctx; ctx.size = 128; ctx.buf = th_malloc(ctx.size); ctx.pos = 0; ctx.ipos = 0; if (ctx.buf == NULL) return NULL; if (siItemFormatStrPrintDo(&ctx, siItemFormatStrPutCH, fmt, opt, d_str, d_int) <= 0) goto err; if (siItemFormatStrPutCH(&ctx, 0) < 0) goto err; return ctx.buf; err: th_free(ctx.buf); return NULL; } static int siItemFormatStrPutCHNone(th_vprintf_ctx *ctx, const char ch) { ctx->pos++; ctx->ipos++; return ch; } BOOL siItemFormatStrCheck(const char *fmt, const PSFOption *opt) { th_vprintf_ctx ctx; memset(&ctx, 0, sizeof(ctx)); return siItemFormatStrPrintDo(&ctx, siItemFormatStrPutCHNone, fmt, opt, NULL, 0) >= 0; } // // 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 == 0) mode = -1; fmt++; break; } if (fmt - start == 0) { item.cmd = -2; item.str = NULL; item.chr = '@'; if (!siStackAddItem(stack, &item)) return FALSE; } else { char *fopt = NULL, *pfield, *field = th_strndup_trim(start, fmt - start, TH_TRIM_BOTH); if ((pfield = strchr(field, ':')) != NULL) { *pfield = 0; fopt = th_strdup_trim(pfield + 1, TH_TRIM_BOTH); } int ret = argMatchPSFieldError(field); if (ret >= 0) { item.cmd = ret; item.flags = 0; item.fmt = NULL; item.str = NULL; if (fopt != NULL) { if (siItemFormatStrCheck(fopt, &optPSOptions[item.cmd])) { item.flags |= OFMT_FORMAT; item.fmt = th_strdup(fopt); } else { THERR("Invalid field format specifier '%s' in '%s'.\n", fopt, field); rval = FALSE; } } if (!siStackAddItem(stack, &item)) rval = FALSE; } else rval = FALSE; th_free(fopt); th_free(field); } mode = 0; 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 PSFStackItem *item, const char *d_str, const int d_int) { const PSFOption *opt = &optPSOptions[item->cmd]; char *fmt, *str = NULL; switch (opt->type) { case OTYPE_INT: if (item->flags & OFMT_FORMAT) fmt = item->fmt; else fmt = optHexadecimal ? "$%04x" : "%d"; str = siItemFormatStrPrint(fmt, opt, d_str, d_int); break; case OTYPE_STR: if (item->flags & OFMT_FORMAT) fmt = item->fmt; else fmt = "%s"; str = siItemFormatStrPrint(fmt, opt, d_str, d_int); break; } siPrintFieldPrefix(outFile, opt); if (str != NULL) fputs(str, outFile); siPrintFieldSeparator(outFile); th_free(str); *shown = TRUE; } #define PR(d_str, d_int) siPrintPSIDInfoLine(outFile, shown, item, d_str, d_int) static void siPrintPSIDInformationField(FILE *outFile, const char *filename, const PSIDHeader *psid, BOOL *shown, const PSFStackItem *item) { const PSFOption *opt = &optPSOptions[item->cmd]; char tmp[128]; switch (item->cmd) { case 0: PR(filename, -1); break; case 1: PR(psid->magic, -1); break; case 2: snprintf(tmp, sizeof(tmp), "%d.%d", (psid->version & 0xff), (psid->version >> 8)); PR(tmp, -1); break; case 3: PR((psid->flags & PSF_PLAYER_TYPE) ? "Compute! SIDPlayer MUS" : "Normal built-in", -1); break; case 4: if (psid->version >= 2) PR((psid->flags & PSF_PLAYSID_TUNE) ? (psid->isRSID ? "C64 BASIC" : "PlaySID") : "C64 compatible", -1); break; case 5: if (psid->version >= 2) PR(si_get_sid_clock_str((psid->flags >> 2) & PSF_CLOCK_MASK), -1); break; case 6: if (psid->version >= 2) PR(si_get_sid_model_str((psid->flags >> 4) & PSF_MODEL_MASK), -1); break; case 7: PR(NULL, psid->dataOffset); break; case 8: PR(NULL, psid->dataSize); break; case 9: PR(NULL, psid->loadAddress); break; case 10: PR(NULL, psid->initAddress); break; case 11: PR(NULL, psid->playAddress); break; case 12: PR(NULL, psid->nSongs); break; case 13: PR(NULL, 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(si_get_sid_model_str(flags), -1); } 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(si_get_sid_model_str(flags), -1); } break; case 16: if (psid->version >= 3) PR(NULL, 0xD000 | (psid->sid2Addr << 4)); break; case 17: if (psid->version >= 4) PR(NULL, 0xD000 | (psid->sid3Addr << 4)); break; case 18: PR(psid->sidName, -1); break; case 19: PR(psid->sidAuthor, -1); break; case 20: PR(psid->sidCopyright, -1); break; case 21: { size_t i, k; for (i = k = 0; i < TH_MD5HASH_LENGTH && k < sizeof(tmp) - 1; i++, k += 2) sprintf(&tmp[k], "%02x", psid->hash[i]); PR(tmp, -1); } 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; 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 (int 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.7.0", 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; }