nrg: Improved support for v2
[deark.git] / modules / wmf.c
blob5a902b5a625a3f6218fa4c8c0a0e76affdf46c00
1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // Windows Metafile (WMF)
7 #include <deark-config.h>
8 #include <deark-private.h>
9 #include <deark-fmtutil.h>
10 DE_DECLARE_MODULE(de_module_wmf);
12 typedef struct localctx_struct {
13 int has_aldus_header;
14 int input_encoding;
15 i64 wmf_file_type;
16 i64 wmf_windows_version;
17 unsigned int num_objects;
18 dbuf *embedded_emf;
19 u8 *object_table;
20 } lctx;
22 struct escape_info {
23 u16 escfn;
24 const char *name;
25 void *reserved1;
28 struct decoder_params {
29 u16 recfunc;
30 u8 rectype; // low byte of recfunc
31 i64 recpos;
32 i64 recsize_words; // total record size in 16-bit units
33 i64 recsize_bytes; // total record size in bytes
34 i64 dpos;
35 i64 dlen;
38 // Handler functions return 0 on fatal error, otherwise 1.
39 typedef int (*record_decoder_fn)(deark *c, lctx *d, struct decoder_params *dp);
41 struct wmf_func_info {
42 u8 rectype; // Low byte of the RecordFunction field
43 // Flags:
44 // 0x1: Creates an object
45 u8 flags;
46 const char *name;
47 record_decoder_fn fn;
50 // Note: This is duplicated in emf.c
51 static u32 colorref_to_color(u32 colorref)
53 u32 r,g,b;
54 r = DE_COLOR_B(colorref);
55 g = DE_COLOR_G(colorref);
56 b = DE_COLOR_R(colorref);
57 return DE_MAKE_RGB(r,g,b);
60 // Note: This is duplicated in emf.c
61 static void do_dbg_colorref(deark *c, lctx *d, struct decoder_params *dp, u32 colorref)
63 u32 clr;
64 char csamp[16];
66 clr = colorref_to_color(colorref);
67 de_get_colorsample_code(c, clr, csamp, sizeof(csamp));
68 de_dbg(c, "colorref: 0x%08x%s", (unsigned int)colorref, csamp);
71 static int handler_colorref(deark *c, lctx *d, struct decoder_params *dp)
73 u32 colorref;
75 if(dp->dlen<4) goto done;
76 colorref = (u32)de_getu32le(dp->dpos);
77 do_dbg_colorref(c, d, dp, colorref);
78 done:
79 return 1;
82 static int wmf_handler_TEXTOUT(deark *c, lctx *d, struct decoder_params *dp)
84 i64 pos = dp->dpos;
85 i64 stringlen;
86 de_ucstring *s = NULL;
88 stringlen = de_getu16le(pos);
89 pos += 2;
91 if(pos+stringlen > dp->dpos+dp->dlen) goto done;
92 s = ucstring_create(c);
93 dbuf_read_to_ucstring_n(c->infile, pos, stringlen, DE_DBG_MAX_STRLEN, s,
94 0, d->input_encoding);
95 de_dbg(c, "text: \"%s\"", ucstring_getpsz(s));
97 done:
98 ucstring_destroy(s);
99 return 1;
102 static int wmf_handler_BITBLT_STRETCHBLT_DIBBITBLT(deark *c, lctx *d, struct decoder_params *dp)
104 i64 pos = dp->dpos;
105 int has_src_bitmap;
106 unsigned int RasterOperation;
107 i64 XSrc, YSrc;
108 i64 Width, Height;
109 i64 YDest, XDest;
111 has_src_bitmap = (dp->recsize_words != ((i64)(dp->recfunc>>8)+3));
112 de_dbg(c, "has src bitmap: %d", has_src_bitmap);
113 if(!has_src_bitmap) goto done;
115 RasterOperation = (unsigned int)de_getu32le(pos);
116 de_dbg(c, "RasterOperation: 0x%08x", RasterOperation);
117 pos += 4;
119 if(dp->rectype==0x23) { // STRETCHBLT
120 i64 SrcWidth, SrcHeight;
121 SrcHeight = de_geti16le_p(&pos);
122 SrcWidth = de_geti16le_p(&pos);
123 de_dbg(c, "SrcWidth, SrcHeight: %d"DE_CHAR_TIMES"%d",
124 (int)SrcWidth, (int)SrcHeight);
127 YSrc = de_geti16le_p(&pos);
128 XSrc = de_geti16le_p(&pos);
129 de_dbg(c, "XSrc, YSrc: (%d, %d)", (int)XSrc, (int)YSrc);
131 Height = de_geti16le_p(&pos);
132 Width = de_geti16le_p(&pos);
133 de_dbg_dimensions(c, Width, Height);
135 YDest = de_geti16le_p(&pos);
136 XDest = de_geti16le_p(&pos);
137 de_dbg(c, "XDest, YDest: (%d, %d)", (int)XDest, (int)YDest);
139 // TODO: Bitmap16 object (if BITBLT or STRETCHBLT)
140 if(dp->rectype==0x40) { // DIBBITBLT
141 i64 dib_pos, dib_len;
143 // TODO: Merge this with the DIBSTRETCHBLT, STRETCHDIB code.
144 dib_pos = pos;
145 dib_len = dp->dpos + dp->dlen - dib_pos;
147 if(dib_len<12) goto done;
148 de_dbg(c, "DIB at %d, size=%d", (int)dib_pos, (int)dib_len);
150 de_dbg_indent(c, 1);
151 de_run_module_by_id_on_slice(c, "dib", NULL, c->infile, dib_pos, dib_len);
152 de_dbg_indent(c, -1);
155 done:
156 return 1;
159 static const struct escape_info escape_info_arr[] = {
160 { 0x0001, "NEWFRAME", NULL },
161 { 0x0002, "ABORTDOC", NULL },
162 { 0x0003, "NEXTBAND", NULL },
163 { 0x0004, "SETCOLORTABLE", NULL },
164 { 0x0005, "GETCOLORTABLE", NULL },
165 { 0x0006, "FLUSHOUT", NULL },
166 { 0x0007, "DRAFTMODE", NULL },
167 { 0x0008, "QUERYESCSUPPORT", NULL },
168 { 0x0009, "SETABORTPROC", NULL },
169 { 0x000a, "STARTDOC", NULL },
170 { 0x000b, "ENDDOC", NULL },
171 { 0x000c, "GETPHYSPAGESIZE", NULL },
172 { 0x000d, "GETPRINTINGOFFSET", NULL },
173 { 0x000e, "GETSCALINGFACTOR", NULL },
174 { 0x000f, "MFCOMMENT", NULL }, // a.k.a. META_ESCAPE_ENHANCED_METAFILE
175 { 0x0010, "SETPENWIDTH", NULL },
176 { 0x0011, "SETCOPYCOUNT", NULL },
177 { 0x0012, "SETPAPERSOURCE", NULL },
178 { 0x0013, "PASSTHROUGH", NULL },
179 { 0x0014, "GETTECHNOLOGY", NULL },
180 { 0x0015, "SETLINECAP", NULL },
181 { 0x0016, "SETLINEJOIN", NULL },
182 { 0x0017, "SETMITERLIMIT", NULL },
183 { 0x0018, "BANDINFO", NULL },
184 { 0x0019, "DRAWPATTERNRECT", NULL },
185 { 0x001a, "GETVECTORPENSIZE", NULL },
186 { 0x001b, "GETVECTORBRUSHSIZE", NULL },
187 { 0x001c, "ENABLEDUPLEX", NULL },
188 { 0x001d, "GETSETPAPERBINS", NULL },
189 { 0x001e, "GETSETPRINTORIENT", NULL },
190 { 0x001f, "ENUMPAPERBINS", NULL },
191 { 0x0020, "SETDIBSCALING", NULL },
192 { 0x0021, "EPSPRINTING", NULL },
193 { 0x0022, "ENUMPAPERMETRICS", NULL },
194 { 0x0023, "GETSETPAPERMETRICS", NULL },
195 { 0x0025, "POSTSCRIPT_DATA", NULL },
196 { 0x0026, "POSTSCRIPT_IGNORE", NULL },
197 { 0x002a, "GETDEVICEUNITS", NULL },
198 { 0x0100, "GETEXTENDEDTEXTMETRICS", NULL },
199 { 0x0102, "GETPAIRKERNTABLE", NULL },
200 { 0x0200, "EXTTEXTOUT", NULL },
201 { 0x0201, "GETFACENAME", NULL },
202 { 0x0202, "DOWNLOADFACE", NULL },
203 { 0x0801, "METAFILE_DRIVER", NULL },
204 { 0x0c01, "QUERYDIBSUPPORT", NULL },
205 { 0x1000, "BEGIN_PATH", NULL },
206 { 0x1001, "CLIP_TO_PATH", NULL },
207 { 0x1002, "END_PATH", NULL },
208 { 0x100e, "OPEN_CHANNEL", NULL },
209 { 0x100f, "DOWNLOADHEADER", NULL },
210 { 0x1010, "CLOSE_CHANNEL", NULL },
211 { 0x1013, "POSTSCRIPT_PASSTHROUGH", NULL },
212 { 0x1014, "ENCAPSULATED_POSTSCRIPT", NULL },
213 { 0x1015, "POSTSCRIPT_IDENTIFY", NULL },
214 { 0x1016, "POSTSCRIPT_INJECTION", NULL },
215 { 0x1017, "CHECKJPEGFORMAT", NULL },
216 { 0x1018, "CHECKPNGFORMAT", NULL },
217 { 0x1019, "GET_PS_FEATURESETTING", NULL },
218 { 0x101a, "MXDC_ESCAPE", NULL },
219 { 0x11d8, "SPCLPASSTHROUGH2", NULL }
222 static void do_ESCAPE_MFCOMMENT_EMF(deark *c, lctx *d, struct decoder_params *dp,
223 i64 pos1, i64 bytecount)
225 i64 pos = pos1;
226 i64 endpos = dp->dpos + dp->dlen;
227 unsigned int CommentId;
228 i64 n;
229 i64 CommentRecordCount, CurrentRecordSize;
230 i64 RemainingBytes, EnhancedMetafileDataSize;
232 if(pos+34>endpos) {
233 de_dbg(c, "[bad/unsupported embedded EMF data (too short)]");
234 goto done;
237 CommentId = (unsigned int)de_getu32le_p(&pos);
238 de_dbg(c, "CommentIdentifier: 0x%08x", CommentId);
239 if(CommentId!=0x43464d57U) goto done;
241 n = de_getu32le_p(&pos);
242 de_dbg(c, "CommentType: 0x%08x", (unsigned int)n);
243 if(n != 1) {
244 de_dbg(c, "[bad/unsupported embedded EMF data (unsupported CommentType)]");
245 goto done;
248 n = de_getu32le_p(&pos);
249 de_dbg(c, "Version: 0x%08x", (unsigned int)n);
250 n = de_getu16le_p(&pos);
251 de_dbg(c, "CheckSum (reported): 0x%04x", (unsigned int)n);
252 n = de_getu32le_p(&pos);
253 de_dbg(c, "Flags: 0x%08x", (unsigned int)n);
254 CommentRecordCount = de_getu32le_p(&pos);
255 de_dbg(c, "CommentRecordCount: %d", (int)CommentRecordCount);
256 CurrentRecordSize = de_getu32le_p(&pos);
257 de_dbg(c, "CurrentRecordSize: %d", (int)CurrentRecordSize);
258 RemainingBytes = de_getu32le_p(&pos);
259 de_dbg(c, "RemainingBytes: %d", (int)RemainingBytes);
261 // The spec says that the ByteCount field must be 34 +
262 // EnhancedMetafileDataSize, but that doesn't make sense to me.
263 // Maybe it was supposed to be 34 + CurrentRecordSize?
264 EnhancedMetafileDataSize = de_getu32le_p(&pos);
265 de_dbg(c, "EnhancedMetafileDataSize: %d", (int)EnhancedMetafileDataSize);
267 if(pos+CurrentRecordSize>endpos) goto done;
268 de_dbg(c, "embedded EMF data at %d, len=%d", (int)pos, (int)CurrentRecordSize);
270 if(!d->embedded_emf && (CurrentRecordSize+RemainingBytes==EnhancedMetafileDataSize)) {
271 // Looks like the first record
272 d->embedded_emf = dbuf_create_output_file(c, "emf", NULL, 0);
275 if(d->embedded_emf) {
276 dbuf_copy(c->infile, pos, CurrentRecordSize, d->embedded_emf);
279 if(d->embedded_emf && RemainingBytes==0) {
280 // Looks like the last record
281 dbuf_close(d->embedded_emf);
282 d->embedded_emf = NULL;
285 done:
289 static void do_ESCAPE_MFCOMMENT(deark *c, lctx *d, struct decoder_params *dp,
290 i64 bytecount)
292 i64 pos;
293 i64 endpos;
294 int commenttype = 0;
295 const char *commenttype_name = "?";
296 unsigned int sig;
298 endpos = dp->dpos + dp->dlen;
299 pos = dp->dpos+4; // Skip over EscapeFunction & ByteCount.
300 if(pos+bytecount > endpos) goto done;
302 if(bytecount>=4) {
303 sig = (unsigned int)de_getu32le(pos);
304 if(sig==0x43464d57U) {
305 commenttype = 1;
306 commenttype_name = "META_ESCAPE_ENHANCED_METAFILE";
310 de_dbg(c, "identified as: %s", commenttype_name);
311 if(commenttype==1) {
312 do_ESCAPE_MFCOMMENT_EMF(c, d, dp, pos, bytecount);
314 else {
315 de_dbg_hexdump(c, c->infile, pos, bytecount, 256, NULL, 0x1);
318 done:
322 static int wmf_handler_ESCAPE(deark *c, lctx *d, struct decoder_params *dp)
324 u16 escfn;
325 i64 bytecount = 0;
326 const struct escape_info *einfo = NULL;
327 const char *name;
328 size_t k;
330 escfn = (u16)de_getu16le(dp->dpos);
332 // Find the name, etc. of this record type
333 for(k=0; k<DE_ARRAYCOUNT(escape_info_arr); k++) {
334 if(escape_info_arr[k].escfn == escfn) {
335 einfo = &escape_info_arr[k];
336 break;
340 if(einfo && einfo->name)
341 name = einfo->name;
342 else
343 name = "?";
345 de_dbg(c, "escape function: 0x%04x (%s)", (unsigned int)escfn, name);
347 if(dp->dlen>=4) {
348 bytecount = de_getu16le(dp->dpos+2);
349 de_dbg(c, "bytecount: %d (offset %d + %d = %d)", (int)bytecount,
350 (int)(dp->dpos+4), (int)bytecount, (int)(dp->dpos+4+bytecount));
353 if(4+bytecount > dp->dlen) {
354 goto done;
357 if(escfn==0x000f) {
358 do_ESCAPE_MFCOMMENT(c, d, dp, bytecount);
361 done:
362 return 1;
365 static int wmf_handler_EXTTEXTOUT(deark *c, lctx *d, struct decoder_params *dp)
367 i64 pos = dp->dpos;
368 i64 stringlen;
369 de_ucstring *s = NULL;
370 u32 fwOpts;
372 pos += 4; // Y, X
374 stringlen = de_getu16le(pos);
375 pos += 2;
377 fwOpts = (u32)de_getu16le(pos);
378 pos += 2;
380 if(fwOpts & 0x0004) {
381 // My best guess is that this flag determines whether the
382 // Rectangle field exists. The specification says the field is
383 // optional, but AFAICT does not say how to tell whether it exists.
384 pos += 8; // Rectangle
387 if(pos+stringlen > dp->dpos+dp->dlen) goto done;
388 s = ucstring_create(c);
389 dbuf_read_to_ucstring_n(c->infile, pos, stringlen, DE_DBG_MAX_STRLEN, s,
390 0, d->input_encoding);
391 de_dbg(c, "text: \"%s\"", ucstring_getpsz(s));
393 done:
394 ucstring_destroy(s);
395 return 1;
398 static int wmf_handler_DIBSTRETCHBLT_STRETCHDIB(deark *c, lctx *d, struct decoder_params *dp)
400 i64 dib_pos;
401 i64 dib_len;
402 int hdrsize;
403 int has_src_bitmap = 1;
405 if(dp->rectype==0x41) { // DIBSTRETCHBLT
406 has_src_bitmap = (dp->recsize_words != ((i64)(dp->recfunc>>8)+3));
407 de_dbg(c, "has src bitmap: %d", has_src_bitmap);
410 if(!has_src_bitmap) goto done;
411 if(dp->rectype==0x41) // DIBSTRETCHBLT
412 hdrsize = 26;
413 else
414 hdrsize = 28;
416 if(dp->recsize_bytes < hdrsize) goto done;
417 dib_pos = dp->recpos + hdrsize;
418 dib_len = dp->recsize_bytes - hdrsize;
419 if(dib_len < 12) goto done;
420 de_dbg(c, "DIB at %d, size=%d", (int)dib_pos, (int)dib_len);
422 de_dbg_indent(c, 1);
423 de_run_module_by_id_on_slice(c, "dib", NULL, c->infile, dib_pos, dib_len);
424 de_dbg_indent(c, -1);
426 done:
427 return 1;
430 static int handler_SELECTOBJECT(deark *c, lctx *d, struct decoder_params *dp)
432 unsigned int oi;
433 oi = (unsigned int)de_getu16le(dp->dpos);
434 de_dbg(c, "object index: %u", oi);
435 return 1;
438 static int handler_DELETEOBJECT(deark *c, lctx *d, struct decoder_params *dp)
440 unsigned int oi;
441 oi = (unsigned int)de_getu16le(dp->dpos);
442 de_dbg(c, "object index: %u", oi);
443 if(d->object_table && oi<d->num_objects) {
444 d->object_table[oi] = 0; // Mark this index as available
446 return 1;
449 static const char* get_brushstyle_name(unsigned int n)
451 static const char *names[7] = { "BS_SOLID", "BS_NULL", "BS_HATCHED", "BS_PATTERN",
452 NULL, "BS_DIBPATTERN", "BS_DIBPATTERNPT"};
453 const char *name = NULL;
455 if(n<=6) {
456 name = names[n];
458 return name?name:"?";
461 static int handler_CREATEBRUSHINDIRECT(deark *c, lctx *d, struct decoder_params *dp)
463 unsigned int style;
464 i64 pos = dp->dpos;
466 if(dp->dlen<8) goto done;
467 style = (unsigned int)de_getu16le_p(&pos);
468 de_dbg(c, "style: 0x%04x (%s)", style, get_brushstyle_name(style));
470 if(style==0x0 || style==0x2) {
471 u32 colorref;
472 colorref = (u32)de_getu32le(pos);
473 do_dbg_colorref(c, d, dp, colorref);
475 pos += 4;
477 if(style==0x2) {
478 unsigned int h;
479 h = (unsigned int)de_getu16le(pos);
480 de_dbg(c, "hatch: %u", h);
483 done:
484 return 1;
487 static const char *get_penbasestyle_name(unsigned int n)
489 static const char *names[9] = { "PS_SOLID", "PS_DASH", "PS_DOT", "PS_DASHDOT",
490 "PS_DASHDOTDOT", "PS_NULL", "PS_INSIDEFRAME", "PS_USERSTYLE", "PS_ALTERNATE" };
491 const char *name = NULL;
493 if(n<=8) {
494 name = names[n];
496 return name?name:"?";
499 static int handler_CREATEPENINDIRECT(deark *c, lctx *d, struct decoder_params *dp)
501 u32 colorref;
502 i64 pos = dp->dpos;
503 unsigned int width;
504 unsigned int style;
505 unsigned int base_style;
506 de_ucstring *style_descr = NULL;
508 if(dp->dlen<10) goto done;
509 style = (unsigned int)de_getu16le_p(&pos);
510 base_style = style&0x0f; // ?
511 style_descr = ucstring_create(c);
512 ucstring_append_flags_item(style_descr, get_penbasestyle_name(base_style));
513 if((style&0x0f00)==0x0100) ucstring_append_flags_item(style_descr, "PS_ENDCAP_SQUARE");
514 if((style&0x0f00)==0x0200) ucstring_append_flags_item(style_descr, "PS_ENDCAP_FLAG");
515 if((style&0xf000)==0x1000) ucstring_append_flags_item(style_descr, "PS_JOIN_BEVEL");
516 if((style&0xf000)==0x2000) ucstring_append_flags_item(style_descr, "PS_JOIN_MITER");
517 de_dbg(c, "style: 0x%04x (%s)", style, ucstring_getpsz(style_descr));
519 if(base_style!=0x5) {
520 width = (unsigned int)de_getu32le(pos);
521 width &= 0x0000ffffU;
522 de_dbg(c, "width: %u", width);
524 pos += 4;
526 if(base_style!=0x5) {
527 colorref = (u32)de_getu32le(pos);
528 do_dbg_colorref(c, d, dp, colorref);
531 done:
532 ucstring_destroy(style_descr);
533 return 1;
536 static int handler_CREATEFONTINDIRECT(deark *c, lctx *d, struct decoder_params *dp)
538 i64 facename_size;
539 i64 n, n2;
540 u8 b;
541 i64 pos = dp->dpos;
543 n = de_geti16le_p(&pos);
544 n2 = de_geti16le_p(&pos);
545 de_dbg(c, "height,width: %d,%d", (int)n, (int)n2);
546 pos += 9;
547 b = de_getbyte_p(&pos);
548 de_dbg(c, "charset: 0x%02x (%s)", (unsigned int)b,
549 fmtutil_get_windows_charset_name(b));
551 facename_size = dp->dlen-18;
552 if(facename_size>32) facename_size=32;
553 if(facename_size>=2) {
554 de_ucstring *facename = NULL;
555 facename = ucstring_create(c);
556 dbuf_read_to_ucstring(c->infile, dp->dpos+18, facename_size, facename,
557 DE_CONVFLAG_STOP_AT_NUL, DE_ENCODING_WINDOWS1252);
558 de_dbg(c, "facename: \"%s\"", ucstring_getpsz_d(facename));
559 ucstring_destroy(facename);
561 return 1;
564 static int handler_FILLREGION(deark *c, lctx *d, struct decoder_params *dp)
566 unsigned int oi;
567 i64 pos = dp->dpos;
569 oi = (unsigned int)de_getu16le_p(&pos);
570 de_dbg(c, "region object index: %u", oi);
571 oi = (unsigned int)de_getu16le_p(&pos);
572 de_dbg(c, "brush object index: %u", oi);
573 return 1;
576 static const struct wmf_func_info wmf_func_info_arr[] = {
577 { 0x00, 0, "EOF", NULL },
578 { 0x01, 0, "SETBKCOLOR", handler_colorref },
579 { 0x02, 0, "SETBKMODE", NULL },
580 { 0x03, 0, "SETMAPMODE", NULL },
581 { 0x04, 0, "SETROP2", NULL },
582 { 0x05, 0, "SETRELABS", NULL },
583 { 0x06, 0, "SETPOLYFILLMODE", NULL },
584 { 0x07, 0, "SETSTRETCHBLTMODE", NULL },
585 { 0x08, 0, "SETTEXTCHAREXTRA", NULL },
586 { 0x09, 0, "SETTEXTCOLOR", handler_colorref },
587 { 0x0a, 0, "SETTEXTJUSTIFICATION", NULL },
588 { 0x0b, 0, "SETWINDOWORG", NULL },
589 { 0x0c, 0, "SETWINDOWEXT", NULL },
590 { 0x0d, 0, "SETVIEWPORTORG", NULL },
591 { 0x0e, 0, "SETVIEWPORTEXT", NULL },
592 { 0x0f, 0, "OFFSETWINDOWORG", NULL },
593 { 0x10, 0, "SCALEWINDOWEXT", NULL },
594 { 0x11, 0, "OFFSETVIEWPORTORG", NULL },
595 { 0x12, 0, "SCALEVIEWPORTEXT", NULL },
596 { 0x13, 0, "LINETO", NULL },
597 { 0x14, 0, "MOVETO", NULL },
598 { 0x15, 0, "EXCLUDECLIPRECT", NULL },
599 { 0x16, 0, "INTERSECTCLIPRECT", NULL },
600 { 0x17, 0, "ARC", NULL },
601 { 0x18, 0, "ELLIPSE", NULL },
602 { 0x19, 0, "FLOODFILL", NULL },
603 { 0x1a, 0, "PIE", NULL },
604 { 0x1b, 0, "RECTANGLE", NULL },
605 { 0x1c, 0, "ROUNDRECT", NULL },
606 { 0x1d, 0, "PATBLT", NULL },
607 { 0x1e, 0, "SAVEDC", NULL },
608 { 0x1f, 0, "SETPIXEL", NULL },
609 { 0x20, 0, "OFFSETCLIPRGN", NULL },
610 { 0x21, 0, "TEXTOUT", wmf_handler_TEXTOUT },
611 { 0x22, 0, "BITBLT", wmf_handler_BITBLT_STRETCHBLT_DIBBITBLT },
612 { 0x23, 0, "STRETCHBLT", wmf_handler_BITBLT_STRETCHBLT_DIBBITBLT },
613 { 0x24, 0, "POLYGON", NULL },
614 { 0x25, 0, "POLYLINE", NULL },
615 { 0x26, 0, "ESCAPE", wmf_handler_ESCAPE },
616 { 0x27, 0, "RESTOREDC", NULL },
617 { 0x28, 0, "FILLREGION", handler_FILLREGION },
618 { 0x29, 0, "FRAMEREGION", NULL },
619 { 0x2a, 0, "INVERTREGION", NULL },
620 { 0x2b, 0, "PAINTREGION", NULL },
621 { 0x2c, 0, "SELECTCLIPREGION", handler_SELECTOBJECT },
622 { 0x2d, 0, "SELECTOBJECT", handler_SELECTOBJECT },
623 { 0x2e, 0, "SETTEXTALIGN", NULL },
624 { 0x30, 0, "CHORD", NULL },
625 { 0x31, 0, "SETMAPPERFLAGS", NULL },
626 { 0x32, 0, "EXTTEXTOUT", wmf_handler_EXTTEXTOUT },
627 { 0x33, 0, "SETDIBTODEV", NULL },
628 { 0x34, 0, "SELECTPALETTE", handler_SELECTOBJECT },
629 { 0x35, 0, "REALIZEPALETTE", NULL },
630 { 0x36, 0, "ANIMATEPALETTE", NULL },
631 { 0x37, 0, "SETPALENTRIES", NULL },
632 { 0x38, 0, "POLYPOLYGON", NULL },
633 { 0x39, 0, "RESIZEPALETTE", NULL },
634 { 0x40, 0, "DIBBITBLT", wmf_handler_BITBLT_STRETCHBLT_DIBBITBLT },
635 { 0x41, 0, "DIBSTRETCHBLT", wmf_handler_DIBSTRETCHBLT_STRETCHDIB },
636 { 0x42, 1, "DIBCREATEPATTERNBRUSH", NULL },
637 { 0x43, 0, "STRETCHDIB", wmf_handler_DIBSTRETCHBLT_STRETCHDIB },
638 { 0x48, 0, "EXTFLOODFILL", NULL },
639 { 0x49, 0, "SETLAYOUT", NULL },
640 { 0xf0, 0, "DELETEOBJECT", handler_DELETEOBJECT },
641 { 0xf7, 1, "CREATEPALETTE", NULL },
642 { 0xf9, 1, "CREATEPATTERNBRUSH", NULL },
643 { 0xfa, 1, "CREATEPENINDIRECT", handler_CREATEPENINDIRECT },
644 { 0xfb, 1, "CREATEFONTINDIRECT", handler_CREATEFONTINDIRECT },
645 { 0xfc, 1, "CREATEBRUSHINDIRECT", handler_CREATEBRUSHINDIRECT },
646 { 0xff, 1, "CREATEREGION", NULL }
649 static void do_read_aldus_header(deark *c, lctx *d)
651 i64 left, top, right, bottom;
652 i64 units_per_inch;
654 de_dbg(c, "Aldus Placeable Metafile header at 0");
655 de_dbg_indent(c, 1);
656 left = de_geti16le(6);
657 top = de_geti16le(8);
658 right = de_geti16le(10);
659 bottom = de_geti16le(12);
660 de_dbg(c, "location: (%d,%d) - (%d,%d)", (int)left, (int)top,
661 (int)right, (int)bottom);
662 units_per_inch = de_getu16le(14);
663 de_dbg(c, "metafile units per inch: %d", (int)units_per_inch);
664 de_dbg_indent(c, -1);
667 static int do_read_wmf_header(deark *c, lctx *d, i64 pos)
669 i64 hsize_words, maxrecsize_words, filesize_words;
670 int retval = 0;
672 de_dbg(c, "WMF header at %d", (int)pos);
673 de_dbg_indent(c, 1);
675 d->wmf_file_type = de_getu16le(pos);
676 de_dbg(c, "file type: %d", (int)d->wmf_file_type);
677 if(d->wmf_file_type!=1 && d->wmf_file_type!=2) {
678 de_err(c, "Invalid or unsupported WMF file type (%d)", (int)d->wmf_file_type);
679 goto done;
681 hsize_words = de_getu16le(pos+2);
682 de_dbg(c, "header size: %d bytes", (int)(hsize_words*2));
683 if(hsize_words != 9) {
684 de_err(c, "Incorrect WMF header size (expected 9, is %d)", (int)hsize_words);
685 goto done;
687 d->wmf_windows_version = de_getu16le(pos+4);
688 de_dbg(c, "Windows version: %d.%d", (int)((d->wmf_windows_version&0xff00)>>8),
689 (int)(d->wmf_windows_version&0x00ff));
690 filesize_words = de_getu32le(pos+6);
691 de_dbg(c, "reported file size: %d bytes", (int)(filesize_words*2));
693 d->num_objects = (unsigned int)de_getu16le(pos+10);
694 de_dbg(c, "number of objects: %u", d->num_objects);
695 if(d->object_table) de_free(c, d->object_table);
696 // d->num_objects is untrusted, but it can only be from 0 to 65535.
697 d->object_table = de_malloc(c, d->num_objects);
699 maxrecsize_words = de_getu32le(pos+12);
700 de_dbg(c, "max record size: %d bytes", (int)(maxrecsize_words*2));
701 retval = 1;
702 done:
703 de_dbg_indent(c, -1);
704 return retval;
707 static const struct wmf_func_info *find_wmf_func_info(u16 recfunc)
709 size_t i;
710 u8 rectype_wanted = (u8)(recfunc&0xff);
712 for(i=0; i<DE_ARRAYCOUNT(wmf_func_info_arr); i++) {
713 if(wmf_func_info_arr[i].rectype == rectype_wanted) {
714 return &wmf_func_info_arr[i];
717 return NULL;
720 static void on_create_object(deark *c, lctx *d, struct decoder_params *dp)
722 unsigned int k;
724 if(!d->object_table) return;
725 // The CREATE* opcodes assign an object index to the new object.
726 // Specifically, the first available index in the object table.
727 // The encoder and decoder must be very careful to use exactly the same
728 // algorithm for index assignment, or they could get out of sync.
729 for(k=0; k<d->num_objects; k++) {
730 if(d->object_table[k]==0) {
731 d->object_table[k] = 1; // Mark this index as used
732 de_dbg(c, "assigned object index: %u", k);
733 return;
736 de_warn(c, "Out of space in object table");
740 // Returns 0 if EOF record was found.
741 static int do_wmf_record(deark *c, lctx *d, i64 recnum, i64 recpos,
742 i64 recsize_bytes)
744 const struct wmf_func_info *fnci;
745 struct decoder_params dp;
747 de_zeromem(&dp, sizeof(struct decoder_params));
748 dp.recpos = recpos;
749 dp.recsize_words = recsize_bytes*2;
750 dp.recsize_bytes = recsize_bytes;
751 dp.dpos = recpos + 6;
752 dp.dlen = recsize_bytes - 6;
754 dp.recfunc = (u16)de_getu16le(recpos+4);
755 dp.rectype = (u8)(dp.recfunc&0xff);
757 fnci = find_wmf_func_info(dp.recfunc);
759 de_dbg(c, "record #%d at %d, func=0x%04x (%s), dpos=%d, dlen=%d", (int)recnum,
760 (int)recpos, (unsigned int)dp.recfunc,
761 fnci ? fnci->name : "?",
762 (int)dp.dpos, (int)dp.dlen);
764 de_dbg_indent(c, 1);
765 if(fnci && (fnci->flags&0x1)) {
766 on_create_object(c, d, &dp);
768 if(fnci && fnci->fn) {
769 fnci->fn(c, d, &dp);
771 de_dbg_indent(c, -1);
773 return (dp.rectype==0x00)?0:1;
776 static void do_wmf_record_list(deark *c, lctx *d, i64 pos)
778 i64 recpos;
779 i64 recsize_words, recsize_bytes;
780 i64 count;
782 de_dbg(c, "record list at %d", (int)pos);
783 de_dbg_indent(c, 1);
785 count = 0;
786 while(1) {
787 recpos = pos;
788 if(recpos >= c->infile->len) break; // Unexpected EOF
790 recsize_words = de_getu32le(recpos);
791 recsize_bytes = recsize_words*2;
792 if(recpos + recsize_bytes > c->infile->len) break; // Unexpected EOF
793 if(recsize_bytes < 6) break; // Invalid size
795 if(!do_wmf_record(c, d, count, recpos, recsize_bytes)) {
796 break;
799 pos += recsize_bytes;
800 count++;
803 de_dbg_indent(c, -1);
806 static void de_run_wmf(deark *c, de_module_params *mparams)
808 lctx *d = NULL;
809 i64 pos = 0;
811 d = de_malloc(c, sizeof(lctx));
813 d->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_WINDOWS1252);
815 if(!dbuf_memcmp(c->infile, 0, "\xd7\xcd\xc6\x9a", 4)) {
816 d->has_aldus_header = 1;
817 de_declare_fmt(c, "WMF (placeable)");
819 else {
820 de_declare_fmt(c, "WMF (non-placeable)");
823 if(d->has_aldus_header) {
824 do_read_aldus_header(c, d);
825 pos = 22;
828 if(!do_read_wmf_header(c, d, pos)) {
829 goto done;
831 pos += 18;
833 do_wmf_record_list(c, d, pos);
835 done:
836 if(d) {
837 if(d->embedded_emf) dbuf_close(d->embedded_emf);
838 de_free(c, d->object_table);
839 de_free(c, d);
843 static int de_identify_wmf(deark *c)
845 u8 buf[4];
847 de_read(buf, 0, 4);
849 if(!de_memcmp(buf, "\xd7\xcd\xc6\x9a", 4))
850 return 100;
852 if(de_input_file_has_ext(c, "wmf")) {
853 i64 ftype, hsize;
854 ftype = de_getu16le_direct(&buf[0]);
855 hsize = de_getu16le_direct(&buf[2]);
856 if(hsize==9 && (ftype==1 || ftype==2)) {
857 return 80;
861 return 0;
864 void de_module_wmf(deark *c, struct deark_module_info *mi)
866 mi->id = "wmf";
867 mi->desc = "Windows Metafile";
868 mi->desc2 = "extract bitmaps only";
869 mi->run_fn = de_run_wmf;
870 mi->identify_fn = de_identify_wmf;