/*

    This program dumps contents of ASF movie file,
    creating very long readable log which is useful
    when analyzing unknown aspects of ASF format.
    It is not linked against libaviplay.la.

*/
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <iostream>
#include <iomanip>
#include <cstring>
using namespace std;
#pragma pack(1)
//const char prefix[]="0x";
const char prefix[]="";

struct GUID
{
    unsigned long v1;
    unsigned short v2;
    unsigned short v3;
    unsigned char v4[8];
    int operator==(const GUID& guid) const{return !memcmp(this, &guid, sizeof(GUID));}
    int LookupChunkType();
};

struct ASFFileHeader
{
//0x0
    GUID guid; //generated by client computer
//0x10 TOTAL_SIZE_8
    long long file_size; //in bytes
//0x18
    long long file_time; //time of creation, in 100-nanosecond units since 1.1.1601
//0x20 NUM_PACKETS
    long long num_packets; //how many packets are there in the file
//0x28 FINE_TOTALTIME
    long long total_time; //end timestamp, in 100-nanosecond units
//0x30 FINE_PLAYTIME
    long long play_time; //file duration, in 100-nanosecond units
//0x38 PLAYTIME_OFFSET
    long play_start_time; //timestamp of first packet, in milliseconds
//0x3C
    long unk3; //unknown, maybe reserved ( usually contain 0 )
//0x40 FLAGS
    long flags; //unknown flags, usually contain 0x02 ( seekable? )
//0x44 CHUNKLENGTH
    long pktsize; //size of a data packet
//0x48 CHUNKLENGTH_CONFIRM
    long pktsize_confirm; // the same
    long frame_size; //uncompressed frame size
};
union ASFStreamHeader
{
    char buf[1024];
    struct
    {
	GUID uid_type;    //audio or video stream
	GUID uid_conceal; //is audio data error concealment used
	long long unk1;	//usually 0
	long data_len;   //size of fixed data
	long edata_len;  //data that follows
	unsigned short stream;   //number ( 1, 2 ... )
	long unk2; //usually the same in both streams
		// Eyes on me.asf: 0x62dffd4
		// Alsou - Solo.asf: 0x10
		// Baby one more time.asf: 0x10
		// Cure_LastDayOfSummer.wma: 0x818f900c
		// Windows Movie Maker Sample File.wmv: 0x3f
		// KellssyV_500VBR.wmv: 0x0
        union
	{
         struct
	 {
           long    width;
           long    height;
           char    c;
           short   biSize;

    	    long 	biSize2;
	    long  	biWidth;
    	    long  	biHeight;
	    short 	biPlanes;
	    short 	biBitCount;
            long 	biCompression;
    	    long 	biSizeImage;
            long  	biXPelsPerMeter;
	    long  	biYPelsPerMeter;
            long 	biClrUsed;
            long 	biClrImportant;
         } vid;
         struct
         {;
//           WAVEFORMATEX wfex;
	    char wfex[14];
          // audio scramble data follows
         } aud;
       };
    } h;
};

struct ASFAudioScrambleDef
{
	unsigned char	block_size;		// total number of audio blocks in each scramble group
	unsigned short	chunk_size;		// byte size of each scrambling chunk
	unsigned short	block_align_1;	// usually = nBlockAlign
	unsigned short	block_align_2;	// usually = nBlockAlign
	unsigned char   unk;
};


ASFFileHeader fh;
ASFStreamHeader sh[2];
int streams_found=0;
off_t data_chunk=-1;

ostream& operator<<(ostream& e, const GUID& guid)
{
    return e<<guid.v1<<"-"<<guid.v2<<"-"<<guid.v3<<"-"
	<<(int)(unsigned char)guid.v4[0]<<(int)(unsigned char)guid.v4[1]
	<<(int)(unsigned char)guid.v4[2]<<(int)(unsigned char)guid.v4[3]
	<<(int)(unsigned char)guid.v4[4]<<(int)(unsigned char)guid.v4[5]
	<<(int)(unsigned char)guid.v4[6]<<(int)(unsigned char)guid.v4[7];
}

const GUID guid_audio_stream = { 0xF8699E40, 0x5B4D, 0x11CF,
{0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B}};

const GUID guid_video_stream = { 0xBC19EFC0, 0x5B4D, 0x11CF,
{0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B}};

const GUID guid_audio_conceal_none = { 0x49f1a440, 0x4ece, 0x11d0,
{0xa3, 0xac, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6}};

const GUID guid_audio_conceal_interleave= { 0xbfc3cd50, 0x618f, 0x11cf,
{0x8b, 0xb2, 0x00, 0xaa, 0x00, 0xb4, 0xe2, 0x20}};

const GUID asf_guids[]=
{
	/* 1: header */
    {0x75B22630, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}},

    /* 2: data chunk */
    {0x75b22636, 0x668e, 0x11cf, {0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c}},

    /* 3: index */
    {0x33000890, 0xe5b1, 0x11cf, {0x89, 0xf4, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xcb}},

    /* 4: stream header */
    {0xB7DC0791, 0xA9B7, 0x11CF, {0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}},

    /* 5: ASF 2.0 header */
    {0xD6E229D1, 0x35da, 0x11d1, {0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe}},

    /* 6: file header */
    {0x8CABDCA1, 0xA947, 0x11CF, {0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}},
};
const char* names[]=
{
    "", "header", "data chunk", "index",
    "stream header", "ASF 2.0 header", "file header"
};

int GUID::LookupChunkType()
{
    for(unsigned i=0; i<sizeof asf_guids/sizeof asf_guids[0]; i++)
	if (asf_guids[i] == *this)
	    return i+1;
    return 0;
}

int Dump(int fd, off_t size, int xpos)
{
    off_t endpos=lseek(fd, 0, SEEK_CUR)+size;
    while(lseek(fd, 0, SEEK_CUR)<endpos)
    {
	GUID id;
	long long size;
	if(read(fd, &id, 16)!=16)break;
	if(read(fd, &size, 8)!=8)break;
	int type=id.LookupChunkType();
	if(type)
	{
	    for(int i=0; i<xpos; i++)cout<<" ";
    	    cout<<prefix<<lseek(fd, 0, SEEK_CUR)-24<<": "<<names[type]<<", size "<<(double)(size-24)<<endl;
	}
	else
	{
	    for(int i=0; i<xpos; i++)cout<<" ";
	    cout<<prefix<<lseek(fd, 0, SEEK_CUR)-24<<": Unknown GUID "<<id<<", size 0x"<<(double)(size-24)<<endl;
	}
	if(size<24)cout<<"Illegal length of chunk"<<endl;
	size-=24;
	switch(type)
	{
	case 1:
    	    lseek(fd, 6, SEEK_CUR);
	    Dump(fd, size-6, xpos+1);
	    size=0;
	    break;
	case 2:
	    data_chunk=lseek(fd, 0, SEEK_CUR)+26;
	    break;
	case 4:
	    if(streams_found<2)
	    {
		read(fd, &sh[streams_found++], size);
		size=0;
	    }
	    break;
	case 6:
	    read(fd, &fh, 0x50);
	    size=0;
	    break;
	}
        lseek(fd, size, SEEK_CUR);
    }
    lseek(fd, endpos, SEEK_SET);
    return 0;
}
void DumpPacket(int fd, off_t start, long packetlen)
{
    lseek(fd, start, SEEK_SET);
    cout<<endl<<" Dumping packet at offset "<<prefix<<start<<endl;
    unsigned char hdr[16];
    read(fd, hdr, 16);
    int i;
    cout<<hex;
    for(i=0; i<16; i++)cout<<setw(2)<<setfill('0')<<(int)hdr[i]<<" ";
    cout<<dec<<endl;
    unsigned char* ptr;
    unsigned char segments = 1;
    unsigned char segsize = 0;
    bool v82=false;
    unsigned char pktflags = 0;
    unsigned char segflags = 0;
    unsigned short padding = 0;
    if(hdr[0]==0x82)
    {
	v82=true;
        ptr=hdr+5;
	pktflags=hdr[3];
	segflags=hdr[4];
	cout<<"Global packet flags "<<prefix<<(int)pktflags<<endl;
	cout<<"Segment format "<<(int)segflags<<endl;
	if(pktflags & 0x40)
	{
	    cout<<"Explicit packet size: "<<*(short*)ptr<<endl;
	    packetlen=*(unsigned short*)ptr;
	    ptr+=2;
	}
	if(pktflags & 0x10)
	{
	    cout<<"Padding size [word] "<<prefix<<*(short*)ptr<<endl;
	    padding+=*(short*)ptr;
	    ptr+=2;
	}
	if(pktflags & 0x8)
	{
	    cout<<"Padding size [byte] "<<prefix<<(int)*ptr<<endl;
	    padding+=*ptr;
	    ptr++;
	}
	cout<<"Send packet time: "<<prefix<<*(long*)ptr/1000.<<" s"<<endl;
	ptr+=4;
	cout<<"Duration: "<<*(short*)ptr/1000.<<" s"<<endl;
	ptr+=2;
	if(pktflags & 0x1)
	{
	    segments=(int)*ptr;
	    segsize=segments & 0xC0;
	    segments=segments & 0x3F;
	    cout<<prefix<<(int)segments<<" segments";
	    if(segsize==0x40)cout<<" ( 1-byte dimensions )"<<endl;
	    ptr++;
	}
    }
    else
    {
	cout<<"!!!!!!!!!!!!! YS packet !!!!!!!!!!!!!!!!"<<endl;
	cout<<"Sequence number: "<<prefix<<(int)hdr[5]<<endl;
	cout<<"Pad length: "<<prefix<<*(short*)(&hdr[6])<<endl;
	segments=(int)hdr[14]-0x80;
	cout<<prefix<<segments<<" segments"<<endl;
	ptr=&hdr[15];
    }
    packetlen-=padding;
    packetlen-=(ptr-hdr);
    cout<<"Segments area: "<<prefix<<packetlen<<" bytes"<<endl;
    int pos=start+((int)ptr-(int)&hdr[0]);

    /***
	One packet may contain several segments. This is indicated
	by bit 0x1 set in pktflags.
    ***/

    for(i=0; i<segments; i++)
    {
	lseek(fd, pos, SEEK_SET);
	unsigned char strhdr[32];
	read(fd, strhdr, 32);
	cout<<" Segment "<<prefix<<i<<" at offset "<<prefix<<pos<<":"<<endl;
	cout<<hex;
        for(int j=0; j<20; j++)cout<<setw(2)<<setfill('0')<<(int)strhdr[j]<<" ";
	cout<<dec<<endl;
	int stream_num=((int)strhdr[0]&0x7F);
//	cout<<"  Stream no "<<prefix<<((int)strhdr[0]&0x7F);
	if(strhdr[0]&0x80)cout<<"Key frame"<<endl;
	int seq_num=strhdr[1];
	int off=2;
	int third_field = 0;
	switch(segflags)
	{
	case 0x55:
	    third_field=strhdr[2];
	    off++;
	    break;
	case 0x59:
	    third_field=*(short*)&strhdr[2];
	    off+=2;
	    break;
	case 0x5d:
	    third_field=*(int*)&strhdr[2];
	    off+=4;
	    break;

	};
//	cout<<"  Sequence number "<<prefix<<(int)strhdr[1]<<endl;
	unsigned char lflags;
//	if(v82)
	{
	    lflags=strhdr[off++];
	    cout<<"  Local flags "<<prefix<<(int)lflags;
	    switch(lflags)
	    {
	    case 0x08:
		cout<<" ( Single fragment )";
		break;
	    case 0x01:
		cout<<" ( Multiple fragments )";
		break;
	    default:
		cout<<" ( Unknown )";
		break;
	    }
	    cout<<endl;
	}
//	else
//	{
//	    lflags=strhdr[4];
//	    cout<<"  Local flags "<<prefix<<(int)strhdr[4]<<endl;
//	}
	int object_time;
	unsigned short object_len;
	if(v82)
	switch(lflags)
	{
	case 0x08:
	    cout<<"  Fragment offset "<<prefix<<third_field<<endl;
	    cout<<"  Object length "<<prefix<<*(int*)(&strhdr[off])<<endl;
	    off+=4;//11
	    object_time=*(int*)(&strhdr[off]);
	    off+=4;//15
	    cout<<"  Object time "<<object_time/1000<<"."<<setw(3)<<setfill('0')<<object_time%1000<<" s"<<endl;
	    if(pktflags & 0x1)
	    {	//multiple segments, one fragment
		int fraglen = 0;
		if(segsize==0x80)fraglen=*(unsigned short*)(&strhdr[off]);
		else if(segsize==0x40)fraglen=*(unsigned char*)(&strhdr[off]);
		else cout<<"Unknown segsize"<<endl;
		cout<<" Stream "<<prefix<<stream_num<<", seq_num "<<prefix<<seq_num++
		<<": Fragment length "<<prefix<<fraglen<<endl;
	        pos+=fraglen+off;
		packetlen-=(fraglen+off);
		if(segsize==0x80){pos+=2;packetlen-=2;}
		else {pos++; packetlen--;}
	    }
	    else
	    {   //one segment, one fragment
		cout<<" Stream "<<prefix<<stream_num<<", seq_num "<<prefix<<seq_num++
		<<": Fragment length "<<prefix<<packetlen-off<<endl;
	        pos+=packetlen;
		packetlen=0;
//		packetlen-=(*(short*)(&strhdr[15])+17);
	    }
	    break;
	case 0x01:
	    object_time=third_field;
	    cout<<"  Object time "<<object_time/1000<<"."<<setw(3)<<setfill('0')<<object_time%1000<<" s"<<endl;
	    cout<<"  Unknown: "<<prefix<<(int)strhdr[off]<<endl;
	    pos+=8;
	    packetlen-=8;
	    object_len=packetlen;
	    if(pktflags & 0x01)
	    /***

		Packet with multiple segments, segment with multiple objects.
		We need to have 'object len' field, so that we know when
		this segment ends.

	    ***/
	    {
	        lseek(fd, pos, SEEK_SET);
		read(fd, &object_len, 2);
		cout<<"  Object len "<<prefix<<object_len<<endl;
		pos+=2;
		packetlen-=2;
	    }
	    while(object_len>0)
	    {
		unsigned char tmp;
	        lseek(fd, pos, SEEK_SET);
		read(fd, &tmp, 1);
	        cout<<" Stream "<<prefix<<stream_num<<", seq_num "<<prefix<<seq_num++<<": Fragment length "<<prefix<<(int)tmp<<endl;
		packetlen-=(tmp+1);
		object_len-=(tmp+1);
	        pos+=(tmp+1);
	    }
	    break;
	default:
	    cout<<" !!!!!!!!!!!!!!!! UNKNOWN LFLAGS "<<prefix<<lflags<<" !!!!!!!!!!!!!!"<<endl;
	    i=segments;
	    break;
	}
    }
}
int main(int argc, char** argv)
{
    int fd=open(argv[1], O_RDONLY);
    if(fd<0)return 0;
    off_t len=lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);
    cout<<hex;
    Dump(fd, len, 0);

    cout<<"Dumping file header: "<<endl;
    cout<<"File size "<<prefix<<(double)(fh.file_size)<<endl;
    long long seconds=fh.file_time/10000000-(369*365*86400LL)-89*86400;
//    cout<<"Creation time "<<double(fh.file_time/10000000)<<" s since 1.1.1601"<<endl;
    cout<<"Creation time "<<ctime((time_t*)&seconds);
    cout<<"Number of packets: "<<(double)fh.num_packets<<endl;
    cout<<"Total time: "<<fh.total_time/10000000.<<" s"<<endl;
    cout<<"Play time: "<<fh.play_time/10000000.<<" s"<<endl;
    cout<<"Play start time: "<<fh.play_start_time/1000.<<" s"<<endl;
    cout<<"Flags: "<<fh.flags<<endl;
    cout<<"Packet size: "<<prefix<<fh.pktsize<<"/"<<fh.pktsize_confirm<<" bytes"<<endl;
    cout<<"Uncompressed frame size: "<<prefix<<fh.frame_size<<" bytes"<<endl;
    cout<<"Unknown field: "<<" "<<fh.unk3<<" "<<endl;
    int i;
    for(i=0; i<streams_found; i++)
    {
	cout<<"Dumping stream header: "<<endl;
	cout<<" Stream ID "<<sh[i].h.stream<<endl;
	cout<<" Total data size: "<<sh[i].h.data_len<<endl;
	cout<<" Variable data size: "<<sh[i].h.edata_len<<endl;
	cout<<" Unknown fields: "<<hex<<"0x"<<(double)sh[i].h.unk1<<" 0x"<<(double)sh[i].h.unk2<<dec<<endl;
	if(sh[i].h.uid_type==guid_audio_stream)
	{
	    cout<<" Audio stream"<<endl;
	    cout<<" Format ID "<<*(short*)sh[i].h.aud.wfex<<endl;
	    if(sh[i].h.uid_conceal==guid_audio_conceal_none)
		cout<<" No data concealment"<<endl;
	    else if(sh[i].h.uid_conceal==guid_audio_conceal_interleave)
	    {
		const ASFAudioScrambleDef* asd=
		(const ASFAudioScrambleDef*)
		( (const char*)&(sh[i].h.aud.wfex) + 18 + *(short*)&sh[i].h.aud.wfex[16]);

		cout<<" Interleave data concealment"<<endl;
		cout<<" Parameters: "<<(int)asd->block_size<<" "<<asd->chunk_size
		<<" "<<asd->block_align_1<<" "<<asd->block_align_2
		<<" "<<(int)asd->unk<<endl;
	    }
	    else cout<<" Unknown uid_conceal"<<endl;
	}
	else if(sh[i].h.uid_type==guid_video_stream)
	{
	    cout<<" Video stream"<<endl;
	    cout<<" biSize "<<prefix<<sh[i].h.vid.biSize<<endl;
	    char s[5];
	    *(int*)s=sh[i].h.vid.biCompression;
	    s[4]=0;
	    cout<<" fourcc "<<s<<endl;
	}
	else cout<<" Unknown stream"<<endl;
    }
    if(data_chunk<0){cout<<"Data chunk not found"<<endl;return 0;}
    cout<<"First data packet: offset "<<prefix<<data_chunk<<endl;
    for(i=0; i<fh.num_packets; i++)
	DumpPacket(fd, data_chunk+i*fh.pktsize, fh.pktsize);
    return 0;
}
