new parameter addspacechars
[swftools.git] / src / swfextract.c
1 /* swfextract.c
2    Allows to extract parts of the swf into a new file.
3
4    Part of the swftools package.
5    
6    Copyright (c) 2001 Matthias Kramm <kramm@quiss.org>
7  
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12
13    This program 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
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
21
22 #include <unistd.h>
23 #include <stdio.h>
24 #include <fcntl.h>
25 #include "../lib/rfxswf.h"
26 #include "../lib/args.h"
27 #include "../lib/log.h"
28 #ifdef HAVE_ZLIB_H
29 #ifdef HAVE_LIBZ
30 #include "zlib.h"
31 #define _ZLIB_INCLUDED_
32 #endif
33 #endif
34
35 char * filename = 0;
36 char * destfilename = "output.swf";
37 int verbose = 3;
38
39 char* extractids = 0;
40 char* extractframes = 0;
41 char* extractjpegids = 0;
42 char* extractfontids = 0;
43 char* extractpngids = 0;
44 char* extractsoundids = 0;
45 char* extractbinaryids = 0;
46 char* extractanyids = 0;
47 char extractmp3 = 0;
48
49 char* extractname = 0;
50
51 char hollow = 0;
52 char originalplaceobjects = 0;
53 char movetozero = 0;
54
55 int numextracts = 0;
56 char *outputformat = NULL;
57
58 struct options_t options[] =
59 {
60  {"o","output"},
61  {"w","hollow"},
62  {"v","verbose"},
63  {"i","id"},
64  {"j","jpegs"},
65  {"p","pngs"},
66  {"a","any"},
67  {"P","placeobject"},
68  {"0","movetozero"},
69  {"m","mp3"},
70  {"s","sound"},
71  {"n","name"},
72  {"f","frame"},
73  {"F","font"},
74  {"V","version"},
75  {"b","binary"},
76  {"O","outputformat"},
77  {0,0}
78 };
79
80
81 int args_callback_option(char*name,char*val)
82 {
83     if(!strcmp(name, "V")) {
84         printf("swfextract - part of %s %s\n", PACKAGE, VERSION);
85         exit(0);
86     } 
87     else if(!strcmp(name, "o")) {
88         destfilename = val;
89         return 1;
90     } 
91     else if(!strcmp(name, "i")) {
92         extractids = val;
93         numextracts++;
94         if(extractname) {
95             fprintf(stderr, "You can only supply either name or id\n");
96             exit(1);
97         }
98         return 1;
99     } 
100     else if(!strcmp(name, "n")) {
101         extractname = val;
102         numextracts++;
103         if(extractids) {
104             fprintf(stderr, "You can only supply either name or id\n");
105             exit(1);
106         }
107         return 1;
108     } 
109     else if(!strcmp(name, "v")) {
110         verbose ++;
111         return 0;
112     } 
113     else if(!strcmp(name, "m")) {
114         extractmp3 = 1;
115         numextracts++;
116         return 0;
117     }
118     else if(!strcmp(name, "j")) {
119         if(extractjpegids) {
120             fprintf(stderr, "Only one --jpegs argument is allowed. (Try to use a range, e.g. -j 1,2,3)\n");
121             exit(1);
122         }
123         /* TODO: count number of IDs in val range */
124         numextracts++;
125         extractjpegids = val;
126         return 1;
127     } 
128     else if(!strcmp(name, "F")) {
129         if(extractfontids) {
130             fprintf(stderr, "Only one --font argument is allowed. (Try to use a range, e.g. -s 1,2,3)\n");
131             exit(1);
132         }
133         numextracts++;
134         extractfontids = val;
135         return 1;
136     } 
137     else if(!strcmp(name, "s")) {
138         if(extractsoundids) {
139             fprintf(stderr, "Only one --sound argument is allowed. (Try to use a range, e.g. -s 1,2,3)\n");
140             exit(1);
141         }
142         numextracts++;
143         extractsoundids = val;
144         return 1;
145     } 
146     else if(!strcmp(name, "b")) {
147         if(extractbinaryids) {
148             fprintf(stderr, "Only one --binary argument is allowed. (Try to use a range, e.g. -s 1,2,3)\n");
149             exit(1);
150         }
151         numextracts++;
152         extractbinaryids = val;
153         return 1;
154     } 
155 #ifdef _ZLIB_INCLUDED_
156     else if(!strcmp(name, "p")) {
157         if(extractpngids) {
158             fprintf(stderr, "Only one --png argument is allowed. (Try to use a range, e.g. -p 1,2,3)\n");
159             exit(1);
160         }
161         numextracts++;
162         extractpngids = val;
163         return 1;
164     } 
165 #endif
166     else if(!strcmp(name, "a")) {
167         numextracts++;
168         extractanyids = val;
169         return 1;
170     }
171     else if(!strcmp(name, "f")) {
172         numextracts++;
173         extractframes = val;
174         return 1;
175     }
176     else if(!strcmp(name, "P")) {
177         originalplaceobjects = 1;
178         return 0;
179     }
180     else if(!strcmp(name, "0")) {
181         movetozero = 1;
182         return 0;
183     }
184     else if(!strcmp(name, "w")) {
185         hollow = 1;
186         return 0;
187     }
188     else if (!strcmp(name, "O")) {
189       outputformat = val;
190         return 1;
191     }
192     else {
193         printf("Unknown option: -%s\n", name);
194         exit(1);
195     }
196
197     return 0;
198 }
199 int args_callback_longoption(char*name,char*val)
200 {
201     return args_long2shortoption(options, name, val);
202 }
203 void args_callback_usage(char*name)
204 {    
205     printf("Usage: %s [-v] [-n name] [-ijf ids] file.swf\n", name);
206     printf("\t-v , --verbose\t\t\t Be more verbose\n");
207     printf("\t-o , --output filename\t\t set output filename\n");
208     printf("\t-V , --version\t\t\t Print program version and exit\n\n");
209     printf("SWF Subelement extraction:\n");
210     printf("\t-n , --name name\t\t instance name of the object (SWF Define) to extract\n");
211     printf("\t-i , --id ID\t\t\t ID of the object, shape or movieclip to extract\n");
212     printf("\t-f , --frame frames\t\t frame numbers to extract\n");
213     printf("\t-w , --hollow\t\t\t hollow mode: don't remove empty frames\n"); 
214     printf("\t             \t\t\t (use with -f)\n");
215     printf("\t-P , --placeobject\t\t\t Insert original placeobject into output file\n"); 
216     printf("\t             \t\t\t (use with -i)\n");
217     printf("SWF Font/Text extraction:\n");
218     printf("\t-F , --font ID\t\t\t Extract font(s)\n");
219     printf("Picture extraction:\n");
220     printf("\t-j , --jpeg ID\t\t\t Extract JPEG picture(s)\n");
221 #ifdef _ZLIB_INCLUDED_
222     printf("\t-p , --pngs ID\t\t\t Extract PNG picture(s)\n");
223 #endif
224     printf("\n");
225     printf("Sound extraction:\n");
226     printf("\t-m , --mp3\t\t\t Extract main mp3 stream\n");
227     printf("\t-s , --sound ID\t\t\t Extract Sound(s)\n");
228 }
229 int args_callback_command(char*name,char*val)
230 {
231     if(filename) {
232         fprintf(stderr, "Only one file allowed. You supplied at least two. (%s and %s)\n",
233                  filename, name);
234     }
235     filename = name;
236     return 0;
237 }
238
239 void prepare_name(char *buf, size_t len, const char *prefix, 
240                   const char *suffix, int idx) {
241   if (outputformat!=NULL) {
242     // override default file name formatting
243     // make sure single-file behavior is not used
244     numextracts = -1;
245     // Other parts of codebase use vsnprintf, so I assume snprintf
246     // is available on all platforms that swftools currently works on.
247     // We need to check for buffer overflows now that the user is 
248     // supplying the format string.
249     snprintf(buf,len,outputformat,idx,suffix);
250   } else {
251     // use default file name formatting, unchanged
252     sprintf(buf,"%s%d.%s",prefix,idx,suffix);
253   }
254 }
255
256 U8 mainr,maing,mainb;
257 /* 1 = used, not expanded,
258    3 = used, expanded
259    5 = wanted, not expanded
260    7 = wanted, expanded
261  */
262 char used[65536];
263 TAG*tags[65536];
264 int changed;
265 char * tagused;
266 int extractname_id = -1;
267
268 void idcallback(void*data)
269 {
270     if(!(used[GET16(data)]&1)) {
271         changed = 1;
272         used[GET16(data)] |= 1;
273     }
274 }
275
276 void enumerateIDs(TAG*tag, void(*callback)(void*))
277 {
278 /*    U8*data;
279     int len = tag->len;
280     if(tag->len>=64) {
281         len += 6;
282         data = (U8*)malloc(len);
283         PUT16(data, (tag->id<<6)+63);
284         *(U8*)&data[2] = tag->len;
285         *(U8*)&data[3] = tag->len>>8;
286         *(U8*)&data[4] = tag->len>>16;
287         *(U8*)&data[5] = tag->len>>24;
288         memcpy(&data[6], tag->data, tag->len);
289     } else {
290         len += 2;
291         data = (U8*)malloc(len);
292         PUT16(data, (tag->id<<6)+tag->len);
293         memcpy(&data[2], tag->data, tag->len);
294     }
295     map_ids_mem(data, len, callback);
296  */
297     int num = swf_GetNumUsedIDs(tag);
298     int *ptr = malloc(sizeof(int)*num);
299     int t;
300     swf_GetUsedIDs(tag, ptr);
301     for(t=0;t<num;t++)
302         callback(&tag->data[ptr[t]]);
303 }
304
305 void moveToZero(TAG*tag)
306 {
307     if(!swf_isPlaceTag(tag))
308         return;
309     SWFPLACEOBJECT obj;
310     swf_GetPlaceObject(tag, &obj);
311     obj.matrix.tx = 0;
312     obj.matrix.ty = 0;
313     swf_ResetTag(tag, tag->id);
314     swf_SetPlaceObject(tag, &obj);
315 }
316
317 void extractTag(SWF*swf, char*filename)
318 {
319     SWF newswf;
320     TAG*desttag;
321     TAG*srctag;
322     RGBA rgb;
323     SRECT objectbbox;
324     char sprite;
325     int f;
326     int t;
327     int tagnum;
328     int copy = 0;
329     memset(&newswf,0x00,sizeof(SWF));        // set global movie parameters
330
331     newswf.fileVersion    = swf->fileVersion;
332     newswf.frameRate      = swf->frameRate;
333     newswf.movieSize      = swf->movieSize;
334     if(movetozero && originalplaceobjects) {
335         newswf.movieSize.xmax = swf->movieSize.xmax - swf->movieSize.xmin;
336         newswf.movieSize.ymax = swf->movieSize.ymax - swf->movieSize.ymin;
337         newswf.movieSize.xmin = 0;
338         newswf.movieSize.ymin = 0;
339     }
340                 
341     newswf.firstTag = swf_InsertTag(NULL,ST_SETBACKGROUNDCOLOR);
342     desttag = newswf.firstTag;
343     rgb.r = mainr;
344     rgb.g = maing;
345     rgb.b = mainb;
346     swf_SetRGB(desttag,&rgb);
347
348     swf_GetRect(0, &objectbbox);
349
350     do {
351        changed = 0;
352        for(t=0;t<65536;t++) {
353            if(used[t] && !(used[t]&2)) {
354              if(tags[t]==0) {
355                  msg("<warning> ID %d is referenced, but never defined.", t);
356              } else if(tags[t]->id==ST_DEFINESPRITE) {
357                  TAG*tag = tags[t];
358                  while(tag->id != ST_END)
359                  {
360                      enumerateIDs(tag, idcallback);
361                      tag = tag->next;
362                  }
363              }
364              else 
365                enumerateIDs(tags[t], idcallback);
366              used[t] |= 2;
367            }
368        }
369     }
370     while(changed);
371
372     srctag = swf->firstTag;
373     tagnum = 0;
374     sprite = 0;
375     while(srctag && (srctag->id || sprite)) {
376         int reset = 0;
377         if(!sprite) {
378             copy = 0;
379         }
380         if(srctag->id == ST_END) {
381             sprite = 0;
382         }
383         if(srctag->id == ST_DEFINESPRITE)
384             sprite = 1;
385         if(srctag->id == ST_JPEGTABLES)
386             copy = 1;
387         if(swf_isDefiningTag(srctag)) {
388             int id = swf_GetDefineID(srctag);
389             if(used[id])  {
390                 SRECT b;
391                 copy = 1;
392                 b = swf_GetDefineBBox(srctag);
393                 swf_ExpandRect2(&objectbbox, &b);
394             }
395         } else 
396         if ((((swf_isPlaceTag(srctag) && originalplaceobjects)
397               || srctag->id == ST_STARTSOUND) && (used[swf_GetPlaceID(srctag)]&4) ) ||
398               (swf_isPseudoDefiningTag(srctag) && used[swf_GetDefineID(srctag)]) ||
399               (tagused[tagnum])) 
400         {
401                 if(copy == 0)
402                     reset = 1;
403                 copy = 1;
404         } 
405         if(srctag->id == ST_REMOVEOBJECT) {
406             if(!used[swf_GetPlaceID(srctag)])
407                 copy = 0;
408         }
409
410         if(copy) {
411             TAG*ttag = (TAG*)malloc(sizeof(TAG));
412             desttag = swf_InsertTag(desttag, srctag->id);
413             desttag->len = desttag->memsize = srctag->len;
414             desttag->data = malloc(srctag->len);
415             memcpy(desttag->data, srctag->data, srctag->len);
416             if(movetozero && swf_isPlaceTag(desttag)) {
417                 moveToZero(desttag);
418             }
419             if(reset)
420                 copy = 0;
421         }
422         
423         srctag = srctag->next;
424         tagnum ++;
425     }
426     if(!extractframes && !hollow) {
427         if(!originalplaceobjects && (extractids||extractname_id>=0)) {
428             int number = 0;
429             int id = 0;
430             int t;
431             TAG* objtag = 0;
432             SRECT bbox;
433             memset(&bbox, 0, sizeof(SRECT));
434             if(extractids) {
435                 for(t=0;t<65536;t++) {
436                     if(is_in_range(t, extractids)) {
437                         id = t;
438                         number++;
439                     }
440                 }
441             }
442             if(number>=2) {
443                 printf("warning! You should use the -P when extracting multiple objects\n");
444             }
445
446             if(number == 1) {
447                 /* if there is only one object, we will scale it.
448                    So let's figure out its bounding box */
449                 TAG*tag = swf->firstTag;
450                 while(tag) {
451                     if(swf_isDefiningTag(tag) && tag->id != ST_DEFINESPRITE) {
452                         if(swf_GetDefineID(tag) == id)
453                             bbox = swf_GetDefineBBox(tag);
454                         objtag = tag;
455                     }
456                     tag = tag->next;
457                 }
458                 newswf.movieSize.xmin = 0;
459                 newswf.movieSize.ymin = 0;
460                 newswf.movieSize.xmax = 512*20;
461                 newswf.movieSize.ymax = 512*20;
462             } else {
463                 if((objectbbox.xmin|objectbbox.ymin|objectbbox.xmax|objectbbox.ymax)!=0)
464                     newswf.movieSize = objectbbox;
465             }
466
467             if(extractname_id>=0) {
468                 desttag = swf_InsertTag(desttag, ST_PLACEOBJECT2);
469                 swf_ObjectPlace(desttag, extractname_id, extractname_id, 0,0,extractname);
470             } else {
471                 for(t=0;t<65536;t++) {
472                     if(is_in_range(t, extractids)) {
473                         MATRIX m;
474                         desttag = swf_InsertTag(desttag, ST_PLACEOBJECT2);
475                         swf_GetMatrix(0, &m);
476                         if(objtag) {
477                             int width = bbox.xmax - bbox.xmin;
478                             int height = bbox.ymax - bbox.ymin;
479                             int max = width>height?width:height;
480                             m.tx = -bbox.xmin;
481                             m.ty = -bbox.ymin;
482                             if(max) {
483                                 m.sx = (512*20*65536)/max;
484                                 m.sy = (512*20*65536)/max;
485                             }
486                             //newswf.movieSize = swf_TurnRect(newswf.movieSize, &m);
487                         }
488                         swf_ObjectPlace(desttag, t, t, &m,0,0);
489                     }
490                 }
491             }
492         }
493         desttag = swf_InsertTag(desttag,ST_SHOWFRAME);
494     }
495     desttag = swf_InsertTag(desttag,ST_END);
496     
497     f = open(filename, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0644);
498     if FAILED(swf_WriteSWF(f,&newswf)) fprintf(stderr,"WriteSWF() failed.\n");
499     close(f);
500
501     swf_FreeTags(&newswf);                       // cleanup
502 }
503
504 int isOfType(int t, TAG*tag)
505 {
506     int show = 0;
507     if(t == 0 && (tag->id == ST_DEFINESHAPE ||
508         tag->id == ST_DEFINESHAPE2 ||
509         tag->id == ST_DEFINESHAPE3)) {
510         show = 1;
511     }
512     if(t==1 && tag->id == ST_DEFINESPRITE) {
513         show = 1;
514     }
515     if(t == 2 && (tag->id == ST_DEFINEBITS ||
516         tag->id == ST_DEFINEBITSJPEG2 ||
517         tag->id == ST_DEFINEBITSJPEG3)) {
518         show = 1;
519     }
520     if(t == 3 && (tag->id == ST_DEFINEBITSLOSSLESS ||
521         tag->id == ST_DEFINEBITSLOSSLESS2)) {
522         show = 1;
523     }
524     if(t == 4 && (tag->id == ST_DEFINESOUND)) {
525         show = 1;
526     }
527     if(t == 5 && (tag->id == ST_DEFINEFONT || tag->id == ST_DEFINEFONT2 || tag->id == ST_DEFINEFONT3)) {
528         show = 1;
529     }
530     if (t== 6 && (tag->id == ST_DEFINEBINARY)) {
531         show = 1;
532     }
533     return show;
534 }
535
536 void listObjects(SWF*swf)
537 {
538     TAG*tag;
539     char first;
540     int t;
541     int frame = 0;
542     char*names[] = {"Shape", "MovieClip", "JPEG", "PNG", "Sound", "Font", "Binary"};
543     char*options[] = {"-i", "-i", "-j", "-p", "-s", "-F","-b"};
544     int mp3=0;
545     printf("Objects in file %s:\n",filename);
546     swf_FoldAll(swf);
547     for(t=0;t<sizeof(names)/sizeof(names[0]);t++) {
548         int nr=0;
549         int lastid = -2, lastprint=-1;
550         int follow=0;
551         tag = swf->firstTag;
552         first = 1;
553         while(tag) {
554             if(tag->id == ST_SOUNDSTREAMHEAD || tag->id == ST_SOUNDSTREAMHEAD2)
555                 mp3 = 1;
556             if(isOfType(t,tag))
557                 nr++;
558             tag = tag->next;
559         }
560         if(!nr)
561             continue;
562         
563         printf(" [%s] %d %s%s: ID(s) ", options[t], nr, names[t], nr>1?"s":"");
564
565         tag = swf->firstTag;
566         while(tag) {
567             char text[80];
568             char show = isOfType(t,tag);
569             int id;
570             if(!show) {
571                 tag = tag->next;
572                 continue;
573             }
574             id = swf_GetDefineID(tag);
575
576             if(id == lastid+1) {
577                 follow=1;
578             } else {
579                 if(first || !follow) {
580                     if(!first)
581                         printf(", ");
582                     printf("%d", id);
583                 } else {
584                     if(lastprint + 1 == lastid) 
585                         printf(", %d, %d", lastid, id);
586                     else
587                         printf("-%d, %d", lastid, id);
588                 }
589                 lastprint = id;
590                 first = 0;
591                 follow = 0;
592             }
593             lastid = id;
594             tag=tag->next;
595         }
596         if(follow) {
597             if(lastprint + 1 == lastid)
598                 printf(", %d", lastid);
599             else
600                 printf("-%d", lastid);
601         }
602         printf("\n");
603     }
604
605     if(frame)
606         printf(" [-f] %d Frames: ID(s) 0-%d\n", frame, frame);
607     else
608         printf(" [-f] 1 Frame: ID(s) 0\n");
609
610     if(mp3)
611         printf(" [-m] 1 MP3 Soundstream\n");
612 }
613
614 int handlefont(SWF*swf, TAG*tag)
615 {
616     SWFFONT* f=0;
617     U16 id;
618     char name[80];
619     char*filename = name;
620     int t;
621
622     id = swf_GetDefineID(tag);
623     prepare_name(name, sizeof(name), "font", "swf", id);
624     if(numextracts==1) {
625         filename = destfilename;
626     }
627
628     swf_FontExtract(swf, id, &f);
629     if(!f) {
630         if (!extractanyids) {
631            printf("Couldn't extract font %d\n", id);
632         }
633         return 0;
634     }
635
636     swf_WriteFont(f, filename);
637     swf_FontFree(f);
638     return 1;
639 }
640
641 static char has_jpegtables=0;
642 static U8*jpegtables = 0;
643 static int jpegtablessize = 0;
644
645 void handlejpegtables(TAG*tag)
646 {
647     if(tag->id == ST_JPEGTABLES) {
648         jpegtables = tag->data;
649         jpegtablessize = tag->len;
650         has_jpegtables = 1;
651     }
652 }
653
654 FILE* save_fopen(char* name, char* mode)
655 {
656     FILE*fi = fopen(name, mode);
657     if(!fi) {
658         fprintf(stderr, "Error: Couldn't open %s\n", name);
659         exit(1);
660     }
661     return fi;
662 }
663
664 int findjpegboundary(U8*data, int len)
665 {
666     int t;
667     int pos=-1;
668     for(t=0;t<len-4;t++) {
669         if(data[t  ]==0xff &&
670            data[t+1]==0xd9 &&
671            data[t+2]==0xff &&
672            data[t+3]==0xd8) {
673             pos = t;
674         }
675     }
676     return pos;
677 }
678
679 /* extract jpeg data out of a tag */
680 int handlejpeg(TAG*tag)
681 {
682     char name[80];
683     char*filename = name;
684     FILE*fi;
685     
686     prepare_name(name, sizeof(name), "pic", "jpg", GET16(tag->data));
687     if(numextracts==1) {
688         filename = destfilename;
689         if(!strcmp(filename,"output.swf"))
690             filename = "output.jpg";
691     }
692     /* swf jpeg images have two streams, which both start with ff d8 and
693        end with ff d9. The following code handles sorting the middle
694        <ff d9 ff d8> bytes out, so that one stream remains */
695     if(tag->id == ST_DEFINEBITSJPEG && tag->len>2 && has_jpegtables) {
696         fi = save_fopen(filename, "wb");
697         if(jpegtablessize>=2) {
698             fwrite(jpegtables, 1, jpegtablessize-2, fi); //don't write end tag (ff,d8)
699             fwrite(&tag->data[2+2], tag->len-2-2, 1, fi); //don't write start tag (ff,d9)
700         } else {
701             fwrite(tag->data+2, tag->len-2, 1, fi);
702         }
703         fclose(fi);
704     }
705     else if(tag->id == ST_DEFINEBITSJPEG2 && tag->len>2) {
706         int end = tag->len;
707         int pos = findjpegboundary(&tag->data[2], tag->len-2);
708         if(pos>=0) {
709             pos+=2;
710             fi = save_fopen(filename, "wb");
711             fwrite(&tag->data[2], pos-2, 1, fi);
712             fwrite(&tag->data[pos+4], end-(pos+4), 1, fi);
713             fclose(fi);
714         } else {
715             fi = save_fopen(filename, "wb");
716             fwrite(&tag->data[2], end-2, 1, fi);
717             fclose(fi);
718         }
719     }
720     else if(tag->id == ST_DEFINEBITSJPEG3 && tag->len>6) {
721         U32 end = GET32(&tag->data[2])+6;
722         int pos = findjpegboundary(&tag->data[6], tag->len-6);
723         if(pos<0) {
724             fi = save_fopen(filename, "wb");
725             fwrite(&tag->data[6], end-6, 1, fi);
726             fclose(fi);
727         } else {
728             pos+=6;
729             fi = save_fopen(filename, "wb");
730             fwrite(&tag->data[6], pos-6, 1, fi);
731             fwrite(&tag->data[pos+4], end-(pos+4), 1, fi);
732             fclose(fi);
733         }
734     }
735     else {
736         int id = GET16(tag->data);
737         if (!extractanyids) {
738           fprintf(stderr, "Object %d is not a JPEG picture!\n", id);
739           exit(1);
740         }
741         return 0;
742     }
743     return 1;
744 }
745
746 #ifdef _ZLIB_INCLUDED_
747 static U32 mycrc32;
748
749 static U32*crc32_table = 0;
750 static void make_crc32_table(void)
751 {
752   int t;
753   if(crc32_table) 
754       return;
755   crc32_table = (U32*)malloc(1024);
756
757   for (t = 0; t < 256; t++) {
758     U32 c = t;
759     int s;
760     for (s = 0; s < 8; s++) {
761       c = (0xedb88320L*(c&1)) ^ (c >> 1);
762     }
763     crc32_table[t] = c;
764   }
765 }
766 static inline void png_write_byte(FILE*fi, U8 byte)
767 {
768     fwrite(&byte,1,1,fi);
769     mycrc32 = crc32_table[(mycrc32 ^ byte) & 0xff] ^ (mycrc32 >> 8);
770 }
771 static void png_start_chunk(FILE*fi, char*type, int len)
772 {
773     U8 mytype[4]={0,0,0,0};
774     U32 mylen = BE_32_TO_NATIVE(len);
775     memcpy(mytype,type,strlen(type));
776     fwrite(&mylen, 4, 1, fi);
777     mycrc32=0xffffffff;
778     png_write_byte(fi,mytype[0]);
779     png_write_byte(fi,mytype[1]);
780     png_write_byte(fi,mytype[2]);
781     png_write_byte(fi,mytype[3]);
782 }
783 static void png_write_bytes(FILE*fi, U8*bytes, int len)
784 {
785     int t;
786     for(t=0;t<len;t++)
787         png_write_byte(fi,bytes[t]);
788 }
789 static void png_write_dword(FILE*fi, U32 dword)
790 {
791     png_write_byte(fi,dword>>24);
792     png_write_byte(fi,dword>>16);
793     png_write_byte(fi,dword>>8);
794     png_write_byte(fi,dword);
795 }
796 static void png_end_chunk(FILE*fi)
797 {
798     U32 tmp = BE_32_TO_NATIVE((mycrc32^0xffffffff));
799     fwrite(&tmp,4,1,fi);
800 }
801
802
803 /* extract a lossless image (png) out of a tag 
804    This routine was originally meant to be a one-pager. I just
805    didn't know png is _that_ much fun. :) -mk
806  */
807 int handlelossless(TAG*tag)
808 {
809     char name[80];
810     char*filename = name;
811     FILE*fi;
812     int width, height;
813     int crc;
814     int id;
815     int t;
816     U8 bpp = 1;
817     U8 format;
818     U8 tmp;
819     Bytef* data=0;
820     U8* data2=0;
821     U8* data3=0;
822     uLongf datalen;
823     uLongf datalen2;
824     U32 datalen3;
825     U8 head[] = {137,80,78,71,13,10,26,10};
826     int cols;
827     char alpha = tag->id == ST_DEFINEBITSLOSSLESS2;
828     RGBA* palette;
829     int pos;
830     int error;
831     U32 tmp32;
832
833     make_crc32_table();
834
835     if(tag->id != ST_DEFINEBITSLOSSLESS &&
836        tag->id != ST_DEFINEBITSLOSSLESS2) {
837         int id = GET16(tag->data);
838         if (!extractanyids) {
839           fprintf(stderr, "Object %d is not a PNG picture!\n",id);
840           exit(1);
841         }
842         return 0;
843     }
844
845     id =swf_GetU16(tag);
846     format = swf_GetU8(tag);
847     if(format == 3) bpp = 8;
848     if(format == 4) bpp = 16;
849     if(format == 5) bpp = 32;
850     if(format!=3 && format!=5) {
851         if(format==4)
852         fprintf(stderr, "Can't handle 16-bit palette images yet (image %d)\n",id);
853         else 
854         fprintf(stderr, "Unknown image type %d in image %d\n", format, id);
855         return 0;
856     }
857     width = swf_GetU16(tag);
858     height = swf_GetU16(tag);
859     if(format == 3) cols = swf_GetU8(tag) + 1;
860 // this is what format means according to the flash specification. (which is
861 // clearly wrong)
862 //    if(format == 4) cols = swf_GetU16(tag) + 1;
863 //    if(format == 5) cols = swf_GetU32(tag) + 1;
864     else cols = 0;
865
866     msg("<verbose> Width %d", width);
867     msg("<verbose> Height %d", height);
868     msg("<verbose> Format %d", format);
869     msg("<verbose> Cols %d", cols);
870     msg("<verbose> Bpp %d", bpp);
871
872     datalen = (width*height*bpp/8+cols*8);
873     do {
874         if(data)
875             free(data);
876         datalen+=4096;
877         data = malloc(datalen);
878         error = uncompress (data, &datalen, &tag->data[tag->pos], tag->len-tag->pos);
879     } while(error == Z_BUF_ERROR);
880     if(error != Z_OK) {
881         fprintf(stderr, "Zlib error %d (image %d)\n", error, id);
882         return 0;
883     }
884     msg("<verbose> Uncompressed image is %d bytes (%d colormap)", datalen, (3+alpha)*cols);
885     pos = 0;
886     datalen2 = datalen+16;
887     data2 = malloc(datalen2);
888     palette = (RGBA*)malloc(cols*sizeof(RGBA));
889
890     for(t=0;t<cols;t++) {
891         palette[t].r = data[pos++];
892         palette[t].g = data[pos++];
893         palette[t].b = data[pos++];
894         if(alpha) {
895             palette[t].a = data[pos++];
896         }
897     }
898
899     prepare_name(name, sizeof(name), "pic", "png", id);
900     if(numextracts==1) {
901         filename = destfilename;
902         if(!strcmp(filename,"output.swf"))
903             filename = "output.png";
904     }
905     fi = save_fopen(filename, "wb");
906     fwrite(head,sizeof(head),1,fi);     
907
908     png_start_chunk(fi, "IHDR", 13);
909      png_write_dword(fi,width);
910      png_write_dword(fi,height);
911      png_write_byte(fi,8);
912      if(format == 3)
913      png_write_byte(fi,3); //indexed
914      else if(format == 5 && alpha==0)
915      png_write_byte(fi,2); //rgb
916      else if(format == 5 && alpha==1)
917      png_write_byte(fi,6); //rgba
918      else return 0;
919
920      png_write_byte(fi,0); //compression mode
921      png_write_byte(fi,0); //filter mode
922      png_write_byte(fi,0); //interlace mode
923     png_end_chunk(fi);
924    
925     if(format == 3) {
926         png_start_chunk(fi, "PLTE", 768);
927          
928          for(t=0;t<256;t++) {
929              png_write_byte(fi,palette[t].r);
930              png_write_byte(fi,palette[t].g);
931              png_write_byte(fi,palette[t].b);
932          }
933         png_end_chunk(fi);
934
935         if(alpha) {
936             /* write alpha palette */
937             png_start_chunk(fi, "tRNS", 256);
938             for(t=0;t<256;t++) {
939                 png_write_byte(fi,palette[t].a);
940             }
941             png_end_chunk(fi);
942         }
943     }
944     {
945         int pos2 = 0;
946         int x,y;
947         int srcwidth = width * (bpp/8);
948         datalen3 = (width*4+5)*height;
949         data3 = (U8*)malloc(datalen3);
950         for(y=0;y<height;y++)
951         {
952            data3[pos2++]=0; //filter type
953            if(bpp==32) {
954             if(!alpha) {
955                 // 32 bit to 24 bit "conversion"
956                 for(x=0;x<width;x++) {
957                     data3[pos2++]=data[pos+1];
958                     data3[pos2++]=data[pos+2];
959                     data3[pos2++]=data[pos+3];
960                     pos+=4; //ignore padding byte
961                 }
962             } else {
963                 for(x=0;x<width;x++) {
964                     data3[pos2++]=data[pos+1];
965                     data3[pos2++]=data[pos+2];
966                     data3[pos2++]=data[pos+3];
967                     data3[pos2++]=data[pos+0]; //alpha
968                     pos+=4;
969                 }
970             }
971            }
972            else {
973                 for(x=0;x<srcwidth;x++)
974                     data3[pos2++]=data[pos++];
975            }
976            
977            pos+=((srcwidth+3)&~3)-srcwidth; //align
978         }
979         datalen3=pos2;
980     }
981
982     if(compress (data2, &datalen2, data3, datalen3) != Z_OK) {
983         fprintf(stderr, "zlib error in pic %d\n", id);
984         return 0;
985     }
986     msg("<verbose> Compressed data is %d bytes", datalen2);
987     png_start_chunk(fi, "IDAT", datalen2);
988     png_write_bytes(fi,data2,datalen2);
989     png_end_chunk(fi);
990     png_start_chunk(fi, "IEND", 0);
991     png_end_chunk(fi);
992
993     free(data);
994     free(data2);
995     free(data3);
996     return 1;
997 }
998 #endif
999
1000 static FILE*mp3file=0;
1001 void handlesoundstream(TAG*tag)
1002 {
1003     char*filename = "output.mp3";
1004     if(numextracts==1) {
1005         filename = destfilename;
1006         if(!strcmp(filename,"output.swf"))
1007             filename = "output.mp3";
1008     }
1009     switch(tag->id) {
1010         case ST_SOUNDSTREAMHEAD:
1011             if((tag->data[1]&0x30) == 0x20) { //mp3 compression
1012                 mp3file = fopen(filename, "wb");
1013                 msg("<notice> Writing mp3 data to %s",filename);
1014             }
1015             else
1016                 msg("<error> Soundstream is not mp3");
1017         break;
1018         case ST_SOUNDSTREAMHEAD2:
1019             if((tag->data[1]&0x30) == 0x20) {//mp3 compression
1020                 mp3file = fopen(filename, "wb");
1021                 msg("<notice> Writing mp3 data to %s",filename);
1022             }
1023             else
1024                 msg("<error> Soundstream is not mp3 (2)");
1025         break;
1026         case ST_SOUNDSTREAMBLOCK:
1027             if(mp3file)
1028                 fwrite(&tag->data[4],tag->len-4,1,mp3file);
1029         break;
1030     }
1031 }
1032
1033 int handledefinesound(TAG*tag)
1034 {
1035     U8 flags;
1036     U32 samples;
1037     char buf[128];
1038     char*filename = buf;
1039     FILE*fi;
1040     char*extension = 0;
1041     int format;
1042     U16 id;
1043     int rate,bits,stereo;
1044     char*rates[] = {"5500","11025","22050","44100"};
1045     id = swf_GetU16(tag); //id
1046     
1047     flags = swf_GetU8(tag);
1048     format = flags>>4;
1049     rate = (flags>>2)&3;
1050     bits = flags&2?16:8;
1051     stereo = flags&1;
1052     
1053     samples = swf_GetU32(tag);
1054
1055     extension = "raw";
1056
1057     if(format == 2) { // mp3
1058         swf_GetU16(tag); //numsamples_seek
1059         extension = "mp3";
1060     } else if(format == 0) { // raw
1061         printf("Sound is RAW, format: %s samples/sec, %d bit, %s\n", rates[rate], bits, stereo?"stereo":"mono");
1062         // TODO: convert to WAV
1063         extension = "raw";
1064     } else if(format == 1) { // adpcm
1065         printf("Sound is ADPCM, format: %s samples/sec, %d bit, %s\n", rates[rate], bits, stereo?"stereo":"mono");
1066         extension = "adpcm";
1067     } else {
1068         return 0;
1069     }
1070     prepare_name(buf, sizeof(buf), "sound", extension, id);
1071     if(numextracts==1) {
1072         filename = destfilename;
1073         if(!strcmp(filename,"output.swf")) {
1074             sprintf(buf, "output.%s", extension);
1075             filename = buf;
1076         }
1077     }
1078     fi = save_fopen(filename, "wb");
1079     fwrite(&tag->data[tag->pos], tag->len - tag->pos, 1, fi);
1080     fclose(fi);
1081     return 1;
1082 }
1083
1084 int handlebinary(TAG*tag) {
1085     FILE *fout = NULL;
1086     char buf[100];
1087     char *filename = buf;
1088     int len = tag->memsize;
1089     int dx = 6; // offset to binary data
1090     if (tag->id!=ST_DEFINEBINARY) {
1091         if (!extractanyids) {
1092           fprintf(stderr, "Object %d is not a binary entity!\n",
1093                           GET16(tag->data));
1094         }
1095         return 0;
1096     }
1097     prepare_name(buf, sizeof(buf), "binary", "bin", GET16(tag->data));
1098     if(numextracts==1) {
1099         filename = destfilename;
1100         if(!strcmp(filename,"output.swf")) {
1101             sprintf(buf, "output.bin");
1102             filename = buf;
1103         }
1104     }
1105     fout = fopen(filename, "wb");
1106     fwrite(tag->data+dx,len-dx,1,fout);
1107     fclose(fout);
1108     return 1;
1109 }
1110
1111 int main (int argc,char ** argv)
1112
1113     TAG*tag;
1114     SWF swf;
1115     int f;
1116     int found = 0;
1117     int frame = 0;
1118     int tagnum = 0;
1119     char depths[65536];
1120     char listavailable = 0;
1121     processargs(argc, argv);
1122
1123     if(!extractframes && !extractids && ! extractname && !extractjpegids && !extractpngids
1124         && !extractmp3 && !extractsoundids && !extractfontids && !extractbinaryids && !extractanyids)
1125         listavailable = 1;
1126
1127     if(!originalplaceobjects && movetozero) {
1128         fprintf(stderr, "Error: -0 (--movetozero) can only be used in conjunction with -P (--placeobject)\n");
1129         return 0;
1130     }
1131
1132     if(!filename)
1133     {
1134         fprintf(stderr, "You must supply a filename.\n");
1135         return 1;
1136     }
1137     initLog(0,-1,0,0,-1, verbose);
1138
1139     f = open(filename,O_RDONLY|O_BINARY);
1140
1141     if (f<0)
1142     { 
1143         perror("Couldn't open file: ");
1144         exit(1);
1145     }
1146     if (swf_ReadSWF(f,&swf) < 0)
1147     { 
1148         fprintf(stderr, "%s is not a valid SWF file or contains errors.\n",filename);
1149         close(f);
1150         exit(1);
1151     }
1152     close(f);
1153
1154     if(listavailable) {
1155         listObjects(&swf);
1156         swf_FreeTags(&swf);
1157         return 0;
1158     }
1159
1160     tag = swf.firstTag;
1161     tagnum = 0;
1162     while(tag) {
1163         tagnum ++;
1164         tag = tag->next;
1165     }
1166
1167     tagused = (char*)malloc(tagnum);
1168     memset(tagused, 0, tagnum);
1169     memset(used, 0, 65536);
1170     memset(depths, 0, 65536);
1171
1172     tag = swf.firstTag;
1173     tagnum = 0;
1174     while(tag) {
1175         if(swf_isAllowedSpriteTag(tag)) {
1176             int write = 0;
1177             if(extractframes && is_in_range(frame, extractframes)) {
1178                 write = 1;
1179                 if(tag->id == ST_PLACEOBJECT || tag->id == ST_PLACEOBJECT2) {
1180                     depths[swf_GetDepth(tag)] = 1;
1181                 }
1182                 if(tag->id == ST_REMOVEOBJECT || tag->id == ST_REMOVEOBJECT2) {
1183                     int depth = swf_GetDepth(tag);
1184                     if(!depths[depth]) 
1185                         write = 0;
1186                     depths[swf_GetDepth(tag)] = 0;
1187                 }
1188             } else {
1189                 if((tag->id == ST_REMOVEOBJECT || tag->id == ST_REMOVEOBJECT2) && 
1190                     (depths[swf_GetDepth(tag)]) && hollow) {
1191                     write = 1;
1192                     depths[swf_GetDepth(tag)] = 0;
1193                 }
1194             }
1195             if(write) {
1196                 enumerateIDs(tag, idcallback);
1197                 found = 1;
1198                 tagused[tagnum] = 1;
1199             }
1200         }
1201
1202         if(tag->id == ST_SOUNDSTREAMHEAD ||
1203            tag->id == ST_SOUNDSTREAMHEAD2 ||
1204            tag->id == ST_SOUNDSTREAMBLOCK) {
1205             if(extractmp3)
1206                 handlesoundstream(tag);
1207         }
1208
1209         if(tag->id == ST_JPEGTABLES) {
1210             handlejpegtables(tag);
1211         }
1212
1213         if(swf_isDefiningTag(tag)) {
1214             int id = swf_GetDefineID(tag);
1215             tags[id] = tag;
1216             if(extractids && is_in_range(id, extractids)) {
1217                 used[id] = 5;
1218                 found = 1;
1219             }
1220             if(extractfontids && is_in_range(id, extractfontids)) {
1221                 handlefont(&swf, tag);
1222             }
1223             if(extractjpegids && is_in_range(id, extractjpegids)) {
1224                 handlejpeg(tag);
1225             }
1226             if(extractsoundids && is_in_range(id, extractsoundids)) {
1227                 handledefinesound(tag);
1228             }
1229             if(extractbinaryids && is_in_range(id, extractbinaryids)) {
1230                 handlebinary(tag);
1231             }
1232 #ifdef _ZLIB_INCLUDED_
1233             if(extractpngids && is_in_range(id, extractpngids)) {
1234                 handlelossless(tag);
1235             }
1236 #endif
1237             if(extractanyids && is_in_range(id, extractanyids)) {
1238                 if (handlefont(&swf,tag)) {
1239                     // pass
1240                 } else if (handlejpeg(tag)) {
1241                     // pass
1242                 } else if (handlebinary(tag)) {
1243                     // pass
1244 #ifdef _ZLIB_INCLUDED_
1245                 } else if (handlelossless(tag)) {
1246                     // pass
1247 #endif
1248                 } else if (handledefinesound(tag)) {
1249                     // Not sure if sound code checks carefully for type.
1250                     // pass
1251                 } else {
1252                     printf("#%d not processed\n", id);
1253                 }
1254             }
1255         }
1256         else if (tag->id == ST_SETBACKGROUNDCOLOR) {
1257             mainr = tag->data[0];
1258             maing = tag->data[1];
1259             mainb = tag->data[2];
1260         }
1261         else if(swf_isPlaceTag(tag) && tag->id != ST_PLACEOBJECT ) {
1262             char*name = swf_GetName(tag);
1263             if(name && extractname && !strcmp(name, extractname)) {
1264                 int id = swf_GetPlaceID(tag); 
1265                 used[id] = 5;
1266                 found = 1;
1267                 if(originalplaceobjects) {
1268                     tagused[tagnum] = 1;
1269                 }
1270                 depths[swf_GetDepth(tag)] = 1;
1271                 extractname_id = id;
1272             }
1273         }
1274         else if(tag->id == ST_SHOWFRAME) {
1275             frame ++;
1276             if(hollow) {
1277                 tagused[tagnum] = 1;
1278                 found = 1;
1279             }
1280         }
1281         
1282         if(tag->id == ST_DEFINESPRITE) {
1283             while(tag->id != ST_END) { 
1284                 tag = tag->next;
1285                 tagnum ++;
1286             }
1287         }
1288         tag = tag->next;
1289         tagnum ++;
1290     }
1291     if (found)
1292         extractTag(&swf, destfilename);
1293
1294     if(mp3file) {
1295         fclose(mp3file);
1296     } else {
1297         if(extractmp3) {
1298             msg("<error> Didn't find a soundstream in file");
1299         }
1300     }
1301
1302     swf_FreeTags(&swf);
1303     return 0;
1304 }
1305