1 /* 2 * Copyright 2005-2009, Ingo Weinhold, bonefish@users.sf.net. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 11 #include <string> 12 13 #include <List.h> 14 #include <Resources.h> 15 #include <StorageDefs.h> 16 #include <TypeConstants.h> 17 18 19 using namespace std; 20 21 22 static const char *kCommandName = "xres"; 23 static const char *kDefaultResourceName = NULL; 24 static const char *kDefaultOutputFile = "xres.output.rsrc"; 25 static const int kMaxSaneResourceSize = 100 * 1024 * 1024; // 100 MB 26 27 static int kArgc; 28 static const char *const *kArgv; 29 30 // usage 31 const char *kUsage = 32 "Usage: %s ( -h | --help )\n" 33 " %s -l <file> ...\n" 34 " %s <command> ...\n" 35 "\n" 36 "The first form prints this help text and exits.\n" 37 "\n" 38 "The second form lists the resources of all given files.\n" 39 "\n" 40 "The third form manipulates the resources of one or more files according to\n" 41 "the given commands.\n" 42 "\n" 43 "Valid commands are:\n" 44 " <input file>\n" 45 " - Add the resources read from file <input file> to the current\n" 46 " output file. The file can either be a resource file or an\n" 47 " executable file.\n" 48 " -a <type>:<id>[:<name>] ( <file> | -s <data> )\n" 49 " - Add a resource to the current output file. The added resource is\n" 50 " of type <type> and has the ID <id>. If given the resource will\n" 51 " have name <name>, otherwise it won't have a name. The resource\n" 52 " data will either be the string <data> provided on the command\n" 53 " line or the data read from file <file> (the whole contents).\n" 54 " -d <type>[:<id>]\n" 55 " - Excludes resources with type <type> and, if given, ID <id> from\n" 56 " being written to the output file. This applies to all resources\n" 57 " read from input files or directly specified via command \"-a\"\n" 58 " following this command until the next \"-d\" command.\n" 59 " -o <output file>\n" 60 " - Changes the output file to <output file>. All resources specified\n" 61 " by subsequent <input file> or \"-a\" commands will be written\n" 62 " to this file until the next output file is specified via the\n" 63 " \"-o\" command. Resources specified later overwrite earlier ones\n" 64 " with the same type and ID. If <output file> doesn't exist yet, \n" 65 " a resource file with the name will be created. If it exists and\n" 66 " is an executable file, the resources will be added to it (if the\n" 67 " file already has resources, they will be removed before). If it\n" 68 " is a resource file or a file of unknown type, it will be\n" 69 " overwritten with a resource file containing the specified\n" 70 " resources. The initial output file is \"xres.output.rsrc\".\n" 71 " Note that an output file will only be created or modified, if at\n" 72 " least one <input file> or \"-a\" command is given for it.\n" 73 " -x <type>[:<id>]\n" 74 " - Only resources with type <type> and, if given, ID <id> will be\n" 75 " written to the output file. This applies to all resources\n" 76 " read from input files or directly specified via command \"-a\"\n" 77 " following this command until the next \"-x\" command.\n" 78 " -- - All following arguments, even if starting with a \"-\" character,\n" 79 " are treated as input file names.\n" 80 "\n" 81 "Parameters:\n" 82 " <type> - A type constant consisting of exactly four characters.\n" 83 " <id> - A positive or negative integer.\n" 84 ; 85 86 87 // resource_type 88 static const char * 89 resource_type(type_code type) 90 { 91 static char typeString[5]; 92 93 typeString[0] = type >> 24; 94 typeString[1] = (type >> 16) & 0xff; 95 typeString[2] = (type >> 8) & 0xff; 96 typeString[3] = type & 0xff; 97 typeString[4] = '\0'; 98 99 return typeString; 100 } 101 102 103 // ResourceID 104 struct ResourceID { 105 type_code type; 106 int32 id; 107 bool wildcardID; 108 109 ResourceID(type_code type = B_ANY_TYPE, int32 id = 0, 110 bool wildcardID = true) 111 : 112 type(type), 113 id(id), 114 wildcardID(wildcardID) 115 { 116 } 117 118 ResourceID(const ResourceID &other) 119 { 120 *this = other; 121 } 122 123 bool Matches(const ResourceID &other) const 124 { 125 return ((type == other.type || type == B_ANY_TYPE) 126 && (wildcardID || id == other.id)); 127 } 128 129 ResourceID &operator=(const ResourceID &other) 130 { 131 type = other.type; 132 id = other.id; 133 wildcardID = other.wildcardID; 134 return *this; 135 } 136 }; 137 138 139 // ResourceDataSource 140 struct ResourceDataSource { 141 ResourceDataSource() 142 { 143 } 144 145 virtual ~ResourceDataSource() 146 { 147 } 148 149 virtual void GetData(const void *&data, size_t &size) = 0; 150 151 virtual void Flush() 152 { 153 } 154 }; 155 156 157 // MemoryResourceDataSource 158 struct MemoryResourceDataSource : ResourceDataSource { 159 MemoryResourceDataSource(const void *data, size_t size, bool clone) 160 { 161 _Init(data, size, clone); 162 } 163 164 MemoryResourceDataSource(const char *data, bool clone) 165 { 166 _Init(data, strlen(data) + 1, clone); 167 } 168 169 virtual ~MemoryResourceDataSource() 170 { 171 if (fOwner) 172 delete[] fData; 173 } 174 175 virtual void GetData(const void *&data, size_t &size) 176 { 177 data = fData; 178 size = fSize; 179 } 180 181 private: 182 void _Init(const void *data, size_t size, bool clone) 183 { 184 if (clone) { 185 fData = new uint8[size]; 186 memcpy(fData, data, size); 187 fSize = size; 188 fOwner = true; 189 } else { 190 fData = (uint8*)data; 191 fSize = size; 192 fOwner = false; 193 } 194 } 195 196 private: 197 uint8 *fData; 198 size_t fSize; 199 bool fOwner; 200 }; 201 202 203 // FileResourceDataSource 204 struct FileResourceDataSource : ResourceDataSource { 205 FileResourceDataSource(const char *path) 206 : 207 fPath(path), 208 fData(NULL), 209 fSize(0) 210 { 211 } 212 213 virtual ~FileResourceDataSource() 214 { 215 Flush(); 216 } 217 218 virtual void GetData(const void *&_data, size_t &_size) 219 { 220 if (!fData) { 221 // open the file for reading 222 BFile file; 223 status_t error = file.SetTo(fPath.c_str(), B_READ_ONLY); 224 if (error != B_OK) { 225 fprintf(stderr, "Error: Failed to open file \"%s\": %s\n", 226 fPath.c_str(), strerror(error)); 227 228 exit(1); 229 } 230 231 // get size 232 off_t size; 233 error = file.GetSize(&size); 234 if (error != B_OK) { 235 fprintf(stderr, "Error: Failed to get size of file \"%s\": " 236 "%s\n", fPath.c_str(), strerror(error)); 237 238 exit(1); 239 } 240 241 // check size 242 if (size > kMaxSaneResourceSize) { 243 fprintf(stderr, "Error: Resource data file \"%s\" is too big\n", 244 fPath.c_str()); 245 246 exit(1); 247 } 248 249 // read the data 250 fData = new uint8[size]; 251 fSize = size; 252 ssize_t bytesRead = file.ReadAt(0, fData, fSize); 253 if (bytesRead < 0) { 254 fprintf(stderr, "Error: Failed to read data size from file " 255 "\"%s\": %s\n", fPath.c_str(), strerror(bytesRead)); 256 257 exit(1); 258 } 259 } 260 261 _data = fData; 262 _size = fSize; 263 } 264 265 virtual void Flush() 266 { 267 if (fData) { 268 delete[] fData; 269 fData = NULL; 270 } 271 } 272 273 private: 274 string fPath; 275 uint8 *fData; 276 size_t fSize; 277 }; 278 279 280 // State 281 struct State { 282 State() 283 { 284 } 285 286 virtual ~State() 287 { 288 } 289 290 virtual void SetOutput(const char *path) 291 { 292 (void)path; 293 } 294 295 virtual void ProcessInput(const char *path) 296 { 297 (void)path; 298 } 299 300 virtual void SetInclusionPattern(const ResourceID &pattern) 301 { 302 (void)pattern; 303 } 304 305 virtual void SetExclusionPattern(const ResourceID &pattern) 306 { 307 (void)pattern; 308 } 309 310 virtual void AddResource(const ResourceID &id, const char *name, 311 ResourceDataSource *dataSource) 312 { 313 (void)id; 314 (void)name; 315 (void)dataSource; 316 } 317 }; 318 319 320 // ListState 321 struct ListState : State { 322 ListState() 323 { 324 } 325 326 virtual ~ListState() 327 { 328 } 329 330 virtual void ProcessInput(const char *path) 331 { 332 // open the file for reading 333 BFile file; 334 status_t error = file.SetTo(path, B_READ_ONLY); 335 if (error != B_OK) { 336 fprintf(stderr, "Error: Failed to open file \"%s\": %s\n", path, 337 strerror(error)); 338 exit(1); 339 } 340 341 // open the resources 342 BResources resources; 343 error = resources.SetTo(&file, false); 344 if (error != B_OK) { 345 if (error == B_ERROR) { 346 fprintf(stderr, "Error: File \"%s\" is not a resource file.\n", 347 path); 348 } else { 349 fprintf(stderr, "Error: Failed to read resources from file " 350 "\"%s\": %s\n", path, strerror(error)); 351 } 352 353 exit(1); 354 } 355 356 // print resources 357 printf("\n%s resources:\n\n", path); 358 printf(" type ID size name\n"); 359 printf("------ ----------- ----------- --------------------\n"); 360 361 type_code type; 362 int32 id; 363 const char *name; 364 size_t size; 365 for (int32 i = 0; 366 resources.GetResourceInfo(i, &type, &id, &name, &size); i++) { 367 printf("'%s' %11" B_PRId32 " %11lu %s\n", resource_type(type), id, 368 size, (name && strlen(name) > 0 ? name : "(no name)")); 369 } 370 } 371 }; 372 373 374 // WriteFileState 375 struct WriteFileState : State { 376 WriteFileState() 377 : 378 fOutputFilePath(kDefaultOutputFile), 379 fResources(NULL), 380 fInclusionPattern(NULL), 381 fExclusionPattern(NULL) 382 { 383 } 384 385 virtual ~WriteFileState() 386 { 387 _FlushOutput(); 388 } 389 390 virtual void SetOutput(const char *path) 391 { 392 _FlushOutput(); 393 394 fOutputFilePath = path; 395 } 396 397 virtual void ProcessInput(const char *path) 398 { 399 // open the file for reading 400 BFile file; 401 status_t error = file.SetTo(path, B_READ_ONLY); 402 if (error != B_OK) { 403 fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n", 404 path, strerror(error)); 405 exit(1); 406 } 407 408 // open the resources 409 BResources resources; 410 error = resources.SetTo(&file, false); 411 if (error != B_OK) { 412 if (error == B_ERROR) { 413 fprintf(stderr, "Error: Input file \"%s\" is not a resource " 414 "file.\n", path); 415 } else { 416 fprintf(stderr, "Error: Failed to read resources from input " 417 "file \"%s\": %s\n", path, strerror(error)); 418 } 419 420 exit(1); 421 } 422 resources.PreloadResourceType(); 423 424 // add resources 425 type_code type; 426 int32 id; 427 const char *name; 428 size_t size; 429 for (int32 i = 0; 430 resources.GetResourceInfo(i, &type, &id, &name, &size); 431 i++) { 432 // load the resource 433 const void *data = resources.LoadResource(type, id, &size); 434 if (!data) { 435 fprintf(stderr, "Error: Failed to read resources from input " 436 "file \"%s\".\n", path); 437 438 exit(1); 439 } 440 441 // add it 442 MemoryResourceDataSource dataSource(data, size, false); 443 AddResource(ResourceID(type, id), name, &dataSource); 444 } 445 } 446 447 virtual void SetInclusionPattern(const ResourceID &pattern) 448 { 449 if (!fInclusionPattern) 450 fInclusionPattern = new ResourceID; 451 *fInclusionPattern = pattern; 452 } 453 454 virtual void SetExclusionPattern(const ResourceID &pattern) 455 { 456 if (!fExclusionPattern) 457 fExclusionPattern = new ResourceID; 458 *fExclusionPattern = pattern; 459 } 460 461 virtual void AddResource(const ResourceID &id, const char *name, 462 ResourceDataSource *dataSource) 463 { 464 _PrepareOutput(); 465 466 // filter resource 467 if ((fInclusionPattern && !fInclusionPattern->Matches(id)) 468 || (fExclusionPattern && fExclusionPattern->Matches(id))) { 469 // not included or explicitly excluded 470 return; 471 } 472 473 // get resource data 474 const void *data; 475 size_t size; 476 dataSource->GetData(data, size); 477 478 // add the resource 479 status_t error = fResources->AddResource(id.type, id.id, data, size, 480 name); 481 if (error != B_OK) { 482 fprintf(stderr, "Error: Failed to add resource type '%s', ID %" 483 B_PRId32 " to output file \"%s\": %s\n", resource_type(id.type), 484 id.id, fOutputFilePath.c_str(), strerror(error)); 485 exit(1); 486 } 487 } 488 489 private: 490 void _FlushOutput() 491 { 492 if (fResources) { 493 status_t error = fResources->Sync(); 494 if (error != B_OK) { 495 fprintf(stderr, "Error: Failed to write resources to output " 496 "file \"%s\": %s\n", fOutputFilePath.c_str(), 497 strerror(error)); 498 499 exit(1); 500 } 501 502 delete fResources; 503 fResources = NULL; 504 } 505 } 506 507 void _PrepareOutput() 508 { 509 if (fResources) 510 return; 511 512 // open the file for writing 513 BFile file; 514 status_t error = file.SetTo(fOutputFilePath.c_str(), 515 B_READ_WRITE | B_CREATE_FILE); 516 if (error != B_OK) { 517 fprintf(stderr, "Error: Failed to open output file \"%s\": %s\n", 518 fOutputFilePath.c_str(), strerror(error)); 519 exit(1); 520 } 521 522 // open the resources 523 fResources = new BResources; 524 error = fResources->SetTo(&file, true); 525 if (error != B_OK) { 526 fprintf(stderr, "Error: Failed to init resources for output " 527 "file \"%s\": %s\n", fOutputFilePath.c_str(), strerror(error)); 528 529 exit(1); 530 } 531 } 532 533 private: 534 string fOutputFilePath; 535 BResources *fResources; 536 ResourceID *fInclusionPattern; 537 ResourceID *fExclusionPattern; 538 }; 539 540 541 // Command 542 struct Command { 543 Command() 544 { 545 } 546 547 virtual ~Command() 548 { 549 } 550 551 virtual void Do(State *state) = 0; 552 }; 553 554 555 // SetOutputCommand 556 struct SetOutputCommand : Command { 557 SetOutputCommand(const char *path) 558 : 559 Command(), 560 fPath(path) 561 { 562 } 563 564 virtual void Do(State *state) 565 { 566 state->SetOutput(fPath.c_str()); 567 } 568 569 private: 570 string fPath; 571 }; 572 573 574 // ProcessInputCommand 575 struct ProcessInputCommand : Command { 576 ProcessInputCommand(const char *path) 577 : 578 Command(), 579 fPath(path) 580 { 581 } 582 583 virtual void Do(State *state) 584 { 585 state->ProcessInput(fPath.c_str()); 586 } 587 588 private: 589 string fPath; 590 }; 591 592 593 // SetResourcePatternCommand 594 struct SetResourcePatternCommand : Command { 595 SetResourcePatternCommand(const ResourceID &pattern, bool inclusion) 596 : 597 Command(), 598 fPattern(pattern), 599 fInclusion(inclusion) 600 { 601 } 602 603 virtual void Do(State *state) 604 { 605 if (fInclusion) 606 state->SetInclusionPattern(fPattern); 607 else 608 state->SetExclusionPattern(fPattern); 609 } 610 611 private: 612 ResourceID fPattern; 613 bool fInclusion; 614 }; 615 616 617 // AddResourceCommand 618 struct AddResourceCommand : Command { 619 AddResourceCommand(const ResourceID &id, const char *name, 620 ResourceDataSource *dataSource) 621 : 622 Command(), 623 fID(id), 624 fHasName(name), 625 fDataSource(dataSource) 626 { 627 if (fHasName) 628 fName = name; 629 } 630 631 virtual ~AddResourceCommand() 632 { 633 delete fDataSource; 634 } 635 636 virtual void Do(State *state) 637 { 638 state->AddResource(fID, (fHasName ? fName.c_str() : NULL), 639 fDataSource); 640 fDataSource->Flush(); 641 } 642 643 private: 644 ResourceID fID; 645 string fName; 646 bool fHasName; 647 ResourceDataSource *fDataSource; 648 }; 649 650 651 // print_usage 652 static void 653 print_usage(bool error) 654 { 655 // get command name 656 const char *commandName = NULL; 657 if (kArgc > 0) { 658 if (const char *lastSlash = strchr(kArgv[0], '/')) 659 commandName = lastSlash + 1; 660 else 661 commandName = kArgv[0]; 662 } 663 664 if (!commandName || strlen(commandName) == 0) 665 commandName = kCommandName; 666 667 // print usage 668 fprintf((error ? stderr : stdout), kUsage, commandName, commandName, 669 commandName); 670 } 671 672 673 // print_usage_and_exit 674 static void 675 print_usage_and_exit(bool error) 676 { 677 print_usage(error); 678 exit(error ? 1 : 0); 679 } 680 681 682 // next_arg 683 static const char * 684 next_arg(int &argi, bool optional = false) 685 { 686 if (argi >= kArgc) { 687 if (!optional) 688 print_usage_and_exit(true); 689 return NULL; 690 } 691 692 return kArgv[argi++]; 693 } 694 695 696 // parse_resource_id 697 static void 698 parse_resource_id(const char *toParse, ResourceID &resourceID, 699 const char **name = NULL) 700 { 701 int len = strlen(toParse); 702 703 // type 704 if (len < 4) 705 print_usage_and_exit(true); 706 707 resourceID.type = ((int32)toParse[0] << 24) | ((int32)toParse[1] << 16) 708 | ((int32)toParse[2] << 8) | (int32)toParse[3]; 709 710 if (toParse[4] == '\0') { 711 // if a name can be provided, the ID is mandatory 712 if (name) 713 print_usage_and_exit(true); 714 715 resourceID.id = 0; 716 resourceID.wildcardID = true; 717 return; 718 } 719 720 if (toParse[4] != ':') 721 print_usage_and_exit(true); 722 723 toParse += 5; 724 len -= 5; 725 726 // ID 727 bool negative = false; 728 if (*toParse == '-') { 729 negative = true; 730 toParse++; 731 len--; 732 } 733 734 if (*toParse < '0' || *toParse > '9') 735 print_usage_and_exit(true); 736 737 int id = 0; 738 while (*toParse >= '0' && *toParse <= '9') { 739 id = 10 * id + (*toParse - '0'); 740 toParse++; 741 len--; 742 } 743 744 resourceID.wildcardID = false; 745 resourceID.id = (negative ? -id : id); 746 747 if (*toParse == '\0') { 748 if (name) 749 *name = kDefaultResourceName; 750 return; 751 } 752 753 if (*toParse != ':') 754 print_usage_and_exit(true); 755 756 // the remainder is name 757 *name = toParse + 1; 758 } 759 760 761 // main 762 int 763 main(int argc, const char *const *argv) 764 { 765 kArgc = argc; 766 kArgv = argv; 767 768 if (argc < 2) 769 print_usage_and_exit(true); 770 771 BList commandList; 772 773 // parse the arguments 774 bool noMoreOptions = false; 775 bool list = false; 776 bool noList = false; 777 bool hasInputFiles = false; 778 for (int argi = 1; argi < argc; ) { 779 const char *arg = argv[argi++]; 780 if (!noMoreOptions && arg[0] == '-') { 781 if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) 782 print_usage_and_exit(false); 783 784 if (strlen(arg) != 2) 785 print_usage_and_exit(true); 786 787 switch (arg[1]) { 788 case 'a': 789 { 790 noList = true; 791 792 // get id 793 const char *typeString = next_arg(argi); 794 ResourceID resourceID; 795 const char *name = NULL; 796 parse_resource_id(typeString, resourceID, &name); 797 798 // get data 799 const char *file = next_arg(argi); 800 ResourceDataSource *dataSource; 801 if (strcmp(file, "-s") == 0) { 802 const char *data = next_arg(argi); 803 dataSource = new MemoryResourceDataSource(data, false); 804 805 } else { 806 dataSource = new FileResourceDataSource(file); 807 } 808 809 // add command 810 Command *command = new AddResourceCommand(resourceID, 811 name, dataSource); 812 commandList.AddItem(command); 813 814 break; 815 } 816 817 case 'd': 818 { 819 noList = true; 820 821 // get pattern 822 const char *typeString = next_arg(argi); 823 ResourceID pattern; 824 parse_resource_id(typeString, pattern); 825 826 // add command 827 Command *command = new SetResourcePatternCommand(pattern, 828 false); 829 commandList.AddItem(command); 830 831 break; 832 } 833 834 case 'l': 835 { 836 list = true; 837 break; 838 } 839 840 case 'o': 841 { 842 noList = true; 843 844 // get file name 845 const char *out = next_arg(argi); 846 847 // add command 848 Command *command = new SetOutputCommand(out); 849 commandList.AddItem(command); 850 851 break; 852 } 853 854 case 'x': 855 { 856 noList = true; 857 858 // get pattern 859 const char *typeString = next_arg(argi); 860 ResourceID pattern; 861 parse_resource_id(typeString, pattern); 862 863 // add command 864 Command *command = new SetResourcePatternCommand(pattern, 865 true); 866 commandList.AddItem(command); 867 868 break; 869 } 870 871 case '-': 872 noMoreOptions = true; 873 break; 874 875 default: 876 print_usage_and_exit(true); 877 break; 878 } 879 880 } else { 881 // input file 882 hasInputFiles = true; 883 Command *command = new ProcessInputCommand(arg); 884 commandList.AddItem(command); 885 } 886 } 887 888 // don't allow "-l" together with other comands or without at least one 889 // input file 890 if ((list && noList) || (list && !hasInputFiles)) 891 print_usage_and_exit(true); 892 893 // create a state 894 State *state; 895 if (list) 896 state = new ListState(); 897 else 898 state = new WriteFileState(); 899 900 // process commands 901 for (int32 i = 0; Command *command = (Command*)commandList.ItemAt(i); i++) 902 command->Do(state); 903 904 // delete state (will flush resources) 905 delete state; 906 907 return 0; 908 } 909