1 /* windres.c -- a program to manipulate Windows resources
2 Copyright 1997, 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
3 Written by Ian Lance Taylor, Cygnus Support.
5 This file is part of GNU Binutils.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 /* This program can read and write Windows resources in various
23 formats. In particular, it can act like the rc resource compiler
24 program, and it can act like the cvtres res to COFF conversion
27 It is based on information taken from the following sources:
29 * Microsoft documentation.
31 * The rcl program, written by Gunther Ebert
32 <gunther.ebert@ixos-leipzig.de>.
34 * The res2coff program, written by Pedro A. Aranda <paag@tid.es>.
41 #include "libiberty.h"
42 #include "safe-ctype.h"
49 /* used by resrc.c at least */
53 /* An enumeration of format types. */
59 /* Textual RC file. */
61 /* Binary RES file. */
67 /* A structure used to map between format types and strings. */
72 enum res_format format
;
75 /* A mapping between names and format types. */
77 static const struct format_map format_names
[] =
79 { "rc", RES_FORMAT_RC
},
80 { "res", RES_FORMAT_RES
},
81 { "coff", RES_FORMAT_COFF
},
82 { NULL
, RES_FORMAT_UNKNOWN
}
85 /* A mapping from file extensions to format types. */
87 static const struct format_map format_fileexts
[] =
89 { "rc", RES_FORMAT_RC
},
90 { "res", RES_FORMAT_RES
},
91 { "exe", RES_FORMAT_COFF
},
92 { "obj", RES_FORMAT_COFF
},
93 { "o", RES_FORMAT_COFF
},
94 { NULL
, RES_FORMAT_UNKNOWN
}
97 /* A list of include directories. */
101 struct include_dir
*next
;
105 static struct include_dir
*include_dirs
;
109 /* 150 isn't special; it's just an arbitrary non-ASCII char value. */
111 #define OPTION_DEFINE 150
112 #define OPTION_HELP (OPTION_DEFINE + 1)
113 #define OPTION_INCLUDE_DIR (OPTION_HELP + 1)
114 #define OPTION_LANGUAGE (OPTION_INCLUDE_DIR + 1)
115 #define OPTION_PREPROCESSOR (OPTION_LANGUAGE + 1)
116 #define OPTION_USE_TEMP_FILE (OPTION_PREPROCESSOR + 1)
117 #define OPTION_NO_USE_TEMP_FILE (OPTION_USE_TEMP_FILE + 1)
118 #define OPTION_VERSION (OPTION_NO_USE_TEMP_FILE + 1)
119 #define OPTION_YYDEBUG (OPTION_VERSION + 1)
121 static const struct option long_options
[] =
123 {"define", required_argument
, 0, OPTION_DEFINE
},
124 {"help", no_argument
, 0, OPTION_HELP
},
125 {"include-dir", required_argument
, 0, OPTION_INCLUDE_DIR
},
126 {"input-format", required_argument
, 0, 'I'},
127 {"language", required_argument
, 0, OPTION_LANGUAGE
},
128 {"output-format", required_argument
, 0, 'O'},
129 {"preprocessor", required_argument
, 0, OPTION_PREPROCESSOR
},
130 {"target", required_argument
, 0, 'F'},
131 {"use-temp-file", no_argument
, 0, OPTION_USE_TEMP_FILE
},
132 {"no-use-temp-file", no_argument
, 0, OPTION_NO_USE_TEMP_FILE
},
133 {"verbose", no_argument
, 0, 'v'},
134 {"version", no_argument
, 0, OPTION_VERSION
},
135 {"yydebug", no_argument
, 0, OPTION_YYDEBUG
},
136 {0, no_argument
, 0, 0}
139 /* Static functions. */
141 static void res_init
PARAMS ((void));
142 static int extended_menuitems
PARAMS ((const struct menuitem
*));
143 static enum res_format format_from_name
PARAMS ((const char *));
144 static enum res_format format_from_filename
PARAMS ((const char *, int));
145 static void usage
PARAMS ((FILE *, int));
146 static int cmp_res_entry
PARAMS ((const PTR
, const PTR
));
147 static struct res_directory
*sort_resources
PARAMS ((struct res_directory
*));
148 static void reswr_init
PARAMS ((void));
149 static const char * quot
PARAMS ((const char *));
151 /* When we are building a resource tree, we allocate everything onto
152 an obstack, so that we can free it all at once if we want. */
154 #define obstack_chunk_alloc xmalloc
155 #define obstack_chunk_free free
157 /* The resource building obstack. */
159 static struct obstack res_obstack
;
161 /* Initialize the resource building obstack. */
166 obstack_init (&res_obstack
);
169 /* Allocate space on the resource building obstack. */
175 return (PTR
) obstack_alloc (&res_obstack
, bytes
);
178 /* We also use an obstack to save memory used while writing out a set
181 static struct obstack reswr_obstack
;
183 /* Initialize the resource writing obstack. */
188 obstack_init (&reswr_obstack
);
191 /* Allocate space on the resource writing obstack. */
197 return (PTR
) obstack_alloc (&reswr_obstack
, bytes
);
200 /* Open a file using the include directory search list. */
203 open_file_search (filename
, mode
, errmsg
, real_filename
)
204 const char *filename
;
207 char **real_filename
;
210 struct include_dir
*d
;
212 e
= fopen (filename
, mode
);
215 *real_filename
= xstrdup (filename
);
221 for (d
= include_dirs
; d
!= NULL
; d
= d
->next
)
225 n
= (char *) xmalloc (strlen (d
->dir
) + strlen (filename
) + 2);
226 sprintf (n
, "%s/%s", d
->dir
, filename
);
239 fatal (_("can't open %s `%s': %s"), errmsg
, filename
, strerror (errno
));
241 /* Return a value to avoid a compiler warning. */
245 /* Compare two resource ID's. We consider name entries to come before
246 numeric entries, because that is how they appear in the COFF .rsrc
260 else if (a
.u
.id
< b
.u
.id
)
267 unichar
*as
, *ase
, *bs
, *bse
;
273 ase
= as
+ a
.u
.n
.length
;
275 bse
= bs
+ b
.u
.n
.length
;
283 i
= (int) *as
- (int) *bs
;
297 /* Print a resource ID. */
300 res_id_print (stream
, id
, quote
)
306 fprintf (stream
, "%lu", id
.u
.id
);
311 unicode_print (stream
, id
.u
.n
.name
, id
.u
.n
.length
);
317 /* Print a list of resource ID's. */
320 res_ids_print (stream
, cids
, ids
)
323 const struct res_id
*ids
;
327 for (i
= 0; i
< cids
; i
++)
329 res_id_print (stream
, ids
[i
], 1);
331 fprintf (stream
, ": ");
335 /* Convert an ASCII string to a resource ID. */
338 res_string_to_id (res_id
, string
)
339 struct res_id
*res_id
;
343 unicode_from_ascii (&res_id
->u
.n
.length
, &res_id
->u
.n
.name
, string
);
346 /* Define a resource. The arguments are the resource tree, RESOURCES,
347 and the location at which to put it in the tree, CIDS and IDS.
348 This returns a newly allocated res_resource structure, which the
349 caller is expected to initialize. If DUPOK is non-zero, then if a
350 resource with this ID exists, it is returned. Otherwise, a warning
351 is issued, and a new resource is created replacing the existing
354 struct res_resource
*
355 define_resource (resources
, cids
, ids
, dupok
)
356 struct res_directory
**resources
;
358 const struct res_id
*ids
;
361 struct res_entry
*re
= NULL
;
365 for (i
= 0; i
< cids
; i
++)
367 struct res_entry
**pp
;
369 if (*resources
== NULL
)
371 static unsigned long timeval
;
373 /* Use the same timestamp for every resource created in a
376 timeval
= time (NULL
);
378 *resources
= ((struct res_directory
*)
379 res_alloc (sizeof **resources
));
380 (*resources
)->characteristics
= 0;
381 (*resources
)->time
= timeval
;
382 (*resources
)->major
= 0;
383 (*resources
)->minor
= 0;
384 (*resources
)->entries
= NULL
;
387 for (pp
= &(*resources
)->entries
; *pp
!= NULL
; pp
= &(*pp
)->next
)
388 if (res_id_cmp ((*pp
)->id
, ids
[i
]) == 0)
395 re
= (struct res_entry
*) res_alloc (sizeof *re
);
416 fprintf (stderr
, "%s: ", program_name
);
417 res_ids_print (stderr
, i
, ids
);
418 fprintf (stderr
, _(": expected to be a directory\n"));
422 resources
= &re
->u
.dir
;
428 fprintf (stderr
, "%s: ", program_name
);
429 res_ids_print (stderr
, cids
, ids
);
430 fprintf (stderr
, _(": expected to be a leaf\n"));
434 if (re
->u
.res
!= NULL
)
439 fprintf (stderr
, _("%s: warning: "), program_name
);
440 res_ids_print (stderr
, cids
, ids
);
441 fprintf (stderr
, _(": duplicate value\n"));
444 re
->u
.res
= ((struct res_resource
*)
445 res_alloc (sizeof (struct res_resource
)));
447 re
->u
.res
->type
= RES_TYPE_UNINITIALIZED
;
448 memset (&re
->u
.res
->res_info
, 0, sizeof (struct res_res_info
));
449 memset (&re
->u
.res
->coff_info
, 0, sizeof (struct res_coff_info
));
454 /* Define a standard resource. This is a version of define_resource
455 that just takes type, name, and language arguments. */
457 struct res_resource
*
458 define_standard_resource (resources
, type
, name
, language
, dupok
)
459 struct res_directory
**resources
;
471 a
[2].u
.id
= language
;
472 return define_resource (resources
, 3, a
, dupok
);
475 /* Comparison routine for resource sorting. */
478 cmp_res_entry (p1
, p2
)
482 const struct res_entry
**re1
, **re2
;
484 re1
= (const struct res_entry
**) p1
;
485 re2
= (const struct res_entry
**) p2
;
486 return res_id_cmp ((*re1
)->id
, (*re2
)->id
);
489 /* Sort the resources. */
491 static struct res_directory
*
492 sort_resources (resdir
)
493 struct res_directory
*resdir
;
496 struct res_entry
*re
;
497 struct res_entry
**a
;
499 if (resdir
->entries
== NULL
)
503 for (re
= resdir
->entries
; re
!= NULL
; re
= re
->next
)
506 /* This is a recursive routine, so using xmalloc is probably better
508 a
= (struct res_entry
**) xmalloc (c
* sizeof (struct res_entry
*));
510 for (i
= 0, re
= resdir
->entries
; re
!= NULL
; re
= re
->next
, i
++)
513 qsort (a
, c
, sizeof (struct res_entry
*), cmp_res_entry
);
515 resdir
->entries
= a
[0];
516 for (i
= 0; i
< c
- 1; i
++)
517 a
[i
]->next
= a
[i
+ 1];
522 /* Now sort the subdirectories. */
524 for (re
= resdir
->entries
; re
!= NULL
; re
= re
->next
)
526 re
->u
.dir
= sort_resources (re
->u
.dir
);
531 /* Return whether the dialog resource DIALOG is a DIALOG or a
535 extended_dialog (dialog
)
536 const struct dialog
*dialog
;
538 const struct dialog_control
*c
;
540 if (dialog
->ex
!= NULL
)
543 for (c
= dialog
->controls
; c
!= NULL
; c
= c
->next
)
544 if (c
->data
!= NULL
|| c
->help
!= 0)
550 /* Return whether MENUITEMS are a MENU or a MENUEX. */
554 const struct menu
*menu
;
556 return extended_menuitems (menu
->items
);
560 extended_menuitems (menuitems
)
561 const struct menuitem
*menuitems
;
563 const struct menuitem
*mi
;
565 for (mi
= menuitems
; mi
!= NULL
; mi
= mi
->next
)
567 if (mi
->help
!= 0 || mi
->state
!= 0)
569 if (mi
->popup
!= NULL
&& mi
->id
!= 0)
572 & ~ (MENUITEM_CHECKED
576 | MENUITEM_MENUBARBREAK
577 | MENUITEM_MENUBREAK
))
580 if (mi
->popup
!= NULL
)
582 if (extended_menuitems (mi
->popup
))
590 /* Convert a string to a format type, or exit if it can't be done. */
592 static enum res_format
593 format_from_name (name
)
596 const struct format_map
*m
;
598 for (m
= format_names
; m
->name
!= NULL
; m
++)
599 if (strcasecmp (m
->name
, name
) == 0)
604 non_fatal (_("unknown format type `%s'"), name
);
605 fprintf (stderr
, _("%s: supported formats:"), program_name
);
606 for (m
= format_names
; m
->name
!= NULL
; m
++)
607 fprintf (stderr
, " %s", m
->name
);
608 fprintf (stderr
, "\n");
615 /* Work out a format type given a file name. If INPUT is non-zero,
616 it's OK to look at the file itself. */
618 static enum res_format
619 format_from_filename (filename
, input
)
620 const char *filename
;
625 unsigned char b1
, b2
, b3
, b4
, b5
;
628 /* If we have an extension, see if we recognize it as implying a
629 particular format. */
630 ext
= strrchr (filename
, '.');
633 const struct format_map
*m
;
636 for (m
= format_fileexts
; m
->name
!= NULL
; m
++)
637 if (strcasecmp (m
->name
, ext
) == 0)
641 /* If we don't recognize the name of an output file, assume it's a
644 return RES_FORMAT_COFF
;
646 /* Read the first few bytes of the file to see if we can guess what
648 e
= fopen (filename
, FOPEN_RB
);
650 fatal ("%s: %s", filename
, strerror (errno
));
660 /* A PE executable starts with 0x4d 0x5a. */
661 if (b1
== 0x4d && b2
== 0x5a)
662 return RES_FORMAT_COFF
;
664 /* A COFF .o file starts with a COFF magic number. */
665 magic
= (b2
<< 8) | b1
;
668 case 0x14c: /* i386 */
669 case 0x166: /* MIPS */
670 case 0x184: /* Alpha */
671 case 0x268: /* 68k */
672 case 0x1f0: /* PowerPC */
674 return RES_FORMAT_COFF
;
677 /* A RES file starts with 0x0 0x0 0x0 0x0 0x20 0x0 0x0 0x0. */
678 if (b1
== 0 && b2
== 0 && b3
== 0 && b4
== 0 && b5
== 0x20)
679 return RES_FORMAT_RES
;
681 /* If every character is printable or space, assume it's an RC file. */
682 if ((ISPRINT (b1
) || ISSPACE (b1
))
683 && (ISPRINT (b2
) || ISSPACE (b2
))
684 && (ISPRINT (b3
) || ISSPACE (b3
))
685 && (ISPRINT (b4
) || ISSPACE (b4
))
686 && (ISPRINT (b5
) || ISSPACE (b5
)))
687 return RES_FORMAT_RC
;
689 /* Otherwise, we give up. */
690 fatal (_("can not determine type of file `%s'; use the -I option"),
693 /* Return something to silence the compiler warning. */
694 return RES_FORMAT_UNKNOWN
;
697 /* Print a usage message and exit. */
700 usage (stream
, status
)
704 fprintf (stream
, _("Usage: %s [option(s)] [input-file] [output-file]\n"),
706 fprintf (stream
, _(" The options are:\n\
707 -i --input=<file> Name input file\n\
708 -o --output=<file> Name output file\n\
709 -I --input-format=<format> Specify input format\n\
710 -O --output-format=<format> Specify output format\n\
711 -F --target=<target> Specify COFF target\n\
712 --preprocessor=<program> Program to use to preprocess rc file\n\
713 --include-dir=<dir> Include directory when preprocessing rc file\n\
714 -D --define <sym>[=<val>] Define SYM when preprocessing rc file\n\
715 -v --verbose Verbose - tells you what it's doing\n\
716 --language=<val> Set language when reading rc file\n\
717 --use-temp-file Use a temporary file instead of popen to read\n\
718 the preprocessor output\n\
719 --no-use-temp-file Use popen (default)\n"));
721 fprintf (stream
, _("\
722 --yydebug Turn on parser debugging\n"));
724 fprintf (stream
, _("\
725 -h --help Print this help message\n\
726 -V --version Print version information\n"));
727 fprintf (stream
, _("\
728 FORMAT is one of rc, res, or coff, and is deduced from the file name\n\
729 extension if not specified. A single file name is an input file.\n\
730 No input-file is stdin, default rc. No output-file is stdout, default rc.\n"));
732 list_supported_targets (program_name
, stream
);
735 fprintf (stream
, _("Report bugs to %s\n"), REPORT_BUGS_TO
);
740 /* Quote characters that will confuse the shell when we run the preprocessor. */
746 static char *buf
= 0;
747 static int buflen
= 0;
748 int slen
= strlen (string
);
752 if ((buflen
< slen
* 2 + 2) || !buf
)
754 buflen
= slen
* 2 + 2;
757 buf
= (char *) xmalloc (buflen
);
760 for (src
=string
, dest
=buf
; *src
; src
++, dest
++)
762 if (*src
== '(' || *src
== ')' || *src
== ' ')
770 /* This keeps gcc happy when using -Wmissing-prototypes -Wstrict-prototypes. */
771 int main
PARAMS ((int, char **));
773 /* The main function. */
781 char *input_filename
;
782 char *output_filename
;
783 enum res_format input_format
;
784 enum res_format output_format
;
788 const char *quotedarg
;
790 struct res_directory
*resources
;
793 #if defined (HAVE_SETLOCALE) && defined (HAVE_LC_MESSAGES)
794 setlocale (LC_MESSAGES
, "");
796 #if defined (HAVE_SETLOCALE)
797 setlocale (LC_CTYPE
, "");
799 bindtextdomain (PACKAGE
, LOCALEDIR
);
800 textdomain (PACKAGE
);
802 program_name
= argv
[0];
803 xmalloc_set_program_name (program_name
);
806 set_default_bfd_target ();
810 input_filename
= NULL
;
811 output_filename
= NULL
;
812 input_format
= RES_FORMAT_UNKNOWN
;
813 output_format
= RES_FORMAT_UNKNOWN
;
817 language
= 0x409; /* LANG_ENGLISH, SUBLANG_ENGLISH_US. */
820 while ((c
= getopt_long (argc
, argv
, "i:o:I:O:F:D:hHvV", long_options
,
826 input_filename
= optarg
;
830 output_filename
= optarg
;
834 input_format
= format_from_name (optarg
);
838 output_format
= format_from_name (optarg
);
845 case OPTION_PREPROCESSOR
:
846 preprocessor
= optarg
;
851 if (preprocargs
== NULL
)
853 quotedarg
= quot (optarg
);
854 preprocargs
= xmalloc (strlen (quotedarg
) + 3);
855 sprintf (preprocargs
, "-D%s", quotedarg
);
861 quotedarg
= quot (optarg
);
862 n
= xmalloc (strlen (preprocargs
) + strlen (quotedarg
) + 4);
863 sprintf (n
, "%s -D%s", preprocargs
, quotedarg
);
873 case OPTION_INCLUDE_DIR
:
874 if (preprocargs
== NULL
)
876 quotedarg
= quot (optarg
);
877 preprocargs
= xmalloc (strlen (quotedarg
) + 3);
878 sprintf (preprocargs
, "-I%s", quotedarg
);
884 quotedarg
= quot (optarg
);
885 n
= xmalloc (strlen (preprocargs
) + strlen (quotedarg
) + 4);
886 sprintf (n
, "%s -I%s", preprocargs
, quotedarg
);
892 struct include_dir
*n
, **pp
;
894 n
= (struct include_dir
*) xmalloc (sizeof *n
);
898 for (pp
= &include_dirs
; *pp
!= NULL
; pp
= &(*pp
)->next
)
905 case OPTION_LANGUAGE
:
906 language
= strtol (optarg
, (char **) NULL
, 16);
909 case OPTION_USE_TEMP_FILE
:
913 case OPTION_NO_USE_TEMP_FILE
:
931 print_version ("windres");
940 if (input_filename
== NULL
&& optind
< argc
)
942 input_filename
= argv
[optind
];
946 if (output_filename
== NULL
&& optind
< argc
)
948 output_filename
= argv
[optind
];
955 if (input_format
== RES_FORMAT_UNKNOWN
)
957 if (input_filename
== NULL
)
958 input_format
= RES_FORMAT_RC
;
960 input_format
= format_from_filename (input_filename
, 1);
963 if (output_format
== RES_FORMAT_UNKNOWN
)
965 if (output_filename
== NULL
)
966 output_format
= RES_FORMAT_RC
;
968 output_format
= format_from_filename (output_filename
, 0);
971 /* Read the input file. */
973 switch (input_format
)
978 resources
= read_rc_file (input_filename
, preprocessor
, preprocargs
,
979 language
, use_temp_file
);
982 resources
= read_res_file (input_filename
);
984 case RES_FORMAT_COFF
:
985 resources
= read_coff_rsrc (input_filename
, target
);
989 if (resources
== NULL
)
990 fatal (_("no resources"));
992 /* Sort the resources. This is required for COFF, convenient for
993 rc, and unimportant for res. */
995 resources
= sort_resources (resources
);
997 /* Write the output file. */
1001 switch (output_format
)
1006 write_rc_file (output_filename
, resources
);
1008 case RES_FORMAT_RES
:
1009 write_res_file (output_filename
, resources
);
1011 case RES_FORMAT_COFF
:
1012 write_coff_file (output_filename
, target
, resources
);