Document how SEH works in Wine.
[wine] / tools / bin2res.c
1 /************************************************
2  *
3  * Converting binary resources from/to *.rc files
4  *
5  * Copyright 1999 Juergen Schmied
6  * Copyright 2003 Dimitrie O. Paun
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "config.h"
24 #include "wine/port.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <ctype.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <limits.h>
32 #ifdef HAVE_SYS_PARAM_H
33 # include <sys/param.h>
34 #endif
35
36 static const char* help =
37         "Usage: bin2res [OPTIONS] <rsrc.rc>\n"
38         "  -a archive binaries into the <rsrc.rc> file\n"
39         "  -x extract binaries from the <rsrc.rc> file\n"
40         "  -i <filename> archive the named file into the <rsrc.rc> file\n"
41         "  -o <filename> extract the named file from the <rsrc.rc> file\n"
42         "  -f force processing of older resources\n"
43         "  -v causes the command to be verbous during processing\n" 
44         "  -h print this help screen and exit\n"
45         "\n"
46         "This tool allows the insertion/extractions of embedded binary\n"
47         "resources to/from .rc files, for storage within the cvs tree.\n"
48         "This is accomplished by placing a magic marker in a comment\n"
49         "just above the resource. The marker consists of the BINRES\n"
50         "string followed by the file name. For example, to insert a\n"
51         "brand new binary resource in a .rc file, place the marker\n"
52         "above empty brackets:\n"
53         "    /* BINRES idb_std_small.bmp */\n"
54         "    IDB_STD_SMALL BITMAP idb_std_small.bmp\n"
55         "    /* {\n"
56         "    } */\n"
57         "To merge the binary resources into the .rc file, run:\n"
58         "   bin2res -a myrsrc.rc\n"
59         "Only resources that are newer than the .rc are processed.\n"
60         "To extract the binary resources from the .rc file, run:\n"
61         "  bin2res -x myrsrc.rc\n"
62         "Binary files newer than the .rc file are not overwritten.\n"
63         "\n"
64         "To force processing of all resources, use the -f flag.\n"
65         "To process a particular file, use the -i/-o options.\n";
66
67 void usage(void)
68 {
69     printf(help);
70     exit(1);
71 }
72
73 int insert_hexdump (FILE* outfile, FILE* infile)
74 {
75     int i, c;
76
77     fprintf (outfile, "{\n '");
78     for (i = 0; (c = fgetc(infile)) != EOF; i++)
79     {
80         if (i && (i % 16) == 0) fprintf (outfile, "'\n '");
81         if (i % 16)  fprintf (outfile, " ");
82         fprintf(outfile, "%02X", c);
83     }
84     fprintf (outfile, "'\n}");
85
86     return 1;
87 }
88
89 int hex2bin(char c)
90 {
91     if (!isxdigit(c)) return -1024;
92     if (isdigit(c)) return c - '0';
93     return toupper(c) - 'A' + 10;
94 }
95
96 int extract_hexdump (FILE* outfile, FILE* infile)
97 {
98     int byte, c;
99
100     while ( (c = fgetc(infile)) != EOF && c != '}')
101     {
102         if (isspace(c) || c == '\'') continue;
103         byte = 16 * hex2bin(c);
104         c = fgetc(infile);
105         if (c == EOF) return 0;
106         byte += hex2bin(c);
107         if (byte < 0) return 0;
108         fputc(byte, outfile);
109     }
110     return 1;
111 }
112
113 const char* parse_marker(const char *line, time_t* last_updated)
114 {
115     static char res_file_name[PATH_MAX], *rpos, *wpos;
116     struct stat st;
117
118     if (!(rpos = strstr(line, "BINRES"))) return 0;
119     for (rpos += 6; *rpos && isspace(*rpos); rpos++) /**/;
120     for (wpos = res_file_name; *rpos && !isspace(*rpos); ) *wpos++ = *rpos++;
121     *wpos = 0;
122
123     *last_updated = (stat(res_file_name, &st) < 0) ? 0 : st.st_mtime;
124
125     return res_file_name;
126 }
127
128 int process_resources(const char* input_file_name, const char* specific_file_name, 
129                       int inserting, int force_processing, int verbose)
130 {
131     char buffer[2048], tmp_file_name[PATH_MAX];
132     const char *res_file_name;
133     time_t rc_last_update, res_last_update;
134     FILE *fin, *fres, *ftmp = 0;
135     struct stat st;
136     int fd, c;
137
138     if (!(fin = fopen(input_file_name, "r"))) return 0;
139     if (stat(input_file_name, &st) < 0) return 0;
140     rc_last_update = st.st_mtime;
141
142     if (inserting)
143     {
144         strcpy(tmp_file_name, input_file_name);
145         strcat(tmp_file_name, "-XXXXXX.temp");
146         if ((fd = mkstemps(tmp_file_name, 5)) == -1)
147         {
148             strcpy(tmp_file_name, "/tmp/bin2res-XXXXXX.temp");
149             if ((fd = mkstemps(tmp_file_name, 5)) == -1) return 0;
150         }
151         if (!(ftmp = fdopen(fd, "w"))) return 0;
152     }
153
154     for (c = EOF; fgets(buffer, sizeof(buffer), fin); c = EOF)
155     {
156         if (inserting) fprintf(ftmp, "%s", buffer);
157         if (!(res_file_name = parse_marker(buffer, &res_last_update))) continue;
158         if ( (specific_file_name && strcmp(specific_file_name, res_file_name)) ||
159              (!force_processing && ((rc_last_update < res_last_update) == !inserting)) )
160         {
161             if (verbose) printf("skipping '%s'\n", res_file_name);
162             continue;
163         }
164
165         if (verbose) printf("processing '%s'\n", res_file_name);
166         while ( (c = fgetc(fin)) != EOF && c != '{')
167             if (inserting) fputc(c, ftmp);
168         if (c == EOF) break;
169
170         if (!(fres = fopen(res_file_name, inserting ? "rb" : "wb"))) break;
171         if (inserting)
172         {
173             if (!insert_hexdump(ftmp, fres)) break;
174             while ( (c = fgetc(fin)) != EOF && c != '}') /**/;
175         }
176         else
177         {
178             if (!extract_hexdump(fres, fin)) break;
179         }
180         fclose(fres);
181     }
182
183     fclose(fin);
184
185     if (inserting)
186     {
187         fclose(ftmp);
188         if (c == EOF)
189         {
190             if (rename(tmp_file_name, input_file_name) < 0)
191             {
192                 /* try unlinking first, Windows rename is brain-damaged */
193                 if (unlink(input_file_name) < 0 || rename(tmp_file_name, input_file_name) < 0)
194                 {
195                     unlink(tmp_file_name);
196                     return 0;
197                 }
198             }
199         }
200         else unlink(tmp_file_name);
201     }
202
203     return c == EOF;
204 }
205
206 int main(int argc, char **argv)
207 {
208     int convert_dir = 0, optc;
209     int force_overwrite = 0, verbose = 0;
210     const char* input_file_name = 0;
211     const char* specific_file_name = 0;
212
213     while((optc = getopt(argc, argv, "axi:o:fhv")) != EOF)
214     {
215         switch(optc)
216         {
217         case 'a':
218         case 'x':
219             if (convert_dir) usage();
220             convert_dir = optc;
221         break;
222         case 'i':
223         case 'o':
224             if (specific_file_name) usage();
225             specific_file_name = optarg;
226             optc = ((optc == 'i') ? 'a' : 'x');
227             if (convert_dir && convert_dir != optc) usage();
228             convert_dir = optc;
229         break;
230         case 'f':
231             force_overwrite = 1;
232         break;
233         case 'v':
234             verbose = 1;
235         break;
236         case 'h':
237             printf(help);
238             exit(0);
239         break;
240         default:
241             usage();
242         }
243     }
244
245     if (optind + 1 != argc) usage();
246     input_file_name = argv[optind];
247
248     if (!convert_dir) usage();
249
250     if (!process_resources(input_file_name, specific_file_name, 
251                            convert_dir == 'a', force_overwrite, verbose))
252     {
253         perror("Processing failed");
254         exit(1);
255     }
256
257     return 0;
258 }