* some endianess fixes
[swftools.git] / src / combine.c
1 /* combine.c 
2    Implements combine(), which merges two swfs in memory.
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 <memory.h>
14 #include "../lib/log.h"
15 #include "./flash.h"
16 #include "./reloc.h"
17 #include "./settings.h"
18
19 // TODO:
20 // * readers should be object-oriented
21
22 static char* slavename = 0;
23 static int slaveid = -1;
24 static int slaveframe = -1;
25
26 static char* tag_placeobject2_name (struct swf_tag* tag)
27 {
28     struct PlaceObject2 plo2;
29     placeobject2_init (&plo2, tag);
30     return plo2.name;
31 }
32
33 static u16 tag_placeobject2_character (struct swf_tag* tag)
34 {
35     struct PlaceObject2 plo2;
36     placeobject2_init (&plo2, tag);
37     return plo2.id;
38 }
39
40 static struct swffile master;
41 static struct swffile slave;
42
43 static int masterids[65536];
44
45 static int get_free_id()
46 {
47     int t;
48     for (t=1;t<65536;t++)
49     {
50         if(masterids[t] == -1)
51         {
52             masterids[t] = 1;
53             return t;
54         }
55     }
56     return -1;
57 }
58
59 void changedepth(struct swf_tag*tag, int add)
60 {
61     if(tag->id == TAGID_PLACEOBJECT)
62         (*(u16*)&tag->data[2]) += add;
63     if(tag->id == TAGID_PLACEOBJECT2)
64         (*(u16*)&tag->data[1]) += add;
65     if(tag->id == TAGID_REMOVEOBJECT)
66         (*(u16*)&tag->data[2]) += add;
67     if(tag->id == TAGID_REMOVEOBJECT2)
68         (*(u16*)&tag->data[0]) += add;
69 }
70
71 /* applies the config move and scale parameters to
72  * a matrix. (If settings would provide a rotation,
73  * this would be a matrix concatenation/multiplication
74  * routine. In this case, it's just element-wise multiplication.
75  */
76 void matrix_adjust(struct MATRIX*m)
77 {
78     if(config.scalex != 1 || config.scaley != 1)
79     {
80         if(!m->hasscale) {
81             m->hasscale = 1;
82             m->a[0][0] = config.scalex;
83             m->a[1][1] = config.scaley;
84         } else {
85             m->a[0][0] *= config.scalex;
86             m->a[1][1] *= config.scaley;
87         }
88         if(m->hasrotate) {
89             m->a[0][1] *= config.scalex;
90             m->a[1][0] *= config.scaley;
91         }
92         m->b[0] *= config.scalex;
93         m->b[1] *= config.scaley;
94     }
95 /*    printf("hasscale: %d\n",m->hasscale);
96     printf("hasrotate: %d\n", m->hasrotate);
97     printf("move: %d %d\n", m->b[0],m->b[1]);
98     printf("rot: %f %f\n",m->a[0][0],m->a[0][1]);
99     printf("     %f %f\n",m->a[1][0],m->a[1][1]);*/
100     m->b[0] += config.movex;
101     m->b[1] += config.movey;
102 }
103
104 void write_changepos(struct swf_tag*tag, struct writer_t*w)
105 {
106     if(config.movex || config.movey || config.scalex != 1 || config.scaley != 1)
107     {
108         switch(tag->id)
109         {
110             case TAGID_PLACEOBJECT: {
111                 struct PlaceObject p;
112                 placeobject_init(&p, tag);
113                 matrix_adjust(&p.matrix);
114                 placeobject_write(&p, w);
115                 break;
116             }
117             case TAGID_PLACEOBJECT2: {
118                 struct PlaceObject2 p;
119                 placeobject2_init(&p, tag);
120                 if(!p.hasmatrix) {
121                     p.hasmatrix = 1;
122                     MATRIX_init(&p.matrix);
123                 }
124                 matrix_adjust(&p.matrix);
125                 placeobject2_write(&p, w);
126                 break;
127             }
128             default:
129             writer_write(w, tag->fulldata, tag->fulllength);
130         }
131     } 
132     else 
133     {
134             writer_write(w, tag->fulldata, tag->fulllength);
135     }
136 }
137
138 void write_sprite_defines(struct writer_t*w)
139 {
140     int pos = 0;
141     while(slave.tags[pos].id != 0) {
142         struct swf_tag * tag = &slave.tags[pos];
143         if(!is_sprite_tag(tag->id)) {
144             logf("<debug> processing sprite tag %02x", slave.tags[pos].id);
145             if(is_defining_tag(tag->id))
146             {
147                 logf("<debug> [sprite defs] write tag %02x (%d bytes in body)", 
148                         tag->id, tag->length);
149                 writer_write(w, tag->fulldata, tag->fulllength);
150             }
151             else
152             {
153                 switch(tag->id)
154                 {case TAGID_DEFINEFONTINFO:
155                     {
156                         /* define font info is not a defining tag, in that
157                          * it doesn't define a new id, but rather extends
158                          * an existing one. It also isn't a sprite tag. 
159                          * Anyway we can't throw it out, so we just pass it
160                          * through.
161                          */
162                         writer_write(w, tag->fulldata, tag->fulllength);
163                         break;
164                     }
165                  case TAGID_JPEGTABLES:
166                         /* according to the flash specs, there may only 
167                            be one JPEGTABLES tag per swf. This is maybe
168                            a big FIXME */
169                         writer_write(w, tag->fulldata, tag->fulllength);
170                     break;
171                  case TAGID_EXPORTASSETS:
172                     logf("<debug> deliberately ignoring EXPORTASSETS tag");
173                     break;
174                  case TAGID_ENABLEDEBUGGER:
175                     logf("<debug> deliberately ignoring ENABLEDEBUGGER tag");
176                     break;
177                  case TAGID_BACKGROUNDCOLOR:
178                     logf("<debug> deliberately ignoring BACKGROUNDCOLOR tag");
179                     break;
180                  case 40:
181                  case 49:
182                  case 51:
183                     logf("<notice> found tag %d. This is a Generator template, isn't it?", slave.tags[pos].id);
184                     break;
185                  default:
186                     logf("<notice> funny tag: %d is neither defining nor sprite", slave.tags[pos].id);
187                 }
188             }
189         }
190         pos++;
191     }
192 }
193
194
195 void write_sprite(struct writer_t*w, int spriteid, int replaceddefine)
196 {
197     u16 tmp;
198     u32 tmp32;
199     u32*tagidpos;
200     u8*startpos;
201     int pos = 0;
202     // write slave(2) (header)
203     tmp = 0x3f + (TAGID_DEFINESPRITE << 6);
204     writer_write(w, &tmp, 2);
205     tagidpos = (u32*)writer_getpos(w);
206     writer_write(w, &tmp32, 4);
207     
208     startpos = (u8*)writer_getpos(w);
209
210     logf ("<notice> sprite id is %d", spriteid);
211     tmp = spriteid;
212     writer_write(w, &tmp, 2);
213     tmp = slave.header.count;
214     writer_write(w, &tmp, 2);
215
216
217     // write slave(2) (body)
218     tmp = slave.header.count;
219     logf("<debug> %d frames to go",tmp);
220
221     if(config.clip) {
222         tmp = 7 + (TAGID_PLACEOBJECT2 << 6);
223         writer_write(w, &tmp, 2);
224         tmp = 2+64; //flags: character + clipaction
225         writer_write(w, &tmp, 1);
226         tmp = 0; //depth
227         writer_write(w, &tmp,2);
228         tmp = replaceddefine; //id
229         writer_write(w, &tmp,2);
230         tmp = 65535; //clipdepth
231         writer_write(w, &tmp,2);
232     }
233
234     if(config.overlay && !config.isframe) {
235         tmp = 5 + (TAGID_PLACEOBJECT2 << 6);
236         writer_write(w, &tmp, 2);
237         tmp = 2; //flags: character
238         writer_write(w, &tmp, 1);
239         tmp = 0; //depth
240         writer_write(w, &tmp,2);
241         tmp = replaceddefine; //id
242         writer_write(w, &tmp,2);
243     }
244
245     do {
246         struct swf_tag * tag = &slave.tags[pos];
247         if (is_sprite_tag(tag->id)) {
248
249             changedepth(tag, +1);
250             logf("<debug> [sprite main] write tag %02x (%d bytes in body)", 
251                     slave.tags[pos].id, slave.tags[pos].length);
252             write_changepos(tag, w);
253
254             if(tag->id == TAGID_SHOWFRAME)
255             {
256                 tmp--;
257                 logf("<debug> %d frames to go",tmp);
258             }
259         }
260     }
261     while(slave.tags[pos++].id != TAGID_END);
262
263     *tagidpos = (u8*)writer_getpos(w) - startpos; // set length of sprite (in header)
264     logf("<verbose> sprite length is %d",*tagidpos);
265 }
266
267 static char tag_ok_for_slave(int id)
268 {
269     if(id == TAGID_BACKGROUNDCOLOR)
270         return 0;
271     return 1;
272 }
273
274 #define FLAGS_WRITEDEFINES 1
275 #define FLAGS_WRITENONDEFINES 2
276 #define FLAGS_WRITESPRITE 4
277 #define FLAGS_WRITESLAVE 8
278 void write_master(struct writer_t*w, int spriteid, int replaceddefine, int flags)
279 {
280     int pos = 0;
281     int spos = 0;
282     int outputslave = 0;
283     int frame = 0;
284     int sframe = 0;
285     int slavewritten = 0;
286     while(master.tags[pos].id != 0)
287     {
288         if(master.tags[pos].id == TAGID_SHOWFRAME && outputslave)
289         {
290             while(slave.tags[spos].id) {
291                 if(slave.tags[spos].id == TAGID_SHOWFRAME) {
292                     spos++;
293                     sframe++;
294                     break;
295                 }
296                 if(tag_ok_for_slave(slave.tags[spos].id))
297                     writer_write(w, slave.tags[spos].fulldata, slave.tags[spos].fulllength);
298                 spos++;
299             }
300             frame ++;
301         }
302
303         if(is_defining_tag(master.tags[pos].id) && (flags&FLAGS_WRITEDEFINES))
304         {
305             logf("<debug> [master] write tag %02x (%d bytes in body)", 
306                     master.tags[pos].id, master.tags[pos].length);
307             if(getidfromtag(&master.tags[pos]) == spriteid && !config.isframe)
308             {
309                 if(config.overlay)
310                 {
311                     *(u16*)master.tags[pos].data = replaceddefine;
312                     writer_write(w, master.tags[pos].fulldata, master.tags[pos].fulllength);
313                 } else {
314                     /* don't write this tag */
315                     logf("<verbose> replacing tag %d id %d with sprite", master.tags[pos].id
316                             ,spriteid);
317                 }
318
319                 if(flags&FLAGS_WRITESPRITE)
320                 {
321                     write_sprite_defines(w);
322                     write_sprite(w, spriteid, replaceddefine);
323                 }
324                 if(flags&FLAGS_WRITESLAVE)
325                 {
326                     outputslave = 1;
327                 }
328             } else { 
329                 writer_write(w, master.tags[pos].fulldata, master.tags[pos].fulllength);
330             }
331         }
332         if(frame == slaveframe)
333         {
334             if(flags&FLAGS_WRITESLAVE) {
335                 outputslave = 1;
336                 slavewritten = 1;
337             }
338             if((flags&FLAGS_WRITESPRITE) && !slavewritten)
339             {
340                 int id = get_free_id();
341                 int depth = 0;
342                 char data[7];
343                 if(config.clip) {
344                     logf("<fatal> Can't combine --clip and --frame");
345                 }
346                 *(u16*)&data[0] = (u16)(TAGID_PLACEOBJECT2<<6) + 5 ;
347                 *(u8*)&data[2]= 2; //flags: id
348                 *(u16*)&data[3]= depth; // depth
349                 *(u16*)&data[5]= id;
350                 write_sprite_defines(w);
351                 write_sprite(w, id, -1);
352                 writer_write(w,data,7);
353                 slavewritten = 1;
354             }
355         }
356         if(!is_defining_tag(master.tags[pos].id) && (flags&FLAGS_WRITENONDEFINES))
357         {
358             int dontwrite = 0;
359             switch(master.tags[pos].id) {
360                 case TAGID_PLACEOBJECT:
361                 case TAGID_PLACEOBJECT2:
362                     if(frame == slaveframe && !config.overlay)
363                         dontwrite = 1;
364                 case TAGID_REMOVEOBJECT:
365 //              case TAGID_REMOVEOBJECT2:
366                     /* place/removetags for the object we replaced
367                        should be discarded, too, as the object to insert 
368                        isn't a sprite 
369                      */
370                     if(spriteid>=0 && getidfromtag(&master.tags[pos]) == spriteid && 
371                             !config.isframe && config.merge)
372                         dontwrite = 1;
373                 break;
374             }
375             if(!dontwrite) {
376                 logf("<debug> [master] write tag %02x (%d bytes in body)", 
377                         master.tags[pos].id, master.tags[pos].length);
378                 writer_write(w, master.tags[pos].fulldata, master.tags[pos].fulllength);
379             }
380         }
381         pos++;
382     }
383    
384     if(outputslave) 
385     while(slave.tags[spos].id)
386     {
387             if(tag_ok_for_slave(slave.tags[spos].id))
388                 writer_write(w, slave.tags[spos].fulldata, slave.tags[spos].fulllength);
389             spos++;
390     }
391     if(!slavewritten && config.isframe && (flags&(FLAGS_WRITESLAVE|FLAGS_WRITESPRITE)))
392     {
393         logf("<warning> Frame %d doesn't exist in file. No substitution will occur",
394                 slaveframe);
395     }
396     //write END tag: 
397     writer_write(w, master.tags[pos].fulldata, master.tags[pos].fulllength);
398 }
399
400 void writeheader(struct writer_t*w, u8*data, int length)
401 {
402     if(config.hassizex || config.hassizey || config.framerate)
403     {
404         struct flash_header head;
405         struct reader_t reader;
406         swf_init(&reader, data-3, length+3);
407         head = swf_read_header(&reader);
408         if(config.hassizex)
409         {
410             head.boundingBox.x2 = head.boundingBox.x1 + config.sizex;
411         }
412         if(config.hassizey)
413         {
414             head.boundingBox.y2 = head.boundingBox.y1 + config.sizey;
415         }
416         if(config.framerate)
417         {
418             head.rate = config.framerate;
419         }
420         swf_write_header(w, &head);
421     }
422     else
423     writer_write(w, data, length);
424 }
425
426 uchar * catcombine(uchar*masterdata, int masterlength, char*_slavename, uchar*slavedata, int slavelength, int*newlength)
427 {
428         struct writer_t w;
429         u32*headlength;
430         u32 tmp32;
431         int length = masterlength*2 + slavelength;
432         int pos = 0;
433         int t;
434         char* depths;
435         uchar*newdata = malloc(length);
436         if(!newdata) {
437             logf("<fatal> Couldn't allocate %d bytes of memory", length);
438             return 0;
439         }
440         if(config.isframe) {
441             logf("<fatal> Can't combine --cat and --frame");
442             exit(1);
443         }
444         writer_init(&w, newdata, length);
445         
446         do {
447             int tag = master.tags[pos].id;
448             if(is_defining_tag(tag)) {
449                 int defineid = getidfromtag(&master.tags[pos]);
450                 logf("<debug> tagid %02x defines object %d", tag, defineid);
451                 masterids[defineid] = 1;
452             }
453         }
454         while(master.tags[pos++].id != 0);
455         
456         swf_relocate (slavedata, slavelength, masterids);
457         read_swf(&slave, slavedata, slavelength);
458         
459         writer_write(&w, "FWS",3);
460         headlength = (u32*)(writer_getpos(&w) + 1);
461         writeheader(&w, master.header.headerdata, master.header.headerlength);
462
463         depths = malloc(65536);
464         if(!depths) {
465             logf("<fatal> Couldn't allocate %d bytes of memory", 65536);
466             return 0;
467         }
468         memset(depths, 0, 65536);
469         pos = 0;
470         do {
471             int num=1;
472             u16 depth;
473             logf("<debug> [master] write tag %02x (%d bytes in body)", 
474                     master.tags[pos].id, master.tags[pos].length);
475             switch(master.tags[pos].id) {
476                 case TAGID_PLACEOBJECT2:
477                     num++;
478                 case TAGID_PLACEOBJECT: {
479                    struct reader_t r;
480                    reader_init (&r, master.tags[pos].data, master.tags[pos].length);
481                    if(num>=2)
482                         reader_readu8(&r);
483                    depth = reader_readu16(&r);
484                    depths[depth] = 1;
485                 }
486                 break;
487                 case TAGID_REMOVEOBJECT: {
488                    struct reader_t r;
489                    reader_init (&r, master.tags[pos].data, master.tags[pos].length);
490                    reader_readu16(&r);
491                    depths[reader_readu16(&r)] = 0;
492                 }
493                 break;
494                 case TAGID_REMOVEOBJECT2: {
495                    struct reader_t r;
496                    reader_init (&r, master.tags[pos].data, master.tags[pos].length);
497                    depths[reader_readu16(&r)] = 0;
498                 }
499                 break;
500             }
501             if(master.tags[pos].id != 0)
502                 writer_write(&w, master.tags[pos].fulldata, master.tags[pos].fulllength);
503         }
504         while(master.tags[pos++].id != 0);
505
506         for(t=0;t<65536;t++) 
507         if(depths[t])
508         {
509             char data[16];
510             int len;
511             *(u16*)(&data[0]) = (TAGID_REMOVEOBJECT2<<6) + 2;
512             *(u16*)(&data[2]) = t;
513             writer_write(&w, data, 4);
514         }
515         free(depths);
516
517         pos = 0;
518         do {
519             logf("<debug> [slave] write tag %02x (%d bytes in body)", 
520                     slave.tags[pos].id, slave.tags[pos].length);
521             writer_write(&w, slave.tags[pos].fulldata, slave.tags[pos].fulllength);
522         }
523         while(slave.tags[pos++].id != 0);
524
525         tmp32 = (u8*)writer_getpos(&w) - (u8*)newdata; //length
526         *newlength = tmp32;
527         *headlength = tmp32; // set the header to the correct length
528
529         return newdata; //length
530 }
531
532 uchar * normalcombine(uchar*masterdata, int masterlength, char*_slavename, uchar*slavedata, int slavelength, int*newlength)
533 {
534         int length;
535         int pos=0;
536         u32 tmp32;
537         u32*headlength;
538         uchar*newdata;
539         int spriteid = -1;
540         int replaceddefine = -1;
541         struct writer_t w;
542         int frame;
543         char*framelabel;
544         
545         length = masterlength + slavelength*2 + 128; // this is a guess, but a good guess.
546         newdata = malloc(length);
547         writer_init(&w, newdata, length);
548
549         if(!newdata) {
550             logf("<fatal> Couldn't allocate %d bytes of memory", length);
551             return 0;
552         }
553
554         // set the idtab
555         pos = 0;
556         do {
557             int tag = master.tags[pos].id;
558             if(is_defining_tag(tag)) {
559                 int defineid = getidfromtag(&master.tags[pos]);
560                 logf("<debug> tagid %02x defines object %d", tag, defineid);
561                 masterids[defineid] = 1;
562             } else if(tag == TAGID_PLACEOBJECT2) {
563                 char * name = tag_placeobject2_name(&master.tags[pos]);
564                 int id = tag_placeobject2_character(&master.tags[pos]);
565
566                 if(name)
567                   logf("<verbose> tagid %02x places object %d named \"%s\"", tag, id, name);
568                 else
569                   logf("<verbose> tagid %02x places object %d (no name)", tag, id);
570
571                 if ((name && slavename && !strcmp(name,slavename)) || 
572                     (!slavename && id==slaveid)) {
573                     if(id>=0) {
574                       spriteid = id;
575                       logf("<notice> Slave file attached to object %d.", id);
576                     }
577                 }
578             } else if(tag == TAGID_SHOWFRAME) {
579                 if(slaveframe>=0 && frame==slaveframe) {
580                     logf("<notice> Slave file attached to frame %d.", frame);
581                 }
582                 frame++;
583             } else if(tag == TAGID_FRAMELABEL) {
584                 char * name = master.tags[pos].data;
585                 if(name && slavename && config.isframe && !strcmp(name, slavename)) {
586                     slaveframe = frame;
587                     logf("<notice> Slave file attached to frame %d (%s).", frame, name);
588                 }
589             }
590         }
591         while(master.tags[pos++].id != 0);
592
593         if (spriteid<0 && !config.isframe) {
594             if(slavename) {
595                 if(strcmp(slavename,"!!dummy!!"))
596                     logf("<warning> Didn't find anything named %s in file. No substitutions will occur.", slavename);
597             }
598             else
599                 logf("<warning> Didn't find id %d in file. No substitutions will occur.", slaveid);
600             spriteid = get_free_id();
601         }
602
603         swf_relocate (slavedata, slavelength, masterids);
604
605         read_swf(&slave, slavedata, slavelength);
606         
607         if (config.overlay)
608             replaceddefine = get_free_id();
609         
610         // write file 
611
612         writer_write(&w, "FWS",3);
613         headlength = (u32*)(writer_getpos(&w) + 1);
614         writeheader(&w, master.header.headerdata, master.header.headerlength);
615
616         if (config.antistream) {
617             if (config.merge) {
618                 logf("<fatal> Can't combine --antistream and --merge");
619             }
620             write_sprite_defines(&w);
621             write_sprite(&w, spriteid, replaceddefine);
622             write_master(&w, spriteid, replaceddefine, FLAGS_WRITEDEFINES);
623             write_master(&w, spriteid, replaceddefine, FLAGS_WRITENONDEFINES);
624         } else {
625             if (config.merge)
626                 write_master(&w, spriteid, replaceddefine, 
627                     FLAGS_WRITEDEFINES|FLAGS_WRITENONDEFINES|FLAGS_WRITESLAVE);
628             else
629                 write_master(&w, spriteid, replaceddefine, 
630                     FLAGS_WRITEDEFINES|FLAGS_WRITENONDEFINES|FLAGS_WRITESPRITE);
631         }
632
633         tmp32 = (u8*)writer_getpos(&w) - (u8*)newdata; //length
634         *newlength = tmp32;
635         *headlength = tmp32; // set the header to the correct length
636
637         return newdata; //length
638 }
639
640 uchar * combine(uchar*masterdata, int masterlength, char*_slavename, uchar*slavedata, int slavelength, int*newlength)
641 {
642     char master_flash = 0;
643     char slave_flash = 0;
644     slavename = _slavename;
645
646     slaveid = -1;
647     slaveframe = -1;
648
649     if(slavename[0] == '#')
650     {
651         slaveid = atoi(&slavename[1]);
652         slavename = 0;
653     }
654     if(config.isframe)
655     {
656         slaveframe = slaveid;
657         slaveid = -1;
658     }
659
660     logf("<debug> move x (%d)", config.movex);
661     logf("<debug> move y (%d)", config.movey);
662     logf("<debug> scale x (%d)", config.scalex);
663     logf("<debug> scale y (%d)", config.scaley);
664     logf("<debug> is frame (%d)", config.isframe);
665     
666     memset(masterids, -1, sizeof(masterids));
667
668     if(masterlength < 3)
669     {
670         logf("<fatal> the master file is too small (%d bytes)", masterlength);
671         return 0;
672     }
673     if(slavelength < 3)
674     {
675         logf("<fatal> the slave file is too small (%d bytes)", slavelength);
676         return 0;
677     }
678     if(masterdata[2] == 'S' &&
679        masterdata[1] == 'W' &&
680        masterdata[0] == 'F')
681     {
682         logf("<notice> the master file is flash (swf) format\n");
683         master_flash = 1;
684     }
685     else
686         logf("<notice> the master file is not flash (swf) format!\n");
687
688     if(slavedata[2] == 'S' &&
689        slavedata[1] == 'W' &&
690        slavedata[0] == 'F')
691     {
692         logf("<notice> the slave file is flash (swf) format\n");
693         slave_flash = 1;
694     }
695     else
696         logf("<notice> the slave file is not flash (swf) format!\n");
697
698     if(master_flash && slave_flash) {
699         read_swf(&master, masterdata, masterlength);
700         if(config.cat) 
701             return catcombine(masterdata, masterlength, _slavename, slavedata, slavelength, newlength);
702         else
703             return normalcombine(masterdata, masterlength, _slavename, slavedata, slavelength, newlength);
704     }
705     
706     *newlength = 0;
707     return 0;
708 }