Mercurial > audlegacy-plugins
view src/sid/xs_length.c @ 3081:4e3712e142b4
gio: merge experimental GIO plugin. not everything is implemented yet (getc/ungetc).
author | William Pitcock <nenolod@atheme.org> |
---|---|
date | Wed, 29 Apr 2009 19:26:49 -0500 |
parents | 1223e8510d8a |
children |
line wrap: on
line source
/* XMMS-SID - SIDPlay input plugin for X MultiMedia System (XMMS) Get song length from SLDB for PSID/RSID files Programmed and designed by Matti 'ccr' Hamalainen <ccr@tnsp.org> (C) Copyright 1999-2007 Tecnic Software productions (TNSP) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "xs_length.h" #include "xs_support.h" #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> /* Free memory allocated for given SLDB node */ static void xs_sldb_node_free(sldb_node_t *node) { if (node) { /* Nothing much to do here ... */ g_free(node->lengths); g_free(node); } } /* Insert given node to db linked list */ static void xs_sldb_node_insert(xs_sldb_t *db, sldb_node_t *node) { assert(db); if (db->nodes) { /* The first node's prev points to last node */ LPREV = db->nodes->prev; /* New node's prev = Previous last node */ db->nodes->prev->next = node; /* Previous last node's next = New node */ db->nodes->prev = node; /* New last node = New node */ LNEXT = NULL; /* But next is NULL! */ } else { db->nodes = node; /* First node ... */ LPREV = node; /* ... it's also last */ LNEXT = NULL; /* But next is NULL! */ } } /* Parse a time-entry in SLDB format */ static gint xs_sldb_gettime(gchar *str, size_t *pos) { gint result, tmp; /* Check if it starts with a digit */ if (isdigit(str[*pos])) { /* Get minutes-field */ result = 0; while (isdigit(str[*pos])) result = (result * 10) + (str[(*pos)++] - '0'); result *= 60; /* Check the field separator char */ if (str[*pos] == ':') { /* Get seconds-field */ (*pos)++; tmp = 0; while (isdigit(str[*pos])) { tmp = (tmp * 10) + (str[(*pos)++] - '0'); } result += tmp; } else result = -2; } else result = -1; /* Ignore and skip the possible attributes */ while (str[*pos] && !isspace(str[*pos])) (*pos)++; return result; } /* Parse one SLDB definition line, return SLDB node */ sldb_node_t * xs_sldb_read_entry(gchar *inLine) { size_t linePos; gint i; gboolean isOK; sldb_node_t *tmnode; /* Allocate new node */ tmnode = (sldb_node_t *) g_malloc0(sizeof(sldb_node_t)); if (!tmnode) { xs_error("Error allocating new node. Fatal error.\n"); return NULL; } /* Get hash value */ linePos = 0; for (i = 0; i < XS_MD5HASH_LENGTH; i++, linePos += 2) { gint tmpu; sscanf(&inLine[linePos], "%2x", &tmpu); tmnode->md5Hash[i] = tmpu; } /* Get playtimes */ if (inLine[linePos] != 0) { if (inLine[linePos] != '=') { xs_error("'=' expected on column #%d.\n", linePos); xs_sldb_node_free(tmnode); return NULL; } else { size_t tmpLen, savePos; /* First playtime is after '=' */ savePos = ++linePos; tmpLen = strlen(inLine); /* Get number of sub-tune lengths */ isOK = TRUE; while ((linePos < tmpLen) && isOK) { xs_findnext(inLine, &linePos); if (xs_sldb_gettime(inLine, &linePos) >= 0) tmnode->nlengths++; else isOK = FALSE; } /* Allocate memory for lengths */ if (tmnode->nlengths > 0) { tmnode->lengths = (gint *) g_malloc0(tmnode->nlengths * sizeof(gint)); if (!tmnode->lengths) { xs_error("Could not allocate memory for node.\n"); xs_sldb_node_free(tmnode); return NULL; } } else { xs_sldb_node_free(tmnode); return NULL; } /* Read lengths in */ i = 0; linePos = savePos; isOK = TRUE; while ((linePos < tmpLen) && (i < tmnode->nlengths) && isOK) { gint l; xs_findnext(inLine, &linePos); l = xs_sldb_gettime(inLine, &linePos); if (l >= 0) tmnode->lengths[i] = l; else isOK = FALSE; i++; } if (!isOK) { xs_sldb_node_free(tmnode); return NULL; } else return tmnode; } } return NULL; } /* Read database to memory */ gint xs_sldb_read(xs_sldb_t *db, const gchar *dbFilename) { FILE *inFile; gchar inLine[XS_BUF_SIZE]; size_t lineNum; sldb_node_t *tmnode; assert(db); /* Try to open the file */ if ((inFile = fopen(dbFilename, "ra")) == NULL) { xs_error("Could not open SongLengthDB '%s'\n", dbFilename); return -1; } /* Read and parse the data */ lineNum = 0; while (fgets(inLine, XS_BUF_SIZE, inFile) != NULL) { size_t linePos = 0; lineNum++; xs_findnext(inLine, &linePos); /* Check if it is datafield */ if (isxdigit(inLine[linePos])) { /* Check the length of the hash */ gint hashLen; for (hashLen = 0; inLine[linePos] && isxdigit(inLine[linePos]); hashLen++, linePos++); if (hashLen != XS_MD5HASH_LENGTH_CH) { xs_error("Invalid MD5-hash in SongLengthDB file '%s' line #%d!\n", dbFilename, lineNum); } else { /* Parse and add node to db */ if ((tmnode = xs_sldb_read_entry(inLine)) != NULL) { xs_sldb_node_insert(db, tmnode); } else { xs_error("Invalid entry in SongLengthDB file '%s' line #%d!\n", dbFilename, lineNum); } } } else if ((inLine[linePos] != ';') && (inLine[linePos] != '[') && (inLine[linePos] != 0)) { xs_error("Invalid line in SongLengthDB file '%s' line #%d\n", dbFilename, lineNum); } } /* Close the file */ fclose(inFile); return 0; } /* Compare two given MD5-hashes. * Return: 0 if equal * negative if testHash1 < testHash2 * positive if testHash1 > testHash2 */ static gint xs_sldb_cmphash(xs_md5hash_t testHash1, xs_md5hash_t testHash2) { gint i, d; /* Compute difference of hashes */ for (i = 0, d = 0; (i < XS_MD5HASH_LENGTH) && !d; i++) d = (testHash1[i] - testHash2[i]); return d; } /* Compare two nodes */ static gint xs_sldb_cmp(const void *node1, const void *node2) { /* We assume here that we never ever get NULL-pointers or similar */ return xs_sldb_cmphash( (*(sldb_node_t **) node1)->md5Hash, (*(sldb_node_t **) node2)->md5Hash); } /* (Re)create index */ gint xs_sldb_index(xs_sldb_t * db) { sldb_node_t *pCurr; size_t i; assert(db); /* Free old index */ if (db->pindex) { g_free(db->pindex); db->pindex = NULL; } /* Get size of db */ pCurr = db->nodes; db->n = 0; while (pCurr) { db->n++; pCurr = pCurr->next; } /* Check number of nodes */ if (db->n > 0) { /* Allocate memory for index-table */ db->pindex = (sldb_node_t **) g_malloc(sizeof(sldb_node_t *) * db->n); if (!db->pindex) return -1; /* Get node-pointers to table */ i = 0; pCurr = db->nodes; while (pCurr && (i < db->n)) { db->pindex[i++] = pCurr; pCurr = pCurr->next; } /* Sort the indexes */ qsort(db->pindex, db->n, sizeof(sldb_node_t *), xs_sldb_cmp); } return 0; } /* Free a given song-length database */ void xs_sldb_free(xs_sldb_t * db) { sldb_node_t *pCurr, *next; if (!db) return; /* Free the memory allocated for nodes */ pCurr = db->nodes; while (pCurr) { next = pCurr->next; xs_sldb_node_free(pCurr); pCurr = next; } db->nodes = NULL; /* Free memory allocated for index */ if (db->pindex) { g_free(db->pindex); db->pindex = NULL; } /* Free structure */ db->n = 0; g_free(db); } /* Compute md5hash of given SID-file */ typedef struct { gchar magicID[4]; /* "PSID" / "RSID" magic identifier */ guint16 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 */ guint32 speed; /* Speed */ gchar sidName[32]; /* Descriptive text-fields, ASCIIZ */ gchar sidAuthor[32]; gchar sidCopyright[32]; } psidv1_header_t; typedef struct { guint16 flags; /* Flags */ guint8 startPage, pageLength; guint16 reserved; } psidv2_header_t; static gint xs_get_sid_hash(const gchar *filename, xs_md5hash_t hash) { xs_file_t *inFile; xs_md5state_t inState; psidv1_header_t psidH; psidv2_header_t psidH2; guint8 *songData; guint8 ib8[2], i8; gint index, result; /* Try to open the file */ if ((inFile = xs_fopen(filename, "rb")) == NULL) return -1; /* Read PSID header in */ xs_fread(psidH.magicID, sizeof(psidH.magicID), 1, inFile); if (strncmp(psidH.magicID, "PSID", 4) && strncmp(psidH.magicID, "RSID", 4)) { xs_fclose(inFile); xs_error("Not a PSID or RSID file '%s'\n", filename); return -2; } psidH.version = xs_fread_be16(inFile); psidH.dataOffset = xs_fread_be16(inFile); psidH.loadAddress = xs_fread_be16(inFile); psidH.initAddress = xs_fread_be16(inFile); psidH.playAddress = xs_fread_be16(inFile); psidH.nSongs = xs_fread_be16(inFile); psidH.startSong = xs_fread_be16(inFile); psidH.speed = xs_fread_be32(inFile); xs_fread(psidH.sidName, sizeof(gchar), sizeof(psidH.sidName), inFile); xs_fread(psidH.sidAuthor, sizeof(gchar), sizeof(psidH.sidAuthor), inFile); xs_fread(psidH.sidCopyright, sizeof(gchar), sizeof(psidH.sidCopyright), inFile); if (xs_feof(inFile) || xs_ferror(inFile)) { xs_fclose(inFile); xs_error("Error reading SID file header from '%s'\n", filename); return -4; } /* Check if we need to load PSIDv2NG header ... */ psidH2.flags = 0; /* Just silence a stupid gcc warning */ if (psidH.version == 2) { /* Yes, we need to */ psidH2.flags = xs_fread_be16(inFile); psidH2.startPage = xs_fgetc(inFile); psidH2.pageLength = xs_fgetc(inFile); psidH2.reserved = xs_fread_be16(inFile); } /* Allocate buffer */ songData = (guint8 *) g_malloc(XS_SIDBUF_SIZE * sizeof(guint8)); if (!songData) { xs_fclose(inFile); xs_error("Error allocating temp data buffer for file '%s'\n", filename); return -3; } /* Read data to buffer */ result = xs_fread(songData, sizeof(guint8), XS_SIDBUF_SIZE, inFile); xs_fclose(inFile); /* Initialize and start MD5-hash calculation */ xs_md5_init(&inState); if (psidH.loadAddress == 0) { /* Strip load address (2 first bytes) */ xs_md5_append(&inState, &songData[2], result - 2); } else { /* Append "as is" */ xs_md5_append(&inState, songData, result); } /* Free buffer */ g_free(songData); /* Append header data to hash */ #define XSADDHASH(QDATAB) do { \ ib8[0] = (QDATAB & 0xff); \ ib8[1] = (QDATAB >> 8); \ xs_md5_append(&inState, (guint8 *) &ib8, sizeof(ib8)); \ } while (0) XSADDHASH(psidH.initAddress); XSADDHASH(psidH.playAddress); XSADDHASH(psidH.nSongs); #undef XSADDHASH /* Append song speed data to hash */ i8 = 0; for (index = 0; (index < psidH.nSongs) && (index < 32); index++) { i8 = (psidH.speed & (1 << index)) ? 60 : 0; xs_md5_append(&inState, &i8, sizeof(i8)); } /* Rest of songs (more than 32) */ for (index = 32; index < psidH.nSongs; index++) { xs_md5_append(&inState, &i8, sizeof(i8)); } /* PSIDv2NG specific */ if (psidH.version == 2) { /* SEE SIDPLAY HEADERS FOR INFO */ i8 = (psidH2.flags >> 2) & 3; if (i8 == 2) xs_md5_append(&inState, &i8, sizeof(i8)); } /* Calculate the hash */ xs_md5_finish(&inState, hash); return 0; } /* Get node from db index via binary search */ sldb_node_t *xs_sldb_get(xs_sldb_t *db, const gchar *filename) { sldb_node_t keyItem, *key, **item; /* Check the database pointers */ if (!db || !db->nodes || !db->pindex) return NULL; /* Get the hash and then look up from db */ if (xs_get_sid_hash(filename, keyItem.md5Hash) == 0) { key = &keyItem; item = bsearch(&key, db->pindex, db->n, sizeof(db->pindex[0]), xs_sldb_cmp); if (item) return *item; else return NULL; } else return NULL; }