summaryrefslogtreecommitdiffstats
path: root/apps/metadata/nsf.c
blob: 29fd8475bb559ba25b0ffe484a84d1af83376398 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>

#include "system.h"
#include "metadata.h"
#include "metadata_common.h"
#include "metadata_parsers.h"
#include "rbunicode.h"
#include "string-extra.h"

struct NESM_HEADER
{
    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));


#define CHAR4_CONST(a, b, c, d) FOURCC(a, b, c, d)
#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

static bool parse_nsfe(int fd, struct mp3entry *id3)
{
    unsigned int chunks_found = 0;
    long track_count = 0;
    long playlist_count = 0;

    struct NSFE_INFOCHUNK info;
    memset(&info, 0, sizeof(struct NSFE_INFOCHUNK));

     /* default values */
    info.nTrackCount = 1;
    id3->length = 2*1000*60;
    
    /* begin reading chunks */
    while (!(chunks_found & CHUNK_NEND))
    {
        uint32_t chunk_size, chunk_type;

        if (read_uint32le(fd, &chunk_size) != (int)sizeof(uint32_t))
            return false;

        if (read_uint32be(fd, &chunk_type) != (int)sizeof(uint32_t))
            return false;

        switch (chunk_type)
        {
        /* first three types are mandatory (but don't worry about NEND
           anyway) */
        case CHAR4_CONST('I', 'N', 'F', 'O'):
        {
            if (chunks_found & CHUNK_INFO)
                return false; /* only one info chunk permitted */

            chunks_found |= CHUNK_INFO;

            /* minimum size */
            if (chunk_size < 8)
                return false;

            ssize_t size = MIN(sizeof(struct NSFE_INFOCHUNK), chunk_size);

            if (read(fd, &info, size) != size)
                return false;

            if (size >= 9)
                track_count = info.nTrackCount;

            chunk_size -= size;
            break;
            }

        case CHAR4_CONST('D', 'A', 'T', 'A'):
        {
            if (!(chunks_found & CHUNK_INFO))
                return false;

            if (chunks_found & CHUNK_DATA)
                return false; /* only one may exist */

            if (chunk_size < 1)
                return false;

            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;

            /* 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;

                if (chunk_size >= (uint32_t)len)
                    chunk_size -= len;
                else
                    chunk_size = 0;
            }

            break;
            }

        case CHAR4_CONST('p', 'l', 's', 't'):
        {
            if (chunks_found & CHUNK_plst)
                return false; /* only one may exist */

            chunks_found |= CHUNK_plst;

            /* each byte is the index of one track */
            playlist_count = chunk_size;
            break;
            }

        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 */
        {
            /* 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'):
            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 */

        lseek(fd, chunk_size, SEEK_CUR);
    } /* end while */

    if (track_count | playlist_count)
        id3->length = MAX(track_count, playlist_count)*1000;

    /*
     * 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
     */
    return (chunks_found & (CHUNK_INFO | CHUNK_DATA)) ==
            (CHUNK_INFO | CHUNK_DATA);
}

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;

    /* Length */
    id3->length = hdr.nTrackCount*1000;

    /* Title */
    id3->title = p;
    p += strlcpy(p, hdr.szGameTitle, 32) + 1;

    /* Artist */
    id3->artist = p;
    p += strlcpy(p, hdr.szArtist, 32) + 1;

    /* Copyright (per codec) */
    id3->album = p;
    strlcpy(p, hdr.szCopyright, 32);
        
    return true;
}

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;
}