Mercurial > hg > sidinfo
view sidinfo.c @ 50:e843b39235db
Bump version.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 04 Mar 2015 06:43:54 +0200 |
parents | af1e12c8ebf1 |
children | 485c8ed6d3fe |
line wrap: on
line source
/* * SIDInfo - PSID/RSID information displayer * Written by Matti 'ccr' Hämäläinen * (C) Copyright 2014 Tecnic Software productions (TNSP) */ #include "th_args.h" #include "th_endian.h" #include "th_string.h" #include "th_crypto.h" // Some constants #define PSID_MAGIC_LEN 4 #define PSID_STR_LEN 32 #define PSID_BUFFER_SIZE (1024 * 16) // 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_DATA_SIZE = 0x00100000, SIF_HASH = 0x00200000, SIF_FILENAME = 0x10000000, SIF_ALL = 0xffffffff, }; typedef struct { uint32_t flag; char *name; char *lname; } PSFOption; static const PSFOption optPSFlags[] = { { SIF_TYPE , "Type" , NULL }, { SIF_VERSION , "Version" , NULL }, { 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_FILENAME , "Filename" , 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_ALL , "All" , NULL }, }; static const int noptPSFlags = sizeof(optPSFlags) / sizeof(optPSFlags[0]); // Option variables BOOL optParsable = FALSE, optNoNamePrefix = FALSE, optHexadecimal = FALSE, optOneLine = FALSE; char * optFieldSep = NULL; uint32_t optFields = SIF_ALL; int optNFiles = 0; // 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 }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp(void) { int index, n; 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 (index = n = 0; index < noptPSFlags; index++) { const PSFOption *opt = &optPSFlags[index]; printf("%s%s", opt->name, (index < noptPSFlags - 1) ? ", " : "\n\n"); if (++n > 5) { printf("\n"); n = 0; } } printf("Example: %s -x -p -f hash,copyright somesidfile.sid\n", th_prog_name); } int argMatchPSField(const char *field) { int index, found = -1; size_t len = strlen(field); for (index = 0; index < noptPSFlags; index++) { const PSFOption *opt = &optPSFlags[index]; if (th_strncasecmp(opt->name, field, len) == 0) { if (found >= 0) return -2; found = index; } } return found; } BOOL argParsePSField(char *opt, char *end, uint32_t *fields) { // Trim whitespace if (end != NULL) { *end = 0; for (end--; end > opt && *end && th_isspace(*end); end--) *end = 0; } while (*opt && th_isspace(*opt)) opt++; // Match field name int found = argMatchPSField(opt); switch (found) { case -1: THERR("No such flag '%s'.\n", opt); return FALSE; case -2: THERR("Flag '%s' is ambiguous.\n", opt); return FALSE; default: *fields |= optPSFlags[found].flag; 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; } //fprintf(stderr, "%08x\n", optFields); } break; case 4: optHexadecimal = TRUE; break; case 5: optNoNamePrefix = TRUE; break; case 6: optOneLine = TRUE; optFieldSep = optArg; break; default: THERR("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } typedef struct { char magic[PSID_MAGIC_LEN + 1]; // "PSID" / "RSID" magic identifier uint16_t version, // Version number dataOffset, // Start of actual c64 data in file loadAddress, // Loading address initAddress, // Initialization address playAddress, // Play one frame nSongs, // Number of subsongs startSong; // Default starting song uint32_t speed; // Speed char sidName[PSID_STR_LEN + 1]; // Descriptive text-fields, ASCIIZ char sidAuthor[PSID_STR_LEN + 1]; char sidCopyright[PSID_STR_LEN + 1]; // PSIDv2 data uint16_t flags; // Flags uint8_t startPage, pageLength; uint16_t reserved; // Extra data BOOL isRSID; size_t dataSize; // Total size of data - header th_md5hash_t hash; // Songlength database hash } PSIDHeader; enum { PSF_PLAYER_TYPE = 0x0001, // 0 = built-in, 1 = Compute! SIDPlayer MUS PSF_PLAYSID_TUNE = 0x0002, // 0 = Real C64-compatible, 1 = PlaySID specific (v2NG) PSF_CLOCK_UNKNOWN = 0x0000, // Video standard used (v2NG) PSF_CLOCK_PAL = 0x0004, PSF_CLOCK_NTSC = 0x0008, PSF_CLOCK_ANY = 0x000c, PSF_CLOCK_MASK = 0x000c, PSF_MODEL_UNKNOWN = 0x0000, // SID model (v2NG) PSF_MODEL_MOS6581 = 0x0010, PSF_MODEL_MOS8580 = 0x0020, PSF_MODEL_ANY = 0x0030, PSF_MODEL_MASK = 0x0030, }; static void siAppendHash16(th_md5state_t *state, uint16_t data) { uint8_t ib8[2]; ib8[0] = data & 0xff; ib8[1] = data >> 8; th_md5_append(state, (uint8_t *) &ib8, sizeof(ib8)); } int siReadPSIDFile(FILE *inFile, PSIDHeader *psid) { th_md5state_t state; uint8_t tmp8, *fileData = NULL; int index, ret = -1; size_t read; BOOL first; memset(psid, 0, sizeof(*psid)); if ((fileData = (uint8_t *) th_malloc(PSID_BUFFER_SIZE)) == NULL) { THERR("Error allocating temporary data buffer of %d bytes.\n", PSID_BUFFER_SIZE); goto error; } // Read PSID header in if (!th_fread_str(inFile, (uint8_t *) psid->magic, PSID_MAGIC_LEN) || !th_fread_be16(inFile, &psid->version) || !th_fread_be16(inFile, &psid->dataOffset) || !th_fread_be16(inFile, &psid->loadAddress) || !th_fread_be16(inFile, &psid->initAddress) || !th_fread_be16(inFile, &psid->playAddress) || !th_fread_be16(inFile, &psid->nSongs) || !th_fread_be16(inFile, &psid->startSong) || !th_fread_be32(inFile, &psid->speed)) { THERR("Could not read PSID/RSID header.\n"); goto error; } psid->magic[PSID_MAGIC_LEN] = 0; if ((psid->magic[0] != 'R' && psid->magic[0] != 'P') || psid->magic[1] != 'S' || psid->magic[2] != 'I' || psid->magic[3] != 'D' || psid->version < 1 || psid->version > 3) { THERR("Not a supported PSID or RSID file.\n"); goto error; } psid->isRSID = psid->magic[0] == 'R'; if (!th_fread_str(inFile, (uint8_t *)psid->sidName, PSID_STR_LEN) || !th_fread_str(inFile, (uint8_t *)psid->sidAuthor, PSID_STR_LEN) || !th_fread_str(inFile, (uint8_t *)psid->sidCopyright, PSID_STR_LEN)) { THERR("Error reading SID file header.\n"); goto error; } psid->sidName[PSID_STR_LEN] = 0; psid->sidAuthor[PSID_STR_LEN] = 0; psid->sidCopyright[PSID_STR_LEN] = 0; // Check if we need to load PSIDv2NG header ... if (psid->version >= 2) { // Yes, we need to if (!th_fread_be16(inFile, &psid->flags) || !th_fread_byte(inFile, &psid->startPage) || !th_fread_byte(inFile, &psid->pageLength) || !th_fread_be16(inFile, &psid->reserved)) { THERR("Error reading PSID/RSID v2+ extra header data.\n"); goto error; } } // Initialize MD5-hash calculation th_md5_init(&state); // Process actual data psid->dataSize = 0; first = TRUE; do { read = fread(fileData, sizeof(uint8_t), PSID_BUFFER_SIZE, inFile); psid->dataSize += read; if (first && psid->loadAddress == 0) { if (read < 4) { THERR("Error reading song data, unexpectedly small file.\n"); goto error; } // Grab the actual load address psid->loadAddress = TH_LE16_TO_NATIVE(*(uint16_t *) fileData); // Strip load address (2 first bytes) th_md5_append(&state, &fileData[2], read - 2); first = FALSE; } else if (read > 0) { // Append "as is" th_md5_append(&state, fileData, read); } } while (read > 0 && !feof(inFile)); // Append header data to hash siAppendHash16(&state, psid->initAddress); siAppendHash16(&state, psid->playAddress); siAppendHash16(&state, psid->nSongs); // Append song speed data to hash tmp8 = psid->isRSID ? 60 : 0; for (index = 0; index < psid->nSongs && index < 32; index++) { if (psid->isRSID) tmp8 = 60; else tmp8 = (psid->speed & (1 << index)) ? 60 : 0; th_md5_append(&state, &tmp8, sizeof(tmp8)); } // Rest of songs (more than 32) for (index = 32; index < psid->nSongs; index++) th_md5_append(&state, &tmp8, sizeof(tmp8)); // PSIDv2NG specific if (psid->version >= 2) { // REFER TO SIDPLAY HEADERS FOR MORE INFORMATION tmp8 = (psid->flags >> 2) & 3; if (tmp8 == 2) th_md5_append(&state, &tmp8, sizeof(tmp8)); } // Calculate the hash th_md5_finish(&state, psid->hash); ret = 0; error: // Free buffer th_free(fileData); return ret; } static void siPrintFieldPrefix(FILE *outFile, const char *name) { if (!optNoNamePrefix) fprintf(outFile, optParsable ? "%s=" : "%-20s : ", name); } static void siPrintFieldSeparator(FILE *outFile) { fprintf(outFile, optOneLine ? optFieldSep : "\n"); } static void siPrintPSIDInfoLine(FILE *outFile, BOOL *shown, const int xindex, const char *xfmt, const char *xaltfmt, ...) { const PSFOption *opt = &optPSFlags[xindex]; if (optFields & opt->flag) { va_list ap; const char *fmt = optHexadecimal ? (xaltfmt != NULL ? xaltfmt : xfmt) : xfmt; siPrintFieldPrefix(outFile, (optParsable || opt->lname == NULL) ? opt->name : opt->lname); va_start(ap, xaltfmt); if (optParsable) vfprintf(outFile, fmt, ap); else vfprintf(outFile, fmt, ap); va_end(ap); siPrintFieldSeparator(outFile); *shown = TRUE; } } #define PR(xindex, xfmt, xaltfmt, ...) siPrintPSIDInfoLine(outFile, &shown, xindex, xfmt, xaltfmt, __VA_ARGS__ ) void siPrintPSIDInformation(FILE *outFile, const char *filename, const PSIDHeader *psid) { BOOL shown = FALSE; PR(13, "%s", NULL, filename); PR( 0, "%s", NULL, psid->magic); PR( 1, "%d.%d", NULL, (psid->version & 0xff), (psid->version >> 8)); PR(14, "%s", NULL, (psid->flags & PSF_PLAYER_TYPE) ? "Compute! SIDPlayer MUS" : "Normal built-in"); if (psid->version >= 2) { char *str; PR(15, "%s", NULL, (psid->flags & PSF_PLAYSID_TUNE) ? "PlaySID" : "C64 compatible"); switch (psid->flags & PSF_CLOCK_MASK) { case PSF_CLOCK_UNKNOWN : str = "Unknown"; break; case PSF_CLOCK_PAL : str = "PAL 50Hz"; break; case PSF_CLOCK_NTSC : str = "NTSC 60Hz"; break; case PSF_CLOCK_ANY : str = "PAL / NTSC"; break; default : str = "?"; break; } PR(16, "%s", NULL, str); switch (psid->flags & PSF_MODEL_MASK) { case PSF_MODEL_UNKNOWN : str = "Unknown"; break; case PSF_MODEL_MOS6581 : str = "MOS6581"; break; case PSF_MODEL_MOS8580 : str = "MOS8580"; break; case PSF_MODEL_ANY : str = "MOS6581 / MOS8580"; break; default : str = "?"; break; } PR(17, "%s", NULL, str); } PR( 2, "%d", "$%08x", psid->dataOffset); PR( 3, "%d", "$%08x", psid->dataSize); PR( 4, "%d", "$%04x", psid->loadAddress); PR( 5, "%d", "$%04x", psid->initAddress); PR( 6, "%d", "$%04x", psid->playAddress); PR( 7, "%d", "$%04x", psid->nSongs); PR( 8, "%d", "$%04x", psid->startSong); PR( 9, "%s", NULL, psid->sidName); PR(10, "%s", NULL, psid->sidAuthor); PR(11, "%s", NULL, psid->sidCopyright); if (optFields & SIF_HASH) { siPrintFieldPrefix(outFile, "Hash"); th_md5_print(outFile, psid->hash); siPrintFieldSeparator(outFile); } if (shown && optOneLine) fprintf(outFile, "\n"); } BOOL argHandleFile(char *filename) { static PSIDHeader psid; static FILE *inFile = NULL; optNFiles++; if ((inFile = fopen(filename, "rb")) == NULL) { THERR("Could not open file '%s'.\n", filename); return TRUE; } // Read PSID data if (siReadPSIDFile(inFile, &psid) != 0) { THERR("Error reading %s\n", filename); goto error; } // Output siPrintPSIDInformation(stdout, filename, &psid); // Shutdown error: if (inFile != NULL) fclose(inFile); return TRUE; } int main(int argc, char *argv[]) { // Initialize th_init("SIDInfo", "PSID/RSID information displayer", "0.5.3", NULL, NULL); th_verbosityLevel = 0; // 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; } // Process files if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_ONLY_OTHER)) return -2; if (optNFiles == 0) { argShowHelp(); THERR("No filename(s) specified.\n"); } return 0; }