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