update libchdr
[pcsx_rearmed.git] / deps / libretro-common / streams / chd_stream.c
CommitLineData
3719602c
PC
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
38struct 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
63typedef 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
78static uint32_t padding_frames(uint32_t frames)
79{
80 return ((frames + TRACK_PAD - 1) & ~(TRACK_PAD - 1)) - frames;
81}
82
83static bool
84chdstream_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
134static bool
135chdstream_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
155static bool
156chdstream_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
198static bool
199chdstream_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
206chdstream_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
270error:
271
272 chdstream_close(stream);
273
274 if (chd)
275 chd_close(chd);
276
277 return NULL;
278}
279
280void 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
292static bool
293chdstream_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
316ssize_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
362int 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
372char *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
387uint64_t chdstream_tell(chdstream_t *stream)
388{
389 return stream->offset;
390}
391
392void chdstream_rewind(chdstream_t *stream)
393{
394 stream->offset = 0;
395}
396
397int64_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
426ssize_t chdstream_get_size(chdstream_t *stream)
427{
428 return stream->track_end - stream->track_start;
429}
430
431uint32_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
448uint32_t chdstream_get_frame_size(chdstream_t *stream)
449{
450 return stream->frame_size;
451}
452
453uint32_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}