/* * This file is part of libextractor. * Copyright (C) 2007, 2009, 2012 Toni Ruottu and Christian Grothoff * * libextractor 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 3, or (at your * option) any later version. * * libextractor 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 libextractor; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ /** * @file plugins/nsfe_extractor.c * @brief plugin to support Nes Sound Format files * @author Toni Ruottu * @author Christian Grothoff */ #include "platform.h" #include "extractor.h" #include "convert.h" /* television system flags */ #define PAL_FLAG 0x01 #define DUAL_FLAG 0x02 /* sound chip flags */ #define VRCVI_FLAG 0x01 #define VRCVII_FLAG 0x02 #define FDS_FLAG 0x04 #define MMC5_FLAG 0x08 #define NAMCO_FLAG 0x10 #define SUNSOFT_FLAG 0x20 /** * "Header" of an NSFE file. */ struct header { char magicid[4]; }; /** * Read an unsigned integer at the current offset. * * @param data input data to parse * @return parsed integer */ static uint32_t nsfeuint (const char *data) { uint32_t value = 0; int i; for (i = 3; i > 0; i--) { value += (unsigned char) data[i]; value *= 0x100; } value += (unsigned char) data[0]; return value; } /** * Copy string starting at 'data' with at most * 'size' bytes. (strndup). * * @param data input data to copy * @param size number of bytes in 'data' * @return copy of the string at data */ static char * nsfestring (const char *data, size_t size) { char *s; size_t length; length = 0; while ( (length < size) && (data[length] != '\0') ) length++; if (NULL == (s = malloc (length + 1))) return NULL; memcpy (s, data, length); s[length] = '\0'; return s; } /** * Give metadata to LE; return if 'proc' returns non-zero. * * @param s metadata value as UTF8 * @param t metadata type to use */ #define ADD(s,t) do { if (0 != ec->proc (ec->cls, "nsfe", t, EXTRACTOR_METAFORMAT_UTF8, "text/plain", s, strlen (s) + 1)) return 1; } while (0) /** * Give metadata to LE; return if 'proc' returns non-zero. * * @param s metadata value as UTF8, free at the end * @param t metadata type to use */ #define ADDF(s,t) do { if (0 != ec->proc (ec->cls, "nsfe", t, EXTRACTOR_METAFORMAT_UTF8, "text/plain", s, strlen (s) + 1)) { free (s); return 1; } free (s); } while (0) /** * Format of an 'INFO' chunk. Last two bytes are optional. */ struct infochunk { /** * Unknown. */ uint16_t loadaddr; /** * Unknown. */ uint16_t initaddr; /** * Unknown. */ uint16_t playaddr; /** * TV encoding flags. */ char tvflags; /** * Chipset encoding flags. */ char chipflags; /** * Number of songs. */ unsigned char songs; /** * Starting song. */ unsigned char firstsong; }; /** * Extract data from the INFO chunk. * * @param ec extraction context * @param size number of bytes in INFO chunk * @return 0 to continue extrating */ static int info_extract (struct EXTRACTOR_ExtractContext *ec, uint32_t size) { void *data; const struct infochunk *ichunk; char songs[32]; if (size < 8) return 0; if ((ssize_t) size > ec->read (ec->cls, &data, size)) return 1; ichunk = data; if (0 != (ichunk->tvflags & DUAL_FLAG)) { ADD ("PAL/NTSC", EXTRACTOR_METATYPE_BROADCAST_TELEVISION_SYSTEM); } else { if (0 != (ichunk->tvflags & PAL_FLAG)) ADD ("PAL", EXTRACTOR_METATYPE_BROADCAST_TELEVISION_SYSTEM); else ADD ("NTSC", EXTRACTOR_METATYPE_BROADCAST_TELEVISION_SYSTEM); } if (0 != (ichunk->chipflags & VRCVI_FLAG)) ADD ("VRCVI", EXTRACTOR_METATYPE_TARGET_ARCHITECTURE); if (0 != (ichunk->chipflags & VRCVII_FLAG)) ADD ("VRCVII", EXTRACTOR_METATYPE_TARGET_ARCHITECTURE); if (0 != (ichunk->chipflags & FDS_FLAG)) ADD ("FDS Sound", EXTRACTOR_METATYPE_TARGET_ARCHITECTURE); if (0 != (ichunk->chipflags & MMC5_FLAG)) ADD ("MMC5 audio", EXTRACTOR_METATYPE_TARGET_ARCHITECTURE); if (0 != (ichunk->chipflags & NAMCO_FLAG)) ADD ("Namco 106", EXTRACTOR_METATYPE_TARGET_ARCHITECTURE); if (0 != (ichunk->chipflags & SUNSOFT_FLAG)) ADD ("Sunsoft FME-07", EXTRACTOR_METATYPE_TARGET_ARCHITECTURE); if (size < sizeof (struct infochunk)) { ADD ("1", EXTRACTOR_METATYPE_SONG_COUNT); return 0; } snprintf (songs, sizeof (songs), "%d", ichunk->songs); ADD (songs, EXTRACTOR_METATYPE_SONG_COUNT); snprintf (songs, sizeof (songs), "%d", ichunk->firstsong); ADD (songs, EXTRACTOR_METATYPE_STARTING_SONG); return 0; } /** * Extract data from the TLBL chunk. * * @param ec extraction context * @param size number of bytes in TLBL chunk * @return 0 to continue extrating */ static int tlbl_extract (struct EXTRACTOR_ExtractContext *ec, uint32_t size) { char *title; ssize_t left; size_t length; void *data; const char *cdata; if ((ssize_t) size > ec->read (ec->cls, &data, size)) return 1; cdata = data; left = size; while (left > 0) { title = nsfestring (&cdata[size - left], left); if (NULL == title) return 0; length = strlen (title) + 1; ADDF (title, EXTRACTOR_METATYPE_TITLE); left -= length; } return 0; } /** * Extract data from the AUTH chunk. * * @param ec extraction context * @param size number of bytes in AUTH chunk * @return 0 to continue extrating */ static int auth_extract (struct EXTRACTOR_ExtractContext *ec, uint32_t size) { char *album; char *artist; char *copyright; char *ripper; uint32_t left = size; void *data; const char *cdata; if (left < 1) return 0; if ((ssize_t) size > ec->read (ec->cls, &data, size)) return 1; cdata = data; album = nsfestring (&cdata[size - left], left); if (NULL != album) { left -= (strlen (album) + 1); ADDF (album, EXTRACTOR_METATYPE_ALBUM); if (left < 1) return 0; } artist = nsfestring (&cdata[size - left], left); if (NULL != artist) { left -= (strlen (artist) + 1); ADDF (artist, EXTRACTOR_METATYPE_ARTIST); if (left < 1) return 0; } copyright = nsfestring (&cdata[size - left], left); if (NULL != copyright) { left -= (strlen (copyright) + 1); ADDF (copyright, EXTRACTOR_METATYPE_COPYRIGHT); if (left < 1) return 0; } ripper = nsfestring (&cdata[size - left], left); if (NULL != ripper) ADDF (ripper, EXTRACTOR_METATYPE_RIPPER); return 0; } /** * "extract" meta data from an Extended Nintendo Sound Format file * * NSFE specification revision 2 (Sep. 3, 2003) was used, while this * piece of software was originally written. * * @param ec extraction context */ void EXTRACTOR_nsfe_extract_method (struct EXTRACTOR_ExtractContext *ec) { const struct header *head; void *data; uint64_t off; uint32_t chunksize; int ret; if ((ssize_t) sizeof (struct header) > ec->read (ec->cls, &data, sizeof (struct header))) return; head = data; if (0 != memcmp (head->magicid, "NSFE", 4)) return; if (0 != ec->proc (ec->cls, "nsfe", EXTRACTOR_METATYPE_MIMETYPE, EXTRACTOR_METAFORMAT_UTF8, "text/plain", "audio/x-nsfe", strlen ("audio/x-nsfe") + 1)) return; off = sizeof (struct header); ret = 0; while (0 == ret) { if (off != ec->seek (ec->cls, off, SEEK_SET)) break; if (8 > ec->read (ec->cls, &data, 8)) break; chunksize = nsfeuint (data); if (off + chunksize + 8LLU <= off) break; /* protect against looping */ off += 8LLU + chunksize; if (0 == memcmp (data + 4, "INFO", 4)) ret = info_extract (ec, chunksize); else if (0 == memcmp (data + 4, "auth", 4)) ret = auth_extract (ec, chunksize); else if (0 == memcmp (data + 4, "tlbl", 4)) ret = tlbl_extract (ec, chunksize); /* Ignored chunks: DATA, NEND, plst, time, fade, BANK */ } } /* end of nsfe_extractor.c */