// (c) 1990-2007 Electronic Arts Inc.

const char *SPIT_history[] =
{
"------------------------------------------------------------------",
"                                                                  ",
"                           Vid2div v1.00                          ",
"                                                                  ",
"                     by FrANK G. Barchard, EAC                    ",
"                                                                  ",
"                    Main program - Oct 20, 2003                   ",
"                                                                  ",
"------------------------------------------------------------------",
"                                                                  ",
" Version Date   SE History:                                       ",
" ------- ----   -- --------                                       ",
"    1.00 031020 FB based on chimp 1.35                            ",
"                                                                  ",
"------------------------------------------------------------------",
"\0"
};

/****************************************************************/
/*  Spit Setup                                                  */
/****************************************************************/

#if defined(_MSC_VER)
#pragma warning(push,1)
#include "windows.h"
#pragma warning(pop)
#endif

#include "spit.h"
#include "range.h"

const char TextVersion[]="1.00 (Samba) 2007.12.4";
#define TOOL_VERSION 100
#define TOOL_NAME "vid2div"

const int STARVETHRESHHOLD=30;

#define MAXCHUNKS 1000000

static char OPTchunks[SPITMAXLINE];
static char OPTsize[SPITMAXLINE];
static int OPTmodifyoffset;
static int OPTmodifyvalue;
static int OPToverwrite;
static int OPThex;
static int OPTpadbyte;
static int OPTalign;
static int OPTrate;
static int OPTlittle;
static char OPTinterleave[SPITMAXLINE];

static void VID2DIV_settitle(char *title)
{
#if defined(_MSC_VER)
    if (OPTverbose)
        SetConsoleTitle(title);
#endif
}

static enum { UNKNOWN, BIG, LITTLE, IFF, MPEG } autochunktype=UNKNOWN;
static unsigned int chunksize=0;
static GPOS chunkoffset= (GPOS)0;

static void gputx(char *ptr,unsigned int val, int len)
{
    switch (autochunktype)
    {
        case BIG:
        case IFF:
        case MPEG:
            gputm(ptr,val,len);
            break;

        case UNKNOWN:
        case LITTLE:
            gputi(ptr,val,len);
            break;
    }
}

static unsigned int ggetx(const char *ptr,int len)
{
    unsigned int val;
    switch (autochunktype)
    {
        case BIG:
        case IFF:
        case MPEG:
            val = ggetm(ptr,len);
            break;

        case UNKNOWN:
        case LITTLE:
            default:
            val = ggeti(ptr,len);
            break;
    }
    return(val);
}

static void *dochunk(GSTREAM *g, bool readit)
{
    char header[8];
    char *chunk=0;
    int ok;
    unsigned int id;
    unsigned int isize;
    GPOS fsize;

    chunkoffset = gtell(g);
    fsize = glen(g);
    chunksize = 0;
    ok = gread(g,header,8);
    if (ok)
    {
        id    = ggetm(header+0,4);
        isize = ggeti(header+4,4);
        switch (autochunktype)
        {
            case UNKNOWN:
                if (id==GMAKEID('F','O','R','M'))
                {
                    autochunktype=IFF;
                    gseek(g,12);
                    return(dochunk(g,readit));
                }
                else if (id==GMAKEID('V','I','D','1'))
                {
                    autochunktype=BIG;
                    if (OPTlittle)
                    {
                        gputm(header+0,GMAKEID('D','I','V','1'),4); // change VID1 to DIV1
                    }
                }
                else if (id==0x000001b3)
                {
                    autochunktype=MPEG;
                }
                else
                    autochunktype=LITTLE;
                break;

            case BIG:
                break;

            case LITTLE:
                break;

            case IFF:
                break;
        }

        chunksize = ggetx(header+4,4);
        if (OPTlittle)
        {
            gputi(header+4,chunksize,4);    // -little to convert to little endian size field
        }
        if (autochunktype==IFF)
            chunksize = (chunksize+8+1)&~1;
        if (autochunktype==MPEG)
            chunksize = ggeti(header+4,2);
        if (chunksize)
        {
            if (readit)
                chunk = (char *) galloc(chunksize);
            else
                chunk = (char *) galloc(8+4+16);
        }
        if (chunk)
        {
            memcpy(chunk,header,8);
            if (chunksize>8)
            {
                if (readit)
                    ok = gread(g,chunk+8,chunksize-8);
                else
                {
                    GPOS newpos = gtell(g)+chunksize-8;
                    int readamount=gmin(16,chunksize-8);
                    memset(chunk+8,0,4+16);
                    gread(g,chunk+8,readamount);
                    ok = gseek(g,newpos);
                }
            }

            if (!ok)
            {
                gfree(chunk);
                chunk = 0;
            }
        }
    }
    return(chunk);
}

static void *skipchunk(GSTREAM *g)
{
    return(dochunk(g,false));
}

static void *getchunk(GSTREAM *g)
{
    return(dochunk(g,true));
}

// align the chunk to the specified alignment value
// alloc new space to accomodate the larger chunk size (with alignment)
static char * constrainchunksize(char *chunk,int oldchunksize,char *OPTsize,int OPTalign)
{
    int best=0;
    int i;
    int maxcheck;
    int mincheck;
    int alignchunksize;
    char *newchunk;

    alignchunksize = oldchunksize;
    if (OPTalign)
        alignchunksize = ((oldchunksize+OPTalign-1)/(OPTalign))*OPTalign;

    maxcheck = RANGE_max(OPTsize, alignchunksize*5+10000);
    mincheck = RANGE_min(OPTsize, 0);

    for (i=alignchunksize; i<=maxcheck; ++i)
    {
        if (RANGE_match(OPTsize,(char *)0,i))
        {
            best = i;
            break;
        }
    }
    if (!best)
    {
        for (i=alignchunksize; i>=mincheck; --i)
        {
            if (RANGE_match(OPTsize,(char *)0,i))
            {
                best = i;
                break;
            }
        }
    }
    if (!best)
        best = alignchunksize;

    newchunk = (char *) galloc(best);
    memset(newchunk,OPTpadbyte,best);
    memcpy(newchunk,chunk,gmin(oldchunksize,best));
    gputx(newchunk+4,best,4);
    gfree(chunk);
    chunksize = best;
    return(newchunk);
}

static bool ischunkid(const char id)
{
    bool ok=false;
    if (isalnum(id)
     || id=='.'
     || id=='_')
        ok = true;
    return(ok);
}

static void printchunkid(const char *id)
{
    if (ischunkid(id[0])
     && ischunkid(id[1])
     && ischunkid(id[2])
     && ischunkid(id[3]))
    {
        printf("'%-4.4s'",id);
    }
    else
    {
        printf("0x%08lx",ggetx(id,4));
    }
}


/*
;
; ABSTRACT
;
; vid2div - Converts Divx VID to EA DIV files
;
; SUMMARY
;
; vid2div -options [files]
;    @file          response file, containing a list of options and files
;    =file          specify a output file ie source=dest
;    -nologo        disable banner
;    -overwrite<n>  overwrite source if no dest (0=no,1=backup,2=overwrite)
;    -f<range>      chunks to extract
;    -size<range>   range of sizes to restrict output to
;    -align<n>      chunks to specified byte (aka -l)
;                   64 for ps2, 32 for gamecube, 4 for others
;    -padbyte<v>    fill padding with specified value
;    -hex           show offsets in hex instead of decimal
;    -v0            no console output
;    -v1            prints source file name and errors
;    -v2            status line for each file (default)
;    -v3            additional info
;    -w0            don't output file
;    -w1            output to file (default)
;    -time          time performance
;
; RANGE SYNTAX
;
;    [[!][<start>][..][<end>][+=<step>][,]]*
;    ie -f             all frames
;       -f5            frame 5
;       -f5..9         frames 5..9 inclusive
;       -f!10          all frames except 10
;       -f5,7,9        frames 5,7 and 9
;       -f10..98+=2    even frames in range between 10 and 98
;       -fbob,fred     named frames
;       -fS*           extract sound chunks
;       -f!S*          extract non-sound (video) chunks
;
; DESCRIPTION
;
; Vid2div converts Divx VID to EA DIV files.
; VID files from from vidconv tool supplied by divx/factor5.
; If one argument is supplied, the file is just viewed.
;
; RETURNS
;
; no return code
;
; SEE ALSO
;
; gx, chimp
;
; SAMPLE OUTPUT
;
; >vid2div bob.vid =bob.div
;
; END ABSTRACT
;
*/

/****************************************************************/
/*  SPIT Routines                                               */
/****************************************************************/

void SPIT_reset(void)
{
    OPThex = 0;
    OPTmodifyoffset = -1;
    OPTmodifyvalue = 0;
    OPToverwrite = 1;
    OPTchunks[0] = 0;
    OPTsize[0] = 0;
    OPTalign = 0;
    OPTrate = 0;
    OPTpadbyte = 0;
    OPTlittle = 1;  // default is to convert
    OPTinterleave[0] = 0;

    return;
}

char *optionhelp[]=
{
    "    @file          response file, containing a list of options and files\n",
    "    =file          specify a output file ie source=dest\n",
    "    -nologo        disable banner\n",
    "    -overwrite<n>  overwrite source if no dest (0=no,1=backup,2=overwrite)\n",
    "    -f<range>      chunks to extract\n"
    "                   -fS* to extract sound chunks\n"
    "                   -f!S* to extract non-sound (video) chunks\n",
    "    -size<range>   range of sizes to restrict output to\n",
    "    -align<n>      chunks to specified byte (aka -l)\n"
    "                   64 for ps2, 32 for gamecube, 4 for others\n",
    "    -padbyte<v>    fill padding with specified value\n",
    "    -hex           show offsets in hex instead of decimal\n",
    "    -v0            no console output\n",
    "    -v1            prints source file name and errors\n",
    "    -v2            status line for each file (default)\n",
    "    -v3            additional info\n",
    "    -w0            don't output file\n",
    "    -w1            output to file (default)\n",
    "    -time          time performance\n",
    "\n",
    "RANGE SYNTAX:\n"
    "    [[!][<start>][..][<end>][+=<step>][,]]*\n"
    "    ie -f             all frames\n"
    "       -f5            frame 5\n"
    "       -f5..9         frames 5..9 inclusive\n"
    "       -f!10          all frames except\n"
    "       -f5,7,9        frames 5,7 and 9\n"
    "       -f10..98+=2    even frames in range between 10 and 98\n"
    "       -fbob,fred     named frames\n"
    "       -fS*           to extract sound chunks\n"
    "       -f!S*          to extract non-sound (video) chunks\n",
    "\n",
    0,
};


void SPIT_usage(const char *prgname, const char *envname, const char *urlname, int utype)
{
    char **hlp;

    printf("Vid2div v%s by FrANK Barchard, EAC\n",TextVersion);

    if (utype)
        printf("USAGE: %s -options [files]\n", prgname);

    switch (utype)
    {
        case 0:
        case 1:
            break;

        case 2:
            puts("  -align chunks to specified     -time performance");
            puts(" -size constain chunk sizes      -padbyte fill pad with specified value");
            puts("    -f chunks to extract         -w write file");
            puts("     @ response file              = output file");
          printf("type '%s -h' for more help\n", prgname);
          printf("type '%s -help' for web page (%s)\n", prgname, urlname);
            break;

        case 3:
            puts("");
          printf("VERSION: %s v%s, Spit v%.2f\n",SPIT_MACHINE,TextVersion,(float)SPIT_VERSION/100.0f);
            puts("SUMMARY: Converts Divx VID to EA DIV files");
          printf("         SPIT environment variable is %s\n",envname);
            puts("");
            hlp = optionhelp;
            while (*hlp)
            {
                printf("%s",*hlp);
                ++hlp;
            }
            break;

         case 4:
            puts("");
            {
                hlp = optionhelp;
                while (*hlp)
                {
                    if (SPIT_strstr(*hlp,envname))
                        printf("%s",*hlp);
                    ++hlp;
                }
                puts("");
            }
            break;

        default:
            break;
    }
}

char *SPIT_opt(const char *optptr)
{
    char *d=NULL;

    switch (*optptr|(char)' ')
    {
        case 'a':
            if (!strnicmp(optptr, "align", 5))
            {
                d = SPIT_getopt( &OPTalign,optptr+5, "alignment", 0, 65536);
                if (OPTalign==0) OPTalign = 1;
            }
            break;

        case 'f':   return((char *)SPIT_getrangefield( OPTchunks,optptr+1, 1,0));

        case 'h':
            if (!strnicmp(optptr,"hex",3))
                return(SPIT_getopt( &OPThex,optptr+3, "view offsets in hex", 0, 1));
            break;

        case 'i':   return((char *)SPIT_getrangefield( OPTinterleave,optptr+1, 1,0));

        case 'l':
            if (!strnicmp(optptr,"little",6))
                return(SPIT_getopt( &OPTlittle,optptr+6, "output as little endian", 0, 1));
            else
            {
                d = SPIT_getopt( &OPTalign,optptr+1, "align", 0, 65536);
                if (OPTalign==0) OPTalign = 1;
            }
            break;

        case 'm':
                    d = SPIT_getopt( &OPTmodifyoffset,optptr+1, "chunk byte offset to modify", -1,1024*1024);
            if (*d==',') d = SPIT_getopt( &OPTmodifyvalue ,d+1, "chunk byte offset to modify", 0,255);
            return((char *)d);

        case 's':
            if (!strnicmp(optptr,"size",4))
            {
                return((char *)SPIT_getrangefield( OPTsize,optptr+4, 1,0));
            }
            break;


        case 'o':
            if (!strnicmp(optptr,"overwrite",9))
            {
                return((char *)SPIT_getopt( &OPToverwrite,optptr+9, "allow overwriting original file", 0, 2));
            }
            break;

        case 'p':
            if (!strnicmp(optptr,"padbyte",7))
                return(SPIT_getopt( &OPTpadbyte,optptr+7, "fill padding with specified byte", 0, 255));
            break;

        case 'r':
            if (!strnicmp(optptr,"rate",4))
            {
                return((char *)SPIT_getopt( &OPTrate,optptr+4, "rate view", 0, 1000000));
            }
            break;

        default:
            break;
    }
    return(d);
}

int main(int argc,char **argv)
{
    int more;
    int i;
    int frame=0;
    int framein=0;
    bool ok=true;
    int first;
    int last;
    int count;
    int chunkoffsetbits=0;
    unsigned int chunksizemax=0;
    unsigned int chunksizemin=0x7fffffff;
    char *chunk;
    char *filename;
    char bakname[SPITMAXLINE];
    GSTREAM **gin;
    GSTREAM *gout=0;

    SPIT_init(SPIT_reset, SPIT_usage, SPIT_opt, SPIT_history);

    double totaltime = SPIT_gettime();
    do
    {
        more = SPIT_parse(argc, argv, TOOL_NAME, TOOL_VERSION);

        /* view if not options given */

        if (SPITargnum==1 && OPTmodifyoffset==-1 && !stricmp(SPITsargv[0],SPITdargv[0]))
        {
            OPTwrite = 0;
            if (OPTverbose==2)
                OPTverbose = 3;
        }
        if (SPITargnum)
        {
            int starvecount=0;
            bool moreargs=true;

            if (OPTverbose>=3)
            {
                if (OPTwrite)
                {
                    printf("Weaving ");
                    if (OPTmodifyoffset!=-1)
                        printf("Modify ");
                }
                else
                    printf("Viewing ");

                printf("%s ",SPITdargv[SPITargnum-1]);
                if (OPTverbose>=4)
                    printf("\n");
                SPIT_flush();
            }


            gin = (GSTREAM **) galloc(SPITargnum*sizeof(GSTREAM *));
            for (i=0; i<SPITargnum; ++i)
            {
                filename = SPITsargv[i];
                if (SPITsargv[i][0]=='-' || SPITsargv[i][0]=='+' || (SPITsargv[i][0]=='.' && SPITsargv[i][1]!='.' && SPITsargv[i][1]!='/' && SPITsargv[i][1]!='\\'))
                {
                    gin[i] = 0;
                }
                else
                {
                    // print out the filename before the extension gets changed to bak
                    if (OPTverbose==1)
                        printf("%s ", filename);


                    if (!stricmp(SPITsargv[i],SPITdargv[SPITargnum-1]) && OPTwrite)
                    {

                        if (OPToverwrite)
                        {
                            strcpy(bakname,filename);
                            SPIT_tempname(bakname,bakname);
                            remove(bakname);

                            if (OPTverbose>=5)
                                printf("overwrite backing up source from '%s' to '%s'\n",SPITdargv[SPITargnum-1],bakname);

                            if (rename(filename,bakname))
                            {
                                if (OPTverbose)
                                    printf("vid2div - COULD NOT BACK UP SOURCE BEFORE OVERWRITING. READ ONLY? '%s'\n",filename);
                                exit(10);
                            }
                            filename = bakname;
                            if (OPTverbose>=3 && OPToverwrite!=2)
                                printf("Overwrite backup '%s'\n",bakname);

                        }
                        else
                        {
                            if (OPTverbose)
                                printf("vid2div - MUST NAME OUTPUT FILE DIFFERENT THAN INPUT FILE '%s'!\n",filename);
                            exit(10);
                        }
                    }

                    gin[i] = gopen(filename);

                    if (!gin[i])
                    {
                        printf("vid2div - ERROR OPENING '%s' FOR READING\n",SPITsargv[i]);
                        ok = false;
                    }
                }
            }

            if (OPTwrite)
            {
                gout = gwopen(SPITdargv[SPITargnum-1]);
                if (!gout)
                {
                    printf("vid2div - ERROR OPENING '%s' FOR WRITING\n",SPITdargv[SPITargnum-1]);
                    ok = false;
                }
            }

            first = RANGE_min(OPTchunks,  0);
            last  = RANGE_max(OPTchunks,  MAXCHUNKS);
            count = RANGE_count(OPTchunks,  0, MAXCHUNKS);
            if (OPTverbose>=6)
                printf("Range bound %d..%d (%d)\n",first,last,count);

            {
                int InterleaveFirst = RANGE_min(OPTinterleave,  0);
                int InterleaveLast  = RANGE_max(OPTinterleave,  MAXCHUNKS);
                int InterleaveCount = RANGE_count(OPTinterleave,  0, MAXCHUNKS);
                if (OPTverbose>=6)
                    printf("Interleave Range bound %d..%d (%d)\n",InterleaveFirst,InterleaveLast,InterleaveCount);
            }

            while (moreargs && ok && frame<=last)
            {
                moreargs = false;
                for (i=0; i<SPITargnum; ++i)
                {
                    chunk = 0;
                    if (gin[i])
                    {
                        if (frame<first)
                            chunk = (char *) skipchunk(gin[i]);
                        else
                        {
                            if (OPTwrite)
                                chunk = (char *) getchunk(gin[i]);
                            else
                                chunk = (char *) skipchunk(gin[i]);
                        }
                    }

                    if (chunk)
                    {
                        char id[8];
                        id[4] = 0;
                        memcpy(id,chunk,4);
                        moreargs = true;

                        if (RANGE_match(OPTchunks,id,frame))
                        {
                            ++framein;

                            /* constrain size */

                            if (!RANGE_match(OPTsize,(char *)0,chunksize) || OPTalign)
                            {
                                chunk = constrainchunksize(chunk,chunksize,OPTsize,OPTalign);
                            }

//                            if (chunksize>=12)
//                                gputi(chunk+8,0,4);

                            if (OPTmodifyoffset>0 && (unsigned int)OPTmodifyoffset<chunksize)
                            {
                                if (OPTverbose>=6)
                                    printf("modifying byte offset %d from value %d to value %d\n",OPTmodifyoffset,(int)(unsigned char)chunk[OPTmodifyoffset],OPTmodifyvalue);
                                chunk[OPTmodifyoffset] = (unsigned char) OPTmodifyvalue;
                            }

                            if (OPTwrite)
                            {
                                chunkoffset = gtell(gout);
                                ok = gwrite(gout,chunk,chunksize) > 0;
                            }
                            else
                                ok = true;

                            chunkoffsetbits |= chunkoffset;
                            if (chunksize<chunksizemin)
                                chunksizemin = chunksize;
                            if (chunksize>chunksizemax)
                                chunksizemax = chunksize;

                            if (OPTverbose>=4)
                            {
                                if (OPThex)
                                    printf("%8" FORMAT64 "x: ",chunkoffset);
                                else
                                    printf("%11" FORMAT64 "d: ",chunkoffset);
                                printchunkid(id);
                                if (OPThex)
                                    printf(" len %8x, ",chunksize);
                                else
                                    printf(" len %11d, ",chunksize);

                                if (OPTverbose>=5)
                                    for (int i=0; i<14; ++i)
                                        printf("%02x ",*((unsigned char *)chunk+8+i));
                                else
                                {
                                    printf("%d",frame);
                                }
                                printf("\n");

                                SPIT_flush();
                            }

                            if (OPTverbose)
                            {
                                char titlebuf[SPITMAXLINE];

                                if (OPTwrite)
                                    sprintf(titlebuf,"%s - VID2DIV Weaving %d",SPITdargv[SPITargnum-1],frame);
                                else
                                    sprintf(titlebuf,"%s - VID2DIV Viewing %d",SPITdargv[SPITargnum-1],frame);
                                VID2DIV_settitle(titlebuf);
                            }

                            gfree(chunk);
                        }
                        ++frame;
                    }
                    else
                        ++starvecount;
                }
            }
            if (OPTverbose>=2 && ok)
            {
                int chunkalignment=1;
                if (OPTwrite)
                {
                    chunkoffset = gtell(gout);
                }
                else
                    chunkoffset = gtell(gin[0]);

                while (!(chunkoffsetbits&1) && chunkalignment<(256*1024))
                {
                    chunkoffsetbits >>= 1;
                    chunkalignment <<= 1;
                }

                if (starvecount>STARVETHRESHHOLD)
                    printf("vid2div - WARNING.  INPUT STREAM STARVED BY %d CHUNKS\n",starvecount);

                printf("%11" FORMAT64 "d bytes, %d chunks, align %d, sizes %d..%d\n",chunkoffset,framein,chunkalignment,chunksizemin,chunksizemax);
                SPIT_flush();
            }

            for (i=0; i<SPITargnum; ++i)
            {
                if (gin[i])
                    gclose(gin[i]);
            }

            if (OPTwrite && gout)
            {
                gclose(gout);
                if (OPToverwrite==2)
                    remove(bakname);
            }


            gfree(gin);

            if (OPTverbose==1)
                puts("");
        }
    } while (more>0);

    totaltime = SPIT_gettime()-totaltime;

    if (OPTtime)
    {
        if (totaltime>=60000.0)
            printf("TIME " TOOL_NAME " totaltime %.2f ms (%.2f seconds) (%.2f minutes)\n",totaltime,totaltime/1000.0,totaltime/60000.0);
        else if (totaltime>=1000.0)
            printf("TIME " TOOL_NAME " totaltime %.2f ms (%.2f seconds)\n",totaltime,totaltime/1000.0);
        else
            printf("TIME " TOOL_NAME " totaltime %.2f ms\n",totaltime);

    }

    return(0);
}

