/** @file

  BootServices overrides module.

  By dmazar, 26/09/2012

**/

#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>

#include "Common.h"


/** Flag is TRUE before ExitBootServices() is called. FALSE in runtime. */
BOOLEAN InBootServices = TRUE;

/** Original boot services. */
EFI_BOOT_SERVICES gOrgBS;

#if CAPTURE_CONSOLE_OUTPUT >= 1
EFI_TEXT_STRING gOrgConOutOutputString = 0;
BOOLEAN InConsolePrint = FALSE;
#endif

/** Helper function that calls GetMemoryMap() and returns new MapKey.
 * Uses gOrgBS.GetMemoryMap to avoid our log PRINT.
 */
EFI_STATUS
GetMemoryMapKey(OUT UINTN *MapKey)
{
	EFI_STATUS					Status;
	UINTN						MemoryMapSize;
	EFI_MEMORY_DESCRIPTOR		*MemoryMap;
	UINTN						DescriptorSize;
	UINT32						DescriptorVersion;
	
	Status = GetMemoryMapAlloc(gOrgBS.GetMemoryMap, &MemoryMapSize, &MemoryMap, MapKey, &DescriptorSize, &DescriptorVersion);
	return Status;
}


////////////////////////////////////////////
// 
// Boot services overrides
//

EFI_TPL EFIAPI
OvrRaiseTPL(IN EFI_TPL NewTpl)
{
	EFI_TPL				Status;
	
	Status = gOrgBS.RaiseTPL(NewTpl);
	// do not print - it's called by UEFI events and timers (from timer interrupts) many times
	//PRINT("->RaiseTPL(NewTpl=%d) = %d\n", NewTpl, Status);
	return Status;
}

VOID EFIAPI
OvrRestoreTPL(IN EFI_TPL OldTpl)
{
	gOrgBS.RestoreTPL(OldTpl);
	// do not print - it's called by UEFI events and timers (from timer interrupts) many times
	//PRINT("->RestoreTPL(OldTpl=%d)\n", OldTpl);
	return;
}

EFI_STATUS EFIAPI
OvrAllocatePages(
	IN EFI_ALLOCATE_TYPE		Type,
	IN EFI_MEMORY_TYPE			MemoryType,
	IN UINTN					NumberOfPages,
	IN OUT EFI_PHYSICAL_ADDRESS	*Memory
)
{
	EFI_STATUS			Status;
//	EFI_PHYSICAL_ADDRESS	inMemory = *Memory;
	
	Status = gOrgBS.AllocatePages(Type, MemoryType, NumberOfPages, Memory);
//	PRINT("-> AllocatePages(%s, %s, 0x%x, 0x%lx/0x%lx) = %r\n", EfiAllocateTypeDesc[Type], EfiMemoryTypeDesc[MemoryType], NumberOfPages, inMemory, *Memory, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrFreePages(
	IN EFI_PHYSICAL_ADDRESS Memory,
	IN UINTN Pages
)  
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.FreePages(Memory, Pages);
//	PRINT("->FreePages(0x%lx, 0x%x) = %r\n", Memory, Pages, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrGetMemoryMap(
	IN OUT UINTN *MemoryMapSize,
	IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,
	OUT UINTN *MapKey,
	OUT UINTN *DescriptorSize,
	OUT UINT32 *DescriptorVersion
)
{
	EFI_STATUS			Status;
//	UINTN				inMemoryMapSize = *MemoryMapSize;
	
	Status = gOrgBS.GetMemoryMap(MemoryMapSize, MemoryMap, MapKey, DescriptorSize, DescriptorVersion);
	// if print to console, then ExitBootServices will not work
//	PRINT("->GetMemoryMap(0x%x/0x%x, %p, 0x%x, 0x%x, 0x%x) = %r\n", inMemoryMapSize, *MemoryMapSize, MemoryMap, *MapKey, *DescriptorSize, *DescriptorVersion, Status);
	if (Status == EFI_SUCCESS) {
		#if PRINT_MEMORY_MAP == 1
		PrintMemMap(*MemoryMapSize, MemoryMap, *DescriptorSize, *DescriptorVersion);
		#endif
	}
	//Print(L"OvrGetMemoryMap\n");
	return Status;
}

EFI_STATUS EFIAPI
OvrAllocatePool(
	IN EFI_MEMORY_TYPE PoolType,
	IN UINTN Size,
	OUT VOID **Buffer
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.AllocatePool(PoolType, Size, Buffer);
	// printing to console requires AllocatePool - recursion, but this is solved by safety check in LogPrint
	// do not print to serial - too many calls from UEFI
	//DebugPrint(1, "->AllocatePool(%s, 0x%x, %p) = %r\n", EfiMemoryTypeDesc[PoolType], Size, *Buffer, Status);
	#if PRINT_ALLOCATE_POOL == 1
	PRINT("->AllocatePool(%s, 0x%x, %p) = %r\n", EfiMemoryTypeDesc[PoolType], Size, *Buffer, Status);
	#endif
	return Status;
}

EFI_STATUS EFIAPI
OvrFreePool(
	IN VOID				*Buffer
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.FreePool(Buffer);
	// do not print to console - requires FreePool - recursion
	// do not print to serial - too many calls from UEFI
	//DebugPrint(1, "->FreePool(%p) = %r\n", Buffer, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrCreateEvent(
	IN UINT32			Type,
	IN EFI_TPL			NotifyTpl,
	IN EFI_EVENT_NOTIFY	NotifyFunction,
	IN VOID				*NotifyContext,
	OUT EFI_EVENT		*Event
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.CreateEvent(Type, NotifyTpl, NotifyFunction, NotifyContext, Event);
	//PRINT("->CreateEvent(0x%x, 0x%x, %p, %p, %p) = %r\n", Type, NotifyTpl, NotifyFunction, NotifyContext, *Event, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrSetTimer(
	IN EFI_EVENT		Event,
	IN EFI_TIMER_DELAY	Type,
	IN UINT64			TriggerTime
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.SetTimer(Event, Type, TriggerTime);
	PRINT("->SetTimer(%p, %d, 0x%x) = %r\n", Event, Type, TriggerTime, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrWaitForEvent(
	IN UINTN		NumberOfEvents,
	IN EFI_EVENT	*Event,
	OUT UINTN		*Index
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.WaitForEvent(NumberOfEvents, Event, Index);
//	PRINT("->WaitForEvent(%d, %p, %d) = %r\n", NumberOfEvents, *Event, *Index, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrSignalEvent(
	IN EFI_EVENT		Event
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.SignalEvent(Event);
	//PRINT("->SignalEvent(%p) = %r\n", Event, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrCloseEvent(
	IN EFI_EVENT		Event
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.CloseEvent(Event);
	//PRINT("->CloseEvent(%p) = %r\n", Event, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrCheckEvent(
	IN EFI_EVENT		Event
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.CheckEvent(Event);
	//PRINT("->CheckEvent(%p) = %r\n", Event, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrInstallProtocolInterface(
	IN OUT EFI_HANDLE		*Handle,
	IN EFI_GUID				*Protocol,
	IN EFI_INTERFACE_TYPE	InterfaceType,
	IN VOID					*Interface
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.InstallProtocolInterface(Handle, Protocol, InterfaceType, Interface);
	PRINT("->InstallProtocolInterface(%p, %s, %d, %p) = %r\n", Handle, GuidStr(Protocol), InterfaceType, Interface, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrReinstallProtocolInterface(
	IN EFI_HANDLE		Handle,
	IN EFI_GUID			*Protocol,
	IN VOID				*OldInterface,
	IN VOID				*NewInterface
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.ReinstallProtocolInterface(Handle, Protocol, OldInterface, NewInterface);
	PRINT("->ReinstallProtocolInterface(%p, %s, %p, %p) = %r\n", Handle, GuidStr(Protocol), OldInterface, NewInterface, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrUninstallProtocolInterface(
	IN EFI_HANDLE		Handle,
	IN EFI_GUID			*Protocol,
	IN VOID				*Interface
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.UninstallProtocolInterface(Handle, Protocol, Interface);
	PRINT("->UninstallProtocolInterface(%p, %s, %p) = %r\n", Handle, GuidStr(Protocol), Interface, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrHandleProtocol(
	IN EFI_HANDLE		Handle,
	IN EFI_GUID			*Protocol,
	OUT VOID			**Interface
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.HandleProtocol(Handle, Protocol, Interface);
	PRINT("->HandleProtocol(%p, %s, %p) = %r\n", Handle, GuidStr(Protocol), *Interface, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrRegisterProtocolNotify(
	IN EFI_GUID			*Protocol,
	IN EFI_EVENT		Event,
	OUT VOID			**Registration
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.RegisterProtocolNotify(Protocol, Event, Registration);
	PRINT("->RegisterProtocolNotify(%s, %p, %p) = %r\n", GuidStr(Protocol), Event, *Registration, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrLocateHandle(
	IN EFI_LOCATE_SEARCH_TYPE	SearchType,
	IN EFI_GUID					*Protocol,
	IN VOID						*SearchKey,
	IN OUT UINTN				*BufferSize,
	OUT EFI_HANDLE				*Buffer
)
{
	EFI_STATUS			Status;
	UINTN				BufferSizeIn = *BufferSize;
	
	Status = gOrgBS.LocateHandle(SearchType, Protocol, SearchKey, BufferSize, Buffer);
	PRINT("->LocateHandle(%d, %s, %p, 0x%x/0x%x, %p) = %r\n", SearchType, GuidStr(Protocol), SearchKey, BufferSizeIn, *BufferSize, Buffer, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrLocateDevicePath(
	IN EFI_GUID						*Protocol,
	IN OUT EFI_DEVICE_PATH_PROTOCOL	**DevicePath,
	OUT EFI_HANDLE					*Device
)
{
	EFI_STATUS					Status;
	EFI_DEVICE_PATH_PROTOCOL	*DevicePathIn = *DevicePath;
//	PRINT("... try to do LocateDevicePath\n");
	Status = gOrgBS.LocateDevicePath(Protocol, DevicePath, Device);
	// TODO: device path to str
	PRINT("->LocateDevicePath(%s, %p/%p, %p) = %r\n", GuidStr(Protocol), DevicePathIn, DevicePath, Device, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrInstallConfigurationTable(
	IN EFI_GUID					*Guid,
	IN VOID						*Table
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.InstallConfigurationTable(Guid, Table);
	// TODO: table guids to Lib.c
	PRINT("->InstallConfigurationTable(%s, %p) = %r\n", GuidStr(Guid), Table, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrLoadImage(
	IN BOOLEAN					BootPolicy,
	IN EFI_HANDLE				ParentImageHandle,
	IN EFI_DEVICE_PATH_PROTOCOL	*DevicePath,
	IN VOID						*SourceBuffer,
	IN UINTN					SourceSize,
	OUT EFI_HANDLE				*ImageHandle
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.LoadImage(BootPolicy, ParentImageHandle, DevicePath, SourceBuffer, SourceSize, ImageHandle);
	// TODO: dev path to str
	PRINT("->LoadImage(%c, %p, %p, %p, 0x%x, %p) = %r\n", BootPolicy ? L'T' : L'F', ParentImageHandle, DevicePath, SourceBuffer, SourceSize, ImageHandle, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrStartImage(
	IN EFI_HANDLE				ImageHandle,
	OUT UINTN					*ExitDataSize,
	OUT CHAR16					**ExitData
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.StartImage(ImageHandle, ExitDataSize, ExitData);
	PRINT("->StartImage(%p, 0x%x, %p) = %r\n", ImageHandle, *ExitDataSize, *ExitData, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrExit(
	IN EFI_HANDLE				ImageHandle,
	IN EFI_STATUS				ExitStatus,
	IN UINTN					ExitDataSize,
	IN CHAR16					*ExitData
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.Exit(ImageHandle, ExitStatus, ExitDataSize, ExitData);
	PRINT("->Exit(%p, %r, 0x%x, %s) = %r\n", ImageHandle, ExitStatus, ExitDataSize, ExitData, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrUnloadImage(
	IN EFI_HANDLE				ImageHandle
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.UnloadImage(ImageHandle);
	PRINT("->UnloadImage(%p) = %r\n", ImageHandle, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrExitBootServices(
	IN EFI_HANDLE				ImageHandle,
	IN UINTN					MapKey
)
{
	EFI_STATUS					Status;
	UINTN					 	NewMapKey;
	
	PRINT("ExitBootServices called. Doing some more dumps ...\n");
	
	#if PRINT_DUMPS >= 1
	// Print ST
	PrintSystemTable(gST);
	
	// Print RT vars
	PrintRTVariables(&gOrgRS);
	#endif
	
	// Restore RT services if we should not log calls during runtime
	#if WORK_DURING_RUNTIME == 0
	RestoreRuntimeServices(gRT);
	#endif
	
	PRINT("->ExitBootServices(%p, 0x%x) ...\n", ImageHandle, MapKey);
	
	// Set flag to FALSE to stop some loggers from messing with memory
	InBootServices = FALSE;
	
	// Restore original OutputString
	#if CAPTURE_CONSOLE_OUTPUT >= 1
	gST->ConOut->OutputString = gOrgConOutOutputString;
	#endif
	
	// Notify loggers that boot services are over
	// Saving our log file can cause a vast amount of logging output on some firmwares, so do this after stopping loggers.
	LogOnExitBootServices();
	
	// Call original
	Status = gOrgBS.ExitBootServices(ImageHandle, MapKey);
	
	if (Status == EFI_SUCCESS) {
	
		PRINT("... ExitBootServices = %r\n", Status);
		
	} else {
		//
		// Error exiting boot services. Probably because
		// some of our loggers changed memory map state.
		// We'll just force exiting by obtaining new
		// MapKey and calling ExitBootServices again.
		//
		PRINT("... ExitBootServices = %r\n", Status);
		PRINT("Forcing ExitBootServices ...\n");
		
		Status = GetMemoryMapKey(&NewMapKey);
		if (Status == EFI_SUCCESS) {
			// we have latest mem map and NewMapKey
			// we'll try again ExitBootServices with NewMapKey
			Status = gOrgBS.ExitBootServices(ImageHandle, NewMapKey);
			PRINT("ExitBootServices: 2nd try = %r\n", Status);
		} else {
			PRINT("ERROR obtaining new map key: %r\n", Status);
			Status = EFI_INVALID_PARAMETER;
		}
	}
	
	return Status;
}

EFI_STATUS EFIAPI
OvrGetNextMonotonicCount(
	OUT UINT64					*Count
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.GetNextMonotonicCount(Count);
	PRINT("->GetNextMonotonicCount(0x%x) = %r\n", *Count, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrStall(
	IN UINTN					Microseconds
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.Stall(Microseconds);
	// do not print - too many calls
	//PRINT("->Stall(%d) = %r\n", Microseconds, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrSetWatchdogTimer(
	IN UINTN					Timeout,
	IN UINT64					WatchdogCode,
	IN UINTN					DataSize,
	IN CHAR16					*WatchdogData
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.SetWatchdogTimer(Timeout, WatchdogCode, DataSize, WatchdogData);
	PRINT("->SetWatchdogTimer(%d, 0x%x, %d, %s) = %r\n", Timeout, WatchdogCode, DataSize, WatchdogData, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrConnectController(
	IN EFI_HANDLE				ControllerHandle,
	IN EFI_HANDLE				*DriverImageHandle,
	IN EFI_DEVICE_PATH_PROTOCOL	*RemainingDevicePath,
	IN BOOLEAN					Recursive
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.ConnectController(ControllerHandle, DriverImageHandle, RemainingDevicePath, Recursive);
	// TODO: dev path to str
	PRINT("->ConnectController(%p, %p, %p, %c) = %r\n", ControllerHandle, DriverImageHandle, RemainingDevicePath, Recursive ? L'T' : L'F', Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrDisconnectController(
	IN EFI_HANDLE				ControllerHandle,
	IN EFI_HANDLE				DriverImageHandle,
	IN EFI_HANDLE				ChildHandle
)
{
	EFI_STATUS					Status;
	
	Status = gOrgBS.DisconnectController(ControllerHandle, DriverImageHandle, ChildHandle);
	PRINT("->DisconnectController(%p, %p, %p) = %r\n", ControllerHandle, DriverImageHandle, ChildHandle, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrOpenProtocol(
	IN EFI_HANDLE Handle,
	IN EFI_GUID *Protocol,
	OUT VOID **Interface,
	IN EFI_HANDLE AgentHandle,
	IN EFI_HANDLE ControllerHandle,
	IN UINT32 Attributes
)
{
	EFI_STATUS			Status;
	VOID				*InterfaceIn = *Interface;
	
	Status = gOrgBS.OpenProtocol(Handle, Protocol, Interface, AgentHandle, ControllerHandle, Attributes);
	PRINT("->OpenProtocol(%p, %s, %p/%p, %p, %p, %x) = %r\n", Handle, GuidStr(Protocol), InterfaceIn, *Interface, AgentHandle, ControllerHandle, Attributes, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrCloseProtocol(
	IN EFI_HANDLE		Handle,
	IN EFI_GUID			*Protocol,
	IN EFI_HANDLE		AgentHandle,
	IN EFI_HANDLE		ControllerHandle
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.CloseProtocol(Handle, Protocol, AgentHandle, ControllerHandle);
	PRINT("->CloseProtocol(%p, %s, %p, %p) = %r\n", Handle, GuidStr(Protocol), AgentHandle, ControllerHandle, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrOpenProtocolInformation(
	IN EFI_HANDLE		Handle,
	IN EFI_GUID			*Protocol,
	OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY	**EntryBuffer,
	OUT UINTN			*EntryCount
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.OpenProtocolInformation(Handle, Protocol, EntryBuffer, EntryCount);
	PRINT("->OpenProtocolInformation(%p, %s, %p, %d) = %r\n", Handle, GuidStr(Protocol), *EntryBuffer, *EntryCount, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrProtocolsPerHandle(
	IN EFI_HANDLE		Handle,
	OUT EFI_GUID		***ProtocolBuffer,
	OUT UINTN			*ProtocolBufferCount
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.ProtocolsPerHandle(Handle, ProtocolBuffer, ProtocolBufferCount);
	// TODO: print list of protocols returned
	PRINT("->ProtocolsPerHandle(%p, %p, %d) = %r\n", Handle, *ProtocolBuffer, *ProtocolBufferCount, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrLocateHandleBuffer(
	IN EFI_LOCATE_SEARCH_TYPE	SearchType,
	IN EFI_GUID					*Protocol,
	IN VOID						*SearchKey,
	IN OUT UINTN				*NoHandles,
	OUT EFI_HANDLE				**Buffer
)
{
	EFI_STATUS			Status;
  STATIC UINTN OldBuffer = 0;
	
	Status = gOrgBS.LocateHandleBuffer(SearchType, Protocol, SearchKey, NoHandles, Buffer);
  if (!CompareGuid(Protocol, &mEfiSimplePointerProtocolGuid) || (UINTN)Buffer != OldBuffer) {
    OldBuffer = (UINTN)Buffer;
    PRINT("->LocateHandleBuffer(%s, %s, %p, %d, %p) = %r\n", EfiLocateSearchType[SearchType], GuidStr(Protocol), SearchKey, *NoHandles, *Buffer, Status);
    
  }
	return Status;
}

EFI_STATUS EFIAPI
OvrLocateProtocol(
	IN EFI_GUID			*Protocol,
	IN VOID				*Registration,
	OUT VOID			**Interface
)
{
	EFI_STATUS			Status;
	VOID				*InterfaceIn = *Interface;
	
	Status = gOrgBS.LocateProtocol(Protocol, Registration, Interface);
	PRINT("->LocateProtocol(%s, %p, %p/%p) = %r\n", GuidStr(Protocol), Registration, InterfaceIn, *Interface, Status);
	return Status;
}

EFI_STATUS EFIAPI
OvrInstallMultipleProtocolInterfaces(
	IN OUT EFI_HANDLE	*Handle,
	...
)
{
	EFI_STATUS			Status;
	EFI_HANDLE			HandleIn = *Handle;
	VA_LIST				Args;
	EFI_GUID			*Protocol[4];
	VOID				*Interface[4];
	UINTN				Index;
	
	// oh my, va args ... well, we'll just have to support constant number
	VA_START(Args, Handle);
	// will start with Index=1 to cover the case when no Protocol/Interface is specified at all
	for (Index = 1; Index < 4; Index++) {
		Protocol[Index] = VA_ARG(Args, EFI_GUID *);
		if (Protocol[Index] == NULL) {
			Index--;
			break;
		}
		Interface[Index] = VA_ARG(Args, VOID *);
	}
	VA_END(Args);
	switch (Index) {
	case 0:
		Status = gOrgBS.InstallMultipleProtocolInterfaces(Handle, NULL);
		PRINT("->InstallMultipleProtocolInterfaces(%p/%p) = %r\n", HandleIn, *Handle, Status);
		break;
		
	case 1:
		Status = gOrgBS.InstallMultipleProtocolInterfaces(Handle, Protocol[1], Interface[1], NULL);
		PRINT("->InstallMultipleProtocolInterfaces(%p/%p, %s, %p) = %r\n",
			HandleIn, *Handle,
			GuidStr(Protocol[1]), Interface[1],
			Status);
		break;
		
	case 2:
		Status = gOrgBS.InstallMultipleProtocolInterfaces(Handle, Protocol[1], Interface[1], Protocol[2], Interface[2], NULL);
		PRINT("->InstallMultipleProtocolInterfaces(%p/%p, %s, %p, %s, %p) = %r\n",
			HandleIn, *Handle,
			GuidStr(Protocol[1]), Interface[1],
			GuidStr(Protocol[2]), Interface[2],
			Status);
		break;
		
	case 3:
		Status = gOrgBS.InstallMultipleProtocolInterfaces(Handle, Protocol[1], Interface[1], Protocol[2], Interface[2], Protocol[3], Interface[3], NULL);
		PRINT("->InstallMultipleProtocolInterfaces(%p/%p, %s, %p, %s, %p, %s, %p) = %r\n",
			HandleIn, *Handle,
			GuidStr(Protocol[1]), Interface[1],
			GuidStr(Protocol[2]), Interface[2],
			GuidStr(Protocol[3]), Interface[3],
			Status);
		break;
	
	default:
		Status = EFI_UNSUPPORTED;
		PRINT("->InstallMultipleProtocolInterfaces(%p, ...) = %r, too many Protocol/Interface pairs\n", *Handle, Status);
		break;
	}
	return Status;
}

EFI_STATUS EFIAPI
OvrUninstallMultipleProtocolInterfaces(
	IN EFI_HANDLE		Handle,
	...
)
{
	EFI_STATUS			Status;
	EFI_HANDLE			HandleIn = Handle;
	VA_LIST				Args;
	EFI_GUID			*Protocol[4];
	VOID				*Interface[4];
	UINTN				Index;
	
	// again, va args ... we'll just have to support constant number
	VA_START(Args, Handle);
	// will start with Index=1 to cover the case when no Protocol/Interface is specified at all
	for (Index = 1; Index < 4; Index++) {
		Protocol[Index] = VA_ARG(Args, EFI_GUID *);
		if (Protocol[Index] == NULL) {
			Index--;
			break;
		}
		Interface[Index] = VA_ARG(Args, VOID *);
	}
	VA_END(Args);
	switch (Index) {
	case 0:
		Status = gOrgBS.UninstallMultipleProtocolInterfaces(Handle);
		PRINT("->UninstallMultipleProtocolInterfaces(%p/%p) = %r\n", HandleIn, Handle, Status);
		break;
		
	case 1:
		Status = gOrgBS.UninstallMultipleProtocolInterfaces(Handle, Protocol[1], Interface[1]);
		PRINT("->UninstallMultipleProtocolInterfaces(%p/%p, %s, %p) = %r\n",
			HandleIn, Handle,
			GuidStr(Protocol[1]), Interface[1],
			Status);
		break;
		
	case 2:
		Status = gOrgBS.UninstallMultipleProtocolInterfaces(Handle, Protocol[1], Interface[1], Protocol[2], Interface[2]);
		PRINT("->UninstallMultipleProtocolInterfaces(%p/%p, %s, %p, %s, %p) = %r\n",
			HandleIn, Handle,
			GuidStr(Protocol[1]), Interface[1],
			GuidStr(Protocol[2]), Interface[2],
			Status);
		break;
		
	case 3:
		Status = gOrgBS.UninstallMultipleProtocolInterfaces(Handle, Protocol[1], Interface[1], Protocol[2], Interface[2], Protocol[3], Interface[3]);
		PRINT("->UninstallMultipleProtocolInterfaces(%p/%p, %s, %p, %s, %p, %s, %p) = %r\n",
			HandleIn, Handle,
			GuidStr(Protocol[1]), Interface[1],
			GuidStr(Protocol[2]), Interface[2],
			GuidStr(Protocol[3]), Interface[3],
			Status);
		break;
	
	default:
		Status = EFI_UNSUPPORTED;
		PRINT("->UninstallMultipleProtocolInterfaces(%p, ...) = %r, too many Protocol/Interface pairs\n", Handle, Status);
		break;
	}
	return Status;
}

EFI_STATUS EFIAPI
OvrCalculateCrc32(
	IN VOID				*Data,
	IN UINTN			DataSize,
	OUT UINT32			*Crc32
)
{
	EFI_STATUS			Status;
	Status = gOrgBS.CalculateCrc32(Data, DataSize, Crc32);
	
	// Omit printing this when using append while logging, as it can end up in calling a file operating inside another file operation
	// (some implementations of File functions use CalculateCrc32)
	#if (LOG_TO_FILE <= 2) && (CLEANER_LOG != 1)
	// Better not print this for cleaner logs - reported by XyZ
	PRINT("->CalculateCrc32(%p, %d, 0x%x) = %r\n", Data, DataSize, Crc32, Status);
	#endif
	return Status;
}

VOID EFIAPI
OvrCopyMem(
	IN VOID				*Destination,
	IN VOID				*Source,
	IN UINTN			Length
)
{
	gOrgBS.CopyMem(Destination, Source, Length);
	//PRINT("->CopyMem(%p, %p, 0x%x)\n", Destination, Source, Length);
	return;
}

VOID EFIAPI
OvrSetMem(
	IN VOID				*Buffer,
	IN UINTN			Size,
	IN UINT8			Value
)
{
	gOrgBS.SetMem(Buffer, Size, Value);
	//PRINT("->SetMem(%p, 0x%x, 0x%x)\n", Buffer, Size, Value);
	return;
}

EFI_STATUS EFIAPI
OvrCreateEventEx(
	IN UINT32			Type,
	IN EFI_TPL			NotifyTpl,
	IN EFI_EVENT_NOTIFY	NotifyFunction,
	IN CONST VOID		*NotifyContext,
	IN CONST EFI_GUID	*EventGroup,
	OUT EFI_EVENT		*Event
)
{
	EFI_STATUS			Status;
	
	Status = gOrgBS.CreateEventEx(Type, NotifyTpl, NotifyFunction, NotifyContext, EventGroup, Event);
//	PRINT("->CreateEventEx(0x%x, 0x%x, %p, %p, %g, %p) = %r\n", Type, NotifyTpl, NotifyFunction, NotifyContext, EventGroup, *Event, Status);
	return Status;
}


////////////////////////////////////////////
//
// Other functions
//

#if CAPTURE_CONSOLE_OUTPUT >= 1
EFI_STATUS EFIAPI
OvrConOutOutputString(IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, IN CHAR16 *String) {
	EFI_STATUS			Status;
	
	#if CAPTURE_CONSOLE_OUTPUT <= 1
	Status = gOrgConOutOutputString(This,String);
	#else
	Status = EFI_SUCCESS;
	#endif
	
	// Print to file only if not invoked from our PRINT()
	if (!InConsolePrint) {
		// Set var to avoid printing to screen again when overriding OutputString
		InConsolePrint = TRUE;
		PRINT("%s",String);
		InConsolePrint = FALSE;
	}
	
	return Status;
}
#endif

/** Installs our boot services overrides. */
EFI_STATUS EFIAPI
OvrBootServices(EFI_BOOT_SERVICES	*BS)
{
	
	PRINT("Overriding boot services ...\n");
	
	// store orig BS
	CopyMem(&gOrgBS, BS, sizeof(EFI_BOOT_SERVICES));
	
	BS->RaiseTPL = OvrRaiseTPL;
	BS->RestoreTPL = OvrRestoreTPL;
	
	BS->AllocatePages = OvrAllocatePages;
	BS->FreePages = OvrFreePages;
	BS->GetMemoryMap = OvrGetMemoryMap;
	BS->AllocatePool = OvrAllocatePool;
	BS->FreePool = OvrFreePool;
	
	BS->CreateEvent = OvrCreateEvent;
	BS->SetTimer = OvrSetTimer;
	BS->WaitForEvent = OvrWaitForEvent;
	BS->SignalEvent = OvrSignalEvent;
	BS->CloseEvent = OvrCloseEvent;
	BS->CheckEvent = OvrCheckEvent;
	
	BS->InstallProtocolInterface = OvrInstallProtocolInterface;
	BS->ReinstallProtocolInterface = OvrReinstallProtocolInterface;
	BS->UninstallProtocolInterface = OvrUninstallProtocolInterface;

	BS->HandleProtocol = OvrHandleProtocol;
	BS->RegisterProtocolNotify = OvrRegisterProtocolNotify;
	BS->LocateHandle = OvrLocateHandle;
	BS->LocateDevicePath = OvrLocateDevicePath;

	BS->InstallConfigurationTable = OvrInstallConfigurationTable;

	BS->LoadImage = OvrLoadImage;
	BS->StartImage = OvrStartImage;
	BS->Exit = OvrExit;
	BS->UnloadImage = OvrUnloadImage;
	
	BS->ExitBootServices = OvrExitBootServices;
	BS->GetNextMonotonicCount = OvrGetNextMonotonicCount;
	BS->Stall = OvrStall;
	BS->SetWatchdogTimer = OvrSetWatchdogTimer;
	
	BS->ConnectController = OvrConnectController;
	BS->DisconnectController = OvrDisconnectController;

	BS->OpenProtocol = OvrOpenProtocol;
	BS->CloseProtocol = OvrCloseProtocol;
	BS->OpenProtocolInformation = OvrOpenProtocolInformation;
	BS->ProtocolsPerHandle = OvrProtocolsPerHandle;
	BS->LocateHandleBuffer = OvrLocateHandleBuffer;
	BS->LocateProtocol = OvrLocateProtocol;
	BS->InstallMultipleProtocolInterfaces = OvrInstallMultipleProtocolInterfaces;
	BS->UninstallMultipleProtocolInterfaces = OvrUninstallMultipleProtocolInterfaces;
	
	BS->CalculateCrc32 = OvrCalculateCrc32;
	BS->CopyMem = OvrCopyMem;
	BS->SetMem = OvrSetMem;
	BS->CreateEventEx = OvrCreateEventEx;
	
	BS->Hdr.CRC32 = 0;
	// use orig function to avoid our PRINT
	gOrgBS.CalculateCrc32(BS, BS->Hdr.HeaderSize, &BS->Hdr.CRC32);
	PRINT("Boot services overriden!\n");
	
	#if CAPTURE_CONSOLE_OUTPUT >= 1
	PRINT("Overriding console output ...\n");
	#if CAPTURE_CONSOLE_OUTPUT == 2
	PRINT("Preventing any further console output, please wait until booting starts ...\n");
	#endif
	gOrgConOutOutputString = gST->ConOut->OutputString;
	gST->ConOut->OutputString = OvrConOutOutputString;
	PRINT("Console output overriden!\n");
	#endif
	
	return EFI_SUCCESS;
}

