]>
Commit | Line | Data |
---|---|---|
252b5132 RH |
1 | /* windres.c -- a program to manipulate Windows resources |
2 | Copyright 1997, 1998, 1999 Free Software Foundation, Inc. | |
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 | <[email protected]>. | |
33 | ||
34 | * The res2coff program, written by Pedro A. Aranda <[email protected]>. | |
35 | ||
36 | */ | |
37 | ||
38 | #include "bfd.h" | |
39 | #include "getopt.h" | |
40 | #include "bucomm.h" | |
41 | #include "libiberty.h" | |
42 | #include "obstack.h" | |
43 | #include "windres.h" | |
44 | ||
45 | #include <assert.h> | |
46 | #include <ctype.h> | |
47 | #include <time.h> | |
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 | ||
132 | static void res_init PARAMS ((void)); | |
133 | static int extended_menuitems PARAMS ((const struct menuitem *)); | |
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)); | |
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 | } | |
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 | ||
228 | fatal (_("can't open %s `%s': %s"), errmsg, filename, strerror (errno)); | |
229 | ||
230 | /* Return a value to avoid a compiler warning. */ | |
231 | return NULL; | |
232 | } | |
233 | \f | |
234 | /* Compare two resource ID's. We consider name entries to come before | |
235 | numeric entries, because that is how they appear in the COFF .rsrc | |
236 | section. */ | |
237 | ||
238 | int | |
239 | res_id_cmp (a, b) | |
240 | struct res_id a; | |
241 | struct res_id b; | |
242 | { | |
243 | if (! a.named) | |
244 | { | |
245 | if (b.named) | |
246 | return 1; | |
247 | if (a.u.id > b.u.id) | |
248 | return 1; | |
249 | else if (a.u.id < b.u.id) | |
250 | return -1; | |
251 | else | |
252 | return 0; | |
253 | } | |
254 | else | |
255 | { | |
256 | unichar *as, *ase, *bs, *bse; | |
257 | ||
258 | if (! b.named) | |
259 | return -1; | |
260 | ||
261 | as = a.u.n.name; | |
262 | ase = as + a.u.n.length; | |
263 | bs = b.u.n.name; | |
264 | bse = bs + b.u.n.length; | |
265 | ||
266 | while (as < ase) | |
267 | { | |
268 | int i; | |
269 | ||
270 | if (bs >= bse) | |
271 | return 1; | |
272 | i = (int) *as - (int) *bs; | |
273 | if (i != 0) | |
274 | return i; | |
275 | ++as; | |
276 | ++bs; | |
277 | } | |
278 | ||
279 | if (bs < bse) | |
280 | return -1; | |
281 | ||
282 | return 0; | |
283 | } | |
284 | } | |
285 | ||
286 | /* Print a resource ID. */ | |
287 | ||
288 | void | |
289 | res_id_print (stream, id, quote) | |
290 | FILE *stream; | |
291 | struct res_id id; | |
292 | int quote; | |
293 | { | |
294 | if (! id.named) | |
295 | fprintf (stream, "%lu", id.u.id); | |
296 | else | |
297 | { | |
298 | if (quote) | |
299 | putc ('"', stream); | |
300 | unicode_print (stream, id.u.n.name, id.u.n.length); | |
301 | if (quote) | |
302 | putc ('"', stream); | |
303 | } | |
304 | } | |
305 | ||
306 | /* Print a list of resource ID's. */ | |
307 | ||
308 | void | |
309 | res_ids_print (stream, cids, ids) | |
310 | FILE *stream; | |
311 | int cids; | |
312 | const struct res_id *ids; | |
313 | { | |
314 | int i; | |
315 | ||
316 | for (i = 0; i < cids; i++) | |
317 | { | |
318 | res_id_print (stream, ids[i], 1); | |
319 | if (i + 1 < cids) | |
320 | fprintf (stream, ": "); | |
321 | } | |
322 | } | |
323 | ||
324 | /* Convert an ASCII string to a resource ID. */ | |
325 | ||
326 | void | |
327 | res_string_to_id (res_id, string) | |
328 | struct res_id *res_id; | |
329 | const char *string; | |
330 | { | |
331 | res_id->named = 1; | |
332 | unicode_from_ascii (&res_id->u.n.length, &res_id->u.n.name, string); | |
333 | } | |
334 | ||
335 | /* Define a resource. The arguments are the resource tree, RESOURCES, | |
336 | and the location at which to put it in the tree, CIDS and IDS. | |
337 | This returns a newly allocated res_resource structure, which the | |
338 | caller is expected to initialize. If DUPOK is non-zero, then if a | |
339 | resource with this ID exists, it is returned. Otherwise, a warning | |
340 | is issued, and a new resource is created replacing the existing | |
341 | one. */ | |
342 | ||
343 | struct res_resource * | |
344 | define_resource (resources, cids, ids, dupok) | |
345 | struct res_directory **resources; | |
346 | int cids; | |
347 | const struct res_id *ids; | |
348 | int dupok; | |
349 | { | |
350 | struct res_entry *re = NULL; | |
351 | int i; | |
352 | ||
353 | assert (cids > 0); | |
354 | for (i = 0; i < cids; i++) | |
355 | { | |
356 | struct res_entry **pp; | |
357 | ||
358 | if (*resources == NULL) | |
359 | { | |
360 | static unsigned long timeval; | |
361 | ||
362 | /* Use the same timestamp for every resource created in a | |
363 | single run. */ | |
364 | if (timeval == 0) | |
365 | timeval = time (NULL); | |
366 | ||
367 | *resources = ((struct res_directory *) | |
368 | res_alloc (sizeof **resources)); | |
369 | (*resources)->characteristics = 0; | |
370 | (*resources)->time = timeval; | |
371 | (*resources)->major = 0; | |
372 | (*resources)->minor = 0; | |
373 | (*resources)->entries = NULL; | |
374 | } | |
375 | ||
376 | for (pp = &(*resources)->entries; *pp != NULL; pp = &(*pp)->next) | |
377 | if (res_id_cmp ((*pp)->id, ids[i]) == 0) | |
378 | break; | |
379 | ||
380 | if (*pp != NULL) | |
381 | re = *pp; | |
382 | else | |
383 | { | |
384 | re = (struct res_entry *) res_alloc (sizeof *re); | |
385 | re->next = NULL; | |
386 | re->id = ids[i]; | |
387 | if ((i + 1) < cids) | |
388 | { | |
389 | re->subdir = 1; | |
390 | re->u.dir = NULL; | |
391 | } | |
392 | else | |
393 | { | |
394 | re->subdir = 0; | |
395 | re->u.res = NULL; | |
396 | } | |
397 | ||
398 | *pp = re; | |
399 | } | |
400 | ||
401 | if ((i + 1) < cids) | |
402 | { | |
403 | if (! re->subdir) | |
404 | { | |
405 | fprintf (stderr, "%s: ", program_name); | |
406 | res_ids_print (stderr, i, ids); | |
407 | fprintf (stderr, _(": expected to be a directory\n")); | |
408 | xexit (1); | |
409 | } | |
410 | ||
411 | resources = &re->u.dir; | |
412 | } | |
413 | } | |
414 | ||
415 | if (re->subdir) | |
416 | { | |
417 | fprintf (stderr, "%s: ", program_name); | |
418 | res_ids_print (stderr, cids, ids); | |
419 | fprintf (stderr, _(": expected to be a leaf\n")); | |
420 | xexit (1); | |
421 | } | |
422 | ||
423 | if (re->u.res != NULL) | |
424 | { | |
425 | if (dupok) | |
426 | return re->u.res; | |
427 | ||
428 | fprintf (stderr, _("%s: warning: "), program_name); | |
429 | res_ids_print (stderr, cids, ids); | |
430 | fprintf (stderr, _(": duplicate value\n")); | |
431 | } | |
432 | ||
433 | re->u.res = ((struct res_resource *) | |
434 | res_alloc (sizeof (struct res_resource))); | |
435 | ||
436 | re->u.res->type = RES_TYPE_UNINITIALIZED; | |
437 | memset (&re->u.res->res_info, 0, sizeof (struct res_res_info)); | |
438 | memset (&re->u.res->coff_info, 0, sizeof (struct res_coff_info)); | |
439 | ||
440 | return re->u.res; | |
441 | } | |
442 | ||
443 | /* Define a standard resource. This is a version of define_resource | |
444 | that just takes type, name, and language arguments. */ | |
445 | ||
446 | struct res_resource * | |
447 | define_standard_resource (resources, type, name, language, dupok) | |
448 | struct res_directory **resources; | |
449 | int type; | |
450 | struct res_id name; | |
451 | int language; | |
452 | int dupok; | |
453 | { | |
454 | struct res_id a[3]; | |
455 | ||
456 | a[0].named = 0; | |
457 | a[0].u.id = type; | |
458 | a[1] = name; | |
459 | a[2].named = 0; | |
460 | a[2].u.id = language; | |
461 | return define_resource (resources, 3, a, dupok); | |
462 | } | |
463 | ||
464 | /* Comparison routine for resource sorting. */ | |
465 | ||
466 | static int | |
467 | cmp_res_entry (p1, p2) | |
468 | const PTR p1; | |
469 | const PTR p2; | |
470 | { | |
471 | const struct res_entry **re1, **re2; | |
472 | ||
473 | re1 = (const struct res_entry **) p1; | |
474 | re2 = (const struct res_entry **) p2; | |
475 | return res_id_cmp ((*re1)->id, (*re2)->id); | |
476 | } | |
477 | ||
478 | /* Sort the resources. */ | |
479 | ||
480 | static struct res_directory * | |
481 | sort_resources (resdir) | |
482 | struct res_directory *resdir; | |
483 | { | |
484 | int c, i; | |
485 | struct res_entry *re; | |
486 | struct res_entry **a; | |
487 | ||
488 | if (resdir->entries == NULL) | |
489 | return resdir; | |
490 | ||
491 | c = 0; | |
492 | for (re = resdir->entries; re != NULL; re = re->next) | |
493 | ++c; | |
494 | ||
495 | /* This is a recursive routine, so using xmalloc is probably better | |
496 | than alloca. */ | |
497 | a = (struct res_entry **) xmalloc (c * sizeof (struct res_entry *)); | |
498 | ||
499 | for (i = 0, re = resdir->entries; re != NULL; re = re->next, i++) | |
500 | a[i] = re; | |
501 | ||
502 | qsort (a, c, sizeof (struct res_entry *), cmp_res_entry); | |
503 | ||
504 | resdir->entries = a[0]; | |
505 | for (i = 0; i < c - 1; i++) | |
506 | a[i]->next = a[i + 1]; | |
507 | a[i]->next = NULL; | |
508 | ||
509 | free (a); | |
510 | ||
511 | /* Now sort the subdirectories. */ | |
512 | ||
513 | for (re = resdir->entries; re != NULL; re = re->next) | |
514 | if (re->subdir) | |
515 | re->u.dir = sort_resources (re->u.dir); | |
516 | ||
517 | return resdir; | |
518 | } | |
519 | \f | |
520 | /* Return whether the dialog resource DIALOG is a DIALOG or a | |
521 | DIALOGEX. */ | |
522 | ||
523 | int | |
524 | extended_dialog (dialog) | |
525 | const struct dialog *dialog; | |
526 | { | |
527 | const struct dialog_control *c; | |
528 | ||
529 | if (dialog->ex != NULL) | |
530 | return 1; | |
531 | ||
532 | for (c = dialog->controls; c != NULL; c = c->next) | |
533 | if (c->data != NULL || c->help != 0) | |
534 | return 1; | |
535 | ||
536 | return 0; | |
537 | } | |
538 | ||
539 | /* Return whether MENUITEMS are a MENU or a MENUEX. */ | |
540 | ||
541 | int | |
542 | extended_menu (menu) | |
543 | const struct menu *menu; | |
544 | { | |
545 | return extended_menuitems (menu->items); | |
546 | } | |
547 | ||
548 | static int | |
549 | extended_menuitems (menuitems) | |
550 | const struct menuitem *menuitems; | |
551 | { | |
552 | const struct menuitem *mi; | |
553 | ||
554 | for (mi = menuitems; mi != NULL; mi = mi->next) | |
555 | { | |
556 | if (mi->help != 0 || mi->state != 0) | |
557 | return 1; | |
558 | if (mi->popup != NULL && mi->id != 0) | |
559 | return 1; | |
560 | if ((mi->type | |
561 | & ~ (MENUITEM_CHECKED | |
562 | | MENUITEM_GRAYED | |
563 | | MENUITEM_HELP | |
564 | | MENUITEM_INACTIVE | |
565 | | MENUITEM_MENUBARBREAK | |
566 | | MENUITEM_MENUBREAK)) | |
567 | != 0) | |
568 | return 1; | |
569 | if (mi->popup != NULL) | |
570 | { | |
571 | if (extended_menuitems (mi->popup)) | |
572 | return 1; | |
573 | } | |
574 | } | |
575 | ||
576 | return 0; | |
577 | } | |
578 | \f | |
579 | /* Convert a string to a format type, or exit if it can't be done. */ | |
580 | ||
581 | static enum res_format | |
582 | format_from_name (name) | |
583 | const char *name; | |
584 | { | |
585 | const struct format_map *m; | |
586 | ||
587 | for (m = format_names; m->name != NULL; m++) | |
588 | if (strcasecmp (m->name, name) == 0) | |
589 | break; | |
590 | ||
591 | if (m->name == NULL) | |
592 | { | |
593 | fprintf (stderr, _("%s: unknown format type `%s'\n"), program_name, name); | |
594 | fprintf (stderr, _("%s: supported formats:"), program_name); | |
595 | for (m = format_names; m->name != NULL; m++) | |
596 | fprintf (stderr, " %s", m->name); | |
597 | fprintf (stderr, "\n"); | |
598 | xexit (1); | |
599 | } | |
600 | ||
601 | return m->format; | |
602 | } | |
603 | ||
604 | /* Work out a format type given a file name. If INPUT is non-zero, | |
605 | it's OK to look at the file itself. */ | |
606 | ||
607 | static enum res_format | |
608 | format_from_filename (filename, input) | |
609 | const char *filename; | |
610 | int input; | |
611 | { | |
612 | const char *ext; | |
613 | FILE *e; | |
614 | unsigned char b1, b2, b3, b4, b5; | |
615 | int magic; | |
616 | ||
617 | /* If we have an extension, see if we recognize it as implying a | |
618 | particular format. */ | |
619 | ext = strrchr (filename, '.'); | |
620 | if (ext != NULL) | |
621 | { | |
622 | const struct format_map *m; | |
623 | ||
624 | ++ext; | |
625 | for (m = format_fileexts; m->name != NULL; m++) | |
626 | if (strcasecmp (m->name, ext) == 0) | |
627 | return m->format; | |
628 | } | |
629 | ||
630 | /* If we don't recognize the name of an output file, assume it's a | |
631 | COFF file. */ | |
632 | ||
633 | if (! input) | |
634 | return RES_FORMAT_COFF; | |
635 | ||
636 | /* Read the first few bytes of the file to see if we can guess what | |
637 | it is. */ | |
638 | ||
639 | e = fopen (filename, FOPEN_RB); | |
640 | if (e == NULL) | |
641 | fatal ("%s: %s", filename, strerror (errno)); | |
642 | ||
643 | b1 = getc (e); | |
644 | b2 = getc (e); | |
645 | b3 = getc (e); | |
646 | b4 = getc (e); | |
647 | b5 = getc (e); | |
648 | ||
649 | fclose (e); | |
650 | ||
651 | /* A PE executable starts with 0x4d 0x5a. */ | |
652 | if (b1 == 0x4d && b2 == 0x5a) | |
653 | return RES_FORMAT_COFF; | |
654 | ||
655 | /* A COFF .o file starts with a COFF magic number. */ | |
656 | magic = (b2 << 8) | b1; | |
657 | switch (magic) | |
658 | { | |
659 | case 0x14c: /* i386 */ | |
660 | case 0x166: /* MIPS */ | |
661 | case 0x184: /* Alpha */ | |
662 | case 0x268: /* 68k */ | |
663 | case 0x1f0: /* PowerPC */ | |
664 | case 0x290: /* PA */ | |
665 | return RES_FORMAT_COFF; | |
666 | } | |
667 | ||
668 | /* A RES file starts with 0x0 0x0 0x0 0x0 0x20 0x0 0x0 0x0. */ | |
669 | if (b1 == 0 && b2 == 0 && b3 == 0 && b4 == 0 && b5 == 0x20) | |
670 | return RES_FORMAT_RES; | |
671 | ||
672 | /* If every character is printable or space, assume it's an RC file. */ | |
673 | if ((isprint (b1) || isspace (b1)) | |
674 | && (isprint (b2) || isspace (b2)) | |
675 | && (isprint (b3) || isspace (b3)) | |
676 | && (isprint (b4) || isspace (b4)) | |
677 | && (isprint (b5) || isspace (b5))) | |
678 | return RES_FORMAT_RC; | |
679 | ||
680 | /* Otherwise, we give up. */ | |
681 | fatal (_("can not determine type of file `%s'; use the -I option"), | |
682 | filename); | |
683 | ||
684 | /* Return something to silence the compiler warning. */ | |
685 | return RES_FORMAT_UNKNOWN; | |
686 | } | |
687 | ||
688 | /* Print a usage message and exit. */ | |
689 | ||
690 | static void | |
691 | usage (stream, status) | |
692 | FILE *stream; | |
693 | int status; | |
694 | { | |
695 | fprintf (stream, _("Usage: %s [options] [input-file] [output-file]\n"), | |
696 | program_name); | |
697 | fprintf (stream, _("\ | |
698 | Options:\n\ | |
699 | -i FILE, --input FILE Name input file\n\ | |
700 | -o FILE, --output FILE Name output file\n\ | |
701 | -I FORMAT, --input-format FORMAT\n\ | |
702 | Specify input format\n\ | |
703 | -O FORMAT, --output-format FORMAT\n\ | |
704 | Specify output format\n\ | |
705 | -F TARGET, --target TARGET Specify COFF target\n\ | |
706 | --preprocessor PROGRAM Program to use to preprocess rc file\n\ | |
707 | --include-dir DIR Include directory when preprocessing rc file\n\ | |
708 | --define SYM[=VAL] Define SYM when preprocessing rc file\n\ | |
709 | --language VAL Set language when reading rc file\n")); | |
710 | #ifdef YYDEBUG | |
711 | fprintf (stream, _("\ | |
712 | --yydebug Turn on parser debugging\n")); | |
713 | #endif | |
714 | fprintf (stream, _("\ | |
715 | --help Print this help message\n\ | |
716 | --version Print version information\n")); | |
717 | fprintf (stream, _("\ | |
718 | FORMAT is one of rc, res, or coff, and is deduced from the file name\n\ | |
719 | extension if not specified. A single file name is an input file.\n\ | |
720 | No input-file is stdin, default rc. No output-file is stdout, default rc.\n")); | |
721 | list_supported_targets (program_name, stream); | |
722 | if (status == 0) | |
723 | fprintf (stream, _("Report bugs to [email protected]\n")); | |
724 | exit (status); | |
725 | } | |
726 | ||
727 | /* The main function. */ | |
728 | ||
729 | int | |
730 | main (argc, argv) | |
731 | int argc; | |
732 | char **argv; | |
733 | { | |
734 | int c; | |
735 | char *input_filename; | |
736 | char *output_filename; | |
737 | enum res_format input_format; | |
738 | enum res_format output_format; | |
739 | char *target; | |
740 | char *preprocessor; | |
741 | char *preprocargs; | |
742 | int language; | |
743 | struct res_directory *resources; | |
744 | ||
745 | #if defined (HAVE_SETLOCALE) && defined (HAVE_LC_MESSAGES) | |
746 | setlocale (LC_MESSAGES, ""); | |
747 | #endif | |
748 | bindtextdomain (PACKAGE, LOCALEDIR); | |
749 | textdomain (PACKAGE); | |
750 | ||
751 | program_name = argv[0]; | |
752 | xmalloc_set_program_name (program_name); | |
753 | ||
754 | bfd_init (); | |
755 | set_default_bfd_target (); | |
756 | ||
757 | res_init (); | |
758 | ||
759 | input_filename = NULL; | |
760 | output_filename = NULL; | |
761 | input_format = RES_FORMAT_UNKNOWN; | |
762 | output_format = RES_FORMAT_UNKNOWN; | |
763 | target = NULL; | |
764 | preprocessor = NULL; | |
765 | preprocargs = NULL; | |
766 | language = -1; | |
767 | ||
768 | while ((c = getopt_long (argc, argv, "i:o:I:O:F:", long_options, | |
769 | (int *) 0)) != EOF) | |
770 | { | |
771 | switch (c) | |
772 | { | |
773 | case 'i': | |
774 | input_filename = optarg; | |
775 | break; | |
776 | ||
777 | case 'o': | |
778 | output_filename = optarg; | |
779 | break; | |
780 | ||
781 | case 'I': | |
782 | input_format = format_from_name (optarg); | |
783 | break; | |
784 | ||
785 | case 'O': | |
786 | output_format = format_from_name (optarg); | |
787 | break; | |
788 | ||
789 | case 'F': | |
790 | target = optarg; | |
791 | break; | |
792 | ||
793 | case OPTION_PREPROCESSOR: | |
794 | preprocessor = optarg; | |
795 | break; | |
796 | ||
797 | case OPTION_DEFINE: | |
798 | if (preprocargs == NULL) | |
799 | { | |
800 | preprocargs = xmalloc (strlen (optarg) + 3); | |
801 | sprintf (preprocargs, "-D%s", optarg); | |
802 | } | |
803 | else | |
804 | { | |
805 | char *n; | |
806 | ||
807 | n = xmalloc (strlen (preprocargs) + strlen (optarg) + 4); | |
808 | sprintf (n, "%s -D%s", preprocargs, optarg); | |
809 | free (preprocargs); | |
810 | preprocargs = n; | |
811 | } | |
812 | break; | |
813 | ||
814 | case OPTION_INCLUDE_DIR: | |
815 | if (preprocargs == NULL) | |
816 | { | |
817 | preprocargs = xmalloc (strlen (optarg) + 3); | |
818 | sprintf (preprocargs, "-I%s", optarg); | |
819 | } | |
820 | else | |
821 | { | |
822 | char *n; | |
823 | ||
824 | n = xmalloc (strlen (preprocargs) + strlen (optarg) + 4); | |
825 | sprintf (n, "%s -I%s", preprocargs, optarg); | |
826 | free (preprocargs); | |
827 | preprocargs = n; | |
828 | } | |
829 | ||
830 | { | |
831 | struct include_dir *n, **pp; | |
832 | ||
833 | n = (struct include_dir *) xmalloc (sizeof *n); | |
834 | n->next = NULL; | |
835 | n->dir = optarg; | |
836 | ||
837 | for (pp = &include_dirs; *pp != NULL; pp = &(*pp)->next) | |
838 | ; | |
839 | *pp = n; | |
840 | } | |
841 | ||
842 | break; | |
843 | ||
844 | case OPTION_LANGUAGE: | |
845 | language = strtol (optarg, (char **) NULL, 16); | |
846 | break; | |
847 | ||
848 | #ifdef YYDEBUG | |
849 | case OPTION_YYDEBUG: | |
850 | yydebug = 1; | |
851 | break; | |
852 | #endif | |
853 | ||
854 | case OPTION_HELP: | |
855 | usage (stdout, 0); | |
856 | break; | |
857 | ||
858 | case OPTION_VERSION: | |
859 | print_version ("windres"); | |
860 | break; | |
861 | ||
862 | default: | |
863 | usage (stderr, 1); | |
864 | break; | |
865 | } | |
866 | } | |
867 | ||
868 | if (input_filename == NULL && optind < argc) | |
869 | { | |
870 | input_filename = argv[optind]; | |
871 | ++optind; | |
872 | } | |
873 | ||
874 | if (output_filename == NULL && optind < argc) | |
875 | { | |
876 | output_filename = argv[optind]; | |
877 | ++optind; | |
878 | } | |
879 | ||
880 | if (argc != optind) | |
881 | usage (stderr, 1); | |
882 | ||
883 | if (input_format == RES_FORMAT_UNKNOWN) | |
884 | { | |
885 | if (input_filename == NULL) | |
886 | input_format = RES_FORMAT_RC; | |
887 | else | |
888 | input_format = format_from_filename (input_filename, 1); | |
889 | } | |
890 | ||
891 | if (output_format == RES_FORMAT_UNKNOWN) | |
892 | { | |
893 | if (output_filename == NULL) | |
894 | output_format = RES_FORMAT_RC; | |
895 | else | |
896 | output_format = format_from_filename (output_filename, 0); | |
897 | } | |
898 | ||
899 | /* Read the input file. */ | |
900 | ||
901 | switch (input_format) | |
902 | { | |
903 | default: | |
904 | abort (); | |
905 | case RES_FORMAT_RC: | |
906 | resources = read_rc_file (input_filename, preprocessor, preprocargs, | |
907 | language); | |
908 | break; | |
909 | case RES_FORMAT_RES: | |
910 | resources = read_res_file (input_filename); | |
911 | break; | |
912 | case RES_FORMAT_COFF: | |
913 | resources = read_coff_rsrc (input_filename, target); | |
914 | break; | |
915 | } | |
916 | ||
917 | if (resources == NULL) | |
918 | fatal (_("no resources")); | |
919 | ||
920 | /* Sort the resources. This is required for COFF, convenient for | |
921 | rc, and unimportant for res. */ | |
922 | ||
923 | resources = sort_resources (resources); | |
924 | ||
925 | /* Write the output file. */ | |
926 | ||
927 | reswr_init (); | |
928 | ||
929 | switch (output_format) | |
930 | { | |
931 | default: | |
932 | abort (); | |
933 | case RES_FORMAT_RC: | |
934 | write_rc_file (output_filename, resources); | |
935 | break; | |
936 | case RES_FORMAT_RES: | |
937 | write_res_file (output_filename, resources); | |
938 | break; | |
939 | case RES_FORMAT_COFF: | |
940 | write_coff_file (output_filename, target, resources); | |
941 | break; | |
942 | } | |
943 | ||
944 | xexit (0); | |
945 | return 0; | |
946 | } | |
947 |