1 /* 2 * Copyright 2005-2010, 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 " %11" B_PRIuSIZE " %s\n", 368 resource_type(type), id, size, 369 name != NULL && name[0] != '\0' ? name : "(no name)"); 370 } 371 } 372 }; 373 374 375 // WriteFileState 376 struct WriteFileState : State { 377 WriteFileState() 378 : 379 fOutputFilePath(kDefaultOutputFile), 380 fResources(NULL), 381 fInclusionPattern(NULL), 382 fExclusionPattern(NULL) 383 { 384 } 385 386 virtual ~WriteFileState() 387 { 388 _FlushOutput(); 389 } 390 391 virtual void SetOutput(const char *path) 392 { 393 _FlushOutput(); 394 395 fOutputFilePath = path; 396 } 397 398 virtual void ProcessInput(const char *path) 399 { 400 // open the file for reading 401 BFile file; 402 status_t error = file.SetTo(path, B_READ_ONLY); 403 if (error != B_OK) { 404 fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n", 405 path, strerror(error)); 406 exit(1); 407 } 408 409 // open the resources 410 BResources resources; 411 error = resources.SetTo(&file, false); 412 if (error != B_OK) { 413 if (error == B_ERROR) { 414 fprintf(stderr, "Error: Input file \"%s\" is not a resource " 415 "file.\n", path); 416 } else { 417 fprintf(stderr, "Error: Failed to read resources from input " 418 "file \"%s\": %s\n", path, strerror(error)); 419 } 420 421 exit(1); 422 } 423 resources.PreloadResourceType(); 424 425 // add resources 426 type_code type; 427 int32 id; 428 const char *name; 429 size_t size; 430 for (int32 i = 0; 431 resources.GetResourceInfo(i, &type, &id, &name, &size); 432 i++) { 433 // load the resource 434 const void *data = resources.LoadResource(type, id, &size); 435 if (!data) { 436 fprintf(stderr, "Error: Failed to read resources from input " 437 "file \"%s\".\n", path); 438 439 exit(1); 440 } 441 442 // add it 443 MemoryResourceDataSource dataSource(data, size, false); 444 AddResource(ResourceID(type, id), name, &dataSource); 445 } 446 } 447 448 virtual void SetInclusionPattern(const ResourceID &pattern) 449 { 450 if (!fInclusionPattern) 451 fInclusionPattern = new ResourceID; 452 *fInclusionPattern = pattern; 453 } 454 455 virtual void SetExclusionPattern(const ResourceID &pattern) 456 { 457 if (!fExclusionPattern) 458 fExclusionPattern = new ResourceID; 459 *fExclusionPattern = pattern; 460 } 461 462 virtual void AddResource(const ResourceID &id, const char *name, 463 ResourceDataSource *dataSource) 464 { 465 _PrepareOutput(); 466 467 // filter resource 468 if ((fInclusionPattern && !fInclusionPattern->Matches(id)) 469 || (fExclusionPattern && fExclusionPattern->Matches(id))) { 470 // not included or explicitly excluded 471 return; 472 } 473 474 // get resource data 475 const void *data; 476 size_t size; 477 dataSource->GetData(data, size); 478 479 // add the resource 480 status_t error = fResources->AddResource(id.type, id.id, data, size, 481 name); 482 if (error != B_OK) { 483 fprintf(stderr, "Error: Failed to add resource type '%s', ID %" 484 B_PRId32 " to output file \"%s\": %s\n", resource_type(id.type), 485 id.id, fOutputFilePath.c_str(), strerror(error)); 486 exit(1); 487 } 488 } 489 490 private: 491 void _FlushOutput() 492 { 493 if (fResources) { 494 status_t error = fResources->Sync(); 495 if (error != B_OK) { 496 fprintf(stderr, "Error: Failed to write resources to output " 497 "file \"%s\": %s\n", fOutputFilePath.c_str(), 498 strerror(error)); 499 500 exit(1); 501 } 502 503 delete fResources; 504 fResources = NULL; 505 } 506 } 507 508 void _PrepareOutput() 509 { 510 if (fResources) 511 return; 512 513 // open the file for writing 514 BFile file; 515 status_t error = file.SetTo(fOutputFilePath.c_str(), 516 B_READ_WRITE | B_CREATE_FILE); 517 if (error != B_OK) { 518 fprintf(stderr, "Error: Failed to open output file \"%s\": %s\n", 519 fOutputFilePath.c_str(), strerror(error)); 520 exit(1); 521 } 522 523 // open the resources 524 fResources = new BResources; 525 error = fResources->SetTo(&file, true); 526 if (error != B_OK) { 527 fprintf(stderr, "Error: Failed to init resources for output " 528 "file \"%s\": %s\n", fOutputFilePath.c_str(), strerror(error)); 529 530 exit(1); 531 } 532 } 533 534 private: 535 string fOutputFilePath; 536 BResources *fResources; 537 ResourceID *fInclusionPattern; 538 ResourceID *fExclusionPattern; 539 }; 540 541 542 // Command 543 struct Command { 544 Command() 545 { 546 } 547 548 virtual ~Command() 549 { 550 } 551 552 virtual void Do(State *state) = 0; 553 }; 554 555 556 // SetOutputCommand 557 struct SetOutputCommand : Command { 558 SetOutputCommand(const char *path) 559 : 560 Command(), 561 fPath(path) 562 { 563 } 564 565 virtual void Do(State *state) 566 { 567 state->SetOutput(fPath.c_str()); 568 } 569 570 private: 571 string fPath; 572 }; 573 574 575 // ProcessInputCommand 576 struct ProcessInputCommand : Command { 577 ProcessInputCommand(const char *path) 578 : 579 Command(), 580 fPath(path) 581 { 582 } 583 584 virtual void Do(State *state) 585 { 586 state->ProcessInput(fPath.c_str()); 587 } 588 589 private: 590 string fPath; 591 }; 592 593 594 // SetResourcePatternCommand 595 struct SetResourcePatternCommand : Command { 596 SetResourcePatternCommand(const ResourceID &pattern, bool inclusion) 597 : 598 Command(), 599 fPattern(pattern), 600 fInclusion(inclusion) 601 { 602 } 603 604 virtual void Do(State *state) 605 { 606 if (fInclusion) 607 state->SetInclusionPattern(fPattern); 608 else 609 state->SetExclusionPattern(fPattern); 610 } 611 612 private: 613 ResourceID fPattern; 614 bool fInclusion; 615 }; 616 617 618 // AddResourceCommand 619 struct AddResourceCommand : Command { 620 AddResourceCommand(const ResourceID &id, const char *name, 621 ResourceDataSource *dataSource) 622 : 623 Command(), 624 fID(id), 625 fHasName(name), 626 fDataSource(dataSource) 627 { 628 if (fHasName) 629 fName = name; 630 } 631 632 virtual ~AddResourceCommand() 633 { 634 delete fDataSource; 635 } 636 637 virtual void Do(State *state) 638 { 639 state->AddResource(fID, (fHasName ? fName.c_str() : NULL), 640 fDataSource); 641 fDataSource->Flush(); 642 } 643 644 private: 645 ResourceID fID; 646 string fName; 647 bool fHasName; 648 ResourceDataSource *fDataSource; 649 }; 650 651 652 // print_usage 653 static void 654 print_usage(bool error) 655 { 656 // get command name 657 const char *commandName = NULL; 658 if (kArgc > 0) { 659 if (const char *lastSlash = strchr(kArgv[0], '/')) 660 commandName = lastSlash + 1; 661 else 662 commandName = kArgv[0]; 663 } 664 665 if (!commandName || commandName[0] == '\0') 666 commandName = kCommandName; 667 668 // print usage 669 fprintf((error ? stderr : stdout), kUsage, commandName, commandName, 670 commandName); 671 } 672 673 674 // print_usage_and_exit 675 static void 676 print_usage_and_exit(bool error) 677 { 678 print_usage(error); 679 exit(error ? 1 : 0); 680 } 681 682 683 // next_arg 684 static const char * 685 next_arg(int &argi, bool optional = false) 686 { 687 if (argi >= kArgc) { 688 if (!optional) 689 print_usage_and_exit(true); 690 return NULL; 691 } 692 693 return kArgv[argi++]; 694 } 695 696 697 // parse_resource_id 698 static void 699 parse_resource_id(const char *toParse, ResourceID &resourceID, 700 const char **name = NULL) 701 { 702 int len = strlen(toParse); 703 704 // type 705 if (len < 4) 706 print_usage_and_exit(true); 707 708 resourceID.type = ((int32)toParse[0] << 24) | ((int32)toParse[1] << 16) 709 | ((int32)toParse[2] << 8) | (int32)toParse[3]; 710 711 if (toParse[4] == '\0') { 712 // if a name can be provided, the ID is mandatory 713 if (name) 714 print_usage_and_exit(true); 715 716 resourceID.id = 0; 717 resourceID.wildcardID = true; 718 return; 719 } 720 721 if (toParse[4] != ':') 722 print_usage_and_exit(true); 723 724 toParse += 5; 725 len -= 5; 726 727 // ID 728 bool negative = false; 729 if (*toParse == '-') { 730 negative = true; 731 toParse++; 732 len--; 733 } 734 735 if (*toParse < '0' || *toParse > '9') 736 print_usage_and_exit(true); 737 738 int id = 0; 739 while (*toParse >= '0' && *toParse <= '9') { 740 id = 10 * id + (*toParse - '0'); 741 toParse++; 742 len--; 743 } 744 745 resourceID.wildcardID = false; 746 resourceID.id = (negative ? -id : id); 747 748 if (*toParse == '\0') { 749 if (name) { 750 *name = kDefaultResourceName; 751 return; 752 } 753 } 754 755 if (*toParse != ':') 756 print_usage_and_exit(true); 757 758 // the remainder is name 759 *name = toParse + 1; 760 } 761 762 763 // main 764 int 765 main(int argc, const char *const *argv) 766 { 767 kArgc = argc; 768 kArgv = argv; 769 770 if (argc < 2) 771 print_usage_and_exit(true); 772 773 BList commandList; 774 775 // parse the arguments 776 bool noMoreOptions = false; 777 bool list = false; 778 bool noList = false; 779 bool hasInputFiles = false; 780 for (int argi = 1; argi < argc; ) { 781 const char *arg = argv[argi++]; 782 if (!noMoreOptions && arg[0] == '-') { 783 if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) 784 print_usage_and_exit(false); 785 786 if (strlen(arg) != 2) 787 print_usage_and_exit(true); 788 789 switch (arg[1]) { 790 case 'a': 791 { 792 noList = true; 793 794 // get id 795 const char *typeString = next_arg(argi); 796 ResourceID resourceID; 797 const char *name = NULL; 798 parse_resource_id(typeString, resourceID, &name); 799 800 // get data 801 const char *file = next_arg(argi); 802 ResourceDataSource *dataSource; 803 if (strcmp(file, "-s") == 0) { 804 const char *data = next_arg(argi); 805 dataSource = new MemoryResourceDataSource(data, false); 806 807 } else { 808 dataSource = new FileResourceDataSource(file); 809 } 810 811 // add command 812 Command *command = new AddResourceCommand(resourceID, 813 name, dataSource); 814 commandList.AddItem(command); 815 816 break; 817 } 818 819 case 'd': 820 { 821 noList = true; 822 823 // get pattern 824 const char *typeString = next_arg(argi); 825 ResourceID pattern; 826 parse_resource_id(typeString, pattern); 827 828 // add command 829 Command *command = new SetResourcePatternCommand(pattern, 830 false); 831 commandList.AddItem(command); 832 833 break; 834 } 835 836 case 'l': 837 { 838 list = true; 839 break; 840 } 841 842 case 'o': 843 { 844 noList = true; 845 846 // get file name 847 const char *out = next_arg(argi); 848 849 // add command 850 Command *command = new SetOutputCommand(out); 851 commandList.AddItem(command); 852 853 break; 854 } 855 856 case 'x': 857 { 858 noList = true; 859 860 // get pattern 861 const char *typeString = next_arg(argi); 862 ResourceID pattern; 863 parse_resource_id(typeString, pattern); 864 865 // add command 866 Command *command = new SetResourcePatternCommand(pattern, 867 true); 868 commandList.AddItem(command); 869 870 break; 871 } 872 873 case '-': 874 noMoreOptions = true; 875 break; 876 877 default: 878 print_usage_and_exit(true); 879 break; 880 } 881 882 } else { 883 // input file 884 hasInputFiles = true; 885 Command *command = new ProcessInputCommand(arg); 886 commandList.AddItem(command); 887 } 888 } 889 890 // don't allow "-l" together with other comands or without at least one 891 // input file 892 if ((list && noList) || (list && !hasInputFiles)) 893 print_usage_and_exit(true); 894 895 // create a state 896 State *state; 897 if (list) 898 state = new ListState(); 899 else 900 state = new WriteFileState(); 901 902 // process commands 903 for (int32 i = 0; Command *command = (Command*)commandList.ItemAt(i); i++) 904 command->Do(state); 905 906 // delete state (will flush resources) 907 delete state; 908 909 return 0; 910 } 911