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
19using namespace std;
20
21
22static const char *kCommandName = "xres";
23static const char *kDefaultResourceName = NULL;
24static const char *kDefaultOutputFile = "xres.output.rsrc";
25static const int kMaxSaneResourceSize = 100 * 1024 * 1024;	// 100 MB
26
27static int kArgc;
28static const char *const *kArgv;
29
30// usage
31const 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
88static const char *
89resource_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
104struct 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
140struct 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
158struct 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
181private:
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
196private:
197	uint8	*fData;
198	size_t	fSize;
199	bool	fOwner;
200};
201
202
203// FileResourceDataSource
204struct 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
273private:
274	string	fPath;
275	uint8	*fData;
276	size_t	fSize;
277};
278
279
280// State
281struct 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
321struct 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
376struct 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
490private:
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
534private:
535	string		fOutputFilePath;
536	BResources	*fResources;
537	ResourceID	*fInclusionPattern;
538	ResourceID	*fExclusionPattern;
539};
540
541
542// Command
543struct Command {
544	Command()
545	{
546	}
547
548	virtual ~Command()
549	{
550	}
551
552	virtual void Do(State *state) = 0;
553};
554
555
556// SetOutputCommand
557struct 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
570private:
571	string	fPath;
572};
573
574
575// ProcessInputCommand
576struct 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
589private:
590	string	fPath;
591};
592
593
594// SetResourcePatternCommand
595struct 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
612private:
613	ResourceID	fPattern;
614	bool		fInclusion;
615};
616
617
618// AddResourceCommand
619struct 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
644private:
645	ResourceID			fID;
646	string				fName;
647	bool				fHasName;
648	ResourceDataSource	*fDataSource;
649};
650
651
652// print_usage
653static void
654print_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
675static void
676print_usage_and_exit(bool error)
677{
678	print_usage(error);
679	exit(error ? 1 : 0);
680}
681
682
683// next_arg
684static const char *
685next_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
698static void
699parse_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
763int
764main(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