cd_parse.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /*
  2. * cuefile handling
  3. * (C) notaz, 2008
  4. *
  5. * This work is licensed under the terms of MAME license.
  6. * See COPYING file in the top-level directory.
  7. */
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include "../pico_int.h"
  12. #include "cd_parse.h"
  13. // #define elprintf(w,f,...) printf(f "\n",##__VA_ARGS__);
  14. #ifdef USE_LIBRETRO_VFS
  15. #include "file_stream_transforms.h"
  16. #endif
  17. #if defined(USE_LIBCHDR)
  18. #include "libchdr/chd.h"
  19. #include "libchdr/cdrom.h"
  20. #endif
  21. #ifdef _MSC_VER
  22. #define snprintf _snprintf
  23. #endif
  24. #ifdef __EPOC32__
  25. #define snprintf(b,s,...) sprintf(b,##__VA_ARGS__)
  26. #endif
  27. static char *mystrip(char *str)
  28. {
  29. int i, len;
  30. len = strlen(str);
  31. for (i = 0; i < len; i++)
  32. if (str[i] != ' ') break;
  33. if (i > 0) memmove(str, str + i, len - i + 1);
  34. len = strlen(str);
  35. for (i = len - 1; i >= 0; i--)
  36. if (str[i] != ' ' && str[i] != '\r' && str[i] != '\n') break;
  37. str[i+1] = 0;
  38. return str;
  39. }
  40. static int get_token(const char *buff, char *dest, int len)
  41. {
  42. const char *p = buff;
  43. char sep = ' ';
  44. int d = 0, skip = 0;
  45. while (*p && *p == ' ') {
  46. skip++;
  47. p++;
  48. }
  49. if (*p == '\"') {
  50. sep = '\"';
  51. p++;
  52. }
  53. while (*p && *p != sep && d < len-1)
  54. dest[d++] = *p++;
  55. dest[d] = 0;
  56. if (sep == '\"' && *p != sep)
  57. elprintf(EL_STATUS, "cue: bad token: \"%s\"", buff);
  58. return d + skip;
  59. }
  60. static int get_ext(const char *fname, char ext[4],
  61. char *base, size_t base_size)
  62. {
  63. int len, pos = 0;
  64. len = strrchr(fname, '.') - fname;
  65. if (len > 0)
  66. pos = len;
  67. strncpy(ext, fname + pos + 1, 4/*sizeof(ext)*/-1);
  68. ext[4/*sizeof(ext)*/-1] = '\0';
  69. if (base != NULL && base_size > 0) {
  70. if (pos + 1 < base_size)
  71. pos = base_size - 1;
  72. len = (pos < len) ? pos : len;
  73. memcpy(base, fname, len);
  74. base[len] = 0;
  75. }
  76. return pos;
  77. }
  78. static void change_case(char *p, int to_upper)
  79. {
  80. for (; *p != 0; p++) {
  81. if (to_upper && 'a' <= *p && *p <= 'z')
  82. *p += 'A' - 'a';
  83. else if (!to_upper && 'A' <= *p && *p <= 'Z')
  84. *p += 'a' - 'A';
  85. }
  86. }
  87. static int file_openable(const char *fname)
  88. {
  89. FILE *f = fopen(fname, "rb");
  90. if (f == NULL)
  91. return 0;
  92. fclose(f);
  93. return 1;
  94. }
  95. #define BEGINS(buff,str) (strncmp(buff,str,sizeof(str)-1) == 0)
  96. /* note: tracks[0] is not used */
  97. cd_data_t *chd_parse(const char *fname)
  98. {
  99. cd_data_t *data = NULL;
  100. #if defined(USE_LIBCHDR)
  101. cd_data_t *tmp;
  102. int count = 0, count_alloc = 2;
  103. int sectors = 0;
  104. char metadata[256];
  105. chd_file *cf = NULL;
  106. if (fname == NULL || *fname == '\0')
  107. return NULL;
  108. if (chd_open(fname, CHD_OPEN_READ, NULL, &cf) != CHDERR_NONE)
  109. goto out;
  110. data = calloc(1, sizeof(*data) + count_alloc * sizeof(cd_track_t));
  111. if (data == NULL)
  112. goto out;
  113. // get track info
  114. while (count < CD_MAX_TRACKS) {
  115. int track = 0, frames = 0, pregap = 0, postgap = 0;
  116. char type[16], subtype[16], pgtype[16], pgsub[16];
  117. type[0] = subtype[0] = pgtype[0] = pgsub[0] = 0;
  118. // get metadata for track
  119. if (chd_get_metadata(cf, CDROM_TRACK_METADATA2_TAG, count,
  120. metadata, sizeof(metadata), 0, 0, 0) == CHDERR_NONE) {
  121. if (sscanf(metadata, CDROM_TRACK_METADATA2_FORMAT,
  122. &track, &type[0], &subtype[0], &frames,
  123. &pregap, &pgtype[0], &pgsub[0], &postgap) != 8)
  124. break;
  125. }
  126. else if (chd_get_metadata(cf, CDROM_TRACK_METADATA_TAG, count,
  127. metadata, sizeof(metadata), 0, 0, 0) == CHDERR_NONE) {
  128. if (sscanf(metadata, CDROM_TRACK_METADATA_FORMAT,
  129. &track, &type[0], &subtype[0], &frames) != 4)
  130. break;
  131. }
  132. else break; // all tracks completed
  133. // metadata sanity check
  134. if (track != count + 1 || frames < 0 || pregap < 0)
  135. break;
  136. // allocate track structure
  137. count ++;
  138. if (count >= count_alloc) {
  139. count_alloc *= 2;
  140. tmp = realloc(data, sizeof(*data) + count_alloc * sizeof(cd_track_t));
  141. if (tmp == NULL) {
  142. count--;
  143. break;
  144. }
  145. data = tmp;
  146. }
  147. memset(&data->tracks[count], 0, sizeof(data->tracks[0]));
  148. if (count == 1) { // binary code
  149. data->tracks[count].fname = strdup(fname);
  150. if (!strcmp(type, "MODE1_RAW") || !strcmp(type, "MODE2_RAW")) {
  151. data->tracks[count].type = CT_BIN;
  152. } else if (!strcmp(type, "MODE1") || !strcmp(type, "MODE2_FORM1")) {
  153. data->tracks[count].type = CT_ISO;
  154. } else
  155. break;
  156. } else { // audio
  157. if (strcmp(type, "AUDIO"))
  158. break;
  159. data->tracks[count].type = CT_CHD;
  160. }
  161. data->tracks[count].pregap = pregap;
  162. if (pgtype[0] != 'V') // VAUDIO includes pregap in file
  163. pregap = 0;
  164. data->tracks[count].sector_offset = sectors + pregap;
  165. data->tracks[count].sector_xlength = frames - pregap;
  166. sectors += (((frames + CD_TRACK_PADDING - 1) / CD_TRACK_PADDING) * CD_TRACK_PADDING);
  167. }
  168. // check if image id OK, i.e. there are tracks, and length <= 80 min
  169. if (count && sectors < (80*60*75)) {
  170. data->track_count = count;
  171. } else {
  172. free(data);
  173. data = NULL;
  174. }
  175. out:
  176. if (cf)
  177. chd_close(cf);
  178. #endif
  179. return data;
  180. }
  181. cd_data_t *cue_parse(const char *fname)
  182. {
  183. char current_file[256], *current_filep, cue_base[256];
  184. char buff[256], buff2[32], ext[4], *p;
  185. int ret, count = 0, count_alloc = 2, pending_pregap = 0;
  186. size_t current_filep_size, fname_len;
  187. cd_data_t *data = NULL, *tmp;
  188. FILE *f = NULL;
  189. if (fname == NULL || (fname_len = strlen(fname)) == 0)
  190. return NULL;
  191. ret = get_ext(fname, ext, cue_base, sizeof(cue_base));
  192. if (strcasecmp(ext, "cue") == 0) {
  193. f = fopen(fname, "r");
  194. }
  195. else {
  196. // not a .cue, try one with the same base name
  197. if (ret + 3 < sizeof(cue_base)) {
  198. strcpy(cue_base + ret, "cue");
  199. f = fopen(cue_base, "r");
  200. if (f == NULL) {
  201. strcpy(cue_base + ret, "CUE");
  202. f = fopen(cue_base, "r");
  203. }
  204. }
  205. }
  206. if (f == NULL)
  207. return NULL;
  208. snprintf(current_file, sizeof(current_file), "%s", fname);
  209. current_filep = current_file + strlen(current_file);
  210. for (; current_filep > current_file; current_filep--)
  211. if (current_filep[-1] == '/' || current_filep[-1] == '\\')
  212. break;
  213. current_filep_size = sizeof(current_file) - (current_filep - current_file);
  214. // the basename of cuefile, no path
  215. snprintf(cue_base, sizeof(cue_base), "%s", current_filep);
  216. p = strrchr(cue_base, '.');
  217. if (p) p[1] = '\0';
  218. data = calloc(1, sizeof(*data) + count_alloc * sizeof(cd_track_t));
  219. if (data == NULL)
  220. goto out;
  221. while (!feof(f))
  222. {
  223. if (fgets(buff, sizeof(buff), f) == NULL)
  224. break;
  225. mystrip(buff);
  226. if (buff[0] == 0)
  227. continue;
  228. if (BEGINS(buff, "TITLE ") || BEGINS(buff, "PERFORMER ") || BEGINS(buff, "SONGWRITER "))
  229. continue; /* who would put those here? Ignore! */
  230. else if (BEGINS(buff, "FILE "))
  231. {
  232. get_token(buff + 5, current_filep, current_filep_size);
  233. }
  234. else if (BEGINS(buff, "TRACK "))
  235. {
  236. count++;
  237. if (count >= count_alloc) {
  238. count_alloc *= 2;
  239. tmp = realloc(data, sizeof(*data) + count_alloc * sizeof(cd_track_t));
  240. if (tmp == NULL) {
  241. count--;
  242. break;
  243. }
  244. data = tmp;
  245. }
  246. memset(&data->tracks[count], 0, sizeof(data->tracks[0]));
  247. if (count == 1 || strcmp(data->tracks[1].fname, current_file) != 0)
  248. {
  249. if (file_openable(current_file))
  250. goto file_ok;
  251. elprintf(EL_STATUS, "cue: bad/missing file: \"%s\"", current_file);
  252. if (count == 1) {
  253. int cue_ucase;
  254. char v;
  255. get_ext(current_file, ext, NULL, 0);
  256. snprintf(current_filep, current_filep_size,
  257. "%s%s", cue_base, ext);
  258. if (file_openable(current_file))
  259. goto file_ok;
  260. // try with the same case (for unix)
  261. v = fname[fname_len - 1];
  262. cue_ucase = ('A' <= v && v <= 'Z');
  263. change_case(ext, cue_ucase);
  264. snprintf(current_filep, current_filep_size,
  265. "%s%s", cue_base, ext);
  266. if (file_openable(current_file))
  267. goto file_ok;
  268. }
  269. count--;
  270. break;
  271. file_ok:
  272. data->tracks[count].fname = strdup(current_file);
  273. if (data->tracks[count].fname == NULL)
  274. break;
  275. }
  276. data->tracks[count].pregap = pending_pregap;
  277. pending_pregap = 0;
  278. // track number
  279. ret = get_token(buff+6, buff2, sizeof(buff2));
  280. if (count != atoi(buff2))
  281. elprintf(EL_STATUS, "cue: track index mismatch: track %i is track %i in cue",
  282. count, atoi(buff2));
  283. // check type
  284. get_token(buff+6+ret, buff2, sizeof(buff2));
  285. if (strcmp(buff2, "MODE1/2352") == 0)
  286. data->tracks[count].type = CT_BIN;
  287. else if (strcmp(buff2, "MODE1/2048") == 0)
  288. data->tracks[count].type = CT_ISO;
  289. else if (strcmp(buff2, "AUDIO") == 0)
  290. {
  291. if (data->tracks[count].fname != NULL)
  292. {
  293. // rely on extension, not type in cue..
  294. get_ext(data->tracks[count].fname, ext, NULL, 0);
  295. if (strcasecmp(ext, "mp3") == 0)
  296. data->tracks[count].type = CT_MP3;
  297. else if (strcasecmp(ext, "wav") == 0)
  298. data->tracks[count].type = CT_WAV;
  299. else if (strcasecmp(ext, "bin") == 0)
  300. data->tracks[count].type = CT_BIN;
  301. else {
  302. elprintf(EL_STATUS, "unhandled audio format: \"%s\"",
  303. data->tracks[count].fname);
  304. }
  305. }
  306. else
  307. {
  308. // propagate previous
  309. data->tracks[count].type = data->tracks[count-1].type;
  310. }
  311. }
  312. else {
  313. elprintf(EL_STATUS, "unhandled track type: \"%s\"", buff2);
  314. }
  315. }
  316. else if (BEGINS(buff, "INDEX "))
  317. {
  318. int m, s, f;
  319. // type
  320. ret = get_token(buff+6, buff2, sizeof(buff2));
  321. if (atoi(buff2) == 0) continue;
  322. if (atoi(buff2) != 1) {
  323. elprintf(EL_STATUS, "cue: don't know how to handle: \"%s\"", buff);
  324. count--; break;
  325. }
  326. // offset in file
  327. get_token(buff+6+ret, buff2, sizeof(buff2));
  328. ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
  329. if (ret != 3) {
  330. elprintf(EL_STATUS, "cue: failed to parse: \"%s\"", buff);
  331. count--; break;
  332. }
  333. data->tracks[count].sector_offset = m*60*75 + s*75 + f;
  334. // some strange .cues may need this
  335. if (data->tracks[count].fname != NULL && strcmp(data->tracks[count].fname, current_file) != 0)
  336. {
  337. free(data->tracks[count].fname);
  338. data->tracks[count].fname = strdup(current_file);
  339. }
  340. if (data->tracks[count].fname == NULL && strcmp(data->tracks[1].fname, current_file) != 0)
  341. {
  342. data->tracks[count].fname = strdup(current_file);
  343. }
  344. }
  345. else if (BEGINS(buff, "PREGAP ") || BEGINS(buff, "POSTGAP "))
  346. {
  347. int m, s, f;
  348. get_token(buff+7, buff2, sizeof(buff2));
  349. ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
  350. if (ret != 3) {
  351. elprintf(EL_STATUS, "cue: failed to parse: \"%s\"", buff);
  352. continue;
  353. }
  354. // pregap overrides previous postgap?
  355. // by looking at some .cues produced by some programs I've decided that..
  356. if (BEGINS(buff, "PREGAP "))
  357. data->tracks[count].pregap = m*60*75 + s*75 + f;
  358. else
  359. pending_pregap = m*60*75 + s*75 + f;
  360. }
  361. else if (BEGINS(buff, "REM LENGTH ")) // custom "extension"
  362. {
  363. int m, s, f;
  364. get_token(buff+11, buff2, sizeof(buff2));
  365. ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
  366. if (ret != 3) continue;
  367. data->tracks[count].sector_xlength = m*60*75 + s*75 + f;
  368. }
  369. else if (BEGINS(buff, "REM"))
  370. continue;
  371. else
  372. {
  373. elprintf(EL_STATUS, "cue: unhandled line: \"%s\"", buff);
  374. }
  375. }
  376. if (count < 1 || data->tracks[1].fname == NULL) {
  377. // failed..
  378. for (; count > 0; count--)
  379. if (data->tracks[count].fname != NULL)
  380. free(data->tracks[count].fname);
  381. free(data);
  382. data = NULL;
  383. goto out;
  384. }
  385. data->track_count = count;
  386. out:
  387. if (f != NULL)
  388. fclose(f);
  389. return data;
  390. }
  391. void cdparse_destroy(cd_data_t *data)
  392. {
  393. int c;
  394. if (data == NULL) return;
  395. for (c = data->track_count; c > 0; c--)
  396. if (data->tracks[c].fname != NULL)
  397. free(data->tracks[c].fname);
  398. free(data);
  399. }
  400. #if 0
  401. int main(int argc, char *argv[])
  402. {
  403. cd_data_t *data = cue_parse(argv[1]);
  404. int c;
  405. if (data == NULL) return 1;
  406. for (c = 1; c <= data->track_count; c++)
  407. printf("%2i: %i %9i %02i:%02i:%02i %9i %s\n", c, data->tracks[c].type, data->tracks[c].sector_offset,
  408. data->tracks[c].sector_offset / (75*60), data->tracks[c].sector_offset / 75 % 60,
  409. data->tracks[c].sector_offset % 75, data->tracks[c].pregap, data->tracks[c].fname);
  410. cdparse_destroy(data);
  411. return 0;
  412. }
  413. #endif