/*
   Xbox XBE bootloader

    Original code by Michael Steil & anonymous
    VESA Framebuffer code by Milosch Meriac
	Modified to boot shadow bioses by Phoenix

	Note that this is a very modified version, not created
	nor endorsed by the xbox-linux team.
*/

#include "printf.h"
#include "consts.h"
#include "xboxkrnl.h"
#include "xbox.h"
#include "boot.h"
#include "BootString.h"
#include "BootParser.h"
#include "BootVideo.h"
#include "config.h"
#include "ntfile.h"

extern void DoIntro();
unsigned char DebugFlag;

int cx, cy;
unsigned int* framebuffer;

volatile CURRENT_VIDEO_MODE_DETAILS currentvideomodedetails;

// Useful for debugging
void die() {
	while(1);
}

long LoadShadowROM(PVOID Filename, long *lFileSize)
{
	HANDLE hFile;
	PBYTE Buffer = 0;
	ULONGLONG FileSize;

    if (!(hFile = OpenFile(NULL, Filename, -1, FILE_NON_DIRECTORY_FILE))) {
		dprintf("Error open file %s\n",Filename);
		die();
	}

	if(!GetFileSize(hFile,&FileSize)) {
		dprintf("Error getting file size %s\n",Filename);
		die();
	}

	if(FileSize != ROM_256K && FileSize != ROM_512K && FileSize != ROM_1024K)
	{
		dprintf("Invalid ROM Size %s\n",Filename);
		die();
	}

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

	if (!Buffer) {
		dprintf("Error alloc memory for File %s\n",Filename);
		die();
	}

	if (!ReadFile(hFile, Buffer, FileSize)) {
		dprintf("Error loading file %s\n",Filename);
		die();
	}

	// Copy rom to fill 1MB if necessary
	switch (FileSize)
	{
		case ROM_256K:
			memcpy(Buffer+ROM_256K, Buffer, ROM_256K);
			// fall through
		case ROM_512K:
			memcpy(Buffer+ROM_512K, Buffer, ROM_512K);
			break;
		default:
			// No need to fill
			break;
	}

	NtClose(hFile);

	*lFileSize = FileSize;

	return (long)Buffer;
}

int CreateLock() 
{
	int rv = 0;

	PCHAR path;
	PCHAR filename;
	
	PCHAR message = "Delete this file to see the intro again\n";

    path = (char *)MmAllocateContiguousMemoryEx(BUFFERSIZE,MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);
    filename = (char *)MmAllocateContiguousMemoryEx(BUFFERSIZE,MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);
	
	memset(path,0,BUFFERSIZE);
	/* get the directory of the bootloader executable */
	HelpCopyUntil(path, XeImageFileName->Buffer, XeImageFileName->Length);
	HelpStrrchr(path, '\\')[1] = 0;
	/* read the config file from there */
	HelpCopyUntil(filename, path, BUFFERSIZE);
	HelpCopyUntil(HelpScan0(filename), "introflag.lock", BUFFERSIZE);

	rv = SaveFile(filename,message,HelpStrlen(message));
	
	MmFreeContiguousMemory(path);	
	MmFreeContiguousMemory(filename);

	return rv;
}

void FadeStep(PUCHAR Buffer, long BufSize, int amount)
{
	PUCHAR Ptr = Buffer;
	int i;

	for (i=0;i<BufSize;i++)
	{
		if (*Ptr + amount < 0)
		{
			*Ptr = 0;
		}
		else if (*Ptr + amount > 255)
		{
			*Ptr = 255;
		}
		else
		{
			*Ptr += amount;
		}
		Ptr++;
	}
}

void DoSplash( PVOID Framebuffer )
{
	HANDLE hFile;
	ULONGLONG FileSize;

	PUCHAR Image = 0;
	PUCHAR FadeBuf = 0;
	PUCHAR FileImage = 0;
	PUCHAR TempPtr = 0;

	PCHAR filename;
	PCHAR path;
	
	int i;
//	int j;

	// 32 Bit
	Image = (PVOID)MmAllocateContiguousMemoryEx(640*480*4, MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);
	FadeBuf = (PVOID)MmAllocateContiguousMemoryEx(640*480*4, MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);
	// 24 Bit
	FileImage = (PVOID)MmAllocateContiguousMemoryEx(640*480*3, MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);

	// Get full path to file
	path = (char *)MmAllocateContiguousMemoryEx(BUFFERSIZE,MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);
    filename = (char *)MmAllocateContiguousMemoryEx(BUFFERSIZE,MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);
	
	memset(path,0,BUFFERSIZE);
	HelpCopyUntil(path, XeImageFileName->Buffer, XeImageFileName->Length);
	HelpStrrchr(path, '\\')[1] = 0;

	HelpCopyUntil(filename, path, BUFFERSIZE);
	HelpCopyUntil(HelpScan0(filename), "phoenix.raw", BUFFERSIZE);

    if (!(hFile = OpenFile(NULL, filename, -1, FILE_NON_DIRECTORY_FILE))) {
		dprintf("Error open file %s\n",filename);
		die();
	}

	if(!GetFileSize(hFile,&FileSize)) {
		dprintf("Error getting file size %s\n",filename);
		die();
	}

	if(FileSize != 640*480*3)
	{
		dprintf("Image has wrong dimensions or depth.\n");
		die();
	}

	if (!ReadFile(hFile, FileImage, FileSize)) {
		dprintf("Error loading file %s\n",filename);
		die();
	}

	// Convert 24->32 bit depth
	TempPtr = Image;

	for (i=0;i<640*480*3;i+=3)
	{
		*TempPtr = *(FileImage+2);
		*(TempPtr+1) = *(FileImage+1);
		*(TempPtr+2) = *(FileImage);
		TempPtr += 4;
		FileImage += 3;
	}

	// Fade in at 4x
	memcpy(FadeBuf, Image, 640*480*4);
	for (i=-64;i<-12;i++)
	{
		memcpy(FadeBuf, Image, 640*480*4);
		FadeStep(FadeBuf,640*480*4,i*4);
		memcpy(Framebuffer, FadeBuf, 640*480*4);
	}

	// Fade out at 2x starting where we left off
	memcpy(FadeBuf, Framebuffer, 640*480*4);
	for (i=0;i>-128;i--)
	{
		FadeStep(FadeBuf,640*480*4,-2);
		memcpy(Framebuffer, FadeBuf, 640*480*4);
	}

	MmFreeContiguousMemory(path);	
	MmFreeContiguousMemory(filename);
	MmFreeContiguousMemory(FadeBuf);	
	MmFreeContiguousMemory(Image);
	MmFreeContiguousMemory(FileImage);
}

// Sets the led color based on the sequence from the config file.
// 'r' = red
// 'g' = green
// 'o' = orange
// 'x' = off
// This code is derived directly from the blink.c code
// (C) 2002-11-11 Georg Lukas <georg@boerde.de>
void setLED(PUCHAR LedSequence)
{
	int reg, r, g;

	r = g = 0;
	while (*LedSequence) {
		r *= 2;
		g *= 2;
		switch (*LedSequence) {
		case 'r' : r++; break;
		case 'g' : g++; break;
		case 'o' : r++; g++; break;
		}
		LedSequence++;
	}
	//printf("collected flags: r=%02x g=%02x\n", r, g);
	reg = ((r<<4) & 0xF0) + (g & 0xF);

	HalWriteSMBusValue(0x20, 0x08, FALSE, reg);
	HalWriteSMBusValue(0x20, 0x07, FALSE, 0x01);
}

PHYSICAL_ADDRESS PhysicalRomPos;
PVOID EntryPoint2BL;
ULONG Version;
NTSTATUS GetConfig(CONFIGENTRY *entry);

void boot() {

	long ShadowRomPos;
	long RomSize;
	int i;
	int sum = 0;

	ULONG val;
	NTSTATUS Error;
	PUCHAR KernelParamPtr = 0;
	PVOID CopyPtr = 0;
	PVOID RC4State = 0;
	PVOID TempPtr = 0;
	PVOID Virt2BL = 0;
	PCHAR ParsePtr = 0;
	UCHAR state = 0;
	CONFIGENTRY entry;

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

	currentvideomodedetails.m_nVideoModeIndex=VIDEO_MODE_640x480;
    currentvideomodedetails.m_pbBaseAddressVideo=(BYTE *)0xfd000000;
	currentvideomodedetails.m_dwFrameBufferStart = FRAMEBUFFER_START;

    BootVgaInitializationKernelNG((CURRENT_VIDEO_MODE_DETAILS *)&currentvideomodedetails);
	
	framebuffer = (unsigned int*)(0xF0000000+*(unsigned int*)0xFD600800);
	memset(framebuffer,0,640*480*4);

	memset(&entry,0,sizeof(CONFIGENTRY));
	cx = 0;
	cy = 0;

	// parse the configuration file                         
	Error = GetConfig(&entry);

	dprintf("Phoenix Bios Loader v1.3\n");
	dprintf("Official Forums: http://forums.maxconsole.com\n\n");

	if (!NT_SUCCESS(Error)) 
	{
		dprintf("Error reading config!\n");
		die();
	}

	// If led is specified, set it
	if (HelpStrlen(entry.szLED) > 0)
	{
		dprintf("Setting led: %s\n", entry.szLED);
		setLED(entry.szLED);
	}
	
	// Only do intro the 1st time
	if (!DebugFlag)
	{
		if (CreateLock())
		{
			DoIntro(framebuffer);
		}
		else
		{
			DoSplash(framebuffer);
		}
	}
	// Load the kernel image into RAM 
	RomSize = MAX_KERNEL_SIZE;

	// Check Tray State and load appropriate bios
	HalReadSMBusValue(0x20, 0x03, 0, &state);
	if (state == 0x10 && HelpStrlen(entry.szAltRom) > 0)
	{
		dprintf("Loading Alternate Rom: %s\n", entry.szAltRom);
		ShadowRomPos = LoadShadowROM(entry.szAltRom, &RomSize);	
	} 
	else 
	{
		dprintf("Loading Rom: %s\n", entry.szRom);
		ShadowRomPos = LoadShadowROM(entry.szRom, &RomSize);
	}

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

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

	// Parse the RC4Key from the config file
	dprintf("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)
	{
		dprintf("Decrypting 2bl\n");
		// Allocate memory for the RC4 state array
		RC4State = MmAllocateContiguousMemoryEx(0x100,MIN_KERNEL, MAX_KERNEL, 0, PAGE_READWRITE);
		if (!RC4State) {
			dprintf("\nNo memory for RC4 state!\n");
			die();
		}

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

		MmFreeContiguousMemory(RC4State);	
	}

	// Copy the 2bl to the appropriate location
	dprintf("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, Version\n"
	);

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

	// Pick appropriate EEprom key from config if specified
	memset(EEPROMKey,0,16);
	sum = 0;
	if (Version == 0x8001)
	{
		dprintf("Xbox is version 1.0\n");
		// 1.0
		if (HelpStrlen(entry.szEEPROMKey1_0) > 0)
		{
			dprintf("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
	{
		dprintf("Xbox is version 1.1+\n");
		// 1.1+
		if (HelpStrlen(entry.szEEPROMKey1_1) > 0)
		{
			dprintf("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)
	{
		dprintf("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++)
		{
			dprintf("0x%02x ",EEPROMKey[i]);
		}
		dprintf("\n");
	}

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

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

	dprintf("Calling 2bl\n");

	if (!DebugFlag)
	{
		// Clear the framebuffer
		memset(framebuffer,0,640*480*4);
	}

	// Call the 2bl
	__asm(
		"mov	PhysicalRomPos, %ecx\n"
		"mov	EntryPoint2BL, %eax\n"
        "call   %eax\n" 
	);

}

