summaryrefslogtreecommitdiffstats
path: root/apps/metadata/nsf.c
blob: 58fc038b42470d1269e3240b7aac386954429efd (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
#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"

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)

static bool parse_nsfe(int fd, struct mp3entry *id3)
{
    bool info_found = false;
    bool end_found = false;
    bool data_found = false;

    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 (!end_found)
    {
        int32_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)
        {
        case CHAR4_CONST('I', 'N', 'F', 'O'):
        {
            /* only one info chunk permitted */
            if (info_found)
                return false;

            info_found = true;

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

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

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

            if (size >= 9)
                id3->length = info.nTrackCount*1000;

            lseek(fd, chunk_size - size, SEEK_CUR);
            break;
            }

        case CHAR4_CONST('a', 'u', 't', 'h'):
        {
            if (!info_found)
                return false;

            /* 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 >= len)
                    chunk_size -= len;
                else
                    chunk_size = 0;
            }

            lseek(fd, chunk_size, SEEK_CUR);
            break;
            }


        case CHAR4_CONST('D', 'A', 'T', 'A'):
            if (chunk_size < 1)
                return false;

            data_found = true;
            /* fall through */
        case CHAR4_CONST('f', 'a', 'd', 'e'):
        case CHAR4_CONST('t', 'i', 'm', 'e'):
        case CHAR4_CONST('B', 'A', 'N', 'K'):
        case CHAR4_CONST('p', 'l', 's', 't'):
        case CHAR4_CONST('t', 'l', 'b', 'l'): /* we unfortunately can't use these anyway */
        {
            if (!info_found)
                return false;

            lseek(fd, chunk_size, SEEK_CUR);
            break;
            }

        case CHAR4_CONST('N', 'E', 'N', 'D'):
        {
            end_found = true;
            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 */
            lseek(fd, chunk_size, SEEK_CUR);
            break;
            }
        } /* end switch */
    } /* end while */

    /*
     * 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 info_found && data_found;
}

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