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