root/trunk/luigi/wrapres.d

Revision 9, 24.0 kB (checked in by baxissimo, 2 years ago)

Added resource wrapper. (A separate util exe)

Line 
1 /** wrapres.d
2  *
3  * A program to wrap up binary or string resources into D byte[] or
4  * character arrays.
5  *
6  * Also includes the ability to read images and decode them into bytes.
7  * Image formats are limited to those supported by DevIL
8  */
9 /*
10   Copyright (C) 2006 William V. Baxter III
11
12   This software is provided 'as-is', without any express or implied
13   warranty.  In no event will the authors be held liable for any
14   damages arising from the use of this software.
15
16   Permission is granted to anyone to use this software for any
17   purpose, including commercial applications, and to alter it and
18   redistribute it freely, subject to the following restrictions:
19
20   1. The origin of this software must not be misrepresented; you must
21      not claim that you wrote the original software. If you use this
22      software in a product, an acknowledgment in the product
23      documentation would be appreciated but is not required.
24
25   2. Altered source versions must be plainly marked as such, and must
26      not be misrepresented as being the original software.
27   3. This notice may not be removed or altered from any source distribution.
28
29   William Baxter wbaxter@gmail.com
30 */
31 import std.file;
32 import std.cstream;
33 import std.stream;
34 import std.ctype;
35 static import std.conv;
36
37 static import std.string;
38 alias std.string.replace replace;
39 alias std.string.rfind rfind;
40 alias std.string.toStringz toStringz;
41 alias std.string.format format;
42
43 // This is a workaround for http://d.puremagic.com/issues/show_bug.cgi?id=590
44 // But it could be useful for allowing non-native endianness as well
45 static char[] NL = "\n";
46
47 import std.c.stdlib : exit,getenv;
48
49
50 import derelict.devil.il;
51
52 import std.stdio : writefln, writef;
53
54 void show_usage()
55 {
56     char[] msg =
57         r"wrapres v1.0 - a program to wrap resources into D arrays
58 Usage:
59     wrapres [flags] resfile [flags] resfile ...
60
61 Flags can be:
62 -h,-help:  Display this help message
63
64 -b,-binary:
65            Use binary mode output.  The output is a ubyte[].
66            [default]
67 -t,-text:
68            Use text mode output.  The output is a char[] instead of a
69            ubyte[].
70 -tw:
71            Text mode output with 'wysiwyg' strings.  Like -t, but a
72            wsyiwyg r-string is used.
73 -o,-out,-output [filename]:
74            Write output to filename instead of stdout. Use '-' to
75            specify stdout.
76 -n,-name [identifier]:
77            Name to use for the variable in the output.  If not
78            specified the name is generated from the filename with
79            non-identifier characters replaced by '_'.  If the name
80            contains a number format like %%03d, then this will be used
81            to create a numbered sequence of variables.
82 -x,-hex:   Hex output for binary mode [the default]
83 -X,-HEX:   Uppercase hex output for binary mode
84 -oct:      Octal output for binary mode
85 -d,-dec:   Decimal output for binary mode
86 -w,-width [number]:
87            Width used in formatting output numbers
88 -i,-indent [number]:
89            Number of spaces of indentation to use in formatting code.
90            [default=4]
91 -wrap [number]:
92            Column at which to wrap output. If 0 then no wrapping.
93            [default=78]
94 -stripext: If no -name is specifed, then this drops the filename
95            extension from the generated variable name.
96            I.e. animage.png becomes 'animage'.
97 -keepext:
98            If no -name is specifed, then this keeps the filename
99            extension in the generated variable name.  I.e. animage.png
100            becomes 'animage_png'.  [default]
101 -image:
102            Treat the resfile as an image, decode it and write the
103            result as a 2D array of ubyte[].  This functionality
104            requires the presence of the DevIL image library, which
105            can be obtained from
106                  http://openil.sourceforge.net/download.php
107 -imagewh:
108            Treat the resfile as an image, decode it and write the
109            result as an array of ubyte[].  The first two bytes
110            represent the width and height of the image.  Because these
111            values are limited to 0<x<255, this method cannot be used
112            for images bigger than 255x255.  This functionality
113            requires the presence of the DevIL image libary, which
114            can be obtained from
115                   http://openil.sourceforge.net/download.php
116 -raw,-noimage:
117            The opposite of -image/imagewh.  Do no special decoding on the
118            input data [default]
119 -m,-module [modulename]:
120            Put the module statement 'module modulename;' at the top
121            of the output.
122 @[filename]
123            Read additional commands from 'filename'. These are treated
124            exactly as if they had been typed at the same location in the
125            commandline.  The file can contain // or # style comments
126            and can have multiple lines.  They can also contain environment
127            variables using $FOO, $(FOO), or ${FOO} syntax.
128
129 Flags are sticky, that is they apply to all subsequent files, unless
130 overridden by a subsequent command.  For instance the -image flag
131 will hold until a -raw flag appears to counteract it.
132 ";
133
134     writefln(msg);
135     exit(0);
136 }
137
138 struct WriterParams
139 {
140     char[] varname = "";
141     int linelen = 78;
142     int indent = 4;
143     int formatwidth = 3;
144     char[] formatchar = "x";
145     bool textmode = false;
146     bool textwysiwyg = false;
147     bool stripext = false;
148     bool decode_image = false;
149     bool image2d = true;
150     char[] modulename = "";
151 }
152
153 class Writer
154 {
155     WriterParams params;
156
157     ubyte[] readAll(InputStream inf)
158     {
159         ubyte[] ret;
160         // prealloc trick
161         ret.length = inf.available;
162         ret.length = 0;
163        
164         const uint rdbuflen = 4096;
165         ubyte[] scratch = new ubyte[rdbuflen];
166         while (true) {
167             uint nread = inf.read(scratch);
168             ret ~= scratch[0..nread];
169             if (rdbuflen != nread)
170                 break;
171         }
172         return ret;
173     }
174    
175     char[] makeDecimalFormatStr()
176     {
177         char [] fmt;
178         fmt ~= "%";
179         if (params.formatwidth) {
180             fmt ~= " ";
181             fmt ~= std.string.toString(params.formatwidth);
182         }
183         fmt ~= params.formatchar;
184         fmt ~= ", ";
185         return fmt;
186     }
187     char[] makeHexFormatStr()
188     {
189         char [] fmt;
190         fmt ~= "%#";
191         if (params.formatwidth) {
192             fmt ~= "0";
193             fmt ~= std.string.toString(params.formatwidth+1);
194         }
195         fmt ~= params.formatchar;
196         fmt ~= ", ";
197         return fmt;
198     }
199
200     char[] makeOctalFormatStr()
201     {
202         char [] fmt;
203         fmt ~= "%#";
204         if (params.formatwidth) {
205             fmt ~= "0";
206             fmt ~= std.string.toString(params.formatwidth+1);
207         }
208         fmt ~= params.formatchar;
209         fmt ~= ", ";
210         return fmt;
211     }
212     char[] makeFormatStr()
213     {
214         switch (params.formatchar)
215         {
216         case "d":
217         case "s": 
218             return makeDecimalFormatStr();
219         case "x":
220         case "X":
221             return makeHexFormatStr();
222         case "o": 
223         case "O":
224             return makeOctalFormatStr();
225         default:
226             throw new Exception("Bad format string");
227         }
228     }
229
230     void output(InputStream inf, OutputStream outf)
231     {
232         if (params.textmode) {
233             if (params.textwysiwyg) {
234                 outputTextWSYIWYG(inf,outf);
235             } else {
236                 outputTextEscaped(inf,outf);
237             }
238         }
239         else {
240             outputBinary(inf,outf);
241         }
242         outf.writef(NL);
243     }
244
245     void outputTextWSYIWYG(InputStream inf, OutputStream outf)
246     {
247         char[] tab = std.string.repeat(" ", params.indent);
248
249         char[] outstr; //outstr.length = inf.available; outstr.length = 0;
250         int outlen = 0;
251         foreach (char[] line; inf)
252         {
253             // the newline is stripped, so +1
254             outlen += line.length+1;
255
256             // Wysywig strings are great, but there's no freaking way to put a quote
257             // inside of one.  I tried several options, but none of them
258             // looks particularly clean.
259             // But overall wsywig strings are nice (no \t's or \n's etc)
260            
261             //outstr ~= line.replace("\"", "\"\"\\\"\"r\"");
262             //outstr ~= line.replace("\"", "\"~'\"'~\"r");
263             outstr ~= line.replace("\"", "\"\\\"r\"");
264             outstr ~= NL;
265         }
266         outf.writef("static const char[%s] %s = ",outlen, params.varname);
267         outf.writef("  "~NL~"r\"");
268         outf.writeString(outstr);
269         outf.writeString("\";"~NL~"");
270     }
271
272     void outputTextEscaped(InputStream inf, OutputStream outf)
273     {
274         char[] tab = std.string.repeat(" ", params.indent);
275
276         char[] outstr; //outstr.length = inf.available; outstr.length = 0;
277         int outlen = 0;
278         foreach (char[] line; inf)
279         {
280             // the newline is stripped, so +1
281             outlen += line.length+1;
282
283             line = line.replace(`\`, `\\`);
284             line = line.replace("\"", "\\\"");
285             outstr ~= tab;
286             outstr ~= "\"";
287             outstr ~= line;
288             outstr ~= "\\n\""~NL;
289         }
290         outf.writef("static const char[%s] %s = "~NL, outlen, params.varname);
291         outstr[$-NL.length] = ';';
292         outf.writeString(outstr);
293         outf.writeString(NL);
294     }
295
296     void outputBinaryData(ubyte[] data, OutputStream outf)
297     {
298         char [] fmt = makeFormatStr();
299
300         char[] tab = std.string.repeat(" ", params.indent);
301
302         outf.writef("static const ubyte[%s] %s = ["~NL, data.length, params.varname);
303         uint llen = 0;
304         outf.writef(tab);
305         llen = tab.length;
306         foreach(b; data) {
307             char[] v = format(fmt, cast(uint)b);
308             llen += v.length;
309             if (params.linelen > 0 && llen > params.linelen) {
310                 outf.writef(NL, tab);
311                 llen = tab.length + v.length;
312             }
313             outf.writef(v);
314         }
315         outf.writef(NL~"];"~NL);
316     }
317
318     void outputBinaryData2D(ubyte[] data, int width, int height, OutputStream outf)
319     {
320         char [] fmt = makeFormatStr();
321
322         char[] tab = std.string.repeat(" ", params.indent);
323
324         int rowlen = data.length/height;
325         outf.writef("static const ubyte %s[%s][%s] = ["~NL, params.varname,height,rowlen);
326         uint llen = 0;
327         for(int j=0; j<height; j++)
328         {
329             outf.writef(tab,"[");
330             llen = tab.length+1;
331             for(int i=0; i<rowlen; i++)
332             {
333                 ubyte b = data[j*rowlen + i];
334                 char[] v = format(fmt, cast(uint)b);
335                 llen += v.length;
336                 if (params.linelen>0 && llen > params.linelen) {
337                     outf.writef(NL, tab, " ");
338                     llen = tab.length + v.length + 1;
339                 }
340                 outf.writef(v);
341             }
342             outf.writef("],"~NL);
343         }
344         outf.writef(NL~"];"~NL);
345     }
346
347     void outputBinary(InputStream inf, OutputStream outf)
348
349     {
350         ubyte[] data = readAll(inf);
351         outputBinaryData(data,outf);
352     }
353
354     void outputImage(char[] fname, OutputStream outf)
355     {
356         ILuint id;
357         ilGenImages(1, &id);
358         ilBindImage(id);
359         if (!ilLoadImage(std.string.toStringz(fname))) {
360             report_fatal_error(format("Unable to open image file '%s'.", fname));
361         }
362
363         int w = ilGetInteger(IL_IMAGE_WIDTH);
364         int h = ilGetInteger(IL_IMAGE_HEIGHT);
365         int ifmt = ilGetInteger(IL_IMAGE_FORMAT);
366         int myfmt;
367         int chan;
368         switch(ifmt) {
369         case IL_COLOUR_INDEX:
370             chan = 4;
371             myfmt = IL_RGBA;
372             break;
373         case IL_RGB:
374             chan = 3;
375             myfmt = IL_RGB;
376             break;
377         case IL_RGBA:
378             chan = 4;
379             myfmt = IL_RGBA;
380             break;
381         case IL_BGR:
382             chan = 3;
383             myfmt = IL_RGB;
384             break;
385         case IL_BGRA:
386             chan = 4;
387             myfmt = IL_RGBA;
388             break;
389         case IL_LUMINANCE:
390             chan = 1;
391             myfmt = IL_LUMINANCE;
392             break;
393         case IL_LUMINANCE_ALPHA:
394             chan = 2;
395             myfmt = IL_LUMINANCE_ALPHA;
396             break;
397         default:
398             report_fatal_error(format("Unknown format for image file '%s'.", fname));
399         }
400        
401         auto data = new ubyte[w*h*chan];
402         ilCopyPixels(0,0,0,
403                      w, h, 1,
404                      myfmt, IL_UNSIGNED_BYTE,
405                      &data[0]);
406         ILenum err = ilGetError();
407         if (err != IL_NO_ERROR) {
408             report_fatal_error(format("Unable to get data from image file '%s'.", fname));
409         }
410        
411         if (!params.image2d) {
412             if (w>ubyte.max || h>ubyte.max) {
413                 report_fatal_error(format("Image %s too big for w,h header mode", fname));
414             }
415             data = [cast(ubyte)w,cast(ubyte)h] ~ data;
416             outputBinaryData(data, outf);
417         }
418         else {
419             outputBinaryData2D(data, w, h, outf);
420         }
421
422         ilDeleteImages(1, &id);
423     }
424 }
425
426 char[] filename_to_argname(char[] fname, bool stripext)
427 {
428     // replace anything not in _a-zA-Z with __
429     // Ignore final .foo extension
430     char[] varname; varname.length = fname.length; varname.length = 0;
431     int end = -1;
432     if (stripext) {
433         end = fname.rfind('.');
434     }
435     if (end<0) end = fname.length;
436     foreach(dchar c; fname[0..end]) {
437         if ( (c>='a' && c<='z') ||
438              (c>='A' && c<='Z') )
439         {
440             varname ~= c;
441         }
442         else
443         {
444             varname ~= "_";
445         }
446     }
447     return varname;
448 }
449
450
451 void report_fatal_error(char [] msg)
452 {
453     derr.writefln(msg);
454     exit(-1);
455 }
456
457
458
459 bool initImageLib()
460 {
461     try {
462         DerelictIL.load();
463         ilInit();
464     }
465     catch (Exception e) {
466         return false;
467     }
468     return true;
469 }
470
471 char[][] fileglob(char[] pattern)
472 {
473     char[][] list = listdir("", pattern);
474     return list;
475 }
476
477 char[][] read_args_from_file(char[] fname)
478 {
479     scope InputStream f = new BufferedFile(fname);
480     // basically we want to do a 'split' on whitespace.
481     // but if there are quoted strings then they should be treated as
482     // one token.
483     // Also allow // comments
484     char getc_esc() {
485         char c = f.getc();
486         if (c == '\\') {
487             // if escaped return the next char literally
488             return f.getc();
489         }
490         return c;
491     }
492
493     void skipspace() {
494         char c;
495         while(!f.eof && (c=f.getc())!=char.init && isspace(c)) {}
496         if (c!=char.init) {
497             f.ungetc(c);
498         }
499     }
500     void skip_to_eol() {
501         char c;
502         while((c=f.getc())!=char.init && c!='\n' && c!='\r') { }
503     }
504     char[] munch_quote(char q) {
505         // go till we find unescaped q char
506         char[] qbuf; qbuf.length = 64; qbuf.length = 0;
507         bool escaped = false;
508         char c;
509         while((c=f.getc())!=char.init) {
510             if (escaped) {
511                 qbuf ~= c;
512                 escaped = false;
513             }
514             else if (c=='\\') {
515                 escaped = true;
516             }
517             else if (c==q) {
518                 return qbuf;
519             }
520             else {
521                 qbuf ~= c;
522             }
523         }
524         return qbuf;
525     }
526
527     char[] munch_identifier() {
528         char[] id; id.length = 64; id.length=0;
529
530         char p = f.getc();
531         if (p=='(') p = ')';
532         else if (p=='{') p = '}';
533         else {
534             id ~= p;
535             p=0;
536         }
537
538         char c;
539         if (p) {
540             while((c=f.getc())!=char.init && c!=p) {
541                 if (!isspace(c))
542                     id ~= c;
543             }
544         }
545         else {
546             while((c=f.getc())!=char.init && (isalnum(c) || c=='_')) {
547                 id ~= c;
548             }
549             if (c!=char.init) f.ungetc(c);
550         }
551         return id;
552     }
553
554     char[] tbuf; tbuf.length = 128; tbuf.length = 0;
555     char[] nextToken()
556     {
557         tbuf.length = 0;
558         skipspace();
559         bool escaped = false;
560         char c;
561         while((c=f.getc())!=char.init && (escaped || !isspace(c))) {
562             if (escaped) {
563                 tbuf ~= c;
564                 escaped = false;
565             }
566             else if (c=='\\') {
567                 escaped = true;
568             }
569             else if (c=='#') {  // # comment
570                 skip_to_eol();
571                 skipspace();
572             }
573             else if (c=='/') {  // start of // comment?
574                 char c2=f.getc();
575                 if (c2=='/') {
576                     skip_to_eol();
577                     skipspace();
578                     if (tbuf.length) {
579                         // return the current token
580                         break;
581                     }
582                     // otherwise keep looking for the next token
583                 }
584                 else {
585                     tbuf ~= c;
586                     f.ungetc(c2);
587                 }
588             }
589             else if (c=='$') {
590                 // start of env var identifier
591                 char[] id = munch_identifier();
592                 tbuf ~= std.string.toString(getenv(toStringz(id)));
593             }
594             else if (c=='"' || c=='\'') {
595                 // start quoted string
596                 tbuf ~= munch_quote(c);
597                 break;
598             }
599             else {
600                 tbuf ~= c;
601             }
602         }
603         return tbuf.dup;
604     }
605
606     char[][] ret;
607     while (true) {
608         char[] t = nextToken();
609         if (!t.length) break;
610         ret ~= t;
611     }
612     return ret;
613 }
614
615 T[] insert(T)(T[] s, size_t index, T[] sub)
616 in
617 {
618     assert(0 <= index && index <= s.length);
619 }
620 body
621 {
622     if (sub.length == 0)
623     return s;
624
625     if (s.length == 0)
626     return sub;
627
628     int newlength = s.length + sub.length;
629     T[] result = new T[newlength];
630