Commit | Line | Data |
---|---|---|
1d371d35 | 1 | /* windres.c -- a program to manipulate Windows resources |
9d04d618 | 2 | Copyright 1997, 1998 Free Software Foundation, Inc. |
1d371d35 ILT |
3 | Written by Ian Lance Taylor, Cygnus Support. |
4 | ||
5 | This file is part of GNU Binutils. | |
6 | ||
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. | |
11 | ||
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. | |
16 | ||
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 | |
20 | 02111-1307, USA. */ | |
21 | ||
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 | |
25 | program. | |
26 | ||
27 | It is based on information taken from the following sources: | |
28 | ||
29 | * Microsoft documentation. | |
30 | ||
31 | * The rcl program, written by Gunther Ebert | |
32 | <gunther.ebert@ixos-leipzig.de>. | |
33 | ||
34 | * The res2coff program, written by Pedro A. Aranda <paag@tid.es>. | |
35 | ||
36 | */ | |
37 | ||
38 | #include "bfd.h" | |
39 | #include "getopt.h" | |
40 | #include "bucomm.h" | |
41 | #include "libiberty.h" | |
662cc41e | 42 | #include "obstack.h" |
1d371d35 ILT |
43 | #include "windres.h" |
44 | ||
45 | #include <assert.h> | |
46 | #include <ctype.h> | |
9d04d618 | 47 | #include <time.h> |
1d371d35 ILT |
48 | |
49 | /* An enumeration of format types. */ | |
50 | ||
51 | enum res_format | |
52 | { | |
53 | /* Unknown format. */ | |
54 | RES_FORMAT_UNKNOWN, | |
55 | /* Textual RC file. */ | |
56 | RES_FORMAT_RC, | |
57 | /* Binary RES file. */ | |
58 | RES_FORMAT_RES, | |
59 | /* COFF file. */ | |
60 | RES_FORMAT_COFF | |
61 | }; | |
62 | ||
63 | /* A structure used to map between format types and strings. */ | |
64 | ||
65 | struct format_map | |
66 | { | |
67 | const char *name; | |
68 | enum res_format format; | |
69 | }; | |
70 | ||
71 | /* A mapping between names and format types. */ | |
72 | ||
73 | static const struct format_map format_names[] = | |
74 | { | |
75 | { "rc", RES_FORMAT_RC }, | |
76 | { "res", RES_FORMAT_RES }, | |
77 | { "coff", RES_FORMAT_COFF }, | |
78 | { NULL, RES_FORMAT_UNKNOWN } | |
79 | }; | |
80 | ||
81 | /* A mapping from file extensions to format types. */ | |
82 | ||
83 | static const struct format_map format_fileexts[] = | |
84 | { | |
85 | { "rc", RES_FORMAT_RC }, | |
86 | { "res", RES_FORMAT_RES }, | |
87 | { "exe", RES_FORMAT_COFF }, | |
88 | { "obj", RES_FORMAT_COFF }, | |
89 | { "o", RES_FORMAT_COFF }, | |
90 | { NULL, RES_FORMAT_UNKNOWN } | |
91 | }; | |
92 | ||
93 | /* A list of include directories. */ | |
94 | ||
95 | struct include_dir | |
96 | { | |
97 | struct include_dir *next; | |
98 | char *dir; | |
99 | }; | |
100 | ||
101 | static struct include_dir *include_dirs; | |
102 | ||
103 | /* Long options. */ | |
104 | ||
105 | /* 150 isn't special; it's just an arbitrary non-ASCII char value. */ | |
106 | ||
107 | #define OPTION_DEFINE 150 | |
108 | #define OPTION_HELP (OPTION_DEFINE + 1) | |
109 | #define OPTION_INCLUDE_DIR (OPTION_HELP + 1) | |
110 | #define OPTION_LANGUAGE (OPTION_INCLUDE_DIR + 1) | |
111 | #define OPTION_PREPROCESSOR (OPTION_LANGUAGE + 1) | |
112 | #define OPTION_VERSION (OPTION_PREPROCESSOR + 1) | |
113 | #define OPTION_YYDEBUG (OPTION_VERSION + 1) | |
114 | ||
115 | static const struct option long_options[] = | |
116 | { | |
117 | {"define", required_argument, 0, OPTION_DEFINE}, | |
118 | {"help", no_argument, 0, OPTION_HELP}, | |
119 | {"include-dir", required_argument, 0, OPTION_INCLUDE_DIR}, | |
120 | {"input-format", required_argument, 0, 'I'}, | |
121 | {"language", required_argument, 0, OPTION_LANGUAGE}, | |
122 | {"output-format", required_argument, 0, 'O'}, | |
123 | {"preprocessor", required_argument, 0, OPTION_PREPROCESSOR}, | |
124 | {"target", required_argument, 0, 'F'}, | |
125 | {"version", no_argument, 0, OPTION_VERSION}, | |
126 | {"yydebug", no_argument, 0, OPTION_YYDEBUG}, | |
127 | {0, no_argument, 0, 0} | |
128 | }; | |
129 | ||
130 | /* Static functions. */ | |
131 | ||
662cc41e ILT |
132 | static void res_init PARAMS ((void)); |
133 | static int extended_menuitems PARAMS ((const struct menuitem *)); | |
1d371d35 ILT |
134 | static enum res_format format_from_name PARAMS ((const char *)); |
135 | static enum res_format format_from_filename PARAMS ((const char *, int)); | |
136 | static void usage PARAMS ((FILE *, int)); | |
662cc41e ILT |
137 | static int cmp_res_entry PARAMS ((const PTR, const PTR)); |
138 | static struct res_directory *sort_resources PARAMS ((struct res_directory *)); | |
139 | \f | |
140 | /* When we are building a resource tree, we allocate everything onto | |
141 | an obstack, so that we can free it all at once if we want. */ | |
142 | ||
143 | #define obstack_chunk_alloc xmalloc | |
144 | #define obstack_chunk_free free | |
145 | ||
146 | /* The resource building obstack. */ | |
147 | ||
148 | static struct obstack res_obstack; | |
149 | ||
150 | /* Initialize the resource building obstack. */ | |
151 | ||
152 | static void | |
153 | res_init () | |
154 | { | |
155 | obstack_init (&res_obstack); | |
156 | } | |
157 | ||
158 | /* Allocate space on the resource building obstack. */ | |
159 | ||
160 | PTR | |
161 | res_alloc (bytes) | |
162 | size_t bytes; | |
163 | { | |
164 | return (PTR) obstack_alloc (&res_obstack, bytes); | |
165 | } | |
166 | ||
167 | /* We also use an obstack to save memory used while writing out a set | |
168 | of resources. */ | |
169 | ||
170 | static struct obstack reswr_obstack; | |
171 | ||
172 | /* Initialize the resource writing obstack. */ | |
173 | ||
174 | static void | |
175 | reswr_init () | |
176 | { | |
177 | obstack_init (&reswr_obstack); | |
178 | } | |
179 | ||
180 | /* Allocate space on the resource writing obstack. */ | |
181 | ||
182 | PTR | |
183 | reswr_alloc (bytes) | |
184 | size_t bytes; | |
185 | { | |
186 | return (PTR) obstack_alloc (&reswr_obstack, bytes); | |
187 | } | |
1d371d35 ILT |
188 | \f |
189 | /* Open a file using the include directory search list. */ | |
190 | ||
191 | FILE * | |
192 | open_file_search (filename, mode, errmsg, real_filename) | |
193 | const char *filename; | |
194 | const char *mode; | |
195 | const char *errmsg; | |
196 | char **real_filename; | |
197 | { | |
198 | FILE *e; | |
199 | struct include_dir *d; | |
200 | ||
201 | e = fopen (filename, mode); | |
202 | if (e != NULL) | |
203 | { | |
204 | *real_filename = xstrdup (filename); | |
205 | return e; | |
206 | } | |
207 | ||
208 | if (errno == ENOENT) | |
209 | { | |
210 | for (d = include_dirs; d != NULL; d = d->next) | |
211 | { | |
212 | char *n; | |
213 | ||
214 | n = (char *) xmalloc (strlen (d->dir) + strlen (filename) + 2); | |
215 | sprintf (n, "%s/%s", d->dir, filename); | |
216 | e = fopen (n, mode); | |
217 | if (e != NULL) | |
218 | { | |
219 | *real_filename = n; | |
220 | return e; | |
221 | } | |
222 | ||
223 | if (errno != ENOENT) | |
224 | break; | |
225 | } | |
226 | } | |
227 | ||
9d04d618 | 228 | fatal (_("can't open %s `%s': %s"), errmsg, filename, strerror (errno)); |
1d371d35 ILT |
229 | |
230 | /* Return a value to avoid a compiler warning. */ | |
231 | return NULL; | |
232 | } | |
233 | \f | |
234 | /* Unicode support. */ | |
235 | ||
236 | /* Convert an ASCII string to a unicode string. We just copy it, | |
237 | expanding chars to shorts, rather than doing something intelligent. */ | |
238 | ||
239 | void | |
240 | unicode_from_ascii (length, unicode, ascii) | |
662cc41e ILT |
241 | int *length; |
242 | unichar **unicode; | |
1d371d35 ILT |
243 | const char *ascii; |
244 | { | |
245 | int len; | |
246 | const char *s; | |
247 | unsigned short *w; | |
248 | ||
249 | len = strlen (ascii); | |
250 | ||
251 | if (length != NULL) | |
662cc41e | 252 | *length = len; |
1d371d35 | 253 | |
662cc41e | 254 | *unicode = ((unichar *) res_alloc ((len + 1) * sizeof (unichar))); |
1d371d35 ILT |
255 | |
256 | for (s = ascii, w = *unicode; *s != '\0'; s++, w++) | |
257 | *w = *s & 0xff; | |
258 | *w = 0; | |
259 | } | |
260 | ||
261 | /* Print the unicode string UNICODE to the file E. LENGTH is the | |
262 | number of characters to print, or -1 if we should print until the | |
263 | end of the string. */ | |
264 | ||
265 | void | |
266 | unicode_print (e, unicode, length) | |
267 | FILE *e; | |
662cc41e | 268 | const unichar *unicode; |
1d371d35 ILT |
269 | int length; |
270 | { | |
271 | while (1) | |
272 | { | |
662cc41e | 273 | unichar ch; |
1d371d35 ILT |
274 | |
275 | if (length == 0) | |
276 | return; | |
277 | if (length > 0) | |
278 | --length; | |
279 | ||
280 | ch = *unicode; | |
281 | ||
662cc41e | 282 | if (ch == 0 && length < 0) |
1d371d35 ILT |
283 | return; |
284 | ||
285 | ++unicode; | |
286 | ||
9d04d618 TT |
287 | if ((ch & 0x7f) == ch) |
288 | { | |
289 | if (ch == '\\') | |
290 | fputs ("\\", e); | |
291 | else if (isprint (ch)) | |
292 | putc (ch, e); | |
293 | else | |
294 | { | |
295 | switch (ch) | |
296 | { | |
297 | case ESCAPE_A: | |
298 | fputs ("\\a", e); | |
299 | break; | |
300 | ||
301 | case ESCAPE_B: | |
302 | fputs ("\\b", e); | |
303 | break; | |
304 | ||
305 | case ESCAPE_F: | |
306 | fputs ("\\f", e); | |
307 | break; | |
308 | ||
309 | case ESCAPE_N: | |
310 | fputs ("\\n", e); | |
311 | break; | |
312 | ||
313 | case ESCAPE_R: | |
314 | fputs ("\\r", e); | |
315 | break; | |
316 | ||
317 | case ESCAPE_T: | |
318 | fputs ("\\t", e); | |
319 | break; | |
320 | ||
321 | case ESCAPE_V: | |
322 | fputs ("\\v", e); | |
323 | break; | |
324 | ||
325 | default: | |
326 | fprintf (e, "\\%03o", (unsigned int) ch); | |
327 | break; | |
328 | } | |
329 | } | |
330 | } | |
1d371d35 ILT |
331 | else if ((ch & 0xff) == ch) |
332 | fprintf (e, "\\%03o", (unsigned int) ch); | |
333 | else | |
334 | fprintf (e, "\\x%x", (unsigned int) ch); | |
335 | } | |
336 | } | |
337 | \f | |
338 | /* Compare two resource ID's. We consider name entries to come before | |
339 | numeric entries, because that is how they appear in the COFF .rsrc | |
340 | section. */ | |
341 | ||
342 | int | |
343 | res_id_cmp (a, b) | |
344 | struct res_id a; | |
345 | struct res_id b; | |
346 | { | |
347 | if (! a.named) | |
348 | { | |
349 | if (b.named) | |
350 | return 1; | |
351 | if (a.u.id > b.u.id) | |
352 | return 1; | |
353 | else if (a.u.id < b.u.id) | |
354 | return -1; | |
355 | else | |
356 | return 0; | |
357 | } | |
358 | else | |
359 | { | |
662cc41e | 360 | unichar *as, *ase, *bs, *bse; |
1d371d35 ILT |
361 | |
362 | if (! b.named) | |
363 | return -1; | |
364 | ||
365 | as = a.u.n.name; | |
366 | ase = as + a.u.n.length; | |
367 | bs = b.u.n.name; | |
368 | bse = bs + b.u.n.length; | |
369 | ||
370 | while (as < ase) | |
371 | { | |
372 | int i; | |
373 | ||
374 | if (bs >= bse) | |
375 | return 1; | |
376 | i = (int) *as - (int) *bs; | |
377 | if (i != 0) | |
378 | return i; | |
379 | ++as; | |
380 | ++bs; | |
381 | } | |
382 | ||
383 | if (bs < bse) | |
384 | return -1; | |
385 | ||
386 | return 0; | |
387 | } | |
388 | } | |
389 | ||
390 | /* Print a resource ID. */ | |
391 | ||
392 | void | |
393 | res_id_print (stream, id, quote) | |
394 | FILE *stream; | |
395 | struct res_id id; | |
396 | int quote; | |
397 | { | |
398 | if (! id.named) | |
399 | fprintf (stream, "%lu", id.u.id); | |
400 | else | |
401 | { | |
1d371d35 ILT |
402 | if (quote) |
403 | putc ('"', stream); | |
e4486bdf | 404 | unicode_print (stream, id.u.n.name, id.u.n.length); |
1d371d35 ILT |
405 | if (quote) |
406 | putc ('"', stream); | |
407 | } | |
408 | } | |
409 | ||
410 | /* Print a list of resource ID's. */ | |
411 | ||
412 | void | |
413 | res_ids_print (stream, cids, ids) | |
414 | FILE *stream; | |
415 | int cids; | |
416 | const struct res_id *ids; | |
417 | { | |
418 | int i; | |
419 | ||
420 | for (i = 0; i < cids; i++) | |
421 | { | |
422 | res_id_print (stream, ids[i], 1); | |
423 | if (i + 1 < cids) | |
424 | fprintf (stream, ": "); | |
425 | } | |
426 | } | |
427 | ||
428 | /* Convert an ASCII string to a resource ID. */ | |
429 | ||
430 | void | |
431 | res_string_to_id (res_id, string) | |
432 | struct res_id *res_id; | |
433 | const char *string; | |
434 | { | |
435 | res_id->named = 1; | |
436 | unicode_from_ascii (&res_id->u.n.length, &res_id->u.n.name, string); | |
437 | } | |
438 | ||
439 | /* Define a resource. The arguments are the resource tree, RESOURCES, | |
440 | and the location at which to put it in the tree, CIDS and IDS. | |
441 | This returns a newly allocated res_resource structure, which the | |
442 | caller is expected to initialize. If DUPOK is non-zero, then if a | |
443 | resource with this ID exists, it is returned. Otherwise, a warning | |
444 | is issued, and a new resource is created replacing the existing | |
445 | one. */ | |
446 | ||
447 | struct res_resource * | |
448 | define_resource (resources, cids, ids, dupok) | |
449 | struct res_directory **resources; | |
450 | int cids; | |
451 | const struct res_id *ids; | |
452 | int dupok; | |
453 | { | |
454 | struct res_entry *re = NULL; | |
455 | int i; | |
456 | ||
457 | assert (cids > 0); | |
458 | for (i = 0; i < cids; i++) | |
459 | { | |
460 | struct res_entry **pp; | |
461 | ||
462 | if (*resources == NULL) | |
463 | { | |
9d04d618 TT |
464 | static unsigned long timeval; |
465 | ||
466 | /* Use the same timestamp for every resource created in a | |
467 | single run. */ | |
468 | if (timeval == 0) | |
469 | timeval = time (NULL); | |
470 | ||
662cc41e ILT |
471 | *resources = ((struct res_directory *) |
472 | res_alloc (sizeof **resources)); | |
1d371d35 | 473 | (*resources)->characteristics = 0; |
9d04d618 | 474 | (*resources)->time = timeval; |
1d371d35 ILT |
475 | (*resources)->major = 0; |
476 | (*resources)->minor = 0; | |
477 | (*resources)->entries = NULL; | |
478 | } | |
479 | ||
480 | for (pp = &(*resources)->entries; *pp != NULL; pp = &(*pp)->next) | |
481 | if (res_id_cmp ((*pp)->id, ids[i]) == 0) | |
482 | break; | |
483 | ||
484 | if (*pp != NULL) | |
485 | re = *pp; | |
486 | else | |
487 | { | |
662cc41e | 488 | re = (struct res_entry *) res_alloc (sizeof *re); |
1d371d35 ILT |
489 | re->next = NULL; |
490 | re->id = ids[i]; | |
491 | if ((i + 1) < cids) | |
492 | { | |
493 | re->subdir = 1; | |
494 | re->u.dir = NULL; | |
495 | } | |
496 | else | |
497 | { | |
498 | re->subdir = 0; | |
499 | re->u.res = NULL; | |
500 | } | |
501 | ||
502 | *pp = re; | |
503 | } | |
504 | ||
505 | if ((i + 1) < cids) | |
506 | { | |
507 | if (! re->subdir) | |
508 | { | |
509 | fprintf (stderr, "%s: ", program_name); | |
510 | res_ids_print (stderr, i, ids); | |
9d04d618 | 511 | fprintf (stderr, _(": expected to be a directory\n")); |
1d371d35 ILT |
512 | xexit (1); |
513 | } | |
514 | ||
515 | resources = &re->u.dir; | |
516 | } | |
517 | } | |
518 | ||
519 | if (re->subdir) | |
520 | { | |
521 | fprintf (stderr, "%s: ", program_name); | |
522 | res_ids_print (stderr, cids, ids); | |
9d04d618 | 523 | fprintf (stderr, _(": expected to be a leaf\n")); |
1d371d35 ILT |
524 | xexit (1); |
525 | } | |
526 | ||
527 | if (re->u.res != NULL) | |
528 | { | |
529 | if (dupok) | |
530 | return re->u.res; | |
531 | ||
9d04d618 | 532 | fprintf (stderr, _("%s: warning: "), program_name); |
1d371d35 | 533 | res_ids_print (stderr, cids, ids); |
9d04d618 | 534 | fprintf (stderr, _(": duplicate value\n")); |
1d371d35 ILT |
535 | } |
536 | ||
662cc41e ILT |
537 | re->u.res = ((struct res_resource *) |
538 | res_alloc (sizeof (struct res_resource))); | |
1d371d35 ILT |
539 | |
540 | re->u.res->type = RES_TYPE_UNINITIALIZED; | |
541 | memset (&re->u.res->res_info, 0, sizeof (struct res_res_info)); | |
542 | memset (&re->u.res->coff_info, 0, sizeof (struct res_coff_info)); | |
543 | ||
544 | return re->u.res; | |
545 | } | |
546 | ||
547 | /* Define a standard resource. This is a version of define_resource | |
548 | that just takes type, name, and language arguments. */ | |
549 | ||
550 | struct res_resource * | |
551 | define_standard_resource (resources, type, name, language, dupok) | |
552 | struct res_directory **resources; | |
553 | int type; | |
554 | struct res_id name; | |
555 | int language; | |
556 | int dupok; | |
557 | { | |
558 | struct res_id a[3]; | |
559 | ||
560 | a[0].named = 0; | |
561 | a[0].u.id = type; | |
562 | a[1] = name; | |
563 | a[2].named = 0; | |
564 | a[2].u.id = language; | |
565 | return define_resource (resources, 3, a, dupok); | |
566 | } | |
662cc41e ILT |
567 | |
568 | /* Comparison routine for resource sorting. */ | |
569 | ||
570 | static int | |
571 | cmp_res_entry (p1, p2) | |
572 | const PTR p1; | |
573 | const PTR p2; | |
574 | { | |
575 | const struct res_entry **re1, **re2; | |
576 | ||
577 | re1 = (const struct res_entry **) p1; | |
578 | re2 = (const struct res_entry **) p2; | |
579 | return res_id_cmp ((*re1)->id, (*re2)->id); | |
580 | } | |
581 | ||
582 | /* Sort the resources. */ | |
583 | ||
584 | static struct res_directory * | |
585 | sort_resources (resdir) | |
586 | struct res_directory *resdir; | |
587 | { | |
588 | int c, i; | |
589 | struct res_entry *re; | |
590 | struct res_entry **a; | |
591 | ||
592 | if (resdir->entries == NULL) | |
593 | return resdir; | |
594 | ||
595 | c = 0; | |
596 | for (re = resdir->entries; re != NULL; re = re->next) | |
597 | ++c; | |
598 | ||
599 | /* This is a recursive routine, so using xmalloc is probably better | |
600 | than alloca. */ | |
601 | a = (struct res_entry **) xmalloc (c * sizeof (struct res_entry *)); | |
602 | ||
603 | for (i = 0, re = resdir->entries; re != NULL; re = re->next, i++) | |
604 | a[i] = re; | |
605 | ||
606 | qsort (a, c, sizeof (struct res_entry *), cmp_res_entry); | |
607 | ||
608 | resdir->entries = a[0]; | |
609 | for (i = 0; i < c - 1; i++) | |
610 | a[i]->next = a[i + 1]; | |
611 | a[i]->next = NULL; | |
612 | ||
613 | free (a); | |
614 | ||
615 | /* Now sort the subdirectories. */ | |
616 | ||
617 | for (re = resdir->entries; re != NULL; re = re->next) | |
618 | if (re->subdir) | |
619 | re->u.dir = sort_resources (re->u.dir); | |
620 | ||
621 | return resdir; | |
622 | } | |
1d371d35 ILT |
623 | \f |
624 | /* Return whether the dialog resource DIALOG is a DIALOG or a | |
625 | DIALOGEX. */ | |
626 | ||
627 | int | |
628 | extended_dialog (dialog) | |
629 | const struct dialog *dialog; | |
630 | { | |
631 | const struct dialog_control *c; | |
632 | ||
633 | if (dialog->ex != NULL) | |
634 | return 1; | |
635 | ||
636 | for (c = dialog->controls; c != NULL; c = c->next) | |
637 | if (c->data != NULL || c->help != 0) | |
638 | return 1; | |
639 | ||
640 | return 0; | |
641 | } | |
642 | ||
643 | /* Return whether MENUITEMS are a MENU or a MENUEX. */ | |
644 | ||
645 | int | |
662cc41e ILT |
646 | extended_menu (menu) |
647 | const struct menu *menu; | |
648 | { | |
649 | return extended_menuitems (menu->items); | |
650 | } | |
651 | ||
652 | static int | |
653 | extended_menuitems (menuitems) | |
1d371d35 ILT |
654 | const struct menuitem *menuitems; |
655 | { | |
656 | const struct menuitem *mi; | |
657 | ||
658 | for (mi = menuitems; mi != NULL; mi = mi->next) | |
659 | { | |
660 | if (mi->help != 0 || mi->state != 0) | |
661 | return 1; | |
662 | if (mi->popup != NULL && mi->id != 0) | |
663 | return 1; | |
664 | if ((mi->type | |
665 | & ~ (MENUITEM_CHECKED | |
666 | | MENUITEM_GRAYED | |
667 | | MENUITEM_HELP | |
668 | | MENUITEM_INACTIVE | |
669 | | MENUITEM_MENUBARBREAK | |
670 | | MENUITEM_MENUBREAK)) | |
671 | != 0) | |
672 | return 1; | |
673 | if (mi->popup != NULL) | |
674 | { | |
662cc41e | 675 | if (extended_menuitems (mi->popup)) |
1d371d35 ILT |
676 | return 1; |
677 | } | |
678 | } | |
679 | ||
680 | return 0; | |
681 | } | |
682 | \f | |
683 | /* Convert a string to a format type, or exit if it can't be done. */ | |
684 | ||
685 | static enum res_format | |
686 | format_from_name (name) | |
687 | const char *name; | |
688 | { | |
689 | const struct format_map *m; | |
690 | ||
691 | for (m = format_names; m->name != NULL; m++) | |
692 | if (strcasecmp (m->name, name) == 0) | |
693 | break; | |
694 | ||
695 | if (m->name == NULL) | |
696 | { | |
9d04d618 TT |
697 | fprintf (stderr, _("%s: unknown format type `%s'\n"), program_name, name); |
698 | fprintf (stderr, _("%s: supported formats:"), program_name); | |
1d371d35 ILT |
699 | for (m = format_names; m->name != NULL; m++) |
700 | fprintf (stderr, " %s", m->name); | |
701 | fprintf (stderr, "\n"); | |
702 | xexit (1); | |
703 | } | |
704 | ||
705 | return m->format; | |
706 | } | |
707 | ||
708 | /* Work out a format type given a file name. If INPUT is non-zero, | |
709 | it's OK to look at the file itself. */ | |
710 | ||
711 | static enum res_format | |
712 | format_from_filename (filename, input) | |
713 | const char *filename; | |
714 | int input; | |
715 | { | |
716 | const char *ext; | |
717 | FILE *e; | |
718 | unsigned char b1, b2, b3, b4, b5; | |
719 | int magic; | |
720 | ||
721 | /* If we have an extension, see if we recognize it as implying a | |
722 | particular format. */ | |
723 | ext = strrchr (filename, '.'); | |
724 | if (ext != NULL) | |
725 | { | |
726 | const struct format_map *m; | |
727 | ||
728 | ++ext; | |
729 | for (m = format_fileexts; m->name != NULL; m++) | |
730 | if (strcasecmp (m->name, ext) == 0) | |
731 | return m->format; | |
732 | } | |
733 | ||
734 | /* If we don't recognize the name of an output file, assume it's a | |
735 | COFF file. */ | |
736 | ||
737 | if (! input) | |
738 | return RES_FORMAT_COFF; | |
739 | ||
740 | /* Read the first few bytes of the file to see if we can guess what | |
741 | it is. */ | |
742 | ||
743 | e = fopen (filename, FOPEN_RB); | |
744 | if (e == NULL) | |
745 | fatal ("%s: %s", filename, strerror (errno)); | |
746 | ||
747 | b1 = getc (e); | |
748 | b2 = getc (e); | |
749 | b3 = getc (e); | |
750 | b4 = getc (e); | |
751 | b5 = getc (e); | |
752 | ||
753 | fclose (e); | |
754 | ||
e4486bdf ILT |
755 | /* A PE executable starts with 0x4d 0x5a. */ |
756 | if (b1 == 0x4d && b2 == 0x5a) | |
1d371d35 ILT |
757 | return RES_FORMAT_COFF; |
758 | ||
759 | /* A COFF .o file starts with a COFF magic number. */ | |
760 | magic = (b2 << 8) | b1; | |
761 | switch (magic) | |
762 | { | |
763 | case 0x14c: /* i386 */ | |
764 | case 0x166: /* MIPS */ | |
765 | case 0x184: /* Alpha */ | |
766 | case 0x268: /* 68k */ | |
767 | case 0x1f0: /* PowerPC */ | |
768 | case 0x290: /* PA */ | |
769 | return RES_FORMAT_COFF; | |
770 | } | |
771 | ||
772 | /* A RES file starts with 0x0 0x0 0x0 0x0 0x20 0x0 0x0 0x0. */ | |
773 | if (b1 == 0 && b2 == 0 && b3 == 0 && b4 == 0 && b5 == 0x20) | |
774 | return RES_FORMAT_RES; | |
775 | ||
776 | /* If every character is printable or space, assume it's an RC file. */ | |
777 | if ((isprint (b1) || isspace (b1)) | |
778 | && (isprint (b2) || isspace (b2)) | |
779 | && (isprint (b3) || isspace (b3)) | |
780 | && (isprint (b4) || isspace (b4)) | |
781 | && (isprint (b5) || isspace (b5))) | |
782 | return RES_FORMAT_RC; | |
783 | ||
784 | /* Otherwise, we give up. */ | |
9d04d618 | 785 | fatal (_("can not determine type of file `%s'; use the -I option"), |
1d371d35 ILT |
786 | filename); |
787 | ||
788 | /* Return something to silence the compiler warning. */ | |
789 | return RES_FORMAT_UNKNOWN; | |
790 | } | |
791 | ||
792 | /* Print a usage message and exit. */ | |
793 | ||
794 | static void | |
795 | usage (stream, status) | |
796 | FILE *stream; | |
797 | int status; | |
798 | { | |
9d04d618 | 799 | fprintf (stream, _("Usage: %s [options] [input-file] [output-file]\n"), |
1d371d35 | 800 | program_name); |
9d04d618 | 801 | fprintf (stream, _("\ |
1d371d35 ILT |
802 | Options:\n\ |
803 | -i FILE, --input FILE Name input file\n\ | |
804 | -o FILE, --output FILE Name output file\n\ | |
805 | -I FORMAT, --input-format FORMAT\n\ | |
806 | Specify input format\n\ | |
807 | -O FORMAT, --output-format FORMAT\n\ | |
808 | Specify output format\n\ | |
809 | -F TARGET, --target TARGET Specify COFF target\n\ | |
810 | --preprocessor PROGRAM Program to use to preprocess rc file\n\ | |
811 | --include-dir DIR Include directory when preprocessing rc file\n\ | |
812 | --define SYM[=VAL] Define SYM when preprocessing rc file\n\ | |
9d04d618 | 813 | --language VAL Set language when reading rc file\n")); |
1d371d35 | 814 | #ifdef YYDEBUG |
9d04d618 TT |
815 | fprintf (stream, _("\ |
816 | --yydebug Turn on parser debugging\n")); | |
1d371d35 | 817 | #endif |
9d04d618 | 818 | fprintf (stream, _("\ |
1d371d35 | 819 | --help Print this help message\n\ |
9d04d618 TT |
820 | --version Print version information\n")); |
821 | fprintf (stream, _("\ | |
1d371d35 ILT |
822 | FORMAT is one of rc, res, or coff, and is deduced from the file name\n\ |
823 | extension if not specified. A single file name is an input file.\n\ | |
9d04d618 | 824 | No input-file is stdin, default rc. No output-file is stdout, default rc.\n")); |
1d371d35 ILT |
825 | list_supported_targets (program_name, stream); |
826 | if (status == 0) | |
9d04d618 | 827 | fprintf (stream, _("Report bugs to bug-gnu-utils@gnu.org\n")); |
1d371d35 ILT |
828 | exit (status); |
829 | } | |
830 | ||
831 | /* The main function. */ | |
832 | ||
833 | int | |
834 | main (argc, argv) | |
835 | int argc; | |
836 | char **argv; | |
837 | { | |
838 | int c; | |
839 | char *input_filename; | |
840 | char *output_filename; | |
841 | enum res_format input_format; | |
842 | enum res_format output_format; | |
843 | char *target; | |
844 | char *preprocessor; | |
845 | char *preprocargs; | |
846 | int language; | |
847 | struct res_directory *resources; | |
848 | ||
19ac4b08 TT |
849 | setlocale (LC_MESSAGES, ""); |
850 | bindtextdomain (PACKAGE, LOCALEDIR); | |
851 | textdomain (PACKAGE); | |
852 | ||
1d371d35 ILT |
853 | program_name = argv[0]; |
854 | xmalloc_set_program_name (program_name); | |
855 | ||
856 | bfd_init (); | |
857 | set_default_bfd_target (); | |
858 | ||
662cc41e ILT |
859 | res_init (); |
860 | ||
1d371d35 ILT |
861 | input_filename = NULL; |
862 | output_filename = NULL; | |
863 | input_format = RES_FORMAT_UNKNOWN; | |
864 | output_format = RES_FORMAT_UNKNOWN; | |
865 | target = NULL; | |
866 | preprocessor = NULL; | |
867 | preprocargs = NULL; | |
868 | language = -1; | |
869 | ||
870 | while ((c = getopt_long (argc, argv, "i:o:I:O:F:", long_options, | |
871 | (int *) 0)) != EOF) | |
872 | { | |
873 | switch (c) | |
874 | { | |
875 | case 'i': | |
876 | input_filename = optarg; | |
877 | break; | |
878 | ||
879 | case 'o': | |
880 | output_filename = optarg; | |
881 | break; | |
882 | ||
883 | case 'I': | |
884 | input_format = format_from_name (optarg); | |
885 | break; | |
886 | ||
887 | case 'O': | |
888 | output_format = format_from_name (optarg); | |
889 | break; | |
890 | ||
891 | case 'F': | |
892 | target = optarg; | |
893 | break; | |
894 | ||
895 | case OPTION_PREPROCESSOR: | |
896 | preprocessor = optarg; | |
897 | break; | |
898 | ||
899 | case OPTION_DEFINE: | |
900 | if (preprocargs == NULL) | |
901 | { | |
902 | preprocargs = xmalloc (strlen (optarg) + 3); | |
903 | sprintf (preprocargs, "-D%s", optarg); | |
904 | } | |
905 | else | |
906 | { | |
907 | char *n; | |
908 | ||
909 | n = xmalloc (strlen (preprocargs) + strlen (optarg) + 4); | |
910 | sprintf (n, "%s -D%s", preprocargs, optarg); | |
911 | free (preprocargs); | |
912 | preprocargs = n; | |
913 | } | |
914 | break; | |
915 | ||
916 | case OPTION_INCLUDE_DIR: | |
917 | if (preprocargs == NULL) | |
918 | { | |
919 | preprocargs = xmalloc (strlen (optarg) + 3); | |
920 | sprintf (preprocargs, "-I%s", optarg); | |
921 | } | |
922 | else | |
923 | { | |
924 | char *n; | |
925 | ||
926 | n = xmalloc (strlen (preprocargs) + strlen (optarg) + 4); | |
927 | sprintf (n, "%s -I%s", preprocargs, optarg); | |
928 | free (preprocargs); | |
929 | preprocargs = n; | |
930 | } | |
931 | ||
932 | { | |
933 | struct include_dir *n, **pp; | |
934 | ||
935 | n = (struct include_dir *) xmalloc (sizeof *n); | |
936 | n->next = NULL; | |
937 | n->dir = optarg; | |
938 | ||
939 | for (pp = &include_dirs; *pp != NULL; pp = &(*pp)->next) | |
940 | ; | |
941 | *pp = n; | |
942 | } | |
943 | ||
944 | break; | |
945 | ||
946 | case OPTION_LANGUAGE: | |
947 | language = strtol (optarg, (char **) NULL, 16); | |
948 | break; | |
949 | ||
950 | #ifdef YYDEBUG | |
951 | case OPTION_YYDEBUG: | |
952 | yydebug = 1; | |
953 | break; | |
954 | #endif | |
955 | ||
956 | case OPTION_HELP: | |
957 | usage (stdout, 0); | |
958 | break; | |
959 | ||
960 | case OPTION_VERSION: | |
961 | print_version ("windres"); | |
962 | break; | |
963 | ||
964 | default: | |
965 | usage (stderr, 1); | |
966 | break; | |
967 | } | |
968 | } | |
969 | ||
970 | if (input_filename == NULL && optind < argc) | |
971 | { | |
972 | input_filename = argv[optind]; | |
973 | ++optind; | |
974 | } | |
975 | ||
976 | if (output_filename == NULL && optind < argc) | |
977 | { | |
978 | output_filename = argv[optind]; | |
979 | ++optind; | |
980 | } | |
981 | ||
982 | if (argc != optind) | |
983 | usage (stderr, 1); | |
984 | ||
985 | if (input_format == RES_FORMAT_UNKNOWN) | |
986 | { | |
987 | if (input_filename == NULL) | |
988 | input_format = RES_FORMAT_RC; | |
989 | else | |
990 | input_format = format_from_filename (input_filename, 1); | |
991 | } | |
992 | ||
993 | if (output_format == RES_FORMAT_UNKNOWN) | |
994 | { | |
995 | if (output_filename == NULL) | |
996 | output_format = RES_FORMAT_RC; | |
997 | else | |
998 | output_format = format_from_filename (output_filename, 0); | |
999 | } | |
1000 | ||
1001 | /* Read the input file. */ | |
1002 | ||
1003 | switch (input_format) | |
1004 | { | |
1005 | default: | |
1006 | abort (); | |
1007 | case RES_FORMAT_RC: | |
1008 | resources = read_rc_file (input_filename, preprocessor, preprocargs, | |
1009 | language); | |
1010 | break; | |
1011 | case RES_FORMAT_RES: | |
1012 | resources = read_res_file (input_filename); | |
1013 | break; | |
1014 | case RES_FORMAT_COFF: | |
1015 | resources = read_coff_rsrc (input_filename, target); | |
1016 | break; | |
1017 | } | |
1018 | ||
d5a7bb53 | 1019 | if (resources == NULL) |
9d04d618 | 1020 | fatal (_("no resources")); |
d5a7bb53 | 1021 | |
662cc41e ILT |
1022 | /* Sort the resources. This is required for COFF, convenient for |
1023 | rc, and unimportant for res. */ | |
1024 | ||
1025 | resources = sort_resources (resources); | |
1026 | ||
1d371d35 ILT |
1027 | /* Write the output file. */ |
1028 | ||
662cc41e ILT |
1029 | reswr_init (); |
1030 | ||
1d371d35 ILT |
1031 | switch (output_format) |
1032 | { | |
1033 | default: | |
1034 | abort (); | |
1035 | case RES_FORMAT_RC: | |
1036 | write_rc_file (output_filename, resources); | |
1037 | break; | |
1038 | case RES_FORMAT_RES: | |
1039 | write_res_file (output_filename, resources); | |
1040 | break; | |
1041 | case RES_FORMAT_COFF: | |
1042 | write_coff_file (output_filename, target, resources); | |
1043 | break; | |
1044 | } | |
1045 | ||
1046 | xexit (0); | |
1047 | return 0; | |
1048 | } | |
1049 | ||
1050 | struct res_directory * | |
1051 | read_res_file (filename) | |
1052 | const char *filename; | |
1053 | { | |
9d04d618 | 1054 | fatal (_("read_res_file unimplemented")); |
1d371d35 ILT |
1055 | return NULL; |
1056 | } | |
1057 | ||
1d371d35 ILT |
1058 | void |
1059 | write_res_file (filename, resources) | |
1060 | const char *filename; | |
1061 | const struct res_directory *resources; | |
1062 | { | |
9d04d618 | 1063 | fatal (_("write_res_file unimplemented")); |
1d371d35 | 1064 | } |