/*
 * refit/lib.c
 * General library functions
 *
 * Copyright (c) 2006-2009 Christoph Pfisterer
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 *  * Neither the name of Christoph Pfisterer nor the names of the
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "Platform.h"

#ifndef DEBUG_ALL
#define DEBUG_LIB 1
#else
#define DEBUG_LIB DEBUG_ALL
#endif

#if DEBUG_LIB == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_LIB, __VA_ARGS__)
#endif

// variables

EFI_HANDLE       SelfImageHandle;
EFI_HANDLE       SelfDeviceHandle;
EFI_LOADED_IMAGE *SelfLoadedImage;
EFI_FILE         *SelfRootDir;
EFI_FILE         *SelfDir;
CHAR16           *SelfDirPath;
EFI_DEVICE_PATH  *SelfDevicePath;
EFI_DEVICE_PATH  *SelfFullDevicePath;
EFI_FILE         *ThemeDir = NULL;
CHAR16           *ThemePath;
BOOLEAN          gThemeChanged = FALSE;
//BOOLEAN          gBootArgsChanged = FALSE;
BOOLEAN          gBootChanged = FALSE;
BOOLEAN          gThemeOptionsChanged = FALSE;

EFI_FILE         *OEMDir;
CHAR16           *OEMPath = L"EFI\\CLOVER";
EFI_FILE         *OemThemeDir = NULL;


REFIT_VOLUME     *SelfVolume = NULL;
REFIT_VOLUME     **Volumes = NULL;
UINTN            VolumesCount = 0;
//
// Unicode collation protocol interface
//
EFI_UNICODE_COLLATION_PROTOCOL *mUnicodeCollation = NULL;

// functions

static EFI_STATUS FinishInitRefitLib(VOID);

static VOID UninitVolumes(VOID);

// S. Mtr
/* Function for parsing nodes from device path
 * IN : DevicePath, sizeof(DevicePath)
 * OUT: Size of cutted device path
 * Description:
 * Device path contains device nodes.
 * From UEFI specification device node struct looks like:
 *typedef struct {
 *  UINT8 Type;   ///< 0x01 Hardware Device Path.
 *                ///< 0x02 ACPI Device Path.
 *                ///< 0x03 Messaging Device Path.
 *                ///< 0x04 Media Device Path.
 *                ///< 0x05 BIOS Boot Specification Device Path.
 *                ///< 0x7F End of Hardware Device Path.
 *
 *  UINT8 SubType;///< Varies by Type
 *                ///< 0xFF End Entire Device Path, or
 *                ///< 0x01 End This Instance of a Device Path and start a new
 *                ///< Device Path.
 *
 *  UINT8 Length[2];  ///< Specific Device Path data. Type and Sub-Type define
 *                    ///< type of data. Size of data is included in Length.
 *
 * } EFI_DEVICE_PATH_PROTOCOL;
 */
UINTN
NodeParser  (UINT8 *DevPath, UINTN PathSize, UINT8 Type)
{
  UINTN i;
  for (i=0; i<PathSize+1;){
    if (DevPath[i] == Type)
    {
      //This type corresponds to Type
      //So.. save position and exit from loop
      PathSize = i;
      break;
    }
    //Jump to the next device node type
    i += (((UINT16)DevPath[i+3]<<8) | DevPath[i+2]);
  }
  return PathSize;
}

BOOLEAN MetaiMatch (
                    IN CHAR16   *String,
                    IN CHAR16   *Pattern
                    );

/**
 This function converts an input device structure to a Unicode string.
 
 @param DevPath                  A pointer to the device path structure.
 
 @return A new allocated Unicode string that represents the device path.
 
 **/
CHAR16 *
EFIAPI
DevicePathToStr (
                 IN EFI_DEVICE_PATH_PROTOCOL     *DevPath
                 )
{
  return ConvertDevicePathToText (DevPath, TRUE, TRUE);
}



EFI_STATUS GetRootFromPath(IN EFI_DEVICE_PATH_PROTOCOL* DevicePath, OUT EFI_FILE **Root)
{
  EFI_STATUS  Status;
  EFI_HANDLE                NewHandle;
  EFI_DEVICE_PATH_PROTOCOL* TmpDevicePath;
  //  DBG("Try to duplicate DevicePath\n");
  TmpDevicePath = DuplicateDevicePath(DevicePath);
  //  DBG("TmpDevicePath found\n");
  NewHandle = NULL;
	Status = gBS->LocateDevicePath (&gEfiSimpleFileSystemProtocolGuid,
                                  &TmpDevicePath,
                                  &NewHandle);
  //   DBG("volume handle found =%x\n", NewHandle);
  CheckError(Status, L"while reopening volume handle");
  *Root = EfiLibOpenRoot(NewHandle);
  if (*Root == NULL) {
    //    DBG("volume Root Dir can't be reopened\n");
    return EFI_NOT_FOUND;
  }
  if (FileExists(*Root, L"mach_kernel")) {
    DBG("mach_kernel exists\n");
  } else {
    DBG("mach_kernel not exists\n");
  }
  
  return Status;
}
//
// self recognition stuff
//

EFI_STATUS InitRefitLib(IN EFI_HANDLE ImageHandle)
{
  EFI_STATUS  Status;
  CHAR16      *FilePathAsString;
  UINTN       i;
  UINTN                     DevicePathSize;
  EFI_DEVICE_PATH_PROTOCOL* TmpDevicePath;
  
  SelfImageHandle = ImageHandle;
  Status = gBS->HandleProtocol(SelfImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **) &SelfLoadedImage);
  if (CheckFatalError(Status, L"while getting a LoadedImageProtocol handle"))
    return Status;
  
  SelfDeviceHandle = SelfLoadedImage->DeviceHandle;
  TmpDevicePath = DevicePathFromHandle (SelfDeviceHandle);
  DevicePathSize = GetDevicePathSize (TmpDevicePath);
  SelfDevicePath = AllocateAlignedPages(EFI_SIZE_TO_PAGES(DevicePathSize), 64);
  CopyMem(SelfDevicePath, TmpDevicePath, DevicePathSize);
  
  DBG("SelfDevicePath=%s @%x\n", FileDevicePathToStr(SelfDevicePath), SelfDeviceHandle);
  
  // find the current directory
  FilePathAsString = FileDevicePathToStr(SelfLoadedImage->FilePath);
  if (FilePathAsString != NULL) {
    SelfFullDevicePath = FileDevicePath(SelfDeviceHandle, FilePathAsString);
    for (i = StrLen(FilePathAsString); i > 0 && FilePathAsString[i] != '\\'; i--) ;
    if (i > 0) {
      FilePathAsString[i] = 0;
    } else {
      FilePathAsString[0] = L'\\';
      FilePathAsString[1] = 0;
    }
  } else {
    FilePathAsString = AllocateCopyPool(StrSize(L"\\"), L"\\");
  }
  SelfDirPath = FilePathAsString;
  
  DBG("SelfDirPath = %s\n", SelfDirPath);
  
  return FinishInitRefitLib();
}

VOID UninitRefitLib(VOID)
{
  // called before running external programs to close open file handles
  
  if (SelfDir != NULL) {
    SelfDir->Close(SelfDir);
    SelfDir = NULL;
  }
  
  if (OEMDir != NULL) {
    OEMDir->Close(OEMDir);
    OEMDir = NULL;
  }
  
  if (ThemeDir != NULL) {
    ThemeDir->Close(ThemeDir);
    ThemeDir = NULL;
  }
  
  if (SelfRootDir != NULL) {
    SelfRootDir->Close(SelfRootDir);
    SelfRootDir = NULL;
  }
  
  UninitVolumes();
}

EFI_STATUS ReinitRefitLib(VOID)
{
  // called after running external programs to re-open file handles
  //
  ReinitVolumes();
  
  if (SelfVolume != NULL && SelfVolume->RootDir != NULL)
    SelfRootDir = SelfVolume->RootDir;
  
  return FinishInitRefitLib();
}

EFI_STATUS ReinitSelfLib(VOID)
{
  // called after reconnect drivers to re-open file handles
  
  EFI_STATUS  Status;
  EFI_HANDLE                NewSelfHandle;
	EFI_DEVICE_PATH_PROTOCOL* TmpDevicePath;

//  DbgHeader("ReinitSelfLib");
  
  if (!SelfDevicePath) {
    return EFI_NOT_FOUND;
  }
  
  TmpDevicePath = DuplicateDevicePath(SelfDevicePath);
  DBG("reinit: self device path=%s\n", FileDevicePathToStr(TmpDevicePath));
  if(TmpDevicePath == NULL)
		return EFI_NOT_FOUND;
  
  NewSelfHandle = NULL;
	Status = gBS->LocateDevicePath (&gEfiSimpleFileSystemProtocolGuid,
                                  &TmpDevicePath,
                                  &NewSelfHandle);
  CheckError(Status, L"while reopening our self handle");
 // DBG("new SelfHandle=%x\n", NewSelfHandle);
  
  SelfRootDir = EfiLibOpenRoot(NewSelfHandle);
  if (SelfRootDir == NULL) {
    DBG("SelfRootDir can't be reopened\n");
    return EFI_NOT_FOUND;
  }
  SelfDeviceHandle = NewSelfHandle;
  /*Status = */SelfRootDir->Open(SelfRootDir, &ThemeDir, ThemePath, EFI_FILE_MODE_READ, 0);
  /*Status = */SelfRootDir->Open(SelfRootDir, &OEMDir, OEMPath, EFI_FILE_MODE_READ, 0);
  Status = SelfRootDir->Open(SelfRootDir, &SelfDir, SelfDirPath, EFI_FILE_MODE_READ, 0);
  CheckFatalError(Status, L"while reopening our installation directory");
  return Status;
}


static
EFI_STATUS FinishInitRefitLib(VOID)
{
	EFI_STATUS                Status;
  
  if (SelfRootDir == NULL) {
    SelfRootDir = EfiLibOpenRoot(SelfLoadedImage->DeviceHandle);
    if (SelfRootDir != NULL) {
      SelfDeviceHandle = SelfLoadedImage->DeviceHandle;
    } else {
      return EFI_LOAD_ERROR;
    }
  }
  /*Status = */SelfRootDir->Open(SelfRootDir, &ThemeDir, ThemePath, EFI_FILE_MODE_READ, 0);
  /*Status = */SelfRootDir->Open(SelfRootDir, &OEMDir, OEMPath, EFI_FILE_MODE_READ, 0);
  Status = SelfRootDir->Open(SelfRootDir, &SelfDir, SelfDirPath, EFI_FILE_MODE_READ, 0);
  CheckFatalError(Status, L"while opening our installation directory");
  return Status;
}

BOOLEAN IsEmbeddedTheme()
{
  if (!GlobalConfig.Theme || !StriCmp(GlobalConfig.Theme, L"embedded")) {
    ThemeDir = NULL;
  }
  return ThemeDir == NULL;
}

//
// list functions
//

VOID CreateList(OUT VOID ***ListPtr, OUT UINTN *ElementCount, IN UINTN InitialElementCount)
{
  UINTN AllocateCount;
  
  *ElementCount = InitialElementCount;
  if (*ElementCount > 0) {
    AllocateCount = (*ElementCount + 7) & ~7;   // next multiple of 8
    *ListPtr = AllocatePool(sizeof(VOID *) * AllocateCount);
  } else {
    *ListPtr = NULL;
  }
}

VOID AddListElement(IN OUT VOID ***ListPtr, IN OUT UINTN *ElementCount, IN VOID *NewElement)
{
  UINTN AllocateCount;
  
  if ((*ElementCount & 7) == 0) {
    AllocateCount = *ElementCount + 8;
    if (*ElementCount == 0)
      *ListPtr = AllocatePool(sizeof(VOID *) * AllocateCount);
    else
      *ListPtr =  EfiReallocatePool((VOID *)*ListPtr, sizeof(VOID *) * (*ElementCount), sizeof(VOID *) * AllocateCount);
  }
  (*ListPtr)[*ElementCount] = NewElement;
  (*ElementCount)++;
}
/*
VOID FreeList(IN OUT VOID ***ListPtr, IN OUT UINTN *ElementCount)
{
  UINTN i;
  
  if (*ElementCount > 0) {
    for (i = 0; i < *ElementCount; i++) {
      // TODO: call a user-provided routine for each element here
      FreePool((*ListPtr)[i]);
    }
    FreePool(*ListPtr);
  }
}
*/
//
// firmware device path discovery
//

static UINT8 LegacyLoaderMediaPathData[] = {
  0x04, 0x06, 0x14, 0x00, 0xEB, 0x85, 0x05, 0x2B,
  0xB8, 0xD8, 0xA9, 0x49, 0x8B, 0x8C, 0xE2, 0x1B,
  0x01, 0xAE, 0xF2, 0xB7, 0x7F, 0xFF, 0x04, 0x00,
};
static EFI_DEVICE_PATH *LegacyLoaderMediaPath = (EFI_DEVICE_PATH *)LegacyLoaderMediaPathData;

EFI_STATUS ExtractLegacyLoaderPaths(EFI_DEVICE_PATH **PathList, UINTN MaxPaths, EFI_DEVICE_PATH **HardcodedPathList)
{
  EFI_STATUS          Status;
  UINTN               HandleCount = 0;
  UINTN               HandleIndex, HardcodedIndex;
  EFI_HANDLE          *Handles = NULL;
  EFI_HANDLE          Handle;
  UINTN               PathCount = 0;
  UINTN               PathIndex;
  EFI_LOADED_IMAGE    *LoadedImage;
  EFI_DEVICE_PATH     *DevicePath;
  BOOLEAN             Seen;
  
  MaxPaths--;  // leave space for the terminating NULL pointer
  
  // get all LoadedImage handles
  Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiLoadedImageProtocolGuid, NULL,
                                   &HandleCount, &Handles);
  if (CheckError(Status, L"while listing LoadedImage handles")) {
    if (HardcodedPathList) {
      for (HardcodedIndex = 0; HardcodedPathList[HardcodedIndex] && PathCount < MaxPaths; HardcodedIndex++)
        PathList[PathCount++] = HardcodedPathList[HardcodedIndex];
    }
    PathList[PathCount] = NULL;
    return Status;
  }
  for (HandleIndex = 0; HandleIndex < HandleCount && PathCount < MaxPaths; HandleIndex++) {
    Handle = Handles[HandleIndex];
    
    Status = gBS->HandleProtocol(Handle, &gEfiLoadedImageProtocolGuid, (VOID **) &LoadedImage);
    if (EFI_ERROR(Status))
      continue;  // This can only happen if the firmware scewed up, ignore it.
    
    Status = gBS->HandleProtocol(LoadedImage->DeviceHandle, &gEfiDevicePathProtocolGuid, (VOID **) &DevicePath);
    if (EFI_ERROR(Status))
      continue;  // This happens, ignore it.
    
    // Only grab memory range nodes
    if (DevicePathType(DevicePath) != HARDWARE_DEVICE_PATH || DevicePathSubType(DevicePath) != HW_MEMMAP_DP)
      continue;
    
    // Check if we have this device path in the list already
    // WARNING: This assumes the first node in the device path is unique!
    Seen = FALSE;
    for (PathIndex = 0; PathIndex < PathCount; PathIndex++) {
      if (DevicePathNodeLength(DevicePath) != DevicePathNodeLength(PathList[PathIndex]))
        continue;
      if (CompareMem(DevicePath, PathList[PathIndex], DevicePathNodeLength(DevicePath)) == 0) {
        Seen = TRUE;
        break;
      }
    }
    if (Seen)
      continue;
    
    PathList[PathCount++] = AppendDevicePath(DevicePath, LegacyLoaderMediaPath);
  }
  FreePool(Handles);
  
  if (HardcodedPathList) {
    for (HardcodedIndex = 0; HardcodedPathList[HardcodedIndex] && PathCount < MaxPaths; HardcodedIndex++)
      PathList[PathCount++] = HardcodedPathList[HardcodedIndex];
  }
  PathList[PathCount] = NULL;
  return (PathCount > 0)?EFI_SUCCESS:EFI_NOT_FOUND;
}

//
// volume functions
//

static VOID ScanVolumeBootcode(IN OUT REFIT_VOLUME *Volume, OUT BOOLEAN *Bootable)
{
  EFI_STATUS              Status;
  UINT8                   *SectorBuffer;
  UINTN                   i;
  //MBR_PARTITION_INFO      *MbrTable;
  //BOOLEAN                 MbrTableFound;
  UINTN       BlockSize = 0;
  CHAR16      volumeName[255];
  CHAR8         tmp[64];
  UINT32        VCrc32;
  //  CHAR16      *kind = NULL;
  
  Volume->HasBootCode = FALSE;
  Volume->LegacyOS->IconName = NULL;
  Volume->LegacyOS->Name = NULL;
  //  Volume->BootType = BOOTING_BY_MBR; //default value
  Volume->BootType = BOOTING_BY_EFI;
  *Bootable = FALSE;
  
  if ((Volume->BlockIO == NULL) || (!Volume->BlockIO->Media->MediaPresent))
    return;
  ZeroMem((CHAR8*)&tmp[0], 64);
  BlockSize = Volume->BlockIO->Media->BlockSize;
  if (BlockSize > 2048)
    return;   // our buffer is too small... the bred of thieve of cable
  SectorBuffer = AllocateAlignedPages(EFI_SIZE_TO_PAGES (2048), 16); //align to 16 byte?! Poher
  ZeroMem((CHAR8*)&SectorBuffer[0], 2048);
  // look at the boot sector (this is used for both hard disks and El Torito images!)
  Status = Volume->BlockIO->ReadBlocks(Volume->BlockIO, Volume->BlockIO->Media->MediaId,
                                       Volume->BlockIOOffset /*start lba*/,
                                       2048, SectorBuffer);
  if (!EFI_ERROR(Status) && (SectorBuffer[1] != 0)) {
    // calc crc checksum of first 2 sectors - it's used later for legacy boot BIOS drive num detection
    // note: possible future issues with AF 4K disks
    *Bootable = TRUE;
    Volume->HasBootCode = TRUE; //we assume that all CD are bootable
    /*      DBG("check SectorBuffer\n");
     for (i=0; i<32; i++) {
     DBG("%2x ", SectorBuffer[i]);
     }
     DBG("\n"); */
    VCrc32 = GetCrc32(SectorBuffer, 512 * 2);
    Volume->DriveCRC32 = VCrc32;
    //gBS->CalculateCrc32 (SectorBuffer, 2 * 512, &Volume->DriveCRC32);
    /*    switch (Volume->DiskKind ) {
     case DISK_KIND_OPTICAL:
     kind = L"DVD";
     break;
     case DISK_KIND_INTERNAL:
     kind = L"HDD";
     break;
     case DISK_KIND_EXTERNAL:
     kind = L"USB";
     break;
     default:
     break;
     }
     DBG("Volume kind=%s CRC=0x%x\n", kind, VCrc32); */
    if (Volume->DiskKind == DISK_KIND_OPTICAL) { //CDROM
      CHAR8* p = (CHAR8*)&SectorBuffer[8];
      while (*p == 0x20) {
        p++;
      }
      for (i=0; i<30 && (*p >= 0x20) && (*p <= 'z'); i++, p++) {
        tmp[i] = *p;
      }
      tmp[i] = 0;
      while ((i>0) && (tmp[--i] == 0x20)) {}
      tmp[i+1] = 0;
			//	if (*p != 0) {
      AsciiStrToUnicodeStrS((CHAR8*)&tmp[0], volumeName, 255);
			//	}
      DBG("Detected name %s\n", volumeName);
      Volume->VolName = PoolPrint(L"%s", volumeName);
      for (i=8; i<2000; i++) { //vendor search
        if (SectorBuffer[i] == 'A') {
          if (AsciiStrStr((CHAR8*)&SectorBuffer[i], "APPLE")) {
            //		StrCpy(Volume->VolName, volumeName);
            DBG("        Found AppleDVD\n");
            Volume->LegacyOS->Type = OSTYPE_OSX;
            Volume->BootType = BOOTING_BY_CD;
            Volume->LegacyOS->IconName = L"mac";
            break;
          }
        } else if (SectorBuffer[i] == 'M') {
          if (AsciiStrStr((CHAR8*)&SectorBuffer[i], "MICROSOFT")) {
            //		StrCpy(Volume->VolName, volumeName);
            DBG("        Found Windows DVD\n");
            Volume->LegacyOS->Type = OSTYPE_WIN;
            Volume->BootType = BOOTING_BY_CD;
            Volume->LegacyOS->IconName = L"win";
            break;
          }
          
        } else if (SectorBuffer[i] == 'L') {
          if (AsciiStrStr((CHAR8*)&SectorBuffer[i], "LINUX")) {
            //		Volume->DevicePath = DuplicateDevicePath(DevicePath);
            
            //		StrCpy(Volume->VolName, volumeName);
            DBG("        Found Linux DVD\n");
            Volume->LegacyOS->Type = OSTYPE_LIN;
            Volume->BootType = BOOTING_BY_CD;
            Volume->LegacyOS->IconName = L"linux";
            break;
          }
        }
      }
      
    }
    //else HDD
    else { //HDD
      /*
       // apianti - does this detect every partition as legacy?
       if (*((UINT16 *)(SectorBuffer + 510)) == 0xaa55 && SectorBuffer[0] != 0) {
       *Bootable = TRUE;
       Volume->HasBootCode = TRUE;
       //    DBG("The volume has bootcode\n");
       Volume->LegacyOS->IconName = L"legacy";
       Volume->LegacyOS->Name = L"Legacy";
       Volume->LegacyOS->Type = OSTYPE_VAR;
       Volume->BootType = BOOTING_BY_PBR;
       }
       // */
      
      // detect specific boot codes
      if (CompareMem(SectorBuffer + 2, "LILO", 4) == 0 ||
          CompareMem(SectorBuffer + 6, "LILO", 4) == 0 ||
          CompareMem(SectorBuffer + 3, "SYSLINUX", 8) == 0 ||
          FindMem(SectorBuffer, 2048, "ISOLINUX", 8) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"linux";
        Volume->LegacyOS->Name = L"Linux";
        Volume->LegacyOS->Type = OSTYPE_LIN;
        Volume->BootType = BOOTING_BY_PBR;
        
      } else if (FindMem(SectorBuffer, 512, "Geom\0Hard Disk\0Read\0 Error", 26) >= 0) {   // GRUB
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"grub,linux";
        Volume->LegacyOS->Name = L"Linux";
        Volume->BootType = BOOTING_BY_PBR;
        /*
      } else if ((*((UINT32 *)(SectorBuffer)) == 0x4d0062e9 &&
                  *((UINT16 *)(SectorBuffer + 510)) == 0xaa55) ||
                 FindMem(SectorBuffer, 2048, "BOOT      ", 10) >= 0) { //reboot Clover
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"clover";
        Volume->LegacyOS->Name = L"Clover";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
        //        DBG("Detected Clover FAT32 bootcode\n");
     */   
        
      } else if ((*((UINT32 *)(SectorBuffer + 502)) == 0 &&
                  *((UINT32 *)(SectorBuffer + 506)) == 50000 &&
                  *((UINT16 *)(SectorBuffer + 510)) == 0xaa55) ||
                 FindMem(SectorBuffer, 2048, "Starting the BTX loader", 23) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"freebsd";
        Volume->LegacyOS->Name = L"FreeBSD";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
        
        
      } else if (FindMem(SectorBuffer, 512, "!Loading", 8) >= 0 ||
                 FindMem(SectorBuffer, 2048, "/cdboot\0/CDBOOT\0", 16) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"openbsd";
        Volume->LegacyOS->Name = L"OpenBSD";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
        
      } else if (FindMem(SectorBuffer, 512, "Not a bootxx image", 18) >= 0 ||
                 *((UINT32 *)(SectorBuffer + 1028)) == 0x7886b6d1) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"netbsd";
        Volume->LegacyOS->Name = L"NetBSD";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
        
      } else if (FindMem(SectorBuffer, 2048, "NTLDR", 5) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"win";
        Volume->LegacyOS->Name = L"Windows";
        Volume->LegacyOS->Type = OSTYPE_WIN;
        Volume->BootType = BOOTING_BY_PBR;
        
        
      } else if (FindMem(SectorBuffer, 2048, "BOOTMGR", 7) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"vista,win";
        Volume->LegacyOS->Name = L"Windows";
        Volume->LegacyOS->Type = OSTYPE_WIN;
        Volume->BootType = BOOTING_BY_PBR;
        
      } else if (FindMem(SectorBuffer, 512, "CPUBOOT SYS", 11) >= 0 ||
                 FindMem(SectorBuffer, 512, "KERNEL  SYS", 11) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"freedos";
        Volume->LegacyOS->Name = L"FreeDOS";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
/*
      } else if (FindMem(SectorBuffer, 512, "OS2LDR", 6) >= 0 ||
                 FindMem(SectorBuffer, 512, "OS2BOOT", 7) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"ecomstation";
        Volume->LegacyOS->Name = L"eComStation";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
        
      } else if (FindMem(SectorBuffer, 512, "Be Boot Loader", 14) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"beos";
        Volume->LegacyOS->Name = L"BeOS";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
        
      } else if (FindMem(SectorBuffer, 512, "yT Boot Loader", 14) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"zeta";
        Volume->LegacyOS->Name = L"ZETA";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
        
      } else if (FindMem(SectorBuffer, 512, "\x04" "beos\x06" "system\x05" "zbeos", 18) >= 0 ||
                 FindMem(SectorBuffer, 512, "haiku_loader", 12) >= 0) {
        Volume->HasBootCode = TRUE;
        Volume->LegacyOS->IconName = L"haiku";
        Volume->LegacyOS->Name = L"Haiku";
        Volume->LegacyOS->Type = OSTYPE_VAR;
        Volume->BootType = BOOTING_BY_PBR;
 */
      } 
    }
    
    // NOTE: If you add an operating system with a name that starts with 'W' or 'L', you
    //  need to fix AddLegacyEntry in main.c.
    
#if REFIT_DEBUG > 0
    DBG("        Result of bootcode detection: %s %s (%s)\n",
        Volume->HasBootCode ? L"bootable" : L"non-bootable",
        Volume->LegacyOS->Name ? Volume->LegacyOS->Name: L"unknown",
        Volume->LegacyOS->IconName ? Volume->LegacyOS->IconName: L"legacy");
#endif
    
    if (FindMem(SectorBuffer, 512, "Non-system disk", 15) >= 0)   // dummy FAT boot sector
      Volume->HasBootCode = FALSE;
    
    // check for MBR partition table
    /*
     // apianti - this is littered with bugs and probably not needed lol
     if (*((UINT16 *)(SectorBuffer + 510)) == 0xaa55) {
     MbrTableFound = FALSE;
     MbrTable = (MBR_PARTITION_INFO *)(SectorBuffer + 446);
     for (i = 0; i < 4; i++)
     if (MbrTable[i].StartLBA && MbrTable[i].Size)
     MbrTableFound = TRUE;
     for (i = 0; i < 4; i++)
     if (MbrTable[i].Flags != 0x00 && MbrTable[i].Flags != 0x80)
     MbrTableFound = FALSE;
     if (MbrTableFound) {
     Volume->MbrPartitionTable = AllocatePool(4 * 16);
     CopyMem(Volume->MbrPartitionTable, MbrTable, 4 * 16);
     Volume->BootType = BOOTING_BY_MBR;
     }
     }
     // */
  }
  gBS->FreePages((EFI_PHYSICAL_ADDRESS)(UINTN)SectorBuffer, 1);
}

//at start we have only Volume->DeviceHandle
static EFI_STATUS ScanVolume(IN OUT REFIT_VOLUME *Volume)
{
  EFI_STATUS              Status;
  EFI_DEVICE_PATH         *DevicePath, *NextDevicePath;
  EFI_DEVICE_PATH         *DiskDevicePath, *RemainingDevicePath = NULL;
  HARDDRIVE_DEVICE_PATH   *HdPath     = NULL;
  EFI_HANDLE              WholeDiskHandle;
  UINTN                   PartialLength = 0;
  UINTN                   DevicePathSize;
  //  UINTN                   BufferSize = 255;
  EFI_FILE_SYSTEM_VOLUME_LABEL *VolumeInfo;
  EFI_FILE_SYSTEM_INFO    *FileSystemInfoPtr;
  EFI_FILE_INFO           *RootInfo = NULL;
  BOOLEAN                 Bootable;
  //  EFI_INPUT_KEY           Key;
  CHAR16                  *tmpName;
  
  // get device path
  DiskDevicePath = DevicePathFromHandle(Volume->DeviceHandle);
  DevicePathSize = GetDevicePathSize (DiskDevicePath);
  Volume->DevicePath = AllocateAlignedPages(EFI_SIZE_TO_PAGES(DevicePathSize), 64);
  CopyMem(Volume->DevicePath, DiskDevicePath, DevicePathSize);
  Volume->DevicePathString = FileDevicePathToStr(Volume->DevicePath);
  
  //    Volume->DevicePath = DuplicateDevicePath(DevicePathFromHandle(Volume->DeviceHandle));
#if REFIT_DEBUG > 0
  if (Volume->DevicePath != NULL) {
    DBG(" %s\n", FileDevicePathToStr(Volume->DevicePath));
//#if REFIT_DEBUG >= 2
    //       DumpHex(1, 0, GetDevicePathSize(Volume->DevicePath), Volume->DevicePath);
//#endif
  }
#else
    DBG("\n");
#endif
  
  Volume->DiskKind = DISK_KIND_INTERNAL;  // default
  
  // get block i/o
  Status = gBS->HandleProtocol(Volume->DeviceHandle, &gEfiBlockIoProtocolGuid, (VOID **) &(Volume->BlockIO));
  if (EFI_ERROR(Status)) {
    Volume->BlockIO = NULL;
    DBG("        Warning: Can't get BlockIO protocol.\n");
    //     WaitForSingleEvent (gST->ConIn->WaitForKey, 0);
    //     gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
    
    return Status;
  }
  
  Bootable = FALSE;
  if (Volume->BlockIO->Media->BlockSize == 2048){
    DBG("        Found optical drive\n");
    Volume->DiskKind = DISK_KIND_OPTICAL;
    Volume->BlockIOOffset = 0x10; // offset already applied for FS but not for blockio
    ScanVolumeBootcode(Volume, &Bootable);
  } else {
    //        DBG("        Found HD drive\n");
    Volume->BlockIOOffset = 0;
    // scan for bootcode and MBR table
    ScanVolumeBootcode(Volume, &Bootable);
    //  DBG("        ScanVolumeBootcode success\n");
    // detect device type
    DevicePath = DuplicateDevicePath(Volume->DevicePath);
    while (DevicePath != NULL && !IsDevicePathEndType(DevicePath)) {
      NextDevicePath = NextDevicePathNode(DevicePath);
      
      if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) &&
          ((DevicePathSubType (DevicePath) == MSG_SATA_DP) ||
           (DevicePathSubType (DevicePath) == MSG_NVME_NAMESPACE_DP) ||
           (DevicePathSubType (DevicePath) == MSG_ATAPI_DP))) {
            //        DBG("        HDD volume\n");
            Volume->DiskKind = DISK_KIND_INTERNAL;
            break;
          }
      if (DevicePathType(DevicePath) == MESSAGING_DEVICE_PATH &&
          (DevicePathSubType(DevicePath) == MSG_USB_DP || DevicePathSubType(DevicePath) == MSG_USB_CLASS_DP)) {
        DBG("        USB volume\n");
        Volume->DiskKind = DISK_KIND_EXTERNAL;
        //     break;
      }
      // FIREWIRE Devices
      if (DevicePathType(DevicePath) == MESSAGING_DEVICE_PATH &&
          (DevicePathSubType(DevicePath) == MSG_1394_DP || DevicePathSubType(DevicePath) == MSG_FIBRECHANNEL_DP)) {
        //        DBG("        FireWire volume\n");
        Volume->DiskKind = DISK_KIND_FIREWIRE;
        break;
      }
      // CD-ROM Devices
      if (DevicePathType(DevicePath) == MEDIA_DEVICE_PATH &&
          DevicePathSubType(DevicePath) == MEDIA_CDROM_DP) {
        //        DBG("        CD-ROM volume\n");
        Volume->DiskKind = DISK_KIND_OPTICAL;    //it's impossible
        break;
      }
      // VENDOR Specific Path
      if (DevicePathType(DevicePath) == MEDIA_DEVICE_PATH &&
          DevicePathSubType(DevicePath) == MEDIA_VENDOR_DP) {
        //        DBG("        Vendor volume\n");
        Volume->DiskKind = DISK_KIND_NODISK;
        break;
      }
      // LEGACY CD-ROM
      if (DevicePathType(DevicePath) == BBS_DEVICE_PATH &&
          (DevicePathSubType(DevicePath) == BBS_BBS_DP || DevicePathSubType(DevicePath) == BBS_TYPE_CDROM)) {
        //        DBG("        Legacy CD-ROM volume\n");
        Volume->DiskKind = DISK_KIND_OPTICAL;
        break;
      }
      // LEGACY HARDDISK
      if (DevicePathType(DevicePath) == BBS_DEVICE_PATH &&
          (DevicePathSubType(DevicePath) == BBS_BBS_DP || DevicePathSubType(DevicePath) == BBS_TYPE_HARDDRIVE)) {
        //        DBG("        Legacy HDD volume\n");
        Volume->DiskKind = DISK_KIND_INTERNAL;
        break;
      }
      //one more we must take into account
      // subtype = MSG_NVME_NAMESPACE_DP
      // diskKind = NVME
      //#define MSG_NVME_NAMESPACE_DP     0x17
      
      DevicePath = NextDevicePath;
    }
    
    /*  what is the bread?
     // Bootable = TRUE;
     
     if (DevicePathType(DevicePath) == MEDIA_DEVICE_PATH &&
     DevicePathSubType(DevicePath) == MEDIA_VENDOR_DP) {
     Volume->IsAppleLegacy = TRUE;             // legacy BIOS device entry
     // TODO: also check for Boot Camp GUID
     //gEfiPartTypeSystemPartGuid
     Bootable = FALSE;   // this handle's BlockIO is just an alias for the whole device
     Print(L"AppleLegacy device\n");
     }
     */
  }
  
  
  DevicePath = DuplicateDevicePath(Volume->DevicePath);
  RemainingDevicePath = DevicePath; //initial value
	//
	// find the partition device path node
	//
	while (DevicePath && !IsDevicePathEnd (DevicePath)) {
		if ((DevicePathType (DevicePath) == MEDIA_DEVICE_PATH) &&
        (DevicePathSubType (DevicePath) == MEDIA_HARDDRIVE_DP)) {
      HdPath = (HARDDRIVE_DEVICE_PATH *)DevicePath;
      //			break;
		}
		DevicePath = NextDevicePathNode (DevicePath);
	}
  //  DBG("DevicePath scanned\n");
  if (HdPath) {
    //      Print(L"Partition found %s\n", DevicePathToStr((EFI_DEVICE_PATH *)HdPath));
    
    PartialLength = (UINTN)((UINT8 *)HdPath - (UINT8 *)(RemainingDevicePath));
    if (PartialLength > 0x1000) {
      PartialLength = sizeof(EFI_DEVICE_PATH); //something wrong here but I don't want to be freezed
      //       return EFI_SUCCESS;
    }
    DiskDevicePath = (EFI_DEVICE_PATH *)AllocatePool(PartialLength + sizeof(EFI_DEVICE_PATH));
    CopyMem(DiskDevicePath, Volume->DevicePath, PartialLength);
    CopyMem((UINT8 *)DiskDevicePath + PartialLength, DevicePath, sizeof(EFI_DEVICE_PATH)); //EndDevicePath
    //      Print(L"WholeDevicePath  %s\n", DevicePathToStr(DiskDevicePath));
    //      DBG("WholeDevicePath  %s\n", DevicePathToStr(DiskDevicePath));
    RemainingDevicePath = DiskDevicePath;
    Status = gBS->LocateDevicePath(&gEfiDevicePathProtocolGuid, &RemainingDevicePath, &WholeDiskHandle);
    if (EFI_ERROR(Status)) {
      DBG("Can't find WholeDevicePath: %r\n", Status);
    } else {
      Volume->WholeDiskDeviceHandle = WholeDiskHandle;
      Volume->WholeDiskDevicePath = DuplicateDevicePath(RemainingDevicePath);
      // look at the BlockIO protocol
      Status = gBS->HandleProtocol(WholeDiskHandle, &gEfiBlockIoProtocolGuid, (VOID **) &Volume->WholeDiskBlockIO);
      if (!EFI_ERROR(Status)) {
        //          DBG("WholeDiskBlockIO %x BlockSize=%d\n", Volume->WholeDiskBlockIO, Volume->WholeDiskBlockIO->Media->BlockSize);
        // check the media block size
        if (Volume->WholeDiskBlockIO->Media->BlockSize == 2048)
          Volume->DiskKind = DISK_KIND_OPTICAL;
        
      } else {
        Volume->WholeDiskBlockIO = NULL;
        DBG("no WholeDiskBlockIO: %r\n", Status);
        
        //CheckError(Status, L"from HandleProtocol");
      }
    }
    FreePool(DiskDevicePath);
  }
  /*  else {
   DBG("HD path is not found\n"); //master volume!
   }*/
  
  //  if (GlobalConfig.FastBoot) {
  //    return EFI_SUCCESS;
  //  }
  
  if (!Bootable) {
#if REFIT_DEBUG > 0
    if (Volume->HasBootCode){
      DBG("  Volume considered non-bootable, but boot code is present\n");
      //        WaitForSingleEvent (gST->ConIn->WaitForKey, 0);
      //        gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
    }
#endif
    Volume->HasBootCode = FALSE;
  }
  
  // open the root directory of the volume
  Volume->RootDir = EfiLibOpenRoot(Volume->DeviceHandle);
  //  DBG("Volume->RootDir OK\n");
  if (Volume->RootDir == NULL) {
    //Print(L"Error: Can't open volume.\n");
    // TODO: signal that we had an error
    //Slice - there is LegacyBoot volume
    //properties are set before
    //    DBG("LegacyBoot volume\n");
    
    if (HdPath) {
      tmpName = (CHAR16*)AllocateZeroPool(60);
      UnicodeSPrint(tmpName, 60, L"Legacy HD%d", HdPath->PartitionNumber);
      Volume->VolName = EfiStrDuplicate(tmpName);
      FreePool(tmpName);
    } else if (!Volume->VolName) {
      Volume->VolName =  EfiStrDuplicate(L"Whole Disc Boot");
    }
    if (!Volume->LegacyOS->IconName)
      Volume->LegacyOS->IconName = EfiStrDuplicate(L"legacy");
    
    return EFI_SUCCESS;
  }
  
  // get volume name
  if (!Volume->VolName) {
    FileSystemInfoPtr = EfiLibFileSystemInfo(Volume->RootDir);
    if (FileSystemInfoPtr) {
      //DBG("  Volume name from FileSystem: '%s'\n", FileSystemInfoPtr->VolumeLabel);
      Volume->VolName = EfiStrDuplicate(FileSystemInfoPtr->VolumeLabel);
      FreePool(FileSystemInfoPtr);
    }
  }
  if (!Volume->VolName) {
    VolumeInfo = EfiLibFileSystemVolumeLabelInfo(Volume->RootDir);
    if (VolumeInfo) {
      //DBG("  Volume name from VolumeLabel: '%s'\n", VolumeInfo->VolumeLabel);
      Volume->VolName = EfiStrDuplicate(VolumeInfo->VolumeLabel);
      FreePool(VolumeInfo);
    }
  }
  if (!Volume->VolName) {
    RootInfo = EfiLibFileInfo (Volume->RootDir);
    if (RootInfo) {
      //DBG("  Volume name from RootFile: '%s'\n", RootInfo->FileName);
      Volume->VolName = EfiStrDuplicate(RootInfo->FileName);
      FreePool(RootInfo);
    }
  }
  if (
      Volume->VolName == NULL
      || Volume->VolName[0] == 0
      || (Volume->VolName[0] == L'\\' && Volume->VolName[1] == 0)
      || (Volume->VolName[0] == L'/' && Volume->VolName[1] == 0)
      )
  {
    VOID *Instance;
    if (!EFI_ERROR (gBS->HandleProtocol(Volume->DeviceHandle, &gEfiPartTypeSystemPartGuid, &Instance))) {
      Volume->VolName = L"EFI";                                 \
    }
  }
  if (!Volume->VolName) {
//    DBG("Create unknown name\n");
    //        WaitForSingleEvent (gST->ConIn->WaitForKey, 0);
    //        gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
    
    if (HdPath) {
      
      tmpName = (CHAR16*)AllocateZeroPool(128);
      UnicodeSPrint(tmpName, 128, L"Unknown HD%d", HdPath->PartitionNumber);
      Volume->VolName = EfiStrDuplicate(tmpName);
      FreePool(tmpName);
      // NOTE: this is normal for Apple's VenMedia device paths
    } else {
      Volume->VolName = EfiStrDuplicate(L"Unknown HD"); //To be able to free it
    }
  }
  
  //Status = GetOSVersion(Volume); NOTE: Sothor - We will find icon names later once we have found boot.efi on the volume //here we set Volume->IconName (tiger,leo,snow,lion,cougar, etc)
  
  return EFI_SUCCESS;
}

static VOID ScanExtendedPartition(REFIT_VOLUME *WholeDiskVolume, MBR_PARTITION_INFO *MbrEntry)
{
  EFI_STATUS              Status;
  REFIT_VOLUME            *Volume;
  UINT32                  ExtBase, ExtCurrent, NextExtCurrent;
  UINTN                   i;
  UINTN                   LogicalPartitionIndex = 4;
  UINT8                   *SectorBuffer;
  BOOLEAN                 Bootable;
  MBR_PARTITION_INFO      *EMbrTable;
  
  ExtBase = MbrEntry->StartLBA;
  SectorBuffer = AllocateAlignedPages (EFI_SIZE_TO_PAGES (512), WholeDiskVolume->BlockIO->Media->IoAlign);
  
  for (ExtCurrent = ExtBase; ExtCurrent; ExtCurrent = NextExtCurrent) {
    // read current EMBR
    Status = WholeDiskVolume->BlockIO->ReadBlocks(WholeDiskVolume->BlockIO,
                                                  WholeDiskVolume->BlockIO->Media->MediaId,
                                                  ExtCurrent, 512, SectorBuffer);
    if (EFI_ERROR(Status))
      break;
    if (*((UINT16 *)(SectorBuffer + 510)) != 0xaa55)
      break;
    EMbrTable = (MBR_PARTITION_INFO *)(SectorBuffer + 446);
    
    // scan logical partitions in this EMBR
    NextExtCurrent = 0;
    for (i = 0; i < 4; i++) {
      if ((EMbrTable[i].Flags != 0x00 && EMbrTable[i].Flags != 0x80) ||
          EMbrTable[i].StartLBA == 0 || EMbrTable[i].Size == 0)
        break;
      if (IS_EXTENDED_PART_TYPE(EMbrTable[i].Type)) {
        // set next ExtCurrent
        NextExtCurrent = ExtBase + EMbrTable[i].StartLBA;
        break;
      } else {
        
        // found a logical partition
        Volume = AllocateZeroPool(sizeof(REFIT_VOLUME));
        Volume->DiskKind = WholeDiskVolume->DiskKind;
        Volume->IsMbrPartition = TRUE;
        Volume->MbrPartitionIndex = LogicalPartitionIndex++;
        Volume->VolName = PoolPrint(L"Partition %d", Volume->MbrPartitionIndex + 1);
        Volume->BlockIO = WholeDiskVolume->BlockIO;
        Volume->BlockIOOffset = ExtCurrent + EMbrTable[i].StartLBA;
        Volume->WholeDiskBlockIO = WholeDiskVolume->BlockIO;
        Volume->WholeDiskDeviceHandle = WholeDiskVolume->DeviceHandle;
        
        Bootable = FALSE;
        ScanVolumeBootcode(Volume, &Bootable);
        if (!Bootable)
          Volume->HasBootCode = FALSE;
        
        AddListElement((VOID ***) &Volumes, &VolumesCount, Volume);
      }
    }
  }
  gBS->FreePages((EFI_PHYSICAL_ADDRESS)(UINTN)SectorBuffer, 1);
}

VOID ScanVolumes(VOID)
{
  EFI_STATUS              Status;
  UINTN                   HandleCount = 0;
  UINTN                   HandleIndex;
  EFI_HANDLE              *Handles = NULL;
  REFIT_VOLUME            *Volume, *WholeDiskVolume;
  UINTN                   VolumeIndex, VolumeIndex2;
  MBR_PARTITION_INFO      *MbrTable;
  UINTN                   PartitionIndex;
  UINT8                   *SectorBuffer1, *SectorBuffer2;
  UINTN                   SectorSum, i;
  //  EFI_DEVICE_PATH_PROTOCOL  *VolumeDevicePath;
  //  EFI_GUID                *Guid; //for debug only
  //  EFI_INPUT_KEY Key;
  INT32                   HVi;
  
  //    DBG("Scanning volumes...\n");
  DbgHeader("ScanVolumes");
  
  // get all BlockIo handles
  Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiBlockIoProtocolGuid, NULL, &HandleCount, &Handles);
  if (Status == EFI_NOT_FOUND)
    return;
  DBG("Found %d volumes with blockIO\n", HandleCount);
  // first pass: collect information about all handles
  for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
    
    Volume = AllocateZeroPool(sizeof(REFIT_VOLUME));
    Volume->LegacyOS = AllocateZeroPool(sizeof(LEGACY_OS));
    Volume->DeviceHandle = Handles[HandleIndex];
    if (Volume->DeviceHandle == SelfDeviceHandle) {
      SelfVolume = Volume;
    }
    
    DBG("- [%02d]: Volume:", HandleIndex);
    
    Volume->Hidden = FALSE; // default to not hidden
    
    Status = ScanVolume(Volume);
    if (!EFI_ERROR(Status)) {
      
      AddListElement((VOID ***) &Volumes, &VolumesCount, Volume);
      if (!gSettings.ShowHiddenEntries) {
        for (HVi = 0; HVi < gSettings.HVCount; HVi++) {
          if (StriStr(Volume->DevicePathString, gSettings.HVHideStrings[HVi]) ||
              (Volume->VolName != NULL && StriStr(Volume->VolName, gSettings.HVHideStrings[HVi]))) {
            Volume->Hidden = TRUE;
            DBG("        hiding this volume\n");
            break;
          }
        }
      }
      
//      Guid = FindGPTPartitionGuidInDevicePath(Volume->DevicePath);
      if (!Volume->LegacyOS->IconName) {
        Volume->LegacyOS->IconName = L"legacy";
      }
//      DBG("  Volume '%s', LegacyOS '%s', LegacyIcon(s) '%s', GUID = %g\n",
//          Volume->VolName, Volume->LegacyOS->Name ? Volume->LegacyOS->Name : L"", Volume->LegacyOS->IconName, Guid);
      if (SelfVolume == Volume) {
        DBG("        This is SelfVolume !!\n");
      }
      
    } else {
      DBG("        wrong volume Nr%d?!\n", HandleIndex);
      FreePool(Volume);
    }
  }
  FreePool(Handles);
  //  DBG("Found %d volumes\n", VolumesCount);
  if (SelfVolume == NULL){
    DBG("        WARNING: SelfVolume not found"); //Slice - and what?
    SelfVolume = AllocateZeroPool(sizeof(REFIT_VOLUME));
    SelfVolume->DeviceHandle = SelfDeviceHandle;
    SelfVolume->DevicePath = SelfDevicePath;
    SelfVolume->RootDir = SelfRootDir;
    SelfVolume->DiskKind = DISK_KIND_BOOTER;
    SelfVolume->VolName = L"Clover";
    SelfVolume->LegacyOS->Type = OSTYPE_EFI;
    SelfVolume->HasBootCode = TRUE;
    SelfVolume->BootType = BOOTING_BY_PBR;
    //   AddListElement((VOID ***) &Volumes, &VolumesCount, SelfVolume);
    //    DBG("SelfVolume Nr %d created\n", VolumesCount);
  }
  
  // second pass: relate partitions and whole disk devices
  for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
    Volume = Volumes[VolumeIndex];
    
    // check MBR partition table for extended partitions
    if (Volume->BlockIO != NULL && Volume->WholeDiskBlockIO != NULL &&
        Volume->BlockIO == Volume->WholeDiskBlockIO && Volume->BlockIOOffset == 0 &&
        Volume->MbrPartitionTable != NULL) {
      DBG("        Volume %d has MBR\n", VolumeIndex);
      MbrTable = Volume->MbrPartitionTable;
      for (PartitionIndex = 0; PartitionIndex < 4; PartitionIndex++) {
        if (IS_EXTENDED_PART_TYPE(MbrTable[PartitionIndex].Type)) {
          ScanExtendedPartition(Volume, MbrTable + PartitionIndex);
        }
      }
    }
    
    // search for corresponding whole disk volume entry
    WholeDiskVolume = NULL;
    if (Volume->BlockIO != NULL && Volume->WholeDiskBlockIO != NULL &&
        Volume->BlockIO != Volume->WholeDiskBlockIO) {
      for (VolumeIndex2 = 0; VolumeIndex2 < VolumesCount; VolumeIndex2++) {
        if (Volumes[VolumeIndex2]->BlockIO == Volume->WholeDiskBlockIO &&
            Volumes[VolumeIndex2]->BlockIOOffset == 0)
          WholeDiskVolume = Volumes[VolumeIndex2];
      }
    }
    if (WholeDiskVolume != NULL && WholeDiskVolume->MbrPartitionTable != NULL) {
      // check if this volume is one of the partitions in the table
      MbrTable = WholeDiskVolume->MbrPartitionTable;
      SectorBuffer1 = AllocateAlignedPages (EFI_SIZE_TO_PAGES (512), 16);
      SectorBuffer2 = AllocateAlignedPages (EFI_SIZE_TO_PAGES (512), 16);
      
      for (PartitionIndex = 0; PartitionIndex < 4; PartitionIndex++) {
        // check size
        if ((UINT64)(MbrTable[PartitionIndex].Size) != Volume->BlockIO->Media->LastBlock + 1)
          continue;
        
        // compare boot sector read through offset vs. directly
        Status = Volume->BlockIO->ReadBlocks(Volume->BlockIO, Volume->BlockIO->Media->MediaId,
                                             Volume->BlockIOOffset, 512, SectorBuffer1);
        if (EFI_ERROR(Status))
          break;
        Status = Volume->WholeDiskBlockIO->ReadBlocks(Volume->WholeDiskBlockIO, Volume->WholeDiskBlockIO->Media->MediaId,
                                                      MbrTable[PartitionIndex].StartLBA, 512, SectorBuffer2);
        if (EFI_ERROR(Status))
          break;
        if (CompareMem(SectorBuffer1, SectorBuffer2, 512) != 0)
          continue;
        SectorSum = 0;
        for (i = 0; i < 512; i++)
          SectorSum += SectorBuffer1[i];
        if (SectorSum < 1000)
          continue;
        
        // TODO: mark entry as non-bootable if it is an extended partition
        
        // now we're reasonably sure the association is correct...
        Volume->IsMbrPartition = TRUE;
        Volume->MbrPartitionTable = MbrTable;
        Volume->MbrPartitionIndex = PartitionIndex;
        if (Volume->VolName == NULL)
          Volume->VolName = PoolPrint(L"Partition %d", PartitionIndex + 1);
        break;
      }
      
      gBS->FreePages((EFI_PHYSICAL_ADDRESS)(UINTN)SectorBuffer1, 1);
      gBS->FreePages((EFI_PHYSICAL_ADDRESS)(UINTN)SectorBuffer2, 1);
    }
    
  }
}

static VOID UninitVolumes(VOID)
{
  REFIT_VOLUME            *Volume;
  UINTN                   VolumeIndex;
  
  for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
    Volume = Volumes[VolumeIndex];
    
    if (Volume->RootDir != NULL) {
      Volume->RootDir->Close(Volume->RootDir);
      Volume->RootDir = NULL;
    }
    
    Volume->DeviceHandle = NULL;
    Volume->BlockIO = NULL;
    Volume->WholeDiskBlockIO = NULL;
    Volume->WholeDiskDeviceHandle = NULL;
    FreePool(Volume);
  }
  
  if (Volumes != NULL) {
    FreePool(Volumes);
    Volumes = NULL;
  }
  VolumesCount = 0;
  
}

VOID ReinitVolumes(VOID)
{
  EFI_STATUS              Status;
  REFIT_VOLUME            *Volume;
  UINTN                   VolumeIndex;
  UINTN           VolumesFound = 0;
  EFI_DEVICE_PATH         *RemainingDevicePath;
  EFI_HANDLE              DeviceHandle, WholeDiskHandle;
  
  for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
    Volume = Volumes[VolumeIndex];
    if (!Volume) {
      continue;
    }
    DBG("Volume %d at reinit found:\n", VolumeIndex);
    DBG("Volume->DevicePath=%s\n", FileDevicePathToStr(Volume->DevicePath));
    VolumesFound++;
    if (Volume->DevicePath != NULL) {
      // get the handle for that path
      RemainingDevicePath = Volume->DevicePath;
      Status = gBS->LocateDevicePath(&gEfiBlockIoProtocolGuid, &RemainingDevicePath, &DeviceHandle);
      
      if (!EFI_ERROR(Status)) {
        Volume->DeviceHandle = DeviceHandle;
        
        // get the root directory
        Volume->RootDir = EfiLibOpenRoot(Volume->DeviceHandle);
        
      }
      //else
      //  CheckError(Status, L"from LocateDevicePath");
    }
    
    if (Volume->WholeDiskDevicePath != NULL) {
      // get the handle for that path
      RemainingDevicePath = DuplicateDevicePath(Volume->WholeDiskDevicePath);
      Status = gBS->LocateDevicePath(&gEfiBlockIoProtocolGuid, &RemainingDevicePath, &WholeDiskHandle);
      
      if (!EFI_ERROR(Status)) {
        Volume->WholeDiskBlockIO = WholeDiskHandle;
        // get the BlockIO protocol
        Status = gBS->HandleProtocol(WholeDiskHandle, &gEfiBlockIoProtocolGuid, (VOID **) &Volume->WholeDiskBlockIO);
        if (EFI_ERROR(Status)) {
          Volume->WholeDiskBlockIO = NULL;
          CheckError(Status, L"from HandleProtocol");
        }
      }
      //else
      //  CheckError(Status, L"from LocateDevicePath");
    }
  }
  VolumesCount = VolumesFound;
}

REFIT_VOLUME *FindVolumeByName(IN CHAR16 *VolName)
{
  REFIT_VOLUME            *Volume;
  UINTN                   VolumeIndex;
  
  if (!VolName) {
    return NULL;
  }
  
  for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
    Volume = Volumes[VolumeIndex];
    if (!Volume) {
      continue;
    }
    if (Volume->VolName && StrCmp(Volume->VolName,VolName) == 0) {
      return Volume;
    }
  }
  
  return NULL;
}

//
// file and dir functions
//

BOOLEAN FileExists(IN EFI_FILE *Root, IN CHAR16 *RelativePath)
{
  EFI_STATUS  Status;
  EFI_FILE    *TestFile = NULL;
  
  Status = Root->Open(Root, &TestFile, RelativePath, EFI_FILE_MODE_READ, 0);
  if (Status == EFI_SUCCESS) {
    if (TestFile && TestFile->Close) {
      TestFile->Close(TestFile);
    }
    return TRUE;
  }
  return FALSE;
}

BOOLEAN DeleteFile(IN EFI_FILE *Root, IN CHAR16 *RelativePath)
{
  EFI_STATUS  Status;
  EFI_FILE    *File;
  EFI_FILE_INFO   *FileInfo;
  
  //DBG("DeleteFile: %s\n", RelativePath);
  // open file for read/write to see if it exists, need write for delete
  Status = Root->Open(Root, &File, RelativePath, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0);
  //DBG(" Open: %r\n", Status);
  if (Status == EFI_SUCCESS) {
    // exists - check if it is a file
    FileInfo = EfiLibFileInfo(File);
    if (FileInfo == NULL) {
      // error
      //DBG(" FileInfo is NULL\n");
      File->Close(File);
      return FALSE;
    }
    //DBG(" FileInfo attr: %x\n", FileInfo->Attribute);
    if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) == EFI_FILE_DIRECTORY) {
      // it's directory - return error
      //DBG(" File is DIR\n");
      FreePool(FileInfo);
      File->Close(File);
      return FALSE;
    }
    FreePool(FileInfo);
    // it's a file - delete it
    //DBG(" File is file\n");
    Status = File->Delete(File);
    //DBG(" Delete: %r\n", Status);
    
    return Status == EFI_SUCCESS;
  }
  return FALSE;
}

EFI_STATUS DirNextEntry(IN EFI_FILE *Directory, IN OUT EFI_FILE_INFO **DirEntry, IN UINTN FilterMode)
{
  EFI_STATUS Status;
  VOID *Buffer;
  UINTN LastBufferSize, BufferSize;
  INTN IterCount;
  
  for (;;) {
    
    // free pointer from last call
    if (*DirEntry != NULL) {
      FreePool(*DirEntry);
      *DirEntry = NULL;
    }
    
    // read next directory entry
    LastBufferSize = BufferSize = 256;
    Buffer = AllocateZeroPool (BufferSize);
    for (IterCount = 0; ; IterCount++) {
      Status = Directory->Read(Directory, &BufferSize, Buffer);
      if (Status != EFI_BUFFER_TOO_SMALL || IterCount >= 4)
        break;
      if (BufferSize <= LastBufferSize) {
        DBG("FS Driver requests bad buffer size %d (was %d), using %d instead\n", BufferSize, LastBufferSize, LastBufferSize * 2);
        BufferSize = LastBufferSize * 2;
#if REFIT_DEBUG > 0
      } else {
        DBG("Reallocating buffer from %d to %d\n", LastBufferSize, BufferSize);
#endif
      }
      Buffer = EfiReallocatePool(Buffer, LastBufferSize, BufferSize);
      LastBufferSize = BufferSize;
    }
    if (EFI_ERROR(Status)) {
      FreePool(Buffer);
      break;
    }
    
    // check for end of listing
    if (BufferSize == 0) {    // end of directory listing
      FreePool(Buffer);
      break;
    }
    
    // entry is ready to be returned
    *DirEntry = (EFI_FILE_INFO *)Buffer;
    if (*DirEntry) {
      // filter results
      if (FilterMode == 1) {   // only return directories
        if (((*DirEntry)->Attribute & EFI_FILE_DIRECTORY))
          break;
      } else if (FilterMode == 2) {   // only return files
        if (((*DirEntry)->Attribute & EFI_FILE_DIRECTORY) == 0)
          break;
      } else                   // no filter or unknown filter -> return everything
        break;
    }
  }
  return Status;
}

VOID DirIterOpen(IN EFI_FILE *BaseDir, IN CHAR16 *RelativePath OPTIONAL, OUT REFIT_DIR_ITER *DirIter)
{
  if (RelativePath == NULL) {
    DirIter->LastStatus = EFI_SUCCESS;
    DirIter->DirHandle = BaseDir;
    DirIter->CloseDirHandle = FALSE;
  } else {
    DirIter->LastStatus = BaseDir->Open(BaseDir, &(DirIter->DirHandle), RelativePath, EFI_FILE_MODE_READ, 0);
    DirIter->CloseDirHandle = EFI_ERROR(DirIter->LastStatus) ? FALSE : TRUE;
  }
  DirIter->LastFileInfo = NULL;
}

BOOLEAN DirIterNext(IN OUT REFIT_DIR_ITER *DirIter, IN UINTN FilterMode, IN CHAR16 *FilePattern OPTIONAL,
                    OUT EFI_FILE_INFO **DirEntry)
{
  if (DirIter->LastFileInfo != NULL) {
    FreePool(DirIter->LastFileInfo);
    DirIter->LastFileInfo = NULL;
  }
  
  if (EFI_ERROR(DirIter->LastStatus))
    return FALSE;   // stop iteration
  
  for (;;) {
    DirIter->LastStatus = DirNextEntry(DirIter->DirHandle, &(DirIter->LastFileInfo), FilterMode);
    if (EFI_ERROR(DirIter->LastStatus))
      return FALSE;
    if (DirIter->LastFileInfo == NULL)  // end of listing
      return FALSE;
    if (FilePattern != NULL) {
      if ((DirIter->LastFileInfo->Attribute & EFI_FILE_DIRECTORY))
        break;
      if (MetaiMatch(DirIter->LastFileInfo->FileName, FilePattern))
        break;
      // else continue loop
    } else
      break;
  }
  
  *DirEntry = DirIter->LastFileInfo;
  return TRUE;
}

EFI_STATUS DirIterClose(IN OUT REFIT_DIR_ITER *DirIter)
{
  if (DirIter->LastFileInfo != NULL) {
    FreePool(DirIter->LastFileInfo);
    DirIter->LastFileInfo = NULL;
  }
  if (DirIter->CloseDirHandle)
    DirIter->DirHandle->Close(DirIter->DirHandle);
  return DirIter->LastStatus;
}

//
// file name manipulation
//
BOOLEAN
MetaiMatch (
            IN CHAR16   *String,
            IN CHAR16   *Pattern
            )
{
	if (!mUnicodeCollation) {
		// quick fix for driver loading on UEFIs without UnicodeCollation
		//return FALSE;
		return TRUE;
	}
	return mUnicodeCollation->MetaiMatch (mUnicodeCollation, String, Pattern);
}


EFI_STATUS
InitializeUnicodeCollationProtocol (VOID)
{
	EFI_STATUS  Status;
	
	if (mUnicodeCollation != NULL) {
		return EFI_SUCCESS;
	}
	
	//
	// BUGBUG: Proper impelmentation is to locate all Unicode Collation Protocol
	// instances first and then select one which support English language.
	// Current implementation just pick the first instance.
	//
	Status = gBS->LocateProtocol (
                                &gEfiUnicodeCollation2ProtocolGuid,
                                NULL,
                                (VOID **) &mUnicodeCollation
                                );
  if (EFI_ERROR(Status)) {
    Status = gBS->LocateProtocol (
                                  &gEfiUnicodeCollationProtocolGuid,
                                  NULL,
                                  (VOID **) &mUnicodeCollation
                                  );
    
  }
	return Status;
}



CHAR16 * Basename(IN CHAR16 *Path)
{
  CHAR16  *FileName;
  UINTN   i;
  
  FileName = Path;
  
  if (Path != NULL) {
    for (i = StrLen(Path); i > 0; i--) {
      if (Path[i-1] == '\\' || Path[i-1] == '/') {
        FileName = Path + i;
        break;
      }
    }
  }
  
  return FileName;
}

VOID ReplaceExtension(IN OUT CHAR16 *Path, IN CHAR16 *Extension)
{
  INTN i;
  
  for (i = StrLen(Path); i >= 0; i--) {
    if (Path[i] == '.') {
      Path[i] = 0;
      break;
    }
    if (Path[i] == '\\' || Path[i] == '/')
      break;
  }
  StrCatS(Path, StrLen(Path)/sizeof(CHAR16)+1, Extension);
}

CHAR16 * egFindExtension(IN CHAR16 *FileName)
{
    INTN i;

    for (i = StrLen(FileName); i >= 0; i--) {
        if (FileName[i] == '.')
            return FileName + i + 1;
        if (FileName[i] == '/' || FileName[i] == '\\')
            break;
    }
    return FileName + StrLen(FileName);
}

//
// memory string search
//

INTN FindMem(IN VOID *Buffer, IN UINTN BufferLength, IN VOID *SearchString, IN UINTN SearchStringLength)
{
  UINT8 *BufferPtr;
  UINTN Offset;
  
  BufferPtr = Buffer;
  BufferLength -= SearchStringLength;
  for (Offset = 0; Offset < BufferLength; Offset++, BufferPtr++) {
    if (CompareMem(BufferPtr, SearchString, SearchStringLength) == 0)
      return (INTN)Offset;
  }
  
  return -1;
}

//
// Aptio UEFI returns File DevPath as 2 nodes (dir, file)
// and DevicePathToStr connects them with /, but we need '\\'
CHAR16 *FileDevicePathToStr(IN EFI_DEVICE_PATH_PROTOCOL *DevPath)
{
  CHAR16      *FilePath;
  CHAR16      *Char;
  CHAR16      *Tail;
  
  FilePath = DevicePathToStr(DevPath);
  // fix / into '\\'
  if (FilePath != NULL) {
    for (Char = FilePath; *Char != L'\0'; Char++) {
      if (*Char == L'/') {
        *Char = L'\\';
      }
    }
  }
  // "\\\\" into '\\'
  Char = StrStr(FilePath, L"\\\\");
  while (Char != NULL) {
//    StrCpyS(Char, 4, Char + 1);  //can't overlap
    Tail = Char + 1;
    while (*Tail != 0) {
      *(Char++) = *(Tail++);
    }
    Char = StrStr(FilePath, L"\\\\");
  }
  return FilePath;
}

CHAR16 *FileDevicePathFileToStr(IN EFI_DEVICE_PATH_PROTOCOL *DevPath)
{
  EFI_DEVICE_PATH_PROTOCOL *Node;
  
  if (DevPath == NULL) {
    return NULL;
  }
  
  Node = (EFI_DEVICE_PATH_PROTOCOL *)DevPath;
  while (!IsDevicePathEnd(Node)) {
    if ((Node->Type == MEDIA_DEVICE_PATH) &&
        (Node->SubType == MEDIA_FILEPATH_DP)) {
      return FileDevicePathToStr(Node);
    }
    Node = NextDevicePathNode(Node);
  }
  return NULL;
}

BOOLEAN DumpVariable(CHAR16* Name, EFI_GUID* Guid, INTN DevicePathAt)
{
  UINTN                     dataSize            = 0;
  UINT8                     *data               = NULL;
  UINTN i;
  EFI_STATUS  Status;
  
  Status = gRT->GetVariable (Name, Guid, NULL, &dataSize, data);
  if (Status == EFI_BUFFER_TOO_SMALL) {
    data = AllocateZeroPool(dataSize);
    Status = gRT->GetVariable (Name, Guid, NULL, &dataSize, data);
    if (EFI_ERROR(Status)) {
      DBG("Can't get %s, size=%d\n", Name, dataSize);
      FreePool(data);
      data = NULL;
    } else {
      DBG("%s var size=%d\n", Name, dataSize);
      for (i = 0; i < dataSize; i++) {
        DBG("%02x ", data[i]);
      }
      DBG("\n");
      if (DevicePathAt >= 0) {
        DBG("%s: %s\n", Name, FileDevicePathToStr((EFI_DEVICE_PATH_PROTOCOL*)&data[DevicePathAt]));
      }
    }
  }
  if (data) {
    FreePool(data);
    return TRUE;
  }
  return FALSE;
}

VOID DbgHeader(CHAR8 *str)
{
#if defined(ADVLOG)
  INTN i, len = 50 - AsciiStrLen(str);
  DebugLog (1, "=== [ %a ] ", str);
  for (i = 0; i < len; i++) DebugLog (1, "=");
  DebugLog (1, "\n");
 
#else //ADVLOG
  DebugLog (1, "%a:\n", str);
#endif //ADVLOG
}

// EOF
