UEFIPatch 0.2.0 / UEFITool 0.18.0

- updated EFI11/Tiano compression and decompression code to UDK2014
versions
- UEFIPatch rewritten to support offset-based patches and patterns with
placeholder symbols
This commit is contained in:
Nikolaj Schlej 2014-07-05 14:56:56 +02:00
parent a3854ad059
commit c23aef47be
11 changed files with 2227 additions and 2429 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,14 @@
/* EFI/Tiano Compress Header
/* EfiTianoCompress.h
Copyright (c) 2004 - 2008, Intel Corporation. All rights reserved.
Copyright (c) 2014, Nikolaj Schlej. All rights reserved.<BR>
Copyright (c) 2004 - 2008, Intel Corporation. All rights reserved.<BR>
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.
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
Module Name:
@ -54,12 +55,12 @@ Returns:
EFI_INVALID_PARAMETER - Parameter supplied is wrong.
--*/
INT32
UINT8
TianoCompress (
UINT8 *SrcBuffer,
UINT32 SrcSize,
UINT8 *DstBuffer,
UINT32 *DstSize
CONST VOID *SrcBuffer,
CONST UINT64 SrcSize,
VOID *DstBuffer,
UINT64 *DstSize
)
;
@ -86,12 +87,12 @@ Returns:
EFI_INVALID_PARAMETER - Parameter supplied is wrong.
--*/
INT32
UINT8
EfiCompress (
UINT8 *SrcBuffer,
UINT32 SrcSize,
UINT8 *DstBuffer,
UINT32 *DstSize
CONST VOID *SrcBuffer,
CONST UINT64 SrcSize,
VOID *DstBuffer,
UINT64 *DstSize
)
;

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
/*++
/* EfiTianoDecompress.h
Copyright (c) 2004 - 2006, Intel Corporation. All rights reserved.<BR>
Copyright (c) 2014, Nikolaj Schlej. All rights reserved.<BR>
Copyright (c) 2004 - 2008, Intel Corporation. All rights reserved.<BR>
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
@ -11,12 +12,12 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
Module Name:
Decompress.h
Decompress.h
Abstract:
Header file for decompression routine.
Providing both EFI and Tiano decompress algorithms.
Header file for decompression routine.
Providing both EFI and Tiano decompress algorithms.
--*/
@ -30,12 +31,13 @@ Providing both EFI and Tiano decompress algorithms.
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
UINT32 CompSize;
UINT32 OrigSize;
} EFI_TIANO_HEADER;
UINT32
EFI_STATUS
EFIAPI
EfiTianoGetInfo (
VOID *Source,
@ -65,7 +67,7 @@ EFI_INVALID_PARAMETER - The source data is corrupted
--*/
;
UINT32
EFI_STATUS
EFIAPI
EfiDecompress (
VOID *Source,
@ -99,7 +101,7 @@ EFI_INVALID_PARAMETER - The source data is corrupted
--*/
;
UINT32
EFI_STATUS
EFIAPI
TianoDecompress (
VOID *Source,

View File

@ -56,6 +56,7 @@ UINT8 UEFIPatch::patchFromFile(QString path)
if (result)
return result;
UINT8 counter = 0;
while (!file.atEnd()) {
QByteArray line = file.readLine();
// Use sharp sign as commentary
@ -68,98 +69,81 @@ UINT8 UEFIPatch::patchFromFile(QString path)
QUuid uuid = QUuid(list.at(0));
QByteArray guid = QByteArray::fromRawData((const char*)&uuid.data1, sizeof(EFI_GUID));
result = patchFile(model->index(0, 0), guid, QByteArray::fromHex(list.at(1)), QByteArray::fromHex(list.at(2)));
if (result)
return result;
}
QByteArray reconstructed;
result = ffsEngine->reconstructImageFile(reconstructed);
if (result)
return result;
if (reconstructed == buffer) {
return ERR_ITEM_NOT_FOUND;
}
QFile outputFile;
outputFile.setFileName(path.append(".patched"));
if (!outputFile.open(QFile::WriteOnly))
return ERR_FILE_WRITE;
outputFile.resize(0);
outputFile.write(reconstructed);
outputFile.close();
return ERR_SUCCESS;
}
UINT8 UEFIPatch::patch(QString path, QString fileGuid, QString findPattern, QString replacePattern)
{
QFileInfo fileInfo = QFileInfo(path);
if (!fileInfo.exists())
return ERR_FILE_OPEN;
QFile inputFile;
inputFile.setFileName(path);
if (!inputFile.open(QFile::ReadOnly))
return ERR_FILE_READ;
QByteArray buffer = inputFile.readAll();
inputFile.close();
UINT8 result = ffsEngine->parseImageFile(buffer);
if (result)
return result;
QUuid uuid = QUuid(fileGuid);
QByteArray guid = QByteArray::fromRawData((const char*)&uuid.data1, sizeof(EFI_GUID));
result = patchFile(model->index(0, 0), guid, QByteArray::fromHex(findPattern.toLatin1()), QByteArray::fromHex(replacePattern.toLatin1()));
if (result)
return result;
QByteArray reconstructed;
result = ffsEngine->reconstructImageFile(reconstructed);
if (result)
return result;
if (reconstructed == buffer) {
return ERR_ITEM_NOT_FOUND;
}
QFile outputFile;
outputFile.setFileName(path.append(".patched"));
if (!outputFile.open(QFile::WriteOnly))
return ERR_FILE_WRITE;
outputFile.resize(0);
outputFile.write(reconstructed);
outputFile.close();
return ERR_SUCCESS;
}
UINT8 UEFIPatch::patchFile(const QModelIndex & index, const QByteArray & fileGuid, const QByteArray & findPattern, const QByteArray & replacePattern)
{
if (!model || !index.isValid())
bool converted;
UINT8 sectionType = (UINT8)list.at(1).toUShort(&converted, 16);
if (!converted)
return ERR_INVALID_PARAMETER;
if (model->type(index) == Types::File && model->header(index).left(sizeof(EFI_GUID)) == fileGuid) {
return ffsEngine->patch(index, findPattern, replacePattern, PATCH_MODE_BODY);
QVector<PatchData> patches;
for (int i = 2; i < list.count(); i++) {
QList<QByteArray> patchList = list.at(i).split(':');
PatchData patch;
patch.type = *(UINT8*)patchList.at(0).constData();
if (patch.type == PATCH_TYPE_PATTERN) {
patch.offset = 0xFFFFFFFF;
patch.hexFindPattern = patchList.at(1);
patch.hexReplacePattern = patchList.at(2);
patches.append(patch);
}
else if (patch.type == PATCH_TYPE_OFFSET) {
patch.offset = patchList.at(1).toUInt(NULL, 16);
patch.hexReplacePattern = patchList.at(2);
patches.append(patch);
}
else {
// Ignore unknown patch type
continue;
}
}
result = patchFile(model->index(0, 0), guid, sectionType, patches);
if (result && result != ERR_NOTHING_TO_PATCH)
return result;
counter++;
}
int childCount = model->rowCount(index);
if (childCount > 0) {
UINT8 result;
for (int i = 0; i < childCount; i++) {
result = patchFile(index.child(i, 0), fileGuid, findPattern, replacePattern);
QByteArray reconstructed;
result = ffsEngine->reconstructImageFile(reconstructed);
if (result)
return result;
if (reconstructed == buffer)
return ERR_NOTHING_TO_PATCH;
QFile outputFile;
outputFile.setFileName(path.append(".patched"));
if (!outputFile.open(QFile::WriteOnly))
return ERR_FILE_WRITE;
outputFile.resize(0);
outputFile.write(reconstructed);
outputFile.close();
return ERR_SUCCESS;
}
UINT8 UEFIPatch::patchFile(const QModelIndex & index, const QByteArray & fileGuid, const UINT8 sectionType, const QVector<PatchData> & patches)
{
if (!model || !index.isValid())
return ERR_INVALID_PARAMETER;
if (model->type(index) == Types::Section && model->subtype(index) == sectionType) {
QModelIndex fileIndex = model->findParentOfType(index, Types::File);
if (model->type(fileIndex) == Types::File &&
model->header(fileIndex).left(sizeof(EFI_GUID)) == fileGuid)
{
return ffsEngine->patch(index, patches);
}
}
if (model->rowCount(index) > 0) {
for (int i = 0; i < model->rowCount(index); i++) {
UINT8 result = patchFile(index.child(i, 0), fileGuid, sectionType, patches);
if (!result)
break;
else if (result != ERR_NOTHING_TO_PATCH)
return result;
}
}
return ERR_SUCCESS;
return ERR_NOTHING_TO_PATCH;
}

View File

@ -37,7 +37,7 @@ public:
UINT8 patch(QString path, QString fileGuid, QString findPattern, QString replacePattern);
private:
UINT8 patchFile(const QModelIndex & index, const QByteArray & fileGuid, const QByteArray & findPattern, const QByteArray & replacePattern);
UINT8 patchFile(const QModelIndex & index, const QByteArray & fileGuid, const UINT8 sectionType, const QVector<PatchData> & patches);
FfsEngine* ffsEngine;
TreeModel* model;
};

View File

@ -27,31 +27,34 @@ int main(int argc, char *argv[])
UINT8 result = ERR_SUCCESS;
UINT32 argumentsCount = a.arguments().length();
if (argumentsCount == 2) {
result = w.patchFromFile(a.arguments().at(1));
}
else if (argumentsCount == 5) {
result = w.patch(a.arguments().at(1), a.arguments().at(2), a.arguments().at(3), a.arguments().at(4));
else {
std::cout << "UEFIPatch 0.2.0 - UEFI image file patching utility" << std::endl << std::endl <<
"Usage: UEFIPatch image_file" << std::endl << std::endl <<
"Patches will be read from patches.txt file\n";
return ERR_SUCCESS;
}
else
result = ERR_INVALID_PARAMETER;
switch (result) {
case ERR_INVALID_PARAMETER:
std::cout << "UEFIPatch 0.1.0 - UEFI image file patching utility" << std::endl << std::endl <<
"Usage: UEFIPatch image_file [ffs_file_guid search_pattern replace_pattern]" << std::endl << std::endl <<
"image_file - full or relative path to UEFI image file" << std::endl <<
"ffs_file_guid - GUID of FFS file to be patched" << std::endl <<
"search_pattern - pattern to search" << std::endl <<
"replace_pattern - pattern to replace" << std::endl << std::endl <<
"If only image_file parameter is specified, patches will be read from patches.txt file";
break;
case ERR_SUCCESS:
std::cout << "Image patched" << std::endl;
break;
case ERR_ITEM_NOT_FOUND:
std::cout << "FFS file or search pattern not found in input file" << std::endl;
case ERR_INVALID_PARAMETER:
std::cout << "Function called with invalid parameter" << std::endl;
break;
case ERR_NOTHING_TO_PATCH:
std::cout << "No patches can be applied to input file" << std::endl;
break;
case ERR_UNKNOWN_PATCH_TYPE:
std::cout << "Unknown patch type" << std::endl;
break;
case ERR_PATCH_OFFSET_OUT_OF_BOUNDS:
std::cout << "Patch offset out of bounds" << std::endl;
break;
case ERR_INVALID_SYMBOL:
std::cout << "Pattern format mismatch" << std::endl;
break;
case ERR_INVALID_FILE:
std::cout << "patches.txt file not found or can't be read" << std::endl;

View File

@ -45,8 +45,6 @@ typedef uint16_t CHAR16;
#define NULL ((VOID *) 0)
#endif
#define EFIAPI
#define ERR_SUCCESS 0
#define ERR_INVALID_PARAMETER 1
#define ERR_BUFFER_TOO_SMALL 2
@ -85,9 +83,23 @@ typedef uint16_t CHAR16;
#define ERR_COMPLEX_BLOCK_MAP 35
#define ERR_DIR_ALREADY_EXIST 36
#define ERR_DIR_CREATE 37
#define ERR_UNKNOWN_PATCH_MODE 38
#define ERR_UNKNOWN_PATCH_TYPE 38
#define ERR_PATCH_OFFSET_OUT_OF_BOUNDS 39
#define ERR_INVALID_SYMBOL 40
#define ERR_NOTHING_TO_PATCH 41
#define ERR_NOT_IMPLEMENTED 0xFF
// UDK porting definitions
#define IN
#define OUT
#define EFIAPI
#define EFI_STATUS UINT8
#define EFI_SUCCESS ERR_SUCCESS
#define EFI_INVALID_PARAMETER ERR_INVALID_PARAMETER
#define EFI_OUT_OF_RESOURCES ERR_OUT_OF_RESOURCES
#define EFI_BUFFER_TOO_SMALL ERR_BUFFER_TOO_SMALL
#define EFI_ERROR(X) X
// Compression algorithms
#define COMPRESSION_ALGORITHM_UNKNOWN 0
#define COMPRESSION_ALGORITHM_NONE 1
@ -114,6 +126,10 @@ typedef uint16_t CHAR16;
#define PATCH_MODE_HEADER 0
#define PATCH_MODE_BODY 1
// Patch types
#define PATCH_TYPE_OFFSET 'O'
#define PATCH_TYPE_PATTERN 'P'
// Erase polarity types
#define ERASE_POLARITY_FALSE 0
#define ERASE_POLARITY_TRUE 1
@ -124,8 +140,6 @@ typedef uint16_t CHAR16;
#define SEARCH_MODE_BODY 2
#define SEARCH_MODE_ALL 3
// EFI GUID
typedef struct {
UINT8 Data[16];

View File

@ -1797,7 +1797,6 @@ UINT8 FfsEngine::decompress(const QByteArray & compressedData, const UINT8 compr
UINT8 FfsEngine::compress(const QByteArray & data, const UINT8 algorithm, QByteArray & compressedData)
{
UINT8* compressed;
UINT32 compressedSize = 0;
switch (algorithm) {
case COMPRESSION_ALGORITHM_NONE:
@ -1808,10 +1807,11 @@ UINT8 FfsEngine::compress(const QByteArray & data, const UINT8 algorithm, QByteA
break;
case COMPRESSION_ALGORITHM_EFI11:
{
if (EfiCompress((UINT8*)data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
UINT64 compressedSize = 0;
if (EfiCompress(data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_STANDARD_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
if (EfiCompress((UINT8*)data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
if (EfiCompress(data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
delete[] compressed;
return ERR_STANDARD_COMPRESSION_FAILED;
}
@ -1822,10 +1822,11 @@ UINT8 FfsEngine::compress(const QByteArray & data, const UINT8 algorithm, QByteA
break;
case COMPRESSION_ALGORITHM_TIANO:
{
if (TianoCompress((UINT8*)data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
UINT64 compressedSize = 0;
if (TianoCompress(data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_STANDARD_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
if (TianoCompress((UINT8*)data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
if (TianoCompress(data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
delete[] compressed;
return ERR_STANDARD_COMPRESSION_FAILED;
}
@ -1836,6 +1837,7 @@ UINT8 FfsEngine::compress(const QByteArray & data, const UINT8 algorithm, QByteA
break;
case COMPRESSION_ALGORITHM_LZMA:
{
UINT32 compressedSize = 0;
if (LzmaCompress((const UINT8*)data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_CUSTOMIZED_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
@ -1850,6 +1852,7 @@ UINT8 FfsEngine::compress(const QByteArray & data, const UINT8 algorithm, QByteA
break;
case COMPRESSION_ALGORITHM_IMLZMA:
{
UINT32 compressedSize = 0;
QByteArray header = data.left(sizeof(EFI_COMMON_SECTION_HEADER));
EFI_COMMON_SECTION_HEADER* sectionHeader = (EFI_COMMON_SECTION_HEADER*)header.constData();
UINT32 headerSize = sizeOfSectionHeader(sectionHeader);
@ -2645,6 +2648,7 @@ UINT8 FfsEngine::reconstructSection(const QModelIndex& index, const UINT32 base,
// Reconstruction successful
reconstructed = header.append(reconstructed);
return ERR_SUCCESS;
}
@ -3198,48 +3202,126 @@ UINT8 FfsEngine::dump(const QModelIndex & index, const QString path)
return ERR_SUCCESS;
}
UINT8 FfsEngine::patch(const QModelIndex & index, const QByteArray & findPattern, const QByteArray & replacePattern, const UINT8 mode)
UINT8 FfsEngine::patch(const QModelIndex & index, const QVector<PatchData> & patches)
{
if (!index.isValid() || findPattern.isEmpty())
if (!index.isValid() || patches.isEmpty() || model->rowCount(index))
return ERR_INVALID_PARAMETER;
// Skip removed files
// Skip removed items
if (model->action(index) == Actions::Remove)
return ERR_SUCCESS;
return ERR_NOTHING_TO_PATCH;
// Patch header
if (mode == PATCH_MODE_HEADER && model->header(index).contains(findPattern)) {
QByteArray patched = model->header(index);
patched.replace(findPattern, replacePattern).append(model->body(index));
msg(tr("Header of %1 patched, %2 -> %3")
.arg(model->nameString(index))
.arg(QString(findPattern.toHex()))
.arg(QString(replacePattern.toHex())), index);
return replace(index, patched, REPLACE_MODE_AS_IS);
}
// Patch body
else if (mode == PATCH_MODE_BODY) {
if (model->rowCount(index)) {
UINT8 result;
for (int i = 0; i < model->rowCount(index); i++) {
result = patch(index.child(i, 0), findPattern, replacePattern, PATCH_MODE_BODY);
// Apply patches to item's body
QByteArray body = model->body(index);
PatchData current;
Q_FOREACH(current, patches)
{
if (current.type == PATCH_TYPE_OFFSET) {
result = patchViaOffset(body, current.offset, current.hexReplacePattern);
if (result)
return result;
}
}
else if (model->body(index).contains(findPattern)){
QByteArray patched = model->body(index);
patched.replace(findPattern, replacePattern);
patched.prepend(model->header(index));
msg(tr("Body of %1 patched, %2 -> %3")
.arg(model->nameString(index))
.arg(QString(findPattern.toHex()))
.arg(QString(replacePattern.toHex())), index);
return replace(index, patched, REPLACE_MODE_AS_IS);
}
else if (current.type == PATCH_TYPE_PATTERN) {
result = patchViaPattern(body, current.hexFindPattern, current.hexReplacePattern);
if (result)
return result;
}
else
return ERR_UNKNOWN_PATCH_MODE;
return ERR_UNKNOWN_PATCH_TYPE;
}
if (body != model->body(index)) {
QByteArray patched = model->header(index);
patched.append(body);
return replace(index, patched, REPLACE_MODE_AS_IS);
}
return ERR_NOTHING_TO_PATCH;
}
UINT8 FfsEngine::patchViaOffset(QByteArray & data, const UINT32 offset, const QByteArray & hexReplacePattern)
{
QByteArray body = data;
// Skip patterns with odd length
if (hexReplacePattern.length() % 2 > 0)
return ERR_INVALID_PARAMETER;
// Check offset bounds
if (offset > body.length() - hexReplacePattern.length() / 2)
return ERR_PATCH_OFFSET_OUT_OF_BOUNDS;
// Parse replace pattern
QByteArray replacePattern;
bool converted;
for (int i = 0; i < hexReplacePattern.length() / 2; i++) {
QByteArray hex = hexReplacePattern.mid(2 * i, 2);
UINT8 value = 0;
if (!hex.contains('.')) { // Normal byte pattern
value = (UINT8)hex.toUShort(&converted, 16);
if (!converted)
return ERR_INVALID_SYMBOL;
}
else { // Placeholder byte pattern
if (hex[0] == '.' && hex[1] == '.') { // Full byte placeholder
value = body.at(offset + i);
}
else if (hex[0] == '.') {// Upper byte part placeholder
hex[0] = '0';
value = (UINT8)(body.at(offset + i) & 0xF0);
value += (UINT8)hex.toUShort(&converted, 16);
if (!converted)
return ERR_INVALID_SYMBOL;
}
else if (hex[1] == '.') { // Lower byte part placeholder
hex[1] = '0';
value = (UINT8)(body.at(offset + i) & 0x0F);
value += (UINT8)hex.toUShort(&converted, 16);
if (!converted)
return ERR_INVALID_SYMBOL;
}
else
return ERR_INVALID_SYMBOL;
}
// Append calculated value to real pattern
replacePattern.append(value);
}
body.replace(offset, replacePattern.length(), replacePattern);
msg(tr("patch: replaced %1 bytes at offset 0x%2 %3 -> %4")
.arg(replacePattern.length())
.arg(offset, 8, 16, QChar('0'))
.arg(QString(data.mid(offset, replacePattern.length()).toHex()))
.arg(QString(replacePattern.toHex())));
data = body;
return ERR_SUCCESS;
}
UINT8 FfsEngine::patchViaPattern(QByteArray & data, const QByteArray hexFindPattern, const QByteArray & hexReplacePattern)
{
QByteArray body = data;
// Skip patterns with odd length
if (hexFindPattern.length() % 2 > 0 || hexReplacePattern.length() % 2 > 0)
return ERR_INVALID_PARAMETER;
// Convert file body to hex;
QString hexBody = QString(body.toHex());
QRegExp regexp = QRegExp(QString(hexFindPattern), Qt::CaseInsensitive);
INT64 offset = regexp.indexIn(hexBody);
while (offset >= 0) {
if (offset % 2 == 0) {
UINT8 result = patchViaOffset(body, offset/2, hexReplacePattern);
if (result)
return result;
}
offset = regexp.indexIn(hexBody, offset + 1);
}
data = body;
return ERR_SUCCESS;
}

View File

@ -32,6 +32,13 @@ WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
class TreeModel;
struct PatchData {
UINT8 type;
UINT32 offset;
QByteArray hexFindPattern;
QByteArray hexReplacePattern;
};
class FfsEngine : public QObject
{
Q_OBJECT
@ -86,7 +93,7 @@ public:
UINT8 remove(const QModelIndex & index);
UINT8 rebuild(const QModelIndex & index);
UINT8 dump(const QModelIndex & index, const QString path);
UINT8 patch(const QModelIndex & index, const QByteArray & findPattern, const QByteArray & replacePattern, const UINT8 mode);
UINT8 patch(const QModelIndex & index, const QVector<PatchData> & patches);
// Search routines
UINT8 findHexPattern(const QByteArray & pattern, const UINT8 mode);
@ -120,6 +127,10 @@ private:
// Patch routines
UINT8 patchVtf(QByteArray &vtf);
// Patch helpers
UINT8 patchViaOffset(QByteArray & data, const UINT32 offset, const QByteArray & hexReplacePattern);
UINT8 patchViaPattern(QByteArray & data, const QByteArray hexFindPattern, const QByteArray & hexReplacePattern);
#ifndef _CONSOLE
QQueue<MessageListItem> messageItems;
#endif

View File

@ -20,7 +20,7 @@
<bool>true</bool>
</property>
<property name="windowTitle">
<string>UEFITool 0.17.10</string>
<string>UEFITool 0.18.0</string>
</property>
<widget class="QWidget" name="centralWidget">
<property name="sizePolicy">