#include <stdlib.h>
#include <stdio.h>
#include <sys/xboxkrnl.h>
#include <ctype.h>
#include <fb.h>
#include <string.h>

#include "debug.h"
#include "pconfig.h"
#include "ntfile.h"

long LoadShadowROM(char *Filename, long *lFileSize)
{
	FILE *fp;
	PBYTE Buffer = 0;
	ULONGLONG FileSize;

	fp = fopen(Filename,"r");
    	if (!fp) {
		Die("Error opening bios file");
	}

	fseek(fp,0L,SEEK_END);
	FileSize = ftell(fp);
	fseek(fp, 0L, SEEK_SET);

	if(FileSize != ROM_256K && FileSize != ROM_512K && FileSize != ROM_1024K)
	{
		Die("Invalid ROM Size");
	}

	Buffer = MmAllocateContiguousMemoryEx((ULONG)SHADOW_ROM_SIZE,
			MIN_SHADOW_ROM, MAX_SHADOW_ROM, 0, PAGE_READWRITE);

	if (!Buffer) {
		Die("Error allocating memory for ROM image");
	}

	fread(Buffer, 1, FileSize, fp);

	// Copy rom to fill 1MB if necessary
	if(FileSize == ROM_256K)
		memcpy(Buffer+ROM_256K, Buffer, ROM_256K);
	if(FileSize == ROM_512K || FileSize == ROM_256K)
		memcpy(Buffer+ROM_512K, Buffer, ROM_512K);

	*lFileSize = FileSize;

	return (long)Buffer;
}

void loadbios(unsigned char *rom, CONFIGENTRY *entry) {
	long ShadowRomPos;
	long RomSize;
	int i;
	int sum = 0;

	PHYSICAL_ADDRESS PhysicalRomPos;
	PVOID EntryPoint2BL;
	ULONG Version = 0;

	ULONG val;
	PUCHAR KernelParamPtr = 0;
	PVOID CopyPtr = 0;
	PVOID RC4State = 0;
	PVOID TempPtr = 0;
	PVOID Virt2BL = 0;
	PCHAR ParsePtr = 0;

	UCHAR RC4Key[16];
	UCHAR EEPROMKey[16];

	// Load the kernel image into RAM 
	RomSize = MAX_KERNEL_SIZE;

	ShadowRomPos = LoadShadowROM(rom, &RomSize);

	// Get physical address of rom
	PhysicalRomPos = MmGetPhysicalAddress((PVOID)ShadowRomPos);

	// Allocate memory for the 2bl.  Has to be at 0x00400000
	printk("Allocate 2bl mem\n");
	Virt2BL = MmAllocateContiguousMemoryEx((ULONG) THE_2BL_SIZE,MIN_2BL, MAX_2BL, 0, PAGE_READWRITE);
	if (!Virt2BL) {
		Die("\nNo memory for 2BL!");
	}

	// Parse the RC4Key from the config file
	printk("Parsing RC4 key: %s\n",entry->szRC4Key);
	memset(RC4Key,0,16);
	ParsePtr = (PCHAR)(entry->szRC4Key);
 	for (i = 0; *ParsePtr && i < 16; i++) 
	{
		while(isspace(*ParsePtr)) 
		{
			++ParsePtr;
		}
		val = strtoul(ParsePtr, &ParsePtr, 0);
		sum += val;
		RC4Key[i] = (UCHAR)val;
	}

	CopyPtr = (PVOID)(ShadowRomPos + (SHADOW_ROM_SIZE - 0x6200));

	// If the RC4 key wasn't blank
	if (sum > 0)
	{
		printk("Decrypting 2bl\n");
		// Allocate memory for the RC4 state array
		RC4State = MmAllocateContiguousMemoryEx(0x100,MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);
		if (!RC4State) {
			Die("\nNo memory for RC4 state!");
		}

		// Decrypt the 2bl
		XcRC4Key(RC4State, 0x10, RC4Key);
		XcRC4Crypt(RC4State, 0x6000, CopyPtr);

		MmFreeContiguousMemory(RC4State);	
	}

	// Copy the 2bl to the appropriate location
	printk("Copying 2bl\n");
	memcpy(Virt2BL, CopyPtr, THE_2BL_SIZE);


		// Detect xbox version
	asm(
		"movw    $0xCF8, %%dx\n"
		"movl    $0x80000810, %%eax\n"
		"outl    %%eax, %%dx\n"
		"addb    $4, %%dl\n"
		"inl     %%dx, %%eax\n"
		"mov	 %%eax, %0\n"
		: "=m" (Version)
		: /* no input registers */
		: "%dx", "%eax", "%dl"
	);

	printk("Detecting xbox version: %08x\n", Version);

	// Pick appropriate EEprom key from config if specified
	memset(EEPROMKey,0,16);
	sum = 0;
	if (Version == 0x8001)
	{
		printk("Xbox is version 1.0\n");
		// 1.0
		if (strlen(entry->szEEPROMKey1_0) > 0)
		{
			printk("Parsing EEPROM 1.0 key: %s\n",entry->szEEPROMKey1_0);
			ParsePtr = (PCHAR)(entry->szEEPROMKey1_0);
 			for (i = 0; *ParsePtr && i < 16; i++) 
			{
				while(isspace(*ParsePtr)) 
				{
					++ParsePtr;
				}
				val = strtoul(ParsePtr, &ParsePtr, 0);
				sum += val;
				EEPROMKey[i] = (UCHAR)val;
			}
		}
	}
	else
	{
		printk("Xbox is version 1.1+\n");
		// 1.1+
		if (strlen(entry->szEEPROMKey1_1) > 0)
		{
			printk("Parsing EEPROM 1.1 key: %s\n",entry->szEEPROMKey1_1);
			ParsePtr = (PCHAR)(entry->szEEPROMKey1_1);
 			for (i = 0; *ParsePtr && i < 16; i++) 
			{
				while(isspace(*ParsePtr)) 
				{
					++ParsePtr;
				}
				val = strtoul(ParsePtr, &ParsePtr, 0);
				sum += val;
				EEPROMKey[i] = (UCHAR)val;
			}
		}
	}

	
	// Patch in the EEprom key if it was specified
	if (sum > 0)
	{
		printk("Patching EEPROM key\n");
		memcpy((PVOID)((ShadowRomPos + (SHADOW_ROM_SIZE - 0x6200)) + 0x64),EEPROMKey, 16);
		memcpy((PVOID)(Virt2BL+0x64),EEPROMKey, 16);

		for(i=0;i<16;i++)
		{
			printk("0x%02x ",EEPROMKey[i]);
		}
		printk("\n");
	}



	
	// set the kernel param string
	printk("Patching param string\n");
	KernelParamPtr = (PUCHAR)0x80400004;
	memcpy(KernelParamPtr, (PUCHAR)" /SHADOW /HDBOOT", 16);

	// Calculate the 2bl entry point
	printk("Calculating 2bl entry point\n");
	TempPtr = (PVOID)(*(PULONG)Virt2BL + 0x8036FFFC);
	EntryPoint2BL = (PVOID)(*(PULONG)TempPtr) + 0x80000000;

	printk("Calling 2bl\n");

	if (!entry->DebugFlag)
	{
		// Clear the framebuffer
		fb_clear();
	}

	// Call the 2bl
	asm(
        "call   %%eax\n" 
		: /* no output */
		: "a" (EntryPoint2BL), "c" (PhysicalRomPos)
	);

}
