Mercurial > hg > sidinfo
view sidinfo.c @ 89:d9cb7c635e7b
Implement initial SLDB support.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 12 Feb 2016 01:17:52 +0200 |
parents | 1e89b757f8a1 |
children | 2ab9ab4b59eb |
line wrap: on
line source
/* * SIDInfo - PSID/RSID information displayer * Written by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2014-2016 Tecnic Software productions (TNSP) */ #include "th_args.h" #include "th_string.h" #include "th_file.h" #include "sidlib.h" // Some constants #define SET_SLDB_FILENAME "Songlengths.txt" // Flags for various information fields enum { SIF_NONE = 0, SIF_TYPE = 0x00000001, SIF_VERSION = 0x00000002, SIF_DATA_OFFS = 0x00000004, SIF_LOAD_ADDR = 0x00000008, SIF_INIT_ADDR = 0x00000010, SIF_PLAY_ADDR = 0x00000020, SIF_SONGS = 0x00000040, SIF_START_SONG = 0x00000080, SIF_SPEEDS = 0x00000100, SIF_SID_NAME = 0x00000200, SIF_SID_AUTHOR = 0x00000400, SIF_SID_COPYRIGHT = 0x00000800, SIF_PLAYER_TYPE = 0x00001000, SIF_PLAYSID_TUNE = 0x00002000, SIF_VIDEO_CLOCK = 0x00004000, SIF_SID_MODEL = 0x00008000, SIF_SONGLENGTHS = 0x00010000, SIF_DATA_SIZE = 0x00100000, SIF_HASH = 0x00200000, SIF_FILENAME = 0x01000000, SIF_ALL = 0x0fffffff, }; typedef struct { int cmd; char *str; char chr; } PSFStackItem; typedef struct { int nitems, nallocated; PSFStackItem *items; } PSFStack; typedef struct { uint32_t flag; char *name; char *lname; } PSFOption; static const PSFOption optPSFlags[] = { { SIF_FILENAME , "Filename" , NULL }, { SIF_TYPE , "Type" , NULL }, { SIF_VERSION , "Version" , NULL }, { SIF_PLAYER_TYPE , "PlayerType" , "Player type" }, { SIF_PLAYSID_TUNE , "PlayerCompat", "Player compatibility" }, { SIF_VIDEO_CLOCK , "VideoClock" , "Video clock speed" }, { SIF_SID_MODEL , "SIDModel" , "SID model" }, { SIF_DATA_OFFS , "DataOffs" , "Data offset" }, { SIF_DATA_SIZE , "DataSize" , "Data size" }, { SIF_LOAD_ADDR , "LoadAddr" , "Load address" }, { SIF_INIT_ADDR , "InitAddr" , "Init address" }, { SIF_PLAY_ADDR , "PlayAddr" , "Play address" }, { SIF_SONGS , "Songs" , "Songs" }, { SIF_START_SONG , "StartSong" , "Start song" }, { SIF_SID_NAME , "Name" , NULL }, { SIF_SID_AUTHOR , "Author" , NULL }, { SIF_SID_COPYRIGHT , "Copyright" , NULL }, { SIF_HASH , "Hash" , NULL }, { SIF_SONGLENGTHS , "Songlengths", "Song lengths" }, { SIF_ALL , "All" , NULL }, }; static const int noptPSFlags = sizeof(optPSFlags) / sizeof(optPSFlags[0]); // Option variables char *setHVSCPath = NULL, *setSLDBPath = NULL; BOOL optParsable = FALSE, optNoNamePrefix = FALSE, optHexadecimal = FALSE, optOneLine = FALSE; char *optFieldSep = NULL; uint32_t optFields = SIF_ALL; int optNFiles = 0; PSFStack optFormat; SIDLibSLDB *sidSLDB = NULL; // Define option arguments static const th_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 }, { 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 < noptPSFlags; index++) { const PSFOption *opt = &optPSFlags[index]; len += strlen(opt->name) + 3; if (len >= 72) { printf("\n"); len = 0; } printf("%s%s", opt->name, (index < noptPSFlags - 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 < noptPSFlags; index++) { const PSFOption *opt = &optPSFlags[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 argParsePSField(char *opt, char *end, uint32_t *fields) { // Trim whitespace if (end != NULL) while (end > opt && *end && th_isspace(*end)) end--; while (*opt && th_isspace(*opt)) opt++; // Match field name char *field = (end != NULL) ? th_strndup(opt, end - opt) : th_strdup(opt); int found = argMatchPSFieldError(field); if (found >= 0) *fields |= optPSFlags[found].flag; th_free(field); return FALSE; } 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)); } } // // Parse a format string into a PSFStack structure // BOOL argParsePSFormatStr(PSFStack *stack, const char *fmt) { PSFStackItem item; const char *start = NULL; int mode = 0; 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(start, fmt - start); int ret = argMatchPSFieldError(field); if (ret >= 0) { item.cmd = ret; item.str = NULL; if (!siStackAddItem(stack, &item)) { th_free(field); return FALSE; } } th_free(field); } 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 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 = SIF_NONE; while (*start) { char *end = strchr(start, ','); if (!argParsePSField(start, end, &optFields)) return FALSE; if (!end) break; start = end + 1; } } break; case 4: optHexadecimal = TRUE; break; case 5: optNoNamePrefix = TRUE; break; case 6: optOneLine = TRUE; optFieldSep = optArg; break; case 7: 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 && !optFormat.nitems) fprintf(outFile, optParsable ? "%s=" : "%-20s : ", name); } static void siPrintFieldSeparator(FILE *outFile) { if (!optFormat.nitems) fputs(optOneLine ? optFieldSep : "\n", outFile); } static void siPrintPSIDInfoLine(FILE *outFile, BOOL *shown, const int xindex, const char *xfmt, const char *xaltfmt, ...) { const PSFOption *opt = &optPSFlags[xindex]; if (optFormat.nitems || (optFields & opt->flag)) { 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, xindex, xfmt, xaltfmt, __VA_ARGS__ ) static void siPrintPSIDInformationField(FILE *outFile, const char *filename, const PSIDHeader *psid, BOOL *shown, const int xindex) { switch (xindex) { 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)); break; case 6: if (psid->version >= 2) PR("%s", NULL, si_get_sid_model_str(psid->flags)); 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: PR("%s", NULL, psid->sidName); break; case 15: PR("%s", NULL, psid->sidAuthor); break; case 16: PR("%s", NULL, psid->sidCopyright); break; case 17: { const PSFOption *opt = &optPSFlags[xindex]; if (optFormat.nitems || (optFields & opt->flag)) { siPrintFieldPrefix(outFile, opt); th_md5_print(outFile, psid->hash); siPrintFieldSeparator(outFile); } } break; case 18: { const PSFOption *opt = &optPSFlags[xindex]; if (optFormat.nitems || (optFields & opt->flag)) { siPrintFieldPrefix(outFile, opt); if (psid->lengths != NULL) { int i; 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) { static PSIDHeader psid; 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); return TRUE; } // 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 if (optFormat.nitems) { 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->cmd); break; } } } else { for (index = 0; index < noptPSFlags - 1; index++) siPrintPSIDInformationField(outFile, filename, &psid, &shown, index); if (shown && optOneLine) fprintf(outFile, "\n"); } // Shutdown error: th_io_free(inFile); return TRUE; } int main(int argc, char *argv[]) { // Initialize th_init("SIDInfo", "PSID/RSID information displayer", "0.6.1", NULL, NULL); th_verbosityLevel = 0; memset(&optFormat, 0, sizeof(optFormat)); // 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 (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; th_ioctx *ctx = th_io_new(&th_stdio_io_ops); if (ctx == NULL || th_io_open(ctx, setSLDBPath, "r") != THERR_OK) { THERR("Ululu!\n"); goto out; } if ((sidSLDB = si_sldb_new()) == NULL || si_sldb_read(ctx, sidSLDB) != THERR_OK) { THERR("Error parsing SLDB.\n"); goto out; } if ((ret = si_sldb_build_index(sidSLDB)) != THERR_OK) { THERR("Error building SLDB index: %s.\n", th_error_str(ret)); goto out; } } // Process files if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_ONLY_OTHER)) goto out; if (optNFiles == 0) { argShowHelp(); THERR("No filename(s) specified.\n"); } out: th_free(setHVSCPath); th_free(setSLDBPath); th_free(setSTILPath); si_sldb_free(sidSLDB); return 0; }