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