xref: /haiku/src/bin/xres.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
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