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 *
resource_type(type_code type)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
ResourceIDResourceID109 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
ResourceIDResourceID118 ResourceID(const ResourceID &other)
119 {
120 *this = other;
121 }
122
MatchesResourceID123 bool Matches(const ResourceID &other) const
124 {
125 return ((type == other.type || type == B_ANY_TYPE)
126 && (wildcardID || id == other.id));
127 }
128
operator =ResourceID129 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 {
ResourceDataSourceResourceDataSource141 ResourceDataSource()
142 {
143 }
144
~ResourceDataSourceResourceDataSource145 virtual ~ResourceDataSource()
146 {
147 }
148
149 virtual void GetData(const void *&data, size_t &size) = 0;
150
FlushResourceDataSource151 virtual void Flush()
152 {
153 }
154 };
155
156
157 // MemoryResourceDataSource
158 struct MemoryResourceDataSource : ResourceDataSource {
MemoryResourceDataSourceMemoryResourceDataSource159 MemoryResourceDataSource(const void *data, size_t size, bool clone)
160 {
161 _Init(data, size, clone);
162 }
163
MemoryResourceDataSourceMemoryResourceDataSource164 MemoryResourceDataSource(const char *data, bool clone)
165 {
166 _Init(data, strlen(data) + 1, clone);
167 }
168
~MemoryResourceDataSourceMemoryResourceDataSource169 virtual ~MemoryResourceDataSource()
170 {
171 if (fOwner)
172 delete[] fData;
173 }
174
GetDataMemoryResourceDataSource175 virtual void GetData(const void *&data, size_t &size)
176 {
177 data = fData;
178 size = fSize;
179 }
180
181 private:
_InitMemoryResourceDataSource182 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 {
FileResourceDataSourceFileResourceDataSource205 FileResourceDataSource(const char *path)
206 :
207 fPath(path),
208 fData(NULL),
209 fSize(0)
210 {
211 }
212
~FileResourceDataSourceFileResourceDataSource213 virtual ~FileResourceDataSource()
214 {
215 Flush();
216 }
217
GetDataFileResourceDataSource218 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
FlushFileResourceDataSource265 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 {
StateState282 State()
283 {
284 }
285
~StateState286 virtual ~State()
287 {
288 }
289
SetOutputState290 virtual void SetOutput(const char *path)
291 {
292 (void)path;
293 }
294
ProcessInputState295 virtual void ProcessInput(const char *path)
296 {
297 (void)path;
298 }
299
SetInclusionPatternState300 virtual void SetInclusionPattern(const ResourceID &pattern)
301 {
302 (void)pattern;
303 }
304
SetExclusionPatternState305 virtual void SetExclusionPattern(const ResourceID &pattern)
306 {
307 (void)pattern;
308 }
309
AddResourceState310 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 {
ListStateListState322 ListState()
323 {
324 }
325
~ListStateListState326 virtual ~ListState()
327 {
328 }
329
ProcessInputListState330 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 {
WriteFileStateWriteFileState377 WriteFileState()
378 :
379 fOutputFilePath(kDefaultOutputFile),
380 fResources(NULL),
381 fInclusionPattern(NULL),
382 fExclusionPattern(NULL)
383 {
384 }
385
~WriteFileStateWriteFileState386 virtual ~WriteFileState()
387 {
388 _FlushOutput();
389 }
390
SetOutputWriteFileState391 virtual void SetOutput(const char *path)
392 {
393 _FlushOutput();
394
395 fOutputFilePath = path;
396 }
397
ProcessInputWriteFileState398 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
SetInclusionPatternWriteFileState448 virtual void SetInclusionPattern(const ResourceID &pattern)
449 {
450 if (!fInclusionPattern)
451 fInclusionPattern = new ResourceID;
452 *fInclusionPattern = pattern;
453 }
454
SetExclusionPatternWriteFileState455 virtual void SetExclusionPattern(const ResourceID &pattern)
456 {
457 if (!fExclusionPattern)
458 fExclusionPattern = new ResourceID;
459 *fExclusionPattern = pattern;
460 }
461
AddResourceWriteFileState462 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:
_FlushOutputWriteFileState491 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
_PrepareOutputWriteFileState508 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 {
CommandCommand544 Command()
545 {
546 }
547
~CommandCommand548 virtual ~Command()
549 {
550 }
551
552 virtual void Do(State *state) = 0;
553 };
554
555
556 // SetOutputCommand
557 struct SetOutputCommand : Command {
SetOutputCommandSetOutputCommand558 SetOutputCommand(const char *path)
559 :
560 Command(),
561 fPath(path)
562 {
563 }
564
DoSetOutputCommand565 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 {
ProcessInputCommandProcessInputCommand577 ProcessInputCommand(const char *path)
578 :
579 Command(),
580 fPath(path)
581 {
582 }
583
DoProcessInputCommand584 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 {
SetResourcePatternCommandSetResourcePatternCommand596 SetResourcePatternCommand(const ResourceID &pattern, bool inclusion)
597 :
598 Command(),
599 fPattern(pattern),
600 fInclusion(inclusion)
601 {
602 }
603
DoSetResourcePatternCommand604 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 {
AddResourceCommandAddResourceCommand620 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
~AddResourceCommandAddResourceCommand632 virtual ~AddResourceCommand()
633 {
634 delete fDataSource;
635 }
636
DoAddResourceCommand637 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
print_usage(bool error)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
print_usage_and_exit(bool error)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 *
next_arg(int & argi,bool optional=false)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
parse_resource_id(const char * toParse,ResourceID & resourceID,const char ** name=NULL)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 if (*toParse != ':')
755 print_usage_and_exit(true);
756
757 // the remainder is name
758 *name = toParse + 1;
759 }
760
761
762 // main
763 int
main(int argc,const char * const * argv)764 main(int argc, const char *const *argv)
765 {
766 kArgc = argc;
767 kArgv = argv;
768
769 if (argc < 2)
770 print_usage_and_exit(true);
771
772 BList commandList;
773
774 // parse the arguments
775 bool noMoreOptions = false;
776 bool list = false;
777 bool noList = false;
778 bool hasInputFiles = false;
779 for (int argi = 1; argi < argc; ) {
780 const char *arg = argv[argi++];
781 if (!noMoreOptions && arg[0] == '-') {
782 if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0)
783 print_usage_and_exit(false);
784
785 if (strlen(arg) != 2)
786 print_usage_and_exit(true);
787
788 switch (arg[1]) {
789 case 'a':
790 {
791 noList = true;
792
793 // get id
794 const char *typeString = next_arg(argi);
795 ResourceID resourceID;
796 const char *name = NULL;
797 parse_resource_id(typeString, resourceID, &name);
798
799 // get data
800 const char *file = next_arg(argi);
801 ResourceDataSource *dataSource;
802 if (strcmp(file, "-s") == 0) {
803 const char *data = next_arg(argi);
804 dataSource = new MemoryResourceDataSource(data, false);
805
806 } else {
807 dataSource = new FileResourceDataSource(file);
808 }
809
810 // add command
811 Command *command = new AddResourceCommand(resourceID,
812 name, dataSource);
813 commandList.AddItem(command);
814
815 break;
816 }
817
818 case 'd':
819 {
820 noList = true;
821
822 // get pattern
823 const char *typeString = next_arg(argi);
824 ResourceID pattern;
825 parse_resource_id(typeString, pattern);
826
827 // add command
828 Command *command = new SetResourcePatternCommand(pattern,
829 false);
830 commandList.AddItem(command);
831
832 break;
833 }
834
835 case 'l':
836 {
837 list = true;
838 break;
839 }
840
841 case 'o':
842 {
843 noList = true;
844
845 // get file name
846 const char *out = next_arg(argi);
847
848 // add command
849 Command *command = new SetOutputCommand(out);
850 commandList.AddItem(command);
851
852 break;
853 }
854
855 case 'x':
856 {
857 noList = true;
858
859 // get pattern
860 const char *typeString = next_arg(argi);
861 ResourceID pattern;
862 parse_resource_id(typeString, pattern);
863
864 // add command
865 Command *command = new SetResourcePatternCommand(pattern,
866 true);
867 commandList.AddItem(command);
868
869 break;
870 }
871
872 case '-':
873 noMoreOptions = true;
874 break;
875
876 default:
877 print_usage_and_exit(true);
878 break;
879 }
880
881 } else {
882 // input file
883 hasInputFiles = true;
884 Command *command = new ProcessInputCommand(arg);
885 commandList.AddItem(command);
886 }
887 }
888
889 // don't allow "-l" together with other comands or without at least one
890 // input file
891 if ((list && noList) || (list && !hasInputFiles))
892 print_usage_and_exit(true);
893
894 // create a state
895 State *state;
896 if (list)
897 state = new ListState();
898 else
899 state = new WriteFileState();
900
901 // process commands
902 for (int32 i = 0; Command *command = (Command*)commandList.ItemAt(i); i++)
903 command->Do(state);
904
905 // delete state (will flush resources)
906 delete state;
907
908 return 0;
909 }
910