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