small polygon refinement
[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,2002,2003 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 <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include "../lib/rfxswf.h"
26 #include "../lib/args.h"
27 #include "../lib/log.h"
28 #include "../config.h"
29
30 struct config_t
31 {
32    char overlay;
33    char alloctest;
34    char clip;
35    char stack;
36    char stack1;
37    char antistream;
38    char dummy;
39    char zlib;
40    char cat;
41    char merge;
42    char isframe;
43    char local_with_networking;
44    char local_with_filesystem;
45    char accelerated_blit;
46    char hardware_gpu;
47    int loglevel;
48    int sizex;
49    char hassizex;
50    int sizey;
51    char hassizey;
52    int flashversion;
53    int framerate;
54    int movex;
55    int movey;
56    float scalex;
57    float scaley;
58    int mastermovex;
59    int mastermovey;
60    float masterscalex;
61    float masterscaley;
62 };
63 struct config_t config;
64
65 char * master_filename = 0;
66 char * master_name = 0;
67 char * slave_filename[128];
68 char * slave_name[128];
69 int slave_movex[128];
70 int slave_movey[128];
71 float slave_scalex[128];
72 float slave_scaley[128];
73 char slave_isframe[128];
74 int numslaves = 0;
75
76 char * outputname = "output.swf";
77
78 int args_callback_option(char*name,char*val) {
79     if(!strcmp(name,"c"))
80     {
81         config.clip = 1;
82         return 0;
83     }
84     else if(!strcmp(name,"l"))
85     {
86         config.overlay = 1;
87         return 0;
88     }
89     else if (!strcmp(name, "o"))
90     {
91         outputname = val;
92         return 1;
93     }
94     else if (!strcmp(name, "v"))
95     {
96         config.loglevel ++;
97         return 0;
98     }
99     else if (!strcmp(name, "a"))
100     {
101         config.cat = 1;
102         return 0;
103     }
104     else if (!strcmp(name, "A"))
105     {
106         config.alloctest = 1;
107         return 0;
108     }
109     else if (!strcmp(name, "x"))
110     {
111         float x = atof(val);
112         config.movex = (int)(x*20+0.5);
113         return 1;
114     }
115     else if (!strcmp(name, "y"))
116     {
117         float y = atof(val);
118         config.movey = (int)(y*20+0.5);
119         return 1;
120     }
121     else if (!strcmp(name, "m"))
122     {
123         config.merge = 1;
124         return 0;
125     }
126     else if (!strcmp(name, "f"))
127     {
128         config.isframe = 1;
129         return 0;
130     }
131     else if (!strcmp(name, "d"))
132     {
133         config.dummy = 1;
134         return 0;
135     }
136     else if (!strcmp(name, "z"))
137     {
138         config.zlib = 1;
139         return 0;
140     }
141     else if (!strcmp(name, "r"))
142     {
143
144         float rate = atof(val);
145         if ((rate < 0) ||(rate >= 256.0)) {
146             fprintf(stderr, "Error: You must specify a valid framerate between 1/256 and 255.\n");
147             exit(1);
148         }
149         config.framerate = (int)(rate*256);
150         return 1;
151     }
152     else if (!strcmp(name, "X"))
153     {
154         config.sizex = atoi(val)*20;
155         config.hassizex = 1;
156         return 1;
157     }
158     else if (!strcmp(name, "Y"))
159     {
160         config.sizey = atoi(val)*20;
161         config.hassizey = 1;
162         return 1;
163     }
164     else if (!strcmp(name, "s"))
165     {
166         config.scalex = config.scaley = atoi(val)/100.0;
167         return 1;
168     }
169     else if (!strcmp(name, "w"))
170     {
171         config.scalex = atoi(val)/100.0;
172         return 1;
173     }
174     else if (!strcmp(name, "h"))
175     {
176         config.scaley = atoi(val)/100.0;
177         return 1;
178     }
179     else if (!strcmp(name, "N"))
180     {
181         config.local_with_networking = 1;
182         return 0;
183     }
184     else if (!strcmp(name, "L"))
185     {
186         config.local_with_filesystem = 1;
187         return 0;
188     }
189     else if (!strcmp(name, "B"))
190     {
191         config.accelerated_blit = 1;
192         return 0;
193     }
194     else if (!strcmp(name, "G"))
195     {
196         config.hardware_gpu = 1;
197         return 0;
198     }
199     else if (!strcmp(name, "t") || !strcmp(name, "T"))
200     {
201         if(master_filename) {
202             fprintf(stderr, "error with arguments. Try --help.\n");
203             exit(1);
204         }
205         config.stack = 1;
206         if(!strcmp(name,"T"))
207             config.stack1 = 1;
208         master_filename = "__none__";
209         return 0;
210     }
211     else if (!strcmp(name, "V"))
212     {   
213         printf("swfcombine - part of %s %s\n", PACKAGE, VERSION);
214         exit(0);
215     }
216     else 
217     {
218         fprintf(stderr, "Unknown option: -%s\n", name);
219         exit(1);
220     }
221 }
222
223 static struct options_t options[] = {
224 {"o", "output"},
225 {"t", "stack"},
226 {"T", "stack1"},
227 {"m", "merge"},
228 {"a", "cat"},
229 {"l", "overlay"},
230 {"c", "clip"},
231 {"v", "verbose"},
232 {"F", "flashversion"},
233 {"d", "dummy"},
234 {"f", "frame"},
235 {"x", "movex"},
236 {"y", "movey"},
237 {"s", "scale"},
238 {"r", "rate"},
239 {"X", "width"},
240 {"Y", "height"},
241 {"N", "local-with-networking"},
242 {"G", "hardware-gpu"},
243 {"B", "accelerated-blit"},
244 {"L", "local-with-filesystem"},
245 {"z", "zlib"},
246 {0,0}
247 };
248
249 int args_callback_longoption(char*name,char*val) {
250     return args_long2shortoption(options, name, val);
251 }
252
253 int args_callback_command(char*name, char*val) {
254     char*myname = strdup(name);
255     char*filename;
256     filename = strchr(myname, '=');
257     if(filename) {
258         *filename = 0;
259         filename++;
260     } else {
261         // argument has no explicit name field. guess one from the file name
262         char*path = strrchr(myname, '/');
263         char*ext = strrchr(myname, '.');
264         if(!path) path = myname;
265         else path ++;
266         if(ext) *ext = 0;
267         myname = path;
268         filename = name;
269     }
270
271     if(!master_filename) {
272         master_filename = filename;
273         master_name = myname;
274         config.mastermovex = config.movex;
275         config.mastermovey = config.movey;
276         config.masterscalex = config.scalex;
277         config.masterscaley = config.scaley;
278         config.movex = config.movey = 0;
279         config.scalex = config.scaley = 1.0;
280     } else {             
281         msg("<verbose> slave entity %s (named \"%s\")\n", filename, myname);
282
283         slave_filename[numslaves] = filename;
284         slave_name[numslaves] = myname;
285         slave_movex[numslaves] = config.movex;
286         slave_movey[numslaves] = config.movey;
287         slave_scalex[numslaves] = config.scalex;
288         slave_scaley[numslaves] = config.scaley;
289         slave_isframe[numslaves] = config.isframe; 
290         config.isframe = 0;
291         config.movex = config.movey = 0;
292         config.scalex = config.scaley = 1.0;
293         numslaves ++;
294     }
295     return 0;
296 }
297
298 void args_callback_usage(char *name)
299 {
300     printf("\n");
301     printf("Usage: %s [-rXYomlcv] [-f] masterfile [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
302     printf("OR:    %s [-rXYomv] --stack[1] [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
303     printf("OR:    %s [-rXYov] --cat [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
304     printf("OR:    %s [-rXYomlcv] --dummy [-xys] [file]\n", name);
305     printf("\n");
306     printf("-o , --output <outputfile>      explicitly specify output file. (otherwise, output.swf will be used)\n");
307     printf("-t , --stack                   place each slave in a seperate frame (no master movie)\n");
308     printf("-T , --stack1                  place each slave in the first frame (no master movie)\n");
309     printf("-m , --merge                   Don't store the slaves in Sprites/MovieClips\n");
310     printf("-a , --cat                     concatenate all slave files (no master movie)\n");
311     printf("-l , --overlay                 Don't remove any master objects, only overlay new objects\n");
312     printf("-c , --clip                    Clip the slave objects by the corresponding master objects\n");
313     printf("-v , --verbose                 Be verbose. Use more than one -v for greater effect \n");
314     printf("-F , --flashversion            Set the flash version of the output file.\n");
315     printf("-d , --dummy                   Don't require slave objects (for changing movie attributes)\n");
316     printf("-f , --frame                   The following identifier is a frame or framelabel, not an id or objectname\n");
317     printf("-x , --movex <xpos>            x Adjust position of slave by <xpos> pixels\n");
318     printf("-y , --movey <ypos>            y Adjust position of slave by <ypos> pixels\n");
319     printf("-s , --scale <scale>           Adjust size of slave by <scale> percent (e.g. 100% = original size)\n");
320     printf("-r , --rate <fps>              Set movie framerate to <fps> (frames/sec)\n");
321     printf("-X , --width <width>           Force movie bbox width to <width> (default: use master width (not with -t))\n");
322     printf("-Y , --height <height>          Force movie bbox height to <height> (default: use master height (not with -t))\n");
323     printf("-N , --local-with-networking     Make output file \"local-with-networking\"\n");
324     printf("-G , --hardware-gpu            Set the \"use hardware gpu\" bit in the output file\n");
325     printf("-B , --accelerated-blit        Set the \"use accelerated blit\" bit in the output file\n");
326     printf("-L , --local-with-filesystem     Make output file \"local-with-filesystem\"\n");
327     printf("-z , --zlib <zlib>             Enable Flash 6 (MX) Zlib Compression\n");
328     printf("\n");
329 }
330
331 void removeCommonTags(SWF * swf)
332 {
333     TAG*tag = swf->firstTag;
334     while(tag) {
335         if(tag->id == ST_SCENEDESCRIPTION ||
336            tag->id == ST_FILEATTRIBUTES ||
337            tag->id == ST_REFLEX) {
338             tag = swf_DeleteTag(swf, tag);
339         } else {
340             tag = tag->next;
341         }
342     }
343 }
344
345 static void makestackmaster(SWF*swf)
346 {
347     TAG*tag;
348     int t;
349     SRECT box;
350     int fileversion = config.zlib?6:3;
351     int frameRate = 256;
352     U32 fileAttributes = 0;
353     RGBA rgb;
354     rgb.r=rgb.b=rgb.g=0;
355     memset(&box, 0, sizeof(box));
356
357     /* scan all slaves for bounding box */
358     for(t=numslaves-1;t>=0;t--)
359     {
360         SWF head;
361         int ret;
362         int fi=open(slave_filename[t],O_RDONLY|O_BINARY);
363         TAG*tag;
364         if(fi<0 || swf_ReadSWF(fi, &head)<0) {
365             msg("<fatal> Couldn't open/read %s.", slave_filename[t]);
366             exit(1);
367         }
368         close(fi);
369         swf_RemoveJPEGTables(&head);
370         fileAttributes |= head.fileAttributes;
371         removeCommonTags(&head);
372
373         msg("<verbose> File %s has bounding box %d:%d:%d:%d\n",
374                 slave_filename[t], 
375                 head.movieSize.xmin, head.movieSize.ymin,
376                 head.movieSize.xmax, head.movieSize.ymax);
377
378         tag = head.firstTag;
379         while(tag) {
380             if(tag->id == ST_SETBACKGROUNDCOLOR && tag->len>=3) {
381                 rgb.r = tag->data[0];
382                 rgb.g = tag->data[1];
383                 rgb.b = tag->data[2];
384             }
385             tag=tag->next;
386         }
387         frameRate = head.frameRate;
388         if(head.fileVersion > fileversion)
389             fileversion = head.fileVersion;
390         if(!t)
391             box = head.movieSize;
392         else {
393             if(head.movieSize.xmin < box.xmin)
394                 box.xmin = head.movieSize.xmin;
395             if(head.movieSize.ymin < box.ymin)
396                 box.ymin = head.movieSize.ymin;
397             if(head.movieSize.xmax > box.xmax)
398                 box.xmax = head.movieSize.xmax;
399             if(head.movieSize.ymax > box.ymax)
400                 box.ymax = head.movieSize.ymax;
401         }
402         msg("<verbose> New master bounding box is %d:%d:%d:%d\n",
403                 box.xmin, box.ymin,
404                 box.xmax, box.ymax);
405         swf_FreeTags(&head);
406     }
407
408     memset(swf, 0, sizeof(SWF));
409     swf->fileVersion = fileversion;
410     swf->movieSize = box;
411     swf->frameRate = frameRate;
412     swf->fileAttributes = fileAttributes;
413
414     swf->firstTag = swf_InsertTag(0, ST_SETBACKGROUNDCOLOR);
415     tag = swf->firstTag;
416     swf_SetRGB(tag, &rgb);
417     
418     for(t=0;t<numslaves;t++)
419     {
420         char buf[128];
421         sprintf(buf, "Frame%02d", t);
422         slave_name[t] = strdup(buf);
423
424         tag = swf_InsertTag(tag, ST_DEFINESPRITE);
425         swf_SetU16(tag, t+1);
426         swf_SetU16(tag, 0);
427         tag = swf_InsertTag(tag, ST_END);
428         tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
429         swf_ObjectPlace(tag, t+1, 1+t,0,0, slave_name[t]);
430
431         if(!config.stack1 || t == numslaves-1) {
432             tag = swf_InsertTag(tag, ST_SHOWFRAME);
433         }
434         if(!config.stack)
435         if(t!=numslaves-1)
436         {
437             tag = swf_InsertTag(tag, ST_REMOVEOBJECT2);
438             swf_SetU16(tag, 1+t);
439         }
440     }
441     tag = swf_InsertTag(tag, ST_END);
442     msg("<verbose> temporary SWF created");
443 }
444
445 static char* slavename = 0;
446 static int slaveid = -1;
447 static int slaveframe = -1;
448 static char masterbitmap[65536];
449 static char depthbitmap[65536];
450
451 #define FLAGS_WRITEDEFINES 1
452 #define FLAGS_WRITENONDEFINES 2
453 #define FLAGS_WRITESPRITE 4
454 #define FLAGS_WRITESLAVE 8
455
456 int get_free_id(char*bitmap)
457 {
458     int t;
459     for(t=1;t<65536;t++)
460         if(!bitmap[t]) {
461             bitmap[t] = 1;
462             return t;
463         }
464     return -1;
465 }
466
467 void jpeg_assert(SWF*master, SWF*slave)
468 {
469     /* TODO: if there's a jpegtable found, store it
470        and handle it together with the flash file
471        headers */
472
473     /* check that master and slave don't have both
474        jpegtables (which would be fatal) */
475     int pos;
476     TAG *mpos=0, *spos=0;
477     TAG *mtag,*stag;
478     pos = 0;
479     mtag = master->firstTag;
480     stag = slave->firstTag;
481     while(mtag)
482     {
483         if(mtag->id  == ST_JPEGTABLES)
484             mpos = mtag;
485         mtag = mtag->next;
486     }
487     while(stag)
488     {
489         if(stag->id == ST_JPEGTABLES)
490             spos = stag;
491         stag = stag->next;
492     }
493     if(mpos && spos)
494     {
495         if(spos->len == mpos->len &&
496         !memcmp(spos->data, mpos->data, mpos->len))
497         {
498             // ok, both have jpegtables, but they're identical.
499             // delete one and don't throw an error
500             swf_DeleteTag(slave, spos);
501             spos = 0;
502         }
503     }
504     if(spos && mpos) {
505         msg("<error> Master and slave have incompatible JPEGTABLES.");
506     }
507 }
508
509 TAG* write_sprite_defines(TAG*tag, SWF*sprite)
510 {
511     TAG*rtag = sprite->firstTag;
512     while(rtag && rtag->id!=ST_END) {
513         if(!swf_isAllowedSpriteTag(rtag)) {
514             msg("<debug> processing sprite tag %02x", tag->id);
515             if(swf_isDefiningTag(rtag))
516             {
517                 msg("<debug> [sprite defs] write tag %02x (%d bytes in body)", 
518                         tag->id, tag->len);
519                 tag = swf_InsertTag(tag, rtag->id);
520                 swf_SetBlock(tag, rtag->data, rtag->len);
521             }
522             else if(swf_isPseudoDefiningTag(rtag))
523             {
524                 msg("<debug> [sprite defs] write tag %02x (%d bytes in body)", 
525                         tag->id, tag->len);
526                 tag = swf_InsertTag(tag, rtag->id);
527                 swf_SetBlock(tag, rtag->data, rtag->len);
528             }
529             else {
530                 switch(rtag->id)
531                 {
532                     case ST_JPEGTABLES:
533                            /* if we get here, jpeg_assert has already run,
534                               ensuring this is the only one of it's kind,
535                               so we may safely write it out */
536                            tag = swf_InsertTag(tag, rtag->id);
537                            swf_SetBlock(tag, rtag->data, rtag->len);
538                        break;
539                     case ST_EXPORTASSETS:
540                        msg("<debug> deliberately ignoring EXPORTASSETS tag");
541                        break;
542                     case ST_ENABLEDEBUGGER:
543                        msg("<debug> deliberately ignoring ENABLEDEBUGGER tag");
544                        break;
545                     case ST_SETBACKGROUNDCOLOR:
546                        msg("<debug> deliberately ignoring BACKGROUNDCOLOR tag");
547                        break;
548                     case ST_SHOWFRAME:
549                        msg("<debug> deliberately ignoring SHOWFRAME tag");
550                        break;
551                     case ST_REFLEX:
552                        msg("<debug> deliberately ignoring REFLEX tag");
553                        break;
554                     case 40:
555                     case 49:
556                     case 51:
557                        msg("<notice> found tag %d. This is a Generator template, isn't it?", rtag->id);
558                        break;
559                     default:
560                        msg("<notice> funny tag: %d is neither defining nor sprite", rtag->id);
561                 }
562             }
563         }
564         rtag = rtag->next;
565     }
566     return tag;
567 }
568
569 void changedepth(TAG*tag, int add)
570 {
571     if(tag->id == ST_PLACEOBJECT)
572         PUT16(&tag->data[2],GET16(&tag->data[2])+add);
573     if(tag->id == ST_PLACEOBJECT2)
574         PUT16(&tag->data[1],GET16(&tag->data[1])+add);
575     if(tag->id == ST_REMOVEOBJECT)
576         PUT16(&tag->data[2],GET16(&tag->data[2])+add);
577     if(tag->id == ST_REMOVEOBJECT2)
578         PUT16(&tag->data[0],GET16(&tag->data[0])+add);
579     if(tag->id == ST_PLACEOBJECT2) {
580         SWFPLACEOBJECT obj;
581         U8 flags;
582         swf_SetTagPos(tag, 0);
583         flags = swf_GetU8(tag);
584         if(flags&2) swf_GetU16(tag); //id
585         if(flags&4) swf_GetMatrix(tag, 0);
586         if(flags&8) swf_GetCXForm(tag, 0,1);
587         if(flags&16) swf_GetU16(tag); //ratio
588         if(flags&64) {
589             swf_ResetReadBits(tag);
590             printf("%d->%d\n", GET16(&tag->data[tag->pos]),
591                                GET16(&tag->data[tag->pos])+add);
592             PUT16(&tag->data[tag->pos],GET16(&tag->data[tag->pos])+add);
593         }
594         msg("<warning> Depth relocation not fully working yet with clipdepths", tag->id);
595     }
596 }
597
598 void matrix_adjust(MATRIX*m, int movex, int movey, float scalex, float scaley, int scalepos)
599 {
600     m->sx = (int)(m->sx*scalex);
601     m->sy = (int)(m->sy*scaley);
602     m->r1 = (int)(m->r1*scalex);
603     m->r0 = (int)(m->r0*scaley);
604     if(scalepos) {
605         m->tx *= scalex;
606         m->ty *= scaley;
607     }
608     m->tx += movex;
609     m->ty += movey;
610 }
611
612 void write_changepos(TAG*output, TAG*tag, int movex, int movey, float scalex, float scaley, int scalepos)
613 {
614     if(movex || movey || scalex != 1.0 || scaley != 1.0)
615     {
616         switch(tag->id)
617         {
618             case ST_PLACEOBJECT2: {
619                 MATRIX m;
620                 U8 flags;
621                 swf_GetMatrix(0, &m);
622                 tag->pos = 0;
623                 tag->readBit = 0;
624
625                 flags = swf_GetU8(tag);
626                 swf_SetU8(output, flags|4);
627                 swf_SetU16(output, swf_GetU16(tag)); //depth
628                 //flags&1: move
629                 if(flags&2) {
630                     swf_SetU16(output, swf_GetU16(tag)); //id
631                 }
632                 // flags & 4
633                 if(flags&4) {
634                     swf_GetMatrix(tag, &m);
635                 } else {
636                     swf_GetMatrix(0, &m);
637                 }
638                 matrix_adjust(&m, movex, movey, scalex, scaley, scalepos);
639                 swf_SetMatrix(output, &m);
640
641                 if (tag->readBit)  { tag->pos++; tag->readBit = 0; } //swf_ResetReadBits(tag);
642
643                 swf_SetBlock(output, &tag->data[tag->pos], tag->len - tag->pos);
644                 break;
645             }
646             case ST_PLACEOBJECT: {
647                 MATRIX m;
648                 swf_SetU16(output, swf_GetU16(tag)); //id
649                 swf_SetU16(output, swf_GetU16(tag)); //depth
650                 
651                 swf_GetMatrix(tag, &m);
652                 matrix_adjust(&m, movex, movey, scalex, scaley, scalepos);
653                 swf_SetMatrix(output, &m);
654                 
655                 if (tag->readBit)  { tag->pos++; tag->readBit = 0; } //swf_ResetReadBits(tag);
656
657                 swf_SetBlock(output, &tag->data[tag->pos], tag->len - tag->pos);
658                 break;
659             }
660             default:
661             swf_SetBlock(output, tag->data, tag->len);
662         }
663     } 
664     else 
665     {
666             swf_SetBlock(output, tag->data, tag->len);
667     }
668 }
669
670 TAG* write_sprite(TAG*tag, SWF*sprite, int spriteid, int replaceddefine)
671 {
672     TAG* definespritetag;
673     TAG* rtag;
674     int tmp;
675
676     definespritetag = tag = swf_InsertTag(tag, ST_DEFINESPRITE);
677     swf_SetU16(tag, spriteid);
678     swf_SetU16(tag, sprite->frameCount);
679     msg ("<notice> sprite id is %d", spriteid);
680
681     tmp = sprite->frameCount;
682     msg("<debug> %d frames to go",tmp);
683
684     if(config.clip) {
685         tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
686         swf_SetU8(tag, 2+64); //flags: character+clipdepth
687         swf_SetU16(tag, 0); //depth
688         swf_SetU16(tag, replaceddefine); //id
689         swf_SetU16(tag, 65535); //clipdepth
690     }
691
692     if(config.overlay && !config.isframe) {
693         tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
694         swf_SetU8(tag, 2); //flags: character
695         swf_SetU16(tag, 1); //depth
696         swf_SetU16(tag, replaceddefine); //id
697     }
698
699     rtag = sprite->firstTag;
700     while(rtag && rtag->id!=ST_END)
701     {
702         if (swf_isAllowedSpriteTag(rtag)) {
703
704             msg("<debug> [sprite main] write tag %02x (%d bytes in body)", 
705                     rtag->id, rtag->len);
706             tag = swf_InsertTag(tag, rtag->id);
707             write_changepos(tag, rtag, config.movex, config.movey, config.scalex, config.scaley, 0);
708
709             if(config.clip || (config.overlay && !config.isframe))
710                 changedepth(tag, +2);
711
712             if(tag->id == ST_SHOWFRAME)
713             {
714                 tmp--;
715                 msg("<debug> %d frames to go",tmp);
716             }
717         }
718         rtag = rtag->next;
719     }
720     tag = swf_InsertTag(tag, ST_END);
721     return tag;
722 }
723
724 static char tag_ok_for_slave(int id)
725 {
726     if(id == ST_SETBACKGROUNDCOLOR)
727         return 0;
728     return 1;
729 }
730
731 TAG* write_master(TAG*tag, SWF*master, SWF*slave, int spriteid, int replaceddefine, int flags)
732 {
733     int outputslave = 0;
734     int frame = 1;
735     int sframe = 0;
736     int slavewritten = 0;
737     int deletedepth = -1;
738
739     TAG* rtag = master->firstTag;
740     TAG* stag = slave->firstTag;
741
742     while(rtag && rtag->id!=ST_END)
743     {
744         if(rtag->id == ST_SHOWFRAME && outputslave)
745         {
746             while(stag && stag->id!=ST_END) {
747                 if(stag->id == ST_SHOWFRAME) {
748                     stag = stag->next;
749                     sframe++;
750                     break;
751                 }
752                 if(tag_ok_for_slave(stag->id)) {
753                     tag = swf_InsertTag(tag, stag->id);
754                     write_changepos(tag, stag, config.movex, config.movey, config.scalex, config.scaley, 0);
755                 }
756                 stag = stag->next;
757             }
758         }
759         if(rtag->id == ST_SHOWFRAME)
760         {
761             frame ++;
762             tag = swf_InsertTag(tag, ST_SHOWFRAME);
763             if(deletedepth>=0) {
764                 tag = swf_InsertTag(tag, ST_REMOVEOBJECT2);
765                 swf_SetU16(tag, deletedepth);
766                 deletedepth=-1;
767             }
768             rtag = rtag->next;
769             continue;
770         }
771
772         if(swf_isDefiningTag(rtag) && (flags&FLAGS_WRITEDEFINES))
773         {
774             msg("<debug> [master] write tag %02x (%d bytes in body)", 
775                     rtag->id, rtag->len);
776             if(swf_GetDefineID(rtag) == spriteid && !config.isframe)
777             {
778                 if(config.overlay)
779                 {
780                     tag = swf_InsertTag(tag, rtag->id);
781                     swf_SetBlock(tag, rtag->data, rtag->len);
782                     swf_SetDefineID(tag, replaceddefine);
783                 } else {
784                     /* don't write this tag */
785                     msg("<verbose> replacing tag %d ID %d with sprite", rtag->id ,spriteid);
786                 }
787
788                 if(flags&FLAGS_WRITESPRITE)
789                 {
790                     msg("<debug> writing sprite defines");
791                     tag = write_sprite_defines(tag, slave);
792                     msg("<debug> writing sprite");
793                     tag = write_sprite(tag, slave, spriteid, replaceddefine);
794                 }
795                 if(flags&FLAGS_WRITESLAVE)
796                 {
797                     msg("<debug> writing slave");
798                     outputslave = 1;
799                 }
800             } else { 
801                 tag = swf_InsertTag(tag, rtag->id);
802                 swf_SetBlock(tag, rtag->data, rtag->len);
803             }
804         }
805         if(frame == slaveframe) /* only happens with config.isframe: put slave at specific frame */
806         {
807             if(flags&FLAGS_WRITESLAVE) {
808                 outputslave = 1;
809                 slavewritten = 1;
810             }
811             if((flags&FLAGS_WRITESPRITE) && !slavewritten)
812             {
813                 int id = get_free_id(masterbitmap);
814                 int depth = 65535;
815                 deletedepth = 65535;
816                 if(config.clip) {
817                     msg("<fatal> Can't combine --clip and --frame");
818                 }
819                 
820                 tag = write_sprite_defines(tag, slave);
821                 tag = write_sprite(tag, slave, id, -1);
822
823                 tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
824                     swf_SetU8(tag, 2); //flags: id
825                     swf_SetU16(tag, depth);
826                     swf_SetU16(tag, id);
827
828                 slavewritten = 1;
829             }
830         }
831         if(!swf_isDefiningTag(rtag) && (flags&FLAGS_WRITENONDEFINES))
832         {
833             int dontwrite = 0;
834             switch(rtag->id) {
835                 case ST_PLACEOBJECT:
836                 case ST_PLACEOBJECT2:
837                     if(frame == slaveframe && !config.overlay)
838                         dontwrite = 1;
839                 case ST_REMOVEOBJECT:
840                     /* place/removetags for the object we replaced
841                        should be discarded, too, as the object to insert 
842                        isn't a sprite 
843                      */
844                     if(spriteid>=0 && swf_GetPlaceID(rtag) == spriteid && 
845                             !config.isframe && config.merge)
846                         dontwrite = 1;
847                 break;
848                 case ST_REMOVEOBJECT2:
849                 break;
850             }
851             if(!dontwrite) {
852                 msg("<debug> [master] write tag %02x (%d bytes in body)", 
853                         rtag->id, rtag->len);
854                 tag = swf_InsertTag(tag, rtag->id);
855                 write_changepos(tag, rtag, config.mastermovex, config.mastermovey, config.masterscalex, config.masterscaley, 1);
856                 
857             }
858         }
859         rtag = rtag->next;
860     }
861    
862     if(outputslave) 
863     while(stag && stag->id!=ST_END)
864     {
865             if(tag_ok_for_slave(stag->id)) {
866                 msg("<debug> [slave] write tag %02x (%d bytes in body), %.2f %.2f", rtag->id, rtag->len, config.movex /20.0, config.movey /20.0);
867                 tag = swf_InsertTag(tag, stag->id);
868                 write_changepos(tag, stag, config.movex, config.movey, config.scalex, config.scaley, 0);
869             }
870             stag = stag->next;
871     }
872     if(!slavewritten && config.isframe && (flags&(FLAGS_WRITESLAVE|FLAGS_WRITESPRITE)))
873     {
874         if(slaveframe>=0)
875             msg("<warning> Frame %d doesn't exist in file. No substitution will occur",
876                     slaveframe);
877         else
878             msg("<warning> Frame \"%s\" doesn't exist in file. No substitution will occur",
879                     slavename);
880     }
881     tag = swf_InsertTag(tag, ST_END);
882     return tag;
883 }
884
885 void adjustheader(SWF*swf)
886 {
887     if(config.framerate)
888         swf->frameRate = config.framerate;
889     if(config.hassizex) {
890         swf->movieSize.xmax = 
891         swf->movieSize.xmin + config.sizex;
892     }
893     if(config.hassizey) {
894         swf->movieSize.ymax = 
895         swf->movieSize.ymin + config.sizey;
896     }
897     if(config.flashversion)
898         swf->fileVersion = config.flashversion;
899 }
900
901 void catcombine(SWF*master, char*slave_name, SWF*slave, SWF*newswf)
902 {
903     char* depths;
904     int t;
905     TAG*tag;
906     TAG*mtag,*stag;
907     if(config.isframe) {
908         msg("<fatal> Can't combine --cat and --frame");
909         exit(1);
910     }
911     if(config.flashversion)
912         master->fileVersion = config.flashversion;
913    
914     tag = master->firstTag;
915     while(tag)
916     {
917         if(swf_isDefiningTag(tag)) {
918             int defineid = swf_GetDefineID(tag);
919             msg("<debug> tagid %02x defines object %d", tag->id, defineid);
920             masterbitmap[defineid] = 1;
921         }
922         tag = tag->next;
923     }
924     
925     swf_Relocate(slave, masterbitmap);
926     jpeg_assert(master, slave);
927     
928     memcpy(newswf, master, sizeof(SWF));
929     adjustheader(newswf);
930
931     tag = newswf->firstTag = swf_InsertTag(0, ST_REFLEX); // to be removed later
932
933     depths = malloc(65536);
934     if(!depths) {
935         msg("<fatal> Couldn't allocate %d bytes of memory", 65536);
936         return;
937     }
938     memset(depths, 0, 65536);
939     mtag = master->firstTag;
940     while(mtag && mtag->id!=ST_END)
941     {
942         int num=1;
943         U16 depth;
944         msg("<debug> [master] write tag %02x (%d bytes in body)", 
945                 mtag->id, mtag->len);
946         switch(mtag->id) {
947             case ST_PLACEOBJECT2:
948                 num++;
949             case ST_PLACEOBJECT: {
950                depth = swf_GetDepth(mtag);
951                depths[depth] = 1;
952             }
953             break;
954             case ST_REMOVEOBJECT: {
955                depth = swf_GetDepth(mtag);
956                depths[depth] = 0;
957             }
958             break;
959             case ST_REMOVEOBJECT2: {
960                depth = swf_GetDepth(mtag);
961                depths[depth] = 0;
962             }
963             break;
964         }
965         tag = swf_InsertTag(tag, mtag->id);
966         swf_SetBlock(tag, mtag->data, mtag->len);
967
968         mtag = mtag->next;
969     }
970
971     for(t=0;t<65536;t++) 
972     if(depths[t])
973     {
974         char data[16];
975         int len;
976         tag = swf_InsertTag(tag, ST_REMOVEOBJECT2);
977         swf_SetU16(tag, t);
978     }
979     free(depths);
980
981     stag = slave->firstTag;
982     while(stag && stag->id!=ST_END)
983     {
984         msg("<debug> [slave] write tag %02x (%d bytes in body)", 
985                 stag->id, stag->len);
986         tag = swf_InsertTag(tag, stag->id);
987         swf_SetBlock(tag, stag->data, stag->len);
988         stag = stag->next;
989     }
990     tag = swf_InsertTag(tag, ST_END);
991
992     swf_DeleteTag(newswf, tag);
993 }
994
995 void normalcombine(SWF*master, char*slave_name, SWF*slave, SWF*newswf)
996 {
997     int spriteid = -1;
998     int replaceddefine = -1;
999     int frame = 0;
1000     char*framelabel;
1001     TAG * tag = master->firstTag;
1002
1003     memset(depthbitmap, 0, sizeof(depthbitmap));
1004     
1005     // set the idtab
1006     while(tag)
1007     {
1008         int depth = swf_GetDepth(tag);
1009         if(depth>=0) {
1010             depthbitmap[depth] = 1;
1011         }
1012         if(swf_isDefiningTag(tag)) {
1013             int defineid = swf_GetDefineID(tag);
1014             msg("<debug> tagid %02x defines object %d", tag->id, defineid);
1015             masterbitmap[defineid] = 1;
1016
1017             if (!slavename && defineid==slaveid) {
1018                 if(defineid>=0) {
1019                   spriteid = defineid;
1020                   msg("<notice> Slave file attached to object %d.", defineid);
1021                 }
1022             }
1023         } else if(tag->id == ST_EXPORTASSETS) {
1024             int t;
1025             int num = swf_GetU16(tag);
1026             for(t=0;t<num;t++)
1027             {
1028                 U16 id = swf_GetU16(tag);
1029                 char*name = swf_GetString(tag);
1030                 if(spriteid<0 && slavename && !strcmp(name,slavename)) {
1031                     spriteid = id;
1032                     msg("<notice> Slave file attached to object %d exported as %s.", id, name);
1033                 }
1034             }
1035         } else if(tag->id == ST_SYMBOLCLASS) {
1036             /* a symbolclass tag is like a define tag: it defines id 0000 */
1037             int num = swf_GetU16(tag);
1038             int t;
1039             for(t=0;t<num;t++) {
1040                 U16 id = swf_GetU16(tag);
1041                 if(!id) {
1042                     masterbitmap[id] = 1;
1043                 }
1044                 swf_GetString(tag);
1045             }
1046         } else if(tag->id == ST_PLACEOBJECT2) {
1047             char * name = swf_GetName(tag);
1048             int id = swf_GetPlaceID(tag);
1049
1050             {
1051                 SWFPLACEOBJECT obj;
1052                 swf_GetPlaceObject(tag, &obj);
1053                 swf_PlaceObjectFree(&obj);
1054                 if(obj.clipdepth) {
1055                     depthbitmap[obj.clipdepth] = 1;
1056                 }
1057             }
1058
1059             if(name)
1060               msg("<verbose> tagid %02x places object %d named \"%s\"", tag->id, id, name);
1061             else
1062               msg("<verbose> tagid %02x places object %d (no name)", tag->id, id);
1063
1064             if (name && slavename && !strcmp(name,slavename)) {
1065                 if(id>=0) {
1066                   spriteid = id;
1067                   msg("<notice> Slave file attached to named object %s (%d).", name, id);
1068                 }
1069             }
1070         } else if(tag->id == ST_SHOWFRAME) {
1071             if(slaveframe>=0 && frame==slaveframe) {
1072                 msg("<notice> Slave file attached to frame %d.", frame);
1073             }
1074             frame++;
1075         } else if(tag->id == ST_FRAMELABEL) {
1076             char * name = tag->data;
1077             if(name && slavename && config.isframe && !strcmp(name, slavename)) {
1078                 slaveframe = frame;
1079                 msg("<notice> Slave file attached to frame %d (%s).", frame, name);
1080             }
1081         }
1082         tag = tag->next;
1083     };
1084
1085     if (spriteid<0 && !config.isframe) {
1086         if(slavename) {
1087             if(strcmp(slavename,"!!dummy!!")) {
1088                 msg("<warning> Didn't find anything named %s in file. No substitutions will occur.", slavename);
1089                 if(!strcmp(slavename, "swf")) {
1090                     msg("<warning> (If you were trying to combine rfxview with a document, try replacing 'swf' with 'viewport'.");
1091                 }
1092             }
1093         }
1094         else
1095             msg("<warning> Didn't find id %d in file. No substitutions will occur.", slaveid);
1096         spriteid = get_free_id(masterbitmap);
1097     }
1098
1099     swf_Relocate (slave, masterbitmap);
1100     
1101     if(config.merge)
1102         swf_RelocateDepth (slave, depthbitmap);
1103     jpeg_assert(slave, master);
1104     
1105     if (config.overlay)
1106         replaceddefine = get_free_id(masterbitmap);
1107     
1108     // write file 
1109
1110     memcpy(newswf, master, sizeof(SWF));
1111     adjustheader(newswf);
1112
1113     newswf->firstTag = tag = swf_InsertTag(0, ST_REFLEX); // to be removed later
1114
1115     if (config.antistream) {
1116         if (config.merge) {
1117             msg("<fatal> Can't combine --antistream and --merge");
1118         }
1119         tag = write_sprite_defines(tag, slave);
1120         tag = write_sprite(tag, slave, spriteid, replaceddefine);
1121         tag = write_master(tag, master, slave, spriteid, replaceddefine, FLAGS_WRITEDEFINES);
1122         tag = write_master(tag, master, slave, spriteid, replaceddefine, FLAGS_WRITENONDEFINES);
1123     } else {
1124         if (config.merge)
1125             tag = write_master(tag, master, slave, spriteid, replaceddefine, 
1126                 FLAGS_WRITEDEFINES|FLAGS_WRITENONDEFINES|   FLAGS_WRITESLAVE    );
1127         else
1128             tag = write_master(tag, master, slave, spriteid, replaceddefine, 
1129                 FLAGS_WRITEDEFINES|FLAGS_WRITENONDEFINES|   FLAGS_WRITESPRITE   );
1130     }
1131
1132     swf_DeleteTag(newswf, newswf->firstTag);
1133 }
1134
1135 void combine(SWF*master, char*slave_name, SWF*slave, SWF*newswf)
1136 {
1137     slavename = slave_name;
1138     slaveid = -1;
1139     slaveframe = -1;
1140
1141     if(!master->fileVersion && slave)
1142         master->fileVersion = slave->fileVersion;
1143         
1144     master->fileAttributes |= slave->fileAttributes;
1145
1146     swf_FoldAll(master);
1147     swf_FoldAll(slave);
1148
1149     if(slavename[0] == '#')
1150     {
1151         slaveid = atoi(&slavename[1]);
1152         slavename = 0;
1153     }
1154
1155     if(config.isframe)
1156     {
1157         if(slavename && slavename[0]!='#') {
1158             int tmp;
1159             int len;
1160             sscanf(slavename, "%d%n", &tmp, &len);
1161             if(len == strlen(slavename)) {
1162             /* if the name the slave should replace 
1163                consists only of digits and the -f
1164                option is given, it probably is not
1165                a frame name but a frame number.
1166              */
1167                 slaveid = tmp;
1168                 slavename = 0;
1169             }
1170         }
1171
1172         if(slaveid>=0) {
1173             slaveframe = slaveid;
1174             slaveid = -1;
1175         } else {
1176         /* if id wasn't given as either #number or number,
1177            the name is a frame label. BTW: The user wouldn't necessarily have
1178            needed to supply the -f option in this case */
1179         }
1180     }
1181
1182     msg("<debug> move x (%d)", config.movex);
1183     msg("<debug> move y (%d)", config.movey);
1184     msg("<debug> scale x (%f)", config.scalex);
1185     msg("<debug> scale y (%f)", config.scaley);
1186     msg("<debug> master move x (%d)", config.mastermovex);
1187     msg("<debug> master move y (%d)", config.mastermovey);
1188     msg("<debug> master scale x (%f)", config.masterscalex);
1189     msg("<debug> master scale y (%f)", config.masterscaley);
1190     msg("<debug> is frame (%d)", config.isframe);
1191     
1192     memset(masterbitmap, 0, sizeof(masterbitmap));
1193
1194     if(config.cat) 
1195         return catcombine(master, slave_name, slave, newswf);
1196     else
1197         return normalcombine(master, slave_name, slave, newswf);
1198 }
1199
1200 int main(int argn, char *argv[])
1201 {
1202     int fi;
1203     SWF master;
1204     SWF slave;
1205     SWF newswf;
1206     int t;
1207
1208     config.overlay = 0; 
1209     config.antistream = 0; 
1210     config.alloctest = 0;
1211     config.cat = 0;
1212     config.merge = 0;
1213     config.clip = 0;
1214     config.loglevel = 2; 
1215     config.movex = 0;
1216     config.movey = 0;
1217     config.scalex = 1.0;
1218     config.scaley = 1.0;
1219     config.sizex = 0;
1220     config.sizey = 0;
1221     config.masterscalex = 1.0;
1222     config.masterscaley = 1.0;
1223     config.mastermovex = 0;
1224     config.mastermovey = 0;
1225     config.hassizex = 0;
1226     config.hassizey = 0;
1227     config.framerate = 0;
1228     config.stack = 0;
1229     config.stack1 = 0;
1230     config.dummy = 0;
1231     config.zlib = 0;
1232
1233     processargs(argn, argv);
1234     initLog(0,-1,0,0,-1,config.loglevel);
1235
1236     if(config.merge && config.cat) {
1237         msg("<error> Can't combine --cat and --merge");
1238         exit(1);
1239     }
1240     
1241     if(config.stack && config.cat) {
1242         msg("<error> Can't combine --cat and --stack");
1243         exit(1);
1244     }
1245
1246     if(config.stack) {
1247         if(config.overlay) {
1248             msg("<error> Can't combine -l and -t");
1249             exit(1);
1250         }
1251         if(config.clip) {
1252             msg("<error> Can't combine -c and -t");
1253             exit(1);
1254         }
1255         msg("<verbose> (stacking) %d files found\n", numslaves);
1256
1257         makestackmaster(&master);
1258     }
1259     else {
1260         int ret;
1261         msg("<verbose> master entity %s (named \"%s\")\n", master_filename, master_name);
1262         fi = open(master_filename, O_RDONLY|O_BINARY);
1263         if(fi<0) {
1264             msg("<fatal> Failed to open %s\n", master_filename);
1265             exit(1);
1266         }
1267         ret = swf_ReadSWF(fi, &master);
1268         if(ret<0) {
1269             msg("<fatal> Failed to read from %s\n", master_filename);
1270             exit(1);
1271         }
1272         swf_RemoveJPEGTables(&master);
1273         removeCommonTags(&master);
1274         msg("<debug> Read %d bytes from masterfile\n", ret);
1275         close(fi);
1276     }
1277
1278     for(t=0;t<numslaves;t++) {
1279             msg("<verbose> slave entity(%d) %s (%s \"%s\")\n", t+1, slave_filename[t], 
1280                     slave_isframe[t]?"frame":"object", slave_name[t]);
1281     }
1282
1283     if(config.dummy)
1284     {
1285         if(numslaves)
1286         {
1287             msg("<error> --dummy (-d) implies there are zero slave objects. You supplied %d.", numslaves);
1288             exit(1);
1289         }
1290         numslaves = 1;
1291         slave_filename[0] = "!!dummy!!";
1292         slave_name[0] = "!!dummy!!";
1293         slave_isframe[0] = 0;
1294     }
1295
1296     if (config.alloctest)
1297     {
1298         char*bitmap = malloc(sizeof(char)*65536);
1299         memset(bitmap, 0, 65536*sizeof(char));
1300         memset(bitmap, 1, 101*sizeof(char));
1301         swf_Relocate(&master, bitmap);
1302         newswf = master;
1303         free(bitmap);
1304 //      makestackmaster(&newswf);
1305     }
1306     else
1307     {
1308         if (!numslaves)
1309         {
1310             if(config.cat)
1311                 msg("<error> You must have at least two objects.");
1312             else
1313                 msg("<error> You must have at least one slave entity.");
1314             return 0;
1315         }
1316         for(t = 0; t < numslaves; t++)
1317         {
1318             config.movex = slave_movex[t];
1319             config.movey = slave_movey[t];
1320             config.scalex = slave_scalex[t];
1321             config.scaley = slave_scaley[t];
1322             config.isframe = slave_isframe[t];
1323
1324             msg("<notice> Combine [%s]%s and [%s]%s", master_name, master_filename,
1325                     slave_name[t], slave_filename[t]);
1326             if(!config.dummy)
1327             {
1328                 int ret;
1329                 fi = open(slave_filename[t], O_RDONLY|O_BINARY);
1330                 if(!fi) {
1331                     msg("<fatal> Failed to open %s\n", slave_filename[t]);
1332                     exit(1);
1333                 }
1334                 ret = swf_ReadSWF(fi, &slave);
1335                 if(ret<0) {
1336                     msg("<fatal> Failed to read from %s\n", slave_filename[t]);
1337                     exit(1);
1338                 }
1339                 msg("<debug> Read %d bytes from slavefile\n", ret);
1340                 close(fi);
1341                 swf_RemoveJPEGTables(&slave);
1342                 removeCommonTags(&slave);
1343             }
1344             else
1345             {
1346                 memset(&slave, 0, sizeof(slave));
1347                 slave.firstTag = swf_InsertTag(0, ST_END);
1348                 slave.frameRate = 0;
1349                 slave.fileVersion = 0;
1350                 slave.frameCount = 0;
1351             }
1352
1353             combine(&master, slave_name[t], &slave, &newswf);
1354             master = newswf;
1355         }
1356         if(config.dummy && !config.hassizex && !config.hassizey && !config.mastermovex && !config.mastermovey) {
1357             newswf.movieSize.xmin = newswf.movieSize.xmin*config.masterscalex;
1358             newswf.movieSize.ymin = newswf.movieSize.ymin*config.masterscaley;
1359             newswf.movieSize.xmax = newswf.movieSize.xmax*config.masterscalex;
1360             newswf.movieSize.ymax = newswf.movieSize.ymax*config.masterscaley;
1361         }
1362     }
1363
1364     if(!newswf.fileVersion)
1365         newswf.fileVersion = 4;
1366
1367     if(config.local_with_filesystem)
1368         newswf.fileAttributes &= ~FILEATTRIBUTE_USENETWORK;
1369     if(config.local_with_networking)
1370         newswf.fileAttributes |= FILEATTRIBUTE_USENETWORK;
1371     if(config.accelerated_blit)
1372         newswf.fileAttributes |= FILEATTRIBUTE_USEACCELERATEDBLIT;
1373     if(config.hardware_gpu)
1374         newswf.fileAttributes |= FILEATTRIBUTE_USEHARDWAREGPU;
1375
1376     fi = open(outputname, O_BINARY|O_RDWR|O_TRUNC|O_CREAT, 0777);
1377
1378     if(config.zlib) {
1379         if(newswf.fileVersion < 6)
1380             newswf.fileVersion = 6;
1381         newswf.compressed = 1;
1382         swf_WriteSWF(fi, &newswf);
1383     } else {
1384         newswf.compressed = -1; // don't compress
1385         swf_WriteSWF(fi, &newswf);
1386     }
1387     close(fi);
1388     return 0;
1389 }
1390