Mercurial > hg > sidinfo
view sidinfo.c @ 341:fe061ead51cc
Perform character set conversion after item formatting step instead of before it. This should remedy the potential issue of formatting not taking UTF8 multibyte into account, as our formatting unfortunately does not support multibyte encoding. This way the data should stay in ISO-8859-1 format just up to outputting it.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 13 Jan 2020 16:29:47 +0200 |
parents | 6f8c431a3040 |
children | 020c4f21e861 |
line wrap: on
line source
/* * SIDInfo - PSID/RSID information displayer * Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2014-2020 Tecnic Software productions (TNSP) */ #include "th_args.h" #include "th_string.h" #include "th_file.h" #include "th_datastruct.h" #include "sidlib.h" #include "sidutil.h" #include <sys/types.h> #include <dirent.h> // // Some constants // enum { OFMT_QUOTED = 0x0001, 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; char *dfmt; } PSFOption; #define SET_FMT_HEX_ADDR "%d ($%04x)" static const PSFOption optPSOptions[] = { { "Filename" , NULL , OTYPE_STR , NULL }, { "Type" , NULL , OTYPE_STR , NULL }, { "Version" , NULL , OTYPE_STR , NULL }, { "PlayerType" , "Player type" , OTYPE_STR , NULL }, { "PlayerCompat" , "Player compatibility" , OTYPE_STR , NULL }, { "VideoClock" , "Video clock speed" , OTYPE_STR , NULL }, { "SIDModel" , "SID model" , OTYPE_STR , NULL }, { "DataOffs" , "Data offset" , OTYPE_INT , SET_FMT_HEX_ADDR }, { "DataSize" , "Data size" , OTYPE_INT , SET_FMT_HEX_ADDR }, { "LoadAddr" , "Load address" , OTYPE_INT , SET_FMT_HEX_ADDR }, { "InitAddr" , "Init address" , OTYPE_INT , SET_FMT_HEX_ADDR }, { "PlayAddr" , "Play address" , OTYPE_INT , SET_FMT_HEX_ADDR }, { "Songs" , "Songs" , OTYPE_INT , "%d" }, { "StartSong" , "Start song" , OTYPE_INT , "%d" }, { "SID2Model" , "2nd SID model" , OTYPE_STR , NULL }, { "SID3Model" , "3rd SID model" , OTYPE_STR , NULL }, { "SID2Addr" , "2nd SID address" , OTYPE_INT , SET_FMT_HEX_ADDR }, { "SID3Addr" , "3rd SID address" , OTYPE_INT , SET_FMT_HEX_ADDR }, { "Name" , NULL , OTYPE_STR , NULL }, { "Author" , NULL , OTYPE_STR , NULL }, { "Copyright" , NULL , OTYPE_STR , NULL }, { "Hash" , NULL , OTYPE_STR , NULL }, { "Songlengths" , "Song lengths" , OTYPE_OTHER , NULL }, { "STIL" , "STIL information" , OTYPE_OTHER , NULL }, }; static const int noptPSOptions = sizeof(optPSOptions) / sizeof(optPSOptions[0]); // Option variables char *setHVSCPath = NULL, *setSLDBPath = NULL, *setSTILDBPath = NULL; BOOL setSLDBNewFormat = FALSE, optParsable = FALSE, optFieldNamePrefix = TRUE, optHexadecimal = FALSE, optFieldOutput = TRUE, optRecurseDirs = FALSE, optShowHelp = FALSE; char *optOneLineFieldSep = NULL, *optEscapeChars = NULL; int optNFiles = 0; PSFStack optFormat; SIDLibSLDB *sidSLDB = NULL; SIDLibSTILDB *sidSTILDB = NULL; SIDUtilChConvCtx setChConv; // Define option arguments static const th_optarg optList[] = { { 0, '?', "help" , NULL , "Show this help", OPT_NONE }, { 1, 0, "license" , NULL , "Print out this program's license agreement", OPT_NONE }, { 2, 'v', "verbose" , NULL , "Be more verbose", OPT_NONE }, { 3, 'p', "parsable" , NULL , "Output in script-parsable format", OPT_NONE }, { 4, 'x', "hex" , NULL , "Use hexadecimal values", OPT_NONE }, { 5, 'n', "noprefix" , NULL , "Output without field name prefix", OPT_NONE }, { 6, 'l', "line" , "sep" , "Output in one line format, -l <field sep str>", OPT_ARGREQ }, { 7, 'e', "escape" , "chars" , "Escape these characters in fields (see note)", OPT_ARGREQ }, { 8, 'f', "fields" , "fields" , "Show only specified field(s)", OPT_ARGREQ }, { 9, 'F', "format" , "fmt" , "Use given format string (see below)", OPT_ARGREQ }, { 10, 'H', "hvsc" , "path" , "Specify path to HVSC root directory", OPT_ARGREQ }, { 11, 'S', "sldb" , "file" , "Specify Songlengths.(txt|md5) file", OPT_ARGREQ }, { 12, 'T', "stildb" , "file" , "Specify STIL.txt file", OPT_ARGREQ }, { 13, 'R', "recurse" , NULL , "Recurse into sub-directories", OPT_NONE }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp(void) { int index, width; th_print_banner(stdout, th_prog_name, "[options] <sid file|path> [file|path #2 ..]"); th_args_help(stdout, optList, optListN, 0, 80 - 2); printf( "\n" "Available fields:\n"); for (width = index = 0; index < noptPSOptions; index++) { const PSFOption *opt = &optPSOptions[index]; int len = strlen(opt->name) + 2; width += len; if (width >= 80 - 2) { printf("\n"); width = len; } printf("%s%s", opt->name, (index < noptPSOptions - 1) ? ", " : "\n"); } printf( "\n" "Example: %s -x -p -f hash,copyright somesidfile.sid\n" "\n" "Format strings for '-F' option are composed of @fields@ that get expanded\n" "to their value. Also, escape sequences \\r, \\n and \\t can be used:\n" " -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" "NOTE: One line output (-l <field separator>) also sets escape characters\n" "(option -e <chars>), if escape characters have NOT been separately set.\n" "\n" "TIP: When specifying HVSC paths, it is preferable to use -H/--hvsc option,\n" "as STIL.txt and Songlengths.(txt|md5) will be automatically used from there.\n" "You can also set it via HVSC_BASE environment variable, see README.\n" "\n" "HVSC path : %s\n" "SLDB file : %s\n" "STIL file : %s\n" "\n", th_prog_name, setHVSCPath != NULL ? setHVSCPath : "[not set]", setSLDBPath != NULL ? setSLDBPath : "[not set]", setSTILDBPath != NULL ? setSTILDBPath : "[not set]" ); } 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) { for (int n = 0; n < stack->nitems; n++) { PSFStackItem *item = &stack->items[n]; th_free(item->str); th_free(item->fmt); } th_free(stack->items); } // Clear the stack data memset(stack, 0, sizeof(PSFStack)); } } BOOL argParsePSFields(PSFStack *stack, const char *fmt) { const char *start = fmt; siClearStack(stack); while (*start) { 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) { PSFStackItem item; int found = argMatchPSFieldError(field); th_free(field); if (found < 0) return FALSE; memset(&item, 0, sizeof(item)); item.cmd = found; item.fmt = th_strdup(optPSOptions[found].dfmt); if (!siStackAddItem(stack, &item)) return FALSE; } if (!end) break; start = end + 1; } return TRUE; } static 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, th_vprintf_altfmt_func f_alt) { 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); } static int siItemFormatStrPrintDo(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const char *fmt, const int otype, 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 (otype != 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 (otype != 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 (otype != 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 (otype != 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 int otype, const char *d_str, const int d_int) { th_vprintf_ctx ctx; ctx.size = 128; ctx.pos = 0; ctx.ipos = 0; if ((ctx.buf = th_malloc(ctx.size)) == NULL) return NULL; if (siItemFormatStrPrintDo(&ctx, siItemFormatStrPutCH, fmt, otype, 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; } static BOOL siItemFormatStrCheck(const char *fmt, const PSFOption *opt) { th_vprintf_ctx ctx; memset(&ctx, 0, sizeof(ctx)); return siItemFormatStrPrintDo(&ctx, siItemFormatStrPutCHNone, fmt, opt->type, NULL, 0) >= 0; } // // Parse a format string into a PSFStack structure // static BOOL argParsePSFormatStr(PSFStack *stack, const char *fmt) { 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) { // "@@" sequence, just print out @ PSFStackItem item; memset(&item, 0, sizeof(item)); item.cmd = -2; 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) { PSFStackItem item; memset(&item, 0, sizeof(item)); item.cmd = ret; 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 == '@') { PSFStackItem item; memset(&item, 0, sizeof(item)); 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; } static BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: optShowHelp = TRUE; break; case 1: sidutil_print_license(); exit(0); break; case 2: th_verbosity++; break; case 3: optParsable = TRUE; break; case 4: optHexadecimal = TRUE; break; case 5: optFieldNamePrefix = FALSE; break; case 6: optOneLineFieldSep = optArg; break; case 7: optEscapeChars = optArg; break; case 8: if (!argParsePSFields(&optFormat, optArg)) return FALSE; break; case 9: optFieldOutput = FALSE; if (!argParsePSFormatStr(&optFormat, optArg)) return FALSE; break; case 10: th_pstr_cpy(&setHVSCPath, optArg); break; case 11: th_pstr_cpy(&setSLDBPath, optArg); break; case 12: th_pstr_cpy(&setSTILDBPath, optArg); break; case 13: optRecurseDirs = TRUE; break; default: THERR("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } static void siPrintFieldPrefixName(FILE *outfh, const char *name, const BOOL multifield) { if (optFieldNamePrefix) { if (optFieldOutput && optOneLineFieldSep == NULL) fprintf(outfh, optParsable ? "%s=" : "%-20s : ", name); else if (multifield) fprintf(outfh, "[%s] ", name); } } static void siPrintFieldPrefix(FILE *outfh, const PSFOption *opt) { siPrintFieldPrefixName(outfh, (optParsable || opt->lname == NULL) ? opt->name : opt->lname, FALSE); } static void siPrintFieldSeparator(FILE *outfh, const BOOL multifield, const BOOL last) { if (optFieldOutput) fputs(optOneLineFieldSep != NULL ? optOneLineFieldSep : "\n", outfh); else if (multifield && !last) fputs(optOneLineFieldSep != NULL ? optOneLineFieldSep : ", ", outfh); } static const char *siGetInfoFormat(const PSFStackItem *item, const int otype) { switch (otype) { case OTYPE_INT: if (!optParsable && item->fmt != NULL) return item->fmt; else return optHexadecimal ? "$%04x" : "%d"; case OTYPE_STR: if (!optParsable && item->fmt != NULL) return item->fmt; else return "%s"; default: return NULL; } } static void siPrintPSIDInfoLine(FILE *outfh, BOOL *shown, const char *fmt, const int otype, const char *d_str, const int d_int, const BOOL convert) { char *formatted, *escaped; escaped = sidutil_escape_string(d_str, optEscapeChars); if ((formatted = siItemFormatStrPrint(fmt, otype, escaped, d_int)) != NULL) { char *converted; if (setChConv.enabled && convert && (converted = sidutil_chconv_convert(&setChConv, formatted)) != NULL) { fputs(converted, outfh); th_free(converted); } else fputs(formatted, outfh); } th_free(formatted); th_free(escaped); *shown = TRUE; } #define PRS(d_str, d_conv) do { \ siPrintFieldPrefix(outfh, opt); \ siPrintPSIDInfoLine(outfh, shown, siGetInfoFormat(item, opt->type), opt->type, d_str, -1, d_conv); \ siPrintFieldSeparator(outfh, FALSE, TRUE); \ } while (0) #define PRI(d_int) do { \ siPrintFieldPrefix(outfh, opt); \ siPrintPSIDInfoLine(outfh, shown, siGetInfoFormat(item, opt->type), opt->type, NULL, d_int, FALSE); \ siPrintFieldSeparator(outfh, FALSE, TRUE); \ } while (0) static void siPrintPSIDInformationField(FILE *outfh, const char *filename, const SIDLibPSIDHeader *psid, BOOL *shown, const PSFStackItem *item) { const PSFOption *opt = &optPSOptions[item->cmd]; char tmp[128]; switch (item->cmd) { case 0: PRS(filename, FALSE); break; case 1: PRS(psid->magic, FALSE); break; case 2: snprintf(tmp, sizeof(tmp), "%d.%d", (psid->version & 0xff), (psid->version >> 8)); PRS(tmp, FALSE); break; case 3: PRS((psid->flags & PSF_PLAYER_TYPE) ? "Compute! SIDPlayer MUS" : "Normal built-in", FALSE); break; case 4: if (psid->version >= 2) PRS((psid->flags & PSF_PLAYSID_TUNE) ? (psid->isRSID ? "C64 BASIC" : "PlaySID") : "C64 compatible", FALSE); break; case 5: if (psid->version >= 2) PRS(sidlib_get_sid_clock_str((psid->flags >> 2) & PSF_CLOCK_MASK), FALSE); break; case 6: if (psid->version >= 2) PRS(sidlib_get_sid_model_str((psid->flags >> 4) & PSF_MODEL_MASK), FALSE); break; case 7: PRI(psid->dataOffset); break; case 8: PRI(psid->dataSize); break; case 9: PRI(psid->loadAddress); break; case 10: PRI(psid->initAddress); break; case 11: PRI(psid->playAddress); break; case 12: PRI(psid->nSongs); break; case 13: PRI(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; PRS(sidlib_get_sid_model_str(flags), FALSE); } 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; PRS(sidlib_get_sid_model_str(flags), FALSE); } break; case 16: if (psid->version >= 3) PRI(0xD000 | (psid->sid2Addr << 4)); break; case 17: if (psid->version >= 4) PRI(0xD000 | (psid->sid3Addr << 4)); break; case 18: PRS(psid->sidName, TRUE); break; case 19: PRS(psid->sidAuthor, TRUE); break; case 20: PRS(psid->sidCopyright, TRUE); 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]); PRS(tmp, FALSE); } break; case 22: if (psid->lengths != NULL && psid->lengths->nlengths > 0) { siPrintFieldPrefix(outfh, opt); for (int i = 0; i < psid->lengths->nlengths; i++) { int len = psid->lengths->lengths[i]; snprintf(tmp, sizeof(tmp), "%d:%02d%s", len / 60, len % 60, (i < psid->lengths->nlengths - 1) ? ", " : ""); siPrintPSIDInfoLine(outfh, shown, siGetInfoFormat(item, OTYPE_STR), OTYPE_STR, tmp, -1, FALSE); } siPrintFieldSeparator(outfh, FALSE, TRUE); } break; case 23: if (psid->stil != NULL) { int nfieldn = 0, nfieldcount = 0; // We need to count the number of fields to be outputted // beforehand, so we can know when we are at the last one for (size_t nsubtune = 0; nsubtune < psid->stil->nsubtunes; nsubtune++) { SIDLibSTILSubTune *subtune = &psid->stil->subtunes[nsubtune]; for (int nfield = 0; nfield < STF_LAST; nfield++) nfieldcount += subtune->fields[nfield].ndata; } for (size_t nsubtune = 0; nsubtune < psid->stil->nsubtunes; nsubtune++) { SIDLibSTILSubTune *subtune = &psid->stil->subtunes[nsubtune]; int maxdata = 0; // For each subtune we need to check the max number of field data items for (int nfield = 0; nfield < STF_LAST; nfield++) { SIDLibSTILField *fld = &subtune->fields[nfield]; if (fld->ndata > maxdata) maxdata = fld->ndata; } // Now output the items in field order for (int nitem = 0; nitem < maxdata; nitem++) for (int nfield = 0; nfield < STF_LAST; nfield++) { SIDLibSTILField *fld = &subtune->fields[nfield]; if (nitem < fld->ndata) { if (nsubtune > 0) { snprintf(tmp, sizeof(tmp), "STIL#%d/%s", subtune->tune, sidlib_stil_fields[nfield]); } else { snprintf(tmp, sizeof(tmp), "STIL/%s", sidlib_stil_fields[nfield]); } siPrintFieldPrefixName(outfh, tmp, TRUE); siPrintPSIDInfoLine(outfh, shown, siGetInfoFormat(item, OTYPE_STR), OTYPE_STR, fld->data[nitem], -1, TRUE); siPrintFieldSeparator(outfh, TRUE, ++nfieldn >= nfieldcount); } } } } break; } } void siPSIDError(th_ioctx *fh, const int err, const char *msg) { (void) err; THERR("%s - %s\n", msg, fh->filename); } void siSTILError(th_ioctx *fh, const int err, const char *msg) { (void) err; THERR("[%s:%" PRIu_SIZE_T "] %s\n", fh->filename, fh->line, msg); } BOOL siHandleSIDFile(const char *filename) { SIDLibPSIDHeader *psid = NULL; th_ioctx *infh = NULL; FILE *outfh; BOOL shown = FALSE; int res; outfh = stdout; if ((res = th_io_fopen(&infh, &th_stdio_io_ops, filename, "rb")) != THERR_OK) { THERR("Could not open file '%s': %s\n", filename, th_error_str(res)); goto error; } th_io_set_handlers(infh, siPSIDError, NULL); // Read PSID data if ((res = sidlib_read_sid_file_alloc(infh, &psid, setSLDBNewFormat, NULL)) != THERR_OK) goto error; // Get songlength information, if any if (sidSLDB != NULL) psid->lengths = sidlib_sldb_get_by_hash(sidSLDB, psid->hash); // Get STIL information, if any if (sidSTILDB != NULL) { psid->stil = sidlib_stildb_get_node(sidSTILDB, sidutil_strip_hvsc_path(setHVSCPath, filename)); if (psid->stil != NULL) psid->stil->lengths = psid->lengths; } // Output for (int index = 0; index < optFormat.nitems; index++) { PSFStackItem *item = &optFormat.items[index]; switch (item->cmd) { case -1: sidutil_print_string_escaped(outfh, item->str); break; case -2: fputc(item->chr, outfh); break; default: siPrintPSIDInformationField(outfh, filename, psid, &shown, item); break; } } if (optFieldOutput && shown) { fprintf(outfh, "\n"); } // Shutdown error: sidlib_free_sid_file(psid); th_io_free(infh); return TRUE; } BOOL argHandleFileDir(const char *path, const char *filename, const char *pattern) { th_stat_data sdata; char *npath; BOOL ret = TRUE; if (filename != NULL) npath = th_strdup_printf("%s%c%s", path, TH_DIR_SEPARATOR_CHR, filename); else npath = th_strdup(path); if (!th_stat_path(npath, &sdata)) { THERR("File or path '%s' does not exist.\n", npath); ret = FALSE; goto out; } optNFiles++; if (sdata.flags & TH_IS_DIR) { DIR *dirh; struct dirent *entry; // Check if recursion is disabled if (!optRecurseDirs && optNFiles > 1) goto out; if ((dirh = opendir(npath)) == NULL) { int err = th_get_error(); THERR("Could not open directory '%s': %s\n", path, th_error_str(err)); ret = FALSE; goto out; } while ((entry = readdir(dirh)) != NULL) if (entry->d_name[0] != '.') { if (!argHandleFileDir(npath, entry->d_name, pattern)) { ret = FALSE; goto out; } } closedir(dirh); } else if (pattern == NULL || th_strmatch(filename, pattern)) { siHandleSIDFile(npath); } out: th_free(npath); return ret; } BOOL argHandleFile(char *path) { char *pattern, *filename, *pt, *npath; BOOL ret; if ((npath = th_strdup(path)) == NULL) return FALSE; // Check if we have path separators if ((pt = strrchr(npath, '/')) != NULL || (pt = strrchr(npath, '\\')) != NULL) { *pt++ = 0; } else { th_free(npath); npath = th_strdup("."); pt = strcmp(path, npath) != 0 ? path : NULL; } // Check if we have glob pattern chars pattern = filename = NULL; if (pt != NULL && *pt != 0) { if (strchr(pt, '*') || strchr(pt, '?')) pattern = th_strdup(pt); else filename = th_strdup(pt); } ret = argHandleFileDir(npath, filename, pattern); th_free(pattern); th_free(npath); th_free(filename); return ret; } int main(int argc, char *argv[]) { th_ioctx *infh = NULL; char *setLang = getenv("LANG"); int ret; // Get HVSC_BASE env variable if it is set th_pstr_cpy(&setHVSCPath, getenv("HVSC_BASE")); // Initialize th_init("SIDInfo", "PSID/RSID information displayer", "0.9.2", "By Matti 'ccr' Hamalainen (C) Copyright 2014-2020 TNSP", "This program is distributed under a 3-clause BSD -style license."); th_verbosity = 0; memset(&optFormat, 0, sizeof(optFormat)); memset(&setChConv, 0, sizeof(setChConv)); // Parse command line arguments if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, NULL, OPTH_ONLY_OPTS)) goto exit; // Check if HVSC path is set if (setHVSCPath != NULL) { // Ensure that there is a path separator at the end if (th_strrcasecmp(setHVSCPath, TH_DIR_SEPARATOR_STR) == NULL) th_pstr_printf(&setHVSCPath, "%s%c", setHVSCPath, TH_DIR_SEPARATOR_CHR); // If SLDB path is not set, autocheck for .md5 and .txt if (setSLDBPath == NULL) setSLDBPath = sidutil_check_hvsc_file(setHVSCPath, SIDUTIL_SLDB_FILEBASE, ".md5"); if (setSLDBPath == NULL) setSLDBPath = sidutil_check_hvsc_file(setHVSCPath, SIDUTIL_SLDB_FILEBASE, ".txt"); if (setSTILDBPath == NULL) setSTILDBPath = sidutil_check_hvsc_file(setHVSCPath, SIDUTIL_STILDB_FILENAME, NULL); } // Check if help is requested if (optShowHelp) { argShowHelp(); goto exit; } // Initialize character set conversion if ((ret = sidutil_chconv_init(&setChConv, setLang)) != THERR_OK) { THERR("Could not initialize character set conversion (LANG='%s'): %s\n", setLang, th_error_str(ret)); } THMSG(2, "Requested output LANG='%s', use charset conversion=%s\n", setChConv.outLang, setChConv.enabled ? "yes" : "no"); // Check operation mode if (optOneLineFieldSep != NULL || (!optFieldOutput && optFormat.nitems > 0)) { // For one-line format and formatted output (-F), disable parsable optParsable = FALSE; // If no escape chars have been set, use the field separator(s) if (optEscapeChars == NULL) optEscapeChars = optOneLineFieldSep; } if (optFieldOutput && optFormat.nitems == 0) { // For standard field output, push standard items to format stack siClearStack(&optFormat); for (int i = 0; i < noptPSOptions; i++) { PSFStackItem item; memset(&item, 0, sizeof(item)); item.cmd = i; item.fmt = th_strdup(optPSOptions[i].dfmt); siStackAddItem(&optFormat, &item); } } // Read SLDB and STILDB if (setSLDBPath != NULL) { // Initialize SLDB setSLDBNewFormat = th_strrcasecmp(setSLDBPath, ".md5") != NULL; if ((ret = th_io_fopen(&infh, &th_stdio_io_ops, setSLDBPath, "r")) != THERR_OK) { THERR("Could not open SLDB '%s': %s\n", setSLDBPath, th_error_str(ret)); goto err1; } th_io_set_handlers(infh, siSTILError, NULL); THMSG(1, "Reading SLDB (%s format [%s]): %s\n", setSLDBNewFormat ? "new" : "old", setSLDBNewFormat ? ".md5" : ".txt", setSLDBPath); if ((ret = sidlib_sldb_new(&sidSLDB)) != THERR_OK) { THERR("Could not allocate SLDB database structure: %s\n", th_error_str(ret)); goto err1; } if ((ret = sidlib_sldb_read(infh, sidSLDB)) != THERR_OK) { THERR("Error parsing SLDB: %s\n", th_error_str(ret)); goto err1; } if ((ret = sidlib_sldb_build_index(sidSLDB)) != THERR_OK) { THERR("Error building SLDB index: %s\n", th_error_str(ret)); goto err1; } err1: th_io_free(infh); infh = NULL; } if (setSTILDBPath != NULL) { // Initialize STILDB if ((ret = th_io_fopen(&infh, &th_stdio_io_ops, setSTILDBPath, "r")) != THERR_OK) { THERR("Could not open STIL database '%s': %s\n", setSTILDBPath, th_error_str(ret)); goto err2; } th_io_set_handlers(infh, siSTILError, NULL); THMSG(1, "Reading STIL database: %s\n", setSTILDBPath); if ((ret = sidlib_stildb_new(&sidSTILDB)) != THERR_OK) { THERR("Could not allocate STIL database structure: %s\n", th_error_str(ret)); goto err2; } if ((ret = sidlib_stildb_read(infh, sidSTILDB, NULL)) != THERR_OK) { THERR("Error parsing STIL: %s\n", th_error_str(ret)); goto err2; } if ((ret = sidlib_stildb_build_index(sidSTILDB)) != THERR_OK) { THERR("Error building STIL index: %s\n", th_error_str(ret)); goto err2; } err2: th_io_free(infh); infh = NULL; } // Process files if (!th_args_process(argc, argv, optList, optListN, NULL, argHandleFile, OPTH_ONLY_OTHER)) goto exit; if (optNFiles == 0) { THERR("No filename(s) specified. Try --help.\n"); } exit: sidutil_chconv_close(&setChConv); siClearStack(&optFormat); th_free(setHVSCPath); th_free(setSLDBPath); th_free(setSTILDBPath); sidlib_sldb_free(sidSLDB); sidlib_stildb_free(sidSTILDB); return 0; }