diff --git a/LZMA/SDK/C/LzmaEnc.c b/LZMA/SDK/C/LzmaEnc.c index bafd158..95e7891 100644 --- a/LZMA/SDK/C/LzmaEnc.c +++ b/LZMA/SDK/C/LzmaEnc.c @@ -2078,7 +2078,7 @@ void LzmaEnc_Finish(CLzmaEncHandle pp) if (p->mtMode) MatchFinderMt_ReleaseStream(&p->matchFinderMt); #else - //pp = pp; + pp = pp; #endif } diff --git a/descriptor.cpp b/descriptor.cpp index 266b16f..c49b8d2 100644 --- a/descriptor.cpp +++ b/descriptor.cpp @@ -25,6 +25,12 @@ UINT8* calculateAddress16(UINT8* baseAddress, const UINT16 baseOrLimit) return baseAddress + baseOrLimit * 0x1000; } +// Calculate offset of region using its base +UINT32 calculateRegionOffset(const UINT16 base) +{ + return base * 0x1000; +} + //Calculate size of region using its base and limit UINT32 calculateRegionSize(const UINT16 base, const UINT16 limit) { diff --git a/descriptor.h b/descriptor.h index 1cd3d0e..b23503f 100644 --- a/descriptor.h +++ b/descriptor.h @@ -158,6 +158,9 @@ typedef struct { extern UINT8* calculateAddress8(UINT8* baseAddress, const UINT8 baseOrLimit); // 16 bit base or limit extern UINT8* calculateAddress16(UINT8* baseAddress, const UINT16 baseOrLimit); -//Calculate size of region using its base and limit + +// Calculate offset of region using its base +extern UINT32 calculateRegionOffset(const UINT16 base); +// Calculate size of region using its base and limit extern UINT32 calculateRegionSize(const UINT16 base, const UINT16 limit); #endif \ No newline at end of file diff --git a/ffs.cpp b/ffs.cpp index d82dc59..2458fc7 100644 --- a/ffs.cpp +++ b/ffs.cpp @@ -10,6 +10,7 @@ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. */ +#include #include "ffs.h" const UINT8 ffsAlignmentTable[] = diff --git a/ffs.h b/ffs.h index 6e542c8..6c3d4c8 100644 --- a/ffs.h +++ b/ffs.h @@ -15,7 +15,6 @@ WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include #include -#include #include "basetypes.h" // C++ functions diff --git a/ffsengine.cpp b/ffsengine.cpp new file mode 100644 index 0000000..68e6248 --- /dev/null +++ b/ffsengine.cpp @@ -0,0 +1,1164 @@ +/* ffsengine.cpp + +Copyright (c) 2013, Nikolaj Schlej. 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, +WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +*/ + +#include + +#include "ffsengine.h" +#include "treeitem.h" +#include "treemodel.h" +#include "treeitemtypes.h" +#include "descriptor.h" +#include "ffs.h" +#include "gbe.h" +#include "me.h" +#include "Tiano/EfiTianoCompress.h" +#include "Tiano/EfiTianoDecompress.h" +#include "LZMA/LzmaCompress.h" +#include "LZMA/LzmaDecompress.h" + +FfsEngine::FfsEngine(QObject *parent) + : QObject(parent) +{ + rootItem = new TreeItem(RootItem, 0, 0, tr("Object"), tr("Type"), tr("Subtype"), tr("Text")); + treeModel = new TreeModel(rootItem); +} + +FfsEngine::~FfsEngine(void) +{ + delete treeModel; + delete rootItem; +} + +TreeModel* FfsEngine::model() const +{ + return treeModel; +} + +QString FfsEngine::message() const +{ + return text; +} + +void FfsEngine::msg(const QString & message) +{ + text.append(message).append("\n"); +} + +UINT8 FfsEngine::parseInputFile(const QByteArray & buffer) +{ + UINT32 capsuleHeaderSize = 0; + FLASH_DESCRIPTOR_HEADER* descriptorHeader = NULL; + QByteArray flashImage; + QByteArray bios; + QModelIndex index; + + // Check buffer size to be more or equal then sizeof(EFI_CAPSULE_HEADER) + if (buffer.size() <= sizeof(EFI_CAPSULE_HEADER)) + { + msg(tr("parseInputFile: Input file is smaller then mininum size of %1 bytes").arg(sizeof(EFI_CAPSULE_HEADER))); + return ERR_INVALID_PARAMETER; + } + + // Check buffer for being normal EFI capsule header + if (buffer.startsWith(EFI_CAPSULE_GUID)) { + EFI_CAPSULE_HEADER* capsuleHeader = (EFI_CAPSULE_HEADER*) buffer.constData(); + capsuleHeaderSize = capsuleHeader->HeaderSize; + QByteArray header = buffer.left(capsuleHeaderSize); + QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize); + index = addTreeItem(CapsuleItem, UefiCapsule, 0, header, body); + } + + // Check buffer for being extended Aptio capsule header + else if (buffer.startsWith(APTIO_CAPSULE_GUID)) { + APTIO_CAPSULE_HEADER* aptioCapsuleHeader = (APTIO_CAPSULE_HEADER*) buffer.constData(); + capsuleHeaderSize = aptioCapsuleHeader->RomImageOffset; + QByteArray header = buffer.left(capsuleHeaderSize); + QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize); + index = addTreeItem(CapsuleItem, AptioCapsule, 0, header, body); + } + + // Skip capsule header to have flash chip image + flashImage = buffer.right(buffer.size() - capsuleHeaderSize); + + // Check buffer for being Intel flash descriptor + descriptorHeader = (FLASH_DESCRIPTOR_HEADER*) flashImage.constData(); + // Check descriptor signature + if (descriptorHeader->Signature == FLASH_DESCRIPTOR_SIGNATURE) { + FLASH_DESCRIPTOR_MAP* descriptorMap; + FLASH_DESCRIPTOR_REGION_SECTION* regionSection; + + // Store the beginning of descriptor as descriptor base address + UINT8* descriptor = (UINT8*) flashImage.constData(); + UINT8* gbeRegion = NULL; + UINT8* meRegion = NULL; + UINT8* biosRegion = NULL; + UINT8* pdrRegion = NULL; + + // Check for buffer size to be greater or equal to descriptor region size + if (flashImage.size() < FLASH_DESCRIPTOR_SIZE) { + msg(tr("parseInputFile: Input file is smaller then mininum descriptor size of 4096 bytes")); + return ERR_INVALID_FLASH_DESCRIPTOR; + } + + // Parse descriptor map + descriptorMap = (FLASH_DESCRIPTOR_MAP*) (flashImage.constData() + sizeof(FLASH_DESCRIPTOR_HEADER)); + regionSection = (FLASH_DESCRIPTOR_REGION_SECTION*) calculateAddress8(descriptor, descriptorMap->RegionBase); + + // Add tree item + QByteArray header = flashImage.left(sizeof(FLASH_DESCRIPTOR_HEADER)); + QByteArray body = flashImage.mid(sizeof(FLASH_DESCRIPTOR_HEADER), FLASH_DESCRIPTOR_SIZE - sizeof(FLASH_DESCRIPTOR_HEADER)); + index = addTreeItem(DescriptorItem, 0, 0, header, body, index); + + // Parse region section + QModelIndex gbeIndex(index); + QModelIndex meIndex(index); + QModelIndex biosIndex(index); + QModelIndex pdrIndex(index); + gbeRegion = parseRegion(flashImage, GbeRegion, regionSection->GbeBase, regionSection->GbeLimit, gbeIndex); + meRegion = parseRegion(flashImage, MeRegion, regionSection->MeBase, regionSection->MeLimit, meIndex); + biosRegion = parseRegion(flashImage, BiosRegion, regionSection->BiosBase, regionSection->BiosLimit, biosIndex); + pdrRegion = parseRegion(flashImage, PdrRegion, regionSection->PdrBase, regionSection->PdrLimit, pdrIndex); + + // Parse complete + // Exit if no bios region found + if (!biosRegion) { + msg(tr("parseInputFile: BIOS region not found")); + return ERR_BIOS_REGION_NOT_FOUND; + } + + index = biosIndex; + bios = QByteArray::fromRawData((const char*) biosRegion, calculateRegionSize(regionSection->BiosBase, regionSection->BiosLimit)); + } + else { + bios = buffer; + } + + // We are in the beginning of BIOS space, where firmware volumes are + // Parse BIOS space + + return parseBios(bios, index); +} + +UINT8* FfsEngine::parseRegion(const QByteArray & flashImage, UINT8 regionSubtype, const UINT16 regionBase, const UINT16 regionLimit, QModelIndex & index) +{ + // Check for empty region or flash image + if (!regionLimit || flashImage.size() <= 0) + return NULL; + + // Storing flash image size to unsigned variable, because it can't be negative now and all other values are unsigned + UINT32 flashImageSize = (UINT32) flashImage.size(); + + // Calculate region offset and size + UINT32 regionOffset = calculateRegionOffset(regionBase); + UINT32 regionSize = calculateRegionSize(regionBase, regionLimit); + + // Populate descriptor map + FLASH_DESCRIPTOR_MAP* descriptor_map = (FLASH_DESCRIPTOR_MAP*) (flashImage.constData() + sizeof(FLASH_DESCRIPTOR_HEADER)); + + // Determine presence of 2 flash chips + bool twoChips = descriptor_map->NumberOfFlashChips; + + // construct region name + //!TODO: make this to regionTypeToQString(const UINT8 type) in descriptor.cpp + QString regionName; + switch (regionSubtype) + { + case GbeRegion: + regionName = "GbE"; + break; + case MeRegion: + regionName = "ME"; + break; + case BiosRegion: + regionName = "Bios"; + break; + case PdrRegion: + regionName = "PDR"; + break; + default: + regionName = "Unknown"; + msg(tr("parseRegion: Unknown region type")); + }; + + // Check region base to be in buffer + if (regionOffset >= flashImageSize) + { + msg(tr("parseRegion: %1 region stored in descriptor not found").arg(regionName)); + if (twoChips) + msg(tr("Two flash chips installed, so it could be in another flash chip\n" + "Make a dump from another flash chip and open it to view information about %1 region").arg(regionName)); + else + msg(tr("One flash chip installed, so it is an error caused by damaged or incomplete dump")); + msg(tr("Absence of %1 region assumed").arg(regionName)); + return NULL; + } + + // Check region to be fully present in buffer + else if (regionOffset + regionSize > flashImageSize) + { + msg(tr("parseRegion: %1 region stored in descriptor overlaps the end of opened file").arg(regionName)); + if (twoChips) + msg(tr("Two flash chips installed, so it could be in another flash chip\n" + "Make a dump from another flash chip and open it to view information about %1 region").arg(regionName)); + else + msg(tr("One flash chip installed, so it is an error caused by damaged or incomplete dump")); + msg(tr("Absence of %1 region assumed\n").arg(regionName)); + return NULL; + } + + // Calculate region address + UINT8* region = calculateAddress16((UINT8*) flashImage.constData(), regionBase); + + // Add tree item + QByteArray body = flashImage.mid(regionOffset, regionSize); + index = addTreeItem(RegionItem, regionSubtype, regionOffset, QByteArray(), body, index); + + return region; +} + +UINT8 FfsEngine::parseBios(const QByteArray & bios, const QModelIndex & parent) +{ + // Search for first volume + INT32 prevVolumeOffset = findNextVolume(bios); + + // No volumes found + if (prevVolumeOffset < 0) { + return ERR_VOLUMES_NOT_FOUND; + } + + // First volume is not at the beginning of bios space + if (prevVolumeOffset > 0) { + QByteArray padding = bios.left(prevVolumeOffset); + addTreeItem(PaddingItem, 0, 0, QByteArray(), padding, parent); + } + + // Search for and parse all volumes + INT32 volumeOffset; + UINT32 prevVolumeSize; + for (volumeOffset = prevVolumeOffset, prevVolumeSize = 0; + volumeOffset >= 0; + prevVolumeOffset = volumeOffset, prevVolumeSize = getVolumeSize(bios, volumeOffset), volumeOffset = findNextVolume(bios, volumeOffset + prevVolumeSize)) + { + // Padding between volumes + if ((UINT32) volumeOffset > prevVolumeOffset + prevVolumeSize) { // Conversion to suppress warning, volumeOffset can't be negative here + UINT32 size = volumeOffset - prevVolumeOffset - prevVolumeSize; + QByteArray padding = bios.mid(prevVolumeOffset + prevVolumeSize, size); + addTreeItem(PaddingItem, 0, prevVolumeOffset + prevVolumeSize, QByteArray(), padding, parent); + } + + // Populate volume header + EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) (bios.constData() + volumeOffset); + + //Check that volume is fully present in input + if (volumeOffset + volumeHeader->FvLength > bios.size()) { + msg(tr("parseBios: Volume overlaps the end of input buffer")); + return ERR_INVALID_VOLUME; + } + + // Check volume revision and alignment + UINT32 alignment; + if (volumeHeader->Revision == 1) { + // Aquire alignment bits + bool alignmentCap = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_CAP; + bool alignment2 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_2; + bool alignment4 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_4; + bool alignment8 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_8; + bool alignment16 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_16; + bool alignment32 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_32; + bool alignment64 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_64; + bool alignment128 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_128; + bool alignment256 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_256; + bool alignment512 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_512; + bool alignment1k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_1K; + bool alignment2k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_2K; + bool alignment4k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_4K; + bool alignment8k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_8K; + bool alignment16k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_16K; + bool alignment32k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_32K; + bool alignment64k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_64K; + + // Check alignment setup + if (!alignmentCap && + ( alignment2 || alignment4 || alignment8 || alignment16 + || alignment32 || alignment64 || alignment128 || alignment256 + || alignment512 || alignment1k || alignment2k || alignment4k + || alignment8k || alignment16k || alignment32k || alignment64k)) + msg("parseBios: Incompatible revision 1 volume alignment setup"); + + // Assume that smaller alignment value consumes greater + alignment = 0x01; + if (alignment2) + alignment = 0x02; + else if (alignment4) + alignment = 0x04; + else if (alignment8) + alignment = 0x08; + else if (alignment16) + alignment = 0x10; + else if (alignment32) + alignment = 0x20; + else if (alignment64) + alignment = 0x40; + else if (alignment128) + alignment = 0x80; + else if (alignment256) + alignment = 0x100; + else if (alignment512) + alignment = 0x200; + else if (alignment1k) + alignment = 0x400; + else if (alignment2k) + alignment = 0x800; + else if (alignment4k) + alignment = 0x1000; + else if (alignment8k) + alignment = 0x2000; + else if (alignment16k) + alignment = 0x4000; + else if (alignment32k) + alignment = 0x8000; + else if (alignment64k) + alignment = 0x10000; + + // Check alignment + if (volumeOffset % alignment) { + msg(tr("parseBios: Unaligned revision 1 volume")); + } + } + else if (volumeHeader->Revision == 2) { + // Aquire alignment + alignment = pow(2, (volumeHeader->Attributes & EFI_FVB2_ALIGNMENT) >> 16); + + // Check alignment + if (volumeOffset % alignment) { + msg(tr("parseBios: Unaligned revision 2 volume")); + } + } + else + msg(tr("parseBios: Unknown volume revision (%1)").arg(volumeHeader->Revision)); + + // Check filesystem GUID to be known + // Do not parse volume with unknown FFS, because parsing will fail + bool parseCurrentVolume = true; + // FFS GUID v1 + if (QByteArray((const char*) &volumeHeader->FileSystemGuid, sizeof(EFI_GUID)) == EFI_FIRMWARE_FILE_SYSTEM_GUID) { + // Code can be added here + } + // FFS GUID v2 + else if (QByteArray((const char*) &volumeHeader->FileSystemGuid, sizeof(EFI_GUID)) == EFI_FIRMWARE_FILE_SYSTEM2_GUID) { + // Code can be added here + } + // Other GUID + else { + //info = info.append(tr("File system: unknown\n")); + msg(tr("parseBios: Unknown file system (%1)").arg(guidToQString(volumeHeader->FileSystemGuid))); + parseCurrentVolume = false; + } + + // Check attributes + // Determine erase polarity + bool erasePolarity = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY; + + // Check header checksum by recalculating it + if (!calculateChecksum16((UINT8*) volumeHeader, volumeHeader->HeaderLength)) { + msg(tr("parseBios: Volume header checksum is invalid")); + } + + // Check for presence of extended header, only if header revision is not 1 + UINT32 headerSize; + if (volumeHeader->Revision > 1 && volumeHeader->ExtHeaderOffset) { + EFI_FIRMWARE_VOLUME_EXT_HEADER* extendedHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER*) ((UINT8*) volumeHeader + volumeHeader->ExtHeaderOffset); + headerSize = volumeHeader->ExtHeaderOffset + extendedHeader->ExtHeaderSize; + } else { + headerSize = volumeHeader->HeaderLength; + } + + // Adding tree item + QByteArray header = bios.mid(volumeOffset, headerSize); + QByteArray body = bios.mid(volumeOffset + headerSize, volumeHeader->FvLength - headerSize); + QModelIndex index = addTreeItem(VolumeItem, 0, volumeOffset, header, body, parent); + + // Parse volume + if (parseCurrentVolume) { + UINT32 result = parseVolume(bios.mid(volumeOffset + headerSize, volumeHeader->FvLength - headerSize), headerSize, volumeHeader->Revision, erasePolarity, index); + if (result) + msg(tr("parseBios: Volume parsing failed (%1)").arg(result)); + } + } + + return ERR_SUCCESS; +} + +INT32 FfsEngine::findNextVolume(const QByteArray & bios, INT32 volumeOffset) +{ + if (volumeOffset < 0) + return -1; + + INT32 nextIndex = bios.indexOf(EFI_FV_SIGNATURE, volumeOffset); + if (nextIndex < EFI_FV_SIGNATURE_OFFSET) { + return -1; + } + + return nextIndex - EFI_FV_SIGNATURE_OFFSET; +} + +UINT32 FfsEngine::getVolumeSize(const QByteArray & bios, INT32 volumeOffset) +{ + // Populate volume header + EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) (bios.constData() + volumeOffset); + + // Check volume signature + if (QByteArray((const char*) &volumeHeader->Signature, sizeof(volumeHeader->Signature)) != EFI_FV_SIGNATURE) + return 0; + return volumeHeader->FvLength; +} + +UINT8 FfsEngine::parseVolume(const QByteArray & volume, UINT32 volumeBase, UINT8 revision, bool erasePolarity, const QModelIndex & parent) +{ + // Construct empty byte based on erasePolarity value + // Native char type is used because QByteArray.count() takes it + char empty = erasePolarity ? '\xFF' : '\x00'; + + // Search for and parse all files + INT32 fileOffset = 0; + while (fileOffset >= 0) { + EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*) (volume.constData() + fileOffset); + QByteArray file = volume.mid(fileOffset, uint24ToUint32(fileHeader->Size)); + QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER)); + + // Check file size to at least sizeof(EFI_FFS_FILE_HEADER) + if (file.size() < sizeof(EFI_FFS_FILE_HEADER)) + { + msg(tr("parseVolume: File with invalid size")); + return ERR_INVALID_FILE; + } + + // We are at empty space in the end of volume + if (header.count(empty) == header.size()) { + QByteArray body = volume.right(volume.size() - fileOffset); + addTreeItem(PaddingItem, 0, fileOffset, QByteArray(), body, parent); + break; + } + + // Check header checksum + QByteArray tempHeader = header; + EFI_FFS_FILE_HEADER* tempFileHeader = (EFI_FFS_FILE_HEADER*) (tempHeader.data()); + tempFileHeader->IntegrityCheck.Checksum.Header = 0; + tempFileHeader->IntegrityCheck.Checksum.File = 0; + UINT8 calculated = calculateChecksum8((UINT8*) tempFileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1); + if (fileHeader->IntegrityCheck.Checksum.Header != calculated) + { + msg(tr("parseVolume: %1, stored header checksum %2 differs from calculated %3") + .arg(guidToQString(fileHeader->Name)) + .arg(fileHeader->IntegrityCheck.Checksum.Header, 2, 16, QChar('0')) + .arg(calculated, 2, 16, QChar('0'))); + } + + // Check data checksum, if no tail was found + // Data checksum must be calculated + if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) { + UINT32 bufferSize = file.size() - sizeof(EFI_FFS_FILE_HEADER); + // Exclude file tail from data checksum calculation + if(revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) + bufferSize -= sizeof(UINT16); + calculated = calculateChecksum8((UINT8*)(file.constData() + sizeof(EFI_FFS_FILE_HEADER)), bufferSize); + if (fileHeader->IntegrityCheck.Checksum.File != calculated) { + msg(tr("parseVolume: %1, stored data checksum %2 differs from calculated %3") + .arg(guidToQString(fileHeader->Name)) + .arg(fileHeader->IntegrityCheck.Checksum.File, 2, 16, QChar('0')) + .arg(calculated, 2, 16, QChar('0'))); + } + } + // Data checksum must be one of predefined values + else { + if (fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM &&fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM2) { + msg(tr("parseVolume: %1, stored data checksum %2 differs from standard value") + .arg(guidToQString(fileHeader->Name)) + .arg(fileHeader->IntegrityCheck.Checksum.File, 2, 16, QChar('0'))); + } + } + + // Check file alignment + UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3]; + UINT32 alignment = pow(2, alignmentPower); + if ((volumeBase + fileOffset + sizeof(EFI_FFS_FILE_HEADER)) % alignment) { + msg(tr("parseVolume: %1, unaligned file").arg(guidToQString(fileHeader->Name))); + } + + // Get file body + QByteArray body = file.right(file.size() - sizeof(EFI_FFS_FILE_HEADER)); + // For files in Revision 1 volumes, check for file tail presence + if (revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) + { + //Check file tail; + UINT16* tail = (UINT16*) body.right(sizeof(UINT16)).constData(); + if (!fileHeader->IntegrityCheck.TailReference == *tail) + msg(tr("parseVolume: %1, file tail value %2 is not a bitwise not of %3 stored in file header") + .arg(guidToQString(fileHeader->Name)) + .arg(*tail, 4, 16, QChar('0')) + .arg(fileHeader->IntegrityCheck.TailReference, 4, 16, QChar('0'))); + + // Remove tail from file body + body = body.left(body.size() - sizeof(UINT16)); + } + + // Parse current file by default + bool parseCurrentFile = true; + // Raw files can hide volumes inside them + // So try to parse them as bios space + bool parseAsBios = false; + + // Check file type + //!TODO: add more file specific checks + switch (fileHeader->Type) + { + case EFI_FV_FILETYPE_ALL: + parseAsBios = true; + break; + case EFI_FV_FILETYPE_RAW: + parseAsBios = true; + break; + case EFI_FV_FILETYPE_FREEFORM: + break; + case EFI_FV_FILETYPE_SECURITY_CORE: + break; + case EFI_FV_FILETYPE_PEI_CORE: + break; + case EFI_FV_FILETYPE_DXE_CORE: + break; + case EFI_FV_FILETYPE_PEIM: + break; + case EFI_FV_FILETYPE_DRIVER: + break; + case EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER: + break; + case EFI_FV_FILETYPE_APPLICATION: + break; + case EFI_FV_FILETYPE_SMM: + break; + case EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE: + break; + case EFI_FV_FILETYPE_COMBINED_SMM_DXE: + break; + case EFI_FV_FILETYPE_SMM_CORE: + break; + case EFI_FV_FILETYPE_PAD: + parseCurrentFile = false; + break; + default: + parseCurrentFile = false; + msg(tr("parseVolume: Unknown file type (%1)").arg(fileHeader->Type, 2, 16, QChar('0'))); + }; + + // Check for empty file + if (body.count(empty) == body.size()) + { + // No need to parse empty files + parseCurrentFile = false; + } + + // Add tree item + QModelIndex index = addTreeItem(FileItem, fileHeader->Type, fileOffset, header, body, parent); + + // Parse file + if (parseCurrentFile) { + if (parseAsBios) { + UINT32 result = parseBios(body, index); + if (result && result != ERR_VOLUMES_NOT_FOUND) + msg(tr("parseVolume: Parse file as BIOS failed (%1)").arg(result)); + } + else { + UINT32 result = parseFile(body, revision, erasePolarity, index); + if (result) + msg(tr("parseVolume: Parse file as FFS failed (%1)").arg(result)); + } + } + + // Move to next file + fileOffset += file.size(); + fileOffset = ALIGN8(fileOffset); + + // Exit from loop if no files left + if (fileOffset >= volume.size()) + fileOffset = -1; + } + + return ERR_SUCCESS; +} + +UINT8 FfsEngine::parseFile(const QByteArray & file, UINT8 revision, bool erasePolarity, const QModelIndex & parent) +{ + // Search for and parse all sections + INT32 sectionOffset = 0; + while(sectionOffset >= 0) + { + EFI_COMMON_SECTION_HEADER* sectionHeader = (EFI_COMMON_SECTION_HEADER*) (file.constData() + sectionOffset); + UINT32 sectionSize = uint24ToUint32(sectionHeader->Size); + + // This declarations must be here because of the nature of switch statement + EFI_COMPRESSION_SECTION* compressedSectionHeader; + EFI_GUID_DEFINED_SECTION* guidDefinedSectionHeader; + QByteArray header; + QByteArray body; + UINT32 decompressedSize; + UINT32 scratchSize; + UINT8* decompressed; + UINT8* scratch; + VOID* data; + UINT32 dataSize; + QModelIndex index; + UINT32 result; + UINT32 shittySectionSize; + EFI_COMMON_SECTION_HEADER* shittySectionHeader; + + switch (sectionHeader->Type) + { + // Encapsulated sections + case EFI_SECTION_COMPRESSION: + compressedSectionHeader = (EFI_COMPRESSION_SECTION*) sectionHeader; + header = file.mid(sectionOffset, sizeof(EFI_COMPRESSION_SECTION)); + + // Try to decompress this section + switch (compressedSectionHeader->CompressionType) + { + case EFI_NOT_COMPRESSED: + body = file.mid(sectionOffset + sizeof(EFI_COMPRESSION_SECTION), compressedSectionHeader->UncompressedLength); + index = addTreeItem(SectionItem, CompressionSection, sectionOffset, header, body, parent); + // Parse stored file + result = parseFile(body, revision, erasePolarity, index); + if (result) + msg(tr("parseFile: Stored section can not be parsed as file (%1)").arg(result)); + break; + case EFI_STANDARD_COMPRESSION: + //Must be Tiano for all revisions, needs checking + body = file.mid(sectionOffset + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + + // Get buffer sizes + data = (VOID*) (file.constData() + sectionOffset + sizeof(EFI_COMPRESSION_SECTION)); + dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION); + if (TianoGetInfo(data, dataSize, &decompressedSize, &scratchSize) != ERR_SUCCESS + || decompressedSize != compressedSectionHeader->UncompressedLength) + msg(tr("parseFile: TianoGetInfo failed")); + else { + decompressed = new UINT8[decompressedSize]; + scratch = new UINT8[scratchSize]; + // Decompress section data + if (TianoDecompress(data, dataSize, decompressed, decompressedSize, scratch, scratchSize) != ERR_SUCCESS) + msg(tr("parseFile: TianoDecompress failed")); + else + { + body = QByteArray::fromRawData((const char*) decompressed, decompressedSize); + // Parse stored file + result = parseFile(body, revision, erasePolarity, index); + if (result) + msg(tr("parseFile: Compressed section with Tiano compression can not be parsed as file (%1)").arg(result)); + } + + delete[] decompressed; + delete[] scratch; + } + break; + case EFI_CUSTOMIZED_COMPRESSION: + body = file.mid(sectionOffset + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + + // Get buffer sizes + data = (VOID*) (file.constData() + sectionOffset + sizeof(EFI_COMPRESSION_SECTION)); + dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION); + if (LzmaGetInfo(data, dataSize, &decompressedSize) != ERR_SUCCESS + || decompressedSize != compressedSectionHeader->UncompressedLength) + { + // Shitty file with a section header between COMPRESSED_SECTION_HEADER and LZMA_HEADER + // We must determine section header size by checking it's type before we can unpack that non-standard compressed section + shittySectionHeader = (EFI_COMMON_SECTION_HEADER*) data; + shittySectionSize = sizeOfSectionHeaderOfType(shittySectionHeader->Type); + data = (VOID*) (file.constData() + sectionOffset + sizeof(EFI_COMPRESSION_SECTION) + shittySectionSize); + dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION) - shittySectionSize; + if (LzmaGetInfo(data, dataSize, &decompressedSize) != ERR_SUCCESS) + msg(tr("parseFile: LzmaGetInfo failed")); + } + + decompressed = new UINT8[decompressedSize]; + + // Decompress section data + if (LzmaDecompress(data, dataSize, decompressed) != ERR_SUCCESS) + msg(tr("parseFile: LzmaDecompress failed")); + else + { + body = QByteArray::fromRawData((const char*) decompressed, decompressedSize); + // Parse stored file + result = parseFile(body, revision, erasePolarity, index); + if (result) + msg(tr("parseFile: Compressed section with LZMA compression can not be parsed as file (%1)").arg(result)); + } + + delete[] decompressed; + break; + default: + body = file.mid(sectionOffset + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + msg(tr("parseFile: Compressed section with unknown compression type found (%1)").arg(compressedSectionHeader->CompressionType)); + } + + break; + case EFI_SECTION_GUID_DEFINED: + header = file.mid(sectionOffset, sizeof(EFI_GUID_DEFINED_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_GUID_DEFINED_SECTION), sectionSize - sizeof(EFI_GUID_DEFINED_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + // Parse section body as file + guidDefinedSectionHeader = (EFI_GUID_DEFINED_SECTION*) (header.constData()); + body = file.mid(sectionOffset + guidDefinedSectionHeader->DataOffset, sectionSize - guidDefinedSectionHeader->DataOffset); + result = parseFile(body, revision, erasePolarity, index); + if (result) + msg(tr("parseFile: GUID defined section can not be parsed as file (%1)").arg(result)); + break; + case EFI_SECTION_DISPOSABLE: + header = file.mid(sectionOffset, sizeof(EFI_DISPOSABLE_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_DISPOSABLE_SECTION), sectionSize - sizeof(EFI_DISPOSABLE_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + // Leaf sections + case EFI_SECTION_PE32: + header = file.mid(sectionOffset, sizeof(EFI_PE32_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_PE32_SECTION), sectionSize - sizeof(EFI_PE32_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_PIC: + header = file.mid(sectionOffset, sizeof(EFI_PIC_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_PIC_SECTION), sectionSize - sizeof(EFI_PIC_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_TE: + header = file.mid(sectionOffset, sizeof(EFI_TE_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_TE_SECTION), sectionSize - sizeof(EFI_TE_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_VERSION: + header = file.mid(sectionOffset, sizeof(EFI_VERSION_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_VERSION_SECTION), sectionSize - sizeof(EFI_VERSION_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_USER_INTERFACE: + header = file.mid(sectionOffset, sizeof(EFI_USER_INTERFACE_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_USER_INTERFACE_SECTION), sectionSize - sizeof(EFI_USER_INTERFACE_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_COMPATIBILITY16: + header = file.mid(sectionOffset, sizeof(EFI_COMPATIBILITY16_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_COMPATIBILITY16_SECTION), sectionSize - sizeof(EFI_COMPATIBILITY16_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_FIRMWARE_VOLUME_IMAGE: + header = file.mid(sectionOffset, sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION), sectionSize - sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + // Parse section body as BIOS space + result = parseBios(body, index); + if (result && result != ERR_VOLUMES_NOT_FOUND) + msg(tr("parseFile: Firmware volume image can not be parsed (%1)").arg(result)); + break; + case EFI_SECTION_FREEFORM_SUBTYPE_GUID: + header = file.mid(sectionOffset, sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION), sectionSize - sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_RAW: + header = file.mid(sectionOffset, sizeof(EFI_RAW_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_RAW_SECTION), sectionSize - sizeof(EFI_RAW_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + // Parse section body as BIOS space + result = parseBios(body, index); + if (result && result != ERR_VOLUMES_NOT_FOUND) + msg(tr("parseFile: Raw section can not be parsed as BIOS (%1)").arg(result)); + break; + break; + case EFI_SECTION_DXE_DEPEX: + header = file.mid(sectionOffset, sizeof(EFI_DXE_DEPEX_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_DXE_DEPEX_SECTION), sectionSize - sizeof(EFI_DXE_DEPEX_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_PEI_DEPEX: + header = file.mid(sectionOffset, sizeof(EFI_PEI_DEPEX_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_PEI_DEPEX_SECTION), sectionSize - sizeof(EFI_PEI_DEPEX_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + case EFI_SECTION_SMM_DEPEX: + header = file.mid(sectionOffset, sizeof(EFI_SMM_DEPEX_SECTION)); + body = file.mid(sectionOffset + sizeof(EFI_SMM_DEPEX_SECTION), sectionSize - sizeof(EFI_SMM_DEPEX_SECTION)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + break; + default: + msg(tr("parseFile: Section with unknown type (%1)").arg(sectionHeader->Type, 2, 16, QChar('0'))); + header = file.mid(sectionOffset, sizeof(EFI_COMMON_SECTION_HEADER)); + body = file.mid(sectionOffset + sizeof(EFI_COMMON_SECTION_HEADER), sectionSize - sizeof(EFI_COMMON_SECTION_HEADER)); + index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); + } + + // Move to next section + sectionOffset += uint24ToUint32(sectionHeader->Size); + sectionOffset = ALIGN4(sectionOffset); + + // Exit from loop if no sections left + if (sectionOffset >= file.size()) + sectionOffset = -1; + } + + return ERR_SUCCESS; +} + +bool FfsEngine::hasEmptyHeader(const QModelIndex& index) const +{ + if (!index.isValid()) + return true; + + TreeItem *item = static_cast(index.internalPointer()); + + return item->hasEmptyHeader(); +} + +bool FfsEngine::hasEmptyBody(const QModelIndex& index) const +{ + if (!index.isValid()) + return true; + + TreeItem *item = static_cast(index.internalPointer()); + + return item->hasEmptyBody(); +} + +QModelIndex FfsEngine::addTreeItem(const UINT8 type, const UINT8 subtype, const UINT32 offset, const QByteArray &header, const QByteArray &body, const QModelIndex &parent) +{ + TreeItem *parentItem; + int parentColumn = 0; + + if (!parent.isValid()) + parentItem = rootItem; + else + { + parentItem = static_cast(parent.internalPointer()); + parentColumn = parent.column(); + } + + // All information extraction must be here + QString name, typeName, subtypeName, info; + EFI_CAPSULE_HEADER* capsuleHeader; + APTIO_CAPSULE_HEADER* aptioCapsuleHeader; + FLASH_DESCRIPTOR_MAP* descriptorMap; + //FLASH_DESCRIPTOR_COMPONENT_SECTION* componentSection; + //FLASH_DESCRIPTOR_REGION_SECTION* regionSection; + //FLASH_DESCRIPTOR_MASTER_SECTION* masterSection; + GBE_MAC* gbeMac; + GBE_VERSION* gbeVersion; + ME_VERSION* meVersion; + INT32 meVersionOffset; + EFI_FIRMWARE_VOLUME_HEADER* volumeHeader; + EFI_FFS_FILE_HEADER* fileHeader; + //EFI_COMMON_SECTION_HEADER* sectionHeader; + + switch (type) + { + case RootItem: + // Do not allow to add another root item + return QModelIndex(); + break; + case CapsuleItem: + //typeName = tr("Capsule"); + switch (subtype) + { + case AptioCapsule: + name = tr("AMI Aptio capsule"); + aptioCapsuleHeader = (APTIO_CAPSULE_HEADER*) header.constData(); + info = tr("Offset: %1\nHeader size: %2\nFlags: %3\nImage size: %4") + .arg(offset, 8, 16, QChar('0')) + .arg(aptioCapsuleHeader->RomImageOffset, 4, 16, QChar('0')) + .arg(aptioCapsuleHeader->CapsuleHeader.Flags, 8, 16, QChar('0')) + .arg(aptioCapsuleHeader->CapsuleHeader.CapsuleImageSize - aptioCapsuleHeader->RomImageOffset, 8, 16, QChar('0')); + //!TODO: more info about Aptio capsule + break; + case UefiCapsule: + name = tr("UEFI capsule"); + capsuleHeader = (EFI_CAPSULE_HEADER*) header.constData(); + info = tr("Offset: %1\nHeader size: %2\nFlags: %3\nImage size: %4") + .arg(offset, 8, 16, QChar('0')) + .arg(capsuleHeader->HeaderSize, 8, 16, QChar('0')) + .arg(capsuleHeader->Flags, 8, 16, QChar('0')) + .arg(capsuleHeader->CapsuleImageSize, 8, 16, QChar('0')); + break; + default: + name = tr("Unknown capsule"); + info = tr("Offset: %1\nGUID: %2\n") + .arg(offset, 8, 16, QChar('0')) + .arg(guidToQString(*(EFI_GUID*)header.constData())); + break; + } + break; + case DescriptorItem: + name = tr("Descriptor"); + descriptorMap = (FLASH_DESCRIPTOR_MAP*) body.constData(); + info = tr("Flash chips: %1\nRegions: %2\nMasters: %3\nPCH straps:%4\nPROC straps: %5\nICC table entries: %6") + .arg(descriptorMap->NumberOfFlashChips + 1) // + .arg(descriptorMap->NumberOfRegions + 1) // Zero-based numbers in storage + .arg(descriptorMap->NumberOfMasters + 1) // + .arg(descriptorMap->NumberOfPchStraps) + .arg(descriptorMap->NumberOfProcStraps) + .arg(descriptorMap->NumberOfIccTableEntries); + //!TODO: more info about descriptor + break; + case RegionItem: + typeName = tr("Region"); + info = tr("Offset: %1\nSize: %2") + .arg(offset, 8, 16, QChar('0')) + .arg(body.size(), 8, 16, QChar('0')); + switch (subtype) + { + case GbeRegion: + name = tr("GbE region"); + gbeMac = (GBE_MAC*) body.constData(); + gbeVersion = (GBE_VERSION*) (body.constData() + GBE_VERSION_OFFSET); + info += tr("\nMAC: %1:%2:%3:%4:%5:%6\nVersion: %7.%8") + .arg(gbeMac->vendor[0], 2, 16, QChar('0')) + .arg(gbeMac->vendor[1], 2, 16, QChar('0')) + .arg(gbeMac->vendor[2], 2, 16, QChar('0')) + .arg(gbeMac->device[0], 2, 16, QChar('0')) + .arg(gbeMac->device[1], 2, 16, QChar('0')) + .arg(gbeMac->device[2], 2, 16, QChar('0')) + .arg(gbeVersion->major) + .arg(gbeVersion->minor); + break; + case MeRegion: + name = tr("ME region"); + meVersionOffset = body.indexOf(ME_VERSION_SIGNATURE); + if (meVersionOffset < 0){ + info += tr("\nVersion: unknown"); + msg(tr("addTreeItem: ME region version is unknown, it can be damaged")); + } + else { + meVersion = (ME_VERSION*) (body.constData() + meVersionOffset); + info += tr("\nVersion: %1.%2.%3.%4") + .arg(meVersion->major) + .arg(meVersion->minor) + .arg(meVersion->bugfix) + .arg(meVersion->build); + } + break; + case BiosRegion: + name = tr("BIOS region"); + break; + case PdrRegion: + name = tr("PDR region"); + break; + default: + name = tr("Unknown region"); + msg(tr("addTreeItem: Unknown region")); + break; + } + break; + case PaddingItem: + name = tr("Padding"); + info = tr("Offset: %1\nSize: %2") + .arg(offset, 8, 16, QChar('0')) + .arg(body.size(), 8, 16, QChar('0')); + break; + case VolumeItem: + typeName = tr("Volume"); + // Parse volume header to determine its revision and file system + volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) header.constData(); + name = guidToQString(volumeHeader->FileSystemGuid); + subtypeName = tr("Revision %1").arg(volumeHeader->Revision); + info = tr("Offset: %1\nSize: %2\nAttributes: %3\nHeader size: %4") + .arg(offset, 8, 16, QChar('0')) + .arg(volumeHeader->FvLength, 8, 16, QChar('0')) + .arg(volumeHeader->Attributes, 8, 16, QChar('0')) + .arg(volumeHeader->HeaderLength, 4, 16, QChar('0')); + break; + case FileItem: + typeName = tr("File"); + // Parse file header to determine its GUID and type + fileHeader = (EFI_FFS_FILE_HEADER*) header.constData(); + name = guidToQString(fileHeader->Name); + subtypeName = fileTypeToQString(subtype); + info = tr("Offset: %1\nType: %2\nAttributes: %3\nSize: %4\nState: %5") + .arg(offset, 8, 16, QChar('0')) + .arg(fileHeader->Type, 2, 16, QChar('0')) + .arg(fileHeader->Attributes, 2, 16, QChar('0')) + .arg(uint24ToUint32(fileHeader->Size), 6, 16, QChar('0')) + .arg(fileHeader->State, 2, 16, QChar('0')); + break; + case SectionItem: + typeName = tr("Section"); + name = sectionTypeToQString(subtype) + tr(" section"); + info = tr("Offset: %1\nSize: %2") + .arg(offset, 8, 16, QChar('0')) + .arg(body.size(), 8, 16, QChar('0')); + //!TODO: add more specific info for all section types with uncommon headers + // Set name of file + if (subtype == UserInterfaceSection) + { + QString text = QString::fromUtf16((const ushort*)body.constData()); + setTreeItemText(text, findParentOfType(FileItem, parent)); + } + break; + default: + name = tr("Unknown"); + info = tr("Offset: %1").arg(offset, 8, 16, QChar('0')); + break; + } + + return treeModel->addItem(type, subtype, offset, name, typeName, subtypeName, "", info, header, body, parent); +} + +bool FfsEngine::setTreeItemName(const QString &data, const QModelIndex &index) +{ + if(!index.isValid()) + return false; + + return treeModel->setItemName(data, index); +} + +bool FfsEngine::setTreeItemText(const QString &data, const QModelIndex &index) +{ + if(!index.isValid()) + return false; + + return treeModel->setItemText(data, index); +} + +bool FfsEngine::removeItem(const QModelIndex &index) +{ + TreeItem *item = static_cast(index.internalPointer()); + item->parent()->removeChild(item); + delete item; + return true; +} + +QModelIndex FfsEngine::findParentOfType(UINT8 type, const QModelIndex& index) const +{ + if(!index.isValid()) + return QModelIndex(); + + TreeItem *item; + QModelIndex parent = index; + + for(item = static_cast(parent.internalPointer()); + item != NULL && item != rootItem && item->type() != type; + item = static_cast(parent.internalPointer())) + parent = parent.parent(); + if (item != NULL && item != rootItem) + return parent; + + return QModelIndex(); +} + +QByteArray FfsEngine::header(const QModelIndex& index) const +{ + if (!index.isValid()) + return QByteArray(); + + TreeItem *item = static_cast(index.internalPointer()); + return item->header(); +} + +QByteArray FfsEngine::body(const QModelIndex& index) const +{ + if (!index.isValid()) + return QByteArray(); + + TreeItem *item = static_cast(index.internalPointer()); + return item->body(); +} + +QByteArray FfsEngine::uncompressFile(const QModelIndex& index) const +{ + if (!index.isValid()) + return QByteArray(); + + // Check index item to be FFS file + TreeItem *item = static_cast(index.internalPointer()); + if(item->type() != FileItem) + return QByteArray(); + + QByteArray file; + UINT32 offset = 0; + // Construct new item body + for (int i = 0; i < item->childCount(); i++) + { + // If section is not compressed, add it to new body as is + TreeItem* sectionItem = item->child(i); + if (sectionItem->subtype() != CompressionSection) + { + QByteArray section = sectionItem->header().append(sectionItem->body()); + UINT32 align = ALIGN4(offset) - offset; + file.append(QByteArray(align, '\x00')).append(section); + offset += align + section.size(); + } + else { + // Construct new section body by adding all child sections to this new section + QByteArray section; + UINT32 subOffset = 0; + for (int j = 0; j < sectionItem->childCount(); j++) + { + TreeItem* subSectionItem = sectionItem->child(j); + QByteArray subSection = subSectionItem->header().append(subSectionItem->body()); + UINT32 align = ALIGN4(subOffset) - subOffset; + section.append(QByteArray(align, '\x00')).append(subSection); + subOffset += align + subSection.size(); + } + // Add newly constructed section to file body + + EFI_COMPRESSION_SECTION sectionHeader; + sectionHeader.Type = EFI_SECTION_COMPRESSION; + sectionHeader.CompressionType = EFI_NOT_COMPRESSED; + sectionHeader.UncompressedLength = section.size(); + uint32ToUint24(section.size() + sizeof(EFI_COMPRESSION_SECTION), sectionHeader.Size); + UINT32 align = ALIGN4(offset) - offset; + file.append(QByteArray(align, '\x00')) + .append(QByteArray((const char*) §ionHeader, sizeof(EFI_COMPRESSION_SECTION))) + .append(section); + offset += align + section.size(); + } + } + + QByteArray header = item->header(); + EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*) header.data(); + + // Correct file data checksum, if needed + if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) + { + UINT32 bufferSize = file.size() - sizeof(EFI_FFS_FILE_HEADER); + fileHeader->IntegrityCheck.Checksum.File = calculateChecksum8((UINT8*)(file.data() + sizeof(EFI_FFS_FILE_HEADER)), bufferSize); + } + + // Add file tail, if needed + if(fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) + file.append(!fileHeader->IntegrityCheck.TailReference); + + return header.append(file); +} + +bool FfsEngine::isCompressedFile(const QModelIndex& index) const +{ + if (!index.isValid()) + return false; + + TreeItem *item = static_cast(index.internalPointer()); + if(item->type() != FileItem) + return false; + + for (int i = 0; i < item->childCount(); i++) + { + if (item->child(i)->subtype() == CompressionSection) + return true; + } + + return false; +} \ No newline at end of file diff --git a/ffsengine.h b/ffsengine.h new file mode 100644 index 0000000..ca1d6ff --- /dev/null +++ b/ffsengine.h @@ -0,0 +1,67 @@ +/* ffsengine.h + +Copyright (c) 2013, Nikolaj Schlej. 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, +WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +*/ + +#ifndef __FFSENGINE_H__ +#define __FFSENGINE_H__ + +#include +#include +#include + +#include "basetypes.h" +#include "treemodel.h" + +class TreeModel; + +class FfsEngine : public QObject +{ + Q_OBJECT + +public: + FfsEngine(QObject *parent = 0); + ~FfsEngine(void); + + TreeModel* model() const; + QString message() const; + + UINT8 parseInputFile(const QByteArray & buffer); + UINT8* parseRegion(const QByteArray & flashImage, UINT8 regionSubtype, const UINT16 regionBase, const UINT16 regionLimit, QModelIndex & index); + UINT8 parseBios(const QByteArray & bios, const QModelIndex & parent = QModelIndex()); + INT32 findNextVolume(const QByteArray & bios, INT32 volumeOffset = 0); + UINT32 getVolumeSize(const QByteArray & bios, INT32 volumeOffset); + UINT8 parseVolume(const QByteArray & volume, UINT32 volumeBase, UINT8 revision, bool erasePolarity, const QModelIndex & parent = QModelIndex()); + UINT8 parseFile(const QByteArray & file, UINT8 revision, bool erasePolarity, const QModelIndex & parent = QModelIndex()); + + QModelIndex addTreeItem(const UINT8 type, const UINT8 subtype = 0, const UINT32 offset = 0, + const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), + const QModelIndex & parent = QModelIndex()); + bool removeItem(const QModelIndex &index); + + QByteArray header(const QModelIndex& index) const; + bool hasEmptyHeader(const QModelIndex& index) const; + QByteArray body(const QModelIndex& index) const; + bool hasEmptyBody(const QModelIndex& index) const; + + bool isCompressedFile(const QModelIndex& index) const; + QByteArray uncompressFile(const QModelIndex& index) const; + +private: + QString text; + TreeItem *rootItem; + TreeModel *treeModel; + void msg(const QString & message); + QModelIndex findParentOfType(UINT8 type, const QModelIndex& index) const; + bool setTreeItemName(const QString &data, const QModelIndex &index); + bool setTreeItemText(const QString &data, const QModelIndex &index); +}; + +#endif diff --git a/gbe.h b/gbe.h new file mode 100644 index 0000000..083fd26 --- /dev/null +++ b/gbe.h @@ -0,0 +1,36 @@ +/* gbe.h + +Copyright (c) 2013, Nikolaj Schlej. 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, +WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +*/ + +#ifndef __GBE_H__ +#define __GBE_H__ + +#include "basetypes.h" + +// Make sure we use right packing rules +#pragma pack(push,1) + +typedef struct { + UINT8 vendor[3]; + UINT8 device[3]; +} GBE_MAC; + +#define GBE_VERSION_OFFSET 10 + +typedef struct { + UINT8 id: 4; + UINT8 minor: 4; + UINT8 major; +} GBE_VERSION; + +// Restore previous packing rules +#pragma pack(pop) +#endif \ No newline at end of file diff --git a/me.h b/me.h new file mode 100644 index 0000000..130a3f9 --- /dev/null +++ b/me.h @@ -0,0 +1,34 @@ +/* me.h + +Copyright (c) 2013, Nikolaj Schlej. 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, +WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +*/ + +#ifndef __ME_H__ +#define __ME_H__ + +#include "basetypes.h" + +// Make sure we use right packing rules +#pragma pack(push,1) + +const QByteArray ME_VERSION_SIGNATURE("\x24\x4D\x4E\x32", 4); + +typedef struct { + UINT32 signature; + UINT32 reserved; // Unknown for me + UINT16 major; + UINT16 minor; + UINT16 bugfix; + UINT16 build; +} ME_VERSION; + +// Restore previous packing rules +#pragma pack(pop) +#endif \ No newline at end of file diff --git a/treeitem.cpp b/treeitem.cpp index e4288c5..42d3da5 100644 --- a/treeitem.cpp +++ b/treeitem.cpp @@ -12,14 +12,14 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. */ #include "treeitem.h" -#include "treeitemtypes.h" -TreeItem::TreeItem(const UINT8 type, const UINT8 subtype, const QString & name, const QString & typeName, const QString & subtypeName, +TreeItem::TreeItem(const UINT8 type, const UINT8 subtype, const UINT32 offset, const QString & name, const QString & typeName, const QString & subtypeName, const QString & text, const QString & info, const QByteArray & header, const QByteArray & body, TreeItem *parent) { itemType = type; itemSubtype = subtype; + itemOffset = offset; itemName = name; itemTypeName = typeName; itemSubtypeName = subtypeName; @@ -134,6 +134,11 @@ UINT8 TreeItem::subtype() return itemSubtype; } +UINT32 TreeItem::offset() +{ + return itemOffset; +} + QByteArray TreeItem::header() { return itemHeader; diff --git a/treeitem.h b/treeitem.h index 2a1bec5..7bf678b 100644 --- a/treeitem.h +++ b/treeitem.h @@ -23,13 +23,13 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. class TreeItem { public: - TreeItem(const UINT8 type, const UINT8 subtype = 0, const QString & name = QString(), const QString & typeName = QString(), const QString & subtypeName = QString(), + TreeItem(const UINT8 type, const UINT8 subtype = 0, const UINT32 offset = 0, const QString & name = QString(), const QString & typeName = QString(), const QString & subtypeName = QString(), const QString & text = QString(), const QString & info = QString(), const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), TreeItem *parent = 0); ~TreeItem(); void appendChild(TreeItem *item); void removeChild(TreeItem *item); - + TreeItem *child(int row); int childCount() const; int columnCount() const; @@ -39,6 +39,7 @@ public: UINT8 type(); UINT8 subtype(); + UINT32 offset(); QByteArray header(); QByteArray body(); QString info(); @@ -55,6 +56,7 @@ private: QList childItems; UINT8 itemType; UINT8 itemSubtype; + UINT32 itemOffset; QByteArray itemHeader; QByteArray itemBody; QString itemName; diff --git a/treemodel.cpp b/treemodel.cpp index 2ba8f23..1207af5 100644 --- a/treemodel.cpp +++ b/treemodel.cpp @@ -13,41 +13,17 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include "treeitem.h" #include "treemodel.h" -#include "ffs.h" -#include "descriptor.h" -TreeModel::TreeModel(QObject *parent) +TreeModel::TreeModel(TreeItem *root, QObject *parent) : QAbstractItemModel(parent) { - rootItem = new TreeItem(RootItem, 0, tr("Object"), tr("Type"), tr("Subtype"), tr("Text")); + rootItem = root; } TreeModel::~TreeModel() { - delete rootItem; } -bool TreeModel::hasEmptyHeader(const QModelIndex& index) -{ - if (!index.isValid()) - return true; - - TreeItem *item = static_cast(index.internalPointer()); - - return item->hasEmptyHeader(); -} - -bool TreeModel::hasEmptyBody(const QModelIndex& index) -{ - if (!index.isValid()) - return true; - - TreeItem *item = static_cast(index.internalPointer()); - - return item->hasEmptyBody(); -} - - int TreeModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) @@ -140,153 +116,6 @@ int TreeModel::rowCount(const QModelIndex &parent) const return parentItem->childCount(); } -QModelIndex TreeModel::addItem(UINT8 type, UINT8 subtype, const QByteArray &header, const QByteArray &body, const QModelIndex &parent) -{ - TreeItem *parentItem; - int parentColumn = 0; - - if (!parent.isValid()) - parentItem = rootItem; - else - { - parentItem = static_cast(parent.internalPointer()); - parentColumn = parent.column(); - } - - // All information extraction must be here - QString name, typeName, subtypeName, info; - EFI_CAPSULE_HEADER* capsuleHeader; - APTIO_CAPSULE_HEADER* aptioCapsuleHeader; - FLASH_DESCRIPTOR_MAP* descriptorMap; - //FLASH_DESCRIPTOR_COMPONENT_SECTION* componentSection; - //FLASH_DESCRIPTOR_REGION_SECTION* regionSection; - //FLASH_DESCRIPTOR_MASTER_SECTION* masterSection; - EFI_FIRMWARE_VOLUME_HEADER* volumeHeader; - EFI_FFS_FILE_HEADER* fileHeader; - //EFI_COMMON_SECTION_HEADER* sectionHeader; - - switch (type) - { - case RootItem: - // Do not allow to add another root item - return QModelIndex(); - break; - case CapsuleItem: - //typeName = tr("Capsule"); - switch (subtype) - { - case AptioCapsule: - name = tr("AMI Aptio capsule"); - aptioCapsuleHeader = (APTIO_CAPSULE_HEADER*) header.constData(); - info = tr("GUID: %1\nHeader size: %2\nFlags: %3\nImage size: %4") - .arg(guidToQString(aptioCapsuleHeader->CapsuleHeader.CapsuleGuid)) - .arg(aptioCapsuleHeader->CapsuleHeader.Flags, 8, 16, QChar('0')) - .arg(aptioCapsuleHeader->RomImageOffset, 4, 16, QChar('0')) - .arg(aptioCapsuleHeader->CapsuleHeader.CapsuleImageSize - aptioCapsuleHeader->RomImageOffset, 8, 16, QChar('0')); - //!TODO: more info about Aptio capsule - break; - case UefiCapsule: - name = tr("UEFI capsule"); - capsuleHeader = (EFI_CAPSULE_HEADER*) header.constData(); - info = tr("GUID: %1\nHeader size: %2\nFlags: %3\nImage size: %4") - .arg(guidToQString(capsuleHeader->CapsuleGuid)) - .arg(capsuleHeader->Flags, 8, 16, QChar('0')) - .arg(capsuleHeader->HeaderSize, 8, 16, QChar('0')) - .arg(capsuleHeader->CapsuleImageSize, 8, 16, QChar('0')); - break; - default: - name = tr("Unknown capsule"); - info = tr("GUID: %1\n").arg(guidToQString(*(EFI_GUID*)header.constData())); - break; - } - break; - case DescriptorItem: - name = tr("Descriptor"); - descriptorMap = (FLASH_DESCRIPTOR_MAP*) body.constData(); - info = tr("Flash chips: %1\nRegions: %2\nMasters: %3\nPCH straps:%4\nPROC straps: %5\nICC table entries: %6") - .arg(descriptorMap->NumberOfFlashChips + 1) // - .arg(descriptorMap->NumberOfRegions + 1) // Zero-based numbers in storage - .arg(descriptorMap->NumberOfMasters + 1) // - .arg(descriptorMap->NumberOfPchStraps) - .arg(descriptorMap->NumberOfProcStraps) - .arg(descriptorMap->NumberOfIccTableEntries); - //!TODO: more info about descriptor - break; - case RegionItem: - typeName = tr("Region"); - info = tr("Size: %1").arg(body.size(), 8, 16, QChar('0')); - //!TODO: more info about GbE and ME regions - switch (subtype) - { - case GbeRegion: - name = tr("GbE region"); - break; - case MeRegion: - name = tr("ME region"); - break; - case BiosRegion: - name = tr("BIOS region"); - break; - case PdrRegion: - name = tr("PDR region"); - break; - default: - name = tr("Unknown region"); - break; - } - break; - case PaddingItem: - name = tr("Padding"); - info = tr("Size: %1").arg(body.size(), 8, 16, QChar('0')); - break; - case VolumeItem: - typeName = tr("Volume"); - // Parse volume header to determine its revision and file system - volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) header.constData(); - name = guidToQString(volumeHeader->FileSystemGuid); - subtypeName = tr("Revision %1").arg(volumeHeader->Revision); - info = tr("Size: %1\nSignature: %2\nAttributes: %3\nHeader size: %4") - .arg(volumeHeader->FvLength, 8, 16, QChar('0')) - .arg(volumeHeader->Signature, 8, 16, QChar('0')) - .arg(volumeHeader->Attributes, 8, 16, QChar('0')) - .arg(volumeHeader->HeaderLength, 4, 16, QChar('0')); - break; - case FileItem: - typeName = tr("File"); - // Parse file header to determine its GUID and type - fileHeader = (EFI_FFS_FILE_HEADER*) header.constData(); - name = guidToQString(fileHeader->Name); - subtypeName = fileTypeToQString(subtype); - info = tr("Type: %1\nAttributes: %2\nSize: %3\nState: %4") - .arg(fileHeader->Type, 2, 16, QChar('0')) - .arg(fileHeader->Attributes, 2, 16, QChar('0')) - .arg(uint24ToUint32(fileHeader->Size), 6, 16, QChar('0')) - .arg(fileHeader->State, 2, 16, QChar('0')); - break; - case SectionItem: - typeName = tr("Section"); - name = sectionTypeToQString(subtype) + tr(" section"); - info = tr("Size: %1").arg(body.size(), 8, 16, QChar('0')); - //!TODO: add more specific info for all section types with uncommon headers - // Set name of file - if (subtype == UserInterfaceSection) - { - QString text = QString::fromUtf16((const ushort*)body.constData()); - setItemText(text, findParentOfType(FileItem, parent)); - } - break; - default: - name = tr("Unknown"); - break; - } - - emit layoutAboutToBeChanged(); - TreeItem *item = new TreeItem(type, subtype, name, typeName, subtypeName, "", info, header, body, parentItem); - parentItem->appendChild(item); - emit layoutChanged(); - return createIndex(parentItem->childCount() - 1, parentColumn, item); -} - bool TreeModel::setItemName(const QString &data, const QModelIndex &index) { if(!index.isValid()) @@ -309,46 +138,24 @@ bool TreeModel::setItemText(const QString &data, const QModelIndex &index) return true; } -bool TreeModel::removeItem(const QModelIndex &index) +QModelIndex TreeModel::addItem(const UINT8 type, const UINT8 subtype, const UINT32 offset, const QString & name, const QString & typeName, + const QString & subtypeName, const QString & text, const QString & info, + const QByteArray & header, const QByteArray & body, const QModelIndex & parent) { - TreeItem *item = static_cast(index.internalPointer()); - item->parent()->removeChild(item); - delete item; - return true; -} + TreeItem *parentItem; + int parentColumn = 0; -QModelIndex TreeModel::findParentOfType(UINT8 type, const QModelIndex& index) -{ - if(!index.isValid()) - return QModelIndex(); - - TreeItem *item; - QModelIndex parent = index; - - for(item = static_cast(parent.internalPointer()); - item != NULL && item != rootItem && item->type() != type; - item = static_cast(parent.internalPointer())) - parent = parent.parent(); - if (item != NULL && item != rootItem) - return parent; + if (!parent.isValid()) + parentItem = rootItem; + else + { + parentItem = static_cast(parent.internalPointer()); + parentColumn = parent.column(); + } - return QModelIndex(); -} - -QByteArray TreeModel::header(const QModelIndex& index) -{ - if (!index.isValid()) - return QByteArray(); - - TreeItem *item = static_cast(index.internalPointer()); - return item->header(); -} - -QByteArray TreeModel::body(const QModelIndex& index) -{ - if (!index.isValid()) - return QByteArray(); - - TreeItem *item = static_cast(index.internalPointer()); - return item->body(); + emit layoutAboutToBeChanged(); + TreeItem *item = new TreeItem(type, subtype, offset, name, typeName, subtypeName, text, info, header, body, parentItem); + parentItem->appendChild(item); + emit layoutChanged(); + return createIndex(parentItem->childCount() - 1, parentColumn, item); } \ No newline at end of file diff --git a/treemodel.h b/treemodel.h index 4eba0a3..7bc91cf 100644 --- a/treemodel.h +++ b/treemodel.h @@ -20,7 +20,6 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include #include "basetypes.h" -#include "treeitemtypes.h" class TreeItem; @@ -29,7 +28,7 @@ class TreeModel : public QAbstractItemModel Q_OBJECT public: - TreeModel(QObject *parent = 0); + TreeModel(TreeItem *root, QObject *parent = 0); ~TreeModel(); QVariant data(const QModelIndex &index, int role) const; @@ -42,18 +41,14 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; - QModelIndex addItem(UINT8 type, UINT8 subtype = 0, const QByteArray &header = QByteArray(), const QByteArray &body = QByteArray(), const QModelIndex &parent = QModelIndex()); - bool removeItem(const QModelIndex &index); - - QByteArray header(const QModelIndex& index); - bool hasEmptyHeader(const QModelIndex& index); - QByteArray body(const QModelIndex& index); - bool hasEmptyBody(const QModelIndex& index); - -private: - QModelIndex findParentOfType(UINT8 type, const QModelIndex& index); bool setItemName(const QString &data, const QModelIndex &index); bool setItemText(const QString &data, const QModelIndex &index); + + QModelIndex addItem(const UINT8 type, const UINT8 subtype = 0, const UINT32 offset = 0, const QString & name = QString(), + const QString & typeName = QString(), const QString & subtypeName = QString(), const QString & text = QString(), + const QString & info = QString(), const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), const QModelIndex & parent = QModelIndex()); + +private: TreeItem *rootItem; }; diff --git a/uefitool.cpp b/uefitool.cpp index 2830783..cea9158 100644 --- a/uefitool.cpp +++ b/uefitool.cpp @@ -12,7 +12,6 @@ */ #include "uefitool.h" -#include "treeitemtypes.h" #include "ui_uefitool.h" UEFITool::UEFITool(QWidget *parent) : @@ -20,13 +19,13 @@ UEFITool::UEFITool(QWidget *parent) : ui(new Ui::UEFITool) { ui->setupUi(this); - treeModel = NULL; - - // Signal-slot connections - connect(ui->fromFileButton, SIGNAL(clicked()), this, SLOT(openImageFile())); - connect(ui->exportAllButton, SIGNAL(clicked()), this, SLOT(saveAll())); - connect(ui->exportBodyButton, SIGNAL(clicked()), this, SLOT(saveBody())); + ffsEngine = NULL; + //Connect + connect(ui->actionOpenImageFile, SIGNAL(triggered()), this, SLOT(openImageFile())); + connect(ui->actionExtract, SIGNAL(triggered()), this, SLOT(saveAll())); + connect(ui->actionExtractBody, SIGNAL(triggered()), this, SLOT(saveBody())); + connect(ui->actionExtractUncompressed, SIGNAL(triggered()), this, SLOT(saveUncompressedFile())); // Enable Drag-and-Drop actions this->setAcceptDrops(true); @@ -37,7 +36,7 @@ UEFITool::UEFITool(QWidget *parent) : UEFITool::~UEFITool() { delete ui; - delete treeModel; + delete ffsEngine; } void UEFITool::init() @@ -45,19 +44,26 @@ void UEFITool::init() // Clear UI components ui->debugEdit->clear(); ui->infoEdit->clear(); - ui->exportAllButton->setDisabled(true); - ui->exportBodyButton->setDisabled(true); - // Make new tree model - TreeModel * newModel = new TreeModel(this); - ui->structureTreeView->setModel(newModel); - if (treeModel) - delete treeModel; - treeModel = newModel; - // Show info after selection the item in tree view + + // Disable all actions except openImageFile + ui->actionExtract->setDisabled(true); + ui->actionExtractBody->setDisabled(true); + ui->actionExtractUncompressed->setDisabled(true); + ui->actionReplace->setDisabled(true); + ui->actionDelete->setDisabled(true); + ui->actionReplaceWithPadding->setDisabled(true); + ui->actionInsertBefore->setDisabled(true); + ui->actionInsertAfter->setDisabled(true); + + // Make new ffsEngine + ffsEngine = new FfsEngine(this); + ui->structureTreeView->setModel(ffsEngine->model()); + + // Connect + connect(ui->structureTreeView, SIGNAL(collapsed(const QModelIndex &)), this, SLOT(resizeTreeViewColums(void))); + connect(ui->structureTreeView, SIGNAL(expanded(const QModelIndex &)), this, SLOT(resizeTreeViewColums(void))); connect(ui->structureTreeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(populateUi(const QModelIndex &))); - //connect(ui->structureTreeView, SIGNAL(collapsed(const QModelIndex &)), this, SLOT(resizeTreeViewColums(void))); - connect(ui->structureTreeView, SIGNAL(expanded(const QModelIndex &)), this, SLOT(resizeTreeViewColums(void))); resizeTreeViewColums(); } @@ -66,13 +72,14 @@ void UEFITool::populateUi(const QModelIndex ¤t/*, const QModelIndex &previ //!TODO: make widget currentIndex = current; ui->infoEdit->setPlainText(current.data(Qt::UserRole).toString()); - ui->exportAllButton->setDisabled(treeModel->hasEmptyBody(current) && treeModel->hasEmptyHeader(current)); - ui->exportBodyButton->setDisabled(treeModel->hasEmptyHeader(current)); + ui->actionExtract->setDisabled(ffsEngine->hasEmptyBody(current) && ffsEngine->hasEmptyHeader(current)); + ui->actionExtractBody->setDisabled(ffsEngine->hasEmptyHeader(current)); + ui->actionExtractUncompressed->setEnabled(ffsEngine->isCompressedFile(current)); } void UEFITool::resizeTreeViewColums(/*const QModelIndex &index*/) { - int count = treeModel->columnCount(); + int count = ffsEngine->model()->columnCount(); for(int i = 0; i < count; i++) ui->structureTreeView->resizeColumnToContents(i); } @@ -105,17 +112,19 @@ void UEFITool::openImageFile(QString path) inputFile.close(); init(); - UINT8 result = parseInputFile(buffer); + UINT8 result = ffsEngine->parseInputFile(buffer); if (result) - debug(tr("Opened file can't be parsed as UEFI image (%1)").arg(result)); + ui->statusBar->showMessage(tr("Opened file can't be parsed as UEFI image (%1)").arg(result)); else ui->statusBar->showMessage(tr("Opened: %1").arg(fileInfo.fileName())); + + ui->debugEdit->appendPlainText(ffsEngine->message()); resizeTreeViewColums(); } void UEFITool::saveAll() { - QString path = QFileDialog::getSaveFileName(this, tr("Save header to binary file"),".","Binary files (*.bin);;All files (*.*)"); + QString path = QFileDialog::getSaveFileName(this, tr("Save selected item to binary file"),".","Binary files (*.bin);;All files (*.*)"); QFile outputFile; outputFile.setFileName(path); @@ -125,13 +134,13 @@ void UEFITool::saveAll() return; } - outputFile.write(treeModel->header(currentIndex) + treeModel->body(currentIndex)); + outputFile.write(ffsEngine->header(currentIndex) + ffsEngine->body(currentIndex)); outputFile.close(); } void UEFITool::saveBody() { - QString path = QFileDialog::getSaveFileName(this, tr("Save body to binary file"),".","Binary files (*.bin);;All files (*.*)"); + QString path = QFileDialog::getSaveFileName(this, tr("Save selected item without header to binary file"),".","Binary files (*.bin);;All files (*.*)"); QFile outputFile; outputFile.setFileName(path); @@ -141,7 +150,23 @@ void UEFITool::saveBody() return; } - outputFile.write(treeModel->body(currentIndex)); + outputFile.write(ffsEngine->body(currentIndex)); + outputFile.close(); +} + +void UEFITool::saveUncompressedFile() +{ + QString path = QFileDialog::getSaveFileName(this, tr("Save selected FFS file as uncompressed to binary file"),".","FFS files (*.ffs);;All files (*.*)"); + + QFile outputFile; + outputFile.setFileName(path); + if (!outputFile.open(QFile::WriteOnly)) + { + ui->statusBar->showMessage(tr("Can't open file for writing. Check file permissions.")); + return; + } + + outputFile.write(ffsEngine->uncompressFile(currentIndex)); outputFile.close(); } @@ -177,769 +202,3 @@ void UEFITool::dropEvent(QDropEvent* event) openImageFile(path); } -void UEFITool::debug(const QString & text) -{ - //!TODO: log to separate debug window - ui->debugEdit->appendPlainText(text); -} - -QModelIndex UEFITool::addTreeItem(UINT8 type, UINT8 subtype, - const QByteArray & header, const QByteArray & body, const QModelIndex & parent) -{ - return treeModel->addItem(type, subtype, header, body, parent); -} - -UINT8 UEFITool::parseInputFile(const QByteArray & buffer) -{ - UINT32 capsuleHeaderSize = 0; - FLASH_DESCRIPTOR_HEADER* descriptorHeader = NULL; - QByteArray flashImage; - QByteArray bios; - QModelIndex index; - - // Check buffer for being normal EFI capsule header - if (buffer.startsWith(EFI_CAPSULE_GUID)) { - EFI_CAPSULE_HEADER* capsuleHeader = (EFI_CAPSULE_HEADER*) buffer.constData(); - capsuleHeaderSize = capsuleHeader->HeaderSize; - QByteArray header = buffer.left(capsuleHeaderSize); - QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize); - index = addTreeItem(CapsuleItem, UefiCapsule, header, body); - } - - // Check buffer for being extended Aptio capsule header - else if (buffer.startsWith(APTIO_CAPSULE_GUID)) { - APTIO_CAPSULE_HEADER* aptioCapsuleHeader = (APTIO_CAPSULE_HEADER*) buffer.constData(); - capsuleHeaderSize = aptioCapsuleHeader->RomImageOffset; - QByteArray header = buffer.left(capsuleHeaderSize); - QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize); - index = addTreeItem(CapsuleItem, AptioCapsule, header, body); - } - - // Skip capsule header to have flash chip image - flashImage = buffer.right(buffer.size() - capsuleHeaderSize); - - // Check buffer for being Intel flash descriptor - descriptorHeader = (FLASH_DESCRIPTOR_HEADER*) flashImage.constData(); - // Check descriptor signature - if (descriptorHeader->Signature == FLASH_DESCRIPTOR_SIGNATURE) { - FLASH_DESCRIPTOR_MAP* descriptorMap; - FLASH_DESCRIPTOR_REGION_SECTION* regionSection; - - // Store the beginning of descriptor as descriptor base address - UINT8* descriptor = (UINT8*) flashImage.constData(); - UINT8* gbeRegion = NULL; - UINT8* meRegion = NULL; - UINT8* biosRegion = NULL; - UINT8* pdrRegion = NULL; - - // Check for buffer size to be greater or equal to descriptor region size - if (flashImage.size() < FLASH_DESCRIPTOR_SIZE) { - debug(tr("Input file is smaller then mininum descriptor size of 4KB")); - return ERR_INVALID_FLASH_DESCRIPTOR; - } - - // Parse descriptor map - descriptorMap = (FLASH_DESCRIPTOR_MAP*) (flashImage.constData() + sizeof(FLASH_DESCRIPTOR_HEADER)); - regionSection = (FLASH_DESCRIPTOR_REGION_SECTION*) calculateAddress8(descriptor, descriptorMap->RegionBase); - - // Add tree item - QByteArray header = flashImage.left(sizeof(FLASH_DESCRIPTOR_HEADER)); - QByteArray body = flashImage.mid(sizeof(FLASH_DESCRIPTOR_HEADER), FLASH_DESCRIPTOR_SIZE - sizeof(FLASH_DESCRIPTOR_HEADER)); - index = addTreeItem(DescriptorItem, 0, header, body, index); - - // Parse region section - QModelIndex gbeIndex(index); - QModelIndex meIndex(index); - QModelIndex biosIndex(index); - QModelIndex pdrIndex(index); - gbeRegion = parseRegion(flashImage, GbeRegion, regionSection->GbeBase, regionSection->GbeLimit, gbeIndex); - meRegion = parseRegion(flashImage, MeRegion, regionSection->MeBase, regionSection->MeLimit, meIndex); - biosRegion = parseRegion(flashImage, BiosRegion, regionSection->BiosBase, regionSection->BiosLimit, biosIndex); - pdrRegion = parseRegion(flashImage, PdrRegion, regionSection->PdrBase, regionSection->PdrLimit, pdrIndex); - - // Parse complete - //!TODO: show some info about GbE, ME and PDR regions if found - - // Exit if no bios region found - if (!biosRegion) { - debug(tr("BIOS region not found")); - return ERR_BIOS_REGION_NOT_FOUND; - } - - index = biosIndex; - bios = QByteArray::fromRawData((const char*) biosRegion, calculateRegionSize(regionSection->BiosBase, regionSection->BiosLimit)); - } - else { - bios = buffer; - } - - // We are in the beginning of BIOS space, where firmware volumes are - // Parse BIOS space - - return parseBios(bios, index); -} - -UINT8* UEFITool::parseRegion(const QByteArray & flashImage, UINT8 regionSubtype, const UINT16 regionBase, const UINT16 regionLimit, QModelIndex & index) -{ - // Check for empty region - if (!regionLimit) - return NULL; - - // Calculate region size - UINT32 regionSize = calculateRegionSize(regionBase, regionLimit); - - // Populate descriptor map - FLASH_DESCRIPTOR_MAP* descriptor_map = (FLASH_DESCRIPTOR_MAP*) (flashImage.constData() + sizeof(FLASH_DESCRIPTOR_HEADER)); - - // Determine presence of 2 flash chips - bool twoChips = descriptor_map->NumberOfFlashChips; - - // construct region name - //!TODO: make this to regionTypeToQString(const UINT8 type) in descriptor.cpp - QString regionName; - switch (regionSubtype) - { - case GbeRegion: - regionName = "GbE"; - break; - case MeRegion: - regionName = "ME"; - break; - case BiosRegion: - regionName = "Bios"; - break; - case PdrRegion: - regionName = "PDR"; - break; - default: - regionName = "Unknown"; - debug(tr("Unknown region type")); - }; - - // Check region base to be in buffer - if (regionBase * 0x1000 >= flashImage.size()) - { - debug(tr("%1 region stored in descriptor not found").arg(regionName)); - if (twoChips) - debug(tr("Two flash chips installed, so it could be in another flash chip\n" - "Make a dump from another flash chip and open it to view information about %1 region").arg(regionName)); - else - debug(tr("One flash chip installed, so it is an error caused by damaged or incomplete dump")); - debug(tr("Absence of %1 region assumed").arg(regionName)); - return NULL; - } - - // Check region to be fully present in buffer - else if (regionBase * 0x1000 + regionSize > flashImage.size()) - { - debug(tr("%s region stored in descriptor overlaps the end of opened file").arg(regionName)); - if (twoChips) - debug(tr("Two flash chips installed, so it could be in another flash chip\n" - "Make a dump from another flash chip and open it to view information about %1 region").arg(regionName)); - else - debug(tr("One flash chip installed, so it is an error caused by damaged or incomplete dump")); - debug(tr("Absence of %1 region assumed\n").arg(regionName)); - return NULL; - } - - // Calculate region address - UINT8* region = calculateAddress16((UINT8*) flashImage.constData(), regionBase); - - // Add tree item - QByteArray body = flashImage.mid(regionBase * 0x1000, regionSize); - index = addTreeItem(RegionItem, regionSubtype, QByteArray(), body, index); - - return region; -} - -UINT8 UEFITool::parseBios(const QByteArray & bios, const QModelIndex & parent) -{ - // Search for first volume - INT32 prevVolumeIndex = getNextVolumeIndex(bios); - - // No volumes found - if (prevVolumeIndex < 0) { - return ERR_VOLUMES_NOT_FOUND; - } - - // First volume is not at the beginning of bios space - if (prevVolumeIndex > 0) { - QByteArray padding = bios.left(prevVolumeIndex); - addTreeItem(PaddingItem, 0, QByteArray(), padding, parent); - } - - // Search for and parse all volumes - INT32 volumeIndex; - UINT32 prevVolumeSize; - for (volumeIndex = prevVolumeIndex, prevVolumeSize = 0; - volumeIndex >= 0; - prevVolumeIndex = volumeIndex, prevVolumeSize = getVolumeSize(bios, volumeIndex), volumeIndex = getNextVolumeIndex(bios, volumeIndex + prevVolumeSize)) - { - // Padding between volumes - if (volumeIndex > prevVolumeIndex + prevVolumeSize) { - UINT32 size = volumeIndex - prevVolumeIndex - prevVolumeSize; - QByteArray padding = bios.mid(prevVolumeIndex + prevVolumeSize, size); - addTreeItem(PaddingItem, 0, QByteArray(), padding, parent); - } - - // Populate volume header - EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) (bios.constData() + volumeIndex); - - //Check that volume is fully present in input - if (volumeIndex + volumeHeader->FvLength > bios.size()) { - debug(tr("Volume overlaps the end of input buffer")); - return ERR_INVALID_VOLUME; - } - - // Check volume revision and alignment - UINT32 alignment; - if (volumeHeader->Revision == 1) { - // Aquire alignment bits - bool alignmentCap = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_CAP; - bool alignment2 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_2; - bool alignment4 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_4; - bool alignment8 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_8; - bool alignment16 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_16; - bool alignment32 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_32; - bool alignment64 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_64; - bool alignment128 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_128; - bool alignment256 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_256; - bool alignment512 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_512; - bool alignment1k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_1K; - bool alignment2k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_2K; - bool alignment4k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_4K; - bool alignment8k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_8K; - bool alignment16k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_16K; - bool alignment32k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_32K; - bool alignment64k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_64K; - - // Check alignment setup - if (!alignmentCap && - ( alignment2 || alignment4 || alignment8 || alignment16 - || alignment32 || alignment64 || alignment128 || alignment256 - || alignment512 || alignment1k || alignment2k || alignment4k - || alignment8k || alignment16k || alignment32k || alignment64k)) - debug("Incompatible revision 1 volume alignment setup"); - - // Assume that smaller alignment value consumes greater - alignment = 0x01; - if (alignment2) - alignment = 0x02; - else if (alignment4) - alignment = 0x04; - else if (alignment8) - alignment = 0x08; - else if (alignment16) - alignment = 0x10; - else if (alignment32) - alignment = 0x20; - else if (alignment64) - alignment = 0x40; - else if (alignment128) - alignment = 0x80; - else if (alignment256) - alignment = 0x100; - else if (alignment512) - alignment = 0x200; - else if (alignment1k) - alignment = 0x400; - else if (alignment2k) - alignment = 0x800; - else if (alignment4k) - alignment = 0x1000; - else if (alignment8k) - alignment = 0x2000; - else if (alignment16k) - alignment = 0x4000; - else if (alignment32k) - alignment = 0x8000; - else if (alignment64k) - alignment = 0x10000; - - // Check alignment - if (volumeIndex % alignment) { - debug(tr("Unaligned revision 1 volume")); - } - } - else if (volumeHeader->Revision == 2) { - // Aquire alignment - alignment = pow(2, (volumeHeader->Attributes & EFI_FVB2_ALIGNMENT) >> 16); - - // Check alignment - if (volumeIndex % alignment) { - debug(tr("Unaligned revision 2 volume")); - } - } - else - debug(tr("Unknown volume revision (%1)").arg(volumeHeader->Revision)); - - // Check filesystem GUID to be known - // Do not parse volume with unknown FFS, because parsing will fail - bool parseCurrentVolume = true; - // FFS GUID v1 - if (QByteArray((const char*) &volumeHeader->FileSystemGuid, sizeof(EFI_GUID)) == EFI_FIRMWARE_FILE_SYSTEM_GUID) { - // Code can be added here - } - // FFS GUID v2 - else if (QByteArray((const char*) &volumeHeader->FileSystemGuid, sizeof(EFI_GUID)) == EFI_FIRMWARE_FILE_SYSTEM2_GUID) { - // Code can be added here - } - // Other GUID - else { - //info = info.append(tr("File system: unknown\n")); - debug(tr("Unknown file system (%1)").arg(guidToQString(volumeHeader->FileSystemGuid))); - parseCurrentVolume = false; - } - - // Check attributes - // Determine erase polarity - bool erasePolarity = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY; - - // Check header checksum by recalculating it - if (!calculateChecksum16((UINT8*) volumeHeader, volumeHeader->HeaderLength)) { - debug(tr("Volume header checksum is invalid")); - } - - // Check for presence of extended header, only if header revision is not 1 - UINT32 headerSize; - if (volumeHeader->Revision > 1 && volumeHeader->ExtHeaderOffset) { - EFI_FIRMWARE_VOLUME_EXT_HEADER* extendedHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER*) ((UINT8*) volumeHeader + volumeHeader->ExtHeaderOffset); - headerSize = volumeHeader->ExtHeaderOffset + extendedHeader->ExtHeaderSize; - } else { - headerSize = volumeHeader->HeaderLength; - } - - // Adding tree item - QByteArray header = bios.mid(volumeIndex, headerSize); - QByteArray body = bios.mid(volumeIndex + headerSize, volumeHeader->FvLength - headerSize); - QModelIndex index = addTreeItem(VolumeItem, 0, header, body, parent); - - // Parse volume - if (parseCurrentVolume) { - UINT32 result = parseVolume(bios.mid(volumeIndex + headerSize, volumeHeader->FvLength - headerSize), headerSize, volumeHeader->Revision, erasePolarity, index); - if (result) - debug(tr("Volume parsing failed (%1)").arg(result)); - } - } - - return ERR_SUCCESS; -} - -INT32 UEFITool::getNextVolumeIndex(const QByteArray & bios, INT32 volumeIndex) -{ - if (volumeIndex < 0) - return -1; - - INT32 nextIndex = bios.indexOf(EFI_FV_SIGNATURE, volumeIndex); - if (nextIndex < EFI_FV_SIGNATURE_OFFSET) { - return -1; - } - - return nextIndex - EFI_FV_SIGNATURE_OFFSET; -} - -UINT32 UEFITool::getVolumeSize(const QByteArray & bios, INT32 volumeIndex) -{ - // Populate volume header - EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) (bios.constData() + volumeIndex); - - // Check volume signature - if (QByteArray((const char*) &volumeHeader->Signature, sizeof(volumeHeader->Signature)) != EFI_FV_SIGNATURE) - return 0; - return volumeHeader->FvLength; -} - -UINT8 UEFITool::parseVolume(const QByteArray & volume, UINT32 volumeBase, UINT8 revision, bool erasePolarity, const QModelIndex & parent) -{ - // Construct empty byte based on erasePolarity value - // Native char type is used because QByteArray.count() takes it - char empty = erasePolarity ? '\xFF' : '\x00'; - - // Search for and parse all files - INT32 fileIndex = 0; - while (fileIndex >= 0) { - EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*) (volume.constData() + fileIndex); - QByteArray file = volume.mid(fileIndex, uint24ToUint32(fileHeader->Size)); - QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER)); - - // Check file size to at least sizeof(EFI_FFS_FILE_HEADER) - if (file.size() < sizeof(EFI_FFS_FILE_HEADER)) - { - debug(tr("File with invalid size")); - return ERR_INVALID_FILE; - } - - // We are at empty space in the end of volume - if (header.count(empty) == header.size()) { - QByteArray body = volume.right(volume.size() - fileIndex); - addTreeItem(PaddingItem, 0, QByteArray(), body, parent); - break; - } - - // Check header checksum - QByteArray tempHeader = header; - EFI_FFS_FILE_HEADER* tempFileHeader = (EFI_FFS_FILE_HEADER*) (tempHeader.data()); - tempFileHeader->IntegrityCheck.Checksum.Header = 0; - tempFileHeader->IntegrityCheck.Checksum.File = 0; - UINT8 calculated = calculateChecksum8((UINT8*) tempFileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1); - if (fileHeader->IntegrityCheck.Checksum.Header != calculated) - { - debug(tr("%1: stored header checksum %2 differs from calculated %3") - .arg(guidToQString(fileHeader->Name)) - .arg(fileHeader->IntegrityCheck.Checksum.Header, 2, 16, QChar('0')) - .arg(calculated, 2, 16, QChar('0'))); - } - - // Check data checksum, if no tail was found - // Data checksum must be calculated - if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) { - UINT32 bufferSize = file.size() - sizeof(EFI_FFS_FILE_HEADER); - // Exclude file tail from data checksum calculation - if(revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) - bufferSize -= sizeof(UINT16); - calculated = calculateChecksum8((UINT8*)(file.constData() + sizeof(EFI_FFS_FILE_HEADER)), bufferSize); - if (fileHeader->IntegrityCheck.Checksum.File != calculated) { - debug(tr("%1: stored data checksum %2 differs from calculated %3") - .arg(guidToQString(fileHeader->Name)) - .arg(fileHeader->IntegrityCheck.Checksum.File, 2, 16, QChar('0')) - .arg(calculated, 2, 16, QChar('0'))); - } - } - // Data checksum must be one of predefined values - else { - if (fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM &&fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM2) { - debug(tr("%1: stored data checksum %2 differs from standard value") - .arg(guidToQString(fileHeader->Name)) - .arg(fileHeader->IntegrityCheck.Checksum.File, 2, 16, QChar('0'))); - } - } - - // Check file alignment - UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3]; - UINT32 alignment = pow(2, alignmentPower); - if ((volumeBase + fileIndex + sizeof(EFI_FFS_FILE_HEADER)) % alignment) { - debug(tr("%1: unaligned file").arg(guidToQString(fileHeader->Name))); - } - - // Get file body - QByteArray body = file.right(file.size() - sizeof(EFI_FFS_FILE_HEADER)); - // For files in Revision 1 volumes, check for file tail presence - if (revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) - { - //Check file tail; - UINT16* tail = (UINT16*) body.right(sizeof(UINT16)).constData(); - if (!fileHeader->IntegrityCheck.TailReference == *tail) - debug(tr("%1: file tail value %2 is not a bitwise not of %3 stored in file header") - .arg(guidToQString(fileHeader->Name)) - .arg(*tail, 4, 16, QChar('0')) - .arg(fileHeader->IntegrityCheck.TailReference, 4, 16, QChar('0'))); - - // Remove tail from file body - body = body.left(body.size() - sizeof(UINT16)); - } - - // Parse current file by default - bool parseCurrentFile = true; - // Raw files can hide volumes inside them - // So try to parse them as bios space - bool parseAsBios = false; - - // Check file type - //!TODO: add more file specific checks - switch (fileHeader->Type) - { - case EFI_FV_FILETYPE_ALL: - parseAsBios = true; - break; - case EFI_FV_FILETYPE_RAW: - parseAsBios = true; - break; - case EFI_FV_FILETYPE_FREEFORM: - break; - case EFI_FV_FILETYPE_SECURITY_CORE: - break; - case EFI_FV_FILETYPE_PEI_CORE: - break; - case EFI_FV_FILETYPE_DXE_CORE: - break; - case EFI_FV_FILETYPE_PEIM: - break; - case EFI_FV_FILETYPE_DRIVER: - break; - case EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER: - break; - case EFI_FV_FILETYPE_APPLICATION: - break; - case EFI_FV_FILETYPE_SMM: - break; - case EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE: - break; - case EFI_FV_FILETYPE_COMBINED_SMM_DXE: - break; - case EFI_FV_FILETYPE_SMM_CORE: - break; - case EFI_FV_FILETYPE_PAD: - parseCurrentFile = false; - break; - default: - parseCurrentFile = false; - debug(tr("Unknown file type (%1)").arg(fileHeader->Type, 2, 16, QChar('0'))); - }; - - // Check for empty file - if (body.count(empty) == body.size()) - { - // No need to parse empty files - parseCurrentFile = false; - } - - // Add tree item - QModelIndex index = addTreeItem(FileItem, fileHeader->Type, header, body, parent); - - // Parse file - if (parseCurrentFile) { - if (parseAsBios) { - UINT32 result = parseBios(body, index); - if (result && result != ERR_VOLUMES_NOT_FOUND) - debug(tr("Parse file as BIOS failed (%1)").arg(result)); - } - else { - UINT32 result = parseFile(body, revision, erasePolarity, index); - if (result) - debug(tr("Parse file as FFS failed (%1)").arg(result)); - } - } - - // Move to next file - fileIndex += file.size(); - fileIndex = ALIGN8(fileIndex); - - // Exit from loop if no files left - if (fileIndex >= volume.size()) - fileIndex = -1; - } - - return ERR_SUCCESS; -} - -UINT8 UEFITool::parseFile(const QByteArray & file, UINT8 revision, bool erasePolarity, const QModelIndex & parent) -{ - // Search for and parse all sections - INT32 sectionIndex = 0; - while(sectionIndex >= 0) - { - EFI_COMMON_SECTION_HEADER* sectionHeader = (EFI_COMMON_SECTION_HEADER*) (file.constData() + sectionIndex); - UINT32 sectionSize = uint24ToUint32(sectionHeader->Size); - - // This declarations must be here because of the nature of switch statement - EFI_COMPRESSION_SECTION* compressedSectionHeader; - EFI_GUID_DEFINED_SECTION* guidDefinedSectionHeader; - QByteArray header; - QByteArray body; - UINT32 decompressedSize; - UINT32 scratchSize; - UINT8* decompressed; - UINT8* scratch; - VOID* data; - UINT32 dataSize; - QModelIndex index; - UINT32 result; - UINT32 shittySectionSize; - EFI_COMMON_SECTION_HEADER* shittySectionHeader; - - switch (sectionHeader->Type) - { - // Encapsulated sections - case EFI_SECTION_COMPRESSION: - compressedSectionHeader = (EFI_COMPRESSION_SECTION*) sectionHeader; - header = file.mid(sectionIndex, sizeof(EFI_COMPRESSION_SECTION)); - - // Try to decompress this section - switch (compressedSectionHeader->CompressionType) - { - case EFI_NOT_COMPRESSED: - body = file.mid(sectionIndex + sizeof(EFI_COMPRESSION_SECTION), compressedSectionHeader->UncompressedLength); - index = addTreeItem(SectionItem, CompressionSection, header, body, parent); - // Parse stored file - result = parseFile(body, revision, erasePolarity, index); - if (result) - debug(tr("Stored section can not be parsed as file (%1)").arg(result)); - break; - case EFI_STANDARD_COMPRESSION: - //Must be Tiano for all revisions, needs checking - body = file.mid(sectionIndex + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - - // Get buffer sizes - data = (VOID*) (file.constData() + sectionIndex + sizeof(EFI_COMPRESSION_SECTION)); - dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION); - if (TianoGetInfo(data, dataSize, &decompressedSize, &scratchSize) != ERR_SUCCESS - || decompressedSize != compressedSectionHeader->UncompressedLength) - debug(tr("TianoGetInfo failed")); - else { - decompressed = new UINT8[decompressedSize]; - scratch = new UINT8[scratchSize]; - // Decompress section data - if (TianoDecompress(data, dataSize, decompressed, decompressedSize, scratch, scratchSize) != ERR_SUCCESS) - debug(tr("TianoDecompress failed")); - else - { - body = QByteArray::fromRawData((const char*) decompressed, decompressedSize); - // Parse stored file - result = parseFile(body, revision, erasePolarity, index); - if (result) - debug(tr("Compressed section with Tiano compression can not be parsed as file (%1)").arg(result)); - } - - delete[] decompressed; - delete[] scratch; - } - break; - case EFI_CUSTOMIZED_COMPRESSION: - body = file.mid(sectionIndex + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - - // Get buffer sizes - data = (VOID*) (file.constData() + sectionIndex + sizeof(EFI_COMPRESSION_SECTION)); - dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION); - if (LzmaGetInfo(data, dataSize, &decompressedSize) != ERR_SUCCESS - || decompressedSize != compressedSectionHeader->UncompressedLength) - { - // Shitty file with a section header between COMPRESSED_SECTION_HEADER and LZMA_HEADER - // We must determine section header size by checking it's type before we can unpack that non-standard compressed section - shittySectionHeader = (EFI_COMMON_SECTION_HEADER*) data; - shittySectionSize = sizeOfSectionHeaderOfType(shittySectionHeader->Type); - data = (VOID*) (file.constData() + sectionIndex + sizeof(EFI_COMPRESSION_SECTION) + shittySectionSize); - dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION) - shittySectionSize; - if (LzmaGetInfo(data, dataSize, &decompressedSize) != ERR_SUCCESS) - debug(tr("LzmaGetInfo failed")); - } - - decompressed = new UINT8[decompressedSize]; - - // Decompress section data - if (LzmaDecompress(data, dataSize, decompressed) != ERR_SUCCESS) - debug(tr("LzmaDecompress failed")); - else - { - body = QByteArray::fromRawData((const char*) decompressed, decompressedSize); - // Parse stored file - result = parseFile(body, revision, erasePolarity, index); - if (result) - debug(tr("Compressed section with LZMA compression can not be parsed as file (%1)").arg(result)); - } - - delete[] decompressed; - break; - default: - body = file.mid(sectionIndex + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - debug(tr("Compressed section with unknown compression type found (%1)").arg(compressedSectionHeader->CompressionType)); - } - - break; - case EFI_SECTION_GUID_DEFINED: - header = file.mid(sectionIndex, sizeof(EFI_GUID_DEFINED_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_GUID_DEFINED_SECTION), sectionSize - sizeof(EFI_GUID_DEFINED_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - // Parse section body as file - guidDefinedSectionHeader = (EFI_GUID_DEFINED_SECTION*) (header.constData()); - body = file.mid(sectionIndex + guidDefinedSectionHeader->DataOffset, sectionSize - guidDefinedSectionHeader->DataOffset); - result = parseFile(body, revision, erasePolarity, index); - if (result) - debug(tr("GUID defined section body can not be parsed as file (%1)").arg(result)); - break; - case EFI_SECTION_DISPOSABLE: - header = file.mid(sectionIndex, sizeof(EFI_DISPOSABLE_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_DISPOSABLE_SECTION), sectionSize - sizeof(EFI_DISPOSABLE_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - // Leaf sections - case EFI_SECTION_PE32: - header = file.mid(sectionIndex, sizeof(EFI_PE32_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_PE32_SECTION), sectionSize - sizeof(EFI_PE32_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_PIC: - header = file.mid(sectionIndex, sizeof(EFI_PIC_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_PIC_SECTION), sectionSize - sizeof(EFI_PIC_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_TE: - header = file.mid(sectionIndex, sizeof(EFI_TE_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_TE_SECTION), sectionSize - sizeof(EFI_TE_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_VERSION: - header = file.mid(sectionIndex, sizeof(EFI_VERSION_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_VERSION_SECTION), sectionSize - sizeof(EFI_VERSION_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_USER_INTERFACE: - header = file.mid(sectionIndex, sizeof(EFI_USER_INTERFACE_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_USER_INTERFACE_SECTION), sectionSize - sizeof(EFI_USER_INTERFACE_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_COMPATIBILITY16: - header = file.mid(sectionIndex, sizeof(EFI_COMPATIBILITY16_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_COMPATIBILITY16_SECTION), sectionSize - sizeof(EFI_COMPATIBILITY16_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_FIRMWARE_VOLUME_IMAGE: - header = file.mid(sectionIndex, sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION), sectionSize - sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - // Parse section body as BIOS space - result = parseBios(body, index); - if (result && result != ERR_VOLUMES_NOT_FOUND) - debug(tr("Firmware volume image can not be parsed (%1)").arg(result)); - break; - case EFI_SECTION_FREEFORM_SUBTYPE_GUID: - header = file.mid(sectionIndex, sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION), sectionSize - sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_RAW: - header = file.mid(sectionIndex, sizeof(EFI_RAW_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_RAW_SECTION), sectionSize - sizeof(EFI_RAW_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - // Parse section body as BIOS space - result = parseBios(body, index); - if (result && result != ERR_VOLUMES_NOT_FOUND) - debug(tr("Raw section can not be parsed as BIOS (%1)").arg(result)); - break; - break; - case EFI_SECTION_DXE_DEPEX: - header = file.mid(sectionIndex, sizeof(EFI_DXE_DEPEX_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_DXE_DEPEX_SECTION), sectionSize - sizeof(EFI_DXE_DEPEX_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_PEI_DEPEX: - header = file.mid(sectionIndex, sizeof(EFI_PEI_DEPEX_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_PEI_DEPEX_SECTION), sectionSize - sizeof(EFI_PEI_DEPEX_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - case EFI_SECTION_SMM_DEPEX: - header = file.mid(sectionIndex, sizeof(EFI_SMM_DEPEX_SECTION)); - body = file.mid(sectionIndex + sizeof(EFI_SMM_DEPEX_SECTION), sectionSize - sizeof(EFI_SMM_DEPEX_SECTION)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - break; - default: - debug(tr("Section with unknown type (%1)").arg(sectionHeader->Type, 2, 16, QChar('0'))); - header = file.mid(sectionIndex, sizeof(EFI_COMMON_SECTION_HEADER)); - body = file.mid(sectionIndex + sizeof(EFI_COMMON_SECTION_HEADER), sectionSize - sizeof(EFI_COMMON_SECTION_HEADER)); - index = addTreeItem(SectionItem, sectionHeader->Type, header, body, parent); - } - - // Move to next section - sectionIndex += uint24ToUint32(sectionHeader->Size); - sectionIndex = ALIGN4(sectionIndex); - - // Exit from loop if no sections left - if (sectionIndex >= file.size()) - sectionIndex = -1; - } - - return ERR_SUCCESS; -} - diff --git a/uefitool.h b/uefitool.h index f36429c..1d0d771 100644 --- a/uefitool.h +++ b/uefitool.h @@ -26,17 +26,8 @@ #include #include -#include - -#include "treemodel.h" - #include "basetypes.h" -#include "descriptor.h" -#include "ffs.h" -#include "Tiano/EfiTianoCompress.h" -#include "Tiano/EfiTianoDecompress.h" -#include "LZMA/LzmaCompress.h" -#include "LZMA/LzmaDecompress.h" +#include "ffsengine.h" namespace Ui { class UEFITool; @@ -55,30 +46,19 @@ public: private slots: void init(); void openImageFile(); - //void saveImageFile(); void populateUi(const QModelIndex ¤t/*, const QModelIndex &previous*/); void resizeTreeViewColums(/*const QModelIndex &index*/); void saveAll(); void saveBody(); + void saveUncompressedFile(); private: Ui::UEFITool * ui; - TreeModel * treeModel; + FfsEngine* ffsEngine; QModelIndex currentIndex; - - void debug(const QString & text); + void dragEnterEvent(QDragEnterEvent* event); void dropEvent(QDropEvent* event); - QModelIndex addTreeItem(UINT8 type, UINT8 subtype, const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), - const QModelIndex & parent = QModelIndex()); - - UINT8 parseInputFile(const QByteArray & buffer); - UINT8* parseRegion(const QByteArray & flashImage, UINT8 regionSubtype, const UINT16 regionBase, const UINT16 regionLimit, QModelIndex & index); - UINT8 parseBios(const QByteArray & bios, const QModelIndex & parent = QModelIndex()); - INT32 getNextVolumeIndex(const QByteArray & bios, INT32 volumeIndex = 0); - UINT32 getVolumeSize(const QByteArray & bios, INT32 volumeIndex); - UINT8 parseVolume(const QByteArray & volume, UINT32 volumeBase, UINT8 revision, bool erasePolarity, const QModelIndex & parent = QModelIndex()); - UINT8 parseFile(const QByteArray & file, UINT8 revision, bool erasePolarity, const QModelIndex & parent = QModelIndex()); }; #endif diff --git a/uefitool.pro b/uefitool.pro index 9427990..1f4b4e4 100644 --- a/uefitool.pro +++ b/uefitool.pro @@ -8,6 +8,7 @@ SOURCES += main.cpp \ uefitool.cpp \ descriptor.cpp \ ffs.cpp \ + ffsengine.cpp \ treeitem.cpp \ treemodel.cpp \ LZMA/LzmaCompress.c \ @@ -21,7 +22,10 @@ SOURCES += main.cpp \ HEADERS += uefitool.h \ basetypes.h \ descriptor.h \ + gbe.h \ + me.h \ ffs.h \ + ffsengine.h \ treeitem.h \ treeitemtypes.h \ treemodel.h \ diff --git a/uefitool.ui b/uefitool.ui index 41d23bd..02df6a3 100644 --- a/uefitool.ui +++ b/uefitool.ui @@ -7,7 +7,7 @@ 0 0 900 - 600 + 700 @@ -20,7 +20,7 @@ true - UEFITool 0.2.4 + UEFITool 0.3.0 @@ -30,7 +30,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -88,6 +88,9 @@ Information + + 3 + @@ -119,30 +122,10 @@ - - - - false - - - Export... - - - - - - - false - - - Export without header... - - - - + @@ -194,16 +177,160 @@ - - - - Open BIOS image file... - - - + + + true + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + + + + + + + + + + + + false + + + Insert after + + + Insert an object from file after selected object + + + Ctrl+I + + + + + false + + + Insert before + + + Insert an object from file before selected object + + + Ctrl+Shift+I + + + + + false + + + Replace + + + Replace selected object with an object from file + + + Ctrl+R + + + + + false + + + Extract + + + Extract selected object to file + + + Ctrl+E + + + + + false + + + Extract body + + + Extract selected object without header to file + + + Ctrl+Shift+E + + + + + false + + + Extract uncompressed + + + Extract selected FFS file uncompressing it + + + Ctrl+Alt+E + + + + + false + + + Delete + + + Delete selected object + + + Ctrl+D + + + + + false + + + Replace with padding + + + Delete an object by replacing it with padding + + + Ctrl+Shift+D + + + + + Open image file + + + Open image file + + + Ctrl+O + +