mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
1245 lines
33 KiB
C
1245 lines
33 KiB
C
/** @file
|
|
Copyright (C) 2019, vit9696. All rights reserved.
|
|
|
|
All rights reserved.
|
|
|
|
This program and the accompanying materials
|
|
are licensed and made available under the terms and conditions of the BSD License
|
|
which accompanies this distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
**/
|
|
|
|
#include "BootManagementInternal.h"
|
|
|
|
#include <Guid/AppleFile.h>
|
|
#include <Guid/AppleVariable.h>
|
|
#include <Guid/GlobalVariable.h>
|
|
#include <Guid/OcVariables.h>
|
|
|
|
#include <Protocol/LoadedImage.h>
|
|
#include <Protocol/SimpleFileSystem.h>
|
|
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/DevicePathLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/PrintLib.h>
|
|
#include <Library/OcDebugLogLib.h>
|
|
#include <Library/OcDevicePathLib.h>
|
|
#include <Library/OcFileLib.h>
|
|
#include <Library/OcStringLib.h>
|
|
#include <Library/UefiBootServicesTableLib.h>
|
|
#include <Library/UefiLib.h>
|
|
#include <Library/UefiRuntimeServicesTableLib.h>
|
|
|
|
/**
|
|
Retrieves booting relevant data from an UEFI Boot#### option.
|
|
If BootName is NULL, a BDS-style process is assumed and inactive as well as
|
|
non-Boot type applications are ignored.
|
|
|
|
@param[in] BootOption The boot option's index.
|
|
@param[out] BootName On output, the boot option's description.
|
|
@param[out] OptionalDataSize On output, the optional data size.
|
|
@param[out] OptionalData On output, a pointer to the optional data.
|
|
|
|
**/
|
|
STATIC
|
|
EFI_DEVICE_PATH_PROTOCOL *
|
|
InternalGetBootOptionData (
|
|
IN UINT16 BootOption,
|
|
IN EFI_GUID *BootGuid,
|
|
OUT CHAR16 **BootName OPTIONAL,
|
|
OUT UINT32 *OptionalDataSize OPTIONAL,
|
|
OUT VOID **OptionalData OPTIONAL
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CHAR16 BootVarName[L_STR_LEN (L"Boot####") + 1];
|
|
|
|
UINTN LoadOptionSize;
|
|
EFI_LOAD_OPTION *LoadOption;
|
|
UINT8 *LoadOptionPtr;
|
|
|
|
UINT32 Attributes;
|
|
CONST CHAR16 *Description;
|
|
UINTN DescriptionSize;
|
|
UINT16 FilePathListSize;
|
|
EFI_DEVICE_PATH_PROTOCOL *FilePathList;
|
|
|
|
CHAR16 *BootOptionName;
|
|
VOID *OptionalDataBuffer;
|
|
|
|
UnicodeSPrint (BootVarName, sizeof (BootVarName), L"Boot%04x", BootOption);
|
|
|
|
Status = GetVariable2 (
|
|
BootVarName,
|
|
BootGuid,
|
|
(VOID **)&LoadOption,
|
|
&LoadOptionSize
|
|
);
|
|
if (EFI_ERROR (Status) || (LoadOptionSize < sizeof (*LoadOption))) {
|
|
return NULL;
|
|
}
|
|
|
|
Attributes = LoadOption->Attributes;
|
|
if ((BootName == NULL)
|
|
&& (((Attributes & LOAD_OPTION_ACTIVE) == 0)
|
|
|| ((Attributes & LOAD_OPTION_CATEGORY) != LOAD_OPTION_CATEGORY_BOOT))) {
|
|
FreePool (LoadOption);
|
|
return NULL;
|
|
}
|
|
|
|
FilePathListSize = LoadOption->FilePathListLength;
|
|
|
|
LoadOptionPtr = (UINT8 *)(LoadOption + 1);
|
|
LoadOptionSize -= sizeof (*LoadOption);
|
|
|
|
if (FilePathListSize > LoadOptionSize) {
|
|
FreePool (LoadOption);
|
|
return NULL;
|
|
}
|
|
|
|
LoadOptionSize -= FilePathListSize;
|
|
|
|
Description = (CHAR16 *)LoadOptionPtr;
|
|
DescriptionSize = StrnSizeS (Description, (LoadOptionSize / sizeof (CHAR16)));
|
|
if (DescriptionSize > LoadOptionSize) {
|
|
FreePool (LoadOption);
|
|
return NULL;
|
|
}
|
|
|
|
LoadOptionPtr += DescriptionSize;
|
|
LoadOptionSize -= DescriptionSize;
|
|
|
|
FilePathList = (EFI_DEVICE_PATH_PROTOCOL *)LoadOptionPtr;
|
|
if (!IsDevicePathValid (FilePathList, FilePathListSize)) {
|
|
FreePool (LoadOption);
|
|
return NULL;
|
|
}
|
|
|
|
LoadOptionPtr += FilePathListSize;
|
|
|
|
BootOptionName = NULL;
|
|
|
|
if (BootName != NULL) {
|
|
BootOptionName = AllocateCopyPool (DescriptionSize, Description);
|
|
}
|
|
|
|
OptionalDataBuffer = NULL;
|
|
|
|
if (OptionalDataSize != NULL) {
|
|
ASSERT (OptionalData != NULL);
|
|
if (LoadOptionSize > 0) {
|
|
OptionalDataBuffer = AllocateCopyPool (LoadOptionSize, LoadOptionPtr);
|
|
if (OptionalDataBuffer == NULL) {
|
|
LoadOptionSize = 0;
|
|
}
|
|
}
|
|
|
|
*OptionalDataSize = (UINT32)LoadOptionSize;
|
|
}
|
|
//
|
|
// Use the allocated Load Option buffer for the Device Path.
|
|
//
|
|
CopyMem (LoadOption, FilePathList, FilePathListSize);
|
|
FilePathList = (EFI_DEVICE_PATH_PROTOCOL *)LoadOption;
|
|
|
|
if (BootName != NULL) {
|
|
*BootName = BootOptionName;
|
|
}
|
|
|
|
if (OptionalData != NULL) {
|
|
*OptionalData = OptionalDataBuffer;
|
|
}
|
|
|
|
return FilePathList;
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
InternalDebugBootEnvironment (
|
|
IN CONST UINT16 *BootOrder,
|
|
IN EFI_GUID *BootGuid,
|
|
IN UINTN BootOrderCount
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_DEVICE_PATH_PROTOCOL *UefiDevicePath;
|
|
UINTN UefiDevicePathSize;
|
|
CHAR16 *DevicePathText;
|
|
UINTN Index;
|
|
INT32 Predefined;
|
|
|
|
STATIC CONST CHAR16 *AppleDebugVariables[] = {
|
|
L"efi-boot-device-data",
|
|
L"efi-backup-boot-device-data",
|
|
L"efi-apple-recovery-data"
|
|
};
|
|
|
|
STATIC CONST UINT16 ApplePredefinedVariables[] = {
|
|
0x80, 0x81, 0x82
|
|
};
|
|
|
|
for (Index = 0; Index < ARRAY_SIZE (AppleDebugVariables); ++Index) {
|
|
Status = GetVariable2 (
|
|
AppleDebugVariables[Index],
|
|
&gAppleBootVariableGuid,
|
|
(VOID **)&UefiDevicePath,
|
|
&UefiDevicePathSize
|
|
);
|
|
if (!EFI_ERROR (Status) && IsDevicePathValid (UefiDevicePath, UefiDevicePathSize)) {
|
|
DevicePathText = ConvertDevicePathToText (UefiDevicePath, FALSE, FALSE);
|
|
if (DevicePathText != NULL) {
|
|
DEBUG ((DEBUG_INFO, "OCB: %s = %s\n", AppleDebugVariables[Index], DevicePathText));
|
|
FreePool (DevicePathText);
|
|
FreePool (UefiDevicePath);
|
|
continue;
|
|
}
|
|
|
|
FreePool (UefiDevicePath);
|
|
}
|
|
DEBUG ((DEBUG_INFO, "OCB: %s - %r\n", AppleDebugVariables[Index], Status));
|
|
}
|
|
|
|
DEBUG ((DEBUG_INFO, "OCB: Dumping BootOrder\n"));
|
|
|
|
for (Predefined = 0; Predefined < 2; ++Predefined) {
|
|
for (Index = 0; Index < BootOrderCount; ++Index) {
|
|
UefiDevicePath = InternalGetBootOptionData (
|
|
BootOrder[Index],
|
|
BootGuid,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
if (UefiDevicePath == NULL) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: %u -> Boot%04x - failed to read\n",
|
|
(UINT32) Index,
|
|
BootOrder[Index]
|
|
));
|
|
continue;
|
|
}
|
|
|
|
DevicePathText = ConvertDevicePathToText (UefiDevicePath, FALSE, FALSE);
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: %u -> Boot%04x = %s\n",
|
|
(UINT32) Index,
|
|
BootOrder[Index],
|
|
DevicePathText
|
|
));
|
|
if (DevicePathText != NULL) {
|
|
FreePool (DevicePathText);
|
|
}
|
|
|
|
FreePool (UefiDevicePath);
|
|
}
|
|
|
|
//
|
|
// Redo with predefined.
|
|
//
|
|
BootOrder = &ApplePredefinedVariables[0];
|
|
BootOrderCount = ARRAY_SIZE (ApplePredefinedVariables);
|
|
DEBUG ((DEBUG_INFO, "OCB: Predefined list\n"));
|
|
}
|
|
}
|
|
|
|
STATIC
|
|
OC_BOOT_ENTRY *
|
|
InternalGetBootEntryByDevicePath (
|
|
IN OUT OC_BOOT_ENTRY *BootEntries,
|
|
IN UINTN NumBootEntries,
|
|
IN EFI_DEVICE_PATH_PROTOCOL *UefiDevicePath,
|
|
IN EFI_DEVICE_PATH_PROTOCOL *UefiRemainingDevicePath,
|
|
IN BOOLEAN IsBootNext
|
|
)
|
|
{
|
|
INTN CmpResult;
|
|
|
|
UINTN RootDevicePathSize;
|
|
|
|
EFI_DEVICE_PATH_PROTOCOL *OcDevicePath;
|
|
EFI_DEVICE_PATH_PROTOCOL *OcRemainingDevicePath;
|
|
|
|
OC_BOOT_ENTRY *BootEntry;
|
|
UINTN Index;
|
|
|
|
RootDevicePathSize = ((UINT8 *)UefiRemainingDevicePath - (UINT8 *)UefiDevicePath);
|
|
|
|
for (Index = 0; Index < NumBootEntries; ++Index) {
|
|
BootEntry = &BootEntries[Index];
|
|
if (BootEntry->DevicePath == NULL || BootEntry->Type == OcBootSystem) {
|
|
continue;
|
|
}
|
|
|
|
OcDevicePath = BootEntry->DevicePath;
|
|
|
|
if ((GetDevicePathSize (OcDevicePath) - END_DEVICE_PATH_LENGTH) < RootDevicePathSize) {
|
|
continue;
|
|
}
|
|
|
|
CmpResult = CompareMem (OcDevicePath, UefiDevicePath, RootDevicePathSize);
|
|
if (CmpResult != 0) {
|
|
continue;
|
|
}
|
|
//
|
|
// FIXME: Ensure that all the entries get properly filtered against any
|
|
// malicious sources. The drive itself should already be safe, but it is
|
|
// unclear whether a potentially safe device path can be transformed into
|
|
// an unsafe one.
|
|
//
|
|
OcRemainingDevicePath = (EFI_DEVICE_PATH_PROTOCOL *)(
|
|
(UINT8 *)OcDevicePath + RootDevicePathSize
|
|
);
|
|
if (!IsBootNext) {
|
|
//
|
|
// For non-BootNext boot, the File Paths must match for the entries to be
|
|
// matched. Startup Disk however only stores the drive's Device Path
|
|
// excluding the booter path, which we treat as a match as well.
|
|
//
|
|
if (!IsDevicePathEnd (UefiRemainingDevicePath)
|
|
&& !IsDevicePathEqual (UefiRemainingDevicePath, OcRemainingDevicePath)
|
|
) {
|
|
continue;
|
|
}
|
|
} else {
|
|
//
|
|
// Only use the BootNext path when it has a file path.
|
|
//
|
|
if (!IsDevicePathEnd (UefiRemainingDevicePath)) {
|
|
//
|
|
// TODO: Investigate whether macOS adds BootNext entries that are not
|
|
// possibly located by bless.
|
|
//
|
|
FreePool (BootEntry->DevicePath);
|
|
BootEntry->DevicePath = UefiDevicePath;
|
|
}
|
|
}
|
|
|
|
return BootEntry;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
STATIC
|
|
BOOLEAN
|
|
InternalIsAppleLegacyLoadApp (
|
|
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath
|
|
)
|
|
{
|
|
EFI_DEV_PATH_PTR FwVolDevPath;
|
|
|
|
ASSERT (DevicePath != NULL);
|
|
|
|
if (DevicePath->Type == HARDWARE_DEVICE_PATH
|
|
&& DevicePath->SubType == HW_MEMMAP_DP) {
|
|
FwVolDevPath.DevPath = NextDevicePathNode (DevicePath);
|
|
if (FwVolDevPath.DevPath->Type == MEDIA_DEVICE_PATH
|
|
&& FwVolDevPath.DevPath->SubType == MEDIA_PIWG_FW_FILE_DP) {
|
|
return CompareGuid (
|
|
&FwVolDevPath.FirmwareFile->FvFileName,
|
|
&gAppleLegacyLoadAppFileGuid
|
|
);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
STATIC
|
|
UINT16 *
|
|
InternalGetBootOrder (
|
|
IN EFI_GUID *BootVariableGuid,
|
|
OUT UINTN *BootOrderCount
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINT16 *BootOrder;
|
|
UINTN BootOrderSize;
|
|
|
|
Status = GetVariable2 (
|
|
EFI_BOOT_ORDER_VARIABLE_NAME,
|
|
BootVariableGuid,
|
|
(VOID **) &BootOrder,
|
|
&BootOrderSize
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_INFO, "OCB: BootOrder is unavailable - %r\n", Status));
|
|
*BootOrderCount = 0;
|
|
return NULL;
|
|
}
|
|
|
|
if (BootOrderSize < sizeof (*BootOrder) || BootOrderSize % sizeof (*BootOrder) != 0) {
|
|
DEBUG ((DEBUG_WARN, "OCB: BootOrder is malformed - %x\n", (UINT32) BootOrderSize));
|
|
FreePool (BootOrder);
|
|
*BootOrderCount = 0;
|
|
return NULL;
|
|
}
|
|
|
|
*BootOrderCount = BootOrderSize / sizeof (*BootOrder);
|
|
return BootOrder;
|
|
}
|
|
|
|
/**
|
|
Obtain default entry from the list.
|
|
|
|
@param[in,out] BootEntries Described list of entries, may get updated.
|
|
@param[in] NumBootEntries Positive number of boot entries.
|
|
@param[in] CustomBootGuid Use custom GUID for Boot#### lookup.
|
|
@param[in] LoadHandle Handle to skip (potential OpenCore handle).
|
|
|
|
@retval boot entry or NULL.
|
|
**/
|
|
STATIC
|
|
OC_BOOT_ENTRY *
|
|
InternalGetDefaultBootEntry (
|
|
IN OUT OC_BOOT_ENTRY *BootEntries,
|
|
IN UINTN NumBootEntries,
|
|
IN BOOLEAN CustomBootGuid,
|
|
IN EFI_HANDLE LoadHandle OPTIONAL
|
|
)
|
|
{
|
|
OC_BOOT_ENTRY *BootEntry;
|
|
|
|
EFI_STATUS Status;
|
|
INTN NumPatchedNodes;
|
|
|
|
UINT32 BootNextAttributes;
|
|
UINTN BootNextSize;
|
|
BOOLEAN IsBootNext;
|
|
|
|
UINT16 *BootOrder;
|
|
UINTN BootOrderCount;
|
|
|
|
EFI_DEVICE_PATH_PROTOCOL *UefiDevicePath;
|
|
EFI_DEVICE_PATH_PROTOCOL *UefiRemainingDevicePath;
|
|
EFI_DEVICE_PATH_PROTOCOL *FullDevicePath;
|
|
EFI_DEVICE_PATH_PROTOCOL *PrevDevicePath;
|
|
UINT32 OptionalDataSize;
|
|
VOID *OptionalData;
|
|
EFI_GUID *BootVariableGuid;
|
|
|
|
EFI_HANDLE DeviceHandle;
|
|
|
|
UINT16 BootNextOptionIndex;
|
|
|
|
BOOLEAN IsAppleLegacy;
|
|
UINTN DevPathSize;
|
|
EFI_DEVICE_PATH_PROTOCOL *EspDevicePath;
|
|
|
|
ASSERT (BootEntries != NULL);
|
|
ASSERT (NumBootEntries > 0);
|
|
|
|
IsBootNext = FALSE;
|
|
OptionalData = NULL;
|
|
|
|
if (CustomBootGuid) {
|
|
BootVariableGuid = &gOcVendorVariableGuid;
|
|
} else {
|
|
BootVariableGuid = &gEfiGlobalVariableGuid;
|
|
}
|
|
|
|
BootNextSize = sizeof (BootNextOptionIndex);
|
|
Status = gRT->GetVariable (
|
|
EFI_BOOT_NEXT_VARIABLE_NAME,
|
|
BootVariableGuid,
|
|
&BootNextAttributes,
|
|
&BootNextSize,
|
|
&BootNextOptionIndex
|
|
);
|
|
if (Status == EFI_NOT_FOUND) {
|
|
DEBUG ((DEBUG_INFO, "OCB: BootNext has not been found\n"));
|
|
|
|
BootOrder = InternalGetBootOrder(
|
|
BootVariableGuid,
|
|
&BootOrderCount
|
|
);
|
|
|
|
if (BootOrder == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
DEBUG_CODE_BEGIN ();
|
|
InternalDebugBootEnvironment (BootOrder, BootVariableGuid, BootOrderCount);
|
|
DEBUG_CODE_END ();
|
|
|
|
UefiDevicePath = InternalGetBootOptionData (
|
|
BootOrder[0],
|
|
BootVariableGuid,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
if (UefiDevicePath == NULL) {
|
|
FreePool (BootOrder);
|
|
return NULL;
|
|
}
|
|
|
|
UefiRemainingDevicePath = UefiDevicePath;
|
|
Status = gBS->LocateDevicePath (
|
|
&gEfiSimpleFileSystemProtocolGuid,
|
|
&UefiRemainingDevicePath,
|
|
&DeviceHandle
|
|
);
|
|
if (!EFI_ERROR (Status) && (DeviceHandle == LoadHandle)) {
|
|
DEBUG ((DEBUG_INFO, "OCB: Skipping OC bootstrap application\n"));
|
|
//
|
|
// Skip BOOTx64.EFI at BootOrder[0].
|
|
//
|
|
FreePool (UefiDevicePath);
|
|
|
|
if (BootOrderCount < 2) {
|
|
FreePool (BootOrder);
|
|
return NULL;
|
|
}
|
|
|
|
UefiDevicePath = InternalGetBootOptionData (
|
|
BootOrder[1],
|
|
BootVariableGuid,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
if (UefiDevicePath == NULL) {
|
|
FreePool (BootOrder);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
FreePool (BootOrder);
|
|
} else if (!EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_INFO, "OCB: BootNext: %x\n", BootNextOptionIndex));
|
|
//
|
|
// BootNext must be deleted before attempting to start the image - delete
|
|
// it here because not attempting to boot the image implies user's choice.
|
|
//
|
|
gRT->SetVariable (
|
|
EFI_BOOT_NEXT_VARIABLE_NAME,
|
|
BootVariableGuid,
|
|
BootNextAttributes,
|
|
0,
|
|
NULL
|
|
);
|
|
IsBootNext = TRUE;
|
|
|
|
UefiDevicePath = InternalGetBootOptionData (
|
|
BootNextOptionIndex,
|
|
BootVariableGuid,
|
|
NULL,
|
|
&OptionalDataSize,
|
|
&OptionalData
|
|
);
|
|
if (UefiDevicePath == NULL) {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
DEBUG ((DEBUG_INFO, "OCB: BootNext retrieval failed - %r", Status));
|
|
return NULL;
|
|
}
|
|
|
|
IsAppleLegacy = InternalIsAppleLegacyLoadApp (UefiDevicePath);
|
|
if (IsAppleLegacy) {
|
|
DEBUG ((DEBUG_INFO, "OCB: Default is AppleLegacyLoadApp\n"));
|
|
FreePool (UefiDevicePath);
|
|
Status = GetVariable2 (
|
|
L"BootCampHD",
|
|
&gAppleBootVariableGuid,
|
|
(VOID **)&UefiDevicePath,
|
|
&DevPathSize
|
|
);
|
|
if (EFI_ERROR (Status) || !IsDevicePathValid (UefiDevicePath, DevPathSize)) {
|
|
if (OptionalData != NULL) {
|
|
FreePool (OptionalData);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
DebugPrintDevicePath (DEBUG_INFO, "OCB: Default DP pre-fix", UefiDevicePath);
|
|
|
|
UefiRemainingDevicePath = UefiDevicePath;
|
|
NumPatchedNodes = OcFixAppleBootDevicePath (&UefiRemainingDevicePath);
|
|
|
|
DebugPrintDevicePath (
|
|
DEBUG_INFO,
|
|
"OCB: Default DP post-fix",
|
|
UefiDevicePath
|
|
);
|
|
DebugPrintDevicePath (
|
|
DEBUG_INFO,
|
|
"OCB: Default DP post-fix remainder",
|
|
UefiRemainingDevicePath
|
|
);
|
|
|
|
if (NumPatchedNodes == -1) {
|
|
DEBUG ((DEBUG_WARN, "OCB: Failed to fix the default boot Device Path\n"));
|
|
return NULL;
|
|
}
|
|
|
|
if (IsAppleLegacy) {
|
|
EspDevicePath = OcDiskFindSystemPartitionPath (
|
|
UefiDevicePath,
|
|
&DevPathSize
|
|
);
|
|
|
|
FreePool (UefiDevicePath);
|
|
|
|
if (EspDevicePath == NULL) {
|
|
DEBUG ((DEBUG_INFO, "Failed to locate the disk's ESP\n"));
|
|
if (OptionalData != NULL) {
|
|
FreePool (OptionalData);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
//
|
|
// CAUTION: This must NOT be freed!
|
|
//
|
|
UefiDevicePath = EspDevicePath;
|
|
//
|
|
// The Device Path must be entirely locatable as
|
|
// OcDiskFindSystemPartitionPath() guarantees to only return valid paths.
|
|
//
|
|
ASSERT (DevPathSize > END_DEVICE_PATH_LENGTH);
|
|
DevPathSize -= END_DEVICE_PATH_LENGTH;
|
|
UefiRemainingDevicePath = (EFI_DEVICE_PATH_PROTOCOL *)(
|
|
(UINTN)EspDevicePath + DevPathSize
|
|
);
|
|
|
|
DebugPrintDevicePath (
|
|
DEBUG_INFO,
|
|
"OCB: Default DP post-loc",
|
|
UefiDevicePath
|
|
);
|
|
}
|
|
|
|
if (UefiDevicePath != UefiRemainingDevicePath) {
|
|
//
|
|
// If the Device Path node was advanced, it cannot be a short-form.
|
|
//
|
|
BootEntry = InternalGetBootEntryByDevicePath (
|
|
BootEntries,
|
|
NumBootEntries,
|
|
UefiDevicePath,
|
|
UefiRemainingDevicePath,
|
|
IsBootNext
|
|
);
|
|
} else {
|
|
//
|
|
// If the Device Path node was not advanced, it might be a short-form.
|
|
//
|
|
PrevDevicePath = NULL;
|
|
do {
|
|
FullDevicePath = OcGetNextLoadOptionDevicePath (
|
|
UefiDevicePath,
|
|
PrevDevicePath
|
|
);
|
|
|
|
if (PrevDevicePath != NULL) {
|
|
FreePool (PrevDevicePath);
|
|
}
|
|
|
|
if (FullDevicePath == NULL) {
|
|
DEBUG ((DEBUG_INFO, "OCB: Short-form DP could not be expanded\n"));
|
|
BootEntry = NULL;
|
|
break;
|
|
}
|
|
|
|
PrevDevicePath = FullDevicePath;
|
|
|
|
DebugPrintDevicePath (DEBUG_INFO, "OCB: Expanded DP", FullDevicePath);
|
|
|
|
UefiRemainingDevicePath = FullDevicePath;
|
|
Status = gBS->LocateDevicePath (
|
|
&gEfiDevicePathProtocolGuid,
|
|
&UefiRemainingDevicePath,
|
|
&DeviceHandle
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
continue;
|
|
}
|
|
|
|
DebugPrintDevicePath (
|
|
DEBUG_INFO,
|
|
"OCB: Expanded DP remainder",
|
|
UefiRemainingDevicePath
|
|
);
|
|
|
|
BootEntry = InternalGetBootEntryByDevicePath (
|
|
BootEntries,
|
|
NumBootEntries,
|
|
FullDevicePath,
|
|
UefiRemainingDevicePath,
|
|
IsBootNext
|
|
);
|
|
} while (BootEntry == NULL);
|
|
|
|
if (FullDevicePath != NULL) {
|
|
if (!IsAppleLegacy) {
|
|
FreePool (UefiDevicePath);
|
|
}
|
|
UefiDevicePath = FullDevicePath;
|
|
}
|
|
}
|
|
|
|
if (BootEntry != NULL) {
|
|
#if 0
|
|
if (IsBootNext) {
|
|
//
|
|
// BootNext is allowed to override both the exact file path as well as
|
|
// the used load options.
|
|
// TODO: Investigate whether Apple uses OptionalData, and exploit ways.
|
|
//
|
|
BootEntry->LoadOptionsSize = OptionalDataSize;
|
|
BootEntry->LoadOptions = OptionalData;
|
|
} else
|
|
#endif
|
|
if (OptionalData != NULL) {
|
|
FreePool (OptionalData);
|
|
}
|
|
|
|
if (BootEntry->DevicePath != UefiDevicePath) {
|
|
if (!IsAppleLegacy) {
|
|
FreePool (UefiDevicePath);
|
|
}
|
|
} else {
|
|
ASSERT (IsBootNext);
|
|
}
|
|
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Matched default boot option: %s\n",
|
|
BootEntry->Name
|
|
));
|
|
|
|
return BootEntry;
|
|
}
|
|
|
|
if (OptionalData != NULL) {
|
|
FreePool (OptionalData);
|
|
}
|
|
|
|
if (!IsAppleLegacy) {
|
|
FreePool (UefiDevicePath);
|
|
}
|
|
|
|
DEBUG ((DEBUG_WARN, "OCB: Failed to match a default boot option\n"));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
UINT32
|
|
OcGetDefaultBootEntry (
|
|
IN OC_PICKER_CONTEXT *Context,
|
|
IN OUT OC_BOOT_ENTRY *BootEntries,
|
|
IN UINTN NumBootEntries
|
|
)
|
|
{
|
|
UINT32 BootEntryIndex;
|
|
OC_BOOT_ENTRY *BootEntry;
|
|
UINTN Index;
|
|
|
|
BootEntry = InternalGetDefaultBootEntry (
|
|
BootEntries,
|
|
NumBootEntries,
|
|
Context->CustomBootGuid,
|
|
Context->ExcludeHandle
|
|
);
|
|
|
|
if (BootEntry != NULL) {
|
|
BootEntryIndex = (UINT32) (BootEntry - BootEntries);
|
|
DEBUG ((DEBUG_INFO, "OCB: Initial default is %u\n", BootEntryIndex));
|
|
} else {
|
|
BootEntryIndex = 0;
|
|
DEBUG ((DEBUG_INFO, "OCB: Initial default is 0, fallback\n"));
|
|
}
|
|
|
|
if (Context->PickerCommand == OcPickerBootApple) {
|
|
if (BootEntries[BootEntryIndex].Type != OcBootApple) {
|
|
for (Index = 0; Index < NumBootEntries; ++Index) {
|
|
if (BootEntries[Index].Type == OcBootApple) {
|
|
BootEntryIndex = (UINT32) Index;
|
|
DEBUG ((DEBUG_INFO, "OCB: Override default to Apple %u\n", BootEntryIndex));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (Context->PickerCommand == OcPickerBootAppleRecovery) {
|
|
if (BootEntries[BootEntryIndex].Type != OcBootAppleRecovery) {
|
|
if (BootEntryIndex + 1 < NumBootEntries
|
|
&& BootEntries[BootEntryIndex + 1].Type == OcBootAppleRecovery) {
|
|
BootEntryIndex = BootEntryIndex + 1;
|
|
DEBUG ((DEBUG_INFO, "OCB: Override default to Apple Recovery %u, next\n", BootEntryIndex));
|
|
} else {
|
|
for (Index = 0; Index < NumBootEntries; ++Index) {
|
|
if (BootEntries[Index].Type == OcBootAppleRecovery) {
|
|
BootEntryIndex = (UINT32) Index;
|
|
DEBUG ((DEBUG_INFO, "OCB: Override default option to Apple Recovery %u\n", BootEntryIndex));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return BootEntryIndex;
|
|
}
|
|
|
|
EFI_STATUS
|
|
OcSetDefaultBootEntry (
|
|
IN OC_PICKER_CONTEXT *Context,
|
|
IN OC_BOOT_ENTRY *Entry
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_DEVICE_PATH *BootOptionDevicePath;
|
|
EFI_DEVICE_PATH *BootOptionRemainingDevicePath;
|
|
EFI_HANDLE DeviceHandle;
|
|
OC_BOOT_ENTRY *MatchedEntry;
|
|
EFI_GUID *BootVariableGuid;
|
|
UINT16 *BootOrder;
|
|
UINT16 *NewBootOrder;
|
|
UINT16 BootTmp;
|
|
UINTN BootOrderCount;
|
|
UINTN BootChosenIndex;
|
|
UINTN Index;
|
|
UINTN DevicePathSize;
|
|
UINTN LoadOptionSize;
|
|
UINTN LoadOptionNameSize;
|
|
EFI_LOAD_OPTION *LoadOption;
|
|
|
|
//
|
|
// Do not allow when prohibited.
|
|
//
|
|
if (!Context->AllowSetDefault) {
|
|
return EFI_SECURITY_VIOLATION;
|
|
}
|
|
|
|
//
|
|
// Ignore entries without device paths.
|
|
//
|
|
if (Entry->DevicePath == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Context->CustomBootGuid) {
|
|
BootVariableGuid = &gOcVendorVariableGuid;
|
|
} else {
|
|
BootVariableGuid = &gEfiGlobalVariableGuid;
|
|
}
|
|
|
|
BootOrder = InternalGetBootOrder(
|
|
BootVariableGuid,
|
|
&BootOrderCount
|
|
);
|
|
|
|
MatchedEntry = NULL;
|
|
BootChosenIndex = BootOrderCount;
|
|
for (Index = 0; Index < BootOrderCount; ++Index) {
|
|
if (BootOrder[Index] == 0x80) {
|
|
BootChosenIndex = Index;
|
|
}
|
|
|
|
if (MatchedEntry != NULL) {
|
|
if (BootChosenIndex != BootOrderCount) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
BootOptionDevicePath = InternalGetBootOptionData (
|
|
BootOrder[Index],
|
|
BootVariableGuid,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (BootOptionDevicePath == NULL) {
|
|
continue;
|
|
}
|
|
|
|
BootOptionRemainingDevicePath = BootOptionDevicePath;
|
|
Status = gBS->LocateDevicePath (
|
|
&gEfiSimpleFileSystemProtocolGuid,
|
|
&BootOptionRemainingDevicePath,
|
|
&DeviceHandle
|
|
);
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
MatchedEntry = InternalGetBootEntryByDevicePath (
|
|
Entry,
|
|
1,
|
|
BootOptionDevicePath,
|
|
BootOptionRemainingDevicePath,
|
|
FALSE
|
|
);
|
|
}
|
|
}
|
|
|
|
if (MatchedEntry == NULL) {
|
|
//
|
|
// Write to Boot0080
|
|
//
|
|
LoadOptionNameSize = StrSize (Entry->Name);
|
|
DevicePathSize = GetDevicePathSize (Entry->DevicePath);
|
|
LoadOptionSize = sizeof (EFI_LOAD_OPTION) + LoadOptionNameSize + DevicePathSize;
|
|
|
|
LoadOption = AllocatePool (LoadOptionSize);
|
|
if (LoadOption == NULL) {
|
|
DEBUG ((DEBUG_INFO, "OCB: Failed to allocate default option (%u)\n", (UINT32) LoadOptionSize));
|
|
if (BootOrder != NULL) {
|
|
FreePool (BootOrder);
|
|
}
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
LoadOption->Attributes = LOAD_OPTION_ACTIVE | LOAD_OPTION_CATEGORY_BOOT;
|
|
LoadOption->FilePathListLength = (UINT16) DevicePathSize;
|
|
CopyMem (LoadOption + 1, Entry->Name, LoadOptionNameSize);
|
|
CopyMem ((UINT8 *) (LoadOption + 1) + LoadOptionNameSize, Entry->DevicePath, DevicePathSize);
|
|
|
|
Status = gRT->SetVariable (
|
|
L"Boot0080",
|
|
BootVariableGuid,
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS
|
|
| EFI_VARIABLE_RUNTIME_ACCESS
|
|
| EFI_VARIABLE_NON_VOLATILE,
|
|
LoadOptionSize,
|
|
LoadOption
|
|
);
|
|
|
|
FreePool (LoadOption);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Failed to set default entry Boot0080 - %r\n",
|
|
Status
|
|
));
|
|
if (BootOrder != NULL) {
|
|
FreePool (BootOrder);
|
|
}
|
|
return Status;
|
|
}
|
|
} else {
|
|
DEBUG ((DEBUG_INFO, "OCB: Matched default entry in BootOrder\n"));
|
|
BootChosenIndex = Index;
|
|
}
|
|
|
|
//
|
|
// Update BootOrder to contain new option.
|
|
//
|
|
if (BootChosenIndex != BootOrderCount) {
|
|
BootTmp = BootOrder[0];
|
|
BootOrder[0] = BootOrder[BootChosenIndex];
|
|
BootOrder[BootChosenIndex] = BootTmp;
|
|
NewBootOrder = BootOrder;
|
|
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Found default entry in BootOrder, reordering %X <-> %X\n",
|
|
NewBootOrder[0],
|
|
NewBootOrder[BootChosenIndex]
|
|
));
|
|
} else {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Adding default entry Boot0080 to BootOrder\n"
|
|
));
|
|
|
|
NewBootOrder = AllocatePool ((BootOrderCount + 1) * sizeof (*BootOrder));
|
|
if (NewBootOrder == NULL) {
|
|
if (BootOrder != NULL) {
|
|
FreePool (BootOrder);
|
|
}
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
NewBootOrder[0] = 0x80;
|
|
CopyMem (&NewBootOrder[1], &BootOrder[0], BootOrderCount * sizeof (*BootOrder));
|
|
|
|
if (BootOrder != NULL) {
|
|
FreePool (BootOrder);
|
|
}
|
|
|
|
++BootOrderCount;
|
|
}
|
|
|
|
Status = gRT->SetVariable (
|
|
EFI_BOOT_ORDER_VARIABLE_NAME,
|
|
BootVariableGuid,
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS
|
|
| EFI_VARIABLE_RUNTIME_ACCESS
|
|
| EFI_VARIABLE_NON_VOLATILE,
|
|
BootOrderCount * sizeof (*BootOrder),
|
|
NewBootOrder
|
|
);
|
|
|
|
FreePool (NewBootOrder);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Failed to set default BootOrder - %r\n",
|
|
Status
|
|
));
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
#if 0
|
|
STATIC
|
|
VOID
|
|
InternalReportLoadOption (
|
|
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
|
|
IN EFI_GUID *BootGuid
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN DevicePathSize;
|
|
UINTN LoadOptionSize;
|
|
EFI_LOAD_OPTION *LoadOption;
|
|
UINT16 LoadOptionNo;
|
|
EFI_LOAD_OPTION *CurrLoadOption;
|
|
CONST CHAR16 *LoadOptionName;
|
|
UINTN LoadOptionNameSize;
|
|
UINTN CurrLoadOptionSize;
|
|
|
|
//
|
|
// Always report valid option in BootCurrent.
|
|
// Unless done there is no way for Windows to properly hibernate.
|
|
//
|
|
|
|
LoadOptionName = L"OC Boot";
|
|
LoadOptionNameSize = L_STR_SIZE (L"OC Boot");
|
|
DevicePathSize = GetDevicePathSize (DevicePath);
|
|
LoadOptionSize = sizeof (EFI_LOAD_OPTION) + LoadOptionNameSize + DevicePathSize;
|
|
|
|
LoadOption = AllocatePool (LoadOptionSize);
|
|
if (LoadOption == NULL) {
|
|
DEBUG ((DEBUG_INFO, "OCB: Failed to allocate BootFFFF (%u)\n", (UINT32) LoadOptionSize));
|
|
return;
|
|
}
|
|
|
|
LoadOption->Attributes = LOAD_OPTION_HIDDEN;
|
|
LoadOption->FilePathListLength = (UINT16) DevicePathSize;
|
|
CopyMem (LoadOption + 1, LoadOptionName, LoadOptionNameSize);
|
|
CopyMem ((UINT8 *) (LoadOption + 1) + LoadOptionNameSize, DevicePath, DevicePathSize);
|
|
|
|
CurrLoadOption = NULL;
|
|
CurrLoadOptionSize = 0;
|
|
Status = GetVariable2 (
|
|
L"BootFFFF",
|
|
BootGuid,
|
|
(VOID **) &CurrLoadOption,
|
|
&CurrLoadOptionSize
|
|
);
|
|
if (EFI_ERROR (Status)
|
|
|| CurrLoadOptionSize != LoadOptionSize
|
|
|| CompareMem (CurrLoadOption, LoadOption, LoadOptionSize) != 0) {
|
|
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Overwriting BootFFFF (%r/%u)\n",
|
|
Status,
|
|
(UINT32) CurrLoadOptionSize,
|
|
(UINT32) LoadOptionSize
|
|
));
|
|
|
|
gRT->SetVariable (
|
|
L"BootFFFF",
|
|
BootGuid,
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS
|
|
| EFI_VARIABLE_RUNTIME_ACCESS
|
|
| EFI_VARIABLE_NON_VOLATILE,
|
|
LoadOptionSize,
|
|
LoadOption
|
|
);
|
|
} else {
|
|
DEBUG ((DEBUG_INFO, "OCB: Accepting same BootFFFF\n"));
|
|
}
|
|
|
|
if (CurrLoadOption != NULL) {
|
|
FreePool (CurrLoadOption);
|
|
}
|
|
FreePool (LoadOption);
|
|
|
|
LoadOptionNo = 0xFFFF;
|
|
gRT->SetVariable (
|
|
L"BootCurrent",
|
|
BootGuid,
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS
|
|
| EFI_VARIABLE_RUNTIME_ACCESS,
|
|
sizeof (LoadOptionNo),
|
|
&LoadOptionNo
|
|
);
|
|
}
|
|
#endif
|
|
|
|
EFI_STATUS
|
|
InternalLoadBootEntry (
|
|
IN APPLE_BOOT_POLICY_PROTOCOL *BootPolicy,
|
|
IN OC_PICKER_CONTEXT *Context,
|
|
IN OC_BOOT_ENTRY *BootEntry,
|
|
IN EFI_HANDLE ParentHandle,
|
|
OUT EFI_HANDLE *EntryHandle,
|
|
OUT INTERNAL_DMG_LOAD_CONTEXT *DmgLoadContext
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_STATUS OptionalStatus;
|
|
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
|
|
CHAR16 *UnicodeDevicePath;
|
|
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
|
|
VOID *EntryData;
|
|
UINT32 EntryDataSize;
|
|
CONST CHAR8 *Args;
|
|
UINT32 ArgsLen;
|
|
|
|
ASSERT (BootPolicy != NULL);
|
|
ASSERT (BootEntry != NULL);
|
|
//
|
|
// System entries are not loaded but called directly.
|
|
//
|
|
ASSERT (BootEntry->Type != OcBootSystem);
|
|
ASSERT (Context != NULL);
|
|
ASSERT (DmgLoadContext != NULL);
|
|
|
|
//
|
|
// TODO: support Apple loaded image, policy, and dmg boot.
|
|
//
|
|
|
|
ZeroMem (DmgLoadContext, sizeof (*DmgLoadContext));
|
|
|
|
EntryData = NULL;
|
|
EntryDataSize = 0;
|
|
|
|
if (BootEntry->IsFolder) {
|
|
if ((Context->LoadPolicy & OC_LOAD_ALLOW_DMG_BOOT) == 0) {
|
|
return EFI_SECURITY_VIOLATION;
|
|
}
|
|
|
|
DmgLoadContext->DevicePath = BootEntry->DevicePath;
|
|
DevicePath = InternalLoadDmg (
|
|
DmgLoadContext,
|
|
BootPolicy,
|
|
Context->LoadPolicy
|
|
);
|
|
if (DevicePath == NULL) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
} else if (BootEntry->Type == OcBootCustom && BootEntry->DevicePath == NULL) {
|
|
ASSERT (Context->CustomRead != NULL);
|
|
|
|
Status = Context->CustomRead (
|
|
Context->CustomEntryContext,
|
|
BootEntry,
|
|
&EntryData,
|
|
&EntryDataSize,
|
|
&DevicePath
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
} else {
|
|
DevicePath = BootEntry->DevicePath;
|
|
}
|
|
|
|
DEBUG_CODE_BEGIN ();
|
|
ASSERT (DevicePath != NULL);
|
|
UnicodeDevicePath = ConvertDevicePathToText (DevicePath, FALSE, FALSE);
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Perform boot %s to dp %s (%p/%u)\n",
|
|
BootEntry->Name,
|
|
UnicodeDevicePath != NULL ? UnicodeDevicePath : L"<null>",
|
|
EntryData,
|
|
EntryDataSize
|
|
));
|
|
if (UnicodeDevicePath != NULL) {
|
|
FreePool (UnicodeDevicePath);
|
|
}
|
|
DEBUG_CODE_END ();
|
|
|
|
Status = gBS->LoadImage (
|
|
FALSE,
|
|
ParentHandle,
|
|
DevicePath,
|
|
EntryData,
|
|
EntryDataSize,
|
|
EntryHandle
|
|
);
|
|
|
|
if (EntryData != NULL) {
|
|
FreePool (EntryData);
|
|
}
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
#if 0
|
|
InternalReportLoadOption (
|
|
DevicePath,
|
|
Context->CustomBootGuid ? &gOcVendorVariableGuid : &gEfiGlobalVariableGuid
|
|
);
|
|
#endif
|
|
|
|
OptionalStatus = gBS->HandleProtocol (
|
|
*EntryHandle,
|
|
&gEfiLoadedImageProtocolGuid,
|
|
(VOID **) &LoadedImage
|
|
);
|
|
if (!EFI_ERROR (OptionalStatus)) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Matching <%a> args on type %u %p\n",
|
|
Context->AppleBootArgs,
|
|
BootEntry->Type,
|
|
BootEntry->LoadOptions
|
|
));
|
|
|
|
LoadedImage->LoadOptionsSize = 0;
|
|
LoadedImage->LoadOptions = NULL;
|
|
|
|
if (BootEntry->LoadOptions == NULL
|
|
&& (BootEntry->Type == OcBootApple || BootEntry->Type == OcBootAppleRecovery)) {
|
|
Args = Context->AppleBootArgs;
|
|
ArgsLen = (UINT32)AsciiStrLen (Args);
|
|
} else {
|
|
Args = BootEntry->LoadOptions;
|
|
ArgsLen = BootEntry->LoadOptionsSize;
|
|
ASSERT (ArgsLen == ((Args == NULL) ? 0 : (UINT32)AsciiStrLen (Args)));
|
|
}
|
|
|
|
if (ArgsLen > 0) {
|
|
LoadedImage->LoadOptions = AsciiStrCopyToUnicode (Args, ArgsLen);
|
|
if (LoadedImage->LoadOptions != NULL) {
|
|
LoadedImage->LoadOptionsSize = ArgsLen * sizeof (CHAR16) + sizeof (CHAR16);
|
|
}
|
|
}
|
|
|
|
if (BootEntry->Type == OcBootCustom) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCB: Custom DeviceHandle %p FilePath %p\n",
|
|
LoadedImage->DeviceHandle,
|
|
LoadedImage->FilePath
|
|
));
|
|
}
|
|
}
|
|
} else {
|
|
InternalUnloadDmg (DmgLoadContext);
|
|
}
|
|
|
|
return Status;
|
|
}
|