Commit | Line | Data |
---|---|---|
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 | ||
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 | } |