2009-08-30 03:15:43 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <inttypes.h>
|
2011-10-30 16:05:04 +00:00
|
|
|
#include "platform.h"
|
2009-08-30 03:15:43 +00:00
|
|
|
|
|
|
|
#include "metadata.h"
|
|
|
|
#include "metadata_common.h"
|
|
|
|
#include "metadata_parsers.h"
|
|
|
|
#include "rbunicode.h"
|
2011-04-27 17:06:52 +00:00
|
|
|
#include "string-extra.h"
|
2009-08-30 03:15:43 +00:00
|
|
|
|
2011-08-07 20:01:04 +00:00
|
|
|
/* NOTE: This file was modified to work properly with the new nsf codec based
|
|
|
|
on Game_Music_Emu */
|
|
|
|
|
2011-04-27 16:46:27 +00:00
|
|
|
struct NESM_HEADER
|
2009-08-30 03:15:43 +00:00
|
|
|
{
|
2011-04-27 16:46:27 +00:00
|
|
|
uint32_t nHeader;
|
|
|
|
uint8_t nHeaderExtra;
|
|
|
|
uint8_t nVersion;
|
|
|
|
uint8_t nTrackCount;
|
|
|
|
uint8_t nInitialTrack;
|
|
|
|
uint16_t nLoadAddress;
|
|
|
|
uint16_t nInitAddress;
|
|
|
|
uint16_t nPlayAddress;
|
|
|
|
uint8_t szGameTitle[32];
|
|
|
|
uint8_t szArtist[32];
|
|
|
|
uint8_t szCopyright[32];
|
|
|
|
uint16_t nSpeedNTSC;
|
|
|
|
uint8_t nBankSwitch[8];
|
|
|
|
uint16_t nSpeedPAL;
|
|
|
|
uint8_t nNTSC_PAL;
|
|
|
|
uint8_t nExtraChip;
|
|
|
|
uint8_t nExpansion[4];
|
|
|
|
} __attribute__((packed));
|
|
|
|
|
|
|
|
struct NSFE_INFOCHUNK
|
|
|
|
{
|
|
|
|
uint16_t nLoadAddress;
|
|
|
|
uint16_t nInitAddress;
|
|
|
|
uint16_t nPlayAddress;
|
|
|
|
uint8_t nIsPal;
|
|
|
|
uint8_t nExt;
|
|
|
|
uint8_t nTrackCount;
|
|
|
|
uint8_t nStartingTrack;
|
|
|
|
} __attribute__((packed));
|
2009-08-30 03:15:43 +00:00
|
|
|
|
|
|
|
|
2011-04-27 16:46:27 +00:00
|
|
|
#define CHAR4_CONST(a, b, c, d) FOURCC(a, b, c, d)
|
2011-04-28 02:56:28 +00:00
|
|
|
#define CHUNK_INFO 0x0001
|
|
|
|
#define CHUNK_DATA 0x0002
|
|
|
|
#define CHUNK_NEND 0x0004
|
|
|
|
#define CHUNK_plst 0x0008
|
|
|
|
#define CHUNK_time 0x0010
|
|
|
|
#define CHUNK_fade 0x0020
|
|
|
|
#define CHUNK_tlbl 0x0040
|
|
|
|
#define CHUNK_auth 0x0080
|
|
|
|
#define CHUNK_BANK 0x0100
|
2011-04-27 16:46:27 +00:00
|
|
|
|
|
|
|
static bool parse_nsfe(int fd, struct mp3entry *id3)
|
|
|
|
{
|
2011-04-28 02:56:28 +00:00
|
|
|
unsigned int chunks_found = 0;
|
|
|
|
long track_count = 0;
|
|
|
|
long playlist_count = 0;
|
2011-04-27 16:46:27 +00:00
|
|
|
|
|
|
|
struct NSFE_INFOCHUNK info;
|
|
|
|
memset(&info, 0, sizeof(struct NSFE_INFOCHUNK));
|
|
|
|
|
|
|
|
/* default values */
|
|
|
|
info.nTrackCount = 1;
|
2011-08-07 20:01:04 +00:00
|
|
|
id3->length = 150 * 1000;
|
2011-04-27 16:46:27 +00:00
|
|
|
|
|
|
|
/* begin reading chunks */
|
2011-04-28 02:56:28 +00:00
|
|
|
while (!(chunks_found & CHUNK_NEND))
|
2009-08-30 03:15:43 +00:00
|
|
|
{
|
2011-04-28 02:56:28 +00:00
|
|
|
uint32_t chunk_size, chunk_type;
|
2011-04-27 16:46:27 +00:00
|
|
|
|
|
|
|
if (read_uint32le(fd, &chunk_size) != (int)sizeof(uint32_t))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (read_uint32be(fd, &chunk_type) != (int)sizeof(uint32_t))
|
2009-08-30 03:52:11 +00:00
|
|
|
return false;
|
2009-08-30 03:15:43 +00:00
|
|
|
|
2011-04-27 16:46:27 +00:00
|
|
|
switch (chunk_type)
|
|
|
|
{
|
2011-04-28 02:56:28 +00:00
|
|
|
/* first three types are mandatory (but don't worry about NEND
|
|
|
|
anyway) */
|
2011-04-27 16:46:27 +00:00
|
|
|
case CHAR4_CONST('I', 'N', 'F', 'O'):
|
|
|
|
{
|
2011-04-28 02:56:28 +00:00
|
|
|
if (chunks_found & CHUNK_INFO)
|
|
|
|
return false; /* only one info chunk permitted */
|
2011-04-27 16:46:27 +00:00
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
chunks_found |= CHUNK_INFO;
|
2011-04-27 16:46:27 +00:00
|
|
|
|
|
|
|
/* minimum size */
|
|
|
|
if (chunk_size < 8)
|
|
|
|
return false;
|
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
ssize_t size = MIN(sizeof(struct NSFE_INFOCHUNK), chunk_size);
|
2011-04-27 16:46:27 +00:00
|
|
|
|
|
|
|
if (read(fd, &info, size) != size)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (size >= 9)
|
2011-04-28 02:56:28 +00:00
|
|
|
track_count = info.nTrackCount;
|
2011-04-27 16:46:27 +00:00
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
chunk_size -= size;
|
2011-04-27 16:46:27 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
case CHAR4_CONST('D', 'A', 'T', 'A'):
|
2011-04-27 16:46:27 +00:00
|
|
|
{
|
2011-04-28 02:56:28 +00:00
|
|
|
if (!(chunks_found & CHUNK_INFO))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (chunks_found & CHUNK_DATA)
|
|
|
|
return false; /* only one may exist */
|
|
|
|
|
|
|
|
if (chunk_size < 1)
|
2011-04-27 16:46:27 +00:00
|
|
|
return false;
|
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
chunks_found |= CHUNK_DATA;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case CHAR4_CONST('N', 'E', 'N', 'D'):
|
|
|
|
{
|
|
|
|
/* just end parsing regardless of whether or not this really is the
|
|
|
|
last chunk/data (but it _should_ be) */
|
|
|
|
chunks_found |= CHUNK_NEND;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remaining types are optional */
|
|
|
|
|
|
|
|
case CHAR4_CONST('a', 'u', 't', 'h'):
|
|
|
|
{
|
|
|
|
if (chunks_found & CHUNK_auth)
|
|
|
|
return false; /* only one may exist */
|
|
|
|
|
|
|
|
chunks_found |= CHUNK_auth;
|
|
|
|
|
2011-04-27 16:46:27 +00:00
|
|
|
/* szGameTitle, szArtist, szCopyright */
|
|
|
|
char ** const ar[] = { &id3->title, &id3->artist, &id3->album };
|
|
|
|
|
|
|
|
char *p = id3->id3v2buf;
|
|
|
|
long buf_rem = sizeof (id3->id3v2buf);
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAYLEN(ar) && chunk_size && buf_rem; i++)
|
|
|
|
{
|
|
|
|
long len = read_string(fd, p, buf_rem, '\0', chunk_size);
|
|
|
|
|
|
|
|
if (len < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
*ar[i] = p;
|
|
|
|
p += len;
|
|
|
|
buf_rem -= len;
|
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
if (chunk_size >= (uint32_t)len)
|
2011-04-27 16:46:27 +00:00
|
|
|
chunk_size -= len;
|
|
|
|
else
|
|
|
|
chunk_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case CHAR4_CONST('p', 'l', 's', 't'):
|
|
|
|
{
|
2011-04-28 02:56:28 +00:00
|
|
|
if (chunks_found & CHUNK_plst)
|
|
|
|
return false; /* only one may exist */
|
|
|
|
|
|
|
|
chunks_found |= CHUNK_plst;
|
2011-04-27 16:46:27 +00:00
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
/* each byte is the index of one track */
|
|
|
|
playlist_count = chunk_size;
|
2011-04-27 16:46:27 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
case CHAR4_CONST('t', 'i', 'm', 'e'):
|
|
|
|
case CHAR4_CONST('f', 'a', 'd', 'e'):
|
|
|
|
case CHAR4_CONST('t', 'l', 'b', 'l'): /* we unfortunately can't use these anyway */
|
2011-04-27 16:46:27 +00:00
|
|
|
{
|
2011-04-28 02:56:28 +00:00
|
|
|
/* don't care how many of these there are even though there should
|
|
|
|
be only one */
|
|
|
|
if (!(chunks_found & CHUNK_INFO))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
case CHAR4_CONST('B', 'A', 'N', 'K'):
|
2011-04-27 16:46:27 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: /* unknown chunk */
|
|
|
|
{
|
|
|
|
/* check the first byte */
|
|
|
|
chunk_type = (uint8_t)chunk_type;
|
|
|
|
|
|
|
|
/* chunk is vital... don't continue */
|
|
|
|
if(chunk_type >= 'A' && chunk_type <= 'Z')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/* otherwise, just skip it */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} /* end switch */
|
2011-04-28 02:56:28 +00:00
|
|
|
|
|
|
|
lseek(fd, chunk_size, SEEK_CUR);
|
2011-04-27 16:46:27 +00:00
|
|
|
} /* end while */
|
|
|
|
|
2011-04-28 02:56:28 +00:00
|
|
|
if (track_count | playlist_count)
|
|
|
|
id3->length = MAX(track_count, playlist_count)*1000;
|
|
|
|
|
2011-08-07 20:01:04 +00:00
|
|
|
/* Single subtrack files will be treated differently
|
|
|
|
by gme's nsf codec */
|
|
|
|
if (id3->length <= 1000) id3->length = 150 * 1000;
|
|
|
|
|
2011-04-27 16:46:27 +00:00
|
|
|
/*
|
|
|
|
* if we exited the while loop without a 'return', we must have hit an NEND
|
|
|
|
* chunk if this is the case, the file was layed out as it was expected.
|
|
|
|
* now.. make sure we found both an info chunk, AND a data chunk... since
|
|
|
|
* these are minimum requirements for a valid NSFE file
|
|
|
|
*/
|
2011-04-28 02:56:28 +00:00
|
|
|
return (chunks_found & (CHUNK_INFO | CHUNK_DATA)) ==
|
|
|
|
(CHUNK_INFO | CHUNK_DATA);
|
2011-04-27 16:46:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool parse_nesm(int fd, struct mp3entry *id3)
|
|
|
|
{
|
|
|
|
struct NESM_HEADER hdr;
|
|
|
|
char *p = id3->id3v2buf;
|
|
|
|
|
|
|
|
lseek(fd, 0, SEEK_SET);
|
|
|
|
if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr))
|
|
|
|
return false;
|
2009-08-30 03:15:43 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Length */
|
2011-08-07 20:01:04 +00:00
|
|
|
id3->length = (hdr.nTrackCount > 1 ? hdr.nTrackCount : 150) * 1000;
|
2011-04-27 03:08:23 +00:00
|
|
|
|
2009-08-30 03:15:43 +00:00
|
|
|
/* Title */
|
|
|
|
id3->title = p;
|
2011-04-27 16:46:27 +00:00
|
|
|
p += strlcpy(p, hdr.szGameTitle, 32) + 1;
|
2009-08-30 03:15:43 +00:00
|
|
|
|
|
|
|
/* Artist */
|
|
|
|
id3->artist = p;
|
2011-04-27 16:46:27 +00:00
|
|
|
p += strlcpy(p, hdr.szArtist, 32) + 1;
|
2009-08-30 03:15:43 +00:00
|
|
|
|
|
|
|
/* Copyright (per codec) */
|
|
|
|
id3->album = p;
|
2011-04-27 16:46:27 +00:00
|
|
|
strlcpy(p, hdr.szCopyright, 32);
|
2009-08-30 03:15:43 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-04-27 16:46:27 +00:00
|
|
|
bool get_nsf_metadata(int fd, struct mp3entry* id3)
|
|
|
|
{
|
|
|
|
uint32_t nsf_type;
|
|
|
|
if (lseek(fd, 0, SEEK_SET) < 0 ||
|
|
|
|
read_uint32be(fd, &nsf_type) != (int)sizeof(nsf_type))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
id3->vbr = false;
|
|
|
|
id3->filesize = filesize(fd);
|
|
|
|
/* we only render 16 bits, 44.1KHz, Mono */
|
|
|
|
id3->bitrate = 706;
|
|
|
|
id3->frequency = 44100;
|
|
|
|
|
|
|
|
if (nsf_type == CHAR4_CONST('N', 'S', 'F', 'E'))
|
|
|
|
return parse_nsfe(fd, id3);
|
|
|
|
else if (nsf_type == CHAR4_CONST('N', 'E', 'S', 'M'))
|
|
|
|
return parse_nesm(fd, id3);
|
|
|
|
|
|
|
|
/* not a valid format*/
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|