mirror of
https://github.com/LongSoft/UEFITool.git
synced 2024-11-22 07:58:22 +08:00
Version 0.5.0
- Minor bugs corrected - Implemented section insertion - Replace temporary removed
This commit is contained in:
parent
4a61fcd9d9
commit
6ff5119048
10
basetypes.h
10
basetypes.h
@ -93,11 +93,11 @@ typedef uint16_t CHAR16;
|
|||||||
#define COMPRESSION_ALGORITHM_LZMA 4
|
#define COMPRESSION_ALGORITHM_LZMA 4
|
||||||
#define COMPRESSION_ALGORITHM_IMLZMA 5
|
#define COMPRESSION_ALGORITHM_IMLZMA 5
|
||||||
|
|
||||||
// Item add modes
|
// Item insert modes
|
||||||
#define ADD_MODE_APPEND 0
|
#define INSERT_MODE_APPEND 0
|
||||||
#define ADD_MODE_PREPEND 1
|
#define INSERT_MODE_PREPEND 1
|
||||||
#define ADD_MODE_INSERT_BEFORE 2
|
#define INSERT_MODE_BEFORE 2
|
||||||
#define ADD_MODE_INSERT_AFTER 3
|
#define INSERT_MODE_AFTER 3
|
||||||
|
|
||||||
// EFI GUID
|
// EFI GUID
|
||||||
typedef struct{
|
typedef struct{
|
||||||
|
@ -372,11 +372,10 @@ UINT8 FfsEngine::parseBios(const QByteArray & bios, const QModelIndex & parent)
|
|||||||
UINT32 prevVolumeOffset;
|
UINT32 prevVolumeOffset;
|
||||||
UINT8 result;
|
UINT8 result;
|
||||||
result = findNextVolume(bios, 0, prevVolumeOffset);
|
result = findNextVolume(bios, 0, prevVolumeOffset);
|
||||||
if (result == ERR_VOLUMES_NOT_FOUND)
|
if (result == ERR_VOLUMES_NOT_FOUND) {
|
||||||
{
|
|
||||||
//msg(tr("No volumes found in BIOS space"));
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First volume is not at the beginning of BIOS space
|
// First volume is not at the beginning of BIOS space
|
||||||
QString name;
|
QString name;
|
||||||
QString info;
|
QString info;
|
||||||
@ -514,8 +513,21 @@ UINT8 FfsEngine::parseBios(const QByteArray & bios, const QModelIndex & parent)
|
|||||||
prevVolumeSize = volumeSize;
|
prevVolumeSize = volumeSize;
|
||||||
|
|
||||||
result = findNextVolume(bios, volumeOffset + prevVolumeSize, volumeOffset);
|
result = findNextVolume(bios, volumeOffset + prevVolumeSize, volumeOffset);
|
||||||
if (result == ERR_VOLUMES_NOT_FOUND)
|
if (result) {
|
||||||
|
UINT32 endPaddingSize = bios.size() - prevVolumeOffset - prevVolumeSize;
|
||||||
|
// Padding at the end of BIOS space
|
||||||
|
if (endPaddingSize > 0) {
|
||||||
|
QByteArray padding = bios.right(endPaddingSize);
|
||||||
|
// Get info
|
||||||
|
name = tr("Padding");
|
||||||
|
info = tr("Size: %2")
|
||||||
|
.arg(padding.size(), 8, 16, QChar('0'));
|
||||||
|
// Add tree item
|
||||||
|
treeModel->addItem(TreeItem::Padding, 0, COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), padding, parent);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ERR_SUCCESS;
|
return ERR_SUCCESS;
|
||||||
@ -576,7 +588,7 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, const QModelIndex & par
|
|||||||
msg(tr("parseBios: Volume header checksum is invalid"));
|
msg(tr("parseBios: Volume header checksum is invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for presence of extended header, only if header revision is not 1
|
// Check for presence of extended header, only if header revision is greater then 1
|
||||||
UINT32 headerSize;
|
UINT32 headerSize;
|
||||||
if (volumeHeader->Revision > 1 && volumeHeader->ExtHeaderOffset) {
|
if (volumeHeader->Revision > 1 && volumeHeader->ExtHeaderOffset) {
|
||||||
EFI_FIRMWARE_VOLUME_EXT_HEADER* extendedHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER*) ((UINT8*) volumeHeader + volumeHeader->ExtHeaderOffset);
|
EFI_FIRMWARE_VOLUME_EXT_HEADER* extendedHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER*) ((UINT8*) volumeHeader + volumeHeader->ExtHeaderOffset);
|
||||||
@ -598,10 +610,16 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, const QModelIndex & par
|
|||||||
QByteArray body = volume.mid(headerSize, volumeHeader->FvLength - headerSize);
|
QByteArray body = volume.mid(headerSize, volumeHeader->FvLength - headerSize);
|
||||||
QModelIndex index = treeModel->addItem(TreeItem::Volume, 0, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
|
QModelIndex index = treeModel->addItem(TreeItem::Volume, 0, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
|
||||||
|
|
||||||
|
// Do not parse volumes with unknown FS
|
||||||
|
if (!parseCurrentVolume)
|
||||||
|
return ERR_SUCCESS;
|
||||||
|
|
||||||
// Search for and parse all files
|
// Search for and parse all files
|
||||||
UINT32 fileOffset = headerSize;
|
UINT32 fileOffset = headerSize;
|
||||||
UINT32 fileSize;
|
UINT32 fileSize;
|
||||||
UINT8 result;
|
UINT8 result;
|
||||||
|
QQueue<QByteArray> files;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
result = getFileSize(volume, fileOffset, fileSize);
|
result = getFileSize(volume, fileOffset, fileSize);
|
||||||
if (result)
|
if (result)
|
||||||
@ -616,9 +634,9 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, const QModelIndex & par
|
|||||||
QByteArray file = volume.mid(fileOffset, fileSize);
|
QByteArray file = volume.mid(fileOffset, fileSize);
|
||||||
QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER));
|
QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER));
|
||||||
|
|
||||||
// We are now at empty space in the end of volume
|
// If we are at empty space in the end of volume
|
||||||
if (header.count(empty) == header.size())
|
if (header.count(empty) == header.size())
|
||||||
break;
|
break; // Exit from loop
|
||||||
|
|
||||||
// Check file alignment
|
// Check file alignment
|
||||||
EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*) header.constData();
|
EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*) header.constData();
|
||||||
@ -628,6 +646,14 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, const QModelIndex & par
|
|||||||
msg(tr("parseVolume: %1, unaligned file").arg(guidToQString(fileHeader->Name)));
|
msg(tr("parseVolume: %1, unaligned file").arg(guidToQString(fileHeader->Name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check file GUID
|
||||||
|
if (fileHeader->Type != EFI_FV_FILETYPE_PAD && files.indexOf(header.left(sizeof(EFI_GUID))) != -1)
|
||||||
|
msg(tr("%1: file with duplicate GUID").arg(guidToQString(fileHeader->Name)));
|
||||||
|
|
||||||
|
// Add file GUID to queue
|
||||||
|
files.enqueue(header.left(sizeof(EFI_GUID)));
|
||||||
|
|
||||||
|
// Parse file
|
||||||
result = parseFile(file, volumeHeader->Revision, empty, index);
|
result = parseFile(file, volumeHeader->Revision, empty, index);
|
||||||
if (result)
|
if (result)
|
||||||
msg(tr("parseVolume: Parse FFS file failed (%1)").arg(result));
|
msg(tr("parseVolume: Parse FFS file failed (%1)").arg(result));
|
||||||
@ -790,13 +816,13 @@ UINT8 FfsEngine::parseFile(const QByteArray & file, UINT8 revision, const char e
|
|||||||
result = parseBios(body, index);
|
result = parseBios(body, index);
|
||||||
if (result && result != ERR_VOLUMES_NOT_FOUND)
|
if (result && result != ERR_VOLUMES_NOT_FOUND)
|
||||||
msg(tr("parseVolume: Parse file as BIOS failed (%1)").arg(result));
|
msg(tr("parseVolume: Parse file as BIOS failed (%1)").arg(result));
|
||||||
|
return ERR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse sections
|
// Parse sections
|
||||||
else {
|
result = parseSections(body, revision, empty, index);
|
||||||
result = parseSections(body, revision, empty, index);
|
if (result)
|
||||||
if (result)
|
return result;
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ERR_SUCCESS;
|
return ERR_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -959,11 +985,11 @@ UINT8 FfsEngine::parseSection(const QByteArray & section, const UINT8 revision,
|
|||||||
case EFI_SECTION_PIC:
|
case EFI_SECTION_PIC:
|
||||||
case EFI_SECTION_TE:
|
case EFI_SECTION_TE:
|
||||||
case EFI_SECTION_VERSION:
|
case EFI_SECTION_VERSION:
|
||||||
case EFI_SECTION_COMPATIBILITY16:
|
|
||||||
case EFI_SECTION_FREEFORM_SUBTYPE_GUID:
|
case EFI_SECTION_FREEFORM_SUBTYPE_GUID:
|
||||||
case EFI_SECTION_DXE_DEPEX:
|
case EFI_SECTION_DXE_DEPEX:
|
||||||
case EFI_SECTION_PEI_DEPEX:
|
case EFI_SECTION_PEI_DEPEX:
|
||||||
case EFI_SECTION_SMM_DEPEX:
|
case EFI_SECTION_SMM_DEPEX:
|
||||||
|
case EFI_SECTION_COMPATIBILITY16:
|
||||||
headerSize = sizeOfSectionHeaderOfType(sectionHeader->Type);
|
headerSize = sizeOfSectionHeaderOfType(sectionHeader->Type);
|
||||||
header = section.left(headerSize);
|
header = section.left(headerSize);
|
||||||
body = section.mid(headerSize, sectionSize - headerSize);
|
body = section.mid(headerSize, sectionSize - headerSize);
|
||||||
@ -997,6 +1023,7 @@ UINT8 FfsEngine::parseSection(const QByteArray & section, const UINT8 revision,
|
|||||||
case EFI_SECTION_FIRMWARE_VOLUME_IMAGE:
|
case EFI_SECTION_FIRMWARE_VOLUME_IMAGE:
|
||||||
header = section.left(sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
|
header = section.left(sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
|
||||||
body = section.mid(sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION), sectionSize - sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
|
body = section.mid(sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION), sectionSize - sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
|
||||||
|
|
||||||
// Get info
|
// Get info
|
||||||
info = tr("Type: %1\nSize: %2")
|
info = tr("Type: %1\nSize: %2")
|
||||||
.arg(sectionHeader->Type, 2, 16, QChar('0'))
|
.arg(sectionHeader->Type, 2, 16, QChar('0'))
|
||||||
@ -1015,6 +1042,7 @@ UINT8 FfsEngine::parseSection(const QByteArray & section, const UINT8 revision,
|
|||||||
case EFI_SECTION_RAW:
|
case EFI_SECTION_RAW:
|
||||||
header = section.left(sizeof(EFI_RAW_SECTION));
|
header = section.left(sizeof(EFI_RAW_SECTION));
|
||||||
body = section.mid(sizeof(EFI_RAW_SECTION), sectionSize - sizeof(EFI_RAW_SECTION));
|
body = section.mid(sizeof(EFI_RAW_SECTION), sectionSize - sizeof(EFI_RAW_SECTION));
|
||||||
|
|
||||||
// Get info
|
// Get info
|
||||||
info = tr("Type: %1\nSize: %2")
|
info = tr("Type: %1\nSize: %2")
|
||||||
.arg(sectionHeader->Type, 2, 16, QChar('0'))
|
.arg(sectionHeader->Type, 2, 16, QChar('0'))
|
||||||
@ -1055,22 +1083,21 @@ UINT8 FfsEngine::insert(const QModelIndex & index, const QByteArray & object, co
|
|||||||
// Only files and sections can now be inserted
|
// Only files and sections can now be inserted
|
||||||
if (type == TreeItem::File) {
|
if (type == TreeItem::File) {
|
||||||
QModelIndex parent;
|
QModelIndex parent;
|
||||||
if (mode == ADD_MODE_APPEND || mode == ADD_MODE_PREPEND)
|
if (mode == INSERT_MODE_BEFORE || mode == INSERT_MODE_AFTER)
|
||||||
parent = index;
|
|
||||||
else
|
|
||||||
parent = index.parent();
|
parent = index.parent();
|
||||||
|
else
|
||||||
|
parent = index;
|
||||||
|
|
||||||
// Parent type must be volume
|
// Parent type must be volume
|
||||||
TreeItem * parentItem = static_cast<TreeItem*>(parent.internalPointer());
|
TreeItem * parentItem = static_cast<TreeItem*>(parent.internalPointer());
|
||||||
if (parentItem->type() != TreeItem::Volume) {
|
if (parentItem->type() != TreeItem::Volume) {
|
||||||
msg(tr("insertInto: file can't be inserted into something that is not volume"));
|
msg(tr("insert: file can't be inserted into something that is not volume"));
|
||||||
return ERR_INVALID_VOLUME;
|
return ERR_INVALID_VOLUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
EFI_FIRMWARE_VOLUME_HEADER* header = (EFI_FIRMWARE_VOLUME_HEADER*) parentItem->header().constData();
|
EFI_FIRMWARE_VOLUME_HEADER* header = (EFI_FIRMWARE_VOLUME_HEADER*) parentItem->header().constData();
|
||||||
|
|
||||||
// Parse file
|
// Parse file
|
||||||
//!TODO: add check for same GUIDs
|
|
||||||
UINT8 result = parseFile(object, header->Revision, header->Attributes & EFI_FVB_ERASE_POLARITY ? '\xFF' : '\x00', index, mode);
|
UINT8 result = parseFile(object, header->Revision, header->Attributes & EFI_FVB_ERASE_POLARITY ? '\xFF' : '\x00', index, mode);
|
||||||
if (result)
|
if (result)
|
||||||
return result;
|
return result;
|
||||||
@ -1081,7 +1108,40 @@ UINT8 FfsEngine::insert(const QModelIndex & index, const QByteArray & object, co
|
|||||||
|
|
||||||
}
|
}
|
||||||
else if (type == TreeItem::Section) {
|
else if (type == TreeItem::Section) {
|
||||||
return ERR_NOT_IMPLEMENTED;
|
QModelIndex parent;
|
||||||
|
if (mode == INSERT_MODE_BEFORE || mode == INSERT_MODE_AFTER)
|
||||||
|
parent = index.parent();
|
||||||
|
else
|
||||||
|
parent = index;
|
||||||
|
|
||||||
|
// Parent type must be file or encapsulation section
|
||||||
|
TreeItem * parentItem = static_cast<TreeItem*>(parent.internalPointer());
|
||||||
|
if (parentItem->type() == TreeItem::File || (parentItem->type() == TreeItem::Section &&
|
||||||
|
(parentItem->subtype() == EFI_SECTION_COMPRESSION ||
|
||||||
|
parentItem->subtype() == EFI_SECTION_GUID_DEFINED ||
|
||||||
|
parentItem->subtype() == EFI_SECTION_DISPOSABLE))) {
|
||||||
|
QModelIndex volumeIndex = findParentOfType(TreeItem::Volume, parent);
|
||||||
|
if (!volumeIndex.isValid()) {
|
||||||
|
msg(tr("insert: Parent volume not found"));
|
||||||
|
return ERR_INVALID_VOLUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeItem * volumeItem = static_cast<TreeItem*>(volumeIndex.internalPointer());
|
||||||
|
EFI_FIRMWARE_VOLUME_HEADER* header = (EFI_FIRMWARE_VOLUME_HEADER*) volumeItem->header().constData();
|
||||||
|
|
||||||
|
// Parse section
|
||||||
|
UINT8 result = parseSection(object, header->Revision, header->Attributes & EFI_FVB_ERASE_POLARITY ? '\xFF' : '\x00', index, mode);
|
||||||
|
if (result)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// Set reconstruct action for all parents
|
||||||
|
for (;parent.isValid(); parent = parent.parent())
|
||||||
|
treeModel->setItemAction(TreeItem::Reconstruct, parent);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg(tr("insert: section can't be inserted into something that is not file or encapsulation section"));
|
||||||
|
return ERR_INVALID_FILE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return ERR_NOT_IMPLEMENTED;
|
return ERR_NOT_IMPLEMENTED;
|
||||||
|
10
ffsengine.h
10
ffsengine.h
@ -44,15 +44,15 @@ public:
|
|||||||
UINT8 parseBios(const QByteArray & bios, const QModelIndex & parent = QModelIndex());
|
UINT8 parseBios(const QByteArray & bios, const QModelIndex & parent = QModelIndex());
|
||||||
UINT8 findNextVolume(const QByteArray & bios, const UINT32 volumeOffset, UINT32 & nextVolumeOffset);
|
UINT8 findNextVolume(const QByteArray & bios, const UINT32 volumeOffset, UINT32 & nextVolumeOffset);
|
||||||
UINT8 getVolumeSize(const QByteArray & bios, const UINT32 volumeOffset, UINT32 & volumeSize);
|
UINT8 getVolumeSize(const QByteArray & bios, const UINT32 volumeOffset, UINT32 & volumeSize);
|
||||||
UINT8 parseVolume(const QByteArray & volume, const QModelIndex & parent = QModelIndex(), const UINT8 mode = ADD_MODE_APPEND);
|
UINT8 parseVolume(const QByteArray & volume, const QModelIndex & parent = QModelIndex(), const UINT8 mode = INSERT_MODE_APPEND);
|
||||||
UINT8 getFileSize(const QByteArray & volume, const UINT32 fileOffset, UINT32 & fileSize);
|
UINT8 getFileSize(const QByteArray & volume, const UINT32 fileOffset, UINT32 & fileSize);
|
||||||
UINT8 parseFile(const QByteArray & file, const UINT8 revision, const char empty = '\xFF', const QModelIndex & parent = QModelIndex(), const UINT8 mode = ADD_MODE_APPEND);
|
UINT8 parseFile(const QByteArray & file, const UINT8 revision, const char empty = '\xFF', const QModelIndex & parent = QModelIndex(), const UINT8 mode = INSERT_MODE_APPEND);
|
||||||
UINT8 getSectionSize(const QByteArray & file, const UINT32 sectionOffset, UINT32 & sectionSize);
|
UINT8 getSectionSize(const QByteArray & file, const UINT32 sectionOffset, UINT32 & sectionSize);
|
||||||
UINT8 parseSections(const QByteArray & body, const UINT8 revision, const char empty = '\xFF', const QModelIndex & parent = QModelIndex());
|
UINT8 parseSections(const QByteArray & body, const UINT8 revision, const char empty = '\xFF', const QModelIndex & parent = QModelIndex());
|
||||||
UINT8 parseSection(const QByteArray & section, const UINT8 revision, const char empty = '\xFF', const QModelIndex & parent = QModelIndex(), const UINT8 mode = ADD_MODE_APPEND);
|
UINT8 parseSection(const QByteArray & section, const UINT8 revision, const char empty = '\xFF', const QModelIndex & parent = QModelIndex(), const UINT8 mode = INSERT_MODE_APPEND);
|
||||||
|
|
||||||
// Compression routines
|
// Compression routines
|
||||||
UINT8 decompress(const QByteArray & compressed, const UINT8 compressionType, QByteArray & decompressedSection, UINT8 * algorithm = NULL);
|
UINT8 decompress(const QByteArray & compressed, const UINT8 compressionType, QByteArray & decompressedData, UINT8 * algorithm = NULL);
|
||||||
UINT8 compress(const QByteArray & data, const UINT8 algorithm, QByteArray & compressedData);
|
UINT8 compress(const QByteArray & data, const UINT8 algorithm, QByteArray & compressedData);
|
||||||
|
|
||||||
// Construction routines
|
// Construction routines
|
||||||
@ -74,7 +74,7 @@ public:
|
|||||||
bool isOfType(UINT8 type, const QModelIndex & index) const;
|
bool isOfType(UINT8 type, const QModelIndex & index) const;
|
||||||
bool isOfSubtype(UINT8 subtype, const QModelIndex & index) const;
|
bool isOfSubtype(UINT8 subtype, const QModelIndex & index) const;
|
||||||
QModelIndex findParentOfType(UINT8 type, const QModelIndex& index) const;
|
QModelIndex findParentOfType(UINT8 type, const QModelIndex& index) const;
|
||||||
|
|
||||||
// Will be refactored later
|
// Will be refactored later
|
||||||
bool isCompressedFile(const QModelIndex & index) const;
|
bool isCompressedFile(const QModelIndex & index) const;
|
||||||
QByteArray decompressFile(const QModelIndex & index) const;
|
QByteArray decompressFile(const QModelIndex & index) const;
|
||||||
|
@ -162,14 +162,14 @@ QVariant TreeItem::data(int column) const
|
|||||||
{
|
{
|
||||||
switch(column)
|
switch(column)
|
||||||
{
|
{
|
||||||
case 0: //Action
|
case 0: //Name
|
||||||
|
return itemName;
|
||||||
|
case 1: //Action
|
||||||
if (itemAction == TreeItem::Remove)
|
if (itemAction == TreeItem::Remove)
|
||||||
return "X";
|
return "X";
|
||||||
if (itemAction == TreeItem::Reconstruct)
|
if (itemAction == TreeItem::Reconstruct)
|
||||||
return "R";
|
return "R";
|
||||||
return QVariant();
|
return QVariant();
|
||||||
case 1: //Name
|
|
||||||
return itemName;
|
|
||||||
case 2: //Type
|
case 2: //Type
|
||||||
return itemTypeName;
|
return itemTypeName;
|
||||||
case 3: //Subtype
|
case 3: //Subtype
|
||||||
|
@ -162,31 +162,31 @@ QModelIndex TreeModel::addItem(const UINT8 type, const UINT8 subtype, const UINT
|
|||||||
parentItem = rootItem;
|
parentItem = rootItem;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (mode == ADD_MODE_APPEND || mode == ADD_MODE_PREPEND) {
|
if (mode == INSERT_MODE_BEFORE || mode == INSERT_MODE_AFTER) {
|
||||||
parentItem = static_cast<TreeItem*>(index.internalPointer());
|
|
||||||
parentColumn = index.column();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
item = static_cast<TreeItem*>(index.internalPointer());
|
item = static_cast<TreeItem*>(index.internalPointer());
|
||||||
parentItem = item->parent();
|
parentItem = item->parent();
|
||||||
parentColumn = index.parent().column();
|
parentColumn = index.parent().column();
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
parentItem = static_cast<TreeItem*>(index.internalPointer());
|
||||||
|
parentColumn = index.column();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeItem *newItem = new TreeItem(type, subtype, compression, name, text, info, header, body, parentItem);
|
TreeItem *newItem = new TreeItem(type, subtype, compression, name, text, info, header, body, parentItem);
|
||||||
if (mode == ADD_MODE_APPEND) {
|
if (mode == INSERT_MODE_APPEND) {
|
||||||
emit layoutAboutToBeChanged();
|
emit layoutAboutToBeChanged();
|
||||||
parentItem->appendChild(newItem);
|
parentItem->appendChild(newItem);
|
||||||
}
|
}
|
||||||
else if (mode == ADD_MODE_PREPEND) {
|
else if (mode == INSERT_MODE_PREPEND) {
|
||||||
emit layoutAboutToBeChanged();
|
emit layoutAboutToBeChanged();
|
||||||
parentItem->prependChild(newItem);
|
parentItem->prependChild(newItem);
|
||||||
}
|
}
|
||||||
else if (mode == ADD_MODE_INSERT_BEFORE) {
|
else if (mode == INSERT_MODE_BEFORE) {
|
||||||
emit layoutAboutToBeChanged();
|
emit layoutAboutToBeChanged();
|
||||||
parentItem->insertChildBefore(item, newItem);
|
parentItem->insertChildBefore(item, newItem);
|
||||||
}
|
}
|
||||||
else if (mode == ADD_MODE_INSERT_AFTER) {
|
else if (mode == INSERT_MODE_AFTER) {
|
||||||
emit layoutAboutToBeChanged();
|
emit layoutAboutToBeChanged();
|
||||||
parentItem->insertChildAfter(item, newItem);
|
parentItem->insertChildAfter(item, newItem);
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ public:
|
|||||||
QModelIndex addItem(const UINT8 type, const UINT8 subtype = 0, const UINT8 compression = COMPRESSION_ALGORITHM_NONE,
|
QModelIndex addItem(const UINT8 type, const UINT8 subtype = 0, const UINT8 compression = COMPRESSION_ALGORITHM_NONE,
|
||||||
const QString & name = QString(), const QString & text = QString(), const QString & info = QString(),
|
const QString & name = QString(), const QString & text = QString(), const QString & info = QString(),
|
||||||
const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), const QModelIndex & index = QModelIndex(),
|
const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), const QModelIndex & index = QModelIndex(),
|
||||||
const UINT8 mode = ADD_MODE_APPEND);
|
const UINT8 mode = INSERT_MODE_APPEND);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TreeItem *rootItem;
|
TreeItem *rootItem;
|
||||||
|
202
uefitool.cpp
202
uefitool.cpp
@ -32,10 +32,11 @@ UEFITool::UEFITool(QWidget *parent) :
|
|||||||
connect(ui->actionReplace, SIGNAL(triggered()), this, SLOT(replace()));
|
connect(ui->actionReplace, SIGNAL(triggered()), this, SLOT(replace()));
|
||||||
connect(ui->actionRemove, SIGNAL(triggered()), this, SLOT(remove()));
|
connect(ui->actionRemove, SIGNAL(triggered()), this, SLOT(remove()));
|
||||||
connect(ui->actionSaveImageFile, SIGNAL(triggered()), this, SLOT(saveImageFile()));
|
connect(ui->actionSaveImageFile, SIGNAL(triggered()), this, SLOT(saveImageFile()));
|
||||||
|
|
||||||
// Enable Drag-and-Drop actions
|
// Enable Drag-and-Drop actions
|
||||||
this->setAcceptDrops(true);
|
this->setAcceptDrops(true);
|
||||||
|
|
||||||
// Initialise non-persistent data
|
// Initialize non-persistent data
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,11 +72,12 @@ void UEFITool::init()
|
|||||||
connect(ui->structureTreeView, SIGNAL(expanded(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 &)),
|
connect(ui->structureTreeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
|
||||||
this, SLOT(populateUi(const QModelIndex &)));
|
this, SLOT(populateUi(const QModelIndex &)));
|
||||||
|
|
||||||
|
resizeTreeViewColums();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::populateUi(const QModelIndex ¤t/*, const QModelIndex &previous*/)
|
void UEFITool::populateUi(const QModelIndex ¤t)
|
||||||
{
|
{
|
||||||
//!TODO: make widget
|
|
||||||
currentIndex = current;
|
currentIndex = current;
|
||||||
ui->infoEdit->setPlainText(current.data(Qt::UserRole).toString());
|
ui->infoEdit->setPlainText(current.data(Qt::UserRole).toString());
|
||||||
ui->actionExtract->setDisabled(ffsEngine->hasEmptyBody(current) && ffsEngine->hasEmptyHeader(current));
|
ui->actionExtract->setDisabled(ffsEngine->hasEmptyBody(current) && ffsEngine->hasEmptyHeader(current));
|
||||||
@ -84,10 +86,14 @@ void UEFITool::populateUi(const QModelIndex ¤t/*, const QModelIndex &previ
|
|||||||
ui->actionRemove->setEnabled(ffsEngine->isOfType(TreeItem::Volume, current)
|
ui->actionRemove->setEnabled(ffsEngine->isOfType(TreeItem::Volume, current)
|
||||||
|| ffsEngine->isOfType(TreeItem::File, current)
|
|| ffsEngine->isOfType(TreeItem::File, current)
|
||||||
|| ffsEngine->isOfType(TreeItem::Section, current));
|
|| ffsEngine->isOfType(TreeItem::Section, current));
|
||||||
ui->actionInsertInto->setEnabled(ffsEngine->isOfType(TreeItem::Volume, current));
|
ui->actionInsertInto->setEnabled(ffsEngine->isOfType(TreeItem::Volume, current)
|
||||||
ui->actionInsertBefore->setEnabled(ffsEngine->isOfType(TreeItem::File, current));
|
|| ffsEngine->isOfType(TreeItem::File, current)
|
||||||
ui->actionInsertAfter->setEnabled(ffsEngine->isOfType(TreeItem::File, current));
|
|| ffsEngine->isOfType(TreeItem::Section, current));
|
||||||
ui->actionReplace->setEnabled(ffsEngine->isOfType(TreeItem::File, current));
|
ui->actionInsertBefore->setEnabled(ffsEngine->isOfType(TreeItem::File, current)
|
||||||
|
|| ffsEngine->isOfType(TreeItem::Section, current));
|
||||||
|
ui->actionInsertAfter->setEnabled(ffsEngine->isOfType(TreeItem::File, current)
|
||||||
|
|| ffsEngine->isOfType(TreeItem::Section, current));
|
||||||
|
//ui->actionReplace->setEnabled(ffsEngine->isOfType(TreeItem::File, current));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::remove()
|
void UEFITool::remove()
|
||||||
@ -98,109 +104,87 @@ void UEFITool::remove()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
ui->actionSaveImageFile->setEnabled(true);
|
ui->actionSaveImageFile->setEnabled(true);
|
||||||
resizeTreeViewColums();
|
|
||||||
|
resizeTreeViewColums();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UEFITool::insert(const UINT8 mode)
|
||||||
|
{
|
||||||
|
QString path;
|
||||||
|
TreeItem* item = static_cast<TreeItem*>(currentIndex.internalPointer());
|
||||||
|
|
||||||
|
UINT8 type;
|
||||||
|
UINT8 objectType;
|
||||||
|
if (mode == INSERT_MODE_BEFORE || mode == INSERT_MODE_BEFORE)
|
||||||
|
type = item->parent()->type();
|
||||||
|
else
|
||||||
|
type = item->type();
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case TreeItem::Volume:
|
||||||
|
path = QFileDialog::getOpenFileName(this, tr("Select FFS file to insert"),".","FFS file (*.ffs *.bin);;All files (*.*)");
|
||||||
|
objectType = TreeItem::File;
|
||||||
|
break;
|
||||||
|
case TreeItem::File:
|
||||||
|
case TreeItem::Section:
|
||||||
|
path = QFileDialog::getOpenFileName(this, tr("Select section file to insert"),".","Section file (*.sec *.bin);;All files (*.*)");
|
||||||
|
objectType = TreeItem::Section;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo fileInfo = QFileInfo(path);
|
||||||
|
if (!fileInfo.exists())
|
||||||
|
{
|
||||||
|
ui->statusBar->showMessage(tr("Please select existing file"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile inputFile;
|
||||||
|
inputFile.setFileName(path);
|
||||||
|
|
||||||
|
if (!inputFile.open(QFile::ReadOnly))
|
||||||
|
{
|
||||||
|
ui->statusBar->showMessage(tr("Can't open file for reading"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray buffer = inputFile.readAll();
|
||||||
|
inputFile.close();
|
||||||
|
|
||||||
|
UINT8 result = ffsEngine->insert(currentIndex, buffer, objectType, mode);
|
||||||
|
if (result)
|
||||||
|
ui->statusBar->showMessage(tr("File can't be inserted (%1)").arg(result));
|
||||||
|
else
|
||||||
|
ui->actionSaveImageFile->setEnabled(true);
|
||||||
|
|
||||||
|
resizeTreeViewColums();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::insertInto()
|
void UEFITool::insertInto()
|
||||||
{
|
{
|
||||||
QString path = QFileDialog::getOpenFileName(this, tr("Select FFS file to insert"),".","FFS file (*.ffs *.bin);;All files (*.*)");
|
insert(INSERT_MODE_PREPEND);
|
||||||
QFileInfo fileInfo = QFileInfo(path);
|
|
||||||
if (!fileInfo.exists())
|
|
||||||
{
|
|
||||||
ui->statusBar->showMessage(tr("Please select existing FFS file"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile inputFile;
|
|
||||||
inputFile.setFileName(path);
|
|
||||||
|
|
||||||
if (!inputFile.open(QFile::ReadOnly))
|
|
||||||
{
|
|
||||||
ui->statusBar->showMessage(tr("Can't open file for reading"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray buffer = inputFile.readAll();
|
|
||||||
inputFile.close();
|
|
||||||
|
|
||||||
UINT8 result = ffsEngine->insert(currentIndex, buffer, TreeItem::File, ADD_MODE_PREPEND);
|
|
||||||
if (result)
|
|
||||||
ui->statusBar->showMessage(tr("FFS file can't be inserted (%1)").arg(result));
|
|
||||||
else
|
|
||||||
ui->actionSaveImageFile->setEnabled(true);
|
|
||||||
resizeTreeViewColums();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::insertBefore()
|
void UEFITool::insertBefore()
|
||||||
{
|
{
|
||||||
QString path = QFileDialog::getOpenFileName(this, tr("Select FFS file to insert"),".","FFS file (*.ffs *.bin);;All files (*.*)");
|
insert(INSERT_MODE_BEFORE);
|
||||||
QFileInfo fileInfo = QFileInfo(path);
|
|
||||||
if (!fileInfo.exists())
|
|
||||||
{
|
|
||||||
ui->statusBar->showMessage(tr("Please select existing FFS file"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile inputFile;
|
|
||||||
inputFile.setFileName(path);
|
|
||||||
|
|
||||||
if (!inputFile.open(QFile::ReadOnly))
|
|
||||||
{
|
|
||||||
ui->statusBar->showMessage(tr("Can't open file for reading"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray buffer = inputFile.readAll();
|
|
||||||
inputFile.close();
|
|
||||||
|
|
||||||
UINT8 result = ffsEngine->insert(currentIndex, buffer, TreeItem::File, ADD_MODE_INSERT_BEFORE);
|
|
||||||
if (result)
|
|
||||||
ui->statusBar->showMessage(tr("FFS file can't be inserted (%1)").arg(result));
|
|
||||||
else
|
|
||||||
ui->actionSaveImageFile->setEnabled(true);
|
|
||||||
resizeTreeViewColums();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::insertAfter()
|
void UEFITool::insertAfter()
|
||||||
{
|
{
|
||||||
QString path = QFileDialog::getOpenFileName(this, tr("Select FFS file to insert"),".","FFS file (*.ffs *.bin);;All files (*.*)");
|
insert(INSERT_MODE_AFTER);
|
||||||
QFileInfo fileInfo = QFileInfo(path);
|
|
||||||
if (!fileInfo.exists())
|
|
||||||
{
|
|
||||||
ui->statusBar->showMessage(tr("Please select existing FFS file"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile inputFile;
|
|
||||||
inputFile.setFileName(path);
|
|
||||||
|
|
||||||
if (!inputFile.open(QFile::ReadOnly))
|
|
||||||
{
|
|
||||||
ui->statusBar->showMessage(tr("Can't open file for reading"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray buffer = inputFile.readAll();
|
|
||||||
inputFile.close();
|
|
||||||
|
|
||||||
UINT8 result = ffsEngine->insert(currentIndex, buffer, TreeItem::File, ADD_MODE_INSERT_AFTER);
|
|
||||||
if (result)
|
|
||||||
ui->statusBar->showMessage(tr("FFS file can't be inserted (%1)").arg(result));
|
|
||||||
else
|
|
||||||
ui->actionSaveImageFile->setEnabled(true);
|
|
||||||
resizeTreeViewColums();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::replace()
|
void UEFITool::replace()
|
||||||
{
|
{
|
||||||
remove();
|
|
||||||
insertAfter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::saveImageFile()
|
void UEFITool::saveImageFile()
|
||||||
{
|
{
|
||||||
QString path = QFileDialog::getSaveFileName(this, tr("Save BIOS image file"),".","BIOS image file (*.rom *.bin *.cap *.fd *.fwh);;All files (*.*)");
|
QString path = QFileDialog::getSaveFileName(this, tr("Save BIOS image file"),".","BIOS image file (*.rom *.bin *.cap *.fd *.wph *.efi);;All files (*.*)");
|
||||||
|
|
||||||
QFile outputFile;
|
QFile outputFile;
|
||||||
outputFile.setFileName(path);
|
outputFile.setFileName(path);
|
||||||
@ -219,6 +203,7 @@ void UEFITool::saveImageFile()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputFile.resize(0);
|
||||||
outputFile.write(reconstructed);
|
outputFile.write(reconstructed);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
ui->statusBar->showMessage(tr("Reconstructed image written"));
|
ui->statusBar->showMessage(tr("Reconstructed image written"));
|
||||||
@ -243,7 +228,7 @@ void UEFITool::openImageFile(QString path)
|
|||||||
QFileInfo fileInfo = QFileInfo(path);
|
QFileInfo fileInfo = QFileInfo(path);
|
||||||
if (!fileInfo.exists())
|
if (!fileInfo.exists())
|
||||||
{
|
{
|
||||||
ui->statusBar->showMessage(tr("Please select existing BIOS image file"));
|
ui->statusBar->showMessage(tr("Please select existing file"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +247,7 @@ void UEFITool::openImageFile(QString path)
|
|||||||
init();
|
init();
|
||||||
UINT8 result = ffsEngine->parseInputFile(buffer);
|
UINT8 result = ffsEngine->parseInputFile(buffer);
|
||||||
if (result)
|
if (result)
|
||||||
ui->statusBar->showMessage(tr("Opened file can't be parsed as UEFI image (%1)").arg(result));
|
ui->statusBar->showMessage(tr("Opened file can't be parsed (%1)").arg(result));
|
||||||
else
|
else
|
||||||
ui->statusBar->showMessage(tr("Opened: %1").arg(fileInfo.fileName()));
|
ui->statusBar->showMessage(tr("Opened: %1").arg(fileInfo.fileName()));
|
||||||
|
|
||||||
@ -279,66 +264,49 @@ void UEFITool::extract()
|
|||||||
outputFile.setFileName(path);
|
outputFile.setFileName(path);
|
||||||
if (!outputFile.open(QFile::WriteOnly))
|
if (!outputFile.open(QFile::WriteOnly))
|
||||||
{
|
{
|
||||||
ui->statusBar->showMessage(tr("Can't open file for writing. Check file permissions."));
|
ui->statusBar->showMessage(tr("Can't open file for rewriting"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputFile.resize(0);
|
||||||
outputFile.write(ffsEngine->header(currentIndex) + ffsEngine->body(currentIndex));
|
outputFile.write(ffsEngine->header(currentIndex) + ffsEngine->body(currentIndex));
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::extractBody()
|
void UEFITool::extractBody()
|
||||||
{
|
{
|
||||||
QString path = QFileDialog::getSaveFileName(this, tr("Save selected item without header to binary file"),".","Binary files (*.bin);;All files (*.*)");
|
QString path = QFileDialog::getSaveFileName(this, tr("Save selected item without header to file"),".","Binary files (*.bin);;All files (*.*)");
|
||||||
|
|
||||||
QFile outputFile;
|
QFile outputFile;
|
||||||
outputFile.setFileName(path);
|
outputFile.setFileName(path);
|
||||||
if (!outputFile.open(QFile::WriteOnly))
|
if (!outputFile.open(QFile::WriteOnly))
|
||||||
{
|
{
|
||||||
ui->statusBar->showMessage(tr("Can't open file for writing. Check file permissions."));
|
ui->statusBar->showMessage(tr("Can't open file for rewriting"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputFile.resize(0);
|
||||||
outputFile.write(ffsEngine->body(currentIndex));
|
outputFile.write(ffsEngine->body(currentIndex));
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEFITool::extractUncompressed()
|
void UEFITool::extractUncompressed()
|
||||||
{
|
{
|
||||||
QString path = QFileDialog::getSaveFileName(this, tr("Save selected FFS file as uncompressed to binary file"),".","FFS files (*.ffs);;All files (*.*)");
|
QString path = QFileDialog::getSaveFileName(this, tr("Save selected FFS file as uncompressed to file"),".","FFS files (*.ffs);;All files (*.*)");
|
||||||
|
|
||||||
QFile outputFile;
|
QFile outputFile;
|
||||||
outputFile.setFileName(path);
|
outputFile.setFileName(path);
|
||||||
if (!outputFile.open(QFile::WriteOnly))
|
if (!outputFile.open(QFile::WriteOnly))
|
||||||
{
|
{
|
||||||
ui->statusBar->showMessage(tr("Can't open file for writing. Check file permissions."));
|
ui->statusBar->showMessage(tr("Can't open file for rewriting"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputFile.resize(0);
|
||||||
outputFile.write(ffsEngine->decompressFile(currentIndex));
|
outputFile.write(ffsEngine->decompressFile(currentIndex));
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*void UEFITool::saveImageFile()
|
|
||||||
{
|
|
||||||
QString path = QFileDialog::getSaveFileName(this, tr("Save BIOS image file"),".","BIOS image file (*.rom *.bin *.cap *.fd *.fwh);;All files (*.*)");
|
|
||||||
|
|
||||||
QFileInfo fileInfo = QFileInfo(path);
|
|
||||||
if (!fileInfo.exists())
|
|
||||||
{
|
|
||||||
ui->statusBar->showMessage(tr("Please select existing BIOS image file."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile outputFile;
|
|
||||||
outputFile.setFileName(path);
|
|
||||||
if (!outputFile.open(QFile::ReadWrite))
|
|
||||||
{
|
|
||||||
ui->statusBar->showMessage(tr("Can't open file for writing. Check file permissions."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
void UEFITool::dragEnterEvent(QDragEnterEvent* event)
|
void UEFITool::dragEnterEvent(QDragEnterEvent* event)
|
||||||
{
|
{
|
||||||
if (event->mimeData()->hasFormat("text/uri-list"))
|
if (event->mimeData()->hasFormat("text/uri-list"))
|
||||||
|
@ -52,6 +52,7 @@ private slots:
|
|||||||
void extract();
|
void extract();
|
||||||
void extractBody();
|
void extractBody();
|
||||||
void extractUncompressed();
|
void extractUncompressed();
|
||||||
|
void insert(const UINT8 mode);
|
||||||
void insertInto();
|
void insertInto();
|
||||||
void insertBefore();
|
void insertBefore();
|
||||||
void insertAfter();
|
void insertAfter();
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>UEFITool 0.4.0</string>
|
<string>UEFITool 0.5.0</string>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralWidget">
|
<widget class="QWidget" name="centralWidget">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -203,7 +203,7 @@
|
|||||||
<string>toolBar</string>
|
<string>toolBar</string>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="toolBarArea">
|
<attribute name="toolBarArea">
|
||||||
<enum>TopToolBarArea</enum>
|
<enum>LeftToolBarArea</enum>
|
||||||
</attribute>
|
</attribute>
|
||||||
<attribute name="toolBarBreak">
|
<attribute name="toolBarBreak">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
|
Loading…
Reference in New Issue
Block a user