1 /* Copyright  (C) 2010-2020 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (chd_stream.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include <boolean.h>
28 
29 #include <streams/chd_stream.h>
30 #include <retro_endianness.h>
31 #include <libchdr/chd.h>
32 #include <string/stdstring.h>
33 
34 #define SECTOR_SIZE 2352
35 #define SUBCODE_SIZE 96
36 #define TRACK_PAD 4
37 
38 struct chdstream
39 {
40    chd_file *chd;
41    /* Loaded hunk */
42    uint8_t *hunkmem;
43    /* Byte offset where track data starts (after pregap) */
44    size_t track_start;
45    /* Byte offset where track data ends */
46    size_t track_end;
47    /* Byte offset of read cursor */
48    size_t offset;
49    /* Loaded hunk number */
50    int32_t hunknum;
51    /* Size of frame taken from each hunk */
52    uint32_t frame_size;
53    /* Offset of data within frame */
54    uint32_t frame_offset;
55    /* Number of frames per hunk */
56    uint32_t frames_per_hunk;
57    /* First frame of track in chd */
58    uint32_t track_frame;
59    /* Should we swap bytes? */
60    bool swab;
61 };
62 
63 typedef struct metadata
64 {
65    uint32_t frame_offset;
66    uint32_t frames;
67    uint32_t pad;
68    uint32_t extra;
69    uint32_t pregap;
70    uint32_t postgap;
71    uint32_t track;
72    char type[64];
73    char subtype[32];
74    char pgtype[32];
75    char pgsub[32];
76 } metadata_t;
77 
padding_frames(uint32_t frames)78 static uint32_t padding_frames(uint32_t frames)
79 {
80    return ((frames + TRACK_PAD - 1) & ~(TRACK_PAD - 1)) - frames;
81 }
82 
83 static bool
chdstream_get_meta(chd_file * chd,int idx,metadata_t * md)84 chdstream_get_meta(chd_file *chd, int idx, metadata_t *md)
85 {
86    char meta[256];
87    uint32_t meta_size = 0;
88    chd_error err;
89 
90    memset(md, 0, sizeof(*md));
91 
92    err = chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, idx, meta,
93          sizeof(meta), &meta_size, NULL, NULL);
94 
95    if (err == CHDERR_NONE)
96    {
97       sscanf(meta, CDROM_TRACK_METADATA2_FORMAT,
98             &md->track, md->type,
99             md->subtype, &md->frames, &md->pregap,
100             md->pgtype, md->pgsub,
101             &md->postgap);
102       md->extra = padding_frames(md->frames);
103       return true;
104    }
105 
106    err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, idx, meta,
107          sizeof(meta), &meta_size, NULL, NULL);
108 
109    if (err == CHDERR_NONE)
110    {
111       sscanf(meta, CDROM_TRACK_METADATA_FORMAT, &md->track, md->type,
112              md->subtype, &md->frames);
113       md->extra = padding_frames(md->frames);
114       return true;
115    }
116 
117    err = chd_get_metadata(chd, GDROM_TRACK_METADATA_TAG, idx, meta,
118          sizeof(meta), &meta_size, NULL, NULL);
119 
120    if (err == CHDERR_NONE)
121    {
122       sscanf(meta, GDROM_TRACK_METADATA_FORMAT, &md->track, md->type,
123              md->subtype, &md->frames, &md->pad, &md->pregap, md->pgtype,
124              md->pgsub, &md->postgap);
125       md->extra = padding_frames(md->frames);
126       return true;
127    }
128 
129    return false;
130 }
131 
132 static bool
chdstream_find_track_number(chd_file * fd,int32_t track,metadata_t * meta)133 chdstream_find_track_number(chd_file *fd, int32_t track, metadata_t *meta)
134 {
135    uint32_t i;
136    uint32_t frame_offset = 0;
137 
138    for (i = 0; true; ++i)
139    {
140       if (!chdstream_get_meta(fd, i, meta))
141          return false;
142 
143       if (track == meta->track)
144       {
145          meta->frame_offset = frame_offset;
146          return true;
147       }
148 
149       frame_offset += meta->frames + meta->extra;
150    }
151 }
152 
153 static bool
chdstream_find_special_track(chd_file * fd,int32_t track,metadata_t * meta)154 chdstream_find_special_track(chd_file *fd, int32_t track, metadata_t *meta)
155 {
156    int32_t i;
157    metadata_t iter;
158    int32_t largest_track = 0;
159    uint32_t largest_size = 0;
160 
161    for (i = 1; true; ++i)
162    {
163       if (!chdstream_find_track_number(fd, i, &iter))
164       {
165          if (track == CHDSTREAM_TRACK_LAST && i > 1)
166          {
167             *meta = iter;
168             return true;
169          }
170 
171          if (track == CHDSTREAM_TRACK_PRIMARY && largest_track != 0)
172             return chdstream_find_track_number(fd, largest_track, meta);
173 
174          return false;
175       }
176 
177       switch (track)
178       {
179          case CHDSTREAM_TRACK_FIRST_DATA:
180             if (strcmp(iter.type, "AUDIO"))
181             {
182                *meta = iter;
183                return true;
184             }
185             break;
186          case CHDSTREAM_TRACK_PRIMARY:
187             if (strcmp(iter.type, "AUDIO") && iter.frames > largest_size)
188             {
189                largest_size  = iter.frames;
190                largest_track = iter.track;
191             }
192             break;
193          default:
194             break;
195       }
196    }
197 }
198 
199 static bool
chdstream_find_track(chd_file * fd,int32_t track,metadata_t * meta)200 chdstream_find_track(chd_file *fd, int32_t track, metadata_t *meta)
201 {
202    if (track < 0)
203       return chdstream_find_special_track(fd, track, meta);
204    return chdstream_find_track_number(fd, track, meta);
205 }
206 
chdstream_open(const char * path,int32_t track)207 chdstream_t *chdstream_open(const char *path, int32_t track)
208 {
209    metadata_t meta;
210    uint32_t pregap         = 0;
211    uint8_t *hunkmem        = NULL;
212    const chd_header *hd    = NULL;
213    chdstream_t *stream     = NULL;
214    chd_file *chd           = NULL;
215    chd_error err           = chd_open(path, CHD_OPEN_READ, NULL, &chd);
216 
217    if (err != CHDERR_NONE)
218       return NULL;
219 
220    if (!chdstream_find_track(chd, track, &meta))
221       goto error;
222 
223    stream                  = (chdstream_t*)malloc(sizeof(*stream));
224    if (!stream)
225       goto error;
226 
227    stream->chd             = NULL;
228    stream->swab            = false;
229    stream->frame_size      = 0;
230    stream->frame_offset    = 0;
231    stream->frames_per_hunk = 0;
232    stream->track_frame     = 0;
233    stream->track_start     = 0;
234    stream->track_end       = 0;
235    stream->offset          = 0;
236    stream->hunkmem         = NULL;
237    stream->hunknum         = -1;
238 
239    hd                      = chd_get_header(chd);
240    hunkmem                 = (uint8_t*)malloc(hd->hunkbytes);
241    if (!hunkmem)
242       goto error;
243 
244    stream->hunkmem         = hunkmem;
245 
246    if (string_is_equal(meta.type, "MODE1_RAW"))
247       stream->frame_size   = SECTOR_SIZE;
248    else if (string_is_equal(meta.type, "MODE2_RAW"))
249       stream->frame_size   = SECTOR_SIZE;
250    else if (string_is_equal(meta.type, "AUDIO"))
251    {
252       stream->frame_size   = SECTOR_SIZE;
253       stream->swab         = true;
254    }
255    else
256       stream->frame_size   = hd->unitbytes;
257 
258    /* Only include pregap data if it was in the track file */
259    if (meta.pgtype[0] != 'V')
260       pregap               = meta.pregap;
261 
262    stream->chd             = chd;
263    stream->frames_per_hunk = hd->hunkbytes / hd->unitbytes;
264    stream->track_frame     = meta.frame_offset;
265    stream->track_start     = (size_t)pregap * stream->frame_size;
266    stream->track_end       = stream->track_start +
267                              (size_t)meta.frames * stream->frame_size;
268 
269    return stream;
270 
271 error:
272 
273    chdstream_close(stream);
274 
275    if (chd)
276       chd_close(chd);
277 
278    return NULL;
279 }
280 
chdstream_close(chdstream_t * stream)281 void chdstream_close(chdstream_t *stream)
282 {
283    if (!stream)
284       return;
285 
286    if (stream->hunkmem)
287       free(stream->hunkmem);
288    if (stream->chd)
289       chd_close(stream->chd);
290    free(stream);
291 }
292 
293 static bool
chdstream_load_hunk(chdstream_t * stream,uint32_t hunknum)294 chdstream_load_hunk(chdstream_t *stream, uint32_t hunknum)
295 {
296    uint16_t *array;
297 
298    if (hunknum == stream->hunknum)
299       return true;
300 
301    if (chd_read(stream->chd, hunknum, stream->hunkmem) != CHDERR_NONE)
302       return false;
303 
304    if (stream->swab)
305    {
306       uint32_t i;
307       uint32_t count = chd_get_header(stream->chd)->hunkbytes / 2;
308       array          = (uint16_t*)stream->hunkmem;
309       for (i = 0; i < count; ++i)
310          array[i] = SWAP16(array[i]);
311    }
312 
313    stream->hunknum = hunknum;
314    return true;
315 }
316 
chdstream_read(chdstream_t * stream,void * data,size_t bytes)317 ssize_t chdstream_read(chdstream_t *stream, void *data, size_t bytes)
318 {
319    size_t end;
320    size_t data_offset   = 0;
321    const chd_header *hd = chd_get_header(stream->chd);
322    uint8_t         *out = (uint8_t*)data;
323 
324    if (stream->track_end - stream->offset < bytes)
325       bytes             = stream->track_end - stream->offset;
326 
327    end                  = stream->offset + bytes;
328 
329    while (stream->offset < end)
330    {
331       uint32_t frame_offset = stream->offset % stream->frame_size;
332       uint32_t amount       = stream->frame_size - frame_offset;
333 
334       if (amount > end - stream->offset)
335          amount = (uint32_t)(end - stream->offset);
336 
337       /* In pregap */
338       if (stream->offset < stream->track_start)
339          memset(out + data_offset, 0, amount);
340       else
341       {
342          uint32_t chd_frame   = (uint32_t)(stream->track_frame +
343             (stream->offset - stream->track_start) / stream->frame_size);
344          uint32_t hunk        = chd_frame / stream->frames_per_hunk;
345          uint32_t hunk_offset = (chd_frame % stream->frames_per_hunk)
346             * hd->unitbytes;
347 
348          if (!chdstream_load_hunk(stream, hunk))
349             return -1;
350 
351          memcpy(out + data_offset,
352                 stream->hunkmem + frame_offset
353                 + hunk_offset + stream->frame_offset, amount);
354       }
355 
356       data_offset    += amount;
357       stream->offset += amount;
358    }
359 
360    return bytes;
361 }
362 
chdstream_getc(chdstream_t * stream)363 int chdstream_getc(chdstream_t *stream)
364 {
365    char c = 0;
366 
367    if (chdstream_read(stream, &c, sizeof(c) != sizeof(c)))
368       return EOF;
369 
370    return c;
371 }
372 
chdstream_gets(chdstream_t * stream,char * buffer,size_t len)373 char *chdstream_gets(chdstream_t *stream, char *buffer, size_t len)
374 {
375    int c;
376 
377    size_t offset = 0;
378 
379    while (offset < len && (c = chdstream_getc(stream)) != EOF)
380       buffer[offset++] = c;
381 
382    if (offset < len)
383       buffer[offset]   = '\0';
384 
385    return buffer;
386 }
387 
chdstream_tell(chdstream_t * stream)388 uint64_t chdstream_tell(chdstream_t *stream)
389 {
390    return stream->offset;
391 }
392 
chdstream_rewind(chdstream_t * stream)393 void chdstream_rewind(chdstream_t *stream)
394 {
395    stream->offset = 0;
396 }
397 
chdstream_seek(chdstream_t * stream,int64_t offset,int whence)398 int64_t chdstream_seek(chdstream_t *stream, int64_t offset, int whence)
399 {
400    int64_t new_offset;
401 
402    switch (whence)
403    {
404       case SEEK_SET:
405          new_offset = offset;
406          break;
407       case SEEK_CUR:
408          new_offset = stream->offset + offset;
409          break;
410       case SEEK_END:
411          new_offset = stream->track_end + offset;
412          break;
413       default:
414          return -1;
415    }
416 
417    if (new_offset < 0)
418       return -1;
419 
420    if (new_offset > stream->track_end)
421       new_offset = stream->track_end;
422 
423    stream->offset = new_offset;
424    return 0;
425 }
426 
chdstream_get_size(chdstream_t * stream)427 ssize_t chdstream_get_size(chdstream_t *stream)
428 {
429    return stream->track_end - stream->track_start;
430 }
431 
chdstream_get_track_start(chdstream_t * stream)432 uint32_t chdstream_get_track_start(chdstream_t *stream)
433 {
434    uint32_t i;
435    metadata_t meta;
436    uint32_t frame_offset = 0;
437 
438    for (i = 0; chdstream_get_meta(stream->chd, i, &meta); ++i)
439    {
440       if (stream->track_frame == frame_offset)
441          return meta.pregap * stream->frame_size;
442 
443       frame_offset += meta.frames + meta.extra;
444    }
445 
446    return 0;
447 }
448 
chdstream_get_frame_size(chdstream_t * stream)449 uint32_t chdstream_get_frame_size(chdstream_t *stream)
450 {
451    return stream->frame_size;
452 }
453