mirror of
https://github.com/LongSoft/UEFITool.git
synced 2025-05-13 14:44:47 -04:00
Add Insyde Flash Device Map parser
This commit is contained in:
parent
0b66c5c7ff
commit
b3c68b7006
28 changed files with 1361 additions and 230 deletions
|
@ -40,7 +40,9 @@ SET(PROJECT_SOURCES
|
|||
../common/generated/edk2_vss2.cpp
|
||||
../common/generated/edk2_ftw.cpp
|
||||
../common/generated/insyde_fdc.cpp
|
||||
../common/generated/insyde_fdm.cpp
|
||||
../common/generated/phoenix_flm.cpp
|
||||
../common/generated/phoenix_evsa.cpp
|
||||
../common/generated/intel_acbp_v1.cpp
|
||||
../common/generated/intel_acbp_v2.cpp
|
||||
../common/generated/intel_keym_v1.cpp
|
||||
|
|
|
@ -37,7 +37,9 @@ SET(PROJECT_SOURCES
|
|||
../common/generated/edk2_vss2.cpp
|
||||
../common/generated/edk2_ftw.cpp
|
||||
../common/generated/insyde_fdc.cpp
|
||||
../common/generated/insyde_fdm.cpp
|
||||
../common/generated/phoenix_flm.cpp
|
||||
../common/generated/phoenix_evsa.cpp
|
||||
../common/generated/intel_acbp_v1.cpp
|
||||
../common/generated/intel_acbp_v2.cpp
|
||||
../common/generated/intel_keym_v1.cpp
|
||||
|
|
|
@ -73,6 +73,8 @@ SET(PROJECT_SOURCES
|
|||
../common/generated/edk2_vss2.cpp
|
||||
../common/generated/edk2_ftw.cpp
|
||||
../common/generated/insyde_fdc.cpp
|
||||
../common/generated/insyde_fdm.cpp
|
||||
../common/generated/phoenix_evsa.cpp
|
||||
../common/generated/phoenix_flm.cpp
|
||||
../common/generated/intel_acbp_v1.cpp
|
||||
../common/generated/intel_acbp_v2.cpp
|
||||
|
|
|
@ -247,7 +247,8 @@ void UEFITool::populateUi(const QModelIndex ¤t)
|
|||
|| type == Types::VssEntry
|
||||
|| type == Types::SysFEntry
|
||||
|| type == Types::EvsaEntry
|
||||
|| type == Types::FlashMapEntry
|
||||
|| type == Types::PhoenixFlashMapEntry
|
||||
|| type == Types::InsydeFlashDeviceMapEntry
|
||||
|| type == Types::IfwiHeader
|
||||
|| type == Types::IfwiPartition
|
||||
|| type == Types::FptPartition
|
||||
|
@ -266,7 +267,8 @@ void UEFITool::populateUi(const QModelIndex ¤t)
|
|||
|| type == Types::SysFStore
|
||||
|| type == Types::EvsaStore
|
||||
|| type == Types::FtwStore
|
||||
|| type == Types::FlashMapStore
|
||||
|| type == Types::PhoenixFlashMapStore
|
||||
|| type == Types::InsydeFlashDeviceMapStore
|
||||
|| type == Types::NvarGuidStore
|
||||
|| type == Types::CmdbStore
|
||||
|| type == Types::FptStore
|
||||
|
@ -910,7 +912,8 @@ void UEFITool::contextMenuEvent(QContextMenuEvent* event)
|
|||
case Types::SysFStore:
|
||||
case Types::EvsaStore:
|
||||
case Types::FtwStore:
|
||||
case Types::FlashMapStore:
|
||||
case Types::PhoenixFlashMapStore:
|
||||
case Types::InsydeFlashDeviceMapStore:
|
||||
case Types::NvarGuidStore:
|
||||
case Types::CmdbStore:
|
||||
case Types::FptStore:
|
||||
|
|
|
@ -56,7 +56,9 @@ HEADERS += uefitool.h \
|
|||
../common/generated/edk2_vss2.h \
|
||||
../common/generated/edk2_ftw.h \
|
||||
../common/generated/insyde_fdc.h \
|
||||
../common/generated/insyde_fdm.h \
|
||||
../common/generated/phoenix_flm.h \
|
||||
../common/generated/phoenix_evsa.h \
|
||||
../common/generated/intel_acbp_v1.h \
|
||||
../common/generated/intel_acbp_v2.h \
|
||||
../common/generated/intel_keym_v1.h \
|
||||
|
@ -129,7 +131,9 @@ SOURCES += uefitool_main.cpp \
|
|||
../common/generated/edk2_vss2.cpp \
|
||||
../common/generated/edk2_ftw.cpp \
|
||||
../common/generated/insyde_fdc.cpp \
|
||||
../common/generated/insyde_fdm.cpp \
|
||||
../common/generated/phoenix_flm.cpp \
|
||||
../common/generated/phoenix_evsa.cpp \
|
||||
../common/generated/intel_acbp_v1.cpp \
|
||||
../common/generated/intel_acbp_v2.cpp \
|
||||
../common/generated/intel_keym_v1.cpp \
|
||||
|
|
|
@ -85,6 +85,48 @@ extern const UByteArray EFI_DXE_CORE_GUID // D6A2CB7F-6A18-4E2F-B43B-9920A733700
|
|||
extern const UByteArray AMD_COMPRESSED_RAW_FILE_GUID //20BC8AC9-94D1-4208-AB28-5D673FD73487
|
||||
("\xC9\x8A\xBC\x20\xD1\x94\x08\x42\xAB\x28\x5D\x67\x3F\xD7\x34\x87", 16);
|
||||
|
||||
// Insyde Flash Device Map GUIDs
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_BOOT_FV_GUID
|
||||
("\x56\x6d\xd7\xe3\x8a\x98\x6b\x4d\x89\x13\x64\xf2\xdf\x1d\xf6\xa6", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_BVDT_GUID
|
||||
("\xFC\x5D\x41\x32\x06\xD1\xC7\x48\x9E\xB5\x80\x6C\x11\x4D\xD1\x07", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_EC_GUID
|
||||
("\xBF\xF3\x3E\xA7\xCC\x33\xA9\x43\xB3\x9C\xA9\x12\xC7\x48\x9A\x57", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_FTW_BACKUP_GUID
|
||||
("\xD3\x15\x8E\xB7\xA5\xF0\x48\x42\x8E\x2F\xD3\x15\x7A\xEF\x88\x36", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_FTW_STATE_GUID
|
||||
("\x04\x6E\x41\xC8\x34\x99\x79\x40\xBE\x9A\x39\xF8\xD6\x02\x84\x98", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_FV_GUID
|
||||
("\x58\xE7\xE8\xB5\xE6\xA7\x8B\x4C\xAB\x85\xFF\x2A\x95\x9B\x99\xBA", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_FLASH_DEVICE_MAP_GUID
|
||||
("\xA0\xC1\x78\xF0\x52\xFC\x3F\x4C\xBE\x1F\xD6\x88\x81\x5A\x62\xC0", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_LOGO_GUID
|
||||
("\x69\xAB\xCF\xDA\x77\xF9\x84\x47\x8A\xD8\x77\x24\xA6\xF4\xB4\x40", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_MICROCODE_GUID
|
||||
("\xF8\x66\x98\xB4\xD2\x8C\xE4\x49\xA1\x6D\xB6\x0F\xBE\xC3\x1C\x4B", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_MSDM_TABLE_GUID
|
||||
("\x1A\xEB\x44\xB3\x7E\xF9\x14\x4F\xA1\xE1\x7E\x63\xBC\x40\xC8\xCE", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_MULTI_CONFIG_GUID
|
||||
("\x92\xB5\x94\x59\x14\x2F\xD5\x48\xBB\x40\xBD\x27\x96\x9C\x77\x80", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_VAR_DEFAULT_GUID
|
||||
("\xA2\xAC\xDD\xD9\x16\x08\xF3\x48\xAD\xED\x6B\x71\x65\x6B\x24\x8A", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_SMBIOS_UPDATE_GUID
|
||||
("\xDC\xFE\x64\x89\xE7\x6F\x1E\x4E\xA5\x5E\xFF\x82\x1D\x71\xFF\xCF", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_VAR_GUID
|
||||
("\x74\x53\x3C\x77\xD1\x81\x43\x4D\xB2\x93\xF3\xD7\x4F\x18\x1D\x6B", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_UNKNOWN_GUID
|
||||
("\xE5\x65\x1D\x20\x23\xBE\x75\x48\x80\xF8\xB1\xD4\x79\x5E\x7E\x08", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_UNUSED_GUID
|
||||
("\x20\xB0\xC8\x13\x27\x4F\x3B\x45\x8F\x80\x1B\xFC\xA1\x87\x38\x0F", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_USB_OPTION_ROM_GUID
|
||||
("\x0F\xF3\x7B\x60\x2B\x5F\xA2\x4D\xAE\xED\x56\xF9\xBD\xCD\x2D\x21", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_DXE_FV_GUID
|
||||
("\xCE\xBA\xD0\x1F\x0A\x6F\x85\x40\x90\x1E\xF6\x21\x03\x85\xCB\x6F", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_PEI_FV_GUID
|
||||
("\xC5\x06\x14\xCF\xEC\x3F\xEB\x47\xA6\xC3\xB7\x1A\x3E\xE0\x0B\x95", 16);
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_UNSIGNED_FV_GUID
|
||||
("\xB6\x16\xA0\xF2\x14\xE8\x2E\x40\xA3\x95\x46\xD3\xCF\x75\x26\x4A", 16);
|
||||
|
||||
// GUIDs of GUID-defined sections
|
||||
extern const UByteArray EFI_GUIDED_SECTION_CRC32 // FC1BCDB0-7D31-49AA-936A-A4600D9DD083
|
||||
("\xB0\xCD\x1B\xFC\x31\x7D\xAA\x49\x93\x6A\xA4\x60\x0D\x9D\xD0\x83", 16);
|
||||
|
|
61
common/ffs.h
61
common/ffs.h
|
@ -862,6 +862,67 @@ extern const UByteArray AMI_ROM_HOLE_FILE_GUID_13; //05CA0209-0FC1-11DC-9011-001
|
|||
extern const UByteArray AMI_ROM_HOLE_FILE_GUID_14; // 05CA020A-0FC1-11DC-9011-00173153EBA8
|
||||
extern const UByteArray AMI_ROM_HOLE_FILE_GUID_15; // 05CA020B-0FC1-11DC-9011-00173153EBA8
|
||||
|
||||
//
|
||||
// Insyde Flash Device Map
|
||||
//
|
||||
#define INSYDE_FLASH_DEVICE_MAP_SIGNATURE 0x4D444648 // HFDM
|
||||
|
||||
typedef struct _INSYDE_FLASH_DEVICE_MAP_HEADER {
|
||||
UINT32 Signature;
|
||||
UINT32 Size;
|
||||
UINT32 DataOffset;
|
||||
UINT32 EntrySize;
|
||||
UINT8 EntryFormat;
|
||||
UINT8 Revision;
|
||||
UINT8 ExtensionCount;
|
||||
UINT8 Checksum;
|
||||
UINT64 FdBaseAddress;
|
||||
//INSYDE_FLASH_DEVICE_MAP_EXTENSION Extensions[ExtensionCount];
|
||||
} INSYDE_FLASH_DEVICE_MAP_HEADER;
|
||||
|
||||
typedef struct _INSYDE_FLASH_DEVICE_MAP_EXTENSION {
|
||||
UINT16 EntryOffset;
|
||||
UINT16 EntryCount;
|
||||
} INSYDE_FLASH_DEVICE_MAP_EXTENSION;
|
||||
|
||||
typedef struct _INSYDE_FLASH_DEVICE_MAP_ENTRY {
|
||||
EFI_GUID RegionTypeGuid;
|
||||
UINT8 RegionId[16];
|
||||
UINT64 RegionOffset;
|
||||
UINT64 RegionSize;
|
||||
UINT32 Attributes;
|
||||
//UINT8 Hash[]; // Size depends on EntryFormat and EntrySize of the header
|
||||
} INSYDE_FLASH_DEVICE_MAP_ENTRY;
|
||||
|
||||
typedef struct _INSYDE_FLASH_DEVICE_MAP_BOARD_ID_MAP {
|
||||
UINT32 BoardIdIndex;
|
||||
UINT32 BoardIdCount;
|
||||
//UINT64 BoardIds[Count];
|
||||
} INSYDE_FLASH_DEVICE_MAP_BOARD_ID_MAP;
|
||||
|
||||
#define INSYDE_FLASH_DEVICE_MAP_ENTRY_ATTRIBUTE_MODIFIABLE 0x00000001
|
||||
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_BOOT_FV_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_BVDT_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_EC_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_FTW_BACKUP_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_FTW_STATE_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_FV_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_FLASH_DEVICE_MAP_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_LOGO_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_MICROCODE_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_MSDM_TABLE_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_MULTI_CONFIG_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_VAR_DEFAULT_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_SMBIOS_UPDATE_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_VAR_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_UNKNOWN_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_UNUSED_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_USB_OPTION_ROM_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_DXE_FV_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_PEI_FV_GUID;
|
||||
extern const UByteArray INSYDE_FLASH_MAP_REGION_UNSIGNED_FV_GUID;
|
||||
|
||||
// Restore previous packing rules
|
||||
#pragma pack(pop)
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@
|
|||
#include "digest/sha2.h"
|
||||
#include "digest/sm3.h"
|
||||
|
||||
#include "umemstream.h"
|
||||
#include "kaitai/kaitaistream.h"
|
||||
#include "generated/insyde_fdm.h"
|
||||
|
||||
// Constructor
|
||||
FfsParser::FfsParser(TreeModel* treeModel) : model(treeModel),
|
||||
imageBase(0), addressDiff(0x100000000ULL), protectedRegionsBase(0) {
|
||||
|
@ -948,6 +952,114 @@ USTATUS FfsParser::parseRawArea(const UModelIndex & index)
|
|||
msg(usprintf("%s: BPDT store parsing failed with error ", __FUNCTION__) + errorCodeToUString(result), index);
|
||||
}
|
||||
}
|
||||
else if (itemType == Types::InsydeFlashDeviceMapStore) {
|
||||
UByteArray fdm = data.mid(itemOffset, itemSize);
|
||||
umemstream is(fdm.constData(), fdm.size());
|
||||
kaitai::kstream ks(&is);
|
||||
insyde_fdm_t parsed(&ks);
|
||||
UINT32 storeSize = (UINT32)fdm.size();
|
||||
|
||||
// Construct header and body
|
||||
UByteArray header = fdm.left(parsed.data_offset());
|
||||
UByteArray body = fdm.mid(header.size(), storeSize - header.size());
|
||||
|
||||
// Add info
|
||||
UString name = UString("Insyde H2O FlashDeviceMap");
|
||||
UString info = usprintf("Signature: HFDM\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nData offset: %Xh\nEntry size: %Xh (%u)\nEntry format: %02Xh\nRevision: %02Xh\nExtension count: %u\nFlash descriptor base address: %08Xh\nChecksum: %02Xh",
|
||||
storeSize, storeSize,
|
||||
(UINT32)header.size(), (UINT32)header.size(),
|
||||
(UINT32)body.size(), (UINT32)body.size(),
|
||||
parsed.data_offset(),
|
||||
parsed.entry_size(), parsed.entry_size(),
|
||||
parsed.entry_format(),
|
||||
parsed.revision(),
|
||||
parsed.num_extensions(),
|
||||
(UINT32)parsed.fd_base_address(),
|
||||
parsed.checksum());
|
||||
|
||||
// Check header checksum
|
||||
{
|
||||
UByteArray tempHeader = data.mid(itemOffset, sizeof(INSYDE_FLASH_DEVICE_MAP_HEADER));
|
||||
INSYDE_FLASH_DEVICE_MAP_HEADER* tempFdmHeader = (INSYDE_FLASH_DEVICE_MAP_HEADER*)tempHeader.data();
|
||||
tempFdmHeader->Checksum = 0;
|
||||
UINT8 calculated = calculateChecksum8((const UINT8*)tempFdmHeader, (UINT32)tempHeader.size());
|
||||
if (calculated == parsed.checksum()) {
|
||||
info += UString(", valid");
|
||||
}
|
||||
else {
|
||||
info += usprintf(", invalid, should be %02Xh", calculated);
|
||||
}
|
||||
}
|
||||
|
||||
// Add board IDs
|
||||
if (parsed.revision() == 3) {
|
||||
info += usprintf("\nRegion index: %Xh\nBoardId Count: %u",
|
||||
parsed.board_ids()->region_index(),
|
||||
parsed.board_ids()->num_board_ids());
|
||||
UINT32 i = 0;
|
||||
for (const auto & boardId : *parsed.board_ids()->board_ids()) {
|
||||
info += usprintf("\nBoardId #%u: %" PRIX64 "\n", i++, boardId);
|
||||
}
|
||||
}
|
||||
|
||||
// Add header tree item
|
||||
UModelIndex headerIndex = model->addItem(headerSize + itemOffset, Types::InsydeFlashDeviceMapStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
|
||||
|
||||
// Add entries
|
||||
UINT32 entryOffset = parsed.data_offset();
|
||||
bool protectedRangeFound = false;
|
||||
for (const auto & entry : *parsed.entries()->entries()) {
|
||||
const EFI_GUID guid = readUnaligned((const EFI_GUID*)entry->guid().c_str());
|
||||
name = insydeFlashDeviceMapEntryTypeGuidToUString(guid);
|
||||
UString text;
|
||||
header = data.mid(itemOffset + entryOffset, sizeof(INSYDE_FLASH_DEVICE_MAP_ENTRY));
|
||||
body = data.mid(itemOffset + entryOffset + header.size(), parsed.entry_size() - header.size());
|
||||
|
||||
// Add info
|
||||
UINT32 entrySize = (UINT32)header.size() + (UINT32)body.size();
|
||||
info = UString("Region type: ") + guidToUString(guid, false) + "\n";
|
||||
info += UString("Region id: ");
|
||||
for (UINT8 i = 0; i < 16; i++) {
|
||||
info += usprintf("%02X", *(const UINT8*)(entry->region_id().c_str() + i));
|
||||
}
|
||||
info += usprintf("\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nRegion address: %08Xh\nRegion size: %08Xh\nAttributes: %08Xh",
|
||||
entrySize, entrySize,
|
||||
(UINT32)header.size(), (UINT32)header.size(),
|
||||
(UINT32)body.size(), (UINT32)body.size(),
|
||||
(UINT32)entry->region_base(),
|
||||
(UINT32)entry->region_size(),
|
||||
entry->attributes());
|
||||
|
||||
if ((entry->attributes() & INSYDE_FLASH_DEVICE_MAP_ENTRY_ATTRIBUTE_MODIFIABLE) == 0) {
|
||||
if (!protectedRangeFound) {
|
||||
securityInfo += usprintf("Insyde Flash Device Map found at base %08Xh\nProtected ranges:\n", model->base(headerIndex));
|
||||
protectedRangeFound = true;
|
||||
}
|
||||
|
||||
// TODO: make sure that the only hash possible here is SHA256
|
||||
|
||||
// Add this region to the list of Insyde protected regions
|
||||
PROTECTED_RANGE range = {};
|
||||
range.Offset = (UINT32)entry->region_base();
|
||||
range.Size = (UINT32)entry->region_size();
|
||||
range.AlgorithmId = TCG_HASH_ALGORITHM_ID_SHA256;
|
||||
range.Type = PROTECTED_RANGE_VENDOR_HASH_INSYDE;
|
||||
range.Hash = body;
|
||||
protectedRanges.push_back(range);
|
||||
|
||||
securityInfo += usprintf("Address: %08Xh Size: %Xh\nHash: ", range.Offset, range.Size) + UString(body.toHex().constData()) + "\n";
|
||||
}
|
||||
|
||||
// Add tree item
|
||||
model->addItem(entryOffset, Types::InsydeFlashDeviceMapEntry, 0, name, text, info, header, body, UByteArray(), Fixed, headerIndex);
|
||||
|
||||
entryOffset += entrySize;
|
||||
}
|
||||
|
||||
if (protectedRangeFound) {
|
||||
securityInfo += "\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return U_UNKNOWN_ITEM_TYPE;
|
||||
}
|
||||
|
@ -992,6 +1104,9 @@ USTATUS FfsParser::parseRawArea(const UModelIndex & index)
|
|||
case Types::BpdtPartition:
|
||||
// Parsing already done
|
||||
break;
|
||||
case Types::InsydeFlashDeviceMapStore:
|
||||
// Parsing already done
|
||||
break;
|
||||
case Types::Padding:
|
||||
// No parsing required
|
||||
break;
|
||||
|
@ -1415,6 +1530,28 @@ continue_searching: {}
|
|||
nextItemOffset = offset;
|
||||
break;
|
||||
}
|
||||
else if (readUnaligned(currentPos) == INSYDE_FLASH_DEVICE_MAP_SIGNATURE) {
|
||||
// Check data size
|
||||
if (restSize < sizeof(INSYDE_FLASH_DEVICE_MAP_HEADER))
|
||||
continue;
|
||||
|
||||
const INSYDE_FLASH_DEVICE_MAP_HEADER *fdmHeader = (const INSYDE_FLASH_DEVICE_MAP_HEADER *)currentPos;
|
||||
|
||||
if (restSize < fdmHeader->Size)
|
||||
continue;
|
||||
|
||||
if (fdmHeader->Revision > 3) {
|
||||
msg(usprintf("%s: Insyde Flash Device Map candidate with unknown revision %u", __FUNCTION__, fdmHeader->Revision), index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// All checks passed, FDM found
|
||||
nextItemType = Types::InsydeFlashDeviceMapStore;
|
||||
nextItemSize = fdmHeader->Size;
|
||||
nextItemAlternativeSize = fdmHeader->Size;
|
||||
nextItemOffset = offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No more stores found
|
||||
|
@ -3764,6 +3901,26 @@ USTATUS FfsParser::checkProtectedRanges(const UModelIndex & index)
|
|||
// Do nothing, this range is likely not found in the image
|
||||
}
|
||||
}
|
||||
else if (protectedRanges[i].Type == PROTECTED_RANGE_VENDOR_HASH_INSYDE) {
|
||||
try {
|
||||
protectedRanges[i].Offset -= (UINT32)addressDiff;
|
||||
protectedParts = openedImage.mid(protectedRanges[i].Offset, protectedRanges[i].Size);
|
||||
|
||||
UByteArray digest(SHA256_HASH_SIZE, '\x00');
|
||||
sha256(protectedParts.constData(), protectedParts.size(), digest.data());
|
||||
|
||||
if (digest != protectedRanges[i].Hash) {
|
||||
msg(usprintf("%s: Insyde protected range [%Xh:%Xh] hash mismatch, opened image may refuse to boot", __FUNCTION__,
|
||||
protectedRanges[i].Offset, protectedRanges[i].Offset + protectedRanges[i].Size),
|
||||
model->findByBase(protectedRanges[i].Offset));
|
||||
}
|
||||
|
||||
markProtectedRangeRecursive(index, protectedRanges[i]);
|
||||
}
|
||||
catch(...) {
|
||||
// Do nothing, this range is likely not found in the image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return U_SUCCESS;
|
||||
|
|
|
@ -67,6 +67,7 @@ typedef struct PROTECTED_RANGE_ {
|
|||
#define PROTECTED_RANGE_VENDOR_HASH_AMI_V2 0x06
|
||||
#define PROTECTED_RANGE_VENDOR_HASH_AMI_V3 0x07
|
||||
#define PROTECTED_RANGE_VENDOR_HASH_MICROSOFT_PMDA 0x08
|
||||
#define PROTECTED_RANGE_VENDOR_HASH_INSYDE 0x09
|
||||
|
||||
class FitParser;
|
||||
class NvramParser;
|
||||
|
|
|
@ -12,35 +12,11 @@ edk2_ftw_t::edk2_ftw_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, edk2_
|
|||
}
|
||||
|
||||
void edk2_ftw_t::_read() {
|
||||
m_signature = m__io->read_u4le();
|
||||
m_signature = m__io->read_bytes(16);
|
||||
{
|
||||
uint32_t _ = signature();
|
||||
if (!( ((_ == 4293995405UL) || (_ == 2656577835UL)) )) {
|
||||
throw kaitai::validation_expr_error<uint32_t>(signature(), _io(), std::string("/seq/0"));
|
||||
}
|
||||
}
|
||||
n_signature_main = true;
|
||||
if (signature() == 4293995405UL) {
|
||||
n_signature_main = false;
|
||||
m_signature_main = m__io->read_bytes(12);
|
||||
if (!(signature_main() == std::string("\x96\x76\x8B\x4C\xA9\x85\x27\x47\x07\x5B\x4F\x50", 12))) {
|
||||
throw kaitai::validation_not_equal_error<std::string>(std::string("\x96\x76\x8B\x4C\xA9\x85\x27\x47\x07\x5B\x4F\x50", 12), signature_main(), _io(), std::string("/seq/1"));
|
||||
}
|
||||
}
|
||||
n_signature_edk2_working_block = true;
|
||||
if (signature() == 2656577835UL) {
|
||||
n_signature_edk2_working_block = false;
|
||||
m_signature_edk2_working_block = m__io->read_bytes(12);
|
||||
if (!(signature_edk2_working_block() == std::string("\x68\x7C\x7D\x49\x0A\xCE\x65\x00\xFD\x9F\x1B\x95", 12))) {
|
||||
throw kaitai::validation_not_equal_error<std::string>(std::string("\x68\x7C\x7D\x49\x0A\xCE\x65\x00\xFD\x9F\x1B\x95", 12), signature_edk2_working_block(), _io(), std::string("/seq/2"));
|
||||
}
|
||||
}
|
||||
n_signature_vss2_working_block = true;
|
||||
if (signature() == 2656577835UL) {
|
||||
n_signature_vss2_working_block = false;
|
||||
m_signature_vss2_working_block = m__io->read_bytes(12);
|
||||
if (!(signature_vss2_working_block() == std::string("\x68\x7C\x7D\x49\xA0\xCE\x65\x00\xFD\x9F\x1B\x95", 12))) {
|
||||
throw kaitai::validation_not_equal_error<std::string>(std::string("\x68\x7C\x7D\x49\xA0\xCE\x65\x00\xFD\x9F\x1B\x95", 12), signature_vss2_working_block(), _io(), std::string("/seq/3"));
|
||||
std::string _ = signature();
|
||||
if (!( ((_ == std::string("\x8D\x2B\xF1\xFF\x96\x76\x8B\x4C\xA9\x85\x27\x47\x07\x5B\x4F\x50", 16)) || (_ == std::string("\x2B\x29\x58\x9E\x68\x7C\x7D\x49\x0A\xCE\x65\x00\xFD\x9F\x1B\x95", 16)) || (_ == std::string("\x2B\x29\x58\x9E\x68\x7C\x7D\x49\xA0\xCE\x65\x00\xFD\x9F\x1B\x95", 16))) )) {
|
||||
throw kaitai::validation_expr_error<std::string>(signature(), _io(), std::string("/seq/0"));
|
||||
}
|
||||
}
|
||||
m_crc = m__io->read_u4le();
|
||||
|
@ -69,12 +45,6 @@ edk2_ftw_t::~edk2_ftw_t() {
|
|||
}
|
||||
|
||||
void edk2_ftw_t::_clean_up() {
|
||||
if (!n_signature_main) {
|
||||
}
|
||||
if (!n_signature_edk2_working_block) {
|
||||
}
|
||||
if (!n_signature_vss2_working_block) {
|
||||
}
|
||||
if (!n_len_write_queue_64) {
|
||||
}
|
||||
if (!n_write_queue_32) {
|
||||
|
|
|
@ -38,28 +38,7 @@ public:
|
|||
int8_t len_ftw_store_header_64();
|
||||
|
||||
private:
|
||||
uint32_t m_signature;
|
||||
std::string m_signature_main;
|
||||
bool n_signature_main;
|
||||
|
||||
public:
|
||||
bool _is_null_signature_main() { signature_main(); return n_signature_main; };
|
||||
|
||||
private:
|
||||
std::string m_signature_edk2_working_block;
|
||||
bool n_signature_edk2_working_block;
|
||||
|
||||
public:
|
||||
bool _is_null_signature_edk2_working_block() { signature_edk2_working_block(); return n_signature_edk2_working_block; };
|
||||
|
||||
private:
|
||||
std::string m_signature_vss2_working_block;
|
||||
bool n_signature_vss2_working_block;
|
||||
|
||||
public:
|
||||
bool _is_null_signature_vss2_working_block() { signature_vss2_working_block(); return n_signature_vss2_working_block; };
|
||||
|
||||
private:
|
||||
std::string m_signature;
|
||||
uint32_t m_crc;
|
||||
uint8_t m_state;
|
||||
std::string m_reserved;
|
||||
|
@ -89,10 +68,7 @@ private:
|
|||
kaitai::kstruct* m__parent;
|
||||
|
||||
public:
|
||||
uint32_t signature() const { return m_signature; }
|
||||
std::string signature_main() const { return m_signature_main; }
|
||||
std::string signature_edk2_working_block() const { return m_signature_edk2_working_block; }
|
||||
std::string signature_vss2_working_block() const { return m_signature_vss2_working_block; }
|
||||
std::string signature() const { return m_signature; }
|
||||
uint32_t crc() const { return m_crc; }
|
||||
uint8_t state() const { return m_state; }
|
||||
std::string reserved() const { return m_reserved; }
|
||||
|
|
|
@ -354,7 +354,7 @@ int32_t edk2_vss2_t::vss2_variable_t::end_offset() {
|
|||
int32_t edk2_vss2_t::vss2_variable_t::len_alignment_padding_auth() {
|
||||
if (f_len_alignment_padding_auth)
|
||||
return m_len_alignment_padding_auth;
|
||||
m_len_alignment_padding_auth = ((((end_offset_auth() - offset()) + 3) & ~3) - (end_offset() - offset()));
|
||||
m_len_alignment_padding_auth = ((((end_offset_auth() - offset()) + 3) & ~3) - (end_offset_auth() - offset()));
|
||||
f_len_alignment_padding_auth = true;
|
||||
return m_len_alignment_padding_auth;
|
||||
}
|
||||
|
|
179
common/generated/insyde_fdm.cpp
Normal file
179
common/generated/insyde_fdm.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
|
||||
|
||||
#include "insyde_fdm.h"
|
||||
#include "../kaitai/exceptions.h"
|
||||
|
||||
insyde_fdm_t::insyde_fdm_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, insyde_fdm_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = this; (void)p__root;
|
||||
m_extensions = nullptr;
|
||||
m__io__raw_extensions = nullptr;
|
||||
m_board_ids = nullptr;
|
||||
m_entries = nullptr;
|
||||
m__io__raw_entries = nullptr;
|
||||
_read();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::_read() {
|
||||
m_signature = m__io->read_u4le();
|
||||
if (!(signature() == 1296320072)) {
|
||||
throw kaitai::validation_not_equal_error<uint32_t>(1296320072, signature(), _io(), std::string("/seq/0"));
|
||||
}
|
||||
m_store_size = m__io->read_u4le();
|
||||
m_data_offset = m__io->read_u4le();
|
||||
m_entry_size = m__io->read_u4le();
|
||||
m_entry_format = m__io->read_u1();
|
||||
m_revision = m__io->read_u1();
|
||||
m_num_extensions = m__io->read_u1();
|
||||
m_checksum = m__io->read_u1();
|
||||
m_fd_base_address = m__io->read_u8le();
|
||||
n_extensions = true;
|
||||
if (revision() == 3) {
|
||||
n_extensions = false;
|
||||
m__raw_extensions = m__io->read_bytes((num_extensions() * 4));
|
||||
m__io__raw_extensions = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_extensions));
|
||||
m_extensions = std::unique_ptr<fdm_extensions_t>(new fdm_extensions_t(m__io__raw_extensions.get(), this, m__root));
|
||||
}
|
||||
n_board_ids = true;
|
||||
if (revision() == 3) {
|
||||
n_board_ids = false;
|
||||
m_board_ids = std::unique_ptr<fdm_board_ids_t>(new fdm_board_ids_t(m__io, this, m__root));
|
||||
}
|
||||
m__raw_entries = m__io->read_bytes((store_size() - data_offset()));
|
||||
m__io__raw_entries = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_entries));
|
||||
m_entries = std::unique_ptr<fdm_entries_t>(new fdm_entries_t(m__io__raw_entries.get(), this, m__root));
|
||||
}
|
||||
|
||||
insyde_fdm_t::~insyde_fdm_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::_clean_up() {
|
||||
if (!n_extensions) {
|
||||
}
|
||||
if (!n_board_ids) {
|
||||
}
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_entries_t::fdm_entries_t(kaitai::kstream* p__io, insyde_fdm_t* p__parent, insyde_fdm_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
m_entries = nullptr;
|
||||
_read();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_entries_t::_read() {
|
||||
m_entries = std::unique_ptr<std::vector<std::unique_ptr<fdm_entry_t>>>(new std::vector<std::unique_ptr<fdm_entry_t>>());
|
||||
{
|
||||
int i = 0;
|
||||
while (!m__io->is_eof()) {
|
||||
m_entries->push_back(std::move(std::unique_ptr<fdm_entry_t>(new fdm_entry_t(m__io, this, m__root))));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_entries_t::~fdm_entries_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_entries_t::_clean_up() {
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_extension_t::fdm_extension_t(kaitai::kstream* p__io, insyde_fdm_t::fdm_extensions_t* p__parent, insyde_fdm_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
_read();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_extension_t::_read() {
|
||||
m_offset = m__io->read_u2le();
|
||||
m_count = m__io->read_u2le();
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_extension_t::~fdm_extension_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_extension_t::_clean_up() {
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_board_ids_t::fdm_board_ids_t(kaitai::kstream* p__io, insyde_fdm_t* p__parent, insyde_fdm_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
m_board_ids = nullptr;
|
||||
_read();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_board_ids_t::_read() {
|
||||
m_region_index = m__io->read_u4le();
|
||||
m_num_board_ids = m__io->read_u4le();
|
||||
m_board_ids = std::unique_ptr<std::vector<uint64_t>>(new std::vector<uint64_t>());
|
||||
const int l_board_ids = num_board_ids();
|
||||
for (int i = 0; i < l_board_ids; i++) {
|
||||
m_board_ids->push_back(std::move(m__io->read_u8le()));
|
||||
}
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_board_ids_t::~fdm_board_ids_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_board_ids_t::_clean_up() {
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_extensions_t::fdm_extensions_t(kaitai::kstream* p__io, insyde_fdm_t* p__parent, insyde_fdm_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
m_extensions = nullptr;
|
||||
_read();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_extensions_t::_read() {
|
||||
m_extensions = std::unique_ptr<std::vector<std::unique_ptr<fdm_extension_t>>>(new std::vector<std::unique_ptr<fdm_extension_t>>());
|
||||
{
|
||||
int i = 0;
|
||||
while (!m__io->is_eof()) {
|
||||
m_extensions->push_back(std::move(std::unique_ptr<fdm_extension_t>(new fdm_extension_t(m__io, this, m__root))));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_extensions_t::~fdm_extensions_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_extensions_t::_clean_up() {
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_entry_t::fdm_entry_t(kaitai::kstream* p__io, insyde_fdm_t::fdm_entries_t* p__parent, insyde_fdm_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
f_region_base = false;
|
||||
_read();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_entry_t::_read() {
|
||||
m_guid = m__io->read_bytes(16);
|
||||
m_region_id = m__io->read_bytes(16);
|
||||
m_region_offset = m__io->read_u8le();
|
||||
m_region_size = m__io->read_u8le();
|
||||
m_attributes = m__io->read_u4le();
|
||||
m_hash = m__io->read_bytes((((((_parent()->_parent()->entry_size() - 16) - 16) - 8) - 8) - 4));
|
||||
}
|
||||
|
||||
insyde_fdm_t::fdm_entry_t::~fdm_entry_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void insyde_fdm_t::fdm_entry_t::_clean_up() {
|
||||
}
|
||||
|
||||
int32_t insyde_fdm_t::fdm_entry_t::region_base() {
|
||||
if (f_region_base)
|
||||
return m_region_base;
|
||||
m_region_base = (static_cast<uint32_t>(_root()->fd_base_address()) + static_cast<uint32_t>(region_offset()));
|
||||
f_region_base = true;
|
||||
return m_region_base;
|
||||
}
|
232
common/generated/insyde_fdm.h
Normal file
232
common/generated/insyde_fdm.h
Normal file
|
@ -0,0 +1,232 @@
|
|||
#pragma once
|
||||
|
||||
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
|
||||
|
||||
#include "../kaitai/kaitaistruct.h"
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#if KAITAI_STRUCT_VERSION < 9000L
|
||||
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
|
||||
#endif
|
||||
|
||||
class insyde_fdm_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
class fdm_entries_t;
|
||||
class fdm_extension_t;
|
||||
class fdm_board_ids_t;
|
||||
class fdm_extensions_t;
|
||||
class fdm_entry_t;
|
||||
|
||||
insyde_fdm_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, insyde_fdm_t* p__root = nullptr);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~insyde_fdm_t();
|
||||
|
||||
class fdm_entries_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
fdm_entries_t(kaitai::kstream* p__io, insyde_fdm_t* p__parent = nullptr, insyde_fdm_t* p__root = nullptr);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~fdm_entries_t();
|
||||
|
||||
private:
|
||||
std::unique_ptr<std::vector<std::unique_ptr<fdm_entry_t>>> m_entries;
|
||||
insyde_fdm_t* m__root;
|
||||
insyde_fdm_t* m__parent;
|
||||
|
||||
public:
|
||||
std::vector<std::unique_ptr<fdm_entry_t>>* entries() const { return m_entries.get(); }
|
||||
insyde_fdm_t* _root() const { return m__root; }
|
||||
insyde_fdm_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class fdm_extension_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
fdm_extension_t(kaitai::kstream* p__io, insyde_fdm_t::fdm_extensions_t* p__parent = nullptr, insyde_fdm_t* p__root = nullptr);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~fdm_extension_t();
|
||||
|
||||
private:
|
||||
uint16_t m_offset;
|
||||
uint16_t m_count;
|
||||
insyde_fdm_t* m__root;
|
||||
insyde_fdm_t::fdm_extensions_t* m__parent;
|
||||
|
||||
public:
|
||||
uint16_t offset() const { return m_offset; }
|
||||
uint16_t count() const { return m_count; }
|
||||
insyde_fdm_t* _root() const { return m__root; }
|
||||
insyde_fdm_t::fdm_extensions_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class fdm_board_ids_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
fdm_board_ids_t(kaitai::kstream* p__io, insyde_fdm_t* p__parent = nullptr, insyde_fdm_t* p__root = nullptr);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~fdm_board_ids_t();
|
||||
|
||||
private:
|
||||
uint32_t m_region_index;
|
||||
uint32_t m_num_board_ids;
|
||||
std::unique_ptr<std::vector<uint64_t>> m_board_ids;
|
||||
insyde_fdm_t* m__root;
|
||||
insyde_fdm_t* m__parent;
|
||||
|
||||
public:
|
||||
uint32_t region_index() const { return m_region_index; }
|
||||
uint32_t num_board_ids() const { return m_num_board_ids; }
|
||||
std::vector<uint64_t>* board_ids() const { return m_board_ids.get(); }
|
||||
insyde_fdm_t* _root() const { return m__root; }
|
||||
insyde_fdm_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class fdm_extensions_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
fdm_extensions_t(kaitai::kstream* p__io, insyde_fdm_t* p__parent = nullptr, insyde_fdm_t* p__root = nullptr);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~fdm_extensions_t();
|
||||
|
||||
private:
|
||||
std::unique_ptr<std::vector<std::unique_ptr<fdm_extension_t>>> m_extensions;
|
||||
insyde_fdm_t* m__root;
|
||||
insyde_fdm_t* m__parent;
|
||||
|
||||
public:
|
||||
std::vector<std::unique_ptr<fdm_extension_t>>* extensions() const { return m_extensions.get(); }
|
||||
insyde_fdm_t* _root() const { return m__root; }
|
||||
insyde_fdm_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class fdm_entry_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
fdm_entry_t(kaitai::kstream* p__io, insyde_fdm_t::fdm_entries_t* p__parent = nullptr, insyde_fdm_t* p__root = nullptr);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~fdm_entry_t();
|
||||
|
||||
private:
|
||||
bool f_region_base;
|
||||
int32_t m_region_base;
|
||||
|
||||
public:
|
||||
int32_t region_base();
|
||||
|
||||
private:
|
||||
std::string m_guid;
|
||||
std::string m_region_id;
|
||||
uint64_t m_region_offset;
|
||||
uint64_t m_region_size;
|
||||
uint32_t m_attributes;
|
||||
std::string m_hash;
|
||||
insyde_fdm_t* m__root;
|
||||
insyde_fdm_t::fdm_entries_t* m__parent;
|
||||
|
||||
public:
|
||||
std::string guid() const { return m_guid; }
|
||||
std::string region_id() const { return m_region_id; }
|
||||
uint64_t region_offset() const { return m_region_offset; }
|
||||
uint64_t region_size() const { return m_region_size; }
|
||||
uint32_t attributes() const { return m_attributes; }
|
||||
std::string hash() const { return m_hash; }
|
||||
insyde_fdm_t* _root() const { return m__root; }
|
||||
insyde_fdm_t::fdm_entries_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
private:
|
||||
uint32_t m_signature;
|
||||
uint32_t m_store_size;
|
||||
uint32_t m_data_offset;
|
||||
uint32_t m_entry_size;
|
||||
uint8_t m_entry_format;
|
||||
uint8_t m_revision;
|
||||
uint8_t m_num_extensions;
|
||||
uint8_t m_checksum;
|
||||
uint64_t m_fd_base_address;
|
||||
std::unique_ptr<fdm_extensions_t> m_extensions;
|
||||
bool n_extensions;
|
||||
|
||||
public:
|
||||
bool _is_null_extensions() { extensions(); return n_extensions; };
|
||||
|
||||
private:
|
||||
std::unique_ptr<fdm_board_ids_t> m_board_ids;
|
||||
bool n_board_ids;
|
||||
|
||||
public:
|
||||
bool _is_null_board_ids() { board_ids(); return n_board_ids; };
|
||||
|
||||
private:
|
||||
std::unique_ptr<fdm_entries_t> m_entries;
|
||||
insyde_fdm_t* m__root;
|
||||
kaitai::kstruct* m__parent;
|
||||
std::string m__raw_extensions;
|
||||
bool n__raw_extensions;
|
||||
|
||||
public:
|
||||
bool _is_null__raw_extensions() { _raw_extensions(); return n__raw_extensions; };
|
||||
|
||||
private:
|
||||
std::unique_ptr<kaitai::kstream> m__io__raw_extensions;
|
||||
std::string m__raw_entries;
|
||||
std::unique_ptr<kaitai::kstream> m__io__raw_entries;
|
||||
|
||||
public:
|
||||
uint32_t signature() const { return m_signature; }
|
||||
uint32_t store_size() const { return m_store_size; }
|
||||
uint32_t data_offset() const { return m_data_offset; }
|
||||
uint32_t entry_size() const { return m_entry_size; }
|
||||
uint8_t entry_format() const { return m_entry_format; }
|
||||
uint8_t revision() const { return m_revision; }
|
||||
uint8_t num_extensions() const { return m_num_extensions; }
|
||||
uint8_t checksum() const { return m_checksum; }
|
||||
uint64_t fd_base_address() const { return m_fd_base_address; }
|
||||
fdm_extensions_t* extensions() const { return m_extensions.get(); }
|
||||
fdm_board_ids_t* board_ids() const { return m_board_ids.get(); }
|
||||
fdm_entries_t* entries() const { return m_entries.get(); }
|
||||
insyde_fdm_t* _root() const { return m__root; }
|
||||
kaitai::kstruct* _parent() const { return m__parent; }
|
||||
std::string _raw_extensions() const { return m__raw_extensions; }
|
||||
kaitai::kstream* _io__raw_extensions() const { return m__io__raw_extensions.get(); }
|
||||
std::string _raw_entries() const { return m__raw_entries; }
|
||||
kaitai::kstream* _io__raw_entries() const { return m__io__raw_entries.get(); }
|
||||
};
|
|
@ -17,18 +17,18 @@ void phoenix_evsa_t::_read() {
|
|||
throw kaitai::validation_not_equal_error<uint8_t>(236, type(), _io(), std::string("/seq/0"));
|
||||
}
|
||||
m_checksum = m__io->read_u1();
|
||||
m_size = m__io->read_u2le();
|
||||
if (!(size() == 20)) {
|
||||
throw kaitai::validation_not_equal_error<uint16_t>(20, size(), _io(), std::string("/seq/2"));
|
||||
m_len_evsa_store_header = m__io->read_u2le();
|
||||
if (!(len_evsa_store_header() == 20)) {
|
||||
throw kaitai::validation_not_equal_error<uint16_t>(20, len_evsa_store_header(), _io(), std::string("/seq/2"));
|
||||
}
|
||||
m_signature = m__io->read_u4le();
|
||||
if (!(signature() == 1095980613)) {
|
||||
throw kaitai::validation_not_equal_error<uint32_t>(1095980613, signature(), _io(), std::string("/seq/3"));
|
||||
}
|
||||
m_attributes = m__io->read_u4le();
|
||||
m_store_size = m__io->read_u4le();
|
||||
m_len_evsa_store = m__io->read_u4le();
|
||||
m_reserved = m__io->read_u4le();
|
||||
m__raw_body = m__io->read_bytes((store_size() - 20));
|
||||
m__raw_body = m__io->read_bytes((len_evsa_store() - len_evsa_store_header()));
|
||||
m__io__raw_body = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_body));
|
||||
m_body = std::unique_ptr<evsa_body_t>(new evsa_body_t(m__io__raw_body.get(), this, m__root));
|
||||
}
|
||||
|
@ -47,10 +47,18 @@ phoenix_evsa_t::evsa_entry_t::evsa_entry_t(kaitai::kstream* p__io, phoenix_evsa_
|
|||
}
|
||||
|
||||
void phoenix_evsa_t::evsa_entry_t::_read() {
|
||||
m_type = m__io->read_u1();
|
||||
m_entry_type = m__io->read_u1();
|
||||
n_checksum = true;
|
||||
if ( ((entry_type() == 225) || (entry_type() == 226) || (entry_type() == 227) || (entry_type() == 237) || (entry_type() == 238) || (entry_type() == 239) || (entry_type() == 131)) ) {
|
||||
n_checksum = false;
|
||||
m_checksum = m__io->read_u1();
|
||||
m_size = m__io->read_u2le();
|
||||
switch (type()) {
|
||||
}
|
||||
n_len_evsa_entry = true;
|
||||
if ( ((entry_type() == 225) || (entry_type() == 226) || (entry_type() == 227) || (entry_type() == 237) || (entry_type() == 238) || (entry_type() == 239) || (entry_type() == 131)) ) {
|
||||
n_len_evsa_entry = false;
|
||||
m_len_evsa_entry = m__io->read_u2le();
|
||||
}
|
||||
switch (entry_type()) {
|
||||
case 239: {
|
||||
m_body = std::unique_ptr<evsa_data_t>(new evsa_data_t(m__io, this, m__root));
|
||||
break;
|
||||
|
@ -91,6 +99,10 @@ phoenix_evsa_t::evsa_entry_t::~evsa_entry_t() {
|
|||
}
|
||||
|
||||
void phoenix_evsa_t::evsa_entry_t::_clean_up() {
|
||||
if (!n_checksum) {
|
||||
}
|
||||
if (!n_len_evsa_entry) {
|
||||
}
|
||||
}
|
||||
|
||||
phoenix_evsa_t::evsa_unknown_t::evsa_unknown_t(kaitai::kstream* p__io, phoenix_evsa_t::evsa_entry_t* p__parent, phoenix_evsa_t* p__root) : kaitai::kstruct(p__io) {
|
||||
|
@ -114,15 +126,26 @@ phoenix_evsa_t::evsa_body_t::evsa_body_t(kaitai::kstream* p__io, phoenix_evsa_t*
|
|||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
m_entries = nullptr;
|
||||
m_free_space = nullptr;
|
||||
_read();
|
||||
}
|
||||
|
||||
void phoenix_evsa_t::evsa_body_t::_read() {
|
||||
m_entries = std::unique_ptr<std::vector<std::unique_ptr<evsa_entry_t>>>(new std::vector<std::unique_ptr<evsa_entry_t>>());
|
||||
{
|
||||
int i = 0;
|
||||
evsa_entry_t* _;
|
||||
do {
|
||||
_ = new evsa_entry_t(m__io, this, m__root);
|
||||
m_entries->push_back(std::move(std::unique_ptr<evsa_entry_t>(_)));
|
||||
i++;
|
||||
} while (!( (( ((_->entry_type() != 237) && (_->entry_type() != 238) && (_->entry_type() != 239) && (_->entry_type() != 225) && (_->entry_type() != 226) && (_->entry_type() != 227) && (_->entry_type() != 131)) ) || (_io()->is_eof())) ));
|
||||
}
|
||||
m_free_space = std::unique_ptr<std::vector<uint8_t>>(new std::vector<uint8_t>());
|
||||
{
|
||||
int i = 0;
|
||||
while (!m__io->is_eof()) {
|
||||
m_entries->push_back(std::move(std::unique_ptr<evsa_entry_t>(new evsa_entry_t(m__io, this, m__root))));
|
||||
m_free_space->push_back(std::move(m__io->read_u1()));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +166,7 @@ phoenix_evsa_t::evsa_name_t::evsa_name_t(kaitai::kstream* p__io, phoenix_evsa_t:
|
|||
|
||||
void phoenix_evsa_t::evsa_name_t::_read() {
|
||||
m_var_id = m__io->read_u2le();
|
||||
m_name = m__io->read_bytes((_parent()->size() - 6));
|
||||
m_name = m__io->read_bytes((_parent()->len_evsa_entry() - 6));
|
||||
}
|
||||
|
||||
phoenix_evsa_t::evsa_name_t::~evsa_name_t() {
|
||||
|
@ -162,6 +185,12 @@ phoenix_evsa_t::evsa_guid_t::evsa_guid_t(kaitai::kstream* p__io, phoenix_evsa_t:
|
|||
void phoenix_evsa_t::evsa_guid_t::_read() {
|
||||
m_guid_id = m__io->read_u2le();
|
||||
m_guid = m__io->read_bytes(16);
|
||||
{
|
||||
std::string _ = guid();
|
||||
if (!(_parent()->len_evsa_entry() == 22)) {
|
||||
throw kaitai::validation_expr_error<std::string>(guid(), _io(), std::string("/types/evsa_guid/seq/1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phoenix_evsa_t::evsa_guid_t::~evsa_guid_t() {
|
||||
|
@ -171,30 +200,57 @@ phoenix_evsa_t::evsa_guid_t::~evsa_guid_t() {
|
|||
void phoenix_evsa_t::evsa_guid_t::_clean_up() {
|
||||
}
|
||||
|
||||
phoenix_evsa_t::evsa_variable_attributes_t::evsa_variable_attributes_t(kaitai::kstream* p__io, phoenix_evsa_t::evsa_data_t* p__parent, phoenix_evsa_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
_read();
|
||||
}
|
||||
|
||||
void phoenix_evsa_t::evsa_variable_attributes_t::_read() {
|
||||
m_non_volatile = m__io->read_bits_int_le(1);
|
||||
m_boot_service = m__io->read_bits_int_le(1);
|
||||
m_runtime = m__io->read_bits_int_le(1);
|
||||
m_hw_error_record = m__io->read_bits_int_le(1);
|
||||
m_auth_write = m__io->read_bits_int_le(1);
|
||||
m_time_based_auth = m__io->read_bits_int_le(1);
|
||||
m_append_write = m__io->read_bits_int_le(1);
|
||||
m_reserved = m__io->read_bits_int_le(21);
|
||||
m_extended_header = m__io->read_bits_int_le(1);
|
||||
m_reserved1 = m__io->read_bits_int_le(3);
|
||||
}
|
||||
|
||||
phoenix_evsa_t::evsa_variable_attributes_t::~evsa_variable_attributes_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void phoenix_evsa_t::evsa_variable_attributes_t::_clean_up() {
|
||||
}
|
||||
|
||||
phoenix_evsa_t::evsa_data_t::evsa_data_t(kaitai::kstream* p__io, phoenix_evsa_t::evsa_entry_t* p__parent, phoenix_evsa_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
m_attributes = nullptr;
|
||||
_read();
|
||||
}
|
||||
|
||||
void phoenix_evsa_t::evsa_data_t::_read() {
|
||||
m_guid_id = m__io->read_u2le();
|
||||
m_var_id = m__io->read_u2le();
|
||||
m_attributes = m__io->read_u4le();
|
||||
n_data_size = true;
|
||||
if ((attributes() & 268435456) != 268435456) {
|
||||
n_data_size = false;
|
||||
m_data_size = m__io->read_u4le();
|
||||
m_attributes = std::unique_ptr<evsa_variable_attributes_t>(new evsa_variable_attributes_t(m__io, this, m__root));
|
||||
n_len_data_ext = true;
|
||||
if (attributes()->extended_header()) {
|
||||
n_len_data_ext = false;
|
||||
m_len_data_ext = m__io->read_u4le();
|
||||
}
|
||||
n_data = true;
|
||||
if ((attributes() & 268435456) == 268435456) {
|
||||
if (!(attributes()->extended_header())) {
|
||||
n_data = false;
|
||||
m_data = m__io->read_bytes((_parent()->size() - 12));
|
||||
m_data = m__io->read_bytes((_parent()->len_evsa_entry() - 12));
|
||||
}
|
||||
n_data_ext = true;
|
||||
if ((attributes() & 268435456) != 268435456) {
|
||||
if (attributes()->extended_header()) {
|
||||
n_data_ext = false;
|
||||
m_data_ext = m__io->read_bytes(data_size());
|
||||
m_data_ext = m__io->read_bytes(len_data_ext());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,7 +259,7 @@ phoenix_evsa_t::evsa_data_t::~evsa_data_t() {
|
|||
}
|
||||
|
||||
void phoenix_evsa_t::evsa_data_t::_clean_up() {
|
||||
if (!n_data_size) {
|
||||
if (!n_len_data_ext) {
|
||||
}
|
||||
if (!n_data) {
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ public:
|
|||
class evsa_body_t;
|
||||
class evsa_name_t;
|
||||
class evsa_guid_t;
|
||||
class evsa_variable_attributes_t;
|
||||
class evsa_data_t;
|
||||
|
||||
phoenix_evsa_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, phoenix_evsa_t* p__root = nullptr);
|
||||
|
@ -44,17 +45,29 @@ public:
|
|||
~evsa_entry_t();
|
||||
|
||||
private:
|
||||
uint8_t m_type;
|
||||
uint8_t m_entry_type;
|
||||
uint8_t m_checksum;
|
||||
uint16_t m_size;
|
||||
bool n_checksum;
|
||||
|
||||
public:
|
||||
bool _is_null_checksum() { checksum(); return n_checksum; };
|
||||
|
||||
private:
|
||||
uint16_t m_len_evsa_entry;
|
||||
bool n_len_evsa_entry;
|
||||
|
||||
public:
|
||||
bool _is_null_len_evsa_entry() { len_evsa_entry(); return n_len_evsa_entry; };
|
||||
|
||||
private:
|
||||
std::unique_ptr<kaitai::kstruct> m_body;
|
||||
phoenix_evsa_t* m__root;
|
||||
phoenix_evsa_t::evsa_body_t* m__parent;
|
||||
|
||||
public:
|
||||
uint8_t type() const { return m_type; }
|
||||
uint8_t entry_type() const { return m_entry_type; }
|
||||
uint8_t checksum() const { return m_checksum; }
|
||||
uint16_t size() const { return m_size; }
|
||||
uint16_t len_evsa_entry() const { return m_len_evsa_entry; }
|
||||
kaitai::kstruct* body() const { return m_body.get(); }
|
||||
phoenix_evsa_t* _root() const { return m__root; }
|
||||
phoenix_evsa_t::evsa_body_t* _parent() const { return m__parent; }
|
||||
|
@ -99,11 +112,13 @@ public:
|
|||
|
||||
private:
|
||||
std::unique_ptr<std::vector<std::unique_ptr<evsa_entry_t>>> m_entries;
|
||||
std::unique_ptr<std::vector<uint8_t>> m_free_space;
|
||||
phoenix_evsa_t* m__root;
|
||||
phoenix_evsa_t* m__parent;
|
||||
|
||||
public:
|
||||
std::vector<std::unique_ptr<evsa_entry_t>>* entries() const { return m_entries.get(); }
|
||||
std::vector<uint8_t>* free_space() const { return m_free_space.get(); }
|
||||
phoenix_evsa_t* _root() const { return m__root; }
|
||||
phoenix_evsa_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
@ -160,6 +175,48 @@ public:
|
|||
phoenix_evsa_t::evsa_entry_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class evsa_variable_attributes_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
evsa_variable_attributes_t(kaitai::kstream* p__io, phoenix_evsa_t::evsa_data_t* p__parent = nullptr, phoenix_evsa_t* p__root = nullptr);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~evsa_variable_attributes_t();
|
||||
|
||||
private:
|
||||
bool m_non_volatile;
|
||||
bool m_boot_service;
|
||||
bool m_runtime;
|
||||
bool m_hw_error_record;
|
||||
bool m_auth_write;
|
||||
bool m_time_based_auth;
|
||||
bool m_append_write;
|
||||
uint64_t m_reserved;
|
||||
bool m_extended_header;
|
||||
uint64_t m_reserved1;
|
||||
phoenix_evsa_t* m__root;
|
||||
phoenix_evsa_t::evsa_data_t* m__parent;
|
||||
|
||||
public:
|
||||
bool non_volatile() const { return m_non_volatile; }
|
||||
bool boot_service() const { return m_boot_service; }
|
||||
bool runtime() const { return m_runtime; }
|
||||
bool hw_error_record() const { return m_hw_error_record; }
|
||||
bool auth_write() const { return m_auth_write; }
|
||||
bool time_based_auth() const { return m_time_based_auth; }
|
||||
bool append_write() const { return m_append_write; }
|
||||
uint64_t reserved() const { return m_reserved; }
|
||||
bool extended_header() const { return m_extended_header; }
|
||||
uint64_t reserved1() const { return m_reserved1; }
|
||||
phoenix_evsa_t* _root() const { return m__root; }
|
||||
phoenix_evsa_t::evsa_data_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class evsa_data_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
@ -176,12 +233,12 @@ public:
|
|||
private:
|
||||
uint16_t m_guid_id;
|
||||
uint16_t m_var_id;
|
||||
uint32_t m_attributes;
|
||||
uint32_t m_data_size;
|
||||
bool n_data_size;
|
||||
std::unique_ptr<evsa_variable_attributes_t> m_attributes;
|
||||
uint32_t m_len_data_ext;
|
||||
bool n_len_data_ext;
|
||||
|
||||
public:
|
||||
bool _is_null_data_size() { data_size(); return n_data_size; };
|
||||
bool _is_null_len_data_ext() { len_data_ext(); return n_len_data_ext; };
|
||||
|
||||
private:
|
||||
std::string m_data;
|
||||
|
@ -204,8 +261,8 @@ public:
|
|||
public:
|
||||
uint16_t guid_id() const { return m_guid_id; }
|
||||
uint16_t var_id() const { return m_var_id; }
|
||||
uint32_t attributes() const { return m_attributes; }
|
||||
uint32_t data_size() const { return m_data_size; }
|
||||
evsa_variable_attributes_t* attributes() const { return m_attributes.get(); }
|
||||
uint32_t len_data_ext() const { return m_len_data_ext; }
|
||||
std::string data() const { return m_data; }
|
||||
std::string data_ext() const { return m_data_ext; }
|
||||
phoenix_evsa_t* _root() const { return m__root; }
|
||||
|
@ -215,10 +272,10 @@ public:
|
|||
private:
|
||||
uint8_t m_type;
|
||||
uint8_t m_checksum;
|
||||
uint16_t m_size;
|
||||
uint16_t m_len_evsa_store_header;
|
||||
uint32_t m_signature;
|
||||
uint32_t m_attributes;
|
||||
uint32_t m_store_size;
|
||||
uint32_t m_len_evsa_store;
|
||||
uint32_t m_reserved;
|
||||
std::unique_ptr<evsa_body_t> m_body;
|
||||
phoenix_evsa_t* m__root;
|
||||
|
@ -229,10 +286,10 @@ private:
|
|||
public:
|
||||
uint8_t type() const { return m_type; }
|
||||
uint8_t checksum() const { return m_checksum; }
|
||||
uint16_t size() const { return m_size; }
|
||||
uint16_t len_evsa_store_header() const { return m_len_evsa_store_header; }
|
||||
uint32_t signature() const { return m_signature; }
|
||||
uint32_t attributes() const { return m_attributes; }
|
||||
uint32_t store_size() const { return m_store_size; }
|
||||
uint32_t len_evsa_store() const { return m_len_evsa_store; }
|
||||
uint32_t reserved() const { return m_reserved; }
|
||||
evsa_body_t* body() const { return m_body.get(); }
|
||||
phoenix_evsa_t* _root() const { return m__root; }
|
||||
|
|
|
@ -11,18 +11,11 @@ meta:
|
|||
|
||||
seq:
|
||||
- id: signature
|
||||
type: u4
|
||||
size: 16
|
||||
valid:
|
||||
expr: _ == 0xFFF12B8D or _ == 0x9E58292B
|
||||
- id: signature_main
|
||||
contents: [0x96, 0x76, 0x8B, 0x4C, 0xA9, 0x85, 0x27, 0x47, 0x07, 0x5B, 0x4F, 0x50] # FF12B8D-7696-4C8B-A985-2747075B4F50
|
||||
if: signature == 0xFFF12B8D
|
||||
- id: signature_edk2_working_block
|
||||
contents: [0x68, 0x7C, 0x7D, 0x49, 0x0A, 0xCE, 0x65, 0x00, 0xFD, 0x9F, 0x1B, 0x95] # 9E58292B-7C68-497D-0ACE-6500FD9F1B95
|
||||
if: signature == 0x9E58292B
|
||||
- id: signature_vss2_working_block
|
||||
contents: [0x68, 0x7C, 0x7D, 0x49, 0xA0, 0xCE, 0x65, 0x00, 0xFD, 0x9F, 0x1B, 0x95] # 9E58292B-7C68-497D-A0CE-6500FD9F1B95
|
||||
if: signature == 0x9E58292B
|
||||
expr: _ == [0x8D, 0x2B, 0xF1, 0xFF, 0x96, 0x76, 0x8B, 0x4C, 0xA9, 0x85, 0x27, 0x47, 0x07, 0x5B, 0x4F, 0x50]
|
||||
or _ == [0x2B, 0x29, 0x58, 0x9E, 0x68, 0x7C, 0x7D, 0x49, 0x0A, 0xCE, 0x65, 0x00, 0xFD, 0x9F, 0x1B, 0x95]
|
||||
or _ == [0x2B, 0x29, 0x58, 0x9E, 0x68, 0x7C, 0x7D, 0x49, 0xA0, 0xCE, 0x65, 0x00, 0xFD, 0x9F, 0x1B, 0x95]
|
||||
- id: crc
|
||||
type: u4
|
||||
- id: state
|
||||
|
|
|
@ -40,6 +40,7 @@ seq:
|
|||
- id: body
|
||||
type: vss2_store_body
|
||||
size: vss2_size - len_vss2_store_header
|
||||
|
||||
instances:
|
||||
len_vss2_store_header:
|
||||
value: 7 * sizeof<u4>
|
||||
|
@ -71,7 +72,6 @@ types:
|
|||
- id: reserved
|
||||
type: b25le
|
||||
|
||||
# TODO: check if VSS2 stores can have standard VSS variables
|
||||
vss2_variable:
|
||||
seq:
|
||||
- id: invoke_offset
|
||||
|
@ -152,7 +152,7 @@ types:
|
|||
len_alignment_padding:
|
||||
value: (((end_offset - offset)+3) & ~3) - (end_offset - offset)
|
||||
len_alignment_padding_auth:
|
||||
value: (((end_offset_auth - offset)+3) & ~3) - (end_offset - offset)
|
||||
value: (((end_offset_auth - offset)+3) & ~3) - (end_offset_auth - offset)
|
||||
is_valid:
|
||||
value: state == 0x7F or state == 0x3F
|
||||
is_auth:
|
||||
|
|
91
common/ksy/insyde_fdm.ksy
Normal file
91
common/ksy/insyde_fdm.ksy
Normal file
|
@ -0,0 +1,91 @@
|
|||
meta:
|
||||
id: insyde_fdm
|
||||
title: Insyde Flash Device
|
||||
application: Insyde-based UEFI firmware
|
||||
file-extension: fdm
|
||||
tags:
|
||||
- firmware
|
||||
license: CC0-1.0
|
||||
ks-version: 0.9
|
||||
endian: le
|
||||
|
||||
seq:
|
||||
- id: signature
|
||||
type: u4
|
||||
valid: 0x4D444648 # HFDM
|
||||
- id: store_size
|
||||
type: u4
|
||||
- id: data_offset
|
||||
type: u4
|
||||
- id: entry_size
|
||||
type: u4
|
||||
- id: entry_format
|
||||
type: u1
|
||||
- id: revision
|
||||
type: u1
|
||||
- id: num_extensions
|
||||
type: u1
|
||||
- id: checksum
|
||||
type: u1
|
||||
- id: fd_base_address
|
||||
type: u8
|
||||
- id: extensions
|
||||
type: fdm_extensions
|
||||
size: num_extensions * sizeof<fdm_extension>
|
||||
if: revision == 3
|
||||
- id: board_ids
|
||||
type: fdm_board_ids
|
||||
if: revision == 3
|
||||
- id: entries
|
||||
type: fdm_entries
|
||||
size: store_size - data_offset
|
||||
|
||||
types:
|
||||
fdm_extensions:
|
||||
seq:
|
||||
- id: extensions
|
||||
type: fdm_extension
|
||||
repeat: eos
|
||||
|
||||
fdm_extension:
|
||||
seq:
|
||||
- id: offset
|
||||
type: u2
|
||||
- id: count
|
||||
type: u2
|
||||
|
||||
fdm_board_ids:
|
||||
seq:
|
||||
- id: region_index
|
||||
type: u4
|
||||
- id: num_board_ids
|
||||
type: u4
|
||||
- id: board_ids
|
||||
type: u8
|
||||
repeat: expr
|
||||
repeat-expr: num_board_ids
|
||||
|
||||
fdm_entries:
|
||||
seq:
|
||||
- id: entries
|
||||
type: fdm_entry
|
||||
repeat: eos
|
||||
|
||||
fdm_entry:
|
||||
seq:
|
||||
- id: guid
|
||||
size: 16
|
||||
- id: region_id
|
||||
size: 16
|
||||
- id: region_offset
|
||||
type: u8
|
||||
- id: region_size
|
||||
type: u8
|
||||
- id: attributes
|
||||
type: u4
|
||||
- id: hash
|
||||
size: _parent._parent.entry_size - 16 - 16 - 8 - 8 - 4
|
||||
instances:
|
||||
region_base:
|
||||
value: _root.fd_base_address.as<u4> + region_offset.as<u4>
|
||||
|
|
@ -15,21 +15,21 @@ seq:
|
|||
valid: 0xEC
|
||||
- id: checksum
|
||||
type: u1
|
||||
- id: size
|
||||
- id: len_evsa_store_header
|
||||
type: u2
|
||||
valid: 20
|
||||
- id: signature
|
||||
type: u4
|
||||
valid: 0x41535645
|
||||
valid: 0x41535645 #EVSA
|
||||
- id: attributes
|
||||
type: u4
|
||||
- id: store_size
|
||||
- id: len_evsa_store
|
||||
type: u4
|
||||
- id: reserved
|
||||
type: u4
|
||||
- id: body
|
||||
type: evsa_body
|
||||
size: store_size - 20
|
||||
size: len_evsa_store - len_evsa_store_header
|
||||
|
||||
types:
|
||||
evsa_guid:
|
||||
|
@ -38,13 +38,38 @@ types:
|
|||
type: u2
|
||||
- id: guid
|
||||
size: 16
|
||||
valid:
|
||||
expr: _parent.len_evsa_entry == 22
|
||||
|
||||
evsa_name:
|
||||
seq:
|
||||
- id: var_id
|
||||
type: u2
|
||||
- id: name
|
||||
size: _parent.size - 6
|
||||
size: _parent.len_evsa_entry - 6
|
||||
|
||||
evsa_variable_attributes:
|
||||
seq:
|
||||
- id: non_volatile
|
||||
type: b1le
|
||||
- id: boot_service
|
||||
type: b1le
|
||||
- id: runtime
|
||||
type: b1le
|
||||
- id: hw_error_record
|
||||
type: b1le
|
||||
- id: auth_write
|
||||
type: b1le
|
||||
- id: time_based_auth
|
||||
type: b1le
|
||||
- id: append_write
|
||||
type: b1le
|
||||
- id: reserved
|
||||
type: b21le
|
||||
- id: extended_header
|
||||
type: b1le
|
||||
- id: reserved1
|
||||
type: b3le
|
||||
|
||||
evsa_data:
|
||||
seq:
|
||||
|
@ -53,16 +78,16 @@ types:
|
|||
- id: var_id
|
||||
type: u2
|
||||
- id: attributes
|
||||
type: evsa_variable_attributes
|
||||
- id: len_data_ext
|
||||
type: u4
|
||||
- id: data_size
|
||||
type: u4
|
||||
if: (attributes & 0x10000000) != 0x10000000
|
||||
if: attributes.extended_header
|
||||
- id: data
|
||||
size: _parent.size - 12
|
||||
if: (attributes & 0x10000000) == 0x10000000
|
||||
size: _parent.len_evsa_entry - 12
|
||||
if: not attributes.extended_header
|
||||
- id: data_ext
|
||||
size: data_size
|
||||
if: (attributes & 0x10000000) != 0x10000000
|
||||
size: len_data_ext
|
||||
if: attributes.extended_header
|
||||
|
||||
evsa_unknown:
|
||||
seq:
|
||||
|
@ -71,15 +96,29 @@ types:
|
|||
|
||||
evsa_entry:
|
||||
seq:
|
||||
- id: type
|
||||
- id: entry_type
|
||||
type: u1
|
||||
- id: checksum
|
||||
type: u1
|
||||
- id: size
|
||||
if: entry_type == 0xE1
|
||||
or entry_type == 0xE2
|
||||
or entry_type == 0xE3
|
||||
or entry_type == 0xED
|
||||
or entry_type == 0xEE
|
||||
or entry_type == 0xEF
|
||||
or entry_type == 0x83
|
||||
- id: len_evsa_entry
|
||||
type: u2
|
||||
if: entry_type == 0xE1
|
||||
or entry_type == 0xE2
|
||||
or entry_type == 0xE3
|
||||
or entry_type == 0xED
|
||||
or entry_type == 0xEE
|
||||
or entry_type == 0xEF
|
||||
or entry_type == 0x83
|
||||
- id: body
|
||||
type:
|
||||
switch-on: type
|
||||
switch-on: entry_type
|
||||
cases:
|
||||
0xED: evsa_guid
|
||||
0xE1: evsa_guid
|
||||
|
@ -94,6 +133,15 @@ types:
|
|||
seq:
|
||||
- id: entries
|
||||
type: evsa_entry
|
||||
repeat: until
|
||||
repeat-until: (_.entry_type != 0xED
|
||||
and _.entry_type != 0xEE
|
||||
and _.entry_type != 0xEF
|
||||
and _.entry_type != 0xE1
|
||||
and _.entry_type != 0xE2
|
||||
and _.entry_type != 0xE3
|
||||
and _.entry_type != 0x83)
|
||||
or _io.eof
|
||||
- id: free_space
|
||||
type: u1
|
||||
repeat: eos
|
||||
|
||||
|
|
@ -38,7 +38,9 @@ uefitoolcommon = static_library('uefitoolcommon',
|
|||
'generated/edk2_vss2.cpp',
|
||||
'generated/edk2_ftw.cpp',
|
||||
'generated/insyde_fdc.cpp',
|
||||
'generated/insyde_fdm.cpp',
|
||||
'generated/phoenix_flm.cpp',
|
||||
'generated/phoenix_evsa.cpp',
|
||||
'generated/intel_acbp_v1.cpp',
|
||||
'generated/intel_acbp_v2.cpp',
|
||||
'generated/intel_keym_v1.cpp',
|
||||
|
|
|
@ -152,7 +152,7 @@ UString efiTimeToUString(const EFI_TIME & time)
|
|||
time.Nanosecond);
|
||||
}
|
||||
|
||||
UString flashMapGuidToUString(const EFI_GUID & guid)
|
||||
UString phoenixFlashMapGuidToUString(const EFI_GUID & guid)
|
||||
{
|
||||
const UByteArray baGuid((const char*)&guid, sizeof(EFI_GUID));
|
||||
if (baGuid == NVRAM_PHOENIX_FLASH_MAP_VOLUME_HEADER) return UString("Volume header");
|
||||
|
|
|
@ -274,7 +274,7 @@ typedef struct EVSA_STORE_ENTRY_ {
|
|||
UINT32 Signature; // EVSA signature
|
||||
UINT32 Attributes;
|
||||
UINT32 StoreSize;
|
||||
UINT32 : 32;
|
||||
UINT32 Reserved;
|
||||
} EVSA_STORE_ENTRY;
|
||||
|
||||
typedef struct EVSA_GUID_ENTRY_ {
|
||||
|
@ -347,7 +347,7 @@ typedef struct PHOENIX_FLASH_MAP_ENTRY_ {
|
|||
#define NVRAM_PHOENIX_FLASH_MAP_ENTRY_DATA_TYPE_VOLUME 0x0000
|
||||
#define NVRAM_PHOENIX_FLASH_MAP_ENTRY_DATA_TYPE_DATA_BLOCK 0x0001
|
||||
|
||||
extern UString flashMapGuidToUString(const EFI_GUID & guid);
|
||||
extern UString phoenixFlashMapGuidToUString(const EFI_GUID & guid);
|
||||
|
||||
extern const UByteArray NVRAM_PHOENIX_FLASH_MAP_VOLUME_HEADER; // B091E7D2-05A0-4198-94F0-74B7B8C55459
|
||||
extern const UByteArray NVRAM_PHOENIX_FLASH_MAP_MICROCODES_GUID; // FD3F690E-B4B0-4D68-89DB-19A1A3318F90
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "generated/edk2_ftw.h"
|
||||
#include "generated/insyde_fdc.h"
|
||||
#include "generated/phoenix_flm.h"
|
||||
#include "generated/phoenix_evsa.h"
|
||||
|
||||
USTATUS NvramParser::parseNvarStore(const UModelIndex & index)
|
||||
{
|
||||
|
@ -387,16 +388,15 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
UModelIndex headerIndex = model->addItem(localOffset + storeOffset, Types::VssStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
|
||||
|
||||
// Add variables
|
||||
UINT32 vssVariableOffset = storeOffset + parsed.len_vss_store_header();
|
||||
UINT32 vssVariableOffset = parsed.len_vss_store_header();
|
||||
for (const auto & variable : *parsed.body()->variables()) {
|
||||
UINT8 subtype;
|
||||
|
||||
// This is the terminating entry, needs special processing
|
||||
if (variable->_is_null_signature_last()) {
|
||||
// Add free space or padding after all variables, if needed
|
||||
UINT32 freeSpaceOffset = vssVariableOffset - storeOffset;
|
||||
if (freeSpaceOffset < storeSize) {
|
||||
UByteArray freeSpace = volumeBody.mid(freeSpaceOffset, storeSize - freeSpaceOffset);
|
||||
if (vssVariableOffset < storeSize) {
|
||||
UByteArray freeSpace = vss.mid(vssVariableOffset, storeSize - vssVariableOffset);
|
||||
// Add info
|
||||
info = usprintf("Full size: %Xh (%u)", (UINT32)freeSpace.size(), (UINT32)freeSpace.size());
|
||||
|
||||
|
@ -425,7 +425,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
variableSize = (UINT32)(header.size() + body.size());
|
||||
const EFI_GUID variableGuid = readUnaligned((const EFI_GUID*)(variable->vendor_guid().c_str()));
|
||||
name = guidToUString(variableGuid);
|
||||
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
info = UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
}
|
||||
else if (variable->is_auth()) { // Authenticated
|
||||
subtype = Subtypes::AuthVssEntry;
|
||||
|
@ -435,7 +435,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
const EFI_GUID variableGuid = readUnaligned((const EFI_GUID*)(variable->vendor_guid().c_str()));
|
||||
name = guidToUString(variableGuid);
|
||||
text = uFromUcs2(variable->name_auth().c_str());
|
||||
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
info = UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
}
|
||||
else if (!variable->_is_null_apple_data_crc32()) { // Apple CRC32
|
||||
subtype = Subtypes::AppleVssEntry;
|
||||
|
@ -445,7 +445,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
const EFI_GUID variableGuid = readUnaligned((const EFI_GUID*)(variable->vendor_guid().c_str()));
|
||||
name = guidToUString(variableGuid);
|
||||
text = uFromUcs2(variable->name().c_str());
|
||||
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
info = UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
}
|
||||
else { // Standard
|
||||
subtype = Subtypes::StandardVssEntry;
|
||||
|
@ -455,7 +455,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
const EFI_GUID variableGuid = readUnaligned((const EFI_GUID*)(variable->vendor_guid().c_str()));
|
||||
name = guidToUString(variableGuid);
|
||||
text = uFromUcs2(variable->name().c_str());
|
||||
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
info = UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
}
|
||||
|
||||
// Override variable type to Invalid if needed
|
||||
|
@ -472,6 +472,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
+ (variable->attributes()->auth_write() << 4)
|
||||
+ (variable->attributes()->time_based_auth() << 5)
|
||||
+ (variable->attributes()->append_write() << 6)
|
||||
+ (UINT32)(variable->attributes()->reserved() << 7)
|
||||
+ (variable->attributes()->apple_data_checksum() << 31);
|
||||
|
||||
// Add generic info
|
||||
|
@ -518,6 +519,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
}
|
||||
|
||||
UByteArray vss2 = volumeBody.mid(storeOffset);
|
||||
|
||||
// Check if we are here to parse a special case of FDC store with size override
|
||||
UINT32 originalStoreSize = 0;
|
||||
VSS2_VARIABLE_STORE_HEADER* vss2Header = (VSS2_VARIABLE_STORE_HEADER*)vss2.data();
|
||||
|
@ -527,10 +529,12 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
originalStoreSize = vss2Header->Size;
|
||||
vss2Header->Size = fdcStoreSizeOverride;
|
||||
}
|
||||
|
||||
umemstream is(vss2.constData(), vss2.size());
|
||||
kaitai::kstream ks(&is);
|
||||
edk2_vss2_t parsed(&ks);
|
||||
UINT32 storeSize = parsed.vss2_size();
|
||||
|
||||
// Restore original store size, if needed
|
||||
if (fdcHeaderSizeOverrideRequired) {
|
||||
vss2Header->Size = originalStoreSize;
|
||||
|
@ -573,16 +577,15 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
UModelIndex headerIndex = model->addItem(localOffset + storeOffset, Types::Vss2Store, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
|
||||
|
||||
// Add variables
|
||||
UINT32 vss2VariableOffset = storeOffset + parsed.len_vss2_store_header();
|
||||
UINT32 vss2VariableOffset = parsed.len_vss2_store_header();
|
||||
for (const auto & variable : *parsed.body()->variables()) {
|
||||
UINT8 subtype;
|
||||
|
||||
// This is the terminating entry, needs special processing
|
||||
if (variable->_is_null_signature_last()) {
|
||||
// Add free space or padding after all variables, if needed
|
||||
UINT32 freeSpaceOffset = vss2VariableOffset - storeOffset;
|
||||
if (freeSpaceOffset < storeSize) {
|
||||
UByteArray freeSpace = volumeBody.mid(freeSpaceOffset, storeSize - freeSpaceOffset);
|
||||
if (vss2VariableOffset < storeSize) {
|
||||
UByteArray freeSpace = vss2.mid(vss2VariableOffset, storeSize - vss2VariableOffset);
|
||||
// Add info
|
||||
info = usprintf("Full size: %Xh (%u)", (UINT32)freeSpace.size(), (UINT32)freeSpace.size());
|
||||
|
||||
|
@ -601,25 +604,28 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
|
||||
// This is a normal entry
|
||||
UINT32 variableSize;
|
||||
UINT32 alignmentSize;
|
||||
if (variable->is_auth()) { // Authenticated
|
||||
subtype = Subtypes::AuthVssEntry;
|
||||
header = vss2.mid(vss2VariableOffset, variable->len_auth_header() + variable->len_name_auth());
|
||||
body = vss2.mid(vss2VariableOffset + header.size(), variable->len_data_auth());
|
||||
variableSize = (UINT32)(header.size() + body.size());
|
||||
alignmentSize = variable->len_alignment_padding_auth();
|
||||
const EFI_GUID variableGuid = readUnaligned((const EFI_GUID*)(variable->vendor_guid().c_str()));
|
||||
name = guidToUString(variableGuid);
|
||||
text = uFromUcs2(variable->name_auth().c_str());
|
||||
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
info = UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
}
|
||||
else { // Standard
|
||||
subtype = Subtypes::StandardVssEntry;
|
||||
header = vss2.mid(vss2VariableOffset, variable->len_standard_header() + variable->len_name());
|
||||
body = vss2.mid(vss2VariableOffset + header.size(), variable->len_data());
|
||||
variableSize = (UINT32)(header.size() + body.size());
|
||||
alignmentSize = variable->len_alignment_padding();
|
||||
const EFI_GUID variableGuid = readUnaligned((const EFI_GUID*)(variable->vendor_guid().c_str()));
|
||||
name = guidToUString(variableGuid);
|
||||
text = uFromUcs2(variable->name().c_str());
|
||||
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
info = UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
|
||||
}
|
||||
|
||||
// Override variable type to Invalid if needed
|
||||
|
@ -635,7 +641,8 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
+ (variable->attributes()->hw_error_record() << 3)
|
||||
+ (variable->attributes()->auth_write() << 4)
|
||||
+ (variable->attributes()->time_based_auth() << 5)
|
||||
+ (variable->attributes()->append_write() << 6);
|
||||
+ (variable->attributes()->append_write() << 6)
|
||||
+ (UINT32)(variable->attributes()->reserved() << 7);
|
||||
|
||||
// Add generic info
|
||||
info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nState: %02Xh\nReserved: %02Xh\nAttributes: %08Xh (",
|
||||
|
@ -656,7 +663,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
// Add tree item
|
||||
model->addItem(vss2VariableOffset, Types::VssEntry, subtype, name, text, info, header, body, UByteArray(), Fixed, headerIndex);
|
||||
|
||||
vss2VariableOffset += variableSize;
|
||||
vss2VariableOffset += (variableSize + alignmentSize);
|
||||
}
|
||||
|
||||
storeOffset += storeSize - 1;
|
||||
|
@ -836,7 +843,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
UModelIndex headerIndex = model->addItem(localOffset + storeOffset, Types::SysFStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
|
||||
|
||||
// Add variables
|
||||
UINT32 sysfVariableOffset = storeOffset + parsed.len_sysf_store_header();
|
||||
UINT32 sysfVariableOffset = parsed.len_sysf_store_header();
|
||||
for (const auto & variable : *parsed.body()->variables()) {
|
||||
UINT8 subtype;
|
||||
|
||||
|
@ -850,11 +857,11 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
}
|
||||
|
||||
if (variable->len_name() == 3 && variable->name() == "EOF") {
|
||||
header = volumeBody.mid(sysfVariableOffset, 4);
|
||||
header = sysf.mid(sysfVariableOffset, 4);
|
||||
}
|
||||
else {
|
||||
header = volumeBody.mid(sysfVariableOffset, sizeof(UINT8) + (UINT32)variable->len_name() + sizeof(UINT16));
|
||||
body = volumeBody.mid(sysfVariableOffset + header.size(), (UINT32)variable->len_data());
|
||||
header = sysf.mid(sysfVariableOffset, sizeof(UINT8) + (UINT32)variable->len_name() + sizeof(UINT16));
|
||||
body = sysf.mid(sysfVariableOffset + header.size(), (UINT32)variable->len_data());
|
||||
}
|
||||
// Add generic info
|
||||
UINT32 variableSize = (UINT32)header.size() + (UINT32)body.size();
|
||||
|
@ -870,9 +877,8 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
}
|
||||
|
||||
// Add free space or padding after all variables, if needed
|
||||
UINT32 freeSpaceOffset = sysfVariableOffset - storeOffset;
|
||||
if (freeSpaceOffset < storeSize) {
|
||||
UByteArray freeSpace = volumeBody.mid(freeSpaceOffset, storeSize - freeSpaceOffset);
|
||||
if (sysfVariableOffset < storeSize) {
|
||||
UByteArray freeSpace = sysf.mid(sysfVariableOffset, storeSize - sysfVariableOffset);
|
||||
// Add info
|
||||
info = usprintf("Full size: %Xh (%u)", (UINT32)freeSpace.size(), (UINT32)freeSpace.size());
|
||||
|
||||
|
@ -894,7 +900,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
// Parsing failed, try something else
|
||||
}
|
||||
|
||||
// Phoenix FlashMap
|
||||
// Phoenix SCT FlashMap
|
||||
try {
|
||||
if (volumeBodySize - storeOffset < NVRAM_PHOENIX_FLASH_MAP_TOTAL_SIZE) {
|
||||
// No need to parse further, the rest of the volume is too small
|
||||
|
@ -920,7 +926,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
body = flm.mid(header.size(), storeSize - header.size());
|
||||
|
||||
// Add info
|
||||
name = UString("FlashMap");
|
||||
name = UString("Phoenix SCT FlashMap");
|
||||
info = usprintf("Signature: _FLASH_MAP\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nEntries: %u\nReserved: %08Xh",
|
||||
storeSize, storeSize,
|
||||
(UINT32)header.size(), (UINT32)header.size(),
|
||||
|
@ -929,10 +935,10 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
parsed.reserved());
|
||||
|
||||
// Add header tree item
|
||||
UModelIndex headerIndex = model->addItem(localOffset + storeOffset, Types::FlashMapStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
|
||||
UModelIndex headerIndex = model->addItem(localOffset + storeOffset, Types::PhoenixFlashMapStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
|
||||
|
||||
// Add entries
|
||||
UINT32 entryOffset = storeOffset + parsed.len_flm_store_header();
|
||||
UINT32 entryOffset = parsed.len_flm_store_header();
|
||||
for (const auto & entry : *parsed.entries()) {
|
||||
UINT8 subtype;
|
||||
|
||||
|
@ -948,8 +954,8 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
|
||||
const EFI_GUID guid = readUnaligned((const EFI_GUID*)entry->guid().c_str());
|
||||
name = guidToUString(guid);
|
||||
text = flashMapGuidToUString(guid);
|
||||
header = volumeBody.mid(entryOffset, parsed.len_flm_entry());
|
||||
text = phoenixFlashMapGuidToUString(guid);
|
||||
header = flm.mid(entryOffset, parsed.len_flm_entry());
|
||||
|
||||
// Add info
|
||||
UINT32 entrySize = (UINT32)header.size();
|
||||
|
@ -963,7 +969,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
entry->physical_address());
|
||||
|
||||
// Add tree item
|
||||
model->addItem(entryOffset, Types::FlashMapEntry, subtype, name, text, info, header, UByteArray(), UByteArray(), Fixed, headerIndex);
|
||||
model->addItem(entryOffset, Types::PhoenixFlashMapEntry, subtype, name, text, info, header, UByteArray(), UByteArray(), Fixed, headerIndex);
|
||||
|
||||
entryOffset += entrySize;
|
||||
}
|
||||
|
@ -996,7 +1002,220 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index,const UINT32
|
|||
}
|
||||
|
||||
// Phoenix EVSA
|
||||
try {
|
||||
if (volumeBodySize - storeOffset < sizeof(EVSA_STORE_ENTRY)) {
|
||||
// No need to parse further, the rest of the volume is too small
|
||||
throw 0;
|
||||
}
|
||||
|
||||
UByteArray evsa = volumeBody.mid(storeOffset);
|
||||
umemstream is(evsa.constData(), evsa.size());
|
||||
kaitai::kstream ks(&is);
|
||||
phoenix_evsa_t parsed(&ks);
|
||||
UINT32 storeSize = parsed.len_evsa_store();
|
||||
|
||||
// Phoenix EVSA store at current offset parsed correctly
|
||||
// Check if we need to add a padding before it
|
||||
if (!outerPadding.isEmpty()) {
|
||||
info = usprintf("Full size: %Xh (%u)", (UINT32)outerPadding.size(), (UINT32)outerPadding.size());
|
||||
model->addItem(previousStoreEndOffset, Types::Padding, getPaddingType(outerPadding), UString("Padding"), UString(), info, UByteArray(), outerPadding, UByteArray(), Fixed, index);
|
||||
outerPadding.clear();
|
||||
}
|
||||
|
||||
// Construct header and body
|
||||
header = evsa.left(parsed.len_evsa_store_header());
|
||||
body = evsa.mid(header.size(), storeSize - header.size());
|
||||
|
||||
const EVSA_STORE_ENTRY* evsaStoreHeader = (const EVSA_STORE_ENTRY*)header.constData();
|
||||
UINT8 calculated = calculateChecksum8(((const UINT8*)evsaStoreHeader) + 2, evsaStoreHeader->Header.Size - 2);
|
||||
|
||||
// Add info
|
||||
name = UString("EVSA Store");
|
||||
info = usprintf("Signature: EVSA\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nAttributes: %08Xh\nReserved: %08Xh\nChecksum: %02Xh",
|
||||
storeSize, storeSize,
|
||||
(UINT32)header.size(), (UINT32)header.size(),
|
||||
(UINT32)body.size(), (UINT32)body.size(),
|
||||
parsed.attributes(),
|
||||
parsed.reserved(),
|
||||
parsed.checksum())
|
||||
+ (parsed.checksum() != calculated ? usprintf(", invalid, should be %02Xh", calculated) : UString(", valid"));
|
||||
|
||||
// Add header tree item
|
||||
UModelIndex headerIndex = model->addItem(localOffset + storeOffset, Types::EvsaStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
|
||||
|
||||
// Add entries
|
||||
std::map<UINT16, EFI_GUID> guidMap;
|
||||
std::map<UINT16, UString> nameMap;
|
||||
UINT32 entryOffset = parsed.len_evsa_store_header();
|
||||
for (const auto & entry : *parsed.body()->entries()) {
|
||||
UINT8 subtype;
|
||||
UINT32 entrySize;
|
||||
|
||||
// This is the terminating entry, needs special processing
|
||||
if (entry->_is_null_checksum()) {
|
||||
// Add free space or padding after all variables, if needed
|
||||
if (entryOffset < storeSize) {
|
||||
UByteArray freeSpace = evsa.mid(entryOffset, storeSize - entryOffset);
|
||||
// Add info
|
||||
info = usprintf("Full size: %Xh (%u)", (UINT32)freeSpace.size(), (UINT32)freeSpace.size());
|
||||
|
||||
// Check that remaining unparsed bytes are actually empty
|
||||
if (freeSpace.count(emptyByte) == freeSpace.size()) { // Free space
|
||||
// Add tree item
|
||||
model->addItem(entryOffset, Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), freeSpace, UByteArray(), Fixed, headerIndex);
|
||||
}
|
||||
else {
|
||||
// Add tree item
|
||||
model->addItem(entryOffset, Types::Padding, getPaddingType(freeSpace), UString("Padding"), UString(), info, UByteArray(), freeSpace, UByteArray(), Fixed, headerIndex);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const EVSA_ENTRY_HEADER* entryHeader = (const EVSA_ENTRY_HEADER*)(evsa.constData() + entryOffset);
|
||||
calculated = calculateChecksum8(((const UINT8*)entryHeader) + 2, entryHeader->Size - 2);
|
||||
|
||||
// GUID entry
|
||||
if (entry->entry_type() == NVRAM_EVSA_ENTRY_TYPE_GUID1 || entry->entry_type() == NVRAM_EVSA_ENTRY_TYPE_GUID2) {
|
||||
const phoenix_evsa_t::evsa_guid_t* guidEntry = (const phoenix_evsa_t::evsa_guid_t*)(entry->body());
|
||||
header = evsa.mid(entryOffset, sizeof(EVSA_GUID_ENTRY));
|
||||
body = evsa.mid(entryOffset + sizeof(EVSA_GUID_ENTRY), entry->len_evsa_entry() - header.size());
|
||||
entrySize = (UINT32)(header.size() + body.size());
|
||||
EFI_GUID guid = *(const EFI_GUID*)(guidEntry->guid().c_str());
|
||||
name = guidToUString(guid);
|
||||
info = UString("GUID: ") + guidToUString(guid, false)
|
||||
+ usprintf("\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nType: %02Xh\nChecksum: %02Xh",
|
||||
entrySize, entrySize,
|
||||
(UINT32)header.size(), (UINT32)header.size(),
|
||||
(UINT32)body.size(), (UINT32)body.size(),
|
||||
entry->entry_type(),
|
||||
entry->checksum())
|
||||
+ (entry->checksum() != calculated ? usprintf(", invalid, should be %02Xh", calculated) : UString(", valid"))
|
||||
+ usprintf("\nGuidId: %04Xh", guidEntry->guid_id());
|
||||
subtype = Subtypes::GuidEvsaEntry;
|
||||
guidMap.insert(std::pair<UINT16, EFI_GUID>(guidEntry->guid_id(), guid));
|
||||
}
|
||||
// Name entry
|
||||
else if (entry->entry_type() == NVRAM_EVSA_ENTRY_TYPE_NAME1 || entry->entry_type() == NVRAM_EVSA_ENTRY_TYPE_NAME2) {
|
||||
const phoenix_evsa_t::evsa_name_t* nameEntry = (const phoenix_evsa_t::evsa_name_t*)(entry->body());
|
||||
header = evsa.mid(entryOffset, sizeof(EVSA_NAME_ENTRY));
|
||||
body = evsa.mid(entryOffset + sizeof(EVSA_NAME_ENTRY), entry->len_evsa_entry() - header.size());
|
||||
entrySize = (UINT32)(header.size() + body.size());
|
||||
name = uFromUcs2(body.constData());
|
||||
info = UString("Name: ") + name
|
||||
+ usprintf("\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nType: %02Xh\nChecksum: %02Xh",
|
||||
entrySize, entrySize,
|
||||
(UINT32)header.size(), (UINT32)header.size(),
|
||||
(UINT32)body.size(), (UINT32)body.size(),
|
||||
entry->entry_type(),
|
||||
entry->checksum())
|
||||
+ (entry->checksum() != calculated ? usprintf(", invalid, should be %02Xh", calculated) : UString(", valid"))
|
||||
+ usprintf("\nVarId: %04Xh", nameEntry->var_id());
|
||||
subtype = Subtypes::NameEvsaEntry;
|
||||
nameMap.insert(std::pair<UINT16, UString>(nameEntry->var_id(), name));
|
||||
}
|
||||
// Data entry
|
||||
else if (entry->entry_type() == NVRAM_EVSA_ENTRY_TYPE_DATA1
|
||||
|| entry->entry_type() == NVRAM_EVSA_ENTRY_TYPE_DATA2
|
||||
|| entry->entry_type() == NVRAM_EVSA_ENTRY_TYPE_DATA_INVALID) {
|
||||
phoenix_evsa_t::evsa_data_t* dataEntry = (phoenix_evsa_t::evsa_data_t*)(entry->body());
|
||||
if (dataEntry->_is_null_len_data_ext()) {
|
||||
header = evsa.mid(entryOffset, sizeof(EVSA_DATA_ENTRY));
|
||||
body = evsa.mid(entryOffset + sizeof(EVSA_DATA_ENTRY), entry->len_evsa_entry() - header.size());
|
||||
}
|
||||
else {
|
||||
header = evsa.mid(entryOffset, sizeof(EVSA_DATA_ENTRY_EXTENDED));
|
||||
body = evsa.mid(entryOffset + sizeof(EVSA_DATA_ENTRY_EXTENDED), dataEntry->len_data_ext());
|
||||
}
|
||||
entrySize = (UINT32)(header.size() + body.size());
|
||||
name = UString("Data");
|
||||
subtype = Subtypes::DataEvsaEntry;
|
||||
|
||||
const UINT32 attributes = dataEntry->attributes()->non_volatile()
|
||||
+ (dataEntry->attributes()->boot_service() << 1)
|
||||
+ (dataEntry->attributes()->runtime() << 2)
|
||||
+ (dataEntry->attributes()->hw_error_record() << 3)
|
||||
+ (dataEntry->attributes()->auth_write() << 4)
|
||||
+ (dataEntry->attributes()->time_based_auth() << 5)
|
||||
+ (dataEntry->attributes()->append_write() << 6)
|
||||
+ (UINT32)(dataEntry->attributes()->reserved() << 7)
|
||||
+ (dataEntry->attributes()->extended_header() << 28)
|
||||
+ (UINT32)(dataEntry->attributes()->reserved1() << 29);
|
||||
|
||||
info = usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nType: %02Xh\nChecksum: %02Xh",
|
||||
entrySize, entrySize,
|
||||
(UINT32)header.size(), (UINT32)header.size(),
|
||||
(UINT32)body.size(), (UINT32)body.size(),
|
||||
entry->entry_type(),
|
||||
entry->checksum())
|
||||
+ (entry->checksum() != calculated ? usprintf(", invalid, should be %02Xh", calculated) : UString(", valid"))
|
||||
+ usprintf("\nVarId: %04Xh\nGuidId: %04Xh\nAttributes: %08Xh (",
|
||||
dataEntry->var_id(),
|
||||
dataEntry->guid_id(),
|
||||
attributes)
|
||||
+ evsaAttributesToUString(attributes) + UString(")");
|
||||
}
|
||||
|
||||
// Add tree item
|
||||
model->addItem(entryOffset, Types::EvsaEntry, subtype, name, text, info, header, body, UByteArray(), Fixed, headerIndex);
|
||||
|
||||
entryOffset += entrySize;
|
||||
}
|
||||
|
||||
// Reparse all data variables to detect invalid ones and assign name and test to valid ones
|
||||
for (int i = 0; i < model->rowCount(headerIndex); i++) {
|
||||
UModelIndex current = headerIndex.model()->index(i, 0, headerIndex);
|
||||
|
||||
if (model->subtype(current) == Subtypes::DataEvsaEntry) {
|
||||
UByteArray header = model->header(current);
|
||||
const EVSA_DATA_ENTRY* dataHeader = (const EVSA_DATA_ENTRY*)header.constData();
|
||||
UString guid;
|
||||
if (guidMap.count(dataHeader->GuidId))
|
||||
guid = guidToUString(guidMap[dataHeader->GuidId], false);
|
||||
UString name;
|
||||
if (nameMap.count(dataHeader->VarId))
|
||||
name = nameMap[dataHeader->VarId];
|
||||
|
||||
// Check for variable validity
|
||||
if (guid.isEmpty() && name.isEmpty()) { // Both name and guid aren't found
|
||||
model->setSubtype(current, Subtypes::InvalidEvsaEntry);
|
||||
model->setName(current, UString("Invalid"));
|
||||
model->setText(current, UString());
|
||||
msg(usprintf("%s: data variable with invalid GuidId and invalid VarId", __FUNCTION__), current);
|
||||
}
|
||||
else if (guid.isEmpty()) { // Guid not found
|
||||
model->setSubtype(current, Subtypes::InvalidEvsaEntry);
|
||||
model->setName(current, UString("Invalid"));
|
||||
model->setText(current, UString());
|
||||
msg(usprintf("%s: data variable with invalid GuidId", __FUNCTION__), current);
|
||||
}
|
||||
else if (name.isEmpty()) { // Name not found
|
||||
model->setSubtype(current, Subtypes::InvalidEvsaEntry);
|
||||
model->setName(current, UString("Invalid"));
|
||||
model->setText(current, UString());
|
||||
msg(usprintf("%s: data variable with invalid VarId", __FUNCTION__), current);
|
||||
}
|
||||
else { // Variable is OK, rename it
|
||||
if (dataHeader->Header.Type == NVRAM_EVSA_ENTRY_TYPE_DATA_INVALID) {
|
||||
model->setSubtype(current, Subtypes::InvalidEvsaEntry);
|
||||
model->setName(current, UString("Invalid"));
|
||||
model->setText(current, UString());
|
||||
}
|
||||
else {
|
||||
model->setName(current, guid);
|
||||
model->setText(current, name);
|
||||
model->addInfo(current, UString("GUID: ") + guid + UString("\nName: ") + name + "\n", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storeOffset += storeSize - 1;
|
||||
previousStoreEndOffset = storeOffset + 1;
|
||||
continue;
|
||||
} catch (...) {
|
||||
// Parsing failed, try something else
|
||||
}
|
||||
// Phoenix CMDB
|
||||
|
||||
// Phoenix SLIC Pubkey/Marker
|
||||
|
|
|
@ -58,13 +58,15 @@ UString itemTypeToUString(const UINT8 type)
|
|||
case Types::SysFStore: return UString("SysF store");
|
||||
case Types::EvsaStore: return UString("EVSA store");
|
||||
case Types::CmdbStore: return UString("CMDB store");
|
||||
case Types::FlashMapStore: return UString("FlashMap store");
|
||||
case Types::PhoenixFlashMapStore: return UString("FlashMap store");
|
||||
case Types::InsydeFlashDeviceMapStore: return UString("FlashDeviceMap store");
|
||||
case Types::NvarGuidStore: return UString("NVAR GUID store");
|
||||
case Types::NvarEntry: return UString("NVAR entry");
|
||||
case Types::VssEntry: return UString("VSS entry");
|
||||
case Types::SysFEntry: return UString("SysF entry");
|
||||
case Types::EvsaEntry: return UString("EVSA entry");
|
||||
case Types::FlashMapEntry: return UString("FlashMap entry");
|
||||
case Types::PhoenixFlashMapEntry: return UString("FlashMap entry");
|
||||
case Types::InsydeFlashDeviceMapEntry: return UString("FlashDeviceMap entry");
|
||||
case Types::Microcode: return UString("Microcode");
|
||||
case Types::SlicData: return UString("SLIC data");
|
||||
case Types::FptStore: return UString("FPT store");
|
||||
|
@ -139,7 +141,7 @@ UString itemSubtypeToUString(const UINT8 type, const UINT8 subtype)
|
|||
else if (subtype == Subtypes::NameEvsaEntry) return UString("Name");
|
||||
else if (subtype == Subtypes::DataEvsaEntry) return UString("Data");
|
||||
break;
|
||||
case Types::FlashMapEntry:
|
||||
case Types::PhoenixFlashMapEntry:
|
||||
if (subtype == Subtypes::VolumeFlashMapEntry) return UString("Volume");
|
||||
else if (subtype == Subtypes::DataFlashMapEntry) return UString("Data");
|
||||
else if (subtype == Subtypes::UnknownFlashMapEntry) return UString("Unknown");
|
||||
|
@ -250,3 +252,29 @@ UString hashTypeToUString(const UINT16 algorithm_id)
|
|||
|
||||
return usprintf("Unknown %04Xh", algorithm_id);
|
||||
}
|
||||
|
||||
UString insydeFlashDeviceMapEntryTypeGuidToUString(const EFI_GUID & guid)
|
||||
{
|
||||
const UByteArray baGuid((const char*)&guid, sizeof(EFI_GUID));
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_BOOT_FV_GUID) return UString("Boot Firmare Volume");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_BVDT_GUID) return UString("BIOS Version Data Table");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_EC_GUID) return UString("EC Firmware");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_FTW_BACKUP_GUID) return UString("FTW Backup");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_FTW_STATE_GUID) return UString("FTW State");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_FV_GUID) return UString("Firmare Volume");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_FLASH_DEVICE_MAP_GUID) return UString("Flash Device Map");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_LOGO_GUID) return UString("Logo");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_MICROCODE_GUID) return UString("Microcode");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_MSDM_TABLE_GUID) return UString("MSDM Table");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_MULTI_CONFIG_GUID) return UString("MultiConfig");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_VAR_DEFAULT_GUID) return UString("Variable Defaults");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_SMBIOS_UPDATE_GUID) return UString("SMBIOS Update");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_VAR_GUID) return UString("Variables");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_UNKNOWN_GUID) return UString("Unknown");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_UNUSED_GUID) return UString("Unused");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_USB_OPTION_ROM_GUID) return UString("USB Option ROM");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_DXE_FV_GUID) return UString("DXE Firmare Volume");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_PEI_FV_GUID) return UString("PEI Firmare Volume");
|
||||
if (baGuid == INSYDE_FLASH_MAP_REGION_UNSIGNED_FV_GUID) return UString("Unsigned Firmare Volume");
|
||||
return guidToUString(guid);
|
||||
}
|
||||
|
|
|
@ -49,14 +49,16 @@ namespace Types {
|
|||
FdcStore,
|
||||
SysFStore,
|
||||
EvsaStore,
|
||||
FlashMapStore,
|
||||
PhoenixFlashMapStore,
|
||||
InsydeFlashDeviceMapStore,
|
||||
CmdbStore,
|
||||
NvarGuidStore,
|
||||
NvarEntry,
|
||||
VssEntry,
|
||||
SysFEntry,
|
||||
EvsaEntry,
|
||||
FlashMapEntry,
|
||||
PhoenixFlashMapEntry,
|
||||
InsydeFlashDeviceMapEntry,
|
||||
Microcode,
|
||||
SlicData,
|
||||
IfwiHeader,
|
||||
|
@ -204,5 +206,6 @@ extern UString compressionTypeToUString(const UINT8 algorithm);
|
|||
extern UString regionTypeToUString(const UINT8 type);
|
||||
extern UString fitEntryTypeToUString(const UINT8 type);
|
||||
extern UString hashTypeToUString(const UINT16 digest_agorithm_id);
|
||||
extern UString insydeFlashDeviceMapEntryTypeGuidToUString(const EFI_GUID & guid);
|
||||
|
||||
#endif // TYPES_H
|
||||
|
|
|
@ -83,7 +83,8 @@ UString uniqueItemName(const UModelIndex & index)
|
|||
case Types::VssEntry:
|
||||
case Types::SysFEntry:
|
||||
case Types::EvsaEntry:
|
||||
case Types::FlashMapEntry:
|
||||
case Types::PhoenixFlashMapEntry:
|
||||
case Types::InsydeFlashDeviceMapEntry:
|
||||
case Types::File:
|
||||
name = itemText.isEmpty() ? itemName : itemName + '_' + itemText;
|
||||
break;
|
||||
|
|
|
@ -37,7 +37,9 @@ SET(PROJECT_SOURCES
|
|||
../common/generated/edk2_vss2.cpp
|
||||
../common/generated/edk2_ftw.cpp
|
||||
../common/generated/insyde_fdc.cpp
|
||||
../common/generated/insyde_fdm.cpp
|
||||
../common/generated/phoenix_flm.cpp
|
||||
../common/generated/phoenix_evsa.cpp
|
||||
../common/generated/intel_acbp_v1.cpp
|
||||
../common/generated/intel_acbp_v2.cpp
|
||||
../common/generated/intel_keym_v1.cpp
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue