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