/** @file

Module Name:

  FSInject.c

  FSInject driver - Replaces EFI_SIMPLE_FILE_SYSTEM_PROTOCOL on target volume
  and injects content of specified source folder on source (injection) volume
  into target folder in target volume.

  initial version - dmazar

**/

#include <Library/BaseLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Library/PrintLib.h>
#include <Library/MemLogLib.h>

#include <Protocol/SimpleFileSystem.h>

#include <Guid/GlobalVariable.h>
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>
#include <Guid/FileSystemVolumeLabelInfo.h>

#include <Protocol/FSInjectProtocol.h>

#include "FSInject.h"

//#include "../Version.h"
//CONST CHAR8* CloverRevision = REVISION_STR;


// DBG_TO: 0=no debug, 1=serial, 2=console
// serial requires
// [PcdsFixedAtBuild]
//  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x07
//  gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0xFFFFFFFF
// in package DSC file
#define DBG_TO 3

#if DBG_TO == 3
#define DBG(...) MemLog(FALSE, 0, __VA_ARGS__)
#elif DBG_TO == 2
#define DBG(...) AsciiPrint(__VA_ARGS__)
#elif DBG_TO == 1
#define DBG(...) DebugPrint(1, __VA_ARGS__)
#else
#define DBG(...)
#endif


#define TEST 0
#if TEST
#include "Test.h"
#endif

#define EfiGuidStrMapLen 4
/** Map of known guids and friendly names. Searchable with GuidStr() */
struct {
	EFI_GUID 	*Guid;
	CHAR16		*Str;
} EfiGuidStrMap[EfiGuidStrMapLen] = {
	{NULL, L"Tmp buffer AE074D26-6E9E-11E1-A5B8-9BFC4824019B"},
	{&gEfiFileInfoGuid, L"gEfiFileInfoGuid"},
	{&gEfiFileSystemInfoGuid, L"gEfiFileSystemInfoGuid"},
	{&gEfiFileSystemVolumeLabelInfoIdGuid, L"gEfiFileSystemVolumeLabelInfoIdGuid"}
};

/** Returns GUID as string, with friendly name for known guids. */
CHAR16*
EFIAPI
GuidStr(IN EFI_GUID *Guid)
{
	UINTN		i;
	CHAR16		*Str = NULL;
	
	for(i = 1; i < EfiGuidStrMapLen; i++) {
		if (CompareGuid(EfiGuidStrMap[i].Guid, Guid)) {
			Str = EfiGuidStrMap[i].Str;
			break;
		}
	}
	if (Str == NULL) {
		UnicodeSPrint(EfiGuidStrMap[0].Str, 47 * 2, L"%g", Guid); 
		Str = EfiGuidStrMap[0].Str;
	}
	return Str;
}

/** Returns pointer to last Char in String or NULL. */
CHAR16*
EFIAPI
GetStrLastChar(IN CHAR16 *String)
{
	CHAR16		*Pos;
	
	if (String == NULL || *String == L'\0') {
		return NULL;
	}
	
	// go to end
	Pos = String;
	while (*Pos != L'\0') {
		Pos++;
	}
	Pos--;
	return Pos;
}

/** Returns pointer to last occurence of Char in String or NULL. */
CHAR16*
EFIAPI
GetStrLastCharOccurence(IN CHAR16 *String, IN CHAR16 Char)
{
	CHAR16		*Pos;
	
	if (String == NULL || *String == L'\0') {
		return NULL;
	}
	
	// go to end
	Pos = String;
	while (*Pos != L'\0') {
		Pos++;
	}
	// search for Char
	while (*Pos != Char && Pos != String) {
		Pos--;
	}
	return (*Pos == Char) ? Pos : NULL;
}

/** Returns upper case version of char - valid only for ASCII chars in unicode. */
CHAR16
EFIAPI
ToUpperChar(IN CHAR16 Chr)
{
	CHAR8	C;
	
	if (Chr > 0x100) return Chr;
	C = (CHAR8)Chr;
	return ((C >= 'a' && C <= 'z') ? C - ('a' - 'A') : C);
}


/** Returns 0 if two strings are equal, !=0 otherwise. Compares just first 8 bits of chars (valid for ASCII), case insensitive.. */
UINTN
EFIAPI
StrCmpiBasic(IN CHAR16 *String1, IN CHAR16 *String2)
{
	CHAR16	Chr1;
	CHAR16	Chr2;
	
	DBG("Cmpi('%s', '%s') ", String1, String2);
	
	if (String1 == NULL || String2 == NULL) {
		return 1;
	}
	if (*String1 == L'\0' && *String2 == L'\0') {
		return 0;
	}
	if (*String1 == L'\0' || *String2 == L'\0') {
		return 1;
	}
	
	Chr1 = ToUpperChar(*String1);
	Chr2 = ToUpperChar(*String2);
	while ((*String1 != L'\0') && (Chr1 == Chr2)) {
		String1++;
		String2++;
		Chr1 = ToUpperChar(*String1);
		Chr2 = ToUpperChar(*String2);
	}
	
	DBG("=%s ", (Chr1 - Chr2) ? L"NEQ" : L"EQ");
	return Chr1 - Chr2;
}

/** Returns TRUE if String1 starts with String2, FALSE otherwise. Compares just first 8 bits of chars (valid for ASCII), case insensitive.. */
BOOLEAN
EFIAPI
StriStartsWithBasic(IN CHAR16 *String1, IN CHAR16 *String2)
{
	CHAR16	Chr1;
	CHAR16	Chr2;
	BOOLEAN Result;
	
	DBG("StriStarts('%s', '%s') ", String1, String2);
	
	if (String1 == NULL || String2 == NULL) {
		return FALSE;
	}
	if (*String1 == L'\0' && *String2 == L'\0') {
		return TRUE;
	}
	if (*String1 == L'\0' || *String2 == L'\0') {
		return FALSE;
	}
	
	Chr1 = ToUpperChar(*String1);
	Chr2 = ToUpperChar(*String2);
	while ((Chr1 != L'\0') && (Chr2 != L'\0') && (Chr1 == Chr2)) {
		String1++;
		String2++;
		Chr1 = ToUpperChar(*String1);
		Chr2 = ToUpperChar(*String2);
	}
	
	Result = ((Chr1 == L'\0') && (Chr2 == L'\0'))
		|| ((Chr1 != L'\0') && (Chr2 == L'\0'));
	
	DBG("=%s \n", Result ? L"TRUE" : L"FALSE");
	return Result;
}

/** Composes file name from Parent and FName. Allocates memory for result which should be released by caller. */
CHAR16*
EFIAPI
GetNormalizedFName(IN CHAR16 *Parent, IN CHAR16 *FName)
{
	CHAR16			*TmpStr;
	CHAR16			*TmpStr2;
	UINTN			Len;
	
	DBG("NormFName('%s' + '%s')", Parent, FName);
	// case: FName starts with \ "\System\Xx"
	// we'll just use it as is, but we are wrong if "\System\Xx\..\Yy\.\Zz" or similar
	if (FName[0] == L'\\') {
		FName = AllocateCopyPool(StrSize(FName), FName); // reusing FName
	}
	
	// case: FName is "."
	// we'll just copy Parent assuming Parent is normalized, which will be the case if this func will be correct once
	else if (FName[0] == L'.' && FName[1] == L'\0') {
		FName = AllocateCopyPool(StrSize(Parent), Parent);
	}
	
	// case: FName is ".."
	// we'll extract Parent's parent - also assuming Parent is normalized
	else if (FName[0] == L'.' && FName[1] == L'.' && FName[2] == L'\0') {
		TmpStr = GetStrLastCharOccurence(Parent, L'\\');
		// if there is L'\\' and not at the beginning ...
		if (TmpStr != NULL && TmpStr != Parent) {
			*TmpStr = L'\0'; // terminating Parent; will return L'\\' back
			FName = AllocateCopyPool(StrSize(Parent), Parent);
			*TmpStr = L'\\'; // return L'\\' back to Parent
		} else {
			// caller is doing something wrong - we'll default to L"\\"
			FName = AllocateCopyPool(StrSize(L"\\"), L"\\");
		}
	}
	
	// other cases: for now just do Parent + \ + FName
	// but check if Parent already ends with backslash
	else {
		Len = StrSize(Parent) + StrSize(FName); // has place for extra char (\\) if needed
		TmpStr = AllocateZeroPool(Len);
		StrCpyS(TmpStr, Len/sizeof(CHAR16), Parent);
		TmpStr2 = GetStrLastChar(Parent);
		if (TmpStr2 == NULL || *TmpStr2 != L'\\') {
			StrCatS(TmpStr, Len/sizeof(CHAR16), L"\\");
		}
		StrCatS(TmpStr, Len/sizeof(CHAR16), FName);
    DBG("='%s' ", TmpStr);
    return TmpStr;
	}
	DBG("='%s' ", FName);
	return FName;
}

/** If FName starts with TgtDir, then extracts the rest from FName and copies it to SrcDir and returns it. Or NULL.
  * Caller is responsible for releasing memory for returned string.
  * Example: TgtDir="\S\L\E", SrcDir="\efi\10.7", FName="\S\L\E\Xx.kext\Contents\Info.plist"
  * This should return "\efi\10.7\Xx.kext\Contents\Info.plist"
  */
CHAR16*
EFIAPI
GetInjectionFName(IN CHAR16 *TgtDir, IN CHAR16 *SrcDir, IN CHAR16 *FName)
{
	CHAR16	Chr1;
	CHAR16	Chr2;
	CHAR16	*Str1;
	CHAR16	*Str2;
	UINTN	Size;
	
	DBG("InjFName('%s', '%s', '%s')", TgtDir, SrcDir, FName);
	if (TgtDir == NULL || SrcDir == NULL || FName == NULL) {
		DBG("=NULL0 ");
		return NULL;
	}
	// check if FName starts with TgtDir, kind of case insensitive (only ASCII chars)
	Str1 = TgtDir;
	Str2 = FName;
	Chr1 = ToUpperChar(*Str1);
	Chr2 = ToUpperChar(*Str2);
	while ((*Str1 != L'\0') && (Chr1 == Chr2)) {
		Str1++;
		Str2++;
		Chr1 = ToUpperChar(*Str1);
		Chr2 = ToUpperChar(*Str2);
	}
	// if FName starts with TgtDir, then *Str1 should be '\0'
	if (*Str1 != L'\0') {
		DBG("=NULL1 ");
		return NULL;
	}
	// if FName is a file inside TgtDir, then *Str2 should be == '\\'
	if (*Str2 != L'\\') {
		DBG("=NULL2 ");
		return NULL;
	}
	// we are at '\\' with Str2 - copy from here to the end to SrcDir
	// determine the buffer size for new string
	Size = StrSize(SrcDir) + StrSize(Str2) - 2;
	Str1 = AllocateZeroPool(Size);
	if (Str1 != NULL) {
		StrCpyS(Str1, Size/sizeof(CHAR16), SrcDir);
		StrCatS(Str1, Size/sizeof(CHAR16), Str2);
	}
	DBG("='%s' ", Str1);
	return Str1;
}

/** Openes EFI_FILE_PROTOCOL on given VolumeFS for given FName. */
EFI_FILE_PROTOCOL*
EFIAPI
OpenFileProtocol(
	IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL	*VolumeFS,
	IN CHAR16							*FName,
	IN UINT64							OpenMode,
	IN UINT64							Attributes
)
{
	EFI_STATUS							Status;
	EFI_FILE_PROTOCOL					*RootFP;
	EFI_FILE_PROTOCOL					*FP;
	
	// open volume
	Status = VolumeFS->OpenVolume(VolumeFS, &RootFP);
	if (EFI_ERROR(Status)) {
		DBG("Can not OpenVolume: %r ", Status);
		return NULL;
	}
	// open file/dir
	Status = RootFP->Open(RootFP, &FP, FName, OpenMode, Attributes);
	if (EFI_ERROR(Status)) {
		DBG("Can not Open '%s': %r ", FName, Status);
		FP = NULL;
	}
	RootFP->Close(RootFP);
	return FP;
}

/**************************************************************************************
 * FSI_FILE_PROTOCOL - our implementation of EFI_FILE_PROTOCOL
 **************************************************************************************/

FSI_FILE_PROTOCOL* EFIAPI CreateFSInjectFP(VOID);
 
/** EFI_FILE_PROTOCOL.Open - Opens a new file relative to the source file's location. */
EFI_STATUS
EFIAPI
FSI_FP_Open(
  IN EFI_FILE_PROTOCOL        *This,
  OUT EFI_FILE_PROTOCOL       **NewHandle,
  IN CHAR16                   *FileName,
  IN UINT64                   OpenMode,
  IN UINT64                   Attributes
)
{
	EFI_STATUS				Status = EFI_DEVICE_ERROR;
	CHAR16					*NewFName;
	CHAR16					*InjFName = NULL;
	FSI_FILE_PROTOCOL		*FSIThis;
	FSI_FILE_PROTOCOL		*FSINew;
	FSI_STRING_LIST			*StringList;
	FSI_STRING_LIST_ENTRY	*StringEntry;

	DBG("FSI_FP %p.Open('%s', %x, %x) ", This, FileName, OpenMode, Attributes);
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	NewFName = GetNormalizedFName(FSIThis->FName, FileName);
	
	StringList = FSIThis->FSI_FS->Blacklist;
	if (StringList != NULL && !IsListEmpty(&StringList->List)) {
		// blocking files in Blacklist
		for (StringEntry = (FSI_STRING_LIST_ENTRY *)GetFirstNode(&StringList->List);
			 !IsNull (&StringList->List, &StringEntry->List);
			 StringEntry = (FSI_STRING_LIST_ENTRY *)GetNextNode(&StringList->List, &StringEntry->List)
			 )
		{
			if (StriStartsWithBasic(NewFName, StringEntry->String)) {
				DBG("Blacklisted\n");
				return EFI_NOT_FOUND;
			}
		}
	}
	
	// create our FP implementation
	FSINew = CreateFSInjectFP();
	if (FSINew == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		DBG("CreateFSInjectFP Status=%r\n", Status);
		return Status;
	}
	FSINew->FSI_FS = FSIThis->FSI_FS;		// saving reference to parent FS protocol
	FSINew->FName =NewFName;
	FSINew->TgtFP = NULL;
	FSINew->SrcFP = NULL;
	
	// mach_kernel - if exists in SrcDir, then inject this one
	if (StrCmpiBasic(NewFName, L"\\mach_kernel") == 0) {
		DBG("mach_kernel ");
		if (FSIThis->FSI_FS->SrcDir != NULL && FSIThis->FSI_FS->SrcFS != NULL) {
			InjFName = GetInjectionFName(L"\0", FSIThis->FSI_FS->SrcDir, NewFName);
			if (InjFName != NULL) {
				// if this one exists inside injection dir - should be opened with SrcFP
				FSINew->SrcFP = OpenFileProtocol(FSIThis->FSI_FS->SrcFS, InjFName, OpenMode, Attributes);
				if (FSINew->SrcFP != NULL) {
					FSINew->FromTgt = FALSE;
					DBG("Opened with SrcFP ");
					goto SuccessExit;
				} else {
					DBG(" no injection, SrcFP->Open=%r ", Status);
				}
			}
		}
	}
	// S/L/Kernels/kernel - if exists in SrcDir, then inject this one
	if (StrCmpiBasic(NewFName, L"\\System\\Library\\Kernels\\kernel") == 0) {
		DBG("kernel ");
		if (FSIThis->FSI_FS->SrcDir != NULL && FSIThis->FSI_FS->SrcFS != NULL) {
			InjFName = GetInjectionFName(L"\0", FSIThis->FSI_FS->SrcDir, L"\\kernel");
			if (InjFName != NULL) {
				// if this one exists inside injection dir - should be opened with SrcFP
				FSINew->SrcFP = OpenFileProtocol(FSIThis->FSI_FS->SrcFS, InjFName, OpenMode, Attributes);
				if (FSINew->SrcFP != NULL) {
					FSINew->FromTgt = FALSE;
					DBG("Opened with SrcFP ");
					goto SuccessExit;
				} else {
					DBG(" no injection, SrcFP->Open=%r ", Status);
				}
			}
			InjFName = GetInjectionFName(L"\0", FSIThis->FSI_FS->SrcDir, L"\\mach_kernel");
			if (InjFName != NULL) {
				// if this one exists inside injection dir - should be opened with SrcFP
				FSINew->SrcFP = OpenFileProtocol(FSIThis->FSI_FS->SrcFS, InjFName, OpenMode, Attributes);
				if (FSINew->SrcFP != NULL) {
					FSINew->FromTgt = FALSE;
					DBG("Opened with SrcFP ");
					goto SuccessExit;
				} else {
					DBG(" no injection, SrcFP->Open=%r ", Status);
				}
			}
		}
	}
	
	// try with target
	if (FSIThis->TgtFP != NULL) {
		// call original target implementation to get NewHandle
		Status = FSIThis->TgtFP->Open(FSIThis->TgtFP, NewHandle, NewFName, OpenMode, Attributes);
		if (EFI_ERROR(Status)) {
			DBG("TgtFP->Open=%r ", Status);
		} else {
			DBG("Opened with TgtFP ");
			FSINew->FromTgt = TRUE;
			// save new orig target handle
			FSINew->TgtFP = *NewHandle;
		}
	}
	
	// if write protected - return now
	if (Status == EFI_WRITE_PROTECTED) {
		goto ErrorExit;
	}
	
	// handle injection if needed
	if (EFI_ERROR(Status) && FSIThis->FSI_FS->SrcDir != NULL && FSIThis->FSI_FS->SrcFS != NULL) {
		// if not found and injection requested: try injection dir
		InjFName = GetInjectionFName(FSIThis->FSI_FS->TgtDir, FSIThis->FSI_FS->SrcDir, NewFName);
		if (InjFName != NULL) {
			// this one exists inside injection dir - should be opened with SrcFP
			FSINew->FromTgt = FALSE;
			FSINew->SrcFP = OpenFileProtocol(FSIThis->FSI_FS->SrcFS, InjFName, OpenMode, Attributes);
			if (FSINew->SrcFP == NULL) {
				Status = EFI_DEVICE_ERROR;
				DBG("SrcFP->Open=%r ", Status);
			} else {
				Status = EFI_SUCCESS;
				DBG("Opened with SrcFP ");
				// ok - we are injecting kexts with FSInject driver because user requested
				// it with NoCaches or because boot.efi refused to load kernelcache for some reason.
				// let's inform Clover's kext injection that it does not have to do
				// in-memory kext injection.
				{
					STATIC	UINT8	KextsInjected = 0;
					
					if (KextsInjected == 0) {
						gRT->SetVariable(L"FSInject.KextsInjected",
										 &gEfiGlobalVariableGuid,
										 EFI_VARIABLE_BOOTSERVICE_ACCESS,
										 sizeof(KextsInjected),
										 &KextsInjected
										 );
						//AsciiPrint("\nFSInject.KextsInjected\n");
						//gBS->Stall(3000000);
					}
					KextsInjected++;
				}
			}
		}

		if (EFI_ERROR(Status) && FSIThis->TgtFP == NULL) {
			// this happens when we are called on FP that is opened with SrcFP (we do not have TgtFP)
			// and then with FName ".." (in shell), and when resulting dir in actually on TgtFP.
			// need to open it with TgtFP
			FSINew->TgtFP = OpenFileProtocol(FSIThis->FSI_FS->TgtFS, NewFName, OpenMode, Attributes);
			if (FSINew->TgtFP != NULL) {
				Status = EFI_SUCCESS;
				DBG("Opened with TgtFP ");
				FSINew->FromTgt = TRUE;
			} else {
				Status = EFI_DEVICE_ERROR;
				DBG("TgtFS->OpenVolume Status=%r\n", Status);
			}
		}
	}
	
	
	// if still error - quit
	if (EFI_ERROR(Status)) {
		goto ErrorExit;
	}
	
	// we are here with EFI_SUCCESS

	// check if this is injection point (target dir where we should inject)
	if (FSINew->TgtFP != NULL && FSINew->FSI_FS->SrcFS != NULL && FSINew->FSI_FS->SrcDir != NULL
		&& StrCmpiBasic(FSINew->FSI_FS->TgtDir, FSINew->FName) == 0)
	{
		// it is - open injection dir also
		// this FP will have both TgtFP and SrcFP - can be used for test later
		// in case of error, it will be NULL - all should run fine then, but without injection
		FSINew->SrcFP = OpenFileProtocol(FSINew->FSI_FS->SrcFS, FSINew->FSI_FS->SrcDir, EFI_FILE_MODE_READ, 0);
		if (FSINew->SrcFP != NULL) {
			DBG("Opened also with SrcFP ");
		} else {
			DBG("Error opening with SrcFP ");
		}
	}
		
	
SuccessExit:
	// set our implementation as a result
	*NewHandle = &(FSINew->FP);
	
	DBG("= EFI_SUCCESS, NewHandle=%p, FName='%s'\n", *NewHandle, FSINew->FName);
	return EFI_SUCCESS;
	
ErrorExit:
	if (FSINew->FName != NULL) FreePool(FSINew->FName);
	if (FSINew != NULL) FreePool(FSINew);
	DBG("= %r\n", Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.Close - Closes a specified file handle. */
EFI_STATUS
EFIAPI
FSI_FP_Close(IN EFI_FILE_PROTOCOL  *This)
{
	EFI_STATUS				Status = EFI_SUCCESS;
	FSI_FILE_PROTOCOL		*FSIThis;
	
	DBG("FSI_FP %p.Close() ", This);
	
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	
	if (FSIThis->TgtFP != NULL) {
		// close target FP
		Status = FSIThis->TgtFP->Close(FSIThis->TgtFP);
		FSIThis->TgtFP = NULL;
	}
	if (FSIThis->SrcFP != NULL) {
		// close source (injection) FP
		FSIThis->SrcFP->Close(FSIThis->SrcFP);
		FSIThis->SrcFP = NULL;
	}
	DBG("FName='%s' ", FSIThis->FName);
	if (FSIThis->FName != NULL) {
		FreePool(FSIThis->FName);
		FSIThis->FName = NULL;
	}
	FreePool(FSIThis);
	DBG("= %r\n", Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.Delete - Close and delete the file handle. */
EFI_STATUS
EFIAPI
FSI_FP_Delete(IN EFI_FILE_PROTOCOL  *This)
{
	EFI_STATUS				Status = EFI_WARN_DELETE_FAILURE;
	FSI_FILE_PROTOCOL		*FSIThis;
	
	DBG("FSI_FP %p.Delete()\n", This);
	
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	
	if (FSIThis->TgtFP != NULL) {
		// do it with target FP
		Status = FSIThis->TgtFP->Delete(FSIThis->TgtFP);
		FSIThis->TgtFP = NULL;
	}
	if (FSIThis->SrcFP != NULL) {
		// close source FP
		FSIThis->SrcFP->Close(FSIThis->SrcFP);
		FSIThis->SrcFP = NULL;
	}
	if (FSIThis->FName != NULL) {
		FreePool(FSIThis->FName);
		FSIThis->FName = NULL;
	}
	FreePool(FSIThis);
	DBG("= %r\n", Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.Read - Reads data from a file. */
EFI_STATUS
EFIAPI
FSI_FP_Read(
	IN EFI_FILE_PROTOCOL	*This,
	IN OUT UINTN			*BufferSize,
	OUT VOID				*Buffer
)
{
	EFI_STATUS				Status = EFI_DEVICE_ERROR;
	FSI_FILE_PROTOCOL		*FSIThis;
#if DBG_TO  
	EFI_FILE_INFO			*FInfo;
#endif
	UINTN					BufferSizeOrig;
	CHAR8					*String;
	FSI_STRING_LIST			*StringList;
	FSI_STRING_LIST_ENTRY	*StringEntry;
	VOID					*TmpBuffer;
	UINTN					OrigBufferSize = *BufferSize;
	
	DBG("FSI_FP %p.Read(%d, %p) ", This, *BufferSize, Buffer);
	
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	if (FSIThis->TgtFP != NULL && FSIThis->SrcFP != NULL) {
		// this is injection point
		// first read dir entries from Src and then from Tgt
		BufferSizeOrig = *BufferSize;
		Status = FSIThis->SrcFP->Read(FSIThis->SrcFP, BufferSize, Buffer);
		if (*BufferSize == 0) {
			// no more in Src - read from Tgt
			*BufferSize = BufferSizeOrig;
			Status = FSIThis->TgtFP->Read(FSIThis->TgtFP, BufferSize, Buffer);
		}
	} else if (FSIThis->TgtFP != NULL) {
		// do it with target FP
		Status = FSIThis->TgtFP->Read(FSIThis->TgtFP, BufferSize, Buffer);
		StringList = FSIThis->FSI_FS->ForceLoadKexts;
		if (Status == EFI_INVALID_PARAMETER && *BufferSize == 0) {
			// On some systems FS driver seems to have alignment restrictions on given buffer.
			// UEFIs buffers allocated with standard AllocatePool seem to be aligned properly and reads
			// to them always succeed, so we'll try to overcome this by using new allocated buffer.
			TmpBuffer = AllocateZeroPool(OrigBufferSize);
			*BufferSize = OrigBufferSize;
			Status = FSIThis->TgtFP->Read(FSIThis->TgtFP, BufferSize, TmpBuffer);
			if (Status == EFI_SUCCESS && *BufferSize > 0) {
				CopyMem(Buffer, TmpBuffer, *BufferSize);
			}
			FreePool(TmpBuffer);
		}
		if (Status == EFI_SUCCESS && StringList != NULL && FSIThis->FName != NULL) {
			// check ForceLoadKexts
			for (StringEntry = (FSI_STRING_LIST_ENTRY *)GetFirstNode(&StringList->List);
				 !IsNull (&StringList->List, &StringEntry->List);
				 StringEntry = (FSI_STRING_LIST_ENTRY *)GetNextNode(&StringList->List, &StringEntry->List)
				 )
			{
				if (StrStr(FSIThis->FName, StringEntry->String) != NULL) {
					//Print(L"\nGot: %s\n", FSIThis->FName);
					String = AsciiStrStr((CHAR8*)Buffer, "<string>Safe Boot</string>");
					if (String != NULL) {
						CopyMem (String, "<string>Root</string>     ", 26);
						Print(L"\nForced load: %s\n", FSIThis->FName);
						//gBS->Stall(5000000);
					} else {
						String = AsciiStrStr((CHAR8*)Buffer, "<string>Network-Root</string>");
						if (String != NULL) {
							CopyMem (String, "<string>Root</string>        ", 29);
							Print(L"\nForced load: %s\n", FSIThis->FName);
							//gBS->Stall(5000000);
						}
					}
				}
			}
		}
	} else if (FSIThis->SrcFP != NULL) {
		// do it with source FP
		Status = FSIThis->SrcFP->Read(FSIThis->SrcFP, BufferSize, Buffer);
		if (Status == EFI_INVALID_PARAMETER && *BufferSize == 0) {
			// On some systems FS driver seems to have alignment restrictions on given buffer.
			// UEFIs buffers allocated with standard AllocatePool seem to be aligned properly and reads
			// to them always succeed, so we'll try to overcome this by using new allocated buffer.
			TmpBuffer = AllocateZeroPool(OrigBufferSize);
			*BufferSize = OrigBufferSize;
			Status = FSIThis->SrcFP->Read(FSIThis->SrcFP, BufferSize, TmpBuffer);
			if (Status == EFI_SUCCESS && *BufferSize > 0) {
				CopyMem(Buffer, TmpBuffer, *BufferSize);
			}
			FreePool(TmpBuffer);
		}
	}
#if DBG_TO 	
	if (Status == EFI_SUCCESS && FSIThis->IsDir && *BufferSize > 0) {
		FInfo = (EFI_FILE_INFO *)Buffer;
		DBG("= %r, *BufferSize=%d, dir entry FileName='%s'\n", Status, *BufferSize, FInfo->FileName);
	} else {
		DBG("= %r, *BufferSize=%d\n", Status, *BufferSize);
	}
#endif	
	return Status;
}

/** EFI_FILE_PROTOCOL.Write - Writes data to a file. */
EFI_STATUS
EFIAPI
FSI_FP_Write(
	IN EFI_FILE_PROTOCOL	*This,
	IN OUT UINTN			*BufferSize,
	IN VOID					*Buffer
)
{
	EFI_STATUS				Status = EFI_DEVICE_ERROR;
	FSI_FILE_PROTOCOL		*FSIThis;
	
	DBG("FSI_FP %p.Write(%d, %p) ", This, *BufferSize, Buffer);
	
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	
	if (FSIThis->TgtFP != NULL) {
		// do it with target FP
		Status = FSIThis->TgtFP->Write(FSIThis->TgtFP, BufferSize, Buffer);
	} else if (FSIThis->SrcFP != NULL) {
		// do it with source FP
		Status = FSIThis->SrcFP->Write(FSIThis->SrcFP, BufferSize, Buffer);
	}
	DBG("= %r, *BufferSize=%d\n", Status, *BufferSize);
	return Status;
}

/** EFI_FILE_PROTOCOL.SetPosition - Sets a file's current position. */
EFI_STATUS
EFIAPI
FSI_FP_SetPosition(
	IN EFI_FILE_PROTOCOL	*This,
	IN UINT64				Position
)
{
	EFI_STATUS				Status = EFI_DEVICE_ERROR;
	FSI_FILE_PROTOCOL		*FSIThis;
	
	DBG("FSI_FP %p.SetPosition(%d) ", This, Position);
	
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	
	if (FSIThis->TgtFP != NULL) {
		// do it with target FP
		Status = FSIThis->TgtFP->SetPosition(FSIThis->TgtFP, Position);
	}
	if (FSIThis->SrcFP != NULL) {
		// and with Src
		Status = FSIThis->SrcFP->SetPosition(FSIThis->SrcFP, Position);
	}
	DBG("= %r\n", Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.GetPosition - Returns a file's current position. */
EFI_STATUS
EFIAPI
FSI_FP_GetPosition(
	IN EFI_FILE_PROTOCOL	*This,
	IN UINT64				*Position
)
{
	EFI_STATUS				Status = EFI_DEVICE_ERROR;
	FSI_FILE_PROTOCOL		*FSIThis;
	
	DBG("FSI_FP %p.GetPosition() ", This);
	
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	
	if (FSIThis->TgtFP != NULL) {
		// do it with target FP
		Status = FSIThis->TgtFP->GetPosition(FSIThis->TgtFP, Position);
	} else if (FSIThis->SrcFP != NULL) {
		// do it with source FP
		Status = FSIThis->SrcFP->GetPosition(FSIThis->SrcFP, Position);
	}
	DBG("= %r, Position=%d\n", Status, Position);
	return Status;
}

/** EFI_FILE_PROTOCOL.GetInfo - Returns information about a file. */
EFI_STATUS
EFIAPI
FSI_FP_GetInfo(
	IN EFI_FILE_PROTOCOL	*This,
	IN EFI_GUID				*InformationType,
	IN OUT UINTN			*BufferSize,
	OUT VOID				*Buffer
)
{
	EFI_STATUS				Status = EFI_DEVICE_ERROR;
	FSI_FILE_PROTOCOL		*FSIThis;
	EFI_FILE_INFO 			*FInfo;
	
	DBG("FSI_FP %p.GetInfo(%s, %d, %p) ", This, GuidStr(InformationType), *BufferSize, Buffer);

	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	
	if (FSIThis->TgtFP != NULL) {
		// do it with target FP
		Status = FSIThis->TgtFP->GetInfo(FSIThis->TgtFP, InformationType, BufferSize, Buffer);
		if (Status == EFI_SUCCESS && CompareGuid(InformationType, &gEfiFileInfoGuid)) {
			FInfo = (EFI_FILE_INFO *)Buffer;
			FSIThis->IsDir = FInfo->Attribute & EFI_FILE_DIRECTORY;
		}
	} else if (FSIThis->SrcFP != NULL) {
		// do it with source FP
		Status = FSIThis->SrcFP->GetInfo(FSIThis->SrcFP, InformationType, BufferSize, Buffer);
		if (Status == EFI_SUCCESS && CompareGuid(InformationType, &gEfiFileInfoGuid)) {
			FInfo = (EFI_FILE_INFO *)Buffer;
			FSIThis->IsDir = FInfo->Attribute & EFI_FILE_DIRECTORY;
		}
	}
	DBG("= %r, BufferSize=%d\n", Status, *BufferSize);
	return Status;
}

/** EFI_FILE_PROTOCOL.SetInfo - Sets information about a file. */
EFI_STATUS
EFIAPI
FSI_FP_SetInfo(
	IN EFI_FILE_PROTOCOL	*This,
	IN EFI_GUID				*InformationType,
	IN UINTN				BufferSize,
	IN VOID					*Buffer
)
{
	EFI_STATUS				Status = EFI_DEVICE_ERROR;
	FSI_FILE_PROTOCOL		*FSIThis;
	
	DBG("FSI_FP %p.SetInfo(%s, %d, %p) ", This, GuidStr(InformationType), BufferSize, Buffer);
	
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	
	if (FSIThis->TgtFP != NULL) {
		// do it with target FP
		Status = FSIThis->TgtFP->SetInfo(FSIThis->TgtFP, InformationType, BufferSize, Buffer);
	} else if (FSIThis->SrcFP != NULL) {
		// do it with source FP
		Status = FSIThis->SrcFP->SetInfo(FSIThis->SrcFP, InformationType, BufferSize, Buffer);
	}
	DBG("= %r\n", Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.Flush - Flushes all modified data associated with a file to a device. */
EFI_STATUS
EFIAPI
FSI_FP_Flush(
	IN EFI_FILE_PROTOCOL	*This
)
{
	EFI_STATUS				Status = EFI_DEVICE_ERROR;
	FSI_FILE_PROTOCOL		*FSIThis;
	
	DBG("FSI_FP %p.Flush() ", This);
	
	FSIThis = FSI_FROM_FILE_PROTOCOL(This);
	
	if (FSIThis->TgtFP != NULL) {
		// do it with target FP
		Status = FSIThis->TgtFP->Flush(FSIThis->TgtFP);
	} else if (FSIThis->SrcFP != NULL) {
		// do it with source FP
		Status = FSIThis->SrcFP->Flush(FSIThis->SrcFP);
	}
	DBG("= %r\n", Status);
	return Status;
}

/** Creates our FSI_FILE_PROTOCOL. */
FSI_FILE_PROTOCOL*
EFIAPI
CreateFSInjectFP(VOID)
{
	FSI_FILE_PROTOCOL		*FSINew;

	// wrap it into our implementation
	FSINew = AllocateZeroPool(sizeof(FSI_FILE_PROTOCOL));
	if (FSINew == NULL) {
		return NULL;
	}
	
	FSINew->Signature = FSI_FILE_PROTOCOL_SIGNATURE;
	FSINew->FP.Revision = EFI_FILE_PROTOCOL_REVISION;
	FSINew->FP.Open = FSI_FP_Open;
	FSINew->FP.Close = FSI_FP_Close;
	FSINew->FP.Delete = FSI_FP_Delete;
	FSINew->FP.Read = FSI_FP_Read;
	FSINew->FP.Write = FSI_FP_Write;
	FSINew->FP.GetPosition = FSI_FP_GetPosition;
	FSINew->FP.SetPosition = FSI_FP_SetPosition;
	FSINew->FP.GetInfo = FSI_FP_GetInfo;
	FSINew->FP.SetInfo = FSI_FP_SetInfo;
	FSINew->FP.Flush = FSI_FP_Flush;

	FSINew->FSI_FS = NULL;
	FSINew->FName = NULL;
	FSINew->IsDir = FALSE;
	FSINew->TgtFP = NULL;
	FSINew->SrcFP = NULL;
	FSINew->FromTgt = FALSE;
	
	return FSINew;
}



/**************************************************************************************
 * FSI_SIMPLE_FILE_SYSTEM_PROTOCOL - our implementation of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
 **************************************************************************************/

/**
 * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.OpenVolume implementation.
 */
EFI_STATUS
EFIAPI
FSI_SFS_OpenVolume(
	IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL	*This,
	OUT EFI_FILE_PROTOCOL				**Root
)
{
	EFI_STATUS							Status;
	FSI_SIMPLE_FILE_SYSTEM_PROTOCOL		*FSIThis;
	FSI_FILE_PROTOCOL					*FSINew;
	
	DBG("FSI_FS %p.OpenVolume() ", This);
	FSIThis = FSI_FROM_SIMPLE_FILE_SYSTEM(This);
	
	// do it with target FS
	Status = FSIThis->TgtFS->OpenVolume(FSIThis->TgtFS, Root);
	if (EFI_ERROR(Status)) {
		DBG("TgtFS->OpenVolume Status=%r\n", Status);
		return Status;
	}
	
	// wrap it into our implementation
	FSINew = CreateFSInjectFP();
	if (FSINew == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		DBG("CreateFSInjectFP: %r\n", Status);
		return Status;
	}
	FSINew->FSI_FS = FSIThis;		// saving reference to parent FS protocol
	FSINew->FName = AllocateCopyPool(StrSize(L"\\"), L"\\");
	FSINew->TgtFP = *Root;
	FSINew->SrcFP = NULL;
	FSINew->FromTgt = TRUE;

	// set it as result
	*Root = &FSINew->FP;
	DBG("= %r, returning Root=%p\n", Status, *Root);
	
	return Status;
} 



/**************************************************************************************
 * FSINJECTION_PROTOCOL
 **************************************************************************************/

/**
 * FSINJECTION_PROTOCOL.Install implementation.
 */
EFI_STATUS
EFIAPI
FSInjectionInstall (
	IN EFI_HANDLE			TgtHandle,
	IN CHAR16				*TgtDir,
	IN EFI_HANDLE			SrcHandle,
	IN CHAR16				*SrcDir,
	IN FSI_STRING_LIST		*Blacklist,
	IN FSI_STRING_LIST		*ForceLoadKexts
)
{
	EFI_STATUS							Status;
	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL		*TgtFS;
	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL		*SrcFS;
	FSI_SIMPLE_FILE_SYSTEM_PROTOCOL		*OurFS;
	
	
	DBG("FSInjectionInstall ...\n");
	
	// get existing target EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
	Status = gBS->OpenProtocol(TgtHandle, &gEfiSimpleFileSystemProtocolGuid, (void **)&TgtFS, gImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
	if (EFI_ERROR(Status)) {
		DBG("- target OpenProtocol(gEfiSimpleFileSystemProtocolGuid): %r\n", Status);
		return Status;
	}
	
	// get existing source EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
	Status = gBS->OpenProtocol(SrcHandle, &gEfiSimpleFileSystemProtocolGuid, (void **)&SrcFS, gImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
	if (EFI_ERROR(Status)) {
		DBG("- source OpenProtocol(gEfiSimpleFileSystemProtocolGuid): %r\n", Status);
		return Status;
	}
	
	// create our implementation
	OurFS = AllocateZeroPool(sizeof(FSI_SIMPLE_FILE_SYSTEM_PROTOCOL));
	if (OurFS == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		DBG("- AllocateZeroPool(FSI_SIMPLE_FILE_SYSTEM_PROTOCOL): %r\n", Status);
		return Status;
	}
	OurFS->Signature = FSI_SIMPLE_FILE_SYSTEM_PROTOCOL_SIGNATURE;
	OurFS->FS.Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;
	OurFS->FS.OpenVolume = FSI_SFS_OpenVolume;
	OurFS->TgtHandle = TgtHandle;
	OurFS->TgtFS = TgtFS;
	OurFS->TgtDir = AllocateCopyPool(StrSize(TgtDir), TgtDir);
	if (OurFS->TgtDir == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		DBG("- AllocateCopyPool for TgtDir: %r\n", Status);
		goto ErrorExit;
	}
	OurFS->SrcHandle = SrcHandle;
	OurFS->SrcFS = SrcFS;
	// we can run without SrcDir - no injection, but blocking caches for example
	if (SrcDir != NULL) {
		OurFS->SrcDir = AllocateCopyPool(StrSize(SrcDir), SrcDir);
		if (OurFS->SrcDir == NULL) {
			Status = EFI_OUT_OF_RESOURCES;
			DBG("- AllocateCopyPool for TgtDir or SrcDir: %r\n", Status);
			goto ErrorExit;
		}
	}
	
	if (Blacklist != NULL && !IsListEmpty(&Blacklist->List)) {
		OurFS->Blacklist = Blacklist;
	}
	
	if (ForceLoadKexts != NULL && !IsListEmpty(&ForceLoadKexts->List)) {
		OurFS->ForceLoadKexts = ForceLoadKexts;
	}
	
	// replace existing tagret EFI_SIMPLE_FILE_SYSTEM_PROTOCOL with out implementation
	Status = gBS->ReinstallProtocolInterface(TgtHandle, &gEfiSimpleFileSystemProtocolGuid, TgtFS, &OurFS->FS);
	if (EFI_ERROR(Status)) {
		DBG("- ReinstallProtocolInterface(): %r\n", Status);
		return Status;
	}
	
	DBG("- Our FSI_SIMPLE_FILE_SYSTEM_PROTOCOL installed on handle: %X\n", TgtHandle);
	return EFI_SUCCESS;
	
ErrorExit:
	if (OurFS->TgtDir != NULL) FreePool(OurFS->TgtDir);
	if (OurFS->SrcDir != NULL) FreePool(OurFS->SrcDir);
	FreePool(OurFS);
	return Status;
}


/**
 * FSINJECTION_PROTOCOL.CreateStringList() implementation.
 */
FSI_STRING_LIST *
EFIAPI
FSInjectionCreateStringList(VOID)
{
	FSI_STRING_LIST	*List;
	
	List = AllocateZeroPool(sizeof(FSI_STRING_LIST));
	if (List != NULL) {
		InitializeListHead(&List->List);
	}
	return List;
}


/**
 * FSINJECTION_PROTOCOL.AddStringToList() implementation.
 */
FSI_STRING_LIST *
EFIAPI
FSInjectionAddStringToList(FSI_STRING_LIST *List, CHAR16 *String)
{
	FSI_STRING_LIST_ENTRY	*Entry;
	UINTN					StringSize;
	
	if (List == NULL || String == NULL) {
		return NULL;
	}
	
	StringSize = StrSize(String); // num of bytes, including null terminator
		
	Entry = AllocateZeroPool(sizeof(FSI_STRING_LIST_ENTRY) + StringSize);
	if (Entry != NULL) {
		StrCpyS(Entry->String, StringSize/sizeof(CHAR16), String);
		InsertTailList(&List->List, &Entry->List);
	}
	return Entry != NULL ? List : NULL;
}


/**
 * Installs FSINJECTION_PROTOCOL to na new handle/
 */
EFI_STATUS
EFIAPI
InstallFSInjectionProtocol (VOID)
{
	EFI_STATUS					Status;
	FSINJECTION_PROTOCOL		*FSInjection;
	EFI_HANDLE					FSIHandle;
	
	// install FSINJECTION_PROTOCOL to new handle
	FSInjection = AllocateZeroPool(sizeof(FSINJECTION_PROTOCOL));
	if (FSInjection == NULL)	 {
		DBG("Can not allocate memory for FSINJECTION_PROTOCOL\n");
		return EFI_OUT_OF_RESOURCES;
	}
	
	FSInjection->Install = FSInjectionInstall;
	FSInjection->CreateStringList = FSInjectionCreateStringList;
	FSInjection->AddStringToList = FSInjectionAddStringToList;
	FSIHandle = NULL; // install to new handle
	Status = gBS->InstallMultipleProtocolInterfaces(&FSIHandle, &gFSInjectProtocolGuid, FSInjection, NULL);
	if (EFI_ERROR(Status)) {
		DBG("InstallFSInjectionProtocol: error installing FSINJECTION_PROTOCOL, Status = %r\n", Status);
	}
	return Status;
}



/**************************************************************************************
 * Entry point
 **************************************************************************************/
 
/**
 * FSInjection entry point. Installs FSINJECTION_PROTOCOL.
 */
EFI_STATUS
EFIAPI
FSInjectEntrypoint (
	IN EFI_HANDLE				ImageHandle,
	IN EFI_SYSTEM_TABLE			*SystemTable
	)
{
	EFI_STATUS					Status;
#if TEST
	FSI_STRING_LIST				*Blacklist;
#endif
	
	Status = InstallFSInjectionProtocol();
	if (EFI_ERROR(Status)) {
		return Status;
	}
	
#if TEST
	Blacklist = FSInjectionCreateStringList();

	// Caution! Do not add this list. If add this list, will see "Kernel cache load error (0xe)". This is just a guideline.
	// by Sherlocks, 2017.11

	// Installed/createinstallmedia
	//FSInjectionAddStringToList(Blacklist, L"\\System\\Library\\PrelinkedKernels\\prelinkedkernel"); // 10.10+/10.13.4+

	// Recovery
	//FSInjectionAddStringToList(Blacklist, L"\\com.apple.recovery.boot\\kernelcache"); // 10.7 - 10.10
	//FSInjectionAddStringToList(Blacklist, L"\\com.apple.recovery.boot\\prelinkedkernel"); // 10.11+

	// BaseSytem/InstallESD
	//FSInjectionAddStringToList(Blacklist, L"\\kernelcache"); // 10.7 - 10.9/(10.7/10.8)

	// 1st stage - createinstallmedia
	//FSInjectionAddStringToList(Blacklist, L"\\.IABootFiles\\kernelcache"); // 10.9/10.10
	//FSInjectionAddStringToList(Blacklist, L"\\.IABootFiles\\prelinkedkernel"); // 10.11 - 10.13.3

	// 2nd stage - InstallESD/AppStore/startosinstall
	//FSInjectionAddStringToList(Blacklist, L"\\Mac OS X Install Data\\kernelcache"); // 10.7
	//FSInjectionAddStringToList(Blacklist, L"\\OS X Install Data\\kernelcache"); // 10.8 - 10.10
	//FSInjectionAddStringToList(Blacklist, L"\\OS X Install Data\\prelinkedkernel"); // 10.11
	//FSInjectionAddStringToList(Blacklist, L"\\macOS Install Data\\prelinkedkernel"); // 10.12 - 10.12.3
	//FSInjectionAddStringToList(Blacklist, L"\\macOS Install Data\\Locked Files\\Boot Files\\prelinkedkernel"); // 10.12.4+

	// 2nd stage - Fusion Drive
	//FSInjectionAddStringToList(Blacklist, L"\\com.apple.boot.R\\System\\Library\\PrelinkedKernels\\prelinkedkernel"); // 10.11
	//FSInjectionAddStringToList(Blacklist, L"\\com.apple.boot.P\\System\\Library\\PrelinkedKernels\\prelinkedkernel"); // 10.11
	//FSInjectionAddStringToList(Blacklist, L"\\com.apple.boot.S\\System\\Library\\PrelinkedKernels\\prelinkedkernel"); // 10.11
	//FSInjectionAddStringToList(Blacklist, L"\\com.apple.boot.R\\prelinkedkernel"); // 10.12+
	//FSInjectionAddStringToList(Blacklist, L"\\com.apple.boot.P\\prelinkedkernel"); // 10.12+
	//FSInjectionAddStringToList(Blacklist, L"\\com.apple.boot.S\\prelinkedkernel"); // 10.12+

	// NetInstall
	//FSInjectionAddStringToList(Blacklist, L"\\NetInstall macOS High Sierra.nbi\\i386\\x86_64\\kernelcache");


	// Block Caches list
	// InstallDVD/Installed
	FSInjectionAddStringToList(Blacklist, L"\\System\\Library\\Caches\\com.apple.kext.caches\\Startup\\Extensions.mkext"); // 10.6
	FSInjectionAddStringToList(Blacklist, L"\\System\\Library\\Extensions.mkext"); // 10.6
	FSInjectionAddStringToList(Blacklist, L"\\System\\Library\\Caches\\com.apple.kext.caches\\Startup\\kernelcache"); // 10.6/10.6 - 10.9

	Status = InstallTestFSinjection(L"\\Users\\dmazar", L"\\efi\\kext\\10_7", Blacklist, NULL);
	if (EFI_ERROR(Status)) {
		return Status;
	}
#endif
	
	return EFI_SUCCESS;
}

