Mercurial > hg > sidinfo
view sidinfo.c @ 316:b0c844b39516
Move and rename siEscapeString() to sidutil_escape_string() and
siPrintStrEscapes() to sidutil_print_string_escaped().
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 11 Jan 2020 18:34:34 +0200 |
parents | 67392d7ef391 |
children | f3ba2ba894b1 |
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; 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", "Show this help", OPT_NONE }, { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, {10, 0, "license", "Print out this program's license agreement", 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 }, {11, 'e', "escape", "Escape these characters in fields (see note)", 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 root directory", OPT_ARGREQ }, { 9, 'S', "sldb", "Specify Songlengths.(txt|md5) file", OPT_ARGREQ }, {13, 'T', "stildb", "Specify STIL.txt file", OPT_ARGREQ }, {12, 'R', "recurse", "Recurse into sub-directories", OPT_NONE }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp(void) { int index, len; th_print_banner(stdout, th_prog_name, "[options] <sid file|path> [file|path #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" "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" , 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) { 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.buf = th_malloc(ctx.size); ctx.pos = 0; ctx.ipos = 0; if (ctx.buf == 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: argShowHelp(); exit(0); break; case 10: sidutil_print_license(); exit(0); break; case 1: th_verbosity++; break; case 2: optParsable = TRUE; break; case 3: if (!argParsePSFields(&optFormat, optArg)) return FALSE; break; case 4: optHexadecimal = TRUE; break; case 5: optFieldNamePrefix = FALSE; break; case 6: optOneLineFieldSep = optArg; break; case 7: optFieldOutput = FALSE; optParsable = FALSE; if (!argParsePSFormatStr(&optFormat, optArg)) return FALSE; break; case 8: th_pstr_cpy(&setHVSCPath, optArg); break; case 9: th_pstr_cpy(&setSLDBPath, optArg); break; case 13: th_pstr_cpy(&setSTILDBPath, optArg); break; case 11: optEscapeChars = optArg; break; case 12: optRecurseDirs = TRUE; break; default: THERR("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } static void siPrintFieldPrefixName(FILE *outFile, const char *name, const BOOL multifield) { if (optFieldNamePrefix) { if (optFieldOutput && optOneLineFieldSep == NULL) fprintf(outFile, optParsable ? "%s=" : "%-20s : ", name); else if (multifield) fprintf(outFile, "[%s] ", name); } } static void siPrintFieldPrefix(FILE *outFile, const PSFOption *opt) { siPrintFieldPrefixName(outFile, (optParsable || opt->lname == NULL) ? opt->name : opt->lname, FALSE); } static void siPrintFieldSeparator(FILE *outFile, const BOOL multifield, const BOOL last) { if (optFieldOutput) fputs(optOneLineFieldSep != NULL ? optOneLineFieldSep : "\n", outFile); else if (multifield && !last) fputs(optOneLineFieldSep != NULL ? optOneLineFieldSep : ", ", outFile); } 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 *outFile, BOOL *shown, const char *fmt, const int otype, const char *d_str, const int d_int, const BOOL convert) { char *str, *tmp; if (d_str != NULL && setChConv.enabled && convert) { char *tmp2 = sidutil_chconv_convert(&setChConv, d_str); tmp = sidutil_escape_string(tmp2, optEscapeChars); th_free(tmp2); } else tmp = sidutil_escape_string(d_str, optEscapeChars); if ((str = siItemFormatStrPrint(fmt, otype, tmp, d_int)) != NULL) fputs(str, outFile); th_free(str); th_free(tmp); *shown = TRUE; } #define PRS(d_str, d_conv) do { \ siPrintFieldPrefix(outFile, opt); \ siPrintPSIDInfoLine(outFile, shown, siGetInfoFormat(item, opt->type), opt->type, d_str, -1, d_conv); \ siPrintFieldSeparator(outFile, FALSE, TRUE); \ } while (0) #define PRI(d_int) do { \ siPrintFieldPrefix(outFile, opt); \ siPrintPSIDInfoLine(outFile, shown, siGetInfoFormat(item, opt->type), opt->type, NULL, d_int, FALSE); \ siPrintFieldSeparator(outFile, FALSE, TRUE); \ } while (0) static void siPrintPSIDInformationField(FILE *outFile, 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(outFile, 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(outFile, shown, siGetInfoFormat(item, OTYPE_STR), OTYPE_STR, tmp, -1, FALSE); } siPrintFieldSeparator(outFile, 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(outFile, tmp, TRUE); siPrintPSIDInfoLine(outFile, shown, siGetInfoFormat(item, OTYPE_STR), OTYPE_STR, fld->data[nitem], -1, TRUE); siPrintFieldSeparator(outFile, 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 *inFile = NULL; FILE *outFile; BOOL shown = FALSE; int res; outFile = stdout; if ((res = th_io_fopen(&inFile, &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(inFile, siPSIDError, NULL); // Read PSID data if ((res = sidlib_read_sid_file_alloc(inFile, &psid, setSLDBNewFormat)) != 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(outFile, item->str); break; case -2: fputc(item->chr, outFile); break; default: siPrintPSIDInformationField(outFile, filename, psid, &shown, item); break; } } if (optFieldOutput && shown) { fprintf(outFile, "\n"); } // Shutdown error: sidlib_free_sid_file(psid); th_io_free(inFile); 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 = NULL, *filename = NULL, *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 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 *inFile = 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)); // Initialize character 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)); } // Parse command line arguments if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, NULL, OPTH_ONLY_OPTS)) goto out; THMSG(2, "Requested output LANG='%s', use charset conversion=%s\n", setChConv.outLang, setChConv.enabled ? "yes" : "no"); 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); } } // 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, SET_SLDB_FILEBASE, ".md5"); if (setSLDBPath == NULL) setSLDBPath = sidutil_check_hvsc_file(setHVSCPath, SET_SLDB_FILEBASE, ".txt"); if (setSTILDBPath == NULL) setSTILDBPath = sidutil_check_hvsc_file(setHVSCPath, SET_STILDB_FILENAME, NULL); } if (setSLDBPath != NULL) { // Initialize SLDB setSLDBNewFormat = th_strrcasecmp(setSLDBPath, ".md5") != NULL; if ((ret = th_io_fopen(&inFile, &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(inFile, 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(inFile, 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(inFile); inFile = NULL; } if (setSTILDBPath != NULL) { // Initialize STILDB if ((ret = th_io_fopen(&inFile, &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(inFile, 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(inFile, sidSTILDB)) != 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(inFile); inFile = NULL; } // Process files if (!th_args_process(argc, argv, optList, optListN, NULL, argHandleFile, OPTH_ONLY_OTHER)) goto out; if (optNFiles == 0) { THERR("No filename(s) specified. Try --help.\n"); } out: 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; }