1 /* 2 * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <bus/scsi/scsi_cmds.h> 8 #include <device/scsi.h> 9 10 #include <ctype.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <unistd.h> 15 16 #define kFramesPerSecond 75 17 #define kFramesPerMinute (kFramesPerSecond * 60) 18 19 struct cdtext_pack_data { 20 uint8 id; 21 uint8 track; 22 uint8 number; 23 uint8 character_position : 4; 24 uint8 block_number : 3; 25 uint8 double_byte : 1; 26 char text[12]; 27 uint8 crc[2]; 28 } _PACKED; 29 30 31 static const uint8 kMaxTracks = 0x63; 32 enum { 33 kTrackID = 0x80, 34 kArtistID = 0x81, 35 kMessageID = 0x85, 36 }; 37 38 struct cdtext { 39 char *artist; 40 char *album; 41 char *titles[kMaxTracks]; 42 char *artists[kMaxTracks]; 43 uint8 track_count; 44 char *genre; 45 }; 46 47 48 // #pragma mark - string functions 49 50 51 bool 52 is_garbage(char c) 53 { 54 return isspace(c) || c == '-' || c == '/' || c == '\\'; 55 } 56 57 58 void 59 sanitize_string(char *string) 60 { 61 if (string == NULL) 62 return; 63 64 // strip garbage at the start 65 66 uint32 length = strlen(string); 67 uint32 garbage = 0; 68 while (is_garbage(string[garbage])) { 69 garbage++; 70 } 71 72 length -= garbage; 73 if (garbage) 74 memmove(string, string + garbage, length + 1); 75 76 // strip garbage from the end 77 78 while (length > 1 && isspace(string[length - 1])) { 79 string[--length] = '\0'; 80 } 81 } 82 83 84 //! Finds the first occurrence of \a find in \a string, ignores case. 85 static char* 86 find_string(const char *string, const char *find) 87 { 88 if (string == NULL || find == NULL) 89 return NULL; 90 91 char first = tolower(find[0]); 92 if (first == '\0') 93 return (char *)string; 94 95 int32 findLength = strlen(find) - 1; 96 find++; 97 98 for (; string[0]; string++) { 99 if (tolower(string[0]) != first) 100 continue; 101 if (strncasecmp(string + 1, find, findLength) == 0) 102 return (char *)string; 103 } 104 105 return NULL; 106 } 107 108 109 void 110 cut_string(char *string, char *cut) 111 { 112 if (string == NULL || cut == NULL) 113 return; 114 115 char *found = find_string(string, cut); 116 if (found != NULL) { 117 uint32 foundLength = strlen(found); 118 uint32 cutLength = strlen(cut); 119 memmove(found, found + cutLength, foundLength + 1 - cutLength); 120 } 121 } 122 123 124 void 125 sanitize_album(cdtext &text) 126 { 127 cut_string(text.album, text.artist); 128 sanitize_string(text.album); 129 130 if ((text.artist == NULL || !text.artist[0]) && text.album != NULL) { 131 // try to extract artist from album 132 char *space = strstr(text.album, " "); 133 if (space != NULL) { 134 space[0] = '\0'; 135 text.artist = text.album; 136 text.album = strdup(space + 2); 137 138 sanitize_string(text.artist); 139 sanitize_string(text.album); 140 } 141 } 142 } 143 144 145 void 146 sanitize_titles(cdtext &text) 147 { 148 for (uint8 i = 0; i < text.track_count; i++) { 149 cut_string(text.titles[i], "(Album Version)"); 150 sanitize_string(text.titles[i]); 151 sanitize_string(text.artists[i]); 152 if (!strcasecmp(text.artists[i], text.artist)) { 153 // if the title artist is the same as the main artist, remove it 154 free(text.artists[i]); 155 text.artists[i] = NULL; 156 } 157 158 if (text.titles[i] != NULL && text.titles[i][0] == '\t' && i > 0) 159 text.titles[i] = strdup(text.titles[i - 1]); 160 } 161 } 162 163 164 bool 165 single_case(const char *string, bool &upper, bool &first) 166 { 167 if (string == NULL) 168 return true; 169 170 while (string[0]) { 171 while (!isalpha(string[0])) { 172 string++; 173 } 174 175 if (first) { 176 upper = isupper(string[0]) != 0; 177 first = false; 178 } else if ((isupper(string[0]) != 0) ^ upper) 179 return false; 180 181 string++; 182 } 183 184 return true; 185 } 186 187 188 void 189 capitalize_string(char *string) 190 { 191 if (string == NULL) 192 return; 193 194 bool newWord = isalpha(string[0]) || isspace(string[0]); 195 while (string[0]) { 196 if (isalpha(string[0])) { 197 if (newWord) { 198 string[0] = toupper(string[0]); 199 newWord = false; 200 } else 201 string[0] = tolower(string[0]); 202 } else if (string[0] != '\'') 203 newWord = true; 204 205 string++; 206 } 207 } 208 209 210 void 211 correct_case(cdtext &text) 212 { 213 // check if all titles share a single case 214 bool first = true; 215 bool upper; 216 if (!single_case(text.album, upper, first) 217 || !single_case(text.artist, upper, first)) 218 return; 219 220 for (int32 i = 0; i < text.track_count; i++) { 221 if (!single_case(text.titles[i], upper, first) 222 || !single_case(text.artists[i], upper, first)) 223 return; 224 } 225 226 // If we get here, everything has a single case; we fix that 227 // and capitalize each word 228 229 capitalize_string(text.album); 230 capitalize_string(text.artist); 231 for (int32 i = 0; i < text.track_count; i++) { 232 capitalize_string(text.titles[i]); 233 capitalize_string(text.artists[i]); 234 } 235 } 236 237 238 // #pragma mark - CD-Text 239 240 241 bool 242 is_string_id(uint8 id) 243 { 244 return id >= kTrackID && id <= kMessageID; 245 } 246 247 248 bool 249 parse_pack_data(cdtext_pack_data *&pack, uint32 &packLeft, 250 cdtext_pack_data *&lastPack, uint8 &id, uint8 &track, uint8 &state, 251 char *buffer, size_t &length) 252 { 253 if (packLeft < sizeof(cdtext_pack_data)) 254 return false; 255 256 uint8 number = pack->number; 257 size_t size = length; 258 259 if (state != 0) { 260 // we had a terminated string and a missing track 261 track++; 262 memcpy(buffer, lastPack->text + state, 12 - state); 263 if (pack->track - track == 1) 264 state = 0; 265 else 266 state += strnlen(buffer, 12 - state); 267 return true; 268 } 269 270 id = pack->id; 271 track = pack->track; 272 buffer[0] = '\0'; 273 length = 0; 274 275 size_t position = pack->character_position; 276 if (position > 0 && lastPack != NULL) { 277 memcpy(buffer, &lastPack->text[12 - position], position); 278 length = position; 279 } 280 281 while (id == pack->id && track == pack->track) { 282 #if 1 283 printf("%u.%u.%u, %u.%u.%u, ", pack->id, pack->track, pack->number, 284 pack->double_byte, pack->block_number, pack->character_position); 285 for (int32 i = 0; i < 12; i++) { 286 if (isprint(pack->text[i])) 287 putchar(pack->text[i]); 288 } 289 putchar('\n'); 290 #endif 291 if (is_string_id(id)) { 292 // TODO: support double byte characters 293 if (length + 12 < size) { 294 memcpy(buffer + length, pack->text, 12); 295 length += 12; 296 } 297 } 298 299 packLeft -= sizeof(cdtext_pack_data); 300 if (packLeft < sizeof(cdtext_pack_data)) 301 return false; 302 303 lastPack = pack; 304 number++; 305 pack++; 306 307 if (pack->number != number) 308 return false; 309 } 310 311 if (id == pack->id) { 312 length -= pack->character_position; 313 if (length >= size) 314 length = size - 1; 315 buffer[length] = '\0'; 316 317 if (pack->track > lastPack->track + 1) { 318 // there is a missing track 319 for (int32 i = 0; i < 12; i++) { 320 if (lastPack->text[i] == '\0') { 321 state = i + (lastPack->double_byte ? 2 : 1); 322 break; 323 } 324 } 325 } 326 } 327 328 // TODO: convert text to UTF-8 329 return true; 330 } 331 332 333 void 334 dump_cdtext(cdtext_pack_data *pack, size_t packLength) 335 { 336 cdtext_pack_data *lastPack = NULL; 337 char buffer[256]; 338 uint8 state = 0; 339 cdtext text; 340 341 memset(&text, 0, sizeof(cdtext)); 342 343 while (true) { 344 size_t length = sizeof(buffer); 345 uint8 id, track; 346 347 if (!parse_pack_data(pack, packLength, lastPack, id, track, 348 state, buffer, length)) 349 break; 350 351 switch (id) { 352 case kTrackID: 353 if (track == 0) { 354 if (text.album == NULL) 355 text.album = strdup(buffer); 356 } else if (track <= kMaxTracks) { 357 if (text.titles[track - 1] == NULL) 358 text.titles[track - 1] = strdup(buffer); 359 if (track > text.track_count) 360 text.track_count = track; 361 } 362 break; 363 364 case kArtistID: 365 if (track == 0) { 366 if (text.artist == NULL) 367 text.artist = strdup(buffer); 368 } else if (track <= kMaxTracks) { 369 if (text.artists[track - 1] == NULL) 370 text.artists[track - 1] = strdup(buffer); 371 } 372 break; 373 374 default: 375 if (is_string_id(id)) 376 printf("UNKNOWN %u: \"%s\"\n", id, buffer); 377 break; 378 } 379 } 380 381 sanitize_string(text.artist); 382 sanitize_album(text); 383 sanitize_titles(text); 384 correct_case(text); 385 386 if (text.album) 387 printf("Album: \"%s\"\n", text.album); 388 if (text.artist) 389 printf("Artist: \"%s\"\n", text.artist); 390 for (uint8 i = 0; i < text.track_count; i++) { 391 printf("Track %02u: \"%s\"%s%s%s\n", i + 1, text.titles[i], 392 text.artists[i] ? " (" : "", text.artists[i] ? text.artists[i] : "", 393 text.artists[i] ? ")" : ""); 394 } 395 } 396 397 398 void 399 dump_toc(scsi_toc_toc *toc) 400 { 401 int32 numTracks = toc->last_track + 1 - toc->first_track; 402 403 for (int32 i = 0; i < numTracks; i++) { 404 scsi_toc_track& track = toc->tracks[i]; 405 scsi_cd_msf& next = toc->tracks[i + 1].start.time; 406 // the last track is always lead-out 407 scsi_cd_msf& start = toc->tracks[i].start.time; 408 scsi_cd_msf length; 409 410 uint64 diff = next.minute * kFramesPerMinute 411 + next.second * kFramesPerSecond + next.frame 412 - start.minute * kFramesPerMinute 413 - start.second * kFramesPerSecond - start.frame; 414 length.minute = diff / kFramesPerMinute; 415 length.second = (diff % kFramesPerMinute) / kFramesPerSecond; 416 length.frame = diff % kFramesPerSecond; 417 418 printf("%02u. %02u:%02u.%02u (length %02u:%02u.%02u)\n", 419 track.track_number, start.minute, start.second, start.frame, 420 length.minute, length.second, length.frame); 421 } 422 } 423 424 425 status_t 426 read_table_of_contents(int fd, uint32 track, uint8 format, uint8 *buffer, 427 size_t bufferSize) 428 { 429 raw_device_command raw; 430 uint8 senseData[1024]; 431 432 memset(&raw, 0, sizeof(raw_device_command)); 433 memset(senseData, 0, sizeof(senseData)); 434 memset(buffer, 0, bufferSize); 435 436 scsi_cmd_read_toc &toc = *(scsi_cmd_read_toc*)&raw.command; 437 toc.opcode = SCSI_OP_READ_TOC; 438 toc.time = 1; 439 toc.format = format; 440 toc.track = track; 441 toc.allocation_length = B_HOST_TO_BENDIAN_INT16(bufferSize); 442 443 raw.command_length = 10; 444 raw.flags = B_RAW_DEVICE_DATA_IN | B_RAW_DEVICE_REPORT_RESIDUAL 445 | B_RAW_DEVICE_SHORT_READ_VALID; 446 raw.scsi_status = 0; 447 raw.cam_status = 0; 448 raw.data = buffer; 449 raw.data_length = bufferSize; 450 raw.timeout = 10000000LL; // 10 secs 451 raw.sense_data = senseData; 452 raw.sense_data_length = sizeof(senseData); 453 454 if (ioctl(fd, B_RAW_DEVICE_COMMAND, &raw) == 0) { 455 if (raw.scsi_status == 0 && raw.cam_status == 1) { 456 puts("success!\n"); 457 } else { 458 puts("failure!\n"); 459 } 460 } 461 462 return 0; 463 } 464 465 466 void 467 dump_block(const uint8 *buffer, int size, const char *prefix) 468 { 469 int i; 470 471 for (i = 0; i < size;) 472 { 473 int start = i; 474 475 printf(prefix); 476 for (; i < start+16; i++) 477 { 478 if (!(i % 4)) 479 printf(" "); 480 481 if (i >= size) 482 printf(" "); 483 else 484 printf("%02x", *(unsigned char *)(buffer + i)); 485 } 486 printf(" "); 487 488 for (i = start; i < start + 16; i++) 489 { 490 if (i < size) 491 { 492 char c = buffer[i]; 493 494 if (c < 30) 495 printf("."); 496 else 497 printf("%c", c); 498 } 499 else 500 break; 501 } 502 printf("\n"); 503 } 504 } 505 506 507 int 508 main(int argc, char** argv) 509 { 510 int fd = open(argv[1], O_RDONLY); 511 if (fd < 0) 512 return -1; 513 514 uint8 buffer[1024]; 515 scsi_toc_general *header = (scsi_toc_general *)buffer; 516 517 read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_TOC, buffer, sizeof(buffer)); 518 header->data_length = B_BENDIAN_TO_HOST_INT16(header->data_length); 519 printf("TOC header %u, %d, %d\n", header->data_length, header->first, header->last); 520 //dump_block(buffer, header->data_length + 2, "TOC"); 521 dump_toc((scsi_toc_toc *)buffer); 522 523 read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer, sizeof(buffer)); 524 read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer, sizeof(buffer)); 525 header->data_length = B_BENDIAN_TO_HOST_INT16(header->data_length); 526 printf("TEXT header %u, %d, %d\n", header->data_length, header->first, header->last); 527 //dump_block(buffer, header->data_length + 2, "TEXT"); 528 529 dump_cdtext((cdtext_pack_data *)(buffer + 4), header->data_length - 2); 530 531 close(fd); 532 } 533