git subrepo clone https://github.com/libretro/libretro-common.git deps/libretro-common
[pcsx_rearmed.git] / deps / libretro-common / streams / chd_stream.c
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
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
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    meta[0] = '\0';
91
92    memset(md, 0, sizeof(*md));
93
94    err = chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, idx, meta,
95          sizeof(meta), &meta_size, NULL, NULL);
96
97    if (err == CHDERR_NONE)
98    {
99       sscanf(meta, CDROM_TRACK_METADATA2_FORMAT,
100             &md->track, md->type,
101             md->subtype, &md->frames, &md->pregap,
102             md->pgtype, md->pgsub,
103             &md->postgap);
104       md->extra = padding_frames(md->frames);
105       return true;
106    }
107
108    err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, idx, meta,
109          sizeof(meta), &meta_size, NULL, NULL);
110
111    if (err == CHDERR_NONE)
112    {
113       sscanf(meta, CDROM_TRACK_METADATA_FORMAT, &md->track, md->type,
114              md->subtype, &md->frames);
115       md->extra = padding_frames(md->frames);
116       return true;
117    }
118
119    err = chd_get_metadata(chd, GDROM_TRACK_METADATA_TAG, idx, meta,
120          sizeof(meta), &meta_size, NULL, NULL);
121
122    if (err == CHDERR_NONE)
123    {
124       sscanf(meta, GDROM_TRACK_METADATA_FORMAT, &md->track, md->type,
125              md->subtype, &md->frames, &md->pad, &md->pregap, md->pgtype,
126              md->pgsub, &md->postgap);
127       md->extra = padding_frames(md->frames);
128       return true;
129    }
130
131    return false;
132 }
133
134 static bool
135 chdstream_find_track_number(chd_file *fd, int32_t track, metadata_t *meta)
136 {
137    uint32_t i;
138    uint32_t frame_offset = 0;
139
140    for (i = 0; true; ++i)
141    {
142       if (!chdstream_get_meta(fd, i, meta))
143          return false;
144
145       if (track == (int)meta->track)
146       {
147          meta->frame_offset = frame_offset;
148          return true;
149       }
150
151       frame_offset += meta->frames + meta->extra;
152    }
153 }
154
155 static bool
156 chdstream_find_special_track(chd_file *fd, int32_t track, metadata_t *meta)
157 {
158    int32_t i;
159    metadata_t iter;
160    int32_t largest_track = 0;
161    uint32_t largest_size = 0;
162
163    for (i = 1; true; ++i)
164    {
165       if (!chdstream_find_track_number(fd, i, &iter))
166       {
167          if (track == CHDSTREAM_TRACK_LAST && i > 1)
168             return chdstream_find_track_number(fd, i - 1, meta);
169
170          if (track == CHDSTREAM_TRACK_PRIMARY && largest_track != 0)
171             return chdstream_find_track_number(fd, largest_track, meta);
172
173          return false;
174       }
175
176       switch (track)
177       {
178          case CHDSTREAM_TRACK_FIRST_DATA:
179             if (strcmp(iter.type, "AUDIO"))
180             {
181                *meta = iter;
182                return true;
183             }
184             break;
185          case CHDSTREAM_TRACK_PRIMARY:
186             if (strcmp(iter.type, "AUDIO") && iter.frames > largest_size)
187             {
188                largest_size  = iter.frames;
189                largest_track = iter.track;
190             }
191             break;
192          default:
193             break;
194       }
195    }
196 }
197
198 static bool
199 chdstream_find_track(chd_file *fd, int32_t track, metadata_t *meta)
200 {
201    if (track < 0)
202       return chdstream_find_special_track(fd, track, meta);
203    return chdstream_find_track_number(fd, track, meta);
204 }
205
206 chdstream_t *chdstream_open(const char *path, int32_t track)
207 {
208    metadata_t meta;
209    uint32_t pregap         = 0;
210    uint8_t *hunkmem        = NULL;
211    const chd_header *hd    = NULL;
212    chdstream_t *stream     = NULL;
213    chd_file *chd           = NULL;
214    chd_error err           = chd_open(path, CHD_OPEN_READ, NULL, &chd);
215
216    if (err != CHDERR_NONE)
217       return NULL;
218
219    if (!chdstream_find_track(chd, track, &meta))
220       goto error;
221
222    stream                  = (chdstream_t*)malloc(sizeof(*stream));
223    if (!stream)
224       goto error;
225
226    stream->chd             = NULL;
227    stream->swab            = false;
228    stream->frame_size      = 0;
229    stream->frame_offset    = 0;
230    stream->frames_per_hunk = 0;
231    stream->track_frame     = 0;
232    stream->track_start     = 0;
233    stream->track_end       = 0;
234    stream->offset          = 0;
235    stream->hunkmem         = NULL;
236    stream->hunknum         = -1;
237
238    hd                      = chd_get_header(chd);
239    hunkmem                 = (uint8_t*)malloc(hd->hunkbytes);
240    if (!hunkmem)
241       goto error;
242
243    stream->hunkmem         = hunkmem;
244
245    if (string_is_equal(meta.type, "MODE1_RAW"))
246       stream->frame_size   = SECTOR_SIZE;
247    else if (string_is_equal(meta.type, "MODE2_RAW"))
248       stream->frame_size   = SECTOR_SIZE;
249    else if (string_is_equal(meta.type, "AUDIO"))
250    {
251       stream->frame_size   = SECTOR_SIZE;
252       stream->swab         = true;
253    }
254    else
255       stream->frame_size   = hd->unitbytes;
256
257    /* Only include pregap data if it was in the track file */
258    if (meta.pgtype[0] != 'V')
259       pregap               = meta.pregap;
260
261    stream->chd             = chd;
262    stream->frames_per_hunk = hd->hunkbytes / hd->unitbytes;
263    stream->track_frame     = meta.frame_offset;
264    stream->track_start     = (size_t)pregap * stream->frame_size;
265    stream->track_end       = stream->track_start + 
266                              (size_t)meta.frames * stream->frame_size;
267
268    return stream;
269
270 error:
271
272    chdstream_close(stream);
273
274    if (chd)
275       chd_close(chd);
276
277    return NULL;
278 }
279
280 void chdstream_close(chdstream_t *stream)
281 {
282    if (!stream)
283       return;
284
285    if (stream->hunkmem)
286       free(stream->hunkmem);
287    if (stream->chd)
288       chd_close(stream->chd);
289    free(stream);
290 }
291
292 static bool
293 chdstream_load_hunk(chdstream_t *stream, uint32_t hunknum)
294 {
295    uint16_t *array;
296
297    if ((int)hunknum == stream->hunknum)
298       return true;
299
300    if (chd_read(stream->chd, hunknum, stream->hunkmem) != CHDERR_NONE)
301       return false;
302
303    if (stream->swab)
304    {
305       uint32_t i;
306       uint32_t count = chd_get_header(stream->chd)->hunkbytes / 2;
307       array          = (uint16_t*)stream->hunkmem;
308       for (i = 0; i < count; ++i)
309          array[i] = SWAP16(array[i]);
310    }
311
312    stream->hunknum = hunknum;
313    return true;
314 }
315
316 ssize_t chdstream_read(chdstream_t *stream, void *data, size_t bytes)
317 {
318    size_t end;
319    size_t data_offset   = 0;
320    const chd_header *hd = chd_get_header(stream->chd);
321    uint8_t         *out = (uint8_t*)data;
322
323    if (stream->track_end - stream->offset < bytes)
324       bytes             = stream->track_end - stream->offset;
325
326    end                  = stream->offset + bytes;
327
328    while (stream->offset < end)
329    {
330       uint32_t frame_offset = stream->offset % stream->frame_size;
331       uint32_t amount       = stream->frame_size - frame_offset;
332
333       if (amount > end - stream->offset)
334          amount = (uint32_t)(end - stream->offset);
335
336       /* In pregap */
337       if (stream->offset < stream->track_start)
338          memset(out + data_offset, 0, amount);
339       else
340       {
341          uint32_t chd_frame   = (uint32_t)(stream->track_frame +
342             (stream->offset - stream->track_start) / stream->frame_size);
343          uint32_t hunk        = chd_frame / stream->frames_per_hunk;
344          uint32_t hunk_offset = (chd_frame % stream->frames_per_hunk) 
345             * hd->unitbytes;
346
347          if (!chdstream_load_hunk(stream, hunk))
348             return -1;
349
350          memcpy(out + data_offset,
351                 stream->hunkmem + frame_offset
352                 + hunk_offset + stream->frame_offset, amount);
353       }
354
355       data_offset    += amount;
356       stream->offset += amount;
357    }
358
359    return bytes;
360 }
361
362 int chdstream_getc(chdstream_t *stream)
363 {
364    char c = 0;
365
366    if (chdstream_read(stream, &c, sizeof(c) != sizeof(c)))
367       return EOF;
368
369    return c;
370 }
371
372 char *chdstream_gets(chdstream_t *stream, char *buffer, size_t len)
373 {
374    int c;
375
376    size_t offset = 0;
377
378    while (offset < len && (c = chdstream_getc(stream)) != EOF)
379       buffer[offset++] = c;
380
381    if (offset < len)
382       buffer[offset]   = '\0';
383
384    return buffer;
385 }
386
387 uint64_t chdstream_tell(chdstream_t *stream)
388 {
389    return stream->offset;
390 }
391
392 void chdstream_rewind(chdstream_t *stream)
393 {
394    stream->offset = 0;
395 }
396
397 int64_t chdstream_seek(chdstream_t *stream, int64_t offset, int whence)
398 {
399    int64_t new_offset;
400
401    switch (whence)
402    {
403       case SEEK_SET:
404          new_offset = offset;
405          break;
406       case SEEK_CUR:
407          new_offset = stream->offset + offset;
408          break;
409       case SEEK_END:
410          new_offset = stream->track_end + offset;
411          break;
412       default:
413          return -1;
414    }
415
416    if (new_offset < 0)
417       return -1;
418
419    if ((size_t)new_offset > stream->track_end)
420       new_offset = stream->track_end;
421
422    stream->offset = new_offset;
423    return 0;
424 }
425
426 ssize_t chdstream_get_size(chdstream_t *stream)
427 {
428    return stream->track_end - stream->track_start;
429 }
430
431 uint32_t chdstream_get_track_start(chdstream_t *stream)
432 {
433    uint32_t i;
434    metadata_t meta;
435    uint32_t frame_offset = 0;
436
437    for (i = 0; chdstream_get_meta(stream->chd, i, &meta); ++i)
438    {
439       if (stream->track_frame == frame_offset)
440          return meta.pregap * stream->frame_size;
441
442       frame_offset += meta.frames + meta.extra;
443    }
444
445    return 0;
446 }
447
448 uint32_t chdstream_get_frame_size(chdstream_t *stream)
449 {
450    return stream->frame_size;
451 }
452
453 uint32_t chdstream_get_first_track_sector(chdstream_t* stream)
454 {
455    uint32_t i;
456    metadata_t meta;
457    uint32_t frame_offset = 0;
458    uint32_t sector_offset = 0;
459
460    for (i = 0; chdstream_get_meta(stream->chd, i, &meta); ++i)
461    {
462       if (stream->track_frame == frame_offset)
463          return sector_offset;
464
465       sector_offset += meta.frames;
466       frame_offset += meta.frames + meta.extra;
467    }
468
469    return 0;
470 }