/* sendstream.c
 * Send  a tyStream given its FSID
 * Version 0.2 Dec 1, 2001
 *   Added ability to scan for MFS volumes from first block of device
 *   Added ability to set root MFS device as MFS_ROOT environment variable
 * Version 0.1
 * Derived heavily from ExtractStream.c
 * Derived from PlayStream.c written by D18C7DB Jan 2001
 * released under the Gnu General Public License
 *
 *  v0.1 2001 June 6  Initial release for TiVo 2.0.1.
 *  v0.2 2001 June 13 Sequence restart fix, 
 *                    empty start block fix ("!foundfirstframe")
 *                    buffer flush at end.
 *                    increment p properly by SizeOffset.
 *  v0.3 2001 June 19 /dev/hda1[23] fix for TiVoMAD utility
 *                    1.3 backward compatibility (doesn't work).
 *                    10:1 video:audio buffer ratio ~= best quality
 *                    Throw out audio packets when resetting sequence
 *
 */

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/fs.h>
#include <sys/time.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <errno.h>

#define	DEFAULT_MFSROOT		"/dev/hda10"
#define	MFS_ROOT_ENV		"MFS_ROOT"
#define MAX_DEVS		16
#define MAX_STREAMS	32
#define SECTOR_SIZE	0x200
#define FUNNY_CHUNK_MARKER 0xf5467abd
#define MAGIC_SIGNATURE 0x91231ebc
#define CHUNK_SIZE     (0x20000)
#define BUFFER_SIZE     0x2000
#define min(a,b) ((a) <= (b) ? (a) : (b))
#define max(a,b) ((a) <= (b) ? (b) : (a))

int verbose=0;
int dostdout=0;
int skipfunny=1;

_syscall5(int , _llseek, 
	  int, fd, 
	  unsigned long , offset_high,
	  unsigned long , offset_low,
	  loff_t *, result,
	  unsigned int, origin)

static loff_t llseek(int fd, loff_t offset, unsigned origin) {
  loff_t ofs=0;
  int ret;

  ret = _llseek(fd, offset>>32, offset&0xffffffff, &ofs, origin);
  if (ret == 0) return ofs;
  return ret;
}

char MfsRoot[128];
char mfsDevScanBuffer[512];

struct mfs_dev {
  char *dev;
  unsigned long sectors;
  int fd;
};

struct mfs_dev readDevs[MAX_DEVS];

static struct mfs_dev OrigDevs[] = {
  {"/dev/hda10", 0, -1},
  {"/dev/hda11", 0, -1},
  {"/dev/hda12", 0, -1},
  {"/dev/hda13", 0, -1},
  {"/dev/hdb2", 0, -1},
  {"/dev/hdb3", 0, -1},
  {NULL, 0, -1}
};

struct mfs_dev *devs = OrigDevs;

static void read_chunk(char buf[CHUNK_SIZE], long ofs) {
  int i;
  
  unsigned long start=0;
  for (i=0; devs[i].dev; i++) {
    if (devs[i].fd == -2) continue;
    if (devs[i].fd == -1) {
      devs[i].fd = open(devs[i].dev, O_RDONLY);
      if (devs[i].fd == -1) {
        if (verbose) fprintf(stderr,"Failed to open %s\n", devs[i].dev);
        devs[i].fd = -2;
	continue;
      }
      ioctl(devs[i].fd, BLKGETSIZE, &devs[i].sectors);
      devs[i].sectors &= ~255;
      if (verbose) fprintf(stderr,"%s has 0x%08x sectors\n", devs[i].dev, devs[i].sectors);
    }
    if (ofs < start + devs[i].sectors) break;
    start += devs[i].sectors;
  }
  if (!devs[i].dev) {
    fprintf(stderr,"Failed to map sector %d to any drive\n", ofs);
    exit(1);
  }
  if (verbose) fprintf(stderr,"Mapped %x to %s/%x, seeking to %llx\n", ofs, devs[i].dev, ofs-start, (((loff_t)(ofs - start)) << 9) );

  if (llseek(devs[i].fd, (((loff_t)(ofs - start)) << 9), SEEK_SET) != (((loff_t)(ofs - start)) << 9) ) {
    fprintf(stderr,"Seek failed\n");
    exit(1);
  }
  read(devs[i].fd, buf, CHUNK_SIZE);
/*  if (out_dump > 0) write(out_dump, buf, CHUNK_SIZE); */
  if (*(int *)buf == FUNNY_CHUNK_MARKER && skipfunny)
	{
	}
  else
	  if (dostdout)  write(STDOUT_FILENO, buf, CHUNK_SIZE);
}

#define MAX_SEARCH 20

int get_tyStream_sectors(int fd, unsigned char *buffer, unsigned long fsid, int disp, unsigned long *streamchunks) {
  loff_t nsector=0;
  unsigned long i, count, sector, tot_allocated, tot_used, usedsize, partsize;

  if (disp)
	fprintf(stderr,"Attempting to locate tyStream with fsid 0x%lx...\n", fsid);
#ifdef NOHACK
  nsector = ( ( (loff_t)(((fsid * 0x20db2) & 0x3ffff) + 0x462) ) << 9 );
#else
  nsector = ( ( (loff_t)(((fsid * 0x20db2) & 0xfffff) + 0x462) ) << 9 );
#endif
  for (i=0; i<MAX_SEARCH, nsector += 0x200; i++) {
    if(llseek(fd, nsector, SEEK_SET) < 0 || 
       read(fd, buffer, BUFFER_SIZE) != BUFFER_SIZE) {
      fprintf(stderr,"Failed to lseek/read from disk.\n");
      return(0);
    }
    
    if ( *((unsigned long *)(buffer + 0x2c)) != MAGIC_SIGNATURE ) {
      fprintf(stderr,"Sector fails signature check. Trying next block.\n");
      continue;
    }

    sector = *((unsigned long *)(buffer));
    if ((fsid) != (sector)) {
      fprintf(stderr,"Sector FSID passed (0x%lx) failed to match the FSID value in the sector (0x%lx). Trying next block\n",  fsid, sector);
      } else break;
  }
  if (i == MAX_SEARCH) {
    fprintf(stderr,"tyStream sector search for FSID 0x%lx exhausted\n",fsid);
    return 0;
  }
  if (buffer[0x28] != 2) {
    fprintf(stderr,"fsid value does not reference a tyStream.\n");
    return(0);
  }
  tot_allocated = *((unsigned long *)(buffer + 24));
  tot_used = *((unsigned long *)(buffer + 32));
  if (disp)
	fprintf(stderr,"...tyStream located (%d blocks allocated, %d blocks used): \n", tot_allocated, tot_used);
  if (!tot_used)
	{
	tot_used = tot_allocated;
	if (disp)
		fprintf(stderr, "(Adjusting total used to %d.\n", tot_used);
	}
  *streamchunks = tot_used;
  usedsize = tot_used * (CHUNK_SIZE / 0x200);
  if (disp)
	fprintf(stderr,"Start    Length\n");
  for(i = 0; i < *((unsigned long *)(buffer + 0x38)); i++) {
    partsize = *((unsigned long *)(buffer + (i<<3) + 0x40));
    if (partsize > usedsize)
	{
        partsize = usedsize;
	if (disp)
		fprintf(stderr,"%08x %08x (Adjusted from: %08X)\n", *((unsigned long *)(buffer + (i<<3) + 0x3c)), partsize, *((unsigned long *)(buffer + (i<<3) + 0x40)) );
	*((unsigned long *)(buffer + (i<<3) + 0x40)) = partsize;
	}
    else
	if (disp)
		fprintf(stderr,"%08x %08x\n", *((unsigned long *)(buffer + (i<<3) + 0x3c)), partsize );
    usedsize -= partsize;
  }
   if (disp && verbose) {
           fprintf(stderr, "Raw Sector Data:\n");
	   for (i=0;i<0x200;i += 4)
		fprintf(stderr, "%04x, ", *( (unsigned long *) (buffer+i) ) );
	   fprintf(stderr, "\n");
   }	

  return(1);
}

void media_stream(int snum, int scnt, unsigned long chunksdone, unsigned long totchunks, char *sector_list) {
  unsigned char buf[CHUNK_SIZE];
  unsigned long block, i, index, chunksleft;
  int percent, oldpercent = -1;
  long count;
  
// print the sector list

  chunksleft = (totchunks - chunksdone) - 1;
  for(i = 0; i < *((unsigned long *)(sector_list + 0x38)); i++) {
    block = *( (unsigned long *) (sector_list + (i<<3) + 0x3c) );
    count = *( (unsigned long *) (sector_list + (i<<3) + 0x40) );
    index=0;
    while ( index < count ) {
      if (!(chunksleft & 0x0f))
         {
              percent = (100 * chunksleft)/ max(totchunks, 1);
	      percent = 100 - percent;
	      if (oldpercent != percent)
		{
	        fprintf(stderr,"Progress: %d%%", percent);
                oldpercent = percent;
		}
         }
      chunksleft--;
      read_chunk(buf, block+index);
      fprintf(stderr,"\r");
      index += 0x100;
    }
  }
//  fprintf(stderr, "\n");
}

void GetMfsDevices(int fd)
{
    char *mfsListPtr = &mfsDevScanBuffer[0x24];
    int devcnt = 0;

    if(llseek(fd, 0, SEEK_SET) < 0 || 
       read(fd, mfsDevScanBuffer, 512) != 512) {
      fprintf(stderr,"Failed to read MFS list from MFS_ROOT: %s.\nUsing built-in dev list\n", MfsRoot);
      return;
    }

    if (strncmp(MfsRoot, mfsListPtr, strlen(MfsRoot))) {
       fprintf(stderr,"First entry in list from MFS_ROOT: %s does not match MFS_ROOT!\nUsing built-in list\n", MfsRoot);
       return;
    }

    fprintf(stderr, "Usinged MfsDevList: %s\n", mfsListPtr);

    while(*mfsListPtr) {
       readDevs[devcnt].dev = mfsListPtr;
       readDevs[devcnt].sectors = 0;
       readDevs[devcnt].fd = -1;
       while(*mfsListPtr && *mfsListPtr != 0x20)
          mfsListPtr++;
       if (*mfsListPtr == 0x20)
	*(mfsListPtr++) = 0x00;
       devcnt++;
       readDevs[devcnt].dev = NULL;
       readDevs[devcnt].sectors = 0;
       readDevs[devcnt].fd = -1;
    }
    devs = readDevs;

}

static void usage(void) {
  fprintf(stderr,"
sendstream [-v] [-s] <fsid>...

sendstream takes valid <fsid> numbers of the streams you want to play,
and extracts the sectors allocated to that stream.

-v Verbose output (stderr) 

-s Dump raw tyStream data to stdout

-i Include 0xf5467abd chunks in the output

 ");
  exit(0);
}

int main(int argc, char *argv[]) {
  int fd;
  unsigned char *sector_list;
  unsigned char *blkfile;
  char *rootEnv;
  unsigned long fsid_list[MAX_STREAMS], fsid, totchunks = 0, streamchunks, chunksdone = 0;
  int i = 1, streamcnt = 0, curstream;
  int sectorscan = 0;
  if (argc < 2) usage();

  verbose=0; dostdout=0;
  while (argv[i][0] == '-') {
    if (argv[i][1] == 'v' || argv[i][1] == 'V') verbose=1; 
    else if (argv[i][1] == 's' || argv[i][1] == 'S') dostdout=1; 
    else if (argv[i][1] == 'i' || argv[i][1] == 'I') skipfunny=0; 
    else usage();
    i++;
  } 

  rootEnv = getenv(MFS_ROOT_ENV);
  if (rootEnv) {
    fprintf(stderr, "Using MFS_ROOT: %s\n", rootEnv);
    strcpy(MfsRoot, rootEnv);
  }
  else
    strcpy(MfsRoot, DEFAULT_MFSROOT);
  fd = open(MfsRoot, O_RDONLY);
  if (fd == -1) {
    fprintf(stderr,"Failed to open MFS_ROOT: %s\n", MfsRoot);
    exit(1);
  }

  GetMfsDevices(fd);
  
  sector_list = (unsigned char *)malloc(CHUNK_SIZE);
  if (sector_list == NULL) {
    fprintf(stderr,"Failed to allocate memory.\n");
    exit(1);
  }

    while (i < argc && sscanf(argv[i++], "%ld", &fsid) > 0)
	{
	fsid_list[streamcnt++] = fsid;
	if (!get_tyStream_sectors(fd, sector_list, fsid, 1, &streamchunks))
		exit(0);
        totchunks += streamchunks;
	}

    for (curstream=0;curstream < streamcnt;curstream++)
	{
        fsid = fsid_list[curstream];
        if (get_tyStream_sectors(fd, sector_list, fsid, 0, &streamchunks) && dostdout)
		media_stream(curstream + 1, streamcnt, chunksdone, totchunks, sector_list);
        chunksdone += streamchunks;
	}
  fprintf(stderr, "\nCompleted\n");
  free(sector_list);
  close(fd);
}
