mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-11-30 12:43:41 +01:00
2041 lines
87 KiB
C++
2041 lines
87 KiB
C++
/*
|
|
* refit/scan/loader.c
|
|
*
|
|
* Copyright (c) 2006-2010 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 "../cpp_foundation/XStringW.h"
|
|
#include "entry_scan.h"
|
|
#include "../Platform/Settings.h"
|
|
#include "../Platform/Hibernate.h"
|
|
#include "../refit/screen.h"
|
|
#include "../refit/menu.h"
|
|
#include "common.h"
|
|
|
|
#ifndef DEBUG_ALL
|
|
#define DEBUG_SCAN_LOADER 1
|
|
#else
|
|
#define DEBUG_SCAN_LOADER DEBUG_ALL
|
|
#endif
|
|
|
|
#if DEBUG_SCAN_LOADER == 0
|
|
#define DBG(...)
|
|
#else
|
|
#define DBG(...) DebugLog(DEBUG_SCAN_LOADER, __VA_ARGS__)
|
|
#endif
|
|
|
|
//#define DUMP_KERNEL_KEXT_PATCHES 1
|
|
|
|
#define MACOSX_LOADER_PATH L"\\System\\Library\\CoreServices\\boot.efi"
|
|
|
|
const XStringW LINUX_ISSUE_PATH = L"\\etc\\issue"_XSW;
|
|
#define LINUX_BOOT_PATH L"\\boot"
|
|
#define LINUX_BOOT_ALT_PATH L"\\boot"
|
|
const XString LINUX_LOADER_PATH = "vmlinuz"_XS;
|
|
const XStringW LINUX_FULL_LOADER_PATH = SWPrintf("%ls\\%s", LINUX_BOOT_PATH, LINUX_LOADER_PATH.c_str());
|
|
#define LINUX_LOADER_SEARCH_PATH L"vmlinuz*"
|
|
const XString LINUX_DEFAULT_OPTIONS = "ro add_efi_memmap quiet splash vt.handoff=7"_XS;
|
|
|
|
#if defined(MDE_CPU_X64)
|
|
#define BOOT_LOADER_PATH L"\\EFI\\BOOT\\BOOTX64.efi"
|
|
#else
|
|
#define BOOT_LOADER_PATH L"\\EFI\\BOOT\\BOOTIA32.efi"
|
|
#endif
|
|
|
|
extern LOADER_ENTRY *SubMenuKextInjectMgmt(LOADER_ENTRY *Entry);
|
|
|
|
// Linux loader path data
|
|
typedef struct LINUX_PATH_DATA
|
|
{
|
|
CONST CHAR16 *Path;
|
|
CONST CHAR16 *Title;
|
|
CONST CHAR16 *Icon;
|
|
CONST CHAR8 *Issue;
|
|
} LINUX_PATH_DATA;
|
|
|
|
STATIC LINUX_PATH_DATA LinuxEntryData[] = {
|
|
#if defined(MDE_CPU_X64)
|
|
{ L"\\EFI\\grub\\grubx64.efi", L"Grub EFI boot menu", L"grub,linux" },
|
|
{ L"\\EFI\\Gentoo\\grubx64.efi", L"Gentoo EFI boot menu", L"gentoo,linux", "Gentoo" },
|
|
{ L"\\EFI\\Gentoo\\kernelx64.efi", L"Gentoo EFI kernel", L"gentoo,linux" },
|
|
{ L"\\EFI\\RedHat\\grubx64.efi", L"RedHat EFI boot menu", L"redhat,linux", "Redhat" },
|
|
{ L"\\EFI\\debian\\grubx64.efi", L"Debian EFI boot menu", L"debian,linux", "Debian" },
|
|
{ L"\\EFI\\kali\\grubx64.efi", L"Kali EFI boot menu", L"kali,linux", "Kali" },
|
|
{ L"\\EFI\\ubuntu\\grubx64.efi", L"Ubuntu EFI boot menu", L"ubuntu,linux", "Ubuntu" },
|
|
{ L"\\EFI\\kubuntu\\grubx64.efi", L"kubuntu EFI boot menu", L"kubuntu,linux", "kubuntu" },
|
|
{ L"\\EFI\\LinuxMint\\grubx64.efi", L"Linux Mint EFI boot menu", L"mint,linux", "Linux Mint" },
|
|
{ L"\\EFI\\Fedora\\grubx64.efi", L"Fedora EFI boot menu", L"fedora,linux", "Fedora" },
|
|
{ L"\\EFI\\opensuse\\grubx64.efi", L"OpenSuse EFI boot menu", L"suse,linux", "openSUSE" },
|
|
{ L"\\EFI\\arch\\grubx64.efi", L"ArchLinux EFI boot menu", L"arch,linux" },
|
|
{ L"\\EFI\\arch_grub\\grubx64.efi", L"ArchLinux EFI boot menu", L"arch,linux" },
|
|
{ L"\\EFI\\ORACLE\\grubx64.efi", L"Oracle Solaris EFI boot menu", L"solaris,linux", "Solaris" },
|
|
{ L"\\EFI\\Endless\\grubx64.efi", L"EndlessOS EFI boot menu", L"endless,linux", "EndlessOS" },
|
|
{ L"\\EFI\\antergos_grub\\grubx64.efi", L"Antergos EFI boot menu", L"antergos,linux", "Antergos" },
|
|
{ L"\\EFI\\Deepin\\grubx64.efi", L"Deepin EFI boot menu", L"deepin,linux", "Deepin" },
|
|
{ L"\\EFI\\elementary\\grubx64.efi", L"Elementary EFI boot menu", L"eos,linux", "Elementary" },
|
|
{ L"\\EFI\\Manjaro\\grubx64.efi", L"Manjaro EFI boot menu", L"manjaro,linux", "Manjaro" },
|
|
{ L"\\EFI\\xubuntu\\grubx64.efi", L"Xubuntu EFI boot menu", L"xubuntu,linux", "Xubuntu" },
|
|
{ L"\\EFI\\zorin\\grubx64.efi", L"Zorin EFI boot menu", L"zorin,linux", "Zorin" },
|
|
{ L"\\EFI\\goofiboot\\goofibootx64.efi", L"Solus EFI boot menu", L"solus,linux", "Solus" },
|
|
{ L"\\EFI\\centos\\grubx64.efi", L"CentOS EFI boot menu", L"centos,linux", "CentOS" },
|
|
{ L"\\EFI\\pclinuxos\\grubx64.efi", L"PCLinuxOS EFI boot menu", L"pclinux,linux", "PCLinux" },
|
|
{ L"\\EFI\\neon\\grubx64.efi", L"KDE Neon EFI boot menu", L"neon,linux", "KDE Neon" },
|
|
{ L"\\EFI\\MX19\\grubx64.efi", L"MX Linux EFI boot menu", L"mx,linux", "MX Linux" },
|
|
{ L"\\EFI\\parrot\\grubx64.efi", L"Parrot OS EFI boot menu", L"parrot,linux", "Parrot OS" },
|
|
#else
|
|
{ L"\\EFI\\grub\\grub.efi", L"Grub EFI boot menu", L"grub,linux" },
|
|
{ L"\\EFI\\Gentoo\\grub.efi", L"Gentoo EFI boot menu", L"gentoo,linux", "Gentoo" },
|
|
{ L"\\EFI\\Gentoo\\kernel.efi", L"Gentoo EFI kernel", L"gentoo,linux" },
|
|
{ L"\\EFI\\RedHat\\grub.efi", L"RedHat EFI boot menu", L"redhat,linux", "Redhat" },
|
|
{ L"\\EFI\\debian\\grub.efi", L"Debian EFI boot menu", L"debian,linux", "Debian" },
|
|
{ L"\\EFI\\kali\\grub.efi", L"Kali EFI boot menu", L"kali,linux", "Kali" },
|
|
{ L"\\EFI\\ubuntu\\grub.efi", L"Ubuntu EFI boot menu", L"ubuntu,linux", "Ubuntu" },
|
|
{ L"\\EFI\\kubuntu\\grub.efi", L"kubuntu EFI boot menu", L"kubuntu,linux", "kubuntu" },
|
|
{ L"\\EFI\\LinuxMint\\grub.efi", L"Linux Mint EFI boot menu", L"mint,linux", "Linux Mint" },
|
|
{ L"\\EFI\\Fedora\\grub.efi", L"Fedora EFI boot menu", L"fedora,linux", "Fedora" },
|
|
{ L"\\EFI\\opensuse\\grub.efi", L"OpenSuse EFI boot menu", L"suse,linux", "openSUSE" },
|
|
{ L"\\EFI\\arch\\grub.efi", L"ArchLinux EFI boot menu", L"arch,linux" },
|
|
{ L"\\EFI\\arch_grub\\grub.efi", L"ArchLinux EFI boot menu", L"arch,linux" },
|
|
{ L"\\EFI\\ORACLE\\grub.efi", L"Oracle Solaris EFI boot menu", L"solaris,linux", "Solaris" },
|
|
{ L"\\EFI\\Endless\\grub.efi", L"EndlessOS EFI boot menu", L"endless,linux", "EndlessOS" },
|
|
{ L"\\EFI\\antergos_grub\\grub.efi", L"Antergos EFI boot menu", L"antergos,linux", "Antergos" },
|
|
{ L"\\EFI\\Deepin\\grub.efi", L"Deepin EFI boot menu", L"deepin,linux", "Deepin" },
|
|
{ L"\\EFI\\elementary\\grub.efi", L"Elementary EFI boot menu", L"eos,linux", "Elementary" },
|
|
{ L"\\EFI\\Manjaro\\grub.efi", L"Manjaro EFI boot menu", L"manjaro,linux", "Manjaro" },
|
|
{ L"\\EFI\\xubuntu\\grub.efi", L"Xubuntu EFI boot menu", L"xubuntu,linux", "Xubuntu" },
|
|
{ L"\\EFI\\zorin\\grub.efi", L"Zorin EFI boot menu", L"zorin,linux", "Zorin" },
|
|
{ L"\\EFI\\goofiboot\\goofiboot.efi", L"Solus EFI boot menu", L"solus,linux", "Solus" },
|
|
{ L"\\EFI\\centos\\grub.efi", L"CentOS EFI boot menu", L"centos,linux", "CentOS" },
|
|
{ L"\\EFI\\pclinuxos\\grub.efi", L"PCLinuxOS EFI boot menu", L"pclinux,linux", "PCLinux" },
|
|
{ L"\\EFI\\neon\\grub.efi", L"KDE Neon EFI boot menu", L"neon,linux", "KDE Neon" },
|
|
{ L"\\EFI\\MX19\\grub.efi", L"MX Linux EFI boot menu", L"mx,linux", "MX Linux" },
|
|
{ L"\\EFI\\parrot\\grub.efi", L"Parrot OS EFI boot menu", L"parrot,linux", "Parrot OS" },
|
|
#endif
|
|
{ L"\\EFI\\SuSe\\elilo.efi", L"OpenSuse EFI boot menu", L"suse,linux" },
|
|
};
|
|
|
|
/* TODO
|
|
DirIterOpen (SelfRootDir, L"\\EFI", &DirIter);
|
|
while (DirIterNext(&DirIter, 1, L"*", &DirEntry)) {
|
|
if (DirEntry->FileName[0] == '.') {
|
|
//DBG("Skip folder: %ls\n", DirEntry->FileName);
|
|
continue;
|
|
}
|
|
XStringW OSName = SWPrintf(L"%s", DirEntry->FileName);
|
|
XStringW File = SWPrintf(L"EFI\\%s\\grubx64.efi", DirEntry->FileName);
|
|
// if exist the linux = DirEntry->FileName
|
|
|
|
OSIconName = OSName + L",linux"
|
|
}
|
|
DirIterClose (&DirIter);
|
|
|
|
CONST CHAR16 *Path; -- File
|
|
CONST CHAR16 *Title; -- OSName + L" OS EFI boot menu"
|
|
CONST CHAR16 *Icon; -- OSName + L",linux"
|
|
CONST CHAR8 *Issue; -- OSName
|
|
|
|
|
|
*/
|
|
STATIC CONST UINTN LinuxEntryDataCount = (sizeof(LinuxEntryData) / sizeof(LINUX_PATH_DATA));
|
|
|
|
#if defined(ANDX86)
|
|
#if !defined(MDE_CPU_X64)
|
|
#undef ANDX86
|
|
#else
|
|
// ANDX86 loader path data
|
|
#define ANDX86_FINDLEN 3
|
|
typedef struct ANDX86_PATH_DATA
|
|
{
|
|
CONST CHAR16 *Path;
|
|
CONST CHAR16 *Title;
|
|
CONST CHAR16 *Icon;
|
|
CONST CHAR16 *Find[ANDX86_FINDLEN];
|
|
} ANDX86_PATH_DATA;
|
|
|
|
STATIC ANDX86_PATH_DATA AndroidEntryData[] = {
|
|
//{ L"\\EFI\\boot\\grubx64.efi", L"Grub", L"grub,linux" },
|
|
//{ L"\\EFI\\boot\\bootx64.efi", L"Grub", L"grub,linux" },
|
|
{ L"\\EFI\\remixos\\grubx64.efi", L"Remix", L"remix,grub,linux", { L"\\isolinux\\isolinux.bin", L"\\initrd.img", L"\\kernel" } },
|
|
{ L"\\EFI\\PhoenixOS\\boot\\grubx64.efi", L"PhoenixOS", L"phoenix,grub,linux", { L"\\EFI\\PhoenixOS\\boot\\efi.img", L"\\EFI\\PhoenixOS\\initrd.img", L"\\EFI\\PhoenixOS\\kernel" } },
|
|
{ L"\\EFI\\boot\\grubx64.efi", L"Phoenix", L"phoenix,grub,linux", { L"\\phoenix\\kernel", L"\\phoenix\\initrd.img", L"\\phoenix\\ramdisk.img" } },
|
|
{ L"\\EFI\\boot\\bootx64.efi", L"Chrome", L"chrome,grub,linux", { L"\\syslinux\\vmlinuz.A", L"\\syslinux\\vmlinuz.B", L"\\syslinux\\ldlinux.sys"} },
|
|
/*
|
|
#else
|
|
*/
|
|
};
|
|
STATIC CONST UINTN AndroidEntryDataCount = (sizeof(AndroidEntryData) / sizeof(ANDX86_PATH_DATA));
|
|
#endif
|
|
#endif
|
|
|
|
CONST CHAR16 *PaperBoot = L"\\com.apple.boot.P\\boot.efi";
|
|
CONST CHAR16 *RockBoot = L"\\com.apple.boot.R\\boot.efi";
|
|
CONST CHAR16 *ScissorBoot = L"\\com.apple.boot.S\\boot.efi";
|
|
|
|
// OS X installer paths
|
|
CONST CHAR16 *OSXInstallerPaths[] = {
|
|
L"\\.IABootFiles\\boot.efi", // 10.9 - 10.13.3
|
|
L"\\Mac OS X Install Data\\boot.efi", // 10.7
|
|
L"\\OS X Install Data\\boot.efi", // 10.8 - 10.11
|
|
L"\\macOS Install Data\\boot.efi", // 10.12 - 10.12.3
|
|
L"\\macOS Install Data\\Locked Files\\Boot Files\\boot.efi" // 10.12.4+
|
|
};
|
|
|
|
STATIC CONST UINTN OSXInstallerPathsCount = (sizeof(OSXInstallerPaths) / sizeof(CHAR16 *));
|
|
|
|
STATIC INTN TimeCmp(IN EFI_TIME *Time1,
|
|
IN EFI_TIME *Time2)
|
|
{
|
|
INTN Comparison;
|
|
if (Time1 == NULL) {
|
|
if (Time2 == NULL) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (Time2 == NULL) {
|
|
return 1;
|
|
}
|
|
Comparison = Time1->Year - Time2->Year;
|
|
if (Comparison == 0) {
|
|
Comparison = Time1->Month - Time2->Month;
|
|
if (Comparison == 0) {
|
|
Comparison = Time1->Day - Time2->Day;
|
|
if (Comparison == 0) {
|
|
Comparison = Time1->Hour - Time2->Hour;
|
|
if (Comparison == 0) {
|
|
Comparison = Time1->Minute - Time2->Minute;
|
|
if (Comparison == 0) {
|
|
Comparison = Time1->Second - Time2->Second;
|
|
if (Comparison == 0) {
|
|
Comparison = Time1->Nanosecond - Time2->Nanosecond;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Comparison;
|
|
}
|
|
|
|
UINT8 GetOSTypeFromPath(IN CONST CHAR16 *Path)
|
|
{
|
|
if (Path == NULL) {
|
|
return OSTYPE_OTHER;
|
|
}
|
|
if (StriCmp(Path, MACOSX_LOADER_PATH) == 0) {
|
|
return OSTYPE_OSX;
|
|
} else if ((StriCmp(Path, OSXInstallerPaths[0]) == 0) ||
|
|
(StriCmp(Path, OSXInstallerPaths[1]) == 0) ||
|
|
(StriCmp(Path, OSXInstallerPaths[2]) == 0) ||
|
|
(StriCmp(Path, OSXInstallerPaths[3]) == 0) ||
|
|
(StriCmp(Path, OSXInstallerPaths[4]) == 0) ||
|
|
(StriCmp(Path, RockBoot) == 0) || (StriCmp(Path, PaperBoot) == 0) || (StriCmp(Path, ScissorBoot) == 0) ||
|
|
(!StriCmp(Path, L"\\.IABootFiles\\boot.efi") && StriCmp(Path, L"\\.IAPhysicalMedia") && StriCmp(Path, MACOSX_LOADER_PATH))
|
|
) {
|
|
return OSTYPE_OSX_INSTALLER;
|
|
} else if (StriCmp(Path, L"\\com.apple.recovery.boot\\boot.efi") == 0) {
|
|
return OSTYPE_RECOVERY;
|
|
} else if ((StriCmp(Path, L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi") == 0) ||
|
|
(StriCmp(Path, L"\\EFI\\Microsoft\\Boot\\bootmgfw-orig.efi") == 0) ||
|
|
(StriCmp(Path, L"\\bootmgr.efi") == 0) ||
|
|
(StriCmp(Path, L"\\EFI\\MICROSOFT\\BOOT\\cdboot.efi") == 0)) {
|
|
return OSTYPE_WINEFI;
|
|
} else if (StrniCmp(Path, LINUX_FULL_LOADER_PATH.wc_str(), LINUX_FULL_LOADER_PATH.size()) == 0) {
|
|
return OSTYPE_LINEFI;
|
|
} else {
|
|
UINTN Index;
|
|
#if defined(ANDX86)
|
|
Index = 0;
|
|
while (Index < AndroidEntryDataCount) {
|
|
if (StriCmp(Path, AndroidEntryData[Index].Path) == 0) {
|
|
return OSTYPE_LIN;
|
|
}
|
|
++Index;
|
|
}
|
|
#endif
|
|
Index = 0;
|
|
while (Index < LinuxEntryDataCount) {
|
|
if (StriCmp(Path, LinuxEntryData[Index].Path) == 0) {
|
|
return OSTYPE_LIN;
|
|
}
|
|
++Index;
|
|
}
|
|
}
|
|
return OSTYPE_OTHER;
|
|
}
|
|
|
|
STATIC CONST CHAR16 *LinuxIconNameFromPath(IN CONST CHAR16 *Path,
|
|
IN EFI_FILE_PROTOCOL *RootDir)
|
|
{
|
|
UINTN Index;
|
|
#if defined(ANDX86)
|
|
Index = 0;
|
|
while (Index < AndroidEntryDataCount) {
|
|
if (StriCmp(Path, AndroidEntryData[Index].Path) == 0) {
|
|
return AndroidEntryData[Index].Icon;
|
|
}
|
|
++Index;
|
|
}
|
|
#endif
|
|
Index = 0;
|
|
while (Index < LinuxEntryDataCount) {
|
|
if (StriCmp(Path, LinuxEntryData[Index].Path) == 0) {
|
|
return LinuxEntryData[Index].Icon;
|
|
}
|
|
++Index;
|
|
}
|
|
// Try to open the linux issue
|
|
if ((RootDir != NULL) && (StrniCmp(Path, LINUX_FULL_LOADER_PATH.wc_str(), LINUX_FULL_LOADER_PATH.size()) == 0)) {
|
|
CHAR8 *Issue = NULL;
|
|
UINTN IssueLen = 0;
|
|
if (!EFI_ERROR(egLoadFile(RootDir, LINUX_ISSUE_PATH.wc_str(), (UINT8 **)&Issue, &IssueLen)) && (Issue != NULL)) {
|
|
if (IssueLen > 0) {
|
|
for (Index = 0; Index < LinuxEntryDataCount; ++Index) {
|
|
if ((LinuxEntryData[Index].Issue != NULL) &&
|
|
(AsciiStrStr(Issue, LinuxEntryData[Index].Issue) != NULL)) {
|
|
FreePool(Issue);
|
|
return LinuxEntryData[Index].Icon;
|
|
}
|
|
}
|
|
}
|
|
FreePool(Issue);
|
|
}
|
|
}
|
|
return L"linux";
|
|
}
|
|
|
|
STATIC CONST XString LinuxInitImagePath[] = {
|
|
"initrd%s"_XS,
|
|
"initrd.img%s"_XS,
|
|
"initrd%s.img"_XS,
|
|
"initramfs%s"_XS,
|
|
"initramfs.img%s"_XS,
|
|
"initramfs%s.img"_XS,
|
|
};
|
|
STATIC CONST UINTN LinuxInitImagePathCount = (sizeof(LinuxInitImagePath) / sizeof(CHAR16 *));
|
|
|
|
STATIC XString LinuxKernelOptions(IN EFI_FILE_PROTOCOL *Dir,
|
|
IN CONST CHAR16 *Version,
|
|
IN CONST CHAR16 *PartUUID,
|
|
IN CONST XString& Options OPTIONAL)
|
|
{
|
|
UINTN Index = 0;
|
|
if ((Dir == NULL) || (PartUUID == NULL)) {
|
|
return Options;
|
|
}
|
|
while (Index < LinuxInitImagePathCount) {
|
|
XStringW InitRd = SWPrintf(LinuxInitImagePath[Index++].c_str(), (Version == NULL) ? L"" : Version);
|
|
if (InitRd.notEmpty()) {
|
|
if (FileExists(Dir, InitRd.wc_str())) {
|
|
XString CustomOptions = SPrintf("root=/dev/disk/by-partuuid/%ls initrd=%ls\\%ls %s %s", PartUUID, LINUX_BOOT_ALT_PATH, InitRd.wc_str(), LINUX_DEFAULT_OPTIONS.c_str(), Options.c_str());
|
|
return CustomOptions;
|
|
}
|
|
}
|
|
}
|
|
return SPrintf("root=/dev/disk/by-partuuid/%ls %s %s", PartUUID, LINUX_DEFAULT_OPTIONS.c_str(), Options.c_str());
|
|
}
|
|
|
|
STATIC BOOLEAN isFirstRootUUID(REFIT_VOLUME *Volume)
|
|
{
|
|
UINTN VolumeIndex;
|
|
REFIT_VOLUME *scanedVolume;
|
|
|
|
for (VolumeIndex = 0; VolumeIndex < Volumes.size(); VolumeIndex++) {
|
|
scanedVolume = &Volumes[VolumeIndex];
|
|
|
|
if (scanedVolume == Volume)
|
|
return TRUE;
|
|
|
|
if (CompareGuid(&scanedVolume->RootUUID, &Volume->RootUUID))
|
|
return FALSE;
|
|
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//Set Entry->VolName to .disk_label.contentDetails if it exists
|
|
STATIC EFI_STATUS GetOSXVolumeName(LOADER_ENTRY *Entry)
|
|
{
|
|
EFI_STATUS Status = EFI_NOT_FOUND;
|
|
CONST CHAR16* targetNameFile = L"\\System\\Library\\CoreServices\\.disk_label.contentDetails";
|
|
CHAR8* fileBuffer;
|
|
CHAR8* targetString;
|
|
UINTN fileLen = 0;
|
|
if(FileExists(Entry->Volume->RootDir, targetNameFile)) {
|
|
Status = egLoadFile(Entry->Volume->RootDir, targetNameFile, (UINT8 **)&fileBuffer, &fileLen);
|
|
if(!EFI_ERROR(Status)) {
|
|
CHAR16 *tmpName;
|
|
//Create null terminated string
|
|
targetString = (CHAR8*) AllocateZeroPool(fileLen+1);
|
|
CopyMem( (VOID*)targetString, (VOID*)fileBuffer, fileLen);
|
|
DBG("found disk_label with contents:%s\n", targetString);
|
|
|
|
// NOTE: Sothor - This was never run. If we need this correct it and uncomment it.
|
|
// if (Entry->LoaderType == OSTYPE_OSX) {
|
|
// INTN i;
|
|
// //remove occurence number. eg: "vol_name 2" --> "vol_name"
|
|
// i=fileLen-1;
|
|
// while ((i>0) && (targetString[i]>='0') && (targetString[i]<='9')) {
|
|
// i--;
|
|
// }
|
|
// if (targetString[i] == ' ') {
|
|
// targetString[i] = 0;
|
|
// }
|
|
// }
|
|
|
|
//Convert to Unicode
|
|
tmpName = (CHAR16*)AllocateZeroPool((fileLen+1)*2);
|
|
AsciiStrToUnicodeStrS(targetString, tmpName, fileLen);
|
|
|
|
Entry->VolName = EfiStrDuplicate(tmpName);
|
|
DBG("Created name:%ls\n", Entry->VolName);
|
|
|
|
FreePool(tmpName);
|
|
FreePool(fileBuffer);
|
|
FreePool(targetString);
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
STATIC LOADER_ENTRY *CreateLoaderEntry(IN CONST CHAR16 *LoaderPath,
|
|
IN CONST XString& LoaderOptions,
|
|
IN CONST XStringW& FullTitle,
|
|
IN CONST XStringW& LoaderTitle,
|
|
IN REFIT_VOLUME *Volume,
|
|
IN XImage *Image,
|
|
IN XImage *DriveImage,
|
|
IN UINT8 OSType,
|
|
IN UINT8 Flags,
|
|
IN CHAR16 Hotkey,
|
|
EFI_GRAPHICS_OUTPUT_BLT_PIXEL BootBgColor,
|
|
IN UINT8 CustomBoot,
|
|
IN EG_IMAGE *CustomLogo,
|
|
IN KERNEL_AND_KEXT_PATCHES *Patches,
|
|
IN BOOLEAN CustomEntry)
|
|
{
|
|
EFI_DEVICE_PATH *LoaderDevicePath;
|
|
CONST CHAR16 *LoaderDevicePathString;
|
|
CONST CHAR16 *FilePathAsString;
|
|
CONST CHAR16 *OSIconName;
|
|
CHAR16 ShortcutLetter;
|
|
LOADER_ENTRY *Entry;
|
|
CONST CHAR8 *indent = " ";
|
|
|
|
// Check parameters are valid
|
|
if ((LoaderPath == NULL) || (*LoaderPath == 0) || (Volume == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
// Get the loader device path
|
|
// LoaderDevicePath = FileDevicePath(Volume->DeviceHandle, (*LoaderPath == L'\\') ? (LoaderPath + 1) : LoaderPath);
|
|
LoaderDevicePath = FileDevicePath(Volume->DeviceHandle, LoaderPath);
|
|
if (LoaderDevicePath == NULL) {
|
|
return NULL;
|
|
}
|
|
LoaderDevicePathString = FileDevicePathToStr(LoaderDevicePath);
|
|
if (LoaderDevicePathString == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// Ignore this loader if it's self path
|
|
FilePathAsString = FileDevicePathToStr(SelfFullDevicePath);
|
|
if (FilePathAsString) {
|
|
INTN Comparison = StriCmp(FilePathAsString, LoaderDevicePathString);
|
|
FreePool(FilePathAsString);
|
|
if (Comparison == 0) {
|
|
DBG("%s skipped because path `%ls` is self path!\n", indent, LoaderDevicePathString);
|
|
FreePool(LoaderDevicePathString);
|
|
return NULL;
|
|
}
|
|
}
|
|
// DBG("OSType =%d\n", OSType);
|
|
if (!CustomEntry) {
|
|
CUSTOM_LOADER_ENTRY *Custom;
|
|
UINTN CustomIndex = 0;
|
|
|
|
// Ignore this loader if it's device path is already present in another loader
|
|
// if (MainMenu.Entries) {
|
|
for (UINTN i = 0; i < MainMenu.Entries.size(); ++i) {
|
|
REFIT_ABSTRACT_MENU_ENTRY& MainEntry = MainMenu.Entries[i];
|
|
// Only want loaders
|
|
if (MainEntry.getLOADER_ENTRY()) {
|
|
if (StriCmp(MainEntry.getLOADER_ENTRY()->DevicePathString, LoaderDevicePathString) == 0) {
|
|
DBG("%s skipped because path `%ls` already exists for another entry!\n", indent, LoaderDevicePathString);
|
|
FreePool(LoaderDevicePathString);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
// }
|
|
// If this isn't a custom entry make sure it's not hidden by a custom entry
|
|
Custom = gSettings.CustomEntries;
|
|
while (Custom) {
|
|
// Check if the custom entry is hidden or disabled
|
|
if ((OSFLAG_ISSET(Custom->Flags, OSFLAG_DISABLED) ||
|
|
(OSFLAG_ISSET(Custom->Flags, OSFLAG_HIDDEN) && !gSettings.ShowHiddenEntries))) {
|
|
|
|
INTN volume_match=0;
|
|
INTN volume_type_match=0;
|
|
INTN path_match=0;
|
|
INTN type_match=0;
|
|
|
|
// Check if volume match
|
|
if (Custom->Volume != NULL) {
|
|
// Check if the string matches the volume
|
|
volume_match =
|
|
((StrStr(Volume->DevicePathString, Custom->Volume) != NULL) ||
|
|
((Volume->VolName != NULL) && (StrStr(Volume->VolName, Custom->Volume) != NULL))) ? 1 : -1;
|
|
}
|
|
|
|
// Check if the volume_type match
|
|
if (Custom->VolumeType != 0) {
|
|
volume_type_match =
|
|
(((Volume->DiskKind == DISK_KIND_INTERNAL) && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
((Volume->DiskKind == DISK_KIND_EXTERNAL) && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
((Volume->DiskKind == DISK_KIND_OPTICAL) && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
((Volume->DiskKind == DISK_KIND_FIREWIRE) && (Custom->VolumeType & VOLTYPE_FIREWIRE))) ? 1 : -1;
|
|
}
|
|
|
|
// Check if the path match
|
|
if (Custom->Path != NULL) {
|
|
// Check if the loader path match
|
|
path_match = (StriCmp(Custom->Path, LoaderPath) == 0) ? 1 : -1;
|
|
}
|
|
|
|
// Check if the type match
|
|
if (Custom->Type != 0) {
|
|
type_match = OSTYPE_COMPARE(Custom->Type, OSType) ? 1 : -1;
|
|
}
|
|
|
|
if (volume_match == -1 || volume_type_match == -1 || path_match == -1 || type_match == -1 ) {
|
|
UINTN add_comma = 0;
|
|
|
|
DBG ("%sNot match custom entry %llu: ", indent, CustomIndex);
|
|
if (volume_match != 0) {
|
|
DBG("Volume: %ls", volume_match == 1 ? L"match" : L"not match");
|
|
add_comma++;
|
|
}
|
|
if (path_match != 0) {
|
|
DBG("%lsPath: %ls",
|
|
(add_comma ? L", " : L""),
|
|
path_match == 1 ? L"match" : L"not match");
|
|
add_comma++;
|
|
}
|
|
if (volume_type_match != 0) {
|
|
DBG("%lsVolumeType: %ls",
|
|
(add_comma ? L", " : L""),
|
|
volume_type_match == 1 ? L"match" : L"not match");
|
|
add_comma++;
|
|
}
|
|
if (type_match != 0) {
|
|
DBG("%lsType: %ls",
|
|
(add_comma ? L", " : L""),
|
|
type_match == 1 ? L"match" : L"not match");
|
|
}
|
|
DBG("\n");
|
|
} else {
|
|
// Custom entry match
|
|
DBG("%sSkipped because matching custom entry %llu!\n", indent, CustomIndex);
|
|
FreePool(LoaderDevicePathString);
|
|
return NULL;
|
|
}
|
|
}
|
|
Custom = Custom->Next;
|
|
++CustomIndex;
|
|
}
|
|
}
|
|
// DBG("prepare the menu entry\n");
|
|
// prepare the menu entry
|
|
// Entry = (__typeof__(Entry))AllocateZeroPool(sizeof(LOADER_ENTRY));
|
|
Entry = new LOADER_ENTRY();
|
|
// Entry->Tag = TAG_LOADER;
|
|
Entry->Row = 0;
|
|
Entry->Volume = Volume;
|
|
|
|
Entry->LoaderPath = EfiStrDuplicate(LoaderPath);
|
|
Entry->VolName = Volume->VolName;
|
|
Entry->DevicePath = LoaderDevicePath;
|
|
Entry->DevicePathString = LoaderDevicePathString;
|
|
Entry->Flags = OSFLAG_SET(Flags, OSFLAG_USEGRAPHICS);
|
|
if (LoaderOptions.notEmpty()) {
|
|
if (OSFLAG_ISSET(Flags, OSFLAG_NODEFAULTARGS)) {
|
|
Entry->LoadOptions = LoaderOptions;
|
|
} else {
|
|
Entry->LoadOptions = SPrintf("%s %s", gSettings.BootArgs, LoaderOptions.c_str());
|
|
}
|
|
} else if ((AsciiStrLen(gSettings.BootArgs) > 0) && OSFLAG_ISUNSET(Flags, OSFLAG_NODEFAULTARGS)) {
|
|
Entry->LoadOptions = SPrintf("%s", gSettings.BootArgs);
|
|
}
|
|
//DBG("locate a custom icon \n");
|
|
// locate a custom icon for the loader
|
|
//StrCpy(IconFileName, Volume->OSIconName); Sothor - Unused?
|
|
//actions
|
|
Entry->AtClick = ActionSelect;
|
|
Entry->AtDoubleClick = ActionEnter;
|
|
Entry->AtRightClick = ActionDetails;
|
|
Entry->CustomBoot = CustomBoot;
|
|
Entry->CustomLogo = CustomLogo;
|
|
Entry->LoaderType = OSType;
|
|
Entry->BuildVersion = NULL;
|
|
Entry->OSVersion = GetOSVersion(Entry);
|
|
//DBG("OSVersion=%s \n", Entry->OSVersion);
|
|
// detect specific loaders
|
|
OSIconName = NULL;
|
|
ShortcutLetter = 0;
|
|
|
|
switch (OSType) {
|
|
case OSTYPE_OSX:
|
|
case OSTYPE_RECOVERY:
|
|
case OSTYPE_OSX_INSTALLER:
|
|
OSIconName = GetOSIconName(Entry->OSVersion);// Sothor - Get OSIcon name using OSVersion
|
|
// apianti - force custom logo even when verbose
|
|
if ( Entry->LoadOptions.ExistInIC("-v") ) {
|
|
// OSX is not booting verbose, so we can set console to graphics mode
|
|
Entry->Flags = OSFLAG_UNSET(Entry->Flags, OSFLAG_USEGRAPHICS);
|
|
}
|
|
if (OSType == OSTYPE_OSX && IsOsxHibernated(Entry)) {
|
|
Entry->Flags = OSFLAG_SET(Entry->Flags, OSFLAG_HIBERNATED);
|
|
DBG(" =>set entry as hibernated\n");
|
|
}
|
|
//always unset checkFakeSmc for installer
|
|
if (OSType == OSTYPE_OSX_INSTALLER){
|
|
Entry->Flags = OSFLAG_UNSET(Entry->Flags, OSFLAG_CHECKFAKESMC);
|
|
Entry->Flags = OSFLAG_SET(Entry->Flags, OSFLAG_WITHKEXTS);
|
|
}
|
|
ShortcutLetter = 'M';
|
|
if ((Entry->VolName == NULL) || (StrLen(Entry->VolName) == 0)) {
|
|
// else no sense to override it with dubious name
|
|
GetOSXVolumeName(Entry);
|
|
}
|
|
break;
|
|
case OSTYPE_WIN:
|
|
OSIconName = L"win";
|
|
ShortcutLetter = 'W';
|
|
break;
|
|
case OSTYPE_WINEFI:
|
|
OSIconName = L"vista,win";
|
|
//ShortcutLetter = 'V';
|
|
ShortcutLetter = 'W';
|
|
break;
|
|
case OSTYPE_LIN:
|
|
case OSTYPE_LINEFI:
|
|
OSIconName = LinuxIconNameFromPath(LoaderPath, Volume->RootDir);
|
|
ShortcutLetter = 'L';
|
|
break;
|
|
case OSTYPE_OTHER:
|
|
case OSTYPE_EFI:
|
|
OSIconName = L"clover";
|
|
ShortcutLetter = 'E';
|
|
Entry->LoaderType = OSTYPE_OTHER;
|
|
break;
|
|
default:
|
|
OSIconName = L"unknown";
|
|
Entry->LoaderType = OSTYPE_OTHER;
|
|
break;
|
|
}
|
|
//DBG("OSIconName=%ls \n", OSIconName);
|
|
Entry->Title = FullTitle;
|
|
if (Entry->Title.isEmpty() && Volume->VolLabel != NULL) {
|
|
if (Volume->VolLabel[0] == L'#') {
|
|
Entry->Title.SWPrintf("Boot %ls from %ls", (!LoaderTitle.isEmpty()) ? LoaderTitle.wc_str() : Basename(LoaderPath), Volume->VolLabel+1);
|
|
}else{
|
|
Entry->Title.SWPrintf("Boot %ls from %ls", (!LoaderTitle.isEmpty()) ? LoaderTitle.wc_str() : Basename(LoaderPath), Volume->VolLabel);
|
|
}
|
|
}
|
|
|
|
BOOLEAN BootCampStyle = ThemeX.BootCampStyle;
|
|
|
|
if ( Entry->Title.isEmpty() && ((Entry->VolName == NULL) || (StrLen(Entry->VolName) == 0)) ) {
|
|
// DBG("encounter Entry->VolName ==%ls and StrLen(Entry->VolName) ==%llu\n",Entry->VolName, StrLen(Entry->VolName));
|
|
if (BootCampStyle) {
|
|
if (!LoaderTitle.isEmpty()) {
|
|
Entry->Title = LoaderTitle;
|
|
} else {
|
|
Entry->Title.takeValueFrom(Basename(Volume->DevicePathString));
|
|
}
|
|
} else {
|
|
Entry->Title.SWPrintf("Boot %ls from %ls", (!LoaderTitle.isEmpty()) ? LoaderTitle.wc_str() : Basename(LoaderPath),
|
|
Basename(Volume->DevicePathString));
|
|
}
|
|
}
|
|
// DBG("check Entry->Title \n");
|
|
if ( Entry->Title.isEmpty() ) {
|
|
// DBG("encounter LoaderTitle ==%ls and Entry->VolName ==%ls\n", LoaderTitle.wc_str(), Entry->VolName);
|
|
if (BootCampStyle) {
|
|
if ((StriCmp(LoaderTitle.wc_str(), L"macOS") == 0) || (StriCmp(LoaderTitle.wc_str(), L"Recovery") == 0)) {
|
|
Entry->Title.takeValueFrom(Entry->VolName);
|
|
} else {
|
|
if (!LoaderTitle.isEmpty()) {
|
|
Entry->Title = LoaderTitle;
|
|
} else {
|
|
Entry->Title.takeValueFrom(Basename(LoaderPath));
|
|
}
|
|
}
|
|
} else {
|
|
Entry->Title.SWPrintf("Boot %ls from %ls", (!LoaderTitle.isEmpty()) ? LoaderTitle.wc_str() : Basename(LoaderPath),
|
|
Entry->VolName);
|
|
}
|
|
}
|
|
// DBG("Entry->Title =%ls\n", Entry->Title.wc_str());
|
|
// just an example that UI can show hibernated volume to the user
|
|
// should be better to show it on entry image
|
|
if (OSFLAG_ISSET(Entry->Flags, OSFLAG_HIBERNATED)) {
|
|
Entry->Title.SWPrintf("%ls (hibernated)", Entry->Title.s());
|
|
}
|
|
|
|
Entry->ShortcutLetter = (Hotkey == 0) ? ShortcutLetter : Hotkey;
|
|
|
|
// get custom volume icon if present
|
|
if (GlobalConfig.CustomIcons && FileExists(Volume->RootDir, L"\\.VolumeIcon.icns")){
|
|
Entry->Image.LoadIcns(Volume->RootDir, L"\\.VolumeIcon.icns", 128);
|
|
DBG("using VolumeIcon.icns image from Volume\n");
|
|
} else if (Image) {
|
|
Entry->Image = *Image;
|
|
} else {
|
|
Entry->Image = ThemeX.LoadOSIcon(OSIconName);
|
|
}
|
|
// DBG("Load DriveImage\n");
|
|
// Load DriveImage
|
|
if (DriveImage) {
|
|
// DBG("DriveImage presents\n");
|
|
Entry->DriveImage = *DriveImage;
|
|
} else {
|
|
Entry->DriveImage = ScanVolumeDefaultIcon(Volume, Entry->LoaderType, Volume->DevicePath);
|
|
}
|
|
// DBG("HideBadges=%llu Volume=%ls ", ThemeX.HideBadges, Volume->VolName);
|
|
if (ThemeX.HideBadges & HDBADGES_SHOW) {
|
|
if (ThemeX.HideBadges & HDBADGES_SWAP) {
|
|
// Entry->BadgeImage = egCopyScaledImage(Entry->DriveImage, ThemeX.BadgeScale);
|
|
Entry->BadgeImage = XImage(Entry->DriveImage, ThemeX.BadgeScale/16.f);
|
|
DBG(" Show badge as Drive.");
|
|
} else {
|
|
Entry->BadgeImage = XImage(Entry->Image, ThemeX.BadgeScale/16.f);
|
|
// Entry->BadgeImage = egCopyScaledImage((Entry->Image).ToEGImage(), ThemeX.BadgeScale);
|
|
DBG(" Show badge as OSImage.");
|
|
}
|
|
}
|
|
Entry->BootBgColor = BootBgColor;
|
|
|
|
Entry->KernelAndKextPatches = ((Patches == NULL) ? (KERNEL_AND_KEXT_PATCHES *)(((UINTN)&gSettings) + OFFSET_OF(SETTINGS_DATA, KernelAndKextPatches)) : Patches);
|
|
#ifdef DUMP_KERNEL_KEXT_PATCHES
|
|
DumpKernelAndKextPatches(Entry->KernelAndKextPatches);
|
|
#endif
|
|
DBG("%sLoader entry created for '%ls'\n", indent, Entry->DevicePathString);
|
|
return Entry;
|
|
}
|
|
|
|
STATIC VOID AddDefaultMenu(IN LOADER_ENTRY *Entry)
|
|
{
|
|
CONST CHAR16 *FileName;
|
|
// CHAR16* TempOptions;
|
|
// CHAR16 DiagsFileName[256];
|
|
LOADER_ENTRY *SubEntry;
|
|
REFIT_MENU_SCREEN *SubScreen;
|
|
REFIT_VOLUME *Volume;
|
|
UINT64 VolumeSize;
|
|
EFI_GUID *Guid = NULL;
|
|
BOOLEAN KernelIs64BitOnly;
|
|
UINT64 os_version = AsciiOSVersionToUint64(Entry->OSVersion);
|
|
|
|
if (Entry == NULL) {
|
|
return;
|
|
}
|
|
Volume = Entry->Volume;
|
|
if (Volume == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Only kernels up to 10.7 have 32-bit mode
|
|
KernelIs64BitOnly = (Entry->OSVersion == NULL ||
|
|
AsciiOSVersionToUint64(Entry->OSVersion) >= AsciiOSVersionToUint64("10.8"));
|
|
|
|
FileName = Basename(Entry->LoaderPath);
|
|
|
|
// create the submenu
|
|
// SubScreen = (__typeof__(SubScreen))AllocateZeroPool(sizeof(REFIT_MENU_SCREEN));
|
|
SubScreen = new REFIT_MENU_SCREEN;
|
|
SubScreen->Title.SWPrintf("Options for %ls on %ls", Entry->Title.wc_str(), Entry->VolName);
|
|
|
|
SubScreen->TitleImage = Entry->Image;
|
|
SubScreen->ID = Entry->LoaderType + 20;
|
|
// DBG("get anime for os=%d\n", SubScreen->ID);
|
|
SubScreen->AnimeRun = SubScreen->GetAnime();
|
|
VolumeSize = RShiftU64(MultU64x32(Volume->BlockIO->Media->LastBlock, Volume->BlockIO->Media->BlockSize), 20);
|
|
SubScreen->AddMenuInfoLine_f("Volume size: %lluMb", VolumeSize);
|
|
SubScreen->AddMenuInfoLine_f("%ls", FileDevicePathToStr(Entry->DevicePath));
|
|
Guid = FindGPTPartitionGuidInDevicePath(Volume->DevicePath);
|
|
if (Guid) {
|
|
SubScreen->AddMenuInfoLine_f("UUID: %s", strguid(Guid));
|
|
}
|
|
SubScreen->AddMenuInfoLine_f("Options: %s", Entry->LoadOptions.c_str());
|
|
// loader-specific submenu entries
|
|
if (Entry->LoaderType == OSTYPE_OSX ||
|
|
Entry->LoaderType == OSTYPE_OSX_INSTALLER ||
|
|
Entry->LoaderType == OSTYPE_RECOVERY) { // entries for Mac OS X
|
|
if (os_version < AsciiOSVersionToUint64("10.8")) {
|
|
SubScreen->AddMenuInfoLine_f("Mac OS X: %s", Entry->OSVersion);
|
|
} else if (os_version < AsciiOSVersionToUint64("10.12")) {
|
|
SubScreen->AddMenuInfoLine_f("OS X: %s", Entry->OSVersion);
|
|
} else {
|
|
SubScreen->AddMenuInfoLine_f("macOS: %s", Entry->OSVersion);
|
|
}
|
|
|
|
if (OSFLAG_ISSET(Entry->Flags, OSFLAG_HIBERNATED)) {
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
SubEntry->Title.takeValueFrom("Cancel hibernate wake");
|
|
SubEntry->Flags = OSFLAG_UNSET(SubEntry->Flags, OSFLAG_HIBERNATED);
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
}
|
|
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
if (os_version < AsciiOSVersionToUint64("10.8")) {
|
|
SubEntry->Title.takeValueFrom("Boot Mac OS X with selected options");
|
|
} else if (os_version < AsciiOSVersionToUint64("10.12")) {
|
|
SubEntry->Title.takeValueFrom("Boot OS X with selected options");
|
|
} else {
|
|
SubEntry->Title.takeValueFrom("Boot macOS with selected options");
|
|
}
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
if (os_version < AsciiOSVersionToUint64("10.8")) {
|
|
SubEntry->Title.takeValueFrom("Boot Mac OS X with injected kexts");
|
|
} else if (os_version < AsciiOSVersionToUint64("10.12")) {
|
|
SubEntry->Title.takeValueFrom("Boot OS X with injected kexts");
|
|
} else {
|
|
SubEntry->Title.takeValueFrom("Boot macOS with injected kexts");
|
|
}
|
|
SubEntry->Flags = OSFLAG_UNSET(SubEntry->Flags, OSFLAG_CHECKFAKESMC);
|
|
SubEntry->Flags = OSFLAG_SET(SubEntry->Flags, OSFLAG_WITHKEXTS);
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
if (os_version < AsciiOSVersionToUint64("10.8")) {
|
|
SubEntry->Title.takeValueFrom("Boot Mac OS X without injected kexts");
|
|
} else if (os_version < AsciiOSVersionToUint64("10.12")) {
|
|
SubEntry->Title.takeValueFrom("Boot OS X without injected kexts");
|
|
} else {
|
|
SubEntry->Title.takeValueFrom("Boot macOS without injected kexts");
|
|
}
|
|
SubEntry->Flags = OSFLAG_UNSET(SubEntry->Flags, OSFLAG_CHECKFAKESMC);
|
|
SubEntry->Flags = OSFLAG_UNSET(SubEntry->Flags, OSFLAG_WITHKEXTS);
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
|
|
SubScreen->AddMenuEntry(SubMenuKextInjectMgmt(Entry), true);
|
|
SubScreen->AddMenuInfo_f("=== boot-args ===");
|
|
if (!KernelIs64BitOnly) {
|
|
if (os_version < AsciiOSVersionToUint64("10.8")) {
|
|
SubScreen->AddMenuCheck("Mac OS X 32bit", OPT_I386, 68);
|
|
SubScreen->AddMenuCheck("Mac OS X 64bit", OPT_X64, 68);
|
|
} else if (os_version < AsciiOSVersionToUint64("10.12")) {
|
|
SubScreen->AddMenuCheck("OS X 32bit", OPT_I386, 68);
|
|
SubScreen->AddMenuCheck("OS X 64bit", OPT_X64, 68);
|
|
} else {
|
|
SubScreen->AddMenuCheck("macOS 32bit", OPT_I386, 68);
|
|
SubScreen->AddMenuCheck("macOS 64bit", OPT_X64, 68);
|
|
}
|
|
}
|
|
SubScreen->AddMenuCheck("Verbose (-v)", OPT_VERBOSE, 68);
|
|
// No Caches option works on 10.6 - 10.9
|
|
if (os_version < AsciiOSVersionToUint64("10.10")) {
|
|
SubScreen->AddMenuCheck("Without caches (-f)", OPT_NOCACHES, 68);
|
|
}
|
|
SubScreen->AddMenuCheck("Single User (-s)", OPT_SINGLE_USER, 68);
|
|
SubScreen->AddMenuCheck("Safe Mode (-x)", OPT_SAFE, 68);
|
|
SubScreen->AddMenuCheck("Disable KASLR (slide=0)", OPT_SLIDE, 68);
|
|
SubScreen->AddMenuCheck("Set Nvidia to VESA (nv_disable=1)", OPT_NVDISABLE, 68);
|
|
SubScreen->AddMenuCheck("Use Nvidia WEB drivers (nvda_drv=1)", OPT_NVWEBON, 68);
|
|
SubScreen->AddMenuCheck("Disable PowerNap (darkwake=0)", OPT_POWERNAPOFF, 68);
|
|
SubScreen->AddMenuCheck("Use XNU CPUPM (-xcpm)", OPT_XCPM, 68);
|
|
// SubScreen->AddMenuCheck("Disable Intel Idle Mode (-gux_no_idle)", OPT_GNOIDLE, 68);
|
|
// SubScreen->AddMenuCheck("Sleep Uses Shutdown (-gux_nosleep)", OPT_GNOSLEEP, 68);
|
|
// SubScreen->AddMenuCheck("Force No Msi Int (-gux_nomsi)", OPT_GNOMSI, 68);
|
|
// SubScreen->AddMenuCheck("EHC manage USB2 ports (-gux_defer_usb2)", OPT_EHCUSB, 68);
|
|
SubScreen->AddMenuCheck("Keep symbols on panic (keepsyms=1)", OPT_KEEPSYMS, 68);
|
|
SubScreen->AddMenuCheck("Don't reboot on panic (debug=0x100)", OPT_DEBUG, 68);
|
|
SubScreen->AddMenuCheck("Debug kexts (kextlog=0xffff)", OPT_KEXTLOG, 68);
|
|
// SubScreen->AddMenuCheck("Disable AppleALC (-alcoff)", OPT_APPLEALC, 68);
|
|
// SubScreen->AddMenuCheck("Disable Shiki (-shikioff)", OPT_SHIKI, 68);
|
|
|
|
if (gSettings.CsrActiveConfig == 0) {
|
|
SubScreen->AddMenuCheck("No SIP", OSFLAG_NOSIP, 69);
|
|
}
|
|
|
|
} else if (Entry->LoaderType == OSTYPE_LINEFI) {
|
|
BOOLEAN Quiet = Entry->LoadOptions.ExistIn("quiet");
|
|
BOOLEAN WithSplash = Entry->LoadOptions.ExistIn("splash");
|
|
|
|
// default entry
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
SubEntry->Title.SWPrintf("Run %ls", FileName);
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
if (Quiet) {
|
|
SubEntry->Title.SWPrintf("%ls verbose", Entry->Title.s());
|
|
SubEntry->LoadOptions = RemoveLoadOption(Entry->LoadOptions, "quiet"_XS);
|
|
} else {
|
|
SubEntry->Title.SWPrintf("%ls quiet", Entry->Title.s());
|
|
SubEntry->LoadOptions = AddLoadOption(Entry->LoadOptions, "quiet"_XS);
|
|
}
|
|
}
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
if (WithSplash) {
|
|
SubEntry->Title.SWPrintf("%ls without splash", Entry->Title.s());
|
|
SubEntry->LoadOptions = RemoveLoadOption(Entry->LoadOptions, "splash"_XS);
|
|
} else {
|
|
SubEntry->Title.SWPrintf("%ls with splash", Entry->Title.s());
|
|
SubEntry->LoadOptions = AddLoadOption(Entry->LoadOptions, "splash"_XS);
|
|
}
|
|
}
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
if (WithSplash) {
|
|
if (Quiet) {
|
|
SubEntry->Title.SWPrintf("%ls verbose without splash", Entry->Title.s());
|
|
SubEntry->LoadOptions = RemoveLoadOption(RemoveLoadOption(Entry->LoadOptions, "splash"_XS), "quiet"_XS);
|
|
} else {
|
|
SubEntry->Title.SWPrintf("%ls quiet without splash", Entry->Title.s());
|
|
SubEntry->LoadOptions = AddLoadOption(RemoveLoadOption(Entry->LoadOptions, "splash"_XS), "quiet"_XS);
|
|
}
|
|
} else if (Quiet) {
|
|
// TempOptions = RemoveLoadOption(Entry->LoadOptions, "quiet"_XS);
|
|
SubEntry->Title.SWPrintf("%ls verbose with splash", Entry->Title.s());
|
|
SubEntry->LoadOptions = AddLoadOption(Entry->LoadOptions, "splash"_XS);
|
|
// FreePool(TempOptions);
|
|
} else {
|
|
SubEntry->Title.SWPrintf("%ls quiet with splash", Entry->Title.s());
|
|
SubEntry->LoadOptions = AddLoadOption(Entry->LoadOptions, "quiet splash"_XS);
|
|
}
|
|
}
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
} else if ((Entry->LoaderType == OSTYPE_WIN) || (Entry->LoaderType == OSTYPE_WINEFI)) {
|
|
// by default, skip the built-in selection and boot from hard disk only
|
|
Entry->LoadOptions.SPrintf("-s -h");
|
|
|
|
// default entry
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
SubEntry->Title.SWPrintf("Run %ls", FileName);
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
SubEntry->Title.takeValueFrom("Boot Windows from Hard Disk");
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
SubEntry->Title.takeValueFrom("Boot Windows from CD-ROM");
|
|
SubEntry->LoadOptions.SPrintf("-s -c");
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
|
|
SubEntry = Entry->getPartiallyDuplicatedEntry();
|
|
if (SubEntry) {
|
|
SubEntry->Title.SWPrintf("Run %ls in text mode", FileName);
|
|
SubEntry->Flags = OSFLAG_UNSET(SubEntry->Flags, OSFLAG_USEGRAPHICS);
|
|
SubEntry->LoadOptions.SPrintf("-v");
|
|
SubEntry->LoaderType = OSTYPE_OTHER; // Sothor - Why are we using OSTYPE_OTHER here?
|
|
SubScreen->AddMenuEntry(SubEntry, true);
|
|
}
|
|
|
|
}
|
|
|
|
SubScreen->AddMenuEntry(&MenuEntryReturn, false);
|
|
Entry->SubScreen = SubScreen;
|
|
// DBG(" Added '%ls': OSType='%d', OSVersion='%s'\n", Entry->Title, Entry->LoaderType, Entry->OSVersion);
|
|
}
|
|
BOOLEAN AddLoaderEntry(IN CONST CHAR16 *LoaderPath, IN CONST XString& LoaderOptions,
|
|
IN CONST XStringW& LoaderTitle,
|
|
IN REFIT_VOLUME *Volume, IN XImage *Image,
|
|
IN UINT8 OSType, IN UINT8 Flags)
|
|
{
|
|
LOADER_ENTRY *Entry;
|
|
INTN HVi;
|
|
|
|
if ((LoaderPath == NULL) || (Volume == NULL) || (Volume->RootDir == NULL) || !FileExists(Volume->RootDir, LoaderPath)) {
|
|
return FALSE;
|
|
}
|
|
|
|
DBG(" AddLoaderEntry for Volume Name=%ls\n", Volume->VolName);
|
|
if (OSFLAG_ISSET(Flags, OSFLAG_DISABLED)) {
|
|
DBG(" skipped because entry is disabled\n");
|
|
return FALSE;
|
|
}
|
|
if (!gSettings.ShowHiddenEntries && OSFLAG_ISSET(Flags, OSFLAG_HIDDEN)) {
|
|
DBG(" skipped because entry is hidden\n");
|
|
return FALSE;
|
|
}
|
|
//don't add hided entries
|
|
if (!gSettings.ShowHiddenEntries) {
|
|
for (HVi = 0; HVi < gSettings.HVCount; HVi++) {
|
|
if (StriStr(LoaderPath, gSettings.HVHideStrings[HVi])) {
|
|
DBG(" hiding entry: %ls\n", LoaderPath);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
Entry = CreateLoaderEntry(LoaderPath, LoaderOptions, L""_XSW, LoaderTitle, Volume, Image, NULL, OSType, Flags, 0, MenuBackgroundPixel, CUSTOM_BOOT_DISABLED, NULL, NULL, FALSE);
|
|
if (Entry != NULL) {
|
|
if ((Entry->LoaderType == OSTYPE_OSX) ||
|
|
(Entry->LoaderType == OSTYPE_OSX_INSTALLER ) ||
|
|
(Entry->LoaderType == OSTYPE_RECOVERY)) {
|
|
if (gSettings.WithKexts) {
|
|
Entry->Flags = OSFLAG_SET(Entry->Flags, OSFLAG_WITHKEXTS);
|
|
}
|
|
if (gSettings.WithKextsIfNoFakeSMC) {
|
|
Entry->Flags = OSFLAG_SET(Entry->Flags, OSFLAG_CHECKFAKESMC);
|
|
Entry->Flags = OSFLAG_SET(Entry->Flags, OSFLAG_WITHKEXTS);
|
|
}
|
|
if (gSettings.NoCaches) {
|
|
Entry->Flags = OSFLAG_SET(Entry->Flags, OSFLAG_NOCACHES);
|
|
}
|
|
}
|
|
//TODO there is a problem that Entry->Flags is unique while InputItems are global ;(
|
|
// InputItems[69].IValue = Entry->Flags;
|
|
AddDefaultMenu(Entry);
|
|
MainMenu.AddMenuEntry(Entry, true);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
//constants
|
|
CHAR16 APFSFVBootPath[75] = L"\\00000000-0000-0000-0000-000000000000\\System\\Library\\CoreServices\\boot.efi";
|
|
CHAR16 APFSRecBootPath[47] = L"\\00000000-0000-0000-0000-000000000000\\boot.efi";
|
|
CHAR16 APFSInstallBootPath[67] = L"\\00000000-0000-0000-0000-000000000000\\com.apple.installer\\boot.efi";
|
|
|
|
#define Paper 1
|
|
#define Rock 2
|
|
#define Scissor 4
|
|
|
|
VOID AddPRSEntry(REFIT_VOLUME *Volume)
|
|
{
|
|
INTN WhatBoot = 0;
|
|
//CONST INTN Paper = 1;
|
|
//CONST INTN Rock = 2;
|
|
//CONST INTN Scissor = 4;
|
|
|
|
WhatBoot |= FileExists(Volume->RootDir, RockBoot)?Rock:0;
|
|
WhatBoot |= FileExists(Volume->RootDir, PaperBoot)?Paper:0;
|
|
WhatBoot |= FileExists(Volume->RootDir, ScissorBoot)?Scissor:0;
|
|
switch (WhatBoot) {
|
|
case Paper:
|
|
case (Paper | Rock):
|
|
AddLoaderEntry(PaperBoot, ""_XS, L"macOS InstallP"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0);
|
|
break;
|
|
case Scissor:
|
|
case (Paper | Scissor):
|
|
AddLoaderEntry(ScissorBoot, ""_XS, L"macOS InstallS"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0);
|
|
break;
|
|
case Rock:
|
|
case (Rock | Scissor):
|
|
case (Rock | Scissor | Paper):
|
|
AddLoaderEntry(RockBoot, ""_XS, L"macOS InstallR"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#undef Paper
|
|
#undef Rock
|
|
#undef Scissor
|
|
|
|
VOID ScanLoader(VOID)
|
|
{
|
|
UINTN VolumeIndex, Index;
|
|
REFIT_VOLUME *Volume;
|
|
EFI_GUID *PartGUID;
|
|
|
|
//DBG("Scanning loaders...\n");
|
|
DbgHeader("ScanLoader");
|
|
|
|
for (VolumeIndex = 0; VolumeIndex < Volumes.size(); VolumeIndex++) {
|
|
Volume = &Volumes[VolumeIndex];
|
|
if (Volume->RootDir == NULL) { // || Volume->VolName == NULL)
|
|
//DBG(", no file system\n", VolumeIndex);
|
|
continue;
|
|
}
|
|
DBG("- [%02llu]: '%ls'", VolumeIndex, Volume->VolName);
|
|
if (Volume->VolName == NULL) {
|
|
Volume->VolName = L"Unknown";
|
|
}
|
|
|
|
// skip volume if its kind is configured as disabled
|
|
if ((Volume->DiskKind == DISK_KIND_OPTICAL && (GlobalConfig.DisableFlags & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (GlobalConfig.DisableFlags & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (GlobalConfig.DisableFlags & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (GlobalConfig.DisableFlags & VOLTYPE_FIREWIRE)))
|
|
{
|
|
DBG(", hidden\n");
|
|
continue;
|
|
}
|
|
|
|
if (Volume->Hidden) {
|
|
DBG(", hidden\n");
|
|
continue;
|
|
}
|
|
DBG("\n");
|
|
|
|
// check for Mac OS X Install Data
|
|
// 1st stage - createinstallmedia
|
|
if (FileExists(Volume->RootDir, L"\\.IABootFiles\\boot.efi")) {
|
|
if (FileExists(Volume->RootDir, L"\\Install OS X Mavericks.app") ||
|
|
FileExists(Volume->RootDir, L"\\Install OS X Yosemite.app") ||
|
|
FileExists(Volume->RootDir, L"\\Install OS X El Capitan.app")) {
|
|
AddLoaderEntry(L"\\.IABootFiles\\boot.efi", ""_XS, L"OS X Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.9 - 10.11
|
|
} else {
|
|
AddLoaderEntry(L"\\.IABootFiles\\boot.efi", ""_XS, L"macOS Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.12 - 10.13.3
|
|
}
|
|
} else if (FileExists(Volume->RootDir, L"\\.IAPhysicalMedia") && FileExists(Volume->RootDir, MACOSX_LOADER_PATH)) {
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"macOS Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.13.4+
|
|
}
|
|
// 2nd stage - InstallESD/AppStore/startosinstall/Fusion Drive
|
|
AddLoaderEntry(L"\\Mac OS X Install Data\\boot.efi", ""_XS, L"Mac OS X Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.7
|
|
AddLoaderEntry(L"\\OS X Install Data\\boot.efi", ""_XS, L"OS X Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.8 - 10.11
|
|
AddLoaderEntry(L"\\macOS Install Data\\boot.efi", ""_XS, L"macOS Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.12 - 10.12.3
|
|
AddLoaderEntry(L"\\macOS Install Data\\Locked Files\\Boot Files\\boot.efi", ""_XS, L"macOS Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.12.4+
|
|
AddPRSEntry(Volume); // 10.12+
|
|
|
|
// Netinstall
|
|
AddLoaderEntry(L"\\NetInstall macOS High Sierra.nbi\\i386\\booter", ""_XS, L"macOS Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0);
|
|
// Use standard location for boot.efi, according to the install files is present
|
|
// That file indentifies a DVD/ESD/BaseSystem/Fusion Drive Install Media, so when present, check standard path to avoid entry duplication
|
|
if (FileExists(Volume->RootDir, MACOSX_LOADER_PATH)) {
|
|
if (FileExists(Volume->RootDir, L"\\System\\Installation\\CDIS\\Mac OS X Installer.app")) {
|
|
// InstallDVD/BaseSystem
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"Mac OS X Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.6/10.7
|
|
} else if (FileExists(Volume->RootDir, L"\\System\\Installation\\CDIS\\OS X Installer.app")) {
|
|
// BaseSystem
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"OS X Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.8 - 10.11
|
|
} else if (FileExists(Volume->RootDir, L"\\System\\Installation\\CDIS\\macOS Installer.app")) {
|
|
// BaseSystem
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"macOS Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.12+
|
|
} else if (FileExists(Volume->RootDir, L"\\BaseSystem.dmg") && FileExists(Volume->RootDir, L"\\mach_kernel")) {
|
|
// InstallESD
|
|
if (FileExists(Volume->RootDir, L"\\MacOSX_Media_Background.png")) {
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"Mac OS X Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.7
|
|
} else {
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"OS X Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.8
|
|
}
|
|
} else if (FileExists(Volume->RootDir, L"\\com.apple.boot.R\\System\\Library\\PrelinkedKernels\\prelinkedkernel") ||
|
|
FileExists(Volume->RootDir, L"\\com.apple.boot.P\\System\\Library\\PrelinkedKernels\\prelinkedkernel") ||
|
|
FileExists(Volume->RootDir, L"\\com.apple.boot.S\\System\\Library\\PrelinkedKernels\\prelinkedkernel")) {
|
|
if (StriStr(Volume->VolName, L"Recovery") != NULL) {
|
|
// FileVault of HFS+
|
|
// TODO: need info for 10.11 and lower
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"macOS FileVault"_XSW, Volume, NULL, OSTYPE_OSX, 0); // 10.12+
|
|
} else {
|
|
// Fusion Drive
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"OS X Install"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0); // 10.11
|
|
}
|
|
} else if (!FileExists(Volume->RootDir, L"\\.IAPhysicalMedia")) {
|
|
// Installed
|
|
if (EFI_ERROR(GetRootUUID(Volume)) || isFirstRootUUID(Volume)) {
|
|
if (!FileExists(Volume->RootDir, L"\\System\\Library\\CoreServices\\NotificationCenter.app") && !FileExists(Volume->RootDir, L"\\System\\Library\\CoreServices\\Siri.app")) {
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"Mac OS X"_XSW, Volume, NULL, OSTYPE_OSX, 0); // 10.6 - 10.7
|
|
} else if (FileExists(Volume->RootDir, L"\\System\\Library\\CoreServices\\NotificationCenter.app") && !FileExists(Volume->RootDir, L"\\System\\Library\\CoreServices\\Siri.app")) {
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"OS X"_XSW, Volume, NULL, OSTYPE_OSX, 0); // 10.8 - 10.11
|
|
} else {
|
|
AddLoaderEntry(MACOSX_LOADER_PATH, ""_XS, L"macOS"_XSW, Volume, NULL, OSTYPE_OSX, 0); // 10.12+
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* APFS Container support.
|
|
* s.mtr 2017
|
|
*/
|
|
if ((StriCmp(Volume->VolName, L"Recovery") == 0 || StriCmp(Volume->VolName, L"Preboot") == 0) && APFSSupport == TRUE) {
|
|
for (UINTN i = 0; i < APFSUUIDBankCounter + 1; i++) {
|
|
//Store current UUID
|
|
CHAR16 *CurrentUUID = GuidLEToStr((EFI_GUID *)((UINT8 *)APFSUUIDBank + i * 0x10));
|
|
//Fill with current UUID
|
|
StrnCpy(APFSFVBootPath + 1, CurrentUUID, 36);
|
|
StrnCpy(APFSRecBootPath + 1, CurrentUUID, 36);
|
|
StrnCpy(APFSInstallBootPath + 1, CurrentUUID, 36);
|
|
//Try to add FileVault entry
|
|
AddLoaderEntry(APFSFVBootPath, ""_XS, L"FileVault Prebooter"_XSW, Volume, NULL, OSTYPE_OSX, 0);
|
|
//Try to add Recovery APFS entry
|
|
AddLoaderEntry(APFSRecBootPath, ""_XS, L"Recovery"_XSW, Volume, NULL, OSTYPE_RECOVERY, 0);
|
|
//Try to add macOS install entry
|
|
AddLoaderEntry(APFSInstallBootPath, ""_XS, L"macOS Install Prebooter"_XSW, Volume, NULL, OSTYPE_OSX_INSTALLER, 0);
|
|
FreePool(CurrentUUID);
|
|
}
|
|
}
|
|
// check for Mac OS X Recovery Boot
|
|
AddLoaderEntry(L"\\com.apple.recovery.boot\\boot.efi", ""_XS, L"Recovery"_XSW, Volume, NULL, OSTYPE_RECOVERY, 0);
|
|
|
|
// Sometimes, on some systems (HP UEFI, if Win is installed first)
|
|
// it is needed to get rid of bootmgfw.efi to allow starting of
|
|
// Clover as /efi/boot/bootx64.efi from HD. We can do that by renaming
|
|
// bootmgfw.efi to bootmgfw-orig.efi
|
|
AddLoaderEntry(L"\\EFI\\microsoft\\Boot\\bootmgfw-orig.efi", ""_XS, L"Microsoft EFI"_XSW, Volume, NULL, OSTYPE_WINEFI, 0);
|
|
// check for Microsoft boot loader/menu
|
|
// If there is bootmgfw-orig.efi, then do not check for bootmgfw.efi
|
|
// since on some systems this will actually be CloverX64.efi
|
|
// renamed to bootmgfw.efi
|
|
AddLoaderEntry(L"\\EFI\\microsoft\\Boot\\bootmgfw.efi", ""_XS, L"Microsoft EFI Boot"_XSW, Volume, NULL, OSTYPE_WINEFI, 0);
|
|
// check for Microsoft boot loader/menu. This entry is redundant so excluded
|
|
// AddLoaderEntry(L"\\bootmgr.efi", L"", L"Microsoft EFI mgrboot", Volume, NULL, OSTYPE_WINEFI, 0);
|
|
// check for Microsoft boot loader/menu on CDROM
|
|
if (!AddLoaderEntry(L"\\EFI\\MICROSOFT\\BOOT\\cdboot.efi", ""_XS, L"Microsoft EFI cdboot"_XSW, Volume, NULL, OSTYPE_WINEFI, 0)) {
|
|
AddLoaderEntry(L"\\EFI\\MICROSOF\\BOOT\\CDBOOT.EFI", ""_XS, L"Microsoft EFI CDBOOT"_XSW, Volume, NULL, OSTYPE_WINEFI, 0);
|
|
}
|
|
#if defined(ANDX86)
|
|
if (TRUE) { //gSettings.AndroidScan
|
|
// check for Android loaders
|
|
for (Index = 0; Index < AndroidEntryDataCount; ++Index) {
|
|
UINTN aIndex, aFound;
|
|
if (FileExists(Volume->RootDir, AndroidEntryData[Index].Path)) {
|
|
aFound = 0;
|
|
for (aIndex = 0; aIndex < ANDX86_FINDLEN; ++aIndex) {
|
|
if ((AndroidEntryData[Index].Find[aIndex] == NULL) || FileExists(Volume->RootDir, AndroidEntryData[Index].Find[aIndex])) ++aFound;
|
|
}
|
|
if (aFound && (aFound == aIndex)) {
|
|
XImage ImageX;
|
|
ImageX.LoadXImage(ThemeX.ThemeDir, AndroidEntryData[Index].Icon);
|
|
AddLoaderEntry(AndroidEntryData[Index].Path, ""_XS, XStringW().takeValueFrom(AndroidEntryData[Index].Title), Volume,
|
|
(ImageX.isEmpty() ? NULL : &ImageX), OSTYPE_LIN, OSFLAG_NODEFAULTARGS);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (gSettings.LinuxScan) {
|
|
// check for linux loaders
|
|
for (Index = 0; Index < LinuxEntryDataCount; ++Index) {
|
|
XImage ImageX;
|
|
ImageX.LoadXImage(ThemeX.ThemeDir, LinuxEntryData[Index].Icon);
|
|
AddLoaderEntry(LinuxEntryData[Index].Path, ""_XS, XStringW().takeValueFrom(LinuxEntryData[Index].Title), Volume,
|
|
(ImageX.isEmpty() ? NULL : &ImageX), OSTYPE_LIN, OSFLAG_NODEFAULTARGS);
|
|
}
|
|
// check for linux kernels
|
|
PartGUID = FindGPTPartitionGuidInDevicePath(Volume->DevicePath);
|
|
if ((PartGUID != NULL) && (Volume->RootDir != NULL)) {
|
|
REFIT_DIR_ITER Iter;
|
|
EFI_FILE_INFO *FileInfo = NULL;
|
|
EFI_TIME PreviousTime;
|
|
CHAR16 *Path = NULL;
|
|
// CHAR16 *Options;
|
|
// Get the partition UUID and make sure it's lower case
|
|
CHAR16 PartUUID[40];
|
|
ZeroMem(&PreviousTime, sizeof(EFI_TIME));
|
|
snwprintf(PartUUID, sizeof(PartUUID), "%s", strguid(PartGUID));
|
|
StrToLower(PartUUID);
|
|
// open the /boot directory (or whatever directory path)
|
|
DirIterOpen(Volume->RootDir, LINUX_BOOT_PATH, &Iter);
|
|
// Check which kernel scan to use
|
|
switch (gSettings.KernelScan) {
|
|
case KERNEL_SCAN_FIRST:
|
|
// First kernel found only
|
|
while (DirIterNext(&Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize == 0) {
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
continue;
|
|
}
|
|
// get the kernel file path
|
|
Path = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
if (Path != NULL) {
|
|
XString Options = LinuxKernelOptions(Iter.DirHandle, Basename(Path) + LINUX_LOADER_PATH.length(), PartUUID, ""_XS);
|
|
// Add the entry
|
|
AddLoaderEntry(Path, (Options.isEmpty()) ? LINUX_DEFAULT_OPTIONS : Options, L""_XSW, Volume, NULL, OSTYPE_LINEFI, OSFLAG_NODEFAULTARGS);
|
|
|
|
FreePool(Path);
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_LAST:
|
|
// Last kernel found only
|
|
while (DirIterNext(&Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
if (Path != NULL) {
|
|
FreePool(Path);
|
|
}
|
|
Path = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
if (Path != NULL) {
|
|
XString Options = LinuxKernelOptions(Iter.DirHandle, Basename(Path) + LINUX_LOADER_PATH.length(), PartUUID, ""_XS);
|
|
// Add the entry
|
|
AddLoaderEntry(Path, (Options.isEmpty()) ? LINUX_DEFAULT_OPTIONS : Options, L""_XSW, Volume, NULL, OSTYPE_LINEFI, OSFLAG_NODEFAULTARGS);
|
|
FreePool(Path);
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_NEWEST:
|
|
// Newest dated kernel only
|
|
while (DirIterNext(&Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
if ((PreviousTime.Year == 0) || (TimeCmp(&PreviousTime, &(FileInfo->ModificationTime)) < 0)) {
|
|
if (Path != NULL) {
|
|
FreePool(Path);
|
|
}
|
|
Path = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
PreviousTime = FileInfo->ModificationTime;
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
if (Path != NULL) {
|
|
XString Options = LinuxKernelOptions(Iter.DirHandle, Basename(Path) + LINUX_LOADER_PATH.length(), PartUUID, ""_XS);
|
|
// Add the entry
|
|
AddLoaderEntry(Path, (Options.isEmpty()) ? LINUX_DEFAULT_OPTIONS : Options, L""_XSW, Volume, NULL, OSTYPE_LINEFI, OSFLAG_NODEFAULTARGS);
|
|
FreePool(Path);
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_OLDEST:
|
|
// Oldest dated kernel only
|
|
while (DirIterNext(&Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
if ((PreviousTime.Year == 0) || (TimeCmp(&PreviousTime, &(FileInfo->ModificationTime)) > 0)) {
|
|
if (Path != NULL) {
|
|
FreePool(Path);
|
|
}
|
|
Path = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
PreviousTime = FileInfo->ModificationTime;
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
if (Path != NULL) {
|
|
XString Options = LinuxKernelOptions(Iter.DirHandle, XStringW().takeValueFrom(Basename(Path) + LINUX_LOADER_PATH.length()).wc_str(), PartUUID, ""_XS);
|
|
// Add the entry
|
|
AddLoaderEntry(Path, (Options.isEmpty()) ? LINUX_DEFAULT_OPTIONS : Options, L""_XSW, Volume, NULL, OSTYPE_LINEFI, OSFLAG_NODEFAULTARGS);
|
|
FreePool(Path);
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_MOSTRECENT:
|
|
// most recent kernel version only
|
|
while (DirIterNext(&Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
CHAR16 *NewPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
if (NewPath != NULL) {
|
|
if ((Path == NULL) || (StrCmp(Path, NewPath) < 0)) {
|
|
if (Path != NULL) {
|
|
FreePool(Path);
|
|
}
|
|
Path = NewPath;
|
|
} else {
|
|
FreePool(NewPath);
|
|
}
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
if (Path != NULL) {
|
|
XString Options = LinuxKernelOptions(Iter.DirHandle, XStringW().takeValueFrom(Basename(Path) + LINUX_LOADER_PATH.length()).wc_str(), PartUUID, ""_XS);
|
|
// Add the entry
|
|
AddLoaderEntry(Path, (Options.isEmpty()) ? LINUX_DEFAULT_OPTIONS : Options, L""_XSW, Volume, NULL, OSTYPE_LINEFI, OSFLAG_NODEFAULTARGS);
|
|
|
|
FreePool(Path);
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_EARLIEST:
|
|
// earliest kernel version only
|
|
while (DirIterNext(&Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
CHAR16 *NewPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
if (NewPath != NULL) {
|
|
if ((Path == NULL) || (StrCmp(Path, NewPath) > 0)) {
|
|
if (Path != NULL) {
|
|
FreePool(Path);
|
|
}
|
|
Path = NewPath;
|
|
} else {
|
|
FreePool(NewPath);
|
|
}
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
if (Path != NULL) {
|
|
XString Options = LinuxKernelOptions(Iter.DirHandle, Basename(Path) + LINUX_LOADER_PATH.length(), PartUUID, ""_XS);
|
|
AddLoaderEntry(Path, (Options.isEmpty()) ? LINUX_DEFAULT_OPTIONS : Options, L""_XSW, Volume, NULL, OSTYPE_LINEFI, OSFLAG_NODEFAULTARGS);
|
|
FreePool(Path);
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_ALL:
|
|
// get all the filename matches
|
|
while (DirIterNext(&Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
Path = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
if (Path != NULL) {
|
|
XString Options = LinuxKernelOptions(Iter.DirHandle, Basename(Path) + LINUX_LOADER_PATH.length(), PartUUID, ""_XS);
|
|
// Add the entry
|
|
AddLoaderEntry(Path, (Options.isEmpty()) ? LINUX_DEFAULT_OPTIONS : Options, L""_XSW, Volume, NULL, OSTYPE_LINEFI, OSFLAG_NODEFAULTARGS);
|
|
FreePool(Path);
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_NONE:
|
|
default:
|
|
// No kernel scan
|
|
break;
|
|
}
|
|
//close the directory
|
|
DirIterClose(&Iter);
|
|
}
|
|
} //if linux scan
|
|
// DBG("search for optical UEFI\n");
|
|
if (Volume->DiskKind == DISK_KIND_OPTICAL) {
|
|
AddLoaderEntry(BOOT_LOADER_PATH, ""_XS, L"UEFI optical"_XSW, Volume, NULL, OSTYPE_OTHER, 0);
|
|
}
|
|
// DBG("search for internal UEFI\n");
|
|
if (Volume->DiskKind == DISK_KIND_INTERNAL) {
|
|
AddLoaderEntry(BOOT_LOADER_PATH, ""_XS, L"UEFI internal"_XSW, Volume, NULL, OSTYPE_OTHER, OSFLAG_HIDDEN);
|
|
}
|
|
// DBG("search for external UEFI\n");
|
|
if (Volume->DiskKind == DISK_KIND_EXTERNAL) {
|
|
AddLoaderEntry(BOOT_LOADER_PATH, ""_XS, L"UEFI external"_XSW, Volume, NULL, OSTYPE_OTHER, OSFLAG_HIDDEN);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
STATIC VOID AddCustomEntry(IN UINTN CustomIndex,
|
|
IN CONST CHAR16 *CustomPath,
|
|
IN CUSTOM_LOADER_ENTRY *Custom,
|
|
IN REFIT_MENU_SCREEN *SubMenu)
|
|
{
|
|
UINTN VolumeIndex;
|
|
REFIT_VOLUME *Volume;
|
|
REFIT_DIR_ITER SIter;
|
|
REFIT_DIR_ITER *Iter = &SIter;
|
|
CHAR16 PartUUID[40];
|
|
BOOLEAN IsSubEntry = (SubMenu != NULL);
|
|
BOOLEAN FindCustomPath = (CustomPath == NULL);
|
|
|
|
if (Custom == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (FindCustomPath && (Custom->Type != OSTYPE_LINEFI)) {
|
|
// DBG("Custom %lsentry %llu skipped because it didn't have a ", IsSubEntry ? L"sub " : L"", CustomIndex);
|
|
// if (Custom->Type == 0) {
|
|
// DBG("Type.\n");
|
|
// } else {
|
|
// DBG("Path.\n");
|
|
// }
|
|
return;
|
|
}
|
|
|
|
if (OSFLAG_ISSET(Custom->Flags, OSFLAG_DISABLED)) {
|
|
// DBG("Custom %lsentry %llu skipped because it is disabled.\n", IsSubEntry ? L"sub " : L"", CustomIndex);
|
|
return;
|
|
}
|
|
if (!gSettings.ShowHiddenEntries && OSFLAG_ISSET(Custom->Flags, OSFLAG_HIDDEN)) {
|
|
// DBG("Custom %lsentry %llu skipped because it is hidden.\n", IsSubEntry ? L"sub " : L"", CustomIndex);
|
|
return;
|
|
}
|
|
#if 0 //if someone want to debug this
|
|
DBG("Custom %lsentry %llu ", IsSubEntry ? L"sub " : L"", CustomIndex);
|
|
// if (Custom->Title) {
|
|
DBG("Title:\"%ls\" ", Custom->Title.wc_str());
|
|
// }
|
|
// if (Custom->FullTitle) {
|
|
DBG("FullTitle:\"%ls\" ", Custom->FullTitle.wc_str());
|
|
// }
|
|
if (CustomPath) {
|
|
DBG("Path:\"%ls\" ", CustomPath);
|
|
}
|
|
if (Custom->Options != NULL) {
|
|
DBG("Options:\"%ls\" ", Custom->Options);
|
|
}
|
|
DBG("Type:%d Flags:0x%X matching ", Custom->Type, Custom->Flags);
|
|
if (Custom->Volume) {
|
|
DBG("Volume:\"%ls\"\n", Custom->Volume);
|
|
} else {
|
|
DBG("all volumes\n");
|
|
}
|
|
#endif
|
|
|
|
for (VolumeIndex = 0; VolumeIndex < Volumes.size(); ++VolumeIndex) {
|
|
CUSTOM_LOADER_ENTRY *CustomSubEntry;
|
|
LOADER_ENTRY *Entry = NULL;
|
|
XImage Image; // = new XImage;
|
|
XImage DriveImage;
|
|
|
|
EFI_GUID *Guid = NULL;
|
|
UINT64 VolumeSize;
|
|
|
|
Volume = &Volumes[VolumeIndex];
|
|
if ((Volume == NULL) || (Volume->RootDir == NULL)) {
|
|
continue;
|
|
}
|
|
if (Volume->VolName == NULL) {
|
|
Volume->VolName = L"Unknown";
|
|
}
|
|
|
|
DBG(" Checking volume \"%ls\" (%ls) ... ", Volume->VolName, Volume->DevicePathString);
|
|
|
|
// skip volume if its kind is configured as disabled
|
|
if ((Volume->DiskKind == DISK_KIND_OPTICAL && (GlobalConfig.DisableFlags & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (GlobalConfig.DisableFlags & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (GlobalConfig.DisableFlags & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (GlobalConfig.DisableFlags & VOLTYPE_FIREWIRE)))
|
|
{
|
|
DBG("skipped because media is disabled\n");
|
|
continue;
|
|
}
|
|
|
|
if (Custom->VolumeType != 0) {
|
|
if ((Volume->DiskKind == DISK_KIND_OPTICAL && ((Custom->VolumeType & VOLTYPE_OPTICAL) == 0)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && ((Custom->VolumeType & VOLTYPE_EXTERNAL) == 0)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && ((Custom->VolumeType & VOLTYPE_INTERNAL) == 0)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && ((Custom->VolumeType & VOLTYPE_FIREWIRE) == 0))) {
|
|
DBG("skipped because media is ignored\n");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (Volume->Hidden) {
|
|
DBG("skipped because volume is hidden\n");
|
|
continue;
|
|
}
|
|
|
|
// Check for exact volume matches
|
|
if (Custom->Volume) {
|
|
|
|
if ((StrStr(Volume->DevicePathString, Custom->Volume) == NULL) &&
|
|
((Volume->VolName == NULL) || (StrStr(Volume->VolName, Custom->Volume) == NULL))) {
|
|
DBG("skipped\n");
|
|
continue;
|
|
}
|
|
// NOTE: Sothor - We dont care about legacy OS type // Check if the volume should be of certain os type
|
|
//if ((Custom->Type != 0) && (Volume->OSType != 0) && !OSTYPE_COMPARE(OSType, Volume->OSType)) {
|
|
// DBG("skipped because wrong type (%d != %d)\n", OSType, Volume->OSType);
|
|
// continue;
|
|
//}
|
|
//} else if ((Custom->Type != 0) && (Volume->OSType != 0) && !OSTYPE_COMPARE(OSType, Volume->OSType)) {
|
|
//DBG("skipped because wrong type (%d != %d)\n", OSType, Volume->OSType);
|
|
//continue;
|
|
}
|
|
// Check the volume is readable and the entry exists on the volume
|
|
if (Volume->RootDir == NULL) {
|
|
DBG("skipped because filesystem is not readable\n");
|
|
continue;
|
|
}
|
|
/*
|
|
if (StriCmp(CustomPath, MACOSX_LOADER_PATH) == 0 && FileExists(Volume->RootDir, L"\\.IAPhysicalMedia")) {
|
|
DBG("skipped standard macOS path because volume is 2nd stage Install Media\n");
|
|
continue;
|
|
} */
|
|
Guid = FindGPTPartitionGuidInDevicePath(Volume->DevicePath);
|
|
// Open the boot directory to search for kernels
|
|
if (FindCustomPath) {
|
|
EFI_FILE_INFO *FileInfo = NULL;
|
|
EFI_TIME PreviousTime;
|
|
ZeroMem(&PreviousTime, sizeof(EFI_TIME));
|
|
// Get the partition UUID and make sure it's lower case
|
|
if (Guid == NULL) {
|
|
DBG("skipped because volume does not have partition uuid\n");
|
|
continue;
|
|
}
|
|
snwprintf(PartUUID, sizeof(PartUUID), "%s", strguid(Guid));
|
|
StrToLower(PartUUID);
|
|
// open the /boot directory (or whatever directory path)
|
|
DirIterOpen(Volume->RootDir, LINUX_BOOT_PATH, Iter);
|
|
// Check if user wants to find newest kernel only
|
|
switch (Custom->KernelScan) {
|
|
case KERNEL_SCAN_FIRST:
|
|
// First kernel found only
|
|
while (DirIterNext(Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize == 0) {
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
continue;
|
|
}
|
|
// get the kernel file path
|
|
CustomPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_LAST:
|
|
// Last kernel found only
|
|
while (DirIterNext(Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
if (CustomPath != NULL) {
|
|
FreePool(CustomPath);
|
|
}
|
|
CustomPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_NEWEST:
|
|
// Newest dated kernel only
|
|
while (DirIterNext(Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
if ((PreviousTime.Year == 0) || (TimeCmp(&PreviousTime, &(FileInfo->ModificationTime)) < 0)) {
|
|
if (CustomPath != NULL) {
|
|
FreePool(CustomPath);
|
|
}
|
|
CustomPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
PreviousTime = FileInfo->ModificationTime;
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_OLDEST:
|
|
// Oldest dated kernel only
|
|
while (DirIterNext(Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
if ((PreviousTime.Year == 0) || (TimeCmp(&PreviousTime, &(FileInfo->ModificationTime)) > 0)) {
|
|
if (CustomPath != NULL) {
|
|
FreePool(CustomPath);
|
|
}
|
|
CustomPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
PreviousTime = FileInfo->ModificationTime;
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_MOSTRECENT:
|
|
// most recent kernel version only
|
|
while (DirIterNext(Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
CHAR16 *NewPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
if ((CustomPath == NULL) || (StrCmp(CustomPath, NewPath) < 0)) {
|
|
if (CustomPath != NULL) {
|
|
FreePool(CustomPath);
|
|
}
|
|
CustomPath = NewPath;
|
|
} else {
|
|
FreePool(NewPath);
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KERNEL_SCAN_EARLIEST:
|
|
// earliest kernel version only
|
|
while (DirIterNext(Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo)) {
|
|
if (FileInfo != NULL) {
|
|
if (FileInfo->FileSize > 0) {
|
|
// get the kernel file path
|
|
CHAR16 *NewPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
if ((CustomPath == NULL) || (StrCmp(CustomPath, NewPath) > 0)) {
|
|
if (CustomPath != NULL) {
|
|
FreePool(CustomPath);
|
|
}
|
|
CustomPath = NewPath;
|
|
} else {
|
|
FreePool(NewPath);
|
|
}
|
|
}
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
FileInfo = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Set scan to all just in case
|
|
Custom->KernelScan = KERNEL_SCAN_ALL;
|
|
break;
|
|
}
|
|
} else if (!FileExists(Volume->RootDir, CustomPath)) {
|
|
DBG("skipped because path does not exist\n");
|
|
continue;
|
|
}
|
|
|
|
// Change to custom image if needed
|
|
|
|
Image = Custom->Image;
|
|
if (Image.isEmpty() && Custom->ImagePath) {
|
|
Image.LoadXImage(ThemeX.ThemeDir, Custom->ImagePath);
|
|
}
|
|
|
|
// Change to custom drive image if needed
|
|
DriveImage = Custom->DriveImage;
|
|
|
|
if (DriveImage.isEmpty() && Custom->DriveImagePath) {
|
|
Image.LoadXImage(ThemeX.ThemeDir, Custom->DriveImagePath);
|
|
}
|
|
|
|
do
|
|
{
|
|
// Search for linux kernels
|
|
XString CustomOptions = Custom->Options;
|
|
if (FindCustomPath && (Custom->KernelScan == KERNEL_SCAN_ALL)) {
|
|
EFI_FILE_INFO *FileInfo = NULL;
|
|
// Get the next kernel path or stop looking
|
|
if (!DirIterNext(Iter, 2, LINUX_LOADER_SEARCH_PATH, &FileInfo) || (FileInfo == NULL)) {
|
|
DBG("\n");
|
|
break;
|
|
}
|
|
// who knows....
|
|
if (FileInfo->FileSize == 0) {
|
|
FreePool(FileInfo);
|
|
continue;
|
|
}
|
|
// get the kernel file path
|
|
CustomPath = PoolPrint(L"%s\\%s", LINUX_BOOT_PATH, FileInfo->FileName);
|
|
// free the file info
|
|
FreePool(FileInfo);
|
|
}
|
|
if (CustomPath == NULL) {
|
|
DBG("skipped\n");
|
|
break;
|
|
}
|
|
// Check to make sure we should update custom options or not
|
|
if (FindCustomPath && OSFLAG_ISUNSET(Custom->Flags, OSFLAG_NODEFAULTARGS)) {
|
|
// Find the init ram image and select root
|
|
CustomOptions = LinuxKernelOptions(Iter->DirHandle, Basename(CustomPath) + LINUX_LOADER_PATH.length(), PartUUID, Custom->Options);
|
|
}
|
|
// Check to make sure that this entry is not hidden or disabled by another custom entry
|
|
if (!IsSubEntry) {
|
|
CUSTOM_LOADER_ENTRY *Ptr;
|
|
UINTN i = 0;
|
|
BOOLEAN BetterMatch = FALSE;
|
|
for (Ptr = gSettings.CustomEntries; Ptr != NULL; ++i, Ptr = Ptr->Next) {
|
|
// Don't match against this custom
|
|
if (Ptr == Custom) {
|
|
continue;
|
|
}
|
|
// Can only match the same types
|
|
if (Custom->Type != Ptr->Type) {
|
|
continue;
|
|
}
|
|
// Check if the volume string matches
|
|
if (Custom->Volume != Ptr->Volume) {
|
|
if (Ptr->Volume == NULL) {
|
|
// Less precise volume match
|
|
if (Custom->Path != Ptr->Path) {
|
|
// Better path match
|
|
BetterMatch = ((Ptr->Path != NULL) && (StrCmp(CustomPath, Ptr->Path) == 0) &&
|
|
((Custom->VolumeType == Ptr->VolumeType) ||
|
|
(Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE))));
|
|
}
|
|
} else if ((StrStr(Volume->DevicePathString, Custom->Volume) == NULL) &&
|
|
((Volume->VolName == NULL) || (StrStr(Volume->VolName, Custom->Volume) == NULL))) {
|
|
if (Custom->Volume == NULL) {
|
|
// More precise volume match
|
|
if (Custom->Path != Ptr->Path) {
|
|
// Better path match
|
|
BetterMatch = ((Ptr->Path != NULL) && (StrCmp(CustomPath, Ptr->Path) == 0) &&
|
|
((Custom->VolumeType == Ptr->VolumeType) ||
|
|
(Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE))));
|
|
} else if (Custom->VolumeType != Ptr->VolumeType) {
|
|
// More precise volume type match
|
|
BetterMatch = ((Custom->VolumeType == 0) &&
|
|
((Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE))));
|
|
} else {
|
|
// Better match
|
|
BetterMatch = TRUE;
|
|
}
|
|
// Duplicate volume match
|
|
} else if (Custom->Path != Ptr->Path) {
|
|
// Better path match
|
|
BetterMatch = ((Ptr->Path != NULL) && (StrCmp(CustomPath, Ptr->Path) == 0) &&
|
|
((Custom->VolumeType == Ptr->VolumeType) ||
|
|
(Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE))));
|
|
// Duplicate path match
|
|
} else if (Custom->VolumeType != Ptr->VolumeType) {
|
|
// More precise volume type match
|
|
BetterMatch = ((Custom->VolumeType == 0) &&
|
|
((Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE))));
|
|
} else {
|
|
// Duplicate entry
|
|
BetterMatch = (i <= CustomIndex);
|
|
}
|
|
}
|
|
// Duplicate volume match
|
|
} else if (Custom->Path != Ptr->Path) {
|
|
if (Ptr->Path == NULL) {
|
|
// Less precise path match
|
|
BetterMatch = ((Custom->VolumeType != Ptr->VolumeType) &&
|
|
((Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE))));
|
|
} else if (StrCmp(CustomPath, Ptr->Path) == 0) {
|
|
if (Custom->Path == NULL) {
|
|
// More precise path and volume type match
|
|
BetterMatch = ((Custom->VolumeType == Ptr->VolumeType) ||
|
|
(Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE)));
|
|
} else if (Custom->VolumeType != Ptr->VolumeType) {
|
|
// More precise volume type match
|
|
BetterMatch = ((Custom->VolumeType == 0) &&
|
|
((Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE))));
|
|
} else {
|
|
// Duplicate entry
|
|
BetterMatch = (i <= CustomIndex);
|
|
}
|
|
}
|
|
// Duplicate path match
|
|
} else if (Custom->VolumeType != Ptr->VolumeType) {
|
|
// More precise volume type match
|
|
BetterMatch = ((Custom->VolumeType == 0) &&
|
|
((Volume->DiskKind == DISK_KIND_OPTICAL && (Custom->VolumeType & VOLTYPE_OPTICAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_EXTERNAL && (Custom->VolumeType & VOLTYPE_EXTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_INTERNAL && (Custom->VolumeType & VOLTYPE_INTERNAL)) ||
|
|
(Volume->DiskKind == DISK_KIND_FIREWIRE && (Custom->VolumeType & VOLTYPE_FIREWIRE))));
|
|
} else {
|
|
// Duplicate entry
|
|
BetterMatch = (i <= CustomIndex);
|
|
}
|
|
if (BetterMatch) {
|
|
break;
|
|
}
|
|
}
|
|
if (BetterMatch) {
|
|
DBG("skipped because custom entry %llu is a better match and will produce a duplicate entry\n", i);
|
|
continue;
|
|
}
|
|
}
|
|
DBG("match!\n");
|
|
// Create an entry for this volume
|
|
Entry = CreateLoaderEntry(CustomPath, CustomOptions, Custom->FullTitle, Custom->Title, Volume, &Image, &DriveImage, Custom->Type, Custom->Flags, Custom->Hotkey, Custom->BootBgColor, Custom->CustomBoot, Custom->CustomLogo, /*(KERNEL_AND_KEXT_PATCHES *)(((UINTN)Custom) + OFFSET_OF(CUSTOM_LOADER_ENTRY, KernelAndKextPatches))*/ NULL, TRUE);
|
|
|
|
if (Entry != NULL) {
|
|
DBG("Custom settings: %ls.plist will %s be applied\n",
|
|
Custom->Settings, Custom->CommonSettings?"not":"");
|
|
if (!Custom->CommonSettings) {
|
|
Entry->Settings = Custom->Settings;
|
|
}
|
|
if (OSFLAG_ISUNSET(Custom->Flags, OSFLAG_NODEFAULTMENU)) {
|
|
AddDefaultMenu(Entry);
|
|
} else if (Custom->SubEntries != NULL) {
|
|
UINTN CustomSubIndex = 0;
|
|
// Add subscreen
|
|
// REFIT_MENU_SCREEN *SubScreen = (__typeof__(SubScreen))AllocateZeroPool(sizeof(REFIT_MENU_SCREEN));
|
|
REFIT_MENU_SCREEN *SubScreen = new REFIT_MENU_SCREEN;
|
|
if (SubScreen) {
|
|
SubScreen->Title.SWPrintf("Boot Options for %ls on %ls", (Custom->Title != NULL) ? Custom->Title.wc_str() : CustomPath, Entry->VolName);
|
|
SubScreen->TitleImage = Entry->Image;
|
|
SubScreen->ID = Custom->Type + 20;
|
|
SubScreen->AnimeRun = SubScreen->GetAnime();
|
|
VolumeSize = RShiftU64(MultU64x32(Volume->BlockIO->Media->LastBlock, Volume->BlockIO->Media->BlockSize), 20);
|
|
SubScreen->AddMenuInfoLine_f("Volume size: %lldMb", VolumeSize);
|
|
SubScreen->AddMenuInfoLine_f("%ls", FileDevicePathToStr(Entry->DevicePath));
|
|
if (Guid) {
|
|
SubScreen->AddMenuInfoLine_f("UUID: %s", strguid(Guid));
|
|
}
|
|
SubScreen->AddMenuInfoLine_f("Options: %s", Entry->LoadOptions.c_str());
|
|
DBG("Create sub entries\n");
|
|
for (CustomSubEntry = Custom->SubEntries; CustomSubEntry; CustomSubEntry = CustomSubEntry->Next) {
|
|
if (!CustomSubEntry->Settings) {
|
|
CustomSubEntry->Settings = Custom->Settings;
|
|
}
|
|
AddCustomEntry(CustomSubIndex++, (CustomSubEntry->Path != NULL) ? CustomSubEntry->Path : CustomPath, CustomSubEntry, SubScreen);
|
|
}
|
|
SubScreen->AddMenuEntry(&MenuEntryReturn, true);
|
|
Entry->SubScreen = SubScreen;
|
|
}
|
|
}
|
|
if (IsSubEntry)
|
|
SubMenu->AddMenuEntry(Entry, true);
|
|
else
|
|
MainMenu.AddMenuEntry(Entry, true);
|
|
}
|
|
// cleanup custom
|
|
if (FindCustomPath) {
|
|
FreePool(CustomPath);
|
|
}
|
|
} while (FindCustomPath && (Custom->KernelScan == KERNEL_SCAN_ALL));
|
|
// Close the kernel boot directory
|
|
if (FindCustomPath) {
|
|
DirIterClose(Iter);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add custom entries
|
|
VOID AddCustomEntries(VOID)
|
|
{
|
|
CUSTOM_LOADER_ENTRY *Custom;
|
|
UINTN i = 0;
|
|
|
|
if (!gSettings.CustomEntries) {
|
|
return;
|
|
}
|
|
|
|
//DBG("Custom entries start\n");
|
|
DbgHeader("AddCustomEntries");
|
|
// Traverse the custom entries
|
|
for (Custom = gSettings.CustomEntries; Custom; ++i, Custom = Custom->Next) {
|
|
if ((Custom->Path == NULL) && (Custom->Type != 0)) {
|
|
if (OSTYPE_IS_OSX(Custom->Type)) {
|
|
AddCustomEntry(i, MACOSX_LOADER_PATH, Custom, NULL);
|
|
} else if (OSTYPE_IS_OSX_RECOVERY(Custom->Type)) {
|
|
AddCustomEntry(i, L"\\com.apple.recovery.boot\\boot.efi", Custom, NULL);
|
|
} else if (OSTYPE_IS_OSX_INSTALLER(Custom->Type)) {
|
|
UINTN Index = 0;
|
|
while (Index < OSXInstallerPathsCount) {
|
|
AddCustomEntry(i, OSXInstallerPaths[Index++], Custom, NULL);
|
|
}
|
|
} else if (OSTYPE_IS_WINDOWS(Custom->Type)) {
|
|
AddCustomEntry(i, L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi", Custom, NULL);
|
|
} else if (OSTYPE_IS_LINUX(Custom->Type)) {
|
|
UINTN Index;
|
|
#if defined(ANDX86)
|
|
Index = 0;
|
|
while (Index < AndroidEntryDataCount) {
|
|
AddCustomEntry(i, AndroidEntryData[Index++].Path, Custom, NULL);
|
|
}
|
|
#endif
|
|
Index = 0;
|
|
while (Index < LinuxEntryDataCount) {
|
|
AddCustomEntry(i, LinuxEntryData[Index++].Path, Custom, NULL);
|
|
}
|
|
} else if (Custom->Type == OSTYPE_LINEFI) {
|
|
AddCustomEntry(i, NULL, Custom, NULL);
|
|
} else {
|
|
AddCustomEntry(i, BOOT_LOADER_PATH, Custom, NULL);
|
|
}
|
|
} else {
|
|
AddCustomEntry(i, Custom->Path, Custom, NULL);
|
|
}
|
|
}
|
|
//DBG("Custom entries finish\n");
|
|
}
|