/*

Copyright (C) 2005 FDM <fdm@outflux.net>
 
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2 of the License, or (at your
option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
http://www.gnu.org/copyleft/gpl.html

*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <netinet/in.h>

/*
 * 2005-02-25: initial publication
 *
 * Compile: gcc -Wall -o tivo-parse tivo-parse.c
 *
 * Initial header formats lifted from ezrec's posting:
 * http://www.dealdatabase.com/forum/showthread.php?t=41132
 */

/* All elements are in big-endian format */
typedef struct tivo_stream_header_s {
	char filetype[4];       /* the string 'TiVo' */
	uint16_t dummy_0004;
	uint16_t dummy_0006;
	uint16_t dummy_0008;
	uint32_t mpeg_offset;   /* 0-based offset of MPEG stream */
	uint16_t chunks;        /* Number of metadata chunks */
} __attribute__((packed)) tivo_stream_header;

#define TIVO_CHUNK_XML  0
/*
 * XML notes:
 *  - fingerprints appear to be the same on all files from the same tivo
 */
#define TIVO_CHUNK_BLOB 1

typedef struct tivo_stream_chunk_s {
	uint32_t chunk_size;    /* Size of chunk */
	uint32_t data_size;     /* Length of the payload */
	uint16_t id;            /* Chunk ID */
	uint16_t type;          /* Subtype */
} __attribute__((packed)) tivo_stream_chunk;



void parse_tivo(char * filename)
{
	tivo_stream_header head;
	tivo_stream_chunk  chunk;
	uint8_t *data = NULL;
	int i;
	FILE * tivo;
	unsigned int position;

       	if (!(tivo = fopen(filename,"r"))) {
		perror(filename);
		return;
	}

	if (fread(&head,sizeof(head),1,tivo)!=1) {
		perror(filename);
		fclose(tivo);
		return;
	}
	// network byte order conversion
	head.dummy_0004=ntohs(head.dummy_0004);
	head.dummy_0006=ntohs(head.dummy_0006);
	head.dummy_0008=ntohs(head.dummy_0008);
	head.mpeg_offset=ntohl(head.mpeg_offset);
	head.chunks=ntohs(head.chunks);


	printf("TiVo Stream:\n");
	printf("\tfiletype: %c%c%c%c\n",
			head.filetype[0],
			head.filetype[1],
			head.filetype[2],
			head.filetype[3]);
	printf("\tdummy_0004:\t0x%04x\n",head.dummy_0004);
	printf("\tdummy_0006:\t0x%04x\n",head.dummy_0006);
	printf("\tdummy_0008:\t0x%04x\n",head.dummy_0008);
	printf("\tmpeg_offset:\t0x%08x\n",head.mpeg_offset);
	printf("\tchunks:\t0x%04x\n",head.chunks);

	for (i=0;i<head.chunks;i++) {
		size_t padding;

		if (fread(&chunk,sizeof(chunk),1,tivo)!=1) {
			perror(filename);
			fclose(tivo);
			return;
		}
		// network byte order conversion
		chunk.chunk_size=ntohl(chunk.chunk_size);
		chunk.data_size=ntohl(chunk.data_size);
		chunk.id=ntohs(chunk.id);
		chunk.type=ntohs(chunk.type);

		// offset calculations
		padding = chunk.chunk_size - chunk.data_size - sizeof(chunk);

		printf("Chunk %d:\n",i);
		printf("\tchunk_size:\t0x%08x\n",chunk.chunk_size);
		printf("\tdata_size:\t0x%08x\n",chunk.data_size);
		printf("\tid:\t0x%04x\n",chunk.id);
		printf("\ttype:\t0x%04x\n",chunk.type);
		printf("\tpadding:\t0x%02x\n",padding);

		if (chunk.data_size) {
			FILE * dump;
			char name[16];

			if (!(data = (char*)malloc(chunk.data_size+1))) {
				perror("malloc");
				exit(1);
			}
			if (fread(data,chunk.data_size,1,tivo)!=1) {
				perror(filename);
				free(data);
				fclose(tivo);
				return;
			}
			
			snprintf(name,16,"%02d.data",i);
			if ((dump=fopen(name,"w"))) {
				fwrite(data,chunk.data_size,1,dump);
				fclose(dump);
			}
		}

		/*
		if (data && chunk.type == TIVO_CHUNK_XML) {
			data[chunk.data_size]='\0';
			printf("--- dump chunk ---\n");
			printf("%s",data);
			printf("--- end chunk ---\n");
		}
		*/

		if (data) free(data);
		data=NULL;

		// seek to end of chunk
		if (fseek(tivo,padding,SEEK_CUR)<0)  {
			perror(filename);
			fclose(tivo);
			return;
		}
	}

	position=(unsigned int)ftell(tivo);
	printf("position: 0x%04x\n",position);
	printf("unprocessed: 0x%04x\n",head.mpeg_offset-position);

	fclose(tivo);
}


int main(int argc, char * argv[])
{
	int i;
	for (i=1;i<argc;i++) {
		parse_tivo(argv[i]);
	}
	return 0;
}


