mirror of
https://github.com/LongSoft/UEFITool.git
synced 2024-11-25 01:18:22 +08:00
1459 lines
60 KiB
C++
1459 lines
60 KiB
C++
/* fssbuilder.cpp
|
|
|
|
Copyright (c) 2015, 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,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
|
|
*/
|
|
#include "ffsbuilder.h"
|
|
|
|
#include "descriptor.h"
|
|
#include "ffs.h"
|
|
#include "peimage.h"
|
|
#include "utility.h"
|
|
#include "nvram.h"
|
|
|
|
USTATUS FfsBuilder::erase(const UModelIndex & index, UByteArray & erased)
|
|
{
|
|
// Sanity check
|
|
if (!index.isValid())
|
|
return U_INVALID_PARAMETER;
|
|
|
|
// Try to get emptyByte value from item's parsing data
|
|
UINT8 emptyByte = 0xFF;
|
|
if (!model->hasEmptyParsingData(index)) {
|
|
if (model->type(index) == Types::Volume) {
|
|
VOLUME_PARSING_DATA pdata = *(VOLUME_PARSING_DATA*)model->parsingData(index).constData();
|
|
emptyByte = pdata.emptyByte;
|
|
}
|
|
else if (model->type(index) == Types::File) {
|
|
FILE_PARSING_DATA pdata = *(FILE_PARSING_DATA*)model->parsingData(index).constData();
|
|
emptyByte = pdata.emptyByte;
|
|
}
|
|
}
|
|
|
|
erased = UByteArray(model->header(index).size() + model->body(index).size() + model->tail(index).size(), emptyByte);
|
|
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
USTATUS FfsBuilder::build(const UModelIndex & index, UByteArray & reconstructed)
|
|
{
|
|
if (!index.isValid())
|
|
return U_SUCCESS;
|
|
|
|
USTATUS result;
|
|
|
|
switch (model->type(index)) {
|
|
case Types::Image:
|
|
if (model->subtype(index) == Subtypes::IntelImage) {
|
|
result = buildIntelImage(index, reconstructed);
|
|
if (result)
|
|
return result;
|
|
}
|
|
else {
|
|
//Other images types can be reconstructed like regions
|
|
result = buildRegion(index, reconstructed);
|
|
if (result)
|
|
return result;
|
|
}
|
|
break;
|
|
|
|
case Types::Capsule:
|
|
result = buildCapsule(index, reconstructed);
|
|
if (result)
|
|
return result;
|
|
break;
|
|
|
|
case Types::Region:
|
|
result = buildRegion(index, reconstructed);
|
|
if (result)
|
|
return result;
|
|
break;
|
|
|
|
case Types::Padding:
|
|
result = buildPadding(index, reconstructed);
|
|
if (result)
|
|
return result;
|
|
break;
|
|
|
|
case Types::Volume:
|
|
// Nvram rebuild support
|
|
if(model->subtype(index) == Subtypes::NvramVolume)
|
|
result = buildNvramVolume(index, reconstructed);
|
|
else
|
|
result = buildVolume(index, reconstructed);
|
|
|
|
if (result)
|
|
return result;
|
|
break;
|
|
|
|
case Types::Section:
|
|
result = buildSection(index, 0, reconstructed);
|
|
if (result)
|
|
return result;
|
|
break;
|
|
default:
|
|
msg(usprintf("build: unknown item type %d", model->type(index)), index);
|
|
return U_UNKNOWN_ITEM_TYPE;
|
|
}
|
|
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildRegion(const UModelIndex& index, UByteArray& reconstructed, bool includeHeader)
|
|
{
|
|
if (!index.isValid())
|
|
return U_SUCCESS;
|
|
|
|
USTATUS result;
|
|
|
|
// No action
|
|
if (model->action(index) == Actions::NoAction) {
|
|
reconstructed = model->header(index) + model->body(index);
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Remove) {
|
|
reconstructed.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Rebuild ||
|
|
model->action(index) == Actions::Replace) {
|
|
if (model->rowCount(index)) {
|
|
reconstructed.clear();
|
|
// Reconstruct children
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
UByteArray child;
|
|
result = build(index.child(i, 0), child);
|
|
if (result)
|
|
return result;
|
|
reconstructed += child;
|
|
}
|
|
}
|
|
// Use stored item body
|
|
else
|
|
reconstructed = model->body(index);
|
|
|
|
// Check size of reconstructed region, it must be same
|
|
if (reconstructed.size() > model->body(index).size()) {
|
|
msg("buildRegion: reconstructed region size is bigger then original ",
|
|
index);
|
|
return U_INVALID_PARAMETER;
|
|
}
|
|
else if (reconstructed.size() < model->body(index).size()) {
|
|
msg("buildRegion: reconstructed region size is smaller then original ",
|
|
index);
|
|
return U_INVALID_PARAMETER;
|
|
}
|
|
|
|
// Reconstruction successful
|
|
if (includeHeader)
|
|
reconstructed = model->header(index) + reconstructed;
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
// All other actions are not supported
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildCapsule(const UModelIndex & index, UByteArray & capsule)
|
|
{
|
|
// Sanity check
|
|
if (!index.isValid())
|
|
return U_INVALID_PARAMETER;
|
|
|
|
// No action
|
|
if (model->action(index) == Actions::NoAction) {
|
|
// Use original item data
|
|
capsule = model->header(index) + model->body(index) + model->tail(index);
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
// Rebuild or Replace
|
|
else if (model->action(index) == Actions::Rebuild
|
|
|| model->action(index) == Actions::Replace) {
|
|
if (model->rowCount(index)) {
|
|
// Clear the supplied UByteArray
|
|
capsule.clear();
|
|
|
|
// Right now there is only one capsule image element supported
|
|
if (model->rowCount(index) != 1) {
|
|
msg(usprintf("buildCapsule: building of capsules with %d items is not yet supported", model->rowCount(index)), index);
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
// Build image
|
|
UModelIndex imageIndex = index.child(0, 0);
|
|
UByteArray imageData;
|
|
|
|
// Check image type
|
|
if (model->type(imageIndex) == Types::Image) {
|
|
USTATUS result = U_SUCCESS;
|
|
if (model->subtype(imageIndex) == Subtypes::IntelImage) {
|
|
result = buildIntelImage(imageIndex, imageData);
|
|
}
|
|
else if (model->subtype(imageIndex) == Subtypes::UefiImage) {
|
|
result = buildRawArea(imageIndex, imageData);
|
|
}
|
|
else {
|
|
msg(UString("buildCapsule: unexpected item subtype ") + itemSubtypeToUString(model->type(imageIndex), model->subtype(imageIndex)), imageIndex);
|
|
return U_UNKNOWN_ITEM_TYPE;
|
|
}
|
|
|
|
// Check build result
|
|
if (result) {
|
|
msg(UString("buildCapsule: building of ") + model->name(imageIndex) + UString(" failed with error ") + errorCodeToUString(result), imageIndex);
|
|
return result;
|
|
}
|
|
else
|
|
capsule += imageData;
|
|
}
|
|
else {
|
|
msg(UString("buildCapsule: unexpected item type ") + itemTypeToUString(model->type(imageIndex)), imageIndex);
|
|
return U_UNKNOWN_ITEM_TYPE;
|
|
}
|
|
|
|
// Check size of reconstructed capsule body, it must remain the same
|
|
UINT32 newSize = capsule.size();
|
|
UINT32 oldSize = model->body(index).size();
|
|
if (newSize > oldSize) {
|
|
msg(usprintf("buildCapsule: new capsule size %Xh (%u) is bigger than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index);
|
|
return U_INVALID_CAPSULE;
|
|
}
|
|
else if (newSize < oldSize) {
|
|
msg(usprintf("buildCapsule: new capsule size %Xh (%u) is smaller than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index);
|
|
return U_INVALID_CAPSULE;
|
|
}
|
|
}
|
|
else
|
|
capsule = model->body(index);
|
|
|
|
// Build successful, header and tail
|
|
capsule = model->header(index) + capsule + model->tail(index);
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
msg(UString("buildCapsule: unexpected action " + actionTypeToUString(model->action(index))), index);
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildIntelImage(const UModelIndex & index, UByteArray & intelImage)
|
|
{
|
|
// Sanity check
|
|
if (!index.isValid())
|
|
return U_SUCCESS;
|
|
|
|
// No action
|
|
if (model->action(index) == Actions::NoAction) {
|
|
intelImage = model->header(index) + model->body(index) + model->tail(index);
|
|
return U_SUCCESS;
|
|
}
|
|
// Remove
|
|
else if (model->action(index) == Actions::Remove) {
|
|
intelImage.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
// Rebuild
|
|
else if (model->action(index) == Actions::Rebuild) {
|
|
// First child will always be descriptor for this type of image, and it's read only for now
|
|
intelImage = model->header(index.child(0, 0)) + model->body(index.child(0, 0)) + model->tail(index.child(0, 0));
|
|
|
|
// Process other regions
|
|
for (int i = 1; i < model->rowCount(index); i++) {
|
|
UModelIndex currentRegion = index.child(i, 0);
|
|
|
|
// Skip regions with Remove action
|
|
if (model->action(currentRegion) == Actions::Remove)
|
|
continue;
|
|
|
|
// Check item type to be either region or padding
|
|
UINT8 type = model->type(currentRegion);
|
|
if (type == Types::Padding) {
|
|
// Add padding as is
|
|
intelImage += model->header(currentRegion) + model->body(currentRegion) + model->tail(currentRegion);
|
|
continue;
|
|
}
|
|
|
|
// Check region subtype
|
|
USTATUS result;
|
|
UByteArray region;
|
|
UINT8 regionType = model->subtype(currentRegion);
|
|
switch (regionType) {
|
|
case Subtypes::BiosRegion:
|
|
case Subtypes::PdrRegion:
|
|
result = buildRawArea(currentRegion, region);
|
|
if (result) {
|
|
msg(UString("buildIntelImage: building of region ") + regionTypeToUString(regionType) + UString(" failed with error ") + errorCodeToUString(result), currentRegion);
|
|
return result;
|
|
}
|
|
break;
|
|
case Subtypes::MeRegion:
|
|
case Subtypes::GbeRegion:
|
|
case Subtypes::DevExp1Region:
|
|
case Subtypes::Bios2Region:
|
|
case Subtypes::MicrocodeRegion:
|
|
case Subtypes::EcRegion:
|
|
case Subtypes::DevExp2Region:
|
|
case Subtypes::IeRegion:
|
|
case Subtypes::Tgbe1Region:
|
|
case Subtypes::Tgbe2Region:
|
|
case Subtypes::Reserved1Region:
|
|
case Subtypes::Reserved2Region:
|
|
case Subtypes::PttRegion:
|
|
// Add region as is
|
|
region = model->header(currentRegion) + model->body(currentRegion);
|
|
break;
|
|
default:
|
|
msg(UString("buildIntelImage: unknown region type"), currentRegion);
|
|
return U_UNKNOWN_ITEM_TYPE;
|
|
}
|
|
|
|
// the resulting region
|
|
intelImage += region;
|
|
}
|
|
|
|
// Check size of new image, it must be same as old one
|
|
UINT32 newSize = intelImage.size();
|
|
UINT32 oldSize = model->body(index).size();
|
|
if (newSize > oldSize) {
|
|
msg(usprintf("buildIntelImage: new image size %Xh (%u) is bigger than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index);
|
|
return U_INVALID_IMAGE;
|
|
}
|
|
else if (newSize < oldSize) {
|
|
msg(usprintf("buildIntelImage: new image size %Xh (%u) is smaller than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index);
|
|
return U_INVALID_IMAGE;
|
|
}
|
|
|
|
// Build successful, header and tail
|
|
intelImage = model->header(index) + intelImage + model->tail(index);
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
msg(UString("buildIntelImage: unexpected action " + actionTypeToUString(model->action(index))), index);
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildRawArea(const UModelIndex & index, UByteArray & rawArea, bool includeHeader)
|
|
{
|
|
// Sanity check
|
|
if (!index.isValid())
|
|
return U_INVALID_PARAMETER;
|
|
|
|
// No action required
|
|
if (model->action(index) == Actions::NoAction) {
|
|
rawArea = model->header(index) + model->body(index) + model->tail(index);
|
|
return U_SUCCESS;
|
|
}
|
|
// Remove
|
|
else if (model->action(index) == Actions::Remove) {
|
|
rawArea.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
// Rebuild or Replace
|
|
else if (model->action(index) == Actions::Rebuild
|
|
|| model->action(index) == Actions::Replace) {
|
|
// Rebuild if there is at least 1 child
|
|
if (model->rowCount(index)) {
|
|
// Clear the supplied UByteArray
|
|
rawArea.clear();
|
|
|
|
// Build children
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
USTATUS result = U_SUCCESS;
|
|
UModelIndex currentChild = index.child(i, 0);
|
|
UByteArray currentData;
|
|
// Check child type
|
|
if (model->type(currentChild) == Types::Volume) {
|
|
result = buildVolume(currentChild, currentData);
|
|
}
|
|
else if (model->type(currentChild) == Types::Padding) {
|
|
result = buildPadding(currentChild, currentData);
|
|
}
|
|
else {
|
|
msg(UString("buildRawArea: unexpected item type ") + itemTypeToUString(model->type(currentChild)), currentChild);
|
|
return U_UNKNOWN_ITEM_TYPE;
|
|
}
|
|
// Check build result
|
|
if (result) {
|
|
msg(UString("buildRawArea: building of ") + model->name(currentChild) + UString(" failed with error ") + errorCodeToUString(result), currentChild);
|
|
return result;
|
|
}
|
|
// current data
|
|
rawArea += currentData;
|
|
}
|
|
|
|
// Check size of new raw area, it must be same as original one
|
|
UINT32 newSize = rawArea.size();
|
|
UINT32 oldSize = model->body(index).size();
|
|
if (newSize > oldSize) {
|
|
msg(usprintf("buildRawArea: new area size %Xh (%u) is bigger than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index);
|
|
return U_INVALID_RAW_AREA;
|
|
}
|
|
else if (newSize < oldSize) {
|
|
msg(usprintf("buildRawArea: new area size %Xh (%u) is smaller than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index);
|
|
return U_INVALID_RAW_AREA;
|
|
}
|
|
}
|
|
// No need to rebuild a raw area with no children
|
|
else {
|
|
rawArea = model->body(index);
|
|
}
|
|
|
|
// Build successful, add header if needed
|
|
if(includeHeader)
|
|
rawArea = model->header(index) + rawArea + model->tail(index);
|
|
else
|
|
rawArea = rawArea + model->tail(index);
|
|
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
msg(UString("buildRawArea: unexpected action " + actionTypeToUString(model->action(index))), index);
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildPadding(const UModelIndex & index, UByteArray & padding)
|
|
{
|
|
// Sanity check
|
|
if (!index.isValid())
|
|
return U_INVALID_PARAMETER;
|
|
|
|
// No action required
|
|
if (model->action(index) == Actions::NoAction) {
|
|
padding = model->header(index) + model->body(index) + model->tail(index);
|
|
return U_SUCCESS;
|
|
}
|
|
// Remove
|
|
else if (model->action(index) == Actions::Remove) {
|
|
padding.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
// Erase
|
|
else if (model->action(index) == Actions::Erase) {
|
|
return erase(index, padding);
|
|
}
|
|
|
|
msg(UString("buildPadding: unexpected action " + actionTypeToUString(model->action(index))), index);
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildNonUefiData(const UModelIndex & index, UByteArray & data)
|
|
{
|
|
// Sanity check
|
|
if (!index.isValid())
|
|
return U_INVALID_PARAMETER;
|
|
|
|
// No action required
|
|
if (model->action(index) == Actions::NoAction) {
|
|
data = model->header(index) + model->body(index) + model->tail(index);
|
|
return U_SUCCESS;
|
|
}
|
|
// Remove
|
|
else if (model->action(index) == Actions::Remove) {
|
|
data.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
// Erase
|
|
else if (model->action(index) == Actions::Erase) {
|
|
return erase(index, data);
|
|
}
|
|
|
|
// TODO: rebuild properly
|
|
|
|
msg(UString("buildNoUefiData: unexpected action " + actionTypeToUString(model->action(index))), index);
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildFreeSpace(const UModelIndex & index, UByteArray & freeSpace)
|
|
{
|
|
// Sanity check
|
|
if (!index.isValid())
|
|
return U_INVALID_PARAMETER;
|
|
|
|
// No actions possible for free space
|
|
freeSpace = model->header(index) + model->body(index) + model->tail(index);
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildVolume(const UModelIndex & index, UByteArray & volume)
|
|
{
|
|
if (!index.isValid())
|
|
return U_SUCCESS;
|
|
|
|
USTATUS result;
|
|
|
|
// No action
|
|
if (model->action(index) == Actions::NoAction) {
|
|
volume = model->header(index) + model->body(index);
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Remove) {
|
|
volume.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Replace ||
|
|
model->action(index) == Actions::Rebuild) {
|
|
UByteArray header = model->header(index);
|
|
UByteArray body = model->body(index);
|
|
EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)header.data();
|
|
|
|
// Check sanity of HeaderLength
|
|
if (volumeHeader->HeaderLength > header.size()) {
|
|
msg(UString("buildVolume: invalid volume header length, reconstruction is not possible"), index);
|
|
return U_INVALID_VOLUME;
|
|
}
|
|
|
|
// Recalculate volume header checksum
|
|
volumeHeader->Checksum = 0;
|
|
volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength);
|
|
|
|
// Get volume size
|
|
UINT32 volumeSize = header.size() + body.size();
|
|
|
|
// Reconstruct volume body
|
|
UINT32 freeSpaceOffset = 0;
|
|
if (model->rowCount(index)) {
|
|
volume.clear();
|
|
UINT8 polarity = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE;
|
|
char empty = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY ? '\xFF' : '\x00';
|
|
|
|
// Calculate volume base for volume
|
|
UINT32 volumeBase;
|
|
UByteArray file;
|
|
bool baseFound = false;
|
|
|
|
// Search for VTF
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
file = model->header(index.child(i, 0));
|
|
// VTF found
|
|
if (file.left(sizeof(EFI_GUID)) == EFI_FFS_VOLUME_TOP_FILE_GUID) {
|
|
baseFound = true;
|
|
volumeBase = (UINT32)(0x100000000 - volumeSize);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Determine if volume is inside compressed item
|
|
if (!baseFound) {
|
|
// Iterate up to the root, checking for compression type to be other then none
|
|
for (UModelIndex parentIndex = index.parent(); model->type(parentIndex) != Types::Root; parentIndex = parentIndex.parent()) {
|
|
UByteArray data = model->parsingData(parentIndex);
|
|
const COMPRESSED_SECTION_PARSING_DATA* pdata = (const COMPRESSED_SECTION_PARSING_DATA*)data.constData();
|
|
|
|
if (pdata->algorithm != COMPRESSION_ALGORITHM_NONE) {
|
|
// No rebase needed for compressed PEI files
|
|
baseFound = true;
|
|
volumeBase = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find volume base address using first PEI file in it
|
|
if (!baseFound) {
|
|
// Search for first PEI-file and use it as base source
|
|
UINT32 fileOffset = header.size();
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
if ((model->subtype(index.child(i, 0)) == EFI_FV_FILETYPE_PEI_CORE ||
|
|
model->subtype(index.child(i, 0)) == EFI_FV_FILETYPE_PEIM ||
|
|
model->subtype(index.child(i, 0)) == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER)){
|
|
UModelIndex peiFile = index.child(i, 0);
|
|
UINT32 sectionOffset = sizeof(EFI_FFS_FILE_HEADER);
|
|
// BUGBUG: this parsing is bad and doesn't support large files, but it needs to be performed only for very old images with uncompressed DXE volumes, so whatever
|
|
// Search for PE32 or TE section
|
|
for (int j = 0; j < model->rowCount(peiFile); j++) {
|
|
if (model->subtype(peiFile.child(j, 0)) == EFI_SECTION_PE32 ||
|
|
model->subtype(peiFile.child(j, 0)) == EFI_SECTION_TE) {
|
|
UModelIndex image = peiFile.child(j, 0);
|
|
// Check for correct action
|
|
if (model->action(image) == Actions::Remove || model->action(image) == Actions::Insert)
|
|
continue;
|
|
// Calculate relative base address
|
|
UINT32 relbase = fileOffset + sectionOffset + model->header(image).size();
|
|
// Calculate offset of image relative to file base
|
|
UINT32 imagebase = 0;
|
|
result = getBase(model->body(image), imagebase); // imagebase passed by reference
|
|
if (!result) {
|
|
// Calculate volume base
|
|
volumeBase = imagebase - relbase;
|
|
baseFound = true;
|
|
goto out;
|
|
}
|
|
}
|
|
sectionOffset += model->header(peiFile.child(j, 0)).size() + model->body(peiFile.child(j, 0)).size();
|
|
sectionOffset = ALIGN4(sectionOffset);
|
|
}
|
|
}
|
|
fileOffset += model->header(index.child(i, 0)).size() + model->body(index.child(i, 0)).size();
|
|
fileOffset = ALIGN8(fileOffset);
|
|
}
|
|
}
|
|
out:
|
|
// Do not set volume base
|
|
if (!baseFound)
|
|
volumeBase = 0;
|
|
|
|
// Reconstruct files in volume
|
|
UINT32 offset = 0;
|
|
UByteArray padFileGuid = EFI_FFS_PAD_FILE_GUID;
|
|
UByteArray vtf;
|
|
UModelIndex vtfIndex;
|
|
UINT32 nonUefiDataOffset = 0;
|
|
UByteArray nonUefiData;
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
// Inside a volume can be files, free space or padding with non-UEFI data
|
|
if (model->type(index.child(i, 0)) == Types::File) { // Next item is a file
|
|
|
|
// Align to 8 byte boundary
|
|
UINT32 alignment = offset % 8;
|
|
if (alignment) {
|
|
alignment = 8 - alignment;
|
|
offset += alignment;
|
|
volume += UByteArray(alignment, empty);
|
|
}
|
|
|
|
// Calculate file base
|
|
UINT32 fileBase = volumeBase ? volumeBase + header.size() + offset : 0;
|
|
|
|
// Reconstruct file
|
|
result = buildFile(index.child(i, 0), volumeHeader->Revision, polarity, fileBase, file);
|
|
if (result)
|
|
return result;
|
|
|
|
// Empty file
|
|
if (file.isEmpty())
|
|
continue;
|
|
|
|
EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)file.data();
|
|
UINT32 fileHeaderSize = sizeof(EFI_FFS_FILE_HEADER);
|
|
if (volumeHeader->Revision > 1 && (fileHeader->Attributes & FFS_ATTRIB_LARGE_FILE))
|
|
fileHeaderSize = sizeof(EFI_FFS_FILE_HEADER2);
|
|
|
|
// Pad file
|
|
if (fileHeader->Type == EFI_FV_FILETYPE_PAD) {
|
|
padFileGuid = file.left(sizeof(EFI_GUID));
|
|
|
|
// Parse non-empty pad file
|
|
if (model->rowCount(index.child(i, 0))) {
|
|
//TODO: handle it
|
|
msg("buildVolume: non-empty pad-file contents will be destroyed after volume modifications");
|
|
continue;
|
|
}
|
|
// Skip empty pad-file
|
|
else
|
|
continue;
|
|
}
|
|
|
|
// Volume Top File
|
|
if (file.left(sizeof(EFI_GUID)) == EFI_FFS_VOLUME_TOP_FILE_GUID) {
|
|
vtf = file;
|
|
vtfIndex = index.child(i, 0);
|
|
continue;
|
|
}
|
|
|
|
// Normal file
|
|
// Ensure correct alignment
|
|
UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3];
|
|
alignment = (UINT32)(1UL <<alignmentPower);
|
|
UINT32 alignmentBase = header.size() + offset + fileHeaderSize;
|
|
if (alignmentBase % alignment) {
|
|
// File will be unaligned if added as is, so we must add pad file before it
|
|
// Determine pad file size
|
|
UINT32 size = alignment - (alignmentBase % alignment);
|
|
// Required padding is smaller then minimal pad file size
|
|
while (size < sizeof(EFI_FFS_FILE_HEADER)) {
|
|
size += alignment;
|
|
}
|
|
// Construct pad file
|
|
UByteArray pad;
|
|
result = buildPadFile(padFileGuid, size, volumeHeader->Revision, polarity, pad);
|
|
if (result)
|
|
return result;
|
|
// Append constructed pad file to volume body
|
|
volume += pad;
|
|
offset += size;
|
|
}
|
|
|
|
// Append current file to new volume body
|
|
volume += file;
|
|
|
|
// Change current file offset
|
|
offset += file.size();
|
|
}
|
|
else if (model->type(index.child(i, 0)) == Types::FreeSpace) { //Next item is a free space
|
|
// Some data are located beyond free space
|
|
if (offset + (UINT32)model->body(index.child(i, 0)).size() < (UINT32)model->body(index).size()) {
|
|
// Get non-UEFI data and it's offset
|
|
nonUefiData = model->body(index.child(i + 1, 0));
|
|
nonUefiDataOffset = body.size() - nonUefiData.size();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check volume sanity
|
|
if (!vtf.isEmpty() && !nonUefiData.isEmpty()) {
|
|
msg(usprintf("buildVolume: both VTF and non-UEFI data found in the volume, reconstruction is not possible"), index);
|
|
return U_INVALID_VOLUME;
|
|
}
|
|
|
|
// Check for free space offset in ZeroVector
|
|
if (model->text(index).contains("AppleFSO ")) {
|
|
// Align current offset to 8 byte boundary
|
|
UINT32 alignment = offset % 8;
|
|
freeSpaceOffset = model->header(index).size() + offset;
|
|
if (alignment) {
|
|
alignment = 8 - alignment;
|
|
freeSpaceOffset += alignment;
|
|
}
|
|
}
|
|
|
|
// Insert VTF or non-UEFI data to it's correct place
|
|
if (!vtf.isEmpty()) { // VTF found
|
|
// Determine correct VTF offset
|
|
UINT32 vtfOffset = model->body(index).size() - vtf.size();
|
|
|
|
if (vtfOffset % 8) {
|
|
msg(usprintf("buildVolume: wrong size of the Volume Top File"), index);
|
|
return U_INVALID_FILE;
|
|
}
|
|
// Insert pad file to fill the gap
|
|
if (vtfOffset > offset) {
|
|
// Determine pad file size
|
|
UINT32 size = vtfOffset - offset;
|
|
// Construct pad file
|
|
UByteArray pad;
|
|
result = buildPadFile(padFileGuid, size, volumeHeader->Revision, polarity, pad);
|
|
if (result)
|
|
return result;
|
|
// Append constructed pad file to volume body
|
|
volume += pad;
|
|
}
|
|
// No more space left in volume
|
|
else if (offset > vtfOffset) {
|
|
msg(usprintf("buildVolume: no space left to insert VTF, need %xh (%d) byte(s) more",
|
|
offset - vtfOffset, offset - vtfOffset), index);
|
|
return U_INVALID_VOLUME;
|
|
}
|
|
|
|
// Calculate VTF base
|
|
UINT32 vtfBase = volumeBase ? volumeBase + vtfOffset : 0;
|
|
|
|
// Reconstruct VTF again
|
|
result = buildFile(vtfIndex, volumeHeader->Revision, polarity, vtfBase, vtf);
|
|
if (result)
|
|
return result;
|
|
|
|
// Patch VTF
|
|
if (!parser->peiCoreEntryPoint) {
|
|
msg("patchVtf: PEI Core entry point can't be determined. VTF can't be patched.", index);
|
|
return U_PEI_CORE_ENTRY_POINT_NOT_FOUND;
|
|
}
|
|
if (parser->newPeiCoreEntryPoint && parser->peiCoreEntryPoint != parser->newPeiCoreEntryPoint) {
|
|
// Replace last occurrence of oldPeiCoreEntryPoint with newPeiCoreEntryPoint
|
|
UByteArray old((char*)&parser->peiCoreEntryPoint, sizeof(parser->peiCoreEntryPoint));
|
|
int i = vtf.lastIndexOf(old);
|
|
if (i == -1)
|
|
msg("patchVtf: PEI Core entry point can't be found in VTF. VTF not patched.", index);
|
|
else {
|
|
UINT32* data = (UINT32*)(vtf.data() + i);
|
|
*data = parser->newPeiCoreEntryPoint;
|
|
}
|
|
}
|
|
|
|
// Append VTF
|
|
volume += vtf;
|
|
}
|
|
else if (!nonUefiData.isEmpty()) { //Non-UEFI data found
|
|
// No space left
|
|
if (offset > nonUefiDataOffset) {
|
|
msg(usprintf("buildVolume: no space left to insert non-UEFI data, need %xh (%d) byte(s) more",
|
|
offset - nonUefiDataOffset, offset - nonUefiDataOffset), index);
|
|
return U_INVALID_VOLUME;
|
|
}
|
|
// Append additional free space
|
|
else if (nonUefiDataOffset > offset) {
|
|
volume += UByteArray(nonUefiDataOffset - offset, empty);
|
|
}
|
|
|
|
// Append VTF
|
|
volume += nonUefiData;
|
|
}
|
|
else {
|
|
// Fill the rest of volume space with empty char
|
|
if (body.size() > volume.size()) {
|
|
// Fill volume end with empty char
|
|
volume += UByteArray(body.size() - volume.size(), empty);
|
|
}
|
|
else if (body.size() < volume.size()) {
|
|
// Check if volume can be grown
|
|
// Root volume can't be grown
|
|
UINT8 parentType = model->type(index.parent());
|
|
if (parentType != Types::File && parentType != Types::Section) {
|
|
msg("buildVolume: root volume can't be grown", index);
|
|
return U_INVALID_VOLUME;
|
|
}
|
|
|
|
// Grow volume to fit new body
|
|
UINT32 newSize = header.size() + volume.size();
|
|
result = growVolume(header, volumeSize, newSize);
|
|
if (result)
|
|
return result;
|
|
|
|
// Fill volume end with empty char
|
|
volume += UByteArray(newSize - header.size() - volume.size(), empty);
|
|
volumeSize = newSize;
|
|
}
|
|
}
|
|
}
|
|
// Use current volume body
|
|
else {
|
|
volume = model->body(index);
|
|
|
|
// BUGBUG: volume size may change during this operation for volumes withour files in them
|
|
// but such volumes are fairly rare
|
|
}
|
|
|
|
// Check new volume size
|
|
if ((UINT32)(header.size() + volume.size()) != volumeSize) {
|
|
msg("buildVolume: volume size can't be changed", index);
|
|
return U_INVALID_VOLUME;
|
|
}
|
|
|
|
// Reconstruction successful
|
|
volume = header + volume;
|
|
|
|
// Recalculate CRC32 in ZeroVector, if needed
|
|
if (model->text(index).contains("AppleCRC32 ")) {
|
|
// Get current CRC32 value from volume header
|
|
const UINT32 currentCrc = *(const UINT32*)(volume.constData() + 8);
|
|
// Calculate new value
|
|
UINT32 crc = crc32(0, (const UINT8*)volume.constData() + volumeHeader->HeaderLength, volume.size() - volumeHeader->HeaderLength);
|
|
// Update the value
|
|
if (currentCrc != crc) {
|
|
*(UINT32*)(volume.data() + 8) = crc;
|
|
|
|
// Recalculate header checksum
|
|
volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)volume.data();
|
|
volumeHeader->Checksum = 0;
|
|
volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength);
|
|
}
|
|
}
|
|
|
|
// Store new free space offset, if needed
|
|
if (model->text(index).contains("AppleFSO ")) {
|
|
// Get current CRC32 value from volume header
|
|
const UINT32 currentFso = *(const UINT32*)(volume.constData() + 12);
|
|
// Update the value
|
|
if (freeSpaceOffset != 0 && currentFso != freeSpaceOffset) {
|
|
*(UINT32*)(volume.data() + 12) = freeSpaceOffset;
|
|
|
|
// Recalculate header checksum
|
|
volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)volume.data();
|
|
volumeHeader->Checksum = 0;
|
|
volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength);
|
|
}
|
|
}
|
|
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildNvramVolume(const UModelIndex & index, UByteArray & volume)
|
|
{
|
|
if (!index.isValid())
|
|
return U_SUCCESS;
|
|
|
|
USTATUS result;
|
|
|
|
// No action
|
|
if (model->action(index) == Actions::NoAction) {
|
|
volume = model->header(index) + model->body(index);
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Remove) {
|
|
volume.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Replace ||
|
|
model->action(index) == Actions::Rebuild) {
|
|
UByteArray header = model->header(index);
|
|
UByteArray body = model->body(index);
|
|
EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)header.data();
|
|
|
|
// Check sanity of HeaderLength
|
|
if (volumeHeader->HeaderLength > header.size()) {
|
|
msg(UString("buildNvramVolume: invalid volume header length, reconstruction is not possible"), index);
|
|
return U_INVALID_VOLUME;
|
|
}
|
|
|
|
// Recalculate volume header checksum
|
|
volumeHeader->Checksum = 0;
|
|
volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength);
|
|
|
|
volume.clear();
|
|
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
UModelIndex currentIndex = index.child(i, 0);
|
|
UByteArray store;
|
|
|
|
result = buildNvramStore(currentIndex, store);
|
|
if(result)
|
|
return result;
|
|
|
|
// Element reconstruct success
|
|
volume += store;
|
|
}
|
|
|
|
// Volume reconstruct success
|
|
volume = header + volume;
|
|
|
|
|
|
return U_SUCCESS;
|
|
|
|
}
|
|
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildNvramStore(const UModelIndex & index, UByteArray & store)
|
|
{
|
|
UByteArray header = model->header(index);
|
|
UByteArray body = model->body(index);
|
|
UINT8 type = model->type(index);
|
|
|
|
if(model->action(index) == Actions::Remove) {
|
|
header.clear();
|
|
body.clear();
|
|
}
|
|
else if(model->action(index) == Actions::Replace ||
|
|
model->action(index) == Actions::Rebuild) {
|
|
if(type == Types::FdcStore) {
|
|
// Recalculate store header
|
|
EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)header.data();
|
|
volumeHeader->Checksum = 0;
|
|
volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength);
|
|
|
|
// Rebuild VSS or VSS2 volume inside
|
|
UByteArray vssStore;
|
|
buildNvramStore(index.child(0, 0), vssStore);
|
|
body = vssStore;
|
|
}
|
|
else if(type == Types::VssStore || type == Types::Vss2Store) {
|
|
if(model->rowCount(index)) {
|
|
body.clear();
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
UModelIndex currentIndex = index.child(i, 0);
|
|
UByteArray currentHeader = model->header(currentIndex);
|
|
UByteArray currentBody = model->body(currentIndex);
|
|
UINT8 currentAction = model->action(currentIndex);
|
|
|
|
if(currentAction == Actions::Remove) {
|
|
currentHeader.clear();
|
|
currentBody.clear();
|
|
}
|
|
else if(currentAction == Actions::Rebuild ||
|
|
currentAction == Actions::Replace) {
|
|
// Recalculate all Apple variables crc's
|
|
if(model->subtype(currentIndex) == Subtypes::AppleVssEntry) {
|
|
VSS_APPLE_VARIABLE_HEADER* appleVariableHeader = (VSS_APPLE_VARIABLE_HEADER*)currentHeader.data();
|
|
appleVariableHeader->DataCrc32 = crc32(0, (const UINT8*)currentBody.constData(), currentBody.size());
|
|
//appleVariableHeader->DataSize = currentBody.size();
|
|
}
|
|
}
|
|
body += currentHeader + currentBody;
|
|
}
|
|
}
|
|
}
|
|
else if(type == Types::FsysStore) {
|
|
UByteArray store = header + body;
|
|
|
|
// Recalculate store checksum
|
|
UINT32 calculatedCrc = crc32(0, (const UINT8*)store.constData(), (const UINT32)store.size() - sizeof(UINT32));
|
|
// Write new checksum
|
|
UINT32 *crc = reinterpret_cast<UINT32 *>(body.data() + body.size() - sizeof(UINT32));
|
|
std::memcpy(crc, &calculatedCrc, sizeof(UINT32));
|
|
}
|
|
else if(type == Types::EvsaStore) {
|
|
UByteArray store = header + body;
|
|
|
|
// Recalculate header checksum
|
|
const EVSA_STORE_ENTRY* evsaStoreHeader = (const EVSA_STORE_ENTRY*)store.constData();
|
|
UINT8 storeCrc = calculateChecksum8(((const UINT8*)evsaStoreHeader) + 2, evsaStoreHeader->Header.Size - 2);
|
|
// Write new checksum
|
|
EVSA_ENTRY_HEADER* evsaEntryHeader = (EVSA_ENTRY_HEADER*)header.data();
|
|
evsaEntryHeader->Checksum = storeCrc;
|
|
|
|
// Recalculate all crc's
|
|
if(model->rowCount(index)) {
|
|
body.clear();
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
UModelIndex currentIndex = index.child(i, 0);
|
|
UByteArray currentHeader = model->header(currentIndex);
|
|
UByteArray currentBody = model->body(currentIndex);
|
|
UINT8 currentSubtype = model->subtype(currentIndex);
|
|
UINT8 currentAction = model->action(currentIndex);
|
|
|
|
if(currentAction == Actions::Remove) {
|
|
currentHeader.clear();
|
|
currentBody.clear();
|
|
}
|
|
else if(currentAction == Actions::Rebuild ||
|
|
currentAction == Actions::Replace) {
|
|
|
|
if(currentSubtype == Subtypes::DataEvsaEntry ||
|
|
currentSubtype == Subtypes::GuidEvsaEntry ||
|
|
currentSubtype == Subtypes::NameEvsaEntry) {
|
|
UByteArray currentStore = currentHeader + currentBody;
|
|
|
|
// Recalculate header checksum
|
|
const EVSA_STORE_ENTRY* evsaStoreHeader = (const EVSA_STORE_ENTRY*)currentStore.constData();
|
|
UINT8 entryCrc = calculateChecksum8(((const UINT8*)evsaStoreHeader) + 2, evsaStoreHeader->Header.Size - 2);
|
|
// Write new checksum
|
|
EVSA_ENTRY_HEADER* evsaEntryHeader = (EVSA_ENTRY_HEADER*)currentHeader.data();
|
|
evsaEntryHeader->Checksum = entryCrc;
|
|
}
|
|
}
|
|
|
|
body += currentHeader + currentBody;
|
|
}
|
|
}
|
|
}
|
|
else if(type == Types::FtwStore) {
|
|
// Recalculate block header checksum
|
|
EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32* crcFtwBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32*)header.data();
|
|
crcFtwBlockHeader->Crc = crc32(0, (const UINT8*)crcFtwBlockHeader, header.size());
|
|
}
|
|
}
|
|
|
|
// Rebuild end
|
|
store.clear();
|
|
store = header + body;
|
|
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildPadFile(const UByteArray & guid, const UINT32 size, const UINT8 revision, const UINT8 erasePolarity, UByteArray & pad)
|
|
{
|
|
if (size < sizeof(EFI_FFS_FILE_HEADER) || erasePolarity == ERASE_POLARITY_UNKNOWN)
|
|
return U_INVALID_PARAMETER;
|
|
|
|
if (size >= 0xFFFFFF) // TODO: large file support
|
|
return U_INVALID_PARAMETER;
|
|
|
|
pad = UByteArray(size - guid.size(), erasePolarity == ERASE_POLARITY_TRUE ? '\xFF' : '\x00');
|
|
pad = guid + pad;
|
|
EFI_FFS_FILE_HEADER* header = (EFI_FFS_FILE_HEADER*)pad.data();
|
|
uint32ToUint24(size, header->Size);
|
|
header->Attributes = 0x00;
|
|
header->Type = EFI_FV_FILETYPE_PAD;
|
|
header->State = EFI_FILE_HEADER_CONSTRUCTION | EFI_FILE_HEADER_VALID | EFI_FILE_DATA_VALID;
|
|
// Invert state bits if erase polarity is true
|
|
if (erasePolarity == ERASE_POLARITY_TRUE)
|
|
header->State = ~header->State;
|
|
|
|
// Calculate header checksum
|
|
header->IntegrityCheck.Checksum.Header = 0;
|
|
header->IntegrityCheck.Checksum.File = 0;
|
|
header->IntegrityCheck.Checksum.Header = calculateChecksum8((const UINT8*)header, sizeof(EFI_FFS_FILE_HEADER) - 1);
|
|
|
|
// Set data checksum
|
|
if (revision == 1)
|
|
header->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM;
|
|
else
|
|
header->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM2;
|
|
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
USTATUS FfsBuilder::buildFile(const UModelIndex & index, const UINT8 revision, const UINT8 erasePolarity, const UINT32 base, UByteArray & reconstructed)
|
|
{
|
|
if (!index.isValid())
|
|
return U_SUCCESS;
|
|
|
|
USTATUS result;
|
|
|
|
// No action
|
|
if (model->action(index) == Actions::NoAction) {
|
|
reconstructed = model->header(index) + model->body(index);
|
|
const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)model->header(index).constData();
|
|
// Append tail, if needed
|
|
if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) {
|
|
UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header;
|
|
UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File;
|
|
reconstructed += UByteArray(1, (char)ht);
|
|
reconstructed += UByteArray(1, (char)ft);
|
|
}
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Remove) {
|
|
reconstructed.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Insert ||
|
|
model->action(index) == Actions::Replace ||
|
|
model->action(index) == Actions::Rebuild) {
|
|
UByteArray header = model->header(index);
|
|
EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)header.data();
|
|
|
|
// Check erase polarity
|
|
if (erasePolarity == ERASE_POLARITY_UNKNOWN) {
|
|
msg("buildFile: unknown erase polarity", index);
|
|
return U_INVALID_PARAMETER;
|
|
}
|
|
|
|
// Check file state
|
|
// Check top reserved bit of file state to determine it's original erase polarity
|
|
UINT8 state = fileHeader->State;
|
|
if (state & EFI_FILE_ERASE_POLARITY)
|
|
state = ~state;
|
|
|
|
// Order of this checks must be preserved
|
|
// Check file to have valid state, or delete it otherwise
|
|
if (state & EFI_FILE_HEADER_INVALID) {
|
|
// File marked to have invalid header and must be deleted
|
|
// Do not add anything to queue
|
|
msg("buildFile: file is HEADER_INVALID state, and will be removed from reconstructed image", index);
|
|
return U_SUCCESS;
|
|
}
|
|
else if (state & EFI_FILE_DELETED) {
|
|
// File marked to have been deleted form and must be deleted
|
|
// Do not add anything to queue
|
|
msg("buildFile: file is in DELETED state, and will be removed from reconstructed image", index);
|
|
return U_SUCCESS;
|
|
}
|
|
else if (state & EFI_FILE_MARKED_FOR_UPDATE) {
|
|
// File is marked for update, the mark must be removed
|
|
msg("buildFile: file's MARKED_FOR_UPDATE state cleared", index);
|
|
}
|
|
else if (state & EFI_FILE_DATA_VALID) {
|
|
// File is in good condition, reconstruct it
|
|
}
|
|
else if (state & EFI_FILE_HEADER_VALID) {
|
|
// Header is valid, but data is not, so file must be deleted
|
|
msg("buildFile: file is in HEADER_VALID (but not in DATA_VALID) state, and will be removed from reconstructed image", index);
|
|
return U_SUCCESS;
|
|
}
|
|
else if (state & EFI_FILE_HEADER_CONSTRUCTION) {
|
|
// Header construction not finished, so file must be deleted
|
|
msg("buildFile: file is in HEADER_CONSTRUCTION (but not in DATA_VALID) state, and will be removed from reconstructed image", index);
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
// Reconstruct file body
|
|
if (model->rowCount(index)) {
|
|
reconstructed.clear();
|
|
// Construct new file body
|
|
// File contains raw data, must be parsed as region without header
|
|
if (model->subtype(index) == EFI_FV_FILETYPE_ALL || model->subtype(index) == EFI_FV_FILETYPE_RAW) {
|
|
result = buildRawArea(index, reconstructed, false);
|
|
if (result)
|
|
return result;
|
|
}
|
|
// File contains sections
|
|
else {
|
|
UINT32 offset = 0;
|
|
UINT32 headerSize = sizeof(EFI_FFS_FILE_HEADER);
|
|
if (revision > 1 && (fileHeader->Attributes & FFS_ATTRIB_LARGE_FILE)) {
|
|
headerSize = sizeof(EFI_FFS_FILE_HEADER2);
|
|
}
|
|
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
// Align to 4 byte boundary
|
|
UINT8 alignment = offset % 4;
|
|
if (alignment) {
|
|
alignment = 4 - alignment;
|
|
offset += alignment;
|
|
reconstructed += UByteArray(alignment, '\x00');
|
|
}
|
|
|
|
// Calculate section base
|
|
UINT32 sectionBase = base ? base + headerSize + offset : 0;
|
|
UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3];
|
|
UINT32 fileAlignment = (UINT32)(1UL << alignmentPower);
|
|
UINT32 alignmentBase = base + headerSize;
|
|
if (alignmentBase % fileAlignment) {
|
|
// File will be unaligned if added as is, so we must add pad file before it
|
|
// Determine pad file size
|
|
UINT32 size = fileAlignment - (alignmentBase % fileAlignment);
|
|
// Required padding is smaller then minimal pad file size
|
|
while (size < sizeof(EFI_FFS_FILE_HEADER)) {
|
|
size += fileAlignment;
|
|
}
|
|
// Adjust file base to incorporate pad file that will be added to align it
|
|
sectionBase += size;
|
|
}
|
|
|
|
// Reconstruct section
|
|
UByteArray section;
|
|
result = buildSection(index.child(i, 0), sectionBase, section);
|
|
if (result)
|
|
return result;
|
|
|
|
// Check for empty section
|
|
if (section.isEmpty())
|
|
continue;
|
|
|
|
// Append current section to new file body
|
|
reconstructed += section;
|
|
|
|
// Change current file offset
|
|
offset += section.size();
|
|
}
|
|
}
|
|
}
|
|
// Use current file body
|
|
else
|
|
reconstructed = model->body(index);
|
|
|
|
// Correct file size
|
|
UINT8 tailSize = (revision == 1 && (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT)) ? sizeof(UINT16) : 0;
|
|
if (revision > 1 && (fileHeader->Attributes & FFS_ATTRIB_LARGE_FILE)) {
|
|
uint32ToUint24(EFI_SECTION2_IS_USED, fileHeader->Size);
|
|
EFI_FFS_FILE_HEADER2* fileHeader2 = (EFI_FFS_FILE_HEADER2*)fileHeader;
|
|
fileHeader2->ExtendedSize = sizeof(EFI_FFS_FILE_HEADER2) + reconstructed.size() + tailSize;
|
|
}
|
|
else {
|
|
if (sizeof(EFI_FFS_FILE_HEADER) + reconstructed.size() + tailSize > 0xFFFFFF) {
|
|
msg("buildFile: resulting file size is too big", index);
|
|
return U_INVALID_FILE;
|
|
}
|
|
uint32ToUint24(sizeof(EFI_FFS_FILE_HEADER) + reconstructed.size() + tailSize, fileHeader->Size);
|
|
}
|
|
|
|
// Recalculate header checksum
|
|
fileHeader->IntegrityCheck.Checksum.Header = 0;
|
|
fileHeader->IntegrityCheck.Checksum.File = 0;
|
|
fileHeader->IntegrityCheck.Checksum.Header = 0x100 - (calculateSum8((const UINT8*)header.constData(), header.size()) - fileHeader->State);
|
|
|
|
// Recalculate data checksum, if needed
|
|
if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) {
|
|
fileHeader->IntegrityCheck.Checksum.File = calculateChecksum8((const UINT8*)reconstructed.constData(), reconstructed.size());
|
|
}
|
|
else if (revision == 1)
|
|
fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM;
|
|
else
|
|
fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM2;
|
|
|
|
// Append tail, if needed
|
|
if (revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) {
|
|
UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header;
|
|
UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File;
|
|
reconstructed += UByteArray(1, (char)ht);
|
|
reconstructed += UByteArray(1, (char)ft);
|
|
}
|
|
|
|
// Set file state
|
|
state = EFI_FILE_DATA_VALID | EFI_FILE_HEADER_VALID | EFI_FILE_HEADER_CONSTRUCTION;
|
|
if (erasePolarity == ERASE_POLARITY_TRUE)
|
|
state = ~state;
|
|
fileHeader->State = state;
|
|
|
|
// Reconstruction successful
|
|
reconstructed = header + reconstructed;
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
// All other actions are not supported
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
USTATUS FfsBuilder::buildSection(const UModelIndex & index, const UINT32 base, UByteArray & reconstructed)
|
|
{
|
|
if (!index.isValid())
|
|
return U_SUCCESS;
|
|
|
|
USTATUS result;
|
|
|
|
// No action
|
|
if (model->action(index) == Actions::NoAction) {
|
|
reconstructed = model->header(index) + model->body(index);
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Remove) {
|
|
reconstructed.clear();
|
|
return U_SUCCESS;
|
|
}
|
|
else if (model->action(index) == Actions::Insert ||
|
|
model->action(index) == Actions::Replace ||
|
|
model->action(index) == Actions::Rebuild ||
|
|
model->action(index) == Actions::Rebase) {
|
|
UByteArray header = model->header(index);
|
|
EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)header.data();
|
|
bool extended = false;
|
|
UByteArray data = model->parsingData(index);
|
|
|
|
if (uint24ToUint32(commonHeader->Size) == 0xFFFFFF) {
|
|
extended = true;
|
|
}
|
|
|
|
// Reconstruct section with children
|
|
if (model->rowCount(index)) {
|
|
reconstructed.clear();
|
|
// Construct new section body
|
|
UINT32 offset = 0;
|
|
|
|
// Reconstruct section body
|
|
for (int i = 0; i < model->rowCount(index); i++) {
|
|
// Align to 4 byte boundary
|
|
UINT8 alignment = offset % 4;
|
|
if (alignment) {
|
|
alignment = 4 - alignment;
|
|
offset += alignment;
|
|
reconstructed += UByteArray(alignment, '\x00');
|
|
}
|
|
|
|
// Reconstruct subsections
|
|
UByteArray section;
|
|
result = build(index.child(i, 0), section);
|
|
if (result)
|
|
return result;
|
|
|
|
// Check for empty queue
|
|
if (section.isEmpty())
|
|
continue;
|
|
|
|
// Append current subsection to new section body
|
|
reconstructed += section;
|
|
|
|
// Change current file offset
|
|
offset += section.size();
|
|
}
|
|
|
|
// Only this 2 sections can have compressed body
|
|
UINT8 compression = model->compressed(index) ? model->compression(index) : COMPRESSION_ALGORITHM_NONE;
|
|
if (model->subtype(index) == EFI_SECTION_COMPRESSION) {
|
|
EFI_COMPRESSION_SECTION* compessionHeader = (EFI_COMPRESSION_SECTION*)header.data();
|
|
// Set new uncompressed size
|
|
compessionHeader->UncompressedLength = reconstructed.size();
|
|
// Compress new section body
|
|
UByteArray compressed;
|
|
result = compress(reconstructed, compression, compressed);
|
|
if (result)
|
|
return result;
|
|
|
|
// Correct compression type
|
|
if (compression == COMPRESSION_ALGORITHM_NONE)
|
|
compessionHeader->CompressionType = EFI_NOT_COMPRESSED;
|
|
else if (compression == COMPRESSION_ALGORITHM_LZMA || compression == COMPRESSION_ALGORITHM_IMLZMA)
|
|
compessionHeader->CompressionType = EFI_CUSTOMIZED_COMPRESSION;
|
|
else if (compression == COMPRESSION_ALGORITHM_EFI11 || compression == COMPRESSION_ALGORITHM_TIANO)
|
|
compessionHeader->CompressionType = EFI_STANDARD_COMPRESSION;
|
|
else
|
|
return U_UNKNOWN_COMPRESSION_ALGORITHM;
|
|
|
|
// Replace new section body
|
|
reconstructed = compressed;
|
|
}
|
|
else if (model->subtype(index) == EFI_SECTION_GUID_DEFINED) {
|
|
EFI_GUID_DEFINED_SECTION* guidDefinedHeader = (EFI_GUID_DEFINED_SECTION*)header.data();
|
|
// Compress new section body
|
|
UByteArray compressed;
|
|
result = compress(reconstructed, compression, compressed);
|
|
if (result)
|
|
return result;
|
|
// Check for authentication status valid attribute
|
|
if (guidDefinedHeader->Attributes & EFI_GUIDED_SECTION_AUTH_STATUS_VALID) {
|
|
// CRC32 section
|
|
if (UByteArray((const char*)&guidDefinedHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_GUIDED_SECTION_CRC32) {
|
|
// Check header size
|
|
if ((UINT32)header.size() != sizeof(EFI_GUID_DEFINED_SECTION) + sizeof(UINT32)) {
|
|
msg("buildSection: invalid CRC32 section size", index);
|
|
return U_INVALID_SECTION;
|
|
}
|
|
// Calculate CRC32 of section data
|
|
UINT32 crc = crc32(0, (const UINT8*)compressed.constData(), compressed.size());
|
|
// Store new CRC32
|
|
*(UINT32*)(header.data() + sizeof(EFI_GUID_DEFINED_SECTION)) = crc;
|
|
}
|
|
else {
|
|
const char *guidStr = guidToUString(guidDefinedHeader->SectionDefinitionGuid).toLocal8Bit();
|
|
msg(usprintf("buildSection: GUID defined section signature can become invalid (%s)", guidStr), index);
|
|
}
|
|
}
|
|
// Check for Intel signed section
|
|
if (guidDefinedHeader->Attributes & EFI_GUIDED_SECTION_PROCESSING_REQUIRED
|
|
&& UByteArray((const char*)&guidDefinedHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_FIRMWARE_CONTENTS_SIGNED_GUID) {
|
|
const char *guidStr = guidToUString(guidDefinedHeader->SectionDefinitionGuid).toLocal8Bit();
|
|
msg(usprintf("buildSection: GUID defined section signature can become invalid (%s)", guidStr), index);
|
|
}
|
|
// Replace new section body
|
|
reconstructed = compressed;
|
|
}
|
|
else if (compression != COMPRESSION_ALGORITHM_NONE) {
|
|
msg(usprintf("buildSection: incorrectly required compression for section of type %d", model->subtype(index)), index);
|
|
return U_INVALID_SECTION;
|
|
}
|
|
}
|
|
// Leaf section
|
|
else {
|
|
reconstructed = model->body(index);
|
|
}
|
|
|
|
// Correct section size
|
|
if (extended) {
|
|
EFI_COMMON_SECTION_HEADER2 * extHeader = (EFI_COMMON_SECTION_HEADER2*)commonHeader;
|
|
extHeader->ExtendedSize = header.size() + reconstructed.size();
|
|
uint32ToUint24(0xFFFFFF, commonHeader->Size);
|
|
}
|
|
else {
|
|
uint32ToUint24(header.size() + reconstructed.size(), commonHeader->Size);
|
|
}
|
|
|
|
// Rebase PE32 or TE image, if needed
|
|
if ((model->subtype(index) == EFI_SECTION_PE32 || model->subtype(index) == EFI_SECTION_TE) &&
|
|
(model->subtype(index.parent()) == EFI_FV_FILETYPE_PEI_CORE ||
|
|
model->subtype(index.parent()) == EFI_FV_FILETYPE_PEIM ||
|
|
model->subtype(index.parent()) == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER)) {
|
|
UINT16 teFixup = 0;
|
|
|
|
// Most EFI images today include teFixup in ImageBase value,
|
|
// which doesn't follow the UEFI spec, but is so popular that
|
|
// only a few images out of thousands are different
|
|
|
|
// There are some heuristics possible here to detect if an entry point is calculated correctly
|
|
// or needs a proper fixup, but new_engine already have them and it's better to work on proper
|
|
// builder for it than trying to fix this mess
|
|
|
|
//if (model->subtype(index) == EFI_SECTION_TE) {
|
|
// const EFI_IMAGE_TE_HEADER* teHeader = (const EFI_IMAGE_TE_HEADER*)model->body(index).constData();
|
|
// teFixup = teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER);
|
|
//
|
|
|
|
if (base) {
|
|
result = rebase(reconstructed, base - teFixup + header.size());
|
|
if (result) {
|
|
msg("buildSection: executable section rebase failed", index);
|
|
return result;
|
|
}
|
|
|
|
// Special case of PEI Core rebase
|
|
if (model->subtype(index.parent()) == EFI_FV_FILETYPE_PEI_CORE) {
|
|
result = getEntryPoint(reconstructed, parser->newPeiCoreEntryPoint);
|
|
if (result)
|
|
msg("buildSection: can't get entry point of PEI core", index);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reconstruction successful
|
|
reconstructed = header + reconstructed;
|
|
|
|
return U_SUCCESS;
|
|
}
|
|
|
|
// All other actions are not supported
|
|
return U_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
|