Mercurial > hg > sidinfo
view sidlib.c @ 237:d28f3d537284
Cosmetics.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 04 Jan 2020 13:19:35 +0200 |
parents | 609bfc1bd628 |
children | 82540f819194 |
line wrap: on
line source
/* * SIDInfoLib - Way too simplistic PSID/RSID file library * Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2014-2020 Tecnic Software productions (TNSP) */ #include "sidlib.h" #include "th_endian.h" #include "th_string.h" BOOL sidlib_fread_str(th_ioctx *ctx, char **str, const size_t len) { char *tmp = th_malloc(len + 1); if (tmp == NULL) { th_io_error(ctx, THERR_MALLOC, "Could not allocate %" PRIu_SIZE_T " bytes for a string.\n", len); goto err; } if (!thfread_str(ctx, tmp, len)) { th_io_error(ctx, THERR_FREAD, "Could not read %" PRIu_SIZE_T " bytes from file.\n", len); goto err; } tmp[len] = 0; *str = tmp; return TRUE; err: th_free(tmp); return FALSE; } static BOOL sidlib_read_hash_data(th_ioctx *ctx, SIDLibPSIDHeader *psid, th_md5state_t *state, const BOOL newSLDB) { uint8_t *data = NULL; BOOL ret = FALSE, first = TRUE; size_t read; if ((data = (uint8_t *) th_malloc(SIDLIB_BUFFER_SIZE)) == NULL) { th_io_error(ctx, THERR_MALLOC, "Error allocating temporary data buffer of %d bytes.\n", SIDLIB_BUFFER_SIZE); goto error; } psid->dataSize = 0; do { read = thfread(data, sizeof(uint8_t), SIDLIB_BUFFER_SIZE, ctx); psid->dataSize += read; // If load address is 0 in header and we have the first block, grab it if (first && psid->loadAddress == 0) { if (read < 4) { th_io_error(ctx, THERR_FREAD, "Error reading song data, unexpectedly small file.\n"); goto error; } // Grab the load address psid->loadAddress = TH_LE16_TO_NATIVE(*(uint16_t *) data); // .. do not include the load address to the hash if NEW SLDB format if (newSLDB) th_md5_append(state, &data[2], read - 2); else th_md5_append(state, data, read); first = FALSE; } else if (read > 0) { // Append data "as is" th_md5_append(state, data, read); } } while (read > 0 && !thfeof(ctx)); ret = TRUE; error: th_free(data); return ret; } BOOL sidlib_read_sid_file(th_ioctx *ctx, SIDLibPSIDHeader *psid, const BOOL newSLDB) { th_md5state_t state; BOOL ret = FALSE; off_t hdrStart, hdrEnd; hdrStart = thftell(ctx); // Read PSID header in if (!thfread_str(ctx, (uint8_t *) psid->magic, SIDLIB_PSID_MAGIC_LEN) || !thfread_be16(ctx, &psid->version) || !thfread_be16(ctx, &psid->dataOffset) || !thfread_be16(ctx, &psid->loadAddress) || !thfread_be16(ctx, &psid->initAddress) || !thfread_be16(ctx, &psid->playAddress) || !thfread_be16(ctx, &psid->nSongs) || !thfread_be16(ctx, &psid->startSong) || !thfread_be32(ctx, &psid->speed)) { th_io_error(ctx, ctx->status, "Could not read PSID/RSID header from '%s': %s.\n", ctx->filename, th_error_str(ctx->status)); goto error; } psid->magic[SIDLIB_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 > 4) { th_io_error(ctx, THERR_NOT_SUPPORTED, "Not a supported PSID or RSID file: %s\n", ctx->filename); goto error; } psid->isRSID = psid->magic[0] == 'R'; if (!sidlib_fread_str(ctx, &psid->sidName, SIDLIB_PSID_STR_LEN) || !sidlib_fread_str(ctx, &psid->sidAuthor, SIDLIB_PSID_STR_LEN) || !sidlib_fread_str(ctx, &psid->sidCopyright, SIDLIB_PSID_STR_LEN)) { th_io_error(ctx, ctx->status, "Error reading SID file header from '%s': %s.\n", ctx->filename, th_error_str(ctx->status)); goto error; } // Check if we need to load PSIDv2NG header ... if (psid->version >= 2) { // Yes, we need to if (!thfread_be16(ctx, &psid->flags) || !thfread_u8(ctx, &psid->startPage) || !thfread_u8(ctx, &psid->pageLength) || !thfread_u8(ctx, &psid->sid2Addr) || !thfread_u8(ctx, &psid->sid3Addr)) { th_io_error(ctx, ctx->status, "Error reading PSID/RSID v2+ extra header data from '%s': %s.\n", ctx->filename, th_error_str(ctx->status)); goto error; } } hdrEnd = thftell(ctx); // Initialize MD5-hash calculation th_md5_init(&state); if (newSLDB) { // New Songlengths.md5 style hash calculation: // We just hash the whole file, so seek back to beginning .. thfseek(ctx, hdrStart, SEEK_SET); if (!sidlib_read_hash_data(ctx, psid, &state, FALSE)) goto error; psid->dataSize -= hdrEnd - hdrStart; } else { // "Old" Songlengths.txt style MD5 hash calculation // We need to separately hash data etc. if (!sidlib_read_hash_data(ctx, psid, &state, TRUE)) goto error; // Append header data to hash th_md5_append_le16(&state, psid->initAddress); th_md5_append_le16(&state, psid->playAddress); th_md5_append_le16(&state, psid->nSongs); // Append song speed data to hash uint8_t tmp8 = psid->isRSID ? 60 : 0; for (int 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 (int 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 = TRUE; error: // Free buffer return ret; } BOOL sidlib_read_sid_file_alloc(th_ioctx *ctx, SIDLibPSIDHeader **ppsid, const BOOL newSLDB) { if ((*ppsid = th_malloc0(sizeof(SIDLibPSIDHeader))) == NULL) { th_io_error(ctx, THERR_MALLOC, "Error allocating PSID context struct.\n"); return FALSE; } (*ppsid)->allocated = TRUE; return sidlib_read_sid_file(ctx, *ppsid, newSLDB); } void sidlib_free_sid_file(SIDLibPSIDHeader *psid) { if (psid != NULL) { th_free(psid->sidName); th_free(psid->sidAuthor); th_free(psid->sidCopyright); if (psid->allocated) th_free(psid); } } const char *sidlib_get_sid_clock_str(const int flags) { switch (flags) { case PSF_CLOCK_UNKNOWN : return "Unknown"; case PSF_CLOCK_PAL : return "PAL 50Hz"; case PSF_CLOCK_NTSC : return "NTSC 60Hz"; case PSF_CLOCK_ANY : return "PAL / NTSC"; default : return "?"; } } const char *sidlib_get_sid_model_str(const int flags) { switch (flags) { case PSF_MODEL_UNKNOWN : return "Unknown"; case PSF_MODEL_MOS6581 : return "MOS6581"; case PSF_MODEL_MOS8580 : return "MOS8580"; case PSF_MODEL_ANY : return "MOS6581 / MOS8580"; default : return "?"; } } // Free memory allocated for given SLDB node // static void sidlib_sldb_node_free(SIDLibSLDBNode *node) { if (node != NULL) { th_free_r(&(node->lengths)); th_free_r(&node); } } // Parse a time-entry in SLDB format // static int sidlib_sldb_get_value(const char *str, size_t *pos) { int result = 0; while (isdigit(str[*pos])) result = (result * 10) + (str[(*pos)++] - '0'); return result; } static int sidlib_sldb_gettime(const char *str, size_t *pos) { int result; // Check if it starts with a digit if (th_isdigit(str[*pos])) { // Get minutes-field result = sidlib_sldb_get_value(str, pos) * 60; // Check the field separator char if (str[*pos] == ':') { // Get seconds-field (*pos)++; result += sidlib_sldb_get_value(str, pos); } else result = -2; } else result = -1; // Ignore and skip the possible attributes while (str[*pos] && !th_isspace(str[*pos])) (*pos)++; return result; } // Parse one SLDB definition line, return SLDB node // SIDLibSLDBNode *sidlib_sldb_parse_entry(th_ioctx *ctx, const char *line) { SIDLibSLDBNode *node = NULL; size_t pos, tmpLen, savePos; BOOL isOK; int i; // Allocate new node node = (SIDLibSLDBNode *) th_malloc0(sizeof(SIDLibSLDBNode)); if (node == NULL) { th_io_error(ctx, THERR_MALLOC, "Error allocating new node.\n"); return NULL; } // Get hash value for (pos = 0, i = 0; i < TH_MD5HASH_LENGTH; i++, pos += 2) { unsigned int tmpu; sscanf(&line[pos], "%2x", &tmpu); node->hash[i] = tmpu; } // Get playtimes th_findnext(line, &pos); if (line[pos] != '=') { th_io_error(ctx, THERR_INVALID_DATA, "'=' expected on column #%d.\n", pos); goto error; } // First playtime is after '=' savePos = ++pos; tmpLen = strlen(line); // Get number of sub-tune lengths isOK = TRUE; while (pos < tmpLen && isOK) { th_findnext(line, &pos); if (sidlib_sldb_gettime(line, &pos) >= 0) node->nlengths++; else isOK = FALSE; } // Allocate memory for lengths if (node->nlengths == 0) goto error; node->lengths = (int *) th_calloc(node->nlengths, sizeof(int)); if (node->lengths == NULL) { th_io_error(ctx, THERR_MALLOC, "Could not allocate memory for node.\n"); goto error; } // Read lengths in for (i = 0, pos = savePos, isOK = TRUE; pos < tmpLen && i < node->nlengths && isOK; i++) { int l; th_findnext(line, &pos); l = sidlib_sldb_gettime(line, &pos); if (l >= 0) node->lengths[i] = l; else isOK = FALSE; } return node; error: sidlib_sldb_node_free(node); return NULL; } int sidlib_sldb_new(SIDLibSLDB **pdbh) { SIDLibSLDB *dbh; if ((dbh = *pdbh = (SIDLibSLDB *) th_malloc0(sizeof(SIDLibSLDB))) == NULL) return THERR_MALLOC; return THERR_OK; } // Read SLDB database to memory // int sidlib_sldb_read(th_ioctx *ctx, SIDLibSLDB *dbh) { char *line = NULL; if ((line = th_malloc(SIDLIB_BUFFER_SIZE)) == NULL) { return th_io_error(ctx, THERR_MALLOC, "Error allocating temporary data buffer of %d bytes.\n", SIDLIB_BUFFER_SIZE); } while (thfgets(line, SIDLIB_BUFFER_SIZE, ctx) != NULL) { SIDLibSLDBNode *tmnode; size_t pos = 0; ctx->line++; th_findnext(line, &pos); // Check if it is datafield if (th_isxdigit(line[pos])) { // Check the length of the hash int hashLen; for (hashLen = 0; line[pos] && th_isxdigit(line[pos]); hashLen++, pos++); if (hashLen != TH_MD5HASH_LENGTH_CH) { th_io_error(ctx, THERR_INVALID_DATA, "Invalid MD5-hash in SongLengthDB file '%s' line #%d:\n%s\n", ctx->filename, ctx->line, line); } else { // Parse and add node to db if ((tmnode = sidlib_sldb_parse_entry(ctx, line)) != NULL) { th_llist_append_node((th_llist_t **) &dbh->nodes, (th_llist_t *) tmnode); } else { th_io_error(ctx, THERR_INVALID_DATA, "Invalid entry in SongLengthDB file '%s' line #%d:\n%s\n", ctx->filename, ctx->line, line); } } } else if (line[pos] != ';' && line[pos] != '[' && line[pos] != 0) { th_io_error(ctx, THERR_INVALID_DATA, "Invalid line in SongLengthDB file '%s' line #%d:\n%s\n", ctx->filename, ctx->line, line); } } th_free(line); return THERR_OK; } // Compare two given MD5-hashes. // Return: 0 if equal // negative if hash1 < hash2 // positive if hash1 > hash2 // static int sidlib_sldb_compare_hash(th_md5hash_t hash1, th_md5hash_t hash2) { int i, delta; for (i = delta = 0; i < TH_MD5HASH_LENGTH && !delta; i++) delta = hash1[i] - hash2[i]; return delta; } // Compare two nodes. // We assume here that we never ever get NULL-pointers. static int sidlib_sldb_compare_nodes(const void *node1, const void *node2) { return sidlib_sldb_compare_hash( (*(SIDLibSLDBNode **) node1)->hash, (*(SIDLibSLDBNode **) node2)->hash); } // (Re)create index // int sidlib_sldb_build_index(SIDLibSLDB *dbh) { // Free old index th_free_r(&(dbh->pindex)); // Get size of db dbh->nnodes = th_llist_length((th_llist_t *) dbh->nodes); // Check number of nodes if (dbh->nnodes > 0) { SIDLibSLDBNode *node; size_t i; // Allocate memory for index-table dbh->pindex = (SIDLibSLDBNode **) th_malloc(sizeof(SIDLibSLDBNode *) * dbh->nnodes); if (dbh->pindex == NULL) return THERR_MALLOC; // Get node-pointers to table for (i = 0, node = dbh->nodes; node != NULL && i < dbh->nnodes; node = (SIDLibSLDBNode *) node->node.next) dbh->pindex[i++] = node; // Sort the indexes qsort(dbh->pindex, dbh->nnodes, sizeof(SIDLibSLDBNode *), sidlib_sldb_compare_nodes); } return THERR_OK; } // Free a given song-length database // void sidlib_sldb_free(SIDLibSLDB *dbh) { if (dbh != NULL) { th_llist_free_func((th_llist_t *) dbh->nodes, (void (*)(void *)) sidlib_sldb_node_free); dbh->nodes = NULL; dbh->nnodes = 0; th_free_r(&dbh->pindex); th_free(dbh); } } SIDLibSLDBNode *sidlib_sldb_get_by_hash(SIDLibSLDB *dbh, th_md5hash_t hash) { SIDLibSLDBNode keyItem, *key, **item; memcpy(&keyItem.hash, hash, sizeof(th_md5hash_t)); key = &keyItem; item = bsearch(&key, dbh->pindex, dbh->nnodes, sizeof(dbh->pindex[0]), sidlib_sldb_compare_nodes); return (item != NULL) ? *item : NULL; }