added Flash 6 zlib compression
[swftools.git] / src / swfcombine.c
1 /* swfcombine.c
2    main routine for swfcombine(1), a tool for merging .swf-files.
3
4    Part of the swftools package.
5    
6    Copyright (c) 2001 Matthias Kramm <kramm@quiss.org> 
7
8    This file is distributed under the GPL, see file COPYING for details */
9
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <zlib.h>
14 #include "../lib/rfxswf.h"
15 #include "../lib/args.h"
16 #include "../lib/log.h"
17 #include "combine.h"
18 #include "settings.h"
19 #include "types.h"
20 #include "flash.h"
21 #include "reloc.h"
22 #include "../config.h"
23
24 char * master_filename = 0;
25 char * master_name = 0;
26 char * slave_filename[128];
27 char * slave_name[128];
28 int slave_movex[128];
29 int slave_movey[128];
30 float slave_scalex[128];
31 float slave_scaley[128];
32 char slave_isframe[128];
33 int numslaves = 0;
34
35 char * outputname = "output.swf";
36
37 int args_callback_option(char*name,char*val) {
38     if(!strcmp(name,"c"))
39     {
40         config.clip = 1;
41         return 0;
42     }
43     else if(!strcmp(name,"l"))
44     {
45         config.overlay = 1;
46         return 0;
47     }
48     else if (!strcmp(name, "o"))
49     {
50         outputname = val;
51         return 1;
52     }
53     else if (!strcmp(name, "v"))
54     {
55         config.loglevel ++;
56         return 0;
57     }
58     else if (!strcmp(name, "a"))
59     {
60         config.cat = 1;
61         return 0;
62     }
63     else if (!strcmp(name, "A"))
64     {
65         config.alloctest = 1;
66         return 0;
67     }
68     else if (!strcmp(name, "x"))
69     {
70         config.movex = atoi(val);
71         return 1;
72     }
73     else if (!strcmp(name, "y"))
74     {
75         config.movey = atoi(val);
76         return 1;
77     }
78     else if (!strcmp(name, "m"))
79     {
80         config.merge = 1;
81         return 0;
82     }
83     else if (!strcmp(name, "f"))
84     {
85         config.isframe = 1;
86         return 0;
87     }
88     else if (!strcmp(name, "d"))
89     {
90         config.dummy = 1;
91         return 0;
92     }
93     else if (!strcmp(name, "z"))
94     {
95         config.zlib = 1;
96         return 0;
97     }
98     else if (!strcmp(name, "r"))
99     {
100         config.framerate = atoi(val)*256/100;
101         return 1;
102     }
103     else if (!strcmp(name, "X"))
104     {
105         config.sizex = atoi(val)*20;
106         config.hassizex = 1;
107         return 1;
108     }
109     else if (!strcmp(name, "Y"))
110     {
111         config.sizey = atoi(val)*20;
112         config.hassizey = 1;
113         return 1;
114     }
115     else if (!strcmp(name, "s"))
116     {
117         config.scalex = config.scaley = atoi(val)/100.0;
118         return 1;
119     }
120     else if (!strcmp(name, "t") || !strcmp(name, "T"))
121     {
122         if(master_filename) {
123             fprintf(stderr, "error with arguments. Try --help.\n");
124             exit(1);
125         }
126         config.stack = 1;
127         if(!strcmp(name,"T"))
128             config.stack1 = 1;
129         master_filename = "__none__";
130         return 0;
131     }
132     else if (!strcmp(name, "V"))
133     {   
134         printf("swfcombine - part of %s %s\n", PACKAGE, VERSION);
135         exit(0);
136     }
137     else 
138     {
139         fprintf(stderr, "Unknown option: -%s\n", name);
140         exit(1);
141     }
142 }
143
144 struct options_t options[] =
145 {{"o","output"},
146  {"s","scale"},
147  {"d","dummy"},
148  {"x","xpos"},
149  {"y","ypos"},
150  {"X","width"},
151  {"Y","height"},
152  {"r","rate"},
153  {"f","frame"},
154  {"l","overlay"},
155  {"m","merge"},
156  {"t","stack"},
157  {"T","stack1"},
158  {"v","verbose"},
159  {"V","version"},
160  {"c","clip"},
161  {"a","cat"},
162  {"z","zlib"},
163  {0,0}
164 };
165
166 int args_callback_longoption(char*name,char*val) {
167     return args_long2shortoption(options, name, val);
168 }
169
170 int args_callback_command(char*name, char*val) {
171     char*myname = strdup(name);
172     char*filename;
173     filename = strchr(myname, '=');
174     if(filename) {
175         *filename = 0;
176         filename++;
177     } else {
178         // argument has no explicit name field. guess one from the file name
179         char*path = strrchr(myname, '/');
180         char*ext = strrchr(myname, '.');
181         if(!path) path = myname;
182         else path ++;
183         if(ext) *ext = 0;
184         myname = path;
185         filename = name;
186     }
187
188     if(!master_filename) {
189         master_filename = filename;
190         master_name = myname;
191     } else {             
192         logf("<verbose> slave entity %s (named \"%s\")\n", filename, myname);
193
194         slave_filename[numslaves] = filename;
195         slave_name[numslaves] = myname;
196         slave_movex[numslaves] = config.movex;
197         slave_movey[numslaves] = config.movey;
198         slave_scalex[numslaves] = config.scalex;
199         slave_scaley[numslaves] = config.scaley;
200         slave_isframe[numslaves] = config.isframe; 
201         config.isframe = 0;
202         config.movex = config.movey = 0;
203         config.scalex = config.scaley = 1.0;
204         numslaves ++;
205     }
206     return 0;
207 }
208
209 void args_callback_usage(char*name)
210 {
211     printf("Usage: %s [-rXYomlcv] [-f] masterfile] [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
212     printf("OR:    %s [-rXYomv] --stack[1] [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
213     printf("OR:    %s [-rXYov] --cat [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
214     printf("OR:    %s [-rXYomlcv] --dummy [-xys] [file]\n", name);
215     printf("\n");
216     printf("-o outputfile       --output    explicitly specify output file. (otherwise, output.swf will be used\n");
217     printf("-t                  --stack     place each slave in a seperate frame (no master movie\n");
218     printf("-T                  --stack1    place each slave in the first frame (no master movie\n");
219     printf("-m                  --merge     Don't store the slaves in Sprites/MovieClips\n");
220     printf("-a                  --cat       concatenate all slave files (no master movie\n");
221     printf("-l                  --overlay   Don't remove any master objects, only overlay new objects\n");
222     printf("-c                  --clip      Clip the slave objects by the corresponding master objects\n");
223     printf("-v                  --verbose   Use more than one -v for greater effect \n");
224     printf("-d                  --dummy     Don't require slave objects \n");
225     printf("-f                  --frame     The following identifier is a frame or framelabel, not an id or objectname\n");
226     printf("-x xpos             --movex     x Adjust position of slave by xpos twips (1/20 pixel\n");
227     printf("-y ypos             --movey     y Adjust position of slave by ypos twips (1/20 pixel\n");
228     printf("-s scale            --scale     Adjust size of slave by scale%\n");
229     printf("-r framerate        --rate      Set movie framerate (100 frames/sec\n");
230     printf("-X width            --width     Force movie width to scale (default: use master width (not with -t\n");
231     printf("-Y height           --height    Force movie height to scale (default: use master height (not with -t\n");
232     printf("-z zlib             --zlib      Enable Flash 6 (MX) Zlib Compression\n");
233 }
234
235 static void zlib_error(int ret, char* msg, z_stream*zs)
236 {
237     fprintf(stderr, "%s: zlib error (%d): last zlib error: %s\n",
238           msg,
239           ret,
240           zs->msg?zs->msg:"unknown");
241     perror("errno:");
242     exit(1);
243 }
244
245 static char* fi_depack(FILE* fi, unsigned int * setlength) {
246     char * mem;
247     z_stream zs;
248     int ret;
249     char buffer1[8192], *buffer2;
250     int memsize = 8192;
251     int mempos = 0;
252     memset(&zs,0,sizeof(z_stream));
253     zs.zalloc = Z_NULL;
254     zs.zfree  = Z_NULL;
255     zs.opaque = Z_NULL;
256     buffer2 = malloc(8192);
257
258     ret = inflateInit(&zs);
259     if (ret != Z_OK) zlib_error(ret, "inflate_init", &zs);
260
261     fread(buffer2, 8, 1, fi);
262     buffer2[0] = 'F';
263     
264     zs.next_out         = &buffer2[8];
265     zs.avail_out        = 8192-8;
266     zs.avail_in         = 0;
267
268     while(1)
269     {
270         if(!zs.avail_in) {
271             zs.avail_in = fread(buffer1, 1, 8192, fi);
272             zs.next_in = buffer1;
273         }
274         if(zs.avail_in)
275             ret = inflate(&zs, Z_NO_FLUSH);
276         else
277             ret = inflate(&zs, Z_FINISH);
278
279         if (ret == Z_STREAM_END)
280             break;
281         if (ret != Z_OK) zlib_error(ret, "inflate_init", &zs);
282         
283         if (zs.avail_out == 0)
284         {       
285             buffer2 = realloc(buffer2, memsize + 8192);
286             zs.avail_out = 8192;
287             zs.next_out = &buffer2[memsize];
288             memsize += 8192;
289         }
290     }
291     *setlength = (zs.next_out - (Bytef*)buffer2);
292     ret = inflateEnd(&zs);
293     if(ret != Z_OK) zlib_error(ret, "inflate_init", &zs);
294     return buffer2;
295 }
296
297 /* read a whole file in memory */
298 static char* fi_slurp(FILE*fi, unsigned int * setlength)
299 {
300     char * mem;
301     long long int length; //;)  
302     long long int pos = 0;
303     unsigned char id[3];
304     fseek(fi,0,SEEK_END);
305     length = ftell(fi);
306     fseek(fi,0,SEEK_SET);
307     if(length<3)
308         return 0;
309     fread(id, 3, 1, fi);
310     fseek(fi, 0, SEEK_SET);
311     if(!strncmp(id, "CWS", 3)) {
312         return fi_depack(fi, setlength);
313     }
314
315     mem = malloc(length);
316     if(!mem)
317         return 0;
318     while(!feof(fi))
319     {
320         pos += fread(&mem[pos], 1, 65536, fi);
321     }
322     if (setlength) 
323         *setlength = length;
324     return mem;
325 }
326
327 static void fi_pack(FILE*fi, void*_mem, int length)
328 {
329     z_stream zs;
330     int ret;
331     char*mem = (char*)_mem;
332     char buffer[8192];
333     memset(&zs,0,sizeof(z_stream));
334     zs.zalloc = Z_NULL;
335     zs.zfree  = Z_NULL;
336     zs.opaque = Z_NULL;
337
338     ret = deflateInit(&zs, 9);
339     if (ret != Z_OK) zlib_error(ret, "deflate_init", &zs);
340
341     mem[0] = 'C';
342     fwrite(mem, 8, 1, fi);
343     
344     zs.avail_in         = length-8;
345     zs.next_in          = mem+8;
346     zs.avail_out        = 8192;
347     zs.next_out         = buffer;
348
349     while(1)
350     {
351         if(zs.avail_in)
352             ret = deflate(&zs, Z_NO_FLUSH);
353         else
354             ret = deflate(&zs, Z_FINISH);
355
356         if (ret == Z_STREAM_END)
357             break;
358
359         if (ret != Z_OK) zlib_error(ret, "deflate_sync", &zs);
360         
361         if (zs.avail_out == 0)
362         {       
363             fwrite(buffer, 8192, 1, fi);
364             zs.next_out = buffer;
365             zs.avail_out = 8192;
366         }
367     }
368     fwrite(buffer, zs.next_out - (Bytef*)buffer, 1, fi);
369     
370     ret = deflateEnd(&zs);
371     if (ret != Z_OK) zlib_error(ret, "deflate_end", &zs);
372 }
373
374 static void fi_dump(FILE*fi, void*_mem, int length)
375 {
376     char*mem = (char*)_mem;
377     int pos = 0;
378     while(pos < length)
379     {
380         int size = 65536;
381         if (size > (length - pos))
382                 size = (length - pos);
383         pos += fwrite(&mem[pos], 1, size, fi);
384     }
385 }
386
387 /* todo: use rfxswf */
388 static void makestackmaster(u8**masterdata, int*masterlength)
389 {
390     u8 head[] = {'F','W','S'};
391     u8 *pos;
392     u32 * fixpos;
393     int t;
394     struct RECT box;
395     int strlength = 0;
396     int fileversion = 1;
397
398     /* scan all slaves for bounding box */
399     for(t=0;t<numslaves;t++)
400     {
401         FILE*fi=fopen(slave_filename[t],"rb");
402         u8 data[256];
403         int ret;
404         struct flash_header head;
405         struct reader_t r;
406         strlength += strlen(slave_name[t]) + 9;
407         if(!fi) {
408             logf("<fatal> Couldn't open %s.", slave_filename[t]);
409             exit(1);
410         }
411         ret = fread(data,1,256,fi);
412         if(ret < 13) {
413             logf("<fatal> File %s is to small (%d bytes)", slave_filename[t], ret);
414             exit(1);
415         }
416         swf_init(&r, data,256);
417         head = swf_read_header(&r);
418         logf("<verbose> File %s has bounding box %d:%d:%d:%d\n",
419                 slave_filename[t], 
420                 head.boundingBox.x1, head.boundingBox.y1,
421                 head.boundingBox.x2, head.boundingBox.y2);
422         if(head.version > fileversion)
423             fileversion = head.version;
424         if(!t)
425             box = head.boundingBox;
426         else {
427             if(head.boundingBox.x1 < box.x1)
428                 box.x1 = head.boundingBox.x1;
429             if(head.boundingBox.y1 < box.y1)
430                 box.y1 = head.boundingBox.y1;
431             if(head.boundingBox.x2 > box.x2)
432                 box.x2 = head.boundingBox.x2;
433             if(head.boundingBox.y2 > box.y2)
434                 box.y2 = head.boundingBox.y2;
435         }
436         logf("<verbose> New master bounding box is %d:%d:%d:%d\n",
437                 box.x1, box.y1,
438                 box.x2, box.y2);
439         fclose(fi);
440     }
441
442     /* we don't have a master, so we create one ourselves. */
443     *masterlength = (numslaves + 1) * 32 + strlength;
444     *masterdata = (u8*)malloc(*masterlength);
445     pos = *masterdata;
446     memcpy(pos, head, sizeof(head));
447     pos += sizeof(head);
448     *pos++ = fileversion;
449     fixpos = (u32*)pos;
450     PUT32(pos, 0x12345678); // to be overwritten
451     pos += 4;
452     writeRECT(&pos, &box);
453     PUT16(pos, 0x2000) // framerate
454     pos += 2;
455     PUT16(pos, numslaves) // framerate
456     pos += 2;
457     for(t=0;t<numslaves;t++)
458     {
459         char buf[128];
460         int namelen;
461
462         if(1) {
463             sprintf(buf, "Frame%02d", t);
464             slave_name[t] = strdup(buf);
465         } 
466         namelen = strlen(slave_name[t]);
467
468         PUT16(&pos[0] , ((u16)(TAGID_DEFINESPRITE<<6) + 6));
469         PUT16(&pos[2] , (t+1)); //ID
470         PUT16(&pos[4] , 0); // Frames
471         PUT16(&pos[6] , 0); // TAG1
472         PUT16(&pos[8] , ((u16)(TAGID_PLACEOBJECT2<<6) + 6 + namelen));
473         PUT16(&pos[10], (34)); //flags: id+name
474         PUT16(&pos[11], (1+t)); // depth
475         PUT16(&pos[13], (t+1)); // id
476         sprintf(&pos[15],slave_name[t]);
477         pos += 15 + namelen + 1;
478         if(!config.stack1 || t == numslaves-1) {
479             PUT16(&pos[0],((u16)(TAGID_SHOWFRAME<<6) + 0));
480             pos += 2;
481         }
482         if(!config.stack)
483         if(t!=numslaves-1)
484         {
485             PUT16(&pos[0], ((u16)(TAGID_REMOVEOBJECT2<<6) + 2));
486             PUT16(&pos[2], (1+t)); // depth;
487             pos += 4;
488         }
489     }
490     PUT16(pos, ((TAGID_END<<6) + 0));
491     *masterlength = pos - *masterdata;
492     PUT32(fixpos, *masterlength);
493 }
494
495 struct config_t config;
496 int main(int argn, char *argv[])
497 {
498     FILE*fi;
499     u8*masterdata;
500     unsigned int masterlength;
501     u8*slavedata;
502     unsigned int slavelength;
503     u8*newdata;
504     unsigned int newlength;
505     int t;
506
507     config.overlay = 0; 
508     config.antistream = 0; 
509     config.alloctest = 0;
510     config.cat = 0;
511     config.merge = 0;
512     config.clip = 0;
513     config.loglevel = 2; 
514     config.movex = 0;
515     config.movey = 0;
516     config.scalex = 1.0;
517     config.scaley = 1.0;
518     config.sizex = 0;
519     config.sizey = 0;
520     config.hassizex = 0;
521     config.hassizey = 0;
522     config.framerate = 0;
523     config.stack = 0;
524     config.stack1 = 0;
525     config.dummy = 0;
526     config.zlib = 0;
527
528     processargs(argn, argv);
529     initLog(0,-1,0,0,-1,config.loglevel);
530
531     if(config.merge && config.cat) {
532         logf("<error> Can't combine --cat and --merge");
533         exit(1);
534     }
535
536     if(config.stack) {
537
538         if(config.overlay) {
539             logf("<error> Can't combine -l and -t");
540             exit(1);
541         }
542         if(config.clip) {
543             logf("<error> Can't combine -c and -t");
544             exit(1);
545         }
546         logf("<verbose> (stacking) %d files found\n", numslaves);
547
548         makestackmaster(&masterdata,&masterlength);
549
550         logf("<verbose> Generated %d bytes of master data", masterlength);
551     }
552     else {
553         logf("<verbose> master entity %s (named \"%s\")\n", master_filename, master_name);
554         fi = fopen(master_filename, "rb");
555         if(!fi) {
556             logf("<fatal> Failed to open %s\n", master_filename);
557             exit(1);
558         }
559         masterdata = fi_slurp(fi, &masterlength);
560         if(!masterdata) {
561             logf("<fatal> Failed to read from %s\n", master_filename);
562             exit(1);
563         }
564         logf("<debug> Read %d bytes from masterfile\n", masterlength);
565         fclose(fi);
566     }
567
568     for(t=0;t<numslaves;t++) {
569             logf("<verbose> slave entity(%d) %s (%s \"%s\")\n", t+1, slave_filename[t], 
570                     slave_isframe[t]?"frame":"object", slave_name[t]);
571     }
572
573     if(config.dummy)
574     {
575         if(numslaves)
576         {
577             logf("<error> --dummy (-d) implies there are zero slave objects. You supplied %d.", numslaves);
578             exit(1);
579         }
580         numslaves = 1;
581         slave_filename[0] = "!!dummy!!";
582         slave_name[0] = "!!dummy!!";
583         slave_isframe[0] = 0;
584     }
585
586     if (config.alloctest)
587     {
588         int*bitmap = malloc(sizeof(int)*65536);
589         memset(bitmap, -1, 65536*sizeof(int));
590         memset(bitmap, 1, 101*sizeof(int));
591         swf_relocate(masterdata, masterlength, bitmap);
592         newdata = masterdata;
593         newlength = masterlength;
594         free(bitmap);
595     }
596     else
597     {
598         if (!numslaves)
599         {
600             logf("<error> You must have at least one slave entity.");
601             return 0;
602         }
603         for(t = 0; t < numslaves; t++)
604         {
605             config.movex = slave_movex[t];
606             config.movey = slave_movey[t];
607             config.scalex = slave_scalex[t];
608             config.scaley = slave_scaley[t];
609             config.isframe = slave_isframe[t];
610
611             logf("<notice> Combine [%s]%s and [%s]%s", master_name, master_filename,
612                     slave_name[t], slave_filename[t]);
613             if(!config.dummy)
614             {
615                 fi = fopen(slave_filename[t], "rb");
616                 if(!fi) {
617                     logf("<fatal> Failed to open %s\n", slave_filename[t]);
618                     exit(1);
619                 }
620                 slavedata = fi_slurp(fi, &slavelength);
621                 if(!slavedata) {
622                     logf("<fatal> Failed to read from %s\n", slave_filename[t]);
623                     exit(1);
624                 }
625                 logf("<debug> Read %d bytes from slavefile\n", slavelength);
626                 fclose(fi);
627             }
628             else
629             {
630                 slavedata = (u8*)malloc(16);
631                 slavedata[0] = 'F';
632                 slavedata[1] = 'W';
633                 slavedata[2] = 'S';
634                 slavedata[3] = 4; //version
635                 PUT32(&slavedata[4], 14); ; // length
636                 slavedata[8] = 0; // boundingbox
637                 PUT16(&slavedata[9] , (0)); // rate
638                 PUT16(&slavedata[11] , (0)); // count
639                 PUT16(&slavedata[13] , (0)); // end tag
640                 slavelength = 17;
641             }
642
643             newdata = combine(masterdata, masterlength, slave_name[t], slavedata, slavelength, &newlength);
644             if(!newdata) { 
645                 logf("<fatal> Aborting.");
646                 return 1;
647             }
648
649             free(masterdata);
650             masterdata = newdata;
651             masterlength = newlength;
652         }
653     }
654
655     logf("<debug> New File is %d bytes \n", newlength);
656     if(newdata && newlength) {
657         FILE*fi = fopen(outputname, "wb");
658         if(config.zlib)
659             fi_pack(fi, newdata, newlength);
660         else
661             fi_dump(fi, newdata, newlength);
662         fclose(fi);
663     }
664     return 0;
665 }
666