/*
OSX CLI Blesser
    Copyright (C) 2002  Eric C Wagner

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

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

/* Program return format is 
##: string
string
Done

where ## is as follows and string is a status string
an optional string follows with additional info
Done signifies the output is compete

0: Blessing was successful
1: Format type is Tivo
2: Format type is PC Linux
3: Format type is DOS
4: Format type is Mac
9: Format type is unknown
	key value as hex
10: Drive too small
11: Couldn't open drive
	errno value as int
12: Number partitions
	


*/


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

	/* minimum of one gig for MFS partition */
#define		minMFSSize	1024*1024*2

typedef unsigned long 	UInt32;
typedef unsigned short 	UInt16;
typedef unsigned char 	UInt8;
typedef signed long 	SInt32;
typedef signed short	SInt16;
typedef signed char		SInt8;


struct dpme {
    UInt16     dpme_signature          ;
    UInt16     dpme_reserved_1         ;
    UInt32     dpme_map_entries        ;
    UInt32     dpme_pblock_start       ;
    UInt32     dpme_pblocks            ;
    SInt8      dpme_name[32]  	       ;  /* name of partition */
    SInt8      dpme_type[32]           ;  /* type of partition */
    UInt32     dpme_lblock_start       ;
    UInt32     dpme_lblocks            ;
    UInt32     dpme_flags	       ;
    UInt32     dpme_boot_block         ;
    UInt32     dpme_boot_bytes         ;
    UInt8     *dpme_load_addr          ;
    UInt8     *dpme_load_addr_2        ;
    UInt8     *dpme_goto_addr          ;
    UInt8     *dpme_goto_addr_2        ;
    UInt32     dpme_checksum           ;
    SInt8      dpme_process_id[16]     ;
    UInt32     dpme_boot_args[32]      ;
    UInt32     dpme_reserved_3[62]     ;
};
typedef struct dpme DPME;
void noDrive(void);
int  CheckPriorFormat(FILE *);
void ListPartitions(FILE*);
void WriteBlockZero(FILE *);
void WriteSignature(FILE *, UInt32);
UInt32 WritePartitionMap(UInt32 numXPartitions, UInt32 XPartSizes[],   
                        char* XPartNames[], char* XPartTypes[], 
                        UInt32 driveSize, FILE* );
void usage(char*);

char	unixType[] = "Apple_UNIX_SRV2";
char	typeName1[] = "swap";
char	typeName2[] = "ext2";


int main(int argc,char *argv[]) {

	char		*device; /* device path */
	UInt32		dsize;  /* number of blocks in device */
	char		**etype; /* extra type names */
	char		**ename; /* extra partition names */
	UInt32		*esize; /* extra type sizes */
	UInt32		i, j, num, extraSizeTotal;
	UInt32		MFSMediaStart;
	FILE		*drive; /* the harddrive in question */
	int		checkFormat, checkPartitions;



	/* check for correct number of arguments */
	if( (argc%2) != 1 ||  argc < 3) usage(argv[0]);

	dsize = atol(argv[2]);
	checkFormat = !strcmp(argv[2],"priorFormat");
	checkPartitions = !strcmp(argv[2],"partitions");
	if(argc==3 && (checkFormat || checkPartitions)) {
		drive = fopen(argv[1], "rb");
		if(!drive) noDrive();
		if( (CheckPriorFormat(drive)==1) && checkPartitions) 
			ListPartitions(drive);

		 exit(0);
	}

	drive = fopen(argv[1], "wb"); 

	if(!drive) noDrive();
	

	if(!dsize) usage(argv[0]);

	/* read in the extra partitions */	
	num = (argc-3)/2;
	extraSizeTotal = 0;
	if(num) {
		etype = (char**)malloc(sizeof(char*)*num);
		esize = (unsigned long*)malloc(sizeof(unsigned long)*num);
		ename = (char**)malloc(sizeof(char*)*num);
		for(i=3, j=0; i<argc; i+=2,j++) {
			if(!strcmp(typeName1, argv[i]) ||
			   !strcmp(typeName2, argv[i])) {
				etype[j] = unixType;
				ename[j] = argv[i];
			} else {
				etype[j] = argv[i];
				ename[j] = "";
			}
			esize[j] = atol(argv[i+1]);
			extraSizeTotal += esize[j];
		}
	}
	/* check to see if there is enough room for the extra partitions */
	/* this is off a bit since it assumes 60 some partitions lables of 1 block each */
	if( 8256 + minMFSSize + extraSizeTotal > dsize) {
		printf("10: Error, drive is to small for selected options\n");
		printf("Done\n");
		return -2;
	}
	

	/* Write block zero */
	WriteBlockZero(drive);

	/* Write the Partition Map */
	MFSMediaStart = WritePartitionMap(num, esize, ename, etype, dsize, drive);

	/* Bless the disk */
	WriteSignature(drive, MFSMediaStart);	

	/* return gracefully */
	fclose(drive);
	printf("0: Blessing Successful\nDone\n");
	return 0;
	
}
void	noDrive() {
		printf("11: Error, couldn't open drive\n"
		       "%i\n", errno);
	 	printf("Done\n");
		exit( -11);
}



inline UInt32 byteReverse(register UInt32 *value) {

	register UInt32		output;
	
asm ("lwbrx %0,0,%1" : "=r"(output):"r"(value));
asm ("rotrwi %0,%1,16": "=r"(output):"r"(output));

return output;
}
UInt32* ByteSwap(UInt32* data, UInt32 *length) {
	/* creates a new data block with the endian 2byte reversed */
	/* try to make sure the alignment of the data block is on a 
	   4 byte boundary or this will go very slow */

	UInt32		i;
	UInt32		*dataOut, *marker;
	
	*length +=  (4-(*length%4));
	marker = dataOut = calloc(*length,1);
	for(i=0;i<*length; i+=4, data++,marker++) {
		*marker = byteReverse(data);
	}
	
	return dataOut;
	
}
void WriteSignature(FILE *drive, UInt32 MFS_Start) {

	char		*sig[] = {"Add-on media file system disk, copyright TiVo Inc. 1999.",
			  	"TiVo\6\13\143\0",
			  	"asccd"};
	UInt32		*data;
	UInt32		offsets[] = {0x07B00000, 0x1C6AA390, 0x30F9D8FC};
	UInt32		i;
	UInt32		length;
	
	for (i=0; i<3; i++ ) {
		fseek(drive,offsets[i] + MFS_Start,SEEK_SET);
		length = strlen(sig[i])+1; /* make sure the null is included */
		data = ByteSwap((UInt32*)sig[i], &length);
		fwrite(data, 1, length,  drive);
		free(data);
	}

}
void WriteBlockZero(FILE *drive) {

	
	int		i;
	unsigned long	bytesWritten;
	struct	{
	UInt16		signature;
	UInt8		pKernelPartition;
	UInt8		aKernelPartition;
	UInt8		BootParams[128];
	UInt8		Hostname[32];
	UInt32		IPaddress;
	UInt8		MACaddress[6];
	UInt8		undefined[338];
	}	diskHeader = {0};
	
	UInt32*		data;	
	
	diskHeader.signature = 0x1492;
	diskHeader.pKernelPartition = 3;
	diskHeader.aKernelPartition = 6;
	strcpy((char*)diskHeader.BootParams, (const char*)"root=/dev/hdb4");
	strcpy((char*)diskHeader.Hostname, (const char*)"unnamed");
	
	diskHeader.IPaddress = 0x0ABCDEF0;
	for(i=0;i<6;i++) diskHeader.MACaddress[i] = i+1;

	bytesWritten = sizeof(diskHeader);
	data = ByteSwap((UInt32*)&diskHeader, &bytesWritten);
	fseek(drive, 0, SEEK_SET);
	fwrite(data, 1, bytesWritten, drive);
	free(data);
	

}

void usage(char* name) {

    printf("Use the program as follows:\n"
	"%s device devicesize extratype extrasize ...\n"
	"\t where device is the drive you wish to bless,\n"
	"\t devicesize is the number of blocks the device has,\n"
	"\t extratype is the type of partition you wish to\n"
	"\t\tadd of size extrasize. Multiple partitions are allowed.\n\n"
	"Valid types recognized are %s and %s. If you\n"
	"enter something else the partition will be named\n"
	"that and will probably not work unless you know\n"
	"what you are doing.\n"
        "Note: this version doesn't format the extra partitions\n\n"
    "Version 1.0\n"
    "Copyright 2002\n"
	, name, typeName1, typeName2);
	
	exit(0);

}

int CheckPriorFormat(FILE *device) {
	/* checks the current format of the device */
	UInt32		key;
	
	fseek(device, 0, SEEK_SET);
	fread( &key, 1, sizeof(key), device); 
	if((key>>16)== 0x9214) {
		printf("1: Tivo\nDone\n");
		return 1;
	}
	if( (0x00FF&(key>>16))==0xFA) {
		printf("2: PC Linux\nDone\n");
		return 2;
	}
	if( (key&0xFFFF00FF) == 0xC033008E) {
		printf("3: DOS\nDone\n");
		return 3;
	}
	if( (key>>16)==0x4552) {
		printf("4: Mac\nDone\n");
		return 4;
	}
	printf("9: Unknown format type\n0x%X\nDone\n", key);
	return 9;
}
void ListPartitions(FILE* drive){

	DPME		*partition, *swapped;
	UInt32		i=1,numPartitions;
	UInt32		blockSize = sizeof(DPME);
	char		fmtString[] = "|%32.32s|%32.32s|%d|%d|\n";
	
	partition = calloc(1, sizeof(DPME));
	fseek(drive, 512, SEEK_SET);
	fread(partition, sizeof(DPME), 1, drive);
	swapped = (DPME*)ByteSwap((UInt32*)partition, &blockSize);
	
	numPartitions = swapped->dpme_map_entries;
	printf("12: %d Partitions\n", numPartitions+1);
	printf(fmtString, "Boot Block", "Tivo", 1, 0);
	printf(fmtString,swapped->dpme_name, swapped->dpme_type, 
		swapped->dpme_pblocks, swapped->dpme_pblock_start);	
	free(partition);
	free(swapped);
	while(i<numPartitions){
		i++;
		partition = calloc(1, sizeof(DPME));
		fseek(drive, i*512, SEEK_SET);
		fread(partition, sizeof(DPME), 1, drive);
		swapped = (DPME*)ByteSwap((UInt32*)partition, &blockSize);
		free(partition);
		printf(fmtString,swapped->dpme_name, swapped->dpme_type, 
			swapped->dpme_pblocks, swapped->dpme_pblock_start);
		free(swapped);
	}
	printf("Done, for real this time\n");
}
UInt32 WritePartitionMap(UInt32 numXPartitions, UInt32 XPartSizes[],   
                        char* XPartNames[], char* XPartTypes[], 
                        UInt32 driveSize, FILE *drive ){
 
	DPME		*partition;
	UInt32		*writable;
 	UInt32		bytesWritten;
 	UInt32		numPartitions = 3+numXPartitions;
 	UInt32		mediaSize;
 	UInt32		i;
 	UInt32		MFSMediaStart;
 	UInt32		currentBlock=1;
 	
 
 	
 	/* the partition partition */
  	bytesWritten=sizeof(DPME);	
	partition = calloc(1, sizeof(DPME));
 	partition->dpme_signature 		= 0x504D;
 	partition->dpme_map_entries 	= numPartitions;
 	partition->dpme_pblock_start	= currentBlock;
 	partition->dpme_pblocks			= 3+numXPartitions;
	strcpy((char*)partition->dpme_name, (const char*)"Apple");
	strcpy((char*)partition->dpme_type, (const char*)"Apple_partition_map"); 
	partition->dpme_lblocks = 3+numXPartitions;
	partition->dpme_flags = 0x00000033; /* valid, allocated, readable, writable */
 
 	currentBlock += (3+numXPartitions);
    writable = ByteSwap((UInt32*)partition, &bytesWritten);
	free(partition);
	fseek(drive, 512, SEEK_SET);
	fwrite(writable, bytesWritten-4,1,  drive);
	free(writable);
	
	/* the MFS App Partition */
  	bytesWritten=sizeof(DPME);	
	partition = calloc(1, sizeof(DPME));
 	partition->dpme_signature 		= 0x504D;
 	partition->dpme_map_entries 	= numPartitions;
 	partition->dpme_pblock_start	= currentBlock;
 	partition->dpme_pblocks			= 8192; /* 8192 * 0.5k = 4 Meg */
	strcpy((char*)partition->dpme_name, 
			(const char*)"Second MFS application region");
	strcpy((char*)partition->dpme_type, (const char*)"MFS"); 
	partition->dpme_lblocks = 8192;
	partition->dpme_flags = 0x00000033; /* valid, allocated, readable, writable */
 
 	currentBlock += 8192;
    writable = ByteSwap((UInt32*)partition, &bytesWritten);
	free(partition);
	fseek(drive, 2*512, SEEK_SET);
	fwrite(writable, bytesWritten-4,1,  drive);
	free(writable);

	/* the MFS media Partition */
	
	mediaSize = driveSize - currentBlock;
	for(i=0;i<numXPartitions;i++) mediaSize -= XPartSizes[i];
	
  	bytesWritten=sizeof(DPME);	
	partition = calloc(1, sizeof(DPME));
 	partition->dpme_signature 		= 0x504D;
 	partition->dpme_map_entries 	= numPartitions;
 	partition->dpme_pblock_start	= currentBlock;
 	MFSMediaStart 					= currentBlock * 512;
 	partition->dpme_pblocks			= mediaSize;
	strcpy((char*)partition->dpme_name, 
			(const char*)"Second MFS media region");
	strcpy((char*)partition->dpme_type, (const char*)"MFS"); 
	partition->dpme_lblocks = mediaSize;
	partition->dpme_flags = 0x00000033; /* valid, allocated, readable, writable */
 
    writable = ByteSwap((UInt32*)partition, &bytesWritten);
	free(partition);
	fseek(drive,3*512, SEEK_SET);
	fwrite(writable, bytesWritten-4,1,  drive);
	free(writable);
	
	/* xtra partitions */
	currentBlock += mediaSize;
	
	for(i=0;i<numXPartitions;i++) {
		bytesWritten = sizeof(DPME);
		partition = calloc(1, sizeof(DPME));
 		partition->dpme_signature 		= 0x504D;
 		partition->dpme_map_entries 	= numPartitions;
 		partition->dpme_pblock_start	= currentBlock;
 		partition->dpme_pblocks			= XPartSizes[i];
		strcpy((char*)partition->dpme_name, XPartNames[i]);
		strcpy((char*)partition->dpme_type, XPartTypes[i]); 
		partition->dpme_lblocks = XPartSizes[i];
		partition->dpme_flags = 0x00000033; /* valid, allocated, readable, writable */
 
 		currentBlock += XPartSizes[i];
	    writable = ByteSwap((UInt32*)partition, &bytesWritten);
		free(partition);
		fseek(drive, (4+i)*512, SEEK_SET);
		fwrite(writable, bytesWritten-4,1,  drive);
		free(writable);
   }
    
   return MFSMediaStart;
                        
}

