diff --git a/UEFITool/ffsops.cpp b/UEFITool/ffsops.cpp index c16c78a..53ac90b 100644 --- a/UEFITool/ffsops.cpp +++ b/UEFITool/ffsops.cpp @@ -94,6 +94,17 @@ STATUS FfsOperations::extract(const QModelIndex & index, QString & name, QByteAr extracted.clear(); extracted.append(model->body(index)); } + else if (mode == EXTRACT_MODE_BODY_UNCOMPRESSED) { + name += tr("_body_unc"); + // Extract without header and tail, uncompressed + extracted.clear(); + // There is no need to redo decompression, we can use child items + for (int i = 0; i < model->rowCount(index); i++) { + QModelIndex childIndex = index.child(i, 0); + extracted.append(model->header(childIndex)); + extracted.append(model->body(childIndex)); + } + } else return ERR_UNKNOWN_EXTRACT_MODE; diff --git a/UEFITool/uefitool.cpp b/UEFITool/uefitool.cpp index 564dfff..d0a7e24 100644 --- a/UEFITool/uefitool.cpp +++ b/UEFITool/uefitool.cpp @@ -17,7 +17,7 @@ UEFITool::UEFITool(QWidget *parent) : QMainWindow(parent), ui(new Ui::UEFITool), -version(tr("0.30.0_alpha6")) +version(tr("0.30.0_alpha7")) { clipboard = QApplication::clipboard(); @@ -35,10 +35,12 @@ version(tr("0.30.0_alpha6")) // Connect signals to slots connect(ui->actionOpenImageFile, SIGNAL(triggered()), this, SLOT(openImageFile())); + connect(ui->actionOpenImageFileInNewWindow, SIGNAL(triggered()), this, SLOT(openImageFileInNewWindow())); connect(ui->actionSaveImageFile, SIGNAL(triggered()), this, SLOT(saveImageFile())); connect(ui->actionSearch, SIGNAL(triggered()), this, SLOT(search())); connect(ui->actionExtract, SIGNAL(triggered()), this, SLOT(extractAsIs())); connect(ui->actionExtractBody, SIGNAL(triggered()), this, SLOT(extractBody())); + connect(ui->actionExtractBodyUncompressed, SIGNAL(triggered()), this, SLOT(extractBodyUncompressed())); connect(ui->actionInsertInto, SIGNAL(triggered()), this, SLOT(insertInto())); connect(ui->actionInsertBefore, SIGNAL(triggered()), this, SLOT(insertBefore())); connect(ui->actionInsertAfter, SIGNAL(triggered()), this, SLOT(insertAfter())); @@ -93,6 +95,11 @@ UEFITool::~UEFITool() delete ui; } +void UEFITool::setProgramPath(QString path) +{ + currentProgramPath = path; +}; + void UEFITool::init() { // Clear components @@ -162,6 +169,7 @@ void UEFITool::populateUi(const QModelIndex ¤t) ui->actionExtract->setDisabled(model->hasEmptyHeader(current) && model->hasEmptyBody(current)); //ui->actionRebuild->setEnabled(type == Types::Volume || type == Types::File || type == Types::Section); ui->actionExtractBody->setDisabled(model->hasEmptyBody(current)); + ui->actionExtractBodyUncompressed->setEnabled(enableExtractBodyUncompressed(current)); //ui->actionRemove->setEnabled(type == Types::Volume || type == Types::File || type == Types::Section); //ui->actionInsertInto->setEnabled((type == Types::Volume && subtype != Subtypes::UnknownVolume) || // (type == Types::File && subtype != EFI_FV_FILETYPE_ALL && subtype != EFI_FV_FILETYPE_RAW && subtype != EFI_FV_FILETYPE_PAD) || @@ -173,6 +181,30 @@ void UEFITool::populateUi(const QModelIndex ¤t) ui->actionMessagesCopy->setEnabled(false); } +bool UEFITool::enableExtractBodyUncompressed(const QModelIndex ¤t) +{ + if (current.isValid() && model->type(current) == Types::Section && + (model->subtype(current) == EFI_SECTION_COMPRESSION || model->subtype(current) == EFI_SECTION_GUID_DEFINED)) { + // Get parsing data + PARSING_DATA pdata = parsingDataFromQModelIndex(current); + + if (model->subtype(current) == EFI_SECTION_COMPRESSION && + pdata.section.compressed.algorithm != COMPRESSION_ALGORITHM_NONE && + pdata.section.compressed.algorithm != COMPRESSION_ALGORITHM_UNKNOWN) { //Compressed section + return true; + } + else if (model->subtype(current) == EFI_SECTION_GUID_DEFINED && + (pdata.section.guidDefined.attributes & EFI_GUIDED_SECTION_PROCESSING_REQUIRED)) { + QByteArray guid = QByteArray((const char*)&pdata.section.guidDefined.guid, sizeof(EFI_GUID)); + if (guid == EFI_GUIDED_SECTION_TIANO || guid == EFI_GUIDED_SECTION_LZMA) { + return true; + } + } + } + + return false; +} + void UEFITool::search() { if (searchDialog->exec() != QDialog::Accepted) @@ -378,6 +410,8 @@ void UEFITool::replace(const UINT8 mode) path = QFileDialog::getOpenFileName(this, tr("Select volume file to replace body"), currentDir, "Volume files (*.vol *.bin);;All files (*)"); else if (model->subtype(index) == EFI_SECTION_RAW) path = QFileDialog::getOpenFileName(this, tr("Select raw file to replace body"), currentDir, "Raw files (*.raw *.bin);;All files (*)"); + else if (model->subtype(index) == EFI_SECTION_PE32 || model->subtype(index) == EFI_SECTION_TE || model->subtype(index) == EFI_SECTION_PIC) + path = QFileDialog::getOpenFileName(this, tr("Select EFI executable file to replace body"), currentDir, "EFI executable files (*.efi *.dxe *.pei *.bin);;All files (*)"); else path = QFileDialog::getOpenFileName(this, tr("Select file to replace body"), currentDir, "Binary files (*.bin);;All files (*)"); } @@ -425,6 +459,11 @@ void UEFITool::extractBody() extract(EXTRACT_MODE_BODY); } +void UEFITool::extractBodyUncompressed() +{ + extract(EXTRACT_MODE_BODY_UNCOMPRESSED); +} + void UEFITool::extract(const UINT8 mode) { QModelIndex index = ui->structureTreeView->selectionModel()->currentIndex(); @@ -439,7 +478,7 @@ void UEFITool::extract(const UINT8 mode) return; } - name = currentDir + QDir::separator() + name; + name = QDir::toNativeSeparators(currentDir + QDir::separator() + name); UINT8 type = model->type(index); QString path; @@ -470,7 +509,7 @@ void UEFITool::extract(const UINT8 mode) path = QFileDialog::getSaveFileName(this, tr("Save object to file"), name + ".bin", "Binary files (*.bin);;All files (*)"); } } - else if (mode == EXTRACT_MODE_BODY) { + else if (mode == EXTRACT_MODE_BODY || mode == EXTRACT_MODE_BODY_UNCOMPRESSED) { switch (type) { case Types::Capsule: path = QFileDialog::getSaveFileName(this, tr("Save capsule body to image file"), name + ".rom", "Image files (*.rom *.bin);;All files (*)"); @@ -492,6 +531,8 @@ void UEFITool::extract(const UINT8 mode) path = QFileDialog::getSaveFileName(this, tr("Save section body to volume file"), name + ".vol", "Volume files (*.vol *.bin);;All files (*)"); else if (model->subtype(index) == EFI_SECTION_RAW) path = QFileDialog::getSaveFileName(this, tr("Save section body to raw file"), name + ".raw", "Raw files (*.raw *.bin);;All files (*)"); + else if (model->subtype(index) == EFI_SECTION_PE32 || model->subtype(index) == EFI_SECTION_TE || model->subtype(index) == EFI_SECTION_PIC) + path = QFileDialog::getSaveFileName(this, tr("Save section body to EFI executable file"), name + ".efi", "EFI executable files (*.efi *.dxe *.pei *.bin);;All files (*)"); else path = QFileDialog::getSaveFileName(this, tr("Save section body to file"), name + ".bin", "Binary files (*.bin);;All files (*)"); } @@ -573,10 +614,18 @@ void UEFITool::saveImageFile() void UEFITool::openImageFile() { - QString path = QFileDialog::getOpenFileName(this, tr("Open BIOS image file"), currentDir, "BIOS image files (*.rom *.bin *.cap *.bio *.fd *.wph *.efi *.dec);;All files (*)"); + QString path = QFileDialog::getOpenFileName(this, tr("Open BIOS image file"), currentDir, "BIOS image files (*.rom *.bin *.cap *.bio *.fd *.wph *.dec);;All files (*)"); openImageFile(path); } +void UEFITool::openImageFileInNewWindow() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Open BIOS image file in new window"), currentDir, "BIOS image files (*.rom *.bin *.cap *.bio *.fd *.wph *.dec);;All files (*)"); + if (path.trimmed().isEmpty()) + return; + QProcess::startDetached(currentProgramPath, QStringList(path)); +} + void UEFITool::openImageFile(QString path) { if (path.trimmed().isEmpty()) @@ -636,7 +685,7 @@ void UEFITool::copyMessage() if (ui->messagesTabWidget->currentIndex() == 0) // Parser tab clipboard->setText(ui->parserMessagesListWidget->currentItem()->text()); else if (ui->messagesTabWidget->currentIndex() == 1) // Search tab - clipboard->setText(ui->finderMessagesListWidget->currentItem()->text()); + clipboard->setText(ui->finderMessagesListWidget->currentItem()->text()); } void UEFITool::copyAllMessages() diff --git a/UEFITool/uefitool.h b/UEFITool/uefitool.h index 7dd7766..321199a 100644 --- a/UEFITool/uefitool.h +++ b/UEFITool/uefitool.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,7 @@ public: ~UEFITool(); void openImageFile(QString path); + void setProgramPath(QString path); private slots: void init(); @@ -63,12 +65,14 @@ private slots: void scrollTreeView(QListWidgetItem* item); void openImageFile(); + void openImageFileInNewWindow(); void saveImageFile(); void search(); void extract(const UINT8 mode); void extractAsIs(); void extractBody(); + void extractBodyUncompressed(); void insert(const UINT8 mode); void insertInto(); @@ -104,8 +108,11 @@ private: SearchDialog* searchDialog; QClipboard* clipboard; QString currentDir; + QString currentProgramPath; const QString version; + bool enableExtractBodyUncompressed(const QModelIndex ¤t); + void dragEnterEvent(QDragEnterEvent* event); void dropEvent(QDropEvent* event); void contextMenuEvent(QContextMenuEvent* event); diff --git a/UEFITool/uefitool.ui b/UEFITool/uefitool.ui index b1eb808..a620127 100644 --- a/UEFITool/uefitool.ui +++ b/UEFITool/uefitool.ui @@ -247,6 +247,7 @@ &File + @@ -329,6 +330,7 @@ + @@ -581,6 +583,28 @@ Ctrl+Alt+C + + + O&pen image file in new window... + + + Ctrl+Shift+O + + + + + false + + + Extract &body uncompressed... + + + Uncompress and extract body of selected object to file + + + Ctrl+Alt+E + + diff --git a/UEFITool/uefitool_main.cpp b/UEFITool/uefitool_main.cpp index e5764c0..a9ac03a 100644 --- a/UEFITool/uefitool_main.cpp +++ b/UEFITool/uefitool_main.cpp @@ -23,6 +23,7 @@ int main(int argc, char *argv[]) a.setApplicationName("UEFITool"); UEFITool w; + w.setProgramPath(a.arguments().at(0)); if (a.arguments().length() > 1) w.openImageFile(a.arguments().at(1)); w.show(); diff --git a/common/basetypes.h b/common/basetypes.h index 9adef10..65eddd3 100644 --- a/common/basetypes.h +++ b/common/basetypes.h @@ -77,6 +77,7 @@ typedef UINT8 STATUS; #define ERR_UNKNOWN_RELOCATION_TYPE 31 #define ERR_DIR_ALREADY_EXIST 32 #define ERR_DIR_CREATE 33 +#define ERR_TRUNCATED_IMAGE 34 #define ERR_NOT_IMPLEMENTED 0xFF // UDK porting definitions @@ -105,8 +106,9 @@ typedef UINT8 STATUS; #define CREATE_MODE_AFTER 3 // Item extract modes -#define EXTRACT_MODE_AS_IS 0 -#define EXTRACT_MODE_BODY 1 +#define EXTRACT_MODE_AS_IS 0 +#define EXTRACT_MODE_BODY 1 +#define EXTRACT_MODE_BODY_UNCOMPRESSED 2 // Item replace modes #define REPLACE_MODE_AS_IS 0 diff --git a/common/ffs.h b/common/ffs.h index ef13e67..9c6848a 100644 --- a/common/ffs.h +++ b/common/ffs.h @@ -49,6 +49,26 @@ typedef struct _EFI_CAPSULE_HEADER { const QByteArray EFI_CAPSULE_GUID ("\xBD\x86\x66\x3B\x76\x0D\x30\x40\xB7\x0E\xB5\x51\x9E\x2F\xC5\xA0", 16); +// Intel capsule GUID +const QByteArray INTEL_CAPSULE_GUID +("\xB9\x82\x91\x53\xB5\xAB\x91\x43\xB6\x9A\xE3\xA9\x43\xF7\x2F\xCC", 16); + +// Lenovo capsule GUID +const QByteArray LENOVO_CAPSULE_GUID +("\x8B\xA6\x3C\x4A\x23\x77\xFB\x48\x80\x3D\x57\x8C\xC1\xFE\xC4\x4D", 16); + +// Toshiba EFI Capsule header +typedef struct _TOSHIBA_CAPSULE_HEADER { + EFI_GUID CapsuleGuid; + UINT32 HeaderSize; + UINT32 FullSize; + UINT32 Flags; +} TOSHIBA_CAPSULE_HEADER; + +// Toshiba capsule GUID +const QByteArray TOSHIBA_CAPSULE_GUID +("\x62\x70\xE0\x3B\x51\x1D\xD2\x45\x83\x2B\xF0\x93\x25\x7E\xD4\x61", 16); + // AMI Aptio extended capsule header typedef struct _APTIO_CAPSULE_HEADER { EFI_CAPSULE_HEADER CapsuleHeader; diff --git a/common/ffsparser.cpp b/common/ffsparser.cpp index 5765500..7fef534 100644 --- a/common/ffsparser.cpp +++ b/common/ffsparser.cpp @@ -70,7 +70,9 @@ STATUS FfsParser::parseImageFile(const QByteArray & buffer, const QModelIndex & // Check buffer for being normal EFI capsule header UINT32 capsuleHeaderSize = 0; QModelIndex index; - if (buffer.startsWith(EFI_CAPSULE_GUID)) { + if (buffer.startsWith(EFI_CAPSULE_GUID) + || buffer.startsWith(INTEL_CAPSULE_GUID) + || buffer.startsWith(LENOVO_CAPSULE_GUID)) { // Get info const EFI_CAPSULE_HEADER* capsuleHeader = (const EFI_CAPSULE_HEADER*)buffer.constData(); capsuleHeaderSize = capsuleHeader->HeaderSize; @@ -91,6 +93,28 @@ STATUS FfsParser::parseImageFile(const QByteArray & buffer, const QModelIndex & // Add tree item index = model->addItem(Types::Capsule, Subtypes::UefiCapsule, name, QString(), info, header, body, parsingDataToQByteArray(pdata), root); } + // Check buffer for being Toshiba capsule header + else if (buffer.startsWith(TOSHIBA_CAPSULE_GUID)) { + // Get info + const TOSHIBA_CAPSULE_HEADER* capsuleHeader = (const TOSHIBA_CAPSULE_HEADER*)buffer.constData(); + capsuleHeaderSize = capsuleHeader->HeaderSize; + QByteArray header = buffer.left(capsuleHeaderSize); + QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize); + QString name = tr("UEFI capsule"); + QString info = tr("Offset: 0h\nCapsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h") + .arg(guidToQString(capsuleHeader->CapsuleGuid)) + .hexarg(buffer.size()).arg(buffer.size()) + .hexarg(capsuleHeader->HeaderSize).arg(capsuleHeader->HeaderSize) + .hexarg(capsuleHeader->FullSize - capsuleHeader->HeaderSize).arg(capsuleHeader->FullSize - capsuleHeader->HeaderSize) + .hexarg2(capsuleHeader->Flags, 8); + + // Construct parsing data + PARSING_DATA pdata = parsingDataFromQModelIndex(QModelIndex()); + pdata.fixed = TRUE; + + // Add tree item + index = model->addItem(Types::Capsule, Subtypes::ToshibaCapsule, name, QString(), info, header, body, parsingDataToQByteArray(pdata), root); + } // Check buffer for being extended Aptio signed capsule header else if (buffer.startsWith(APTIO_SIGNED_CAPSULE_GUID) || buffer.startsWith(APTIO_UNSIGNED_CAPSULE_GUID)) { bool signedCapsule = buffer.startsWith(APTIO_SIGNED_CAPSULE_GUID); @@ -411,6 +435,44 @@ STATUS FfsParser::parseIntelImage(const QByteArray & intelImage, const UINT32 pa return result; } + // Add the data after the last region as padding + UINT32 IntelDataEnd = 0; + UINT32 LastRegionOffset = offsets.last(); + if (LastRegionOffset == gbeBegin) + IntelDataEnd = gbeEnd; + else if (LastRegionOffset == meBegin) + IntelDataEnd = meEnd; + else if (LastRegionOffset == biosBegin) + IntelDataEnd = biosEnd; + else if (LastRegionOffset == pdrBegin) + IntelDataEnd = pdrEnd; + + if (IntelDataEnd > (UINT32)intelImage.size()) { // Image file is truncated + msg(tr("parseIntelImage: image size %1 (%2) is smaller than the end of last region %3 (%4), may be damaged") + .hexarg(intelImage.size()).arg(intelImage.size()) + .hexarg(IntelDataEnd).arg(IntelDataEnd), index); + return ERR_TRUNCATED_IMAGE; + } + else if (IntelDataEnd < (UINT32)intelImage.size()) { // Insert padding + QByteArray padding = bios.right(intelImage.size() - IntelDataEnd); + + // Get parent's parsing data + PARSING_DATA pdata = parsingDataFromQModelIndex(index); + + // Get info + name = tr("Padding"); + info = tr("Full size: %1h (%2)") + .hexarg(padding.size()).arg(padding.size()); + + // Construct parsing data + pdata.fixed = TRUE; + pdata.offset = IntelDataEnd; + if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); + + // Add tree item + QModelIndex paddingIndex = model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + } + // Check if the last VTF is found if (!lastVtf.isValid()) { msg(tr("parseIntelImage: not a single Volume Top File is found, the image may be corrupted"), index); @@ -831,16 +893,23 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare // Determine value of empty byte UINT8 emptyByte = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY ? '\xFF' : '\x00'; - // Check for Apple CRC32 in ZeroVector + // Check for AppleCRC32 and AppleFreeSpaceOffset in ZeroVector bool hasAppleCrc32 = false; + bool hasAppleFSO = false; UINT32 volumeSize = volume.size(); UINT32 appleCrc32 = *(UINT32*)(volume.constData() + 8); + UINT32 appleFSO = *(UINT32*)(volume.constData() + 12); if (appleCrc32 != 0) { // Calculate CRC32 of the volume body UINT32 crc = crc32(0, (const UINT8*)(volume.constData() + volumeHeader->HeaderLength), volumeSize - volumeHeader->HeaderLength); if (crc == appleCrc32) { hasAppleCrc32 = true; } + + // Check if FreeSpaceOffset is non-zero + if (appleFSO != 0) { + hasAppleFSO = true; + } } // Check header checksum by recalculating it @@ -884,6 +953,7 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare pdata.volume.alignment = alignment; pdata.volume.revision = volumeHeader->Revision; pdata.volume.hasAppleCrc32 = hasAppleCrc32; + pdata.volume.hasAppleFSO = hasAppleFSO; pdata.volume.isWeakAligned = (volumeHeader->Revision > 1 && (volumeHeader->Attributes & EFI_FVB2_WEAK_ALIGNMENT)); if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); @@ -891,6 +961,8 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare QString text; if (hasAppleCrc32) text += tr("AppleCRC32 "); + if (hasAppleFSO) + text += tr("AppleFSO "); // Add tree item UINT8 subtype = Subtypes::UnknownVolume; diff --git a/common/types.cpp b/common/types.cpp index 733efed..d693b37 100644 --- a/common/types.cpp +++ b/common/types.cpp @@ -97,7 +97,9 @@ QString itemSubtypeToQString(const UINT8 type, const UINT8 subtype) else if (subtype == Subtypes::AptioUnsignedCapsule) return QObject::tr("Aptio unsigned"); else if (subtype == Subtypes::UefiCapsule) - return QObject::tr("UEFI 2.0 "); + return QObject::tr("UEFI 2.0"); + else if (subtype == Subtypes::ToshibaCapsule) + return QObject::tr("Toshiba"); else return QObject::tr("Unknown subtype"); case Types::Region: diff --git a/common/types.h b/common/types.h index 290c5b9..9f3529d 100644 --- a/common/types.h +++ b/common/types.h @@ -56,7 +56,8 @@ namespace Subtypes { enum CapsuleSubtypes { AptioSignedCapsule = 80, AptioUnsignedCapsule, - UefiCapsule + UefiCapsule, + ToshibaCapsule }; enum VolumeSubtypes { diff --git a/common/utility.cpp b/common/utility.cpp index 2073e2b..8a5dfb7 100644 --- a/common/utility.cpp +++ b/common/utility.cpp @@ -92,6 +92,7 @@ QString errorCodeToQString(UINT8 errorCode) //case ERR_INVALID_SYMBOL: return QObject::tr("Invalid symbol"); //case ERR_NOTHING_TO_PATCH: return QObject::tr("Nothing to patch"); case ERR_DEPEX_PARSE_FAILED: return QObject::tr("Dependency expression parsing failed"); + case ERR_TRUNCATED_IMAGE: return QObject::tr("Image is truncated"); default: return QObject::tr("Unknown error %1").arg(errorCode); } }