/* 
 * $Id: BlessTiVo.c,v 1.4 2000/09/08 16:25:21 mhill Exp $
 */  
 
/************************************************************************** 

BlessTiVo - Blesses a TiVo B Drive
Copyright (C) 2000  Mike Hill

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.

**************************************************************************/

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#define PROGRAMSTRING "BlessTiVo Version 1.00.00\n"

void WriteBlockZero(void);
void WriteSignature(void);
void Partition(void);
long GetDriveSize(void);
void ClearDrive(void);
void Options(void);
int CheckDOS(void);
void RingBell(void);

char bootstr[]={'r','o','o','t','=','/','d','e','v','/',
                'h','d','b','4',0};
char hostname[]="unnamed";

unsigned char sig1[]=
  "Add-on media file system disk, copyright TiVo Inc. 1999.";
unsigned char sig2[]={'T','i','V','o',0x06,0x0b,0x63,0x00};
unsigned char sig3[]="asccd";

char tivodrive[100];

long UpgradeSize = 0;

unsigned char Part1[]={ 
               0x50,0x4d,0x00,0x00, 0x00,0x00,0x00,0x03, 0x00,0x00,0x00,0x01,
               0x00,0x00,0x00,0x3f,
               0x41,0x70,0x70,0x6c, 0x65,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00,
               0x41,0x70,0x70,0x6c, 0x65,0x5f,0x70,0x61, 0x72,0x74,0x69,0x74,
               0x69,0x6f,0x6e,0x5f,
               0x6d,0x61,0x70,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x3f, 0x00,0x00,0x00,0x33,
               0x00,0x00,0x00,0x00};

unsigned char Part2[]={ 
               0x50,0x4d,0x00,0x00, 0x00,0x00,0x00,0x03, 0x00,0x00,0x00,0x40,
               0x00,0x00,0x20,0x00,
               0x53,0x65,0x63,0x6f, 0x6e,0x64,0x20,0x4d, 0x46,0x53,0x20,0x61,
               0x70,0x70,0x6c,0x69,
               0x63,0x61,0x74,0x69, 0x6f,0x6e,0x20,0x72, 0x65,0x67,0x69,0x6f,
               0x6e,0x00,0x00,0x00,
               0x4d,0x46,0x53,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00, 0x00,0x00,0x20,0x00, 0x00,0x00,0x00,0x33,
               0x00,0x00,0x00,0x00};

unsigned char Part3[]={ 
               0x50,0x4d,0x00,0x00, 0x00,0x00,0x00,0x03, 0x00,0x00,0x20,0x40,
               0x00,0xc0,0x0e,0xd0,
               0x53,0x65,0x63,0x6f, 0x6e,0x64,0x20,0x4d, 0x46,0x53,0x20,0x6d,
               0x65,0x64,0x69,0x61,
               0x20,0x72,0x65,0x67, 0x69,0x6f,0x6e,0x00, 0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00,
               0x4d,0x46,0x53,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00,
               0x00,0x00,0x00,0x00, 0x00,0xc0,0x0e,0xd0, 0x00,0x00,0x01,0x33,
               0x00,0x00,0x00,0x00};

int main(int argc,char *argv[]) {
  char c;
  int x,options = 15;

  puts(PROGRAMSTRING);

  if(argc<2) {
    RingBell();
    puts("No device specified");
    Options();
    return 1;
  }
  if(argc > 3) {
    RingBell();
    puts("Too many command line options\n\n");
    Options();
    return 0;
  }
       
  if(argc == 3) {
    if(strcmp("erase",argv[2]) == 0) options = 1;
    if(strcmp("bpage",argv[2]) == 0) options = 2;
    if(strcmp("part",argv[2]) == 0) options = 4;
    if(strcmp("id",argv[2]) == 0) options = 8;
    if(options == 15) {
       RingBell();
       puts("Invalid option\n\n");
       Options();
       return 0;
    }
  }

  if(strcmp("/dev/hdb",argv[1]) == 0 ||
     strcmp("/dev/hdc",argv[1]) == 0 ||
     strcmp("/dev/hdd",argv[1]) == 0 ) {

    strcpy(tivodrive,argv[1]);
 
    x = CheckDOS(); 
    if(x == 3) {
      RingBell(); 
      printf("\nERROR, unable to connect to %s\n"
             "Make sure you are entering the correct device name\n\n"
             "\tPrimary Master   = NOT SUPPORTED\n"
             "\tPrimary Slave    = /dev/hdb\n"
             "\tSecondary Master = /dev/hdc\n"
             "\tSecondary Slave  = /dev/hdd\n",tivodrive);
      return 0;
    }
    if(x == 1) {
      RingBell();
      puts("WARNING this drive appears to be a DOS partitioned drive\n\n"
         "Proceeding with this program could render this drive useless\n\n\n");
    }
    if(x == 2) {
      RingBell();
      puts("WARNING this drive appears to be a Linux partition\n\n"
         "Proceeding with this program could render this drive useless\n\n\n");
    } 
    if(x == 0) {
      puts("WARNING!!!\n\nThis program could damage your hard drives on your PC"
           "\nOr the drives for your TiVo rendering them useless\n\n\n");
    }
    printf("About to modify the drive connected to %s, Are you sure? (Y/N) ",
              argv[1]);
    c=getchar();
    puts("");
    if(c=='Y' || c=='y') {
      if(options & 1) {
        ClearDrive();
        puts("Cleared first few sectors on drive");
      }
 
      if(options & 2) {
        WriteBlockZero();
        puts("Created Bootpage in block 0");
      }
    
      if(options & 4) {
        Partition();
        puts("Drive has been partitioned");
      }

      if(options & 8) {
        WriteSignature();
        puts("Signatures written to partition 3\n");
      }
      if(options == 15) {
        printf("Drive has been blessed.\n\n"
               "This drive was determined to be a %ldGB drive.\n\n"
               "If this calculation is not close or correct, you may want to\n"
               "make sure your PC BIOS supports this drive, and that you\n"
               "are not running this program with a different Linux Kernel\n"
               "then the one supplied with the original boot disk.\n\n",
               UpgradeSize);
        puts("If everything looks ok you can install the drive in your TiVo.\n"
             "\nLinux requires a reboot to recognize the new partitions.\n"
             "Reboot if you wish to examine the partitions in Linux\n");
        puts("You should backup your TiVo A drive before installing this\n"
             "newly blessed disk. You have been warned!\n");
      }
      
      return 0;
    }
    else {
      puts("User aborted program");
      return 1;
    }
  }
  else {
    RingBell();
    puts("Invalid Device Specified\n\n"
         "Only the following are supported /dev/hdb /dev/hdc and /dev/hdd");
    return 1;
  }
}

void WriteBlockZero(void) {
  FILE *fp;
  int x,y;

  fp=fopen(tivodrive,"wb");
  if(fp==NULL) {
    RingBell();  
    printf("\nERROR, unable to connect to %s\n"
           "Make sure you are entering the correct device name\n\n"
           "\tPrimary Master   = NOT SUPPORTED\n"
           "\tPrimary Slave    = /dev/hdb\n"
           "\tSecondary Master = /dev/hdc\n"
           "\tSecondary Slave  = /dev/hdd\n",tivodrive);
    exit(0);
  }

  // Signature
  fputc(0x14,fp);
  fputc(0x92,fp);

  // Primary and alt Kernel partition info
  fputc(0x3,fp); fputc(0x6,fp);

  // Boot Params
    fputs(bootstr,fp);
    x=strlen(bootstr);
    // Pad with nulls
    for(y=0;y<(128-x);y++) fputc(0,fp);

  // Hostname
    fputs(hostname,fp);
    x=strlen(hostname);
    for(y=0;y<(32-x);y++) fputc(0,fp);
 
  // IP address
    fputc(0x0a,fp);
    fputc(0x00,fp);
    fputc(0x02,fp);
    fputc(0x03,fp); 

  // MAC address
    for(x=0;x<6;x++) fputc(x+0x0a,fp);

  // Unused space
    for(x=0;x<338;x++) fputc(0x00,fp);

  fclose(fp);
}

void WriteSignature(void) {
  FILE *fp;

  fp=fopen(tivodrive,"wb");
  if(fp==NULL) {
    RingBell();
    printf("\nERROR, unable to connect to %s\n"
           "Make sure you are entering the correct device name\n\n"
           "\tPrimary Master   = NOT SUPPORTED\n"
           "\tPrimary Slave    = /dev/hdb\n"
           "\tSecondary Master = /dev/hdc\n"
           "\tSecondary Slave  = /dev/hdd\n",tivodrive);
    exit(0);
  }

  fseek(fp,0x07f08000,0);
  fputs((char *)sig1,fp);
  fputc(0,fp); // write out extra null

  fseek(fp,0x1cab2390,0);
  fputs((char *)sig2,fp);
  fputc(0,fp); // write out extra null

  fseek(fp,0x313a58fc,0);
  fputs((char *)sig3,fp);
  fputc(0,fp);  // write out extra null

  fclose(fp);
}

void Partition(void) {
  FILE *fp;
  long size;
  unsigned char c,d,e,f;

  size=GetDriveSize();
  size-=8256;

  UpgradeSize = size / 2097512L;

  c=size & 0xff;
  d=( size >> 8 ) & 0xff;
  e=( size >> 16) & 0xff;
  f=( size >> 24) & 0xff;

  Part3[0x0c]=Part3[0x54]=f;
  Part3[0x0d]=Part3[0x55]=e;
  Part3[0x0e]=Part3[0x56]=d;
  Part3[0x0f]=Part3[0x57]=c;

  fp=fopen(tivodrive,"wb");
  if(fp==NULL) {
    RingBell();
    printf("\nERROR, unable to connect to %s\n"
           "Make sure you are entering the correct device name\n\n"
           "\tPrimary Master   = NOT SUPPORTED\n"
           "\tPrimary Slave    = /dev/hdb\n"
           "\tSecondary Master = /dev/hdc\n"
           "\tSecondary Slave  = /dev/hdd\n",tivodrive);
    exit(0); 
  }
  
  fseek(fp,0x200,0);
  fwrite(Part1,1,96,fp);

  fseek(fp,0x400,0);
  fwrite(Part2,1,96,fp);
  
  fseek(fp,0x600,0);
  fwrite(Part3,1,96,fp);

  fclose(fp);
}

long GetDriveSize(void) {
  FILE *fp;
  char buff[100],len[100];
  long length;

  sprintf(buff,"/proc/ide/hd%c/capacity",tivodrive[7]);  
  fp=fopen(buff,"rb");

  if(fp==NULL) {
    RingBell();
    printf("\nERROR, unable to determine capacity of drive connected to %s\n\n"
           "This Linux kernel may not support /proc/ide\n\n"
           "Make sure you are using the bootdisk supplied"
           " with BlessTiVo\n",tivodrive);
    exit(0);
  }
  fgets(len,100,fp);
  fclose(fp);
  length=atol(len);
  return length;
}

void ClearDrive(void) {
  FILE *fp;
  char buff[512];
  int x;

  for(x=0;x<512;x++) buff[x]=0;

  fp=fopen(tivodrive,"wb");
  if(fp==NULL) {
    RingBell();
    printf("\nERROR, unable to connect to %s\n"
           "Make sure you are entering the correct device name\n\n"
           "\tPrimary Master   = NOT SUPPORTED\n"
           "\tPrimary Slave    = /dev/hdb\n"
           "\tSecondary Master = /dev/hdc\n"
           "\tSecondary Slave  = /dev/hdd\n",tivodrive);
    exit(0);
  }

  for(x=0;x<32;x++) {
    fwrite(buff,1,512,fp);
  }
  fclose(fp);
}

void Options(void) {
  puts("format is 'BlessTiVo /dev/hdX stage'\n\n"
       "fully automated operation is 'BlessTiVo /dev/hdX'\n\n"
       "\tthe 'stage' is optional and can be any of the following\n\n"
       "\terase This option erases the first few sectors on the drive\n"
       "\tbpage This creates a valid bootpage only\n"
       "\tpart  This partitions the drive only\n"
       "\tid    This generates the TiVoID only\n\n"
       "\tUSE THE STAGE OPTIONS ONLY IF YOU KNOW WHAT YOUR DOING!\n\n");
}

int CheckDOS(void){
  FILE *fp;
  unsigned char buff[4];

  fp=fopen(tivodrive,"rb");
  if(fp==NULL) return 3;
  fgets(buff,3,fp);
  fclose(fp);

  if(buff[0]==0x33 && buff[1]==0xc0 && buff[2]==0x8e) return 1;
  if(buff[0]==0xfa) return 2;
  return 0;  
}

void RingBell(void) {
  putchar(7);
}
