mirror of
https://github.com/LongSoft/UEFITool.git
synced 2024-11-22 07:58:22 +08:00
NE_alpha4
- second pass of parsing to add physical memory addresses to all uncompressed items in the tree - TE image revisions can be detected now - more builder routines, but still not ready for enabling - PE and TE header info is back
This commit is contained in:
parent
129819314d
commit
87bd80b72c
@ -17,7 +17,7 @@
|
||||
UEFITool::UEFITool(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::UEFITool),
|
||||
version(tr("0.30.0_alpha3"))
|
||||
version(tr("0.30.0_alpha4"))
|
||||
{
|
||||
clipboard = QApplication::clipboard();
|
||||
|
||||
|
@ -37,9 +37,15 @@ void FfsBuilder::clearMessages()
|
||||
messagesVector.clear();
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::build(const QModelIndex & root, QByteArray & image)
|
||||
STATUS FfsBuilder::erase(const QModelIndex & index, QByteArray & erased)
|
||||
{
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
// Sanity check
|
||||
if (!index.isValid())
|
||||
return ERR_INVALID_PARAMETER;
|
||||
|
||||
PARSING_DATA pdata = parsingDataFromQByteArray(index);
|
||||
erased.fill(pdata.emptyByte);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::buildCapsule(const QModelIndex & index, QByteArray & capsule)
|
||||
@ -63,20 +69,24 @@ STATUS FfsBuilder::buildCapsule(const QModelIndex & index, QByteArray & capsule)
|
||||
// Clear the supplied QByteArray
|
||||
capsule.clear();
|
||||
|
||||
// Reconstruct children
|
||||
// Build children
|
||||
for (int i = 0; i < model->rowCount(index); i++) {
|
||||
QModelIndex currentChild = index.child(i, 0);
|
||||
QByteArray currentData;
|
||||
// Check child type
|
||||
if (model->type(currentChild) == Types::Image) {
|
||||
result = buildImage(currentChild, currentData);
|
||||
if (!result) {
|
||||
capsule.append(currentData);
|
||||
}
|
||||
else {
|
||||
if (model->subtype(currentChild) == Subtypes::IntelImage)
|
||||
result = buildIntelImage(currentChild, currentData);
|
||||
else
|
||||
result = buildRawArea(currentChild, currentData);
|
||||
|
||||
// Check build result
|
||||
if (result) {
|
||||
msg(tr("buildCapsule: building of \"%1\" failed with error \"%2\", original item data used").arg(model->name(currentChild)).arg(errorCodeToQString(result)), currentChild);
|
||||
capsule.append(model->header(currentChild)).append(model->body(currentChild));
|
||||
}
|
||||
else
|
||||
capsule.append(currentData);
|
||||
}
|
||||
else {
|
||||
msg(tr("buildCapsule: unexpected child item of type \"%1\" can't be processed, original item data used").arg(itemTypeToQString(model->type(currentChild))), currentChild);
|
||||
@ -85,18 +95,16 @@ STATUS FfsBuilder::buildCapsule(const QModelIndex & index, QByteArray & capsule)
|
||||
}
|
||||
|
||||
// Check size of reconstructed capsule, it must remain the same
|
||||
if (capsule.size() > model->body(index).size()) {
|
||||
UINT32 newSize = capsule.size();
|
||||
UINT32 oldSize = model->body(index).size();
|
||||
if (newSize > oldSize) {
|
||||
msg(tr("buildCapsule: new capsule size %1h (%2) is bigger than the original %3h (%4)")
|
||||
.hexarg(capsule.size()).arg(capsule.size())
|
||||
.hexarg(model->body(index).size()).arg(model->body(index).size()),
|
||||
index);
|
||||
.hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize),index);
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
else if (capsule.size() < model->body(index).size()) {
|
||||
else if (newSize < oldSize) {
|
||||
msg(tr("buildCapsule: new capsule size %1h (%2) is smaller than the original %3h (%4)")
|
||||
.hexarg(capsule.size()).arg(capsule.size())
|
||||
.hexarg(model->body(index).size()).arg(model->body(index).size()),
|
||||
index);
|
||||
.hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index);
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
@ -107,16 +115,197 @@ STATUS FfsBuilder::buildCapsule(const QModelIndex & index, QByteArray & capsule)
|
||||
capsule = model->header(index).append(capsule);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
msg(tr("buildCapsule: unexpected action \"%1\"").arg(actionTypeToQString(model->action(index))), index);
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::buildImage(const QModelIndex & index, QByteArray & intelImage)
|
||||
STATUS FfsBuilder::buildIntelImage(const QModelIndex & index, QByteArray & intelImage)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return ERR_SUCCESS;
|
||||
|
||||
UINT8 result;
|
||||
|
||||
// No action
|
||||
if (model->action(index) == Actions::NoAction) {
|
||||
intelImage = model->header(index).append(model->body(index));
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
// Other supported actions
|
||||
else if (model->action(index) == Actions::Rebuild) {
|
||||
intelImage.clear();
|
||||
// First child will always be descriptor for this type of image
|
||||
QByteArray descriptor;
|
||||
result = buildRegion(index.child(0, 0), descriptor);
|
||||
if (result)
|
||||
return result;
|
||||
intelImage.append(descriptor);
|
||||
|
||||
const FLASH_DESCRIPTOR_MAP* descriptorMap = (const FLASH_DESCRIPTOR_MAP*)(descriptor.constData() + sizeof(FLASH_DESCRIPTOR_HEADER));
|
||||
const FLASH_DESCRIPTOR_REGION_SECTION* regionSection = (const FLASH_DESCRIPTOR_REGION_SECTION*)calculateAddress8((const UINT8*)descriptor.constData(), descriptorMap->RegionBase);
|
||||
QByteArray gbe;
|
||||
UINT32 gbeBegin = calculateRegionOffset(regionSection->GbeBase);
|
||||
UINT32 gbeEnd = gbeBegin + calculateRegionSize(regionSection->GbeBase, regionSection->GbeLimit);
|
||||
QByteArray me;
|
||||
UINT32 meBegin = calculateRegionOffset(regionSection->MeBase);
|
||||
UINT32 meEnd = meBegin + calculateRegionSize(regionSection->MeBase, regionSection->MeLimit);
|
||||
QByteArray bios;
|
||||
UINT32 biosBegin = calculateRegionOffset(regionSection->BiosBase);
|
||||
UINT32 biosEnd = biosBegin + calculateRegionSize(regionSection->BiosBase, regionSection->BiosLimit);
|
||||
QByteArray pdr;
|
||||
UINT32 pdrBegin = calculateRegionOffset(regionSection->PdrBase);
|
||||
UINT32 pdrEnd = pdrBegin + calculateRegionSize(regionSection->PdrBase, regionSection->PdrLimit);
|
||||
|
||||
UINT32 offset = descriptor.size();
|
||||
// Reconstruct other regions
|
||||
char empty = '\xFF';
|
||||
for (int i = 1; i < model->rowCount(index); i++) {
|
||||
QByteArray region;
|
||||
result = buildRegion(index.child(i, 0), region);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
UINT8 type = model->subtype(index.child(i, 0));
|
||||
switch (type)
|
||||
{
|
||||
case Subtypes::GbeRegion:
|
||||
gbe = region;
|
||||
if (gbeBegin > offset)
|
||||
intelImage.append(QByteArray(gbeBegin - offset, empty));
|
||||
intelImage.append(gbe);
|
||||
offset = gbeEnd;
|
||||
break;
|
||||
case Subtypes::MeRegion:
|
||||
me = region;
|
||||
if (meBegin > offset)
|
||||
intelImage.append(QByteArray(meBegin - offset, empty));
|
||||
intelImage.append(me);
|
||||
offset = meEnd;
|
||||
break;
|
||||
case Subtypes::BiosRegion:
|
||||
bios = region;
|
||||
if (biosBegin > offset)
|
||||
intelImage.append(QByteArray(biosBegin - offset, empty));
|
||||
intelImage.append(bios);
|
||||
offset = biosEnd;
|
||||
break;
|
||||
case Subtypes::PdrRegion:
|
||||
pdr = region;
|
||||
if (pdrBegin > offset)
|
||||
intelImage.append(QByteArray(pdrBegin - offset, empty));
|
||||
intelImage.append(pdr);
|
||||
offset = pdrEnd;
|
||||
break;
|
||||
default:
|
||||
msg(tr("buildIntelImage: unknown region type found"), index);
|
||||
return ERR_INVALID_REGION;
|
||||
}
|
||||
}
|
||||
if ((UINT32)model->body(index).size() > offset)
|
||||
intelImage.append(QByteArray((UINT32)model->body(index).size() - offset, empty));
|
||||
|
||||
// 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(tr("buildIntelImage: new image size %1h (%2) is bigger than the original %3h (%4)")
|
||||
.hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index);
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
else if (newSize < oldSize) {
|
||||
msg(tr("buildIntelImage: new image size %1h (%2) is smaller than the original %3h (%4)")
|
||||
.hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index);
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// Reconstruction successful
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
msg(tr("buildIntelImage: unexpected action \"%1\"").arg(actionTypeToQString(model->action(index))), index);
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::buildRawArea(const QModelIndex & index, QByteArray & rawArea)
|
||||
STATUS FfsBuilder::buildRegion(const QModelIndex & index, QByteArray & region)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return ERR_SUCCESS;
|
||||
|
||||
UINT8 result;
|
||||
|
||||
// No action required
|
||||
if (model->action(index) == Actions::NoAction) {
|
||||
region = model->header(index).append(model->body(index));
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
// Erase
|
||||
else if (model->action(index) == Actions::Erase) {
|
||||
region = model->header(index).append(model->body(index));
|
||||
if (erase(index, region))
|
||||
msg(tr("buildRegion: erase failed, original item data used"), index);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
// Rebuild or replace
|
||||
else if (model->action(index) == Actions::Rebuild ||
|
||||
model->action(index) == Actions::Replace) {
|
||||
if (model->rowCount(index)) {
|
||||
region.clear();
|
||||
// Build children
|
||||
for (int i = 0; i < model->rowCount(index); i++) {
|
||||
QModelIndex currentChild = index.child(i, 0);
|
||||
QByteArray 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(tr("buildRegion: unexpected child item of type \"%1\" can't be processed, original item data used").arg(itemTypeToQString(model->type(currentChild))), currentChild);
|
||||
result = ERR_SUCCESS;
|
||||
currentData = model->header(currentChild).append(model->body(currentChild));
|
||||
}
|
||||
// Check build result
|
||||
if (result) {
|
||||
msg(tr("buildRegion: building of \"%1\" failed with error \"%2\", original item data used").arg(model->name(currentChild)).arg(errorCodeToQString(result)), currentChild);
|
||||
currentData = model->header(currentChild).append(model->body(currentChild));
|
||||
}
|
||||
// Append current data
|
||||
region.append(currentData);
|
||||
}
|
||||
}
|
||||
else
|
||||
region = model->body(index);
|
||||
|
||||
// Check size of new region, it must be same as original one
|
||||
UINT32 newSize = region.size();
|
||||
UINT32 oldSize = model->body(index).size();
|
||||
if (newSize > oldSize) {
|
||||
msg(tr("buildRegion: new region size %1h (%2) is bigger than the original %3h (%4)")
|
||||
.hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index);
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
else if (newSize < oldSize) {
|
||||
msg(tr("buildRegion: new region size %1h (%2) is smaller than the original %3h (%4)")
|
||||
.hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index);
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// Build successful
|
||||
region = model->header(index).append(region);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
msg(tr("buildRegion: unexpected action \"%1\"").arg(actionTypeToQString(model->action(index))), index);
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::buildRawArea(const QModelIndex & index, QByteArray & rawArea, bool addHeader)
|
||||
{
|
||||
// Sanity check
|
||||
if (!index.isValid())
|
||||
@ -137,7 +326,7 @@ STATUS FfsBuilder::buildRawArea(const QModelIndex & index, QByteArray & rawArea)
|
||||
// Clear the supplied QByteArray
|
||||
rawArea.clear();
|
||||
|
||||
// Reconstruct children
|
||||
// Build children
|
||||
for (int i = 0; i < model->rowCount(index); i++) {
|
||||
QModelIndex currentChild = index.child(i, 0);
|
||||
QByteArray currentData;
|
||||
@ -162,26 +351,25 @@ STATUS FfsBuilder::buildRawArea(const QModelIndex & index, QByteArray & rawArea)
|
||||
rawArea.append(currentData);
|
||||
}
|
||||
|
||||
// Check size of reconstructed raw area, it must remain the same
|
||||
if (rawArea.size() > model->body(index).size()) {
|
||||
msg(tr("buildRawArea: new raw area size %1h (%2) is bigger than the original %3h (%4)")
|
||||
.hexarg(rawArea.size()).arg(rawArea.size())
|
||||
.hexarg(model->body(index).size()).arg(model->body(index).size()),
|
||||
index);
|
||||
// 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(tr("buildRawArea: new area size %1h (%2) is bigger than the original %3h (%4)")
|
||||
.hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index);
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
else if (rawArea.size() < model->body(index).size()) {
|
||||
msg(tr("buildRawArea: new raw area size %1h (%2) is smaller than the original %3h (%4)")
|
||||
.hexarg(rawArea.size()).arg(rawArea.size())
|
||||
.hexarg(model->body(index).size()).arg(model->body(index).size()),
|
||||
index);
|
||||
else if (newSize < oldSize) {
|
||||
msg(tr("buildRawArea: new area size %1h (%2) is smaller than the original %3h (%4)")
|
||||
.hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index);
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
else
|
||||
rawArea = model->body(index);
|
||||
|
||||
// Build successful, append header
|
||||
// Build successful, add header if needed
|
||||
if (addHeader)
|
||||
rawArea = model->header(index).append(rawArea);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
@ -190,11 +378,6 @@ STATUS FfsBuilder::buildRawArea(const QModelIndex & index, QByteArray & rawArea)
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::buildVolume(const QModelIndex & index, QByteArray & volume)
|
||||
{
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::buildPadding(const QModelIndex & index, QByteArray & padding)
|
||||
{
|
||||
// Sanity check
|
||||
@ -209,7 +392,9 @@ STATUS FfsBuilder::buildPadding(const QModelIndex & index, QByteArray & padding)
|
||||
|
||||
// Erase
|
||||
else if (model->action(index) == Actions::Erase) {
|
||||
padding.fill('\xFF', model->header(index).size() + model->body(index).size());
|
||||
padding = model->header(index).append(model->body(index));
|
||||
if(erase(index, padding))
|
||||
msg(tr("buildPadding: erase failed, original item data used"), index);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
@ -231,7 +416,9 @@ STATUS FfsBuilder::buildNonUefiData(const QModelIndex & index, QByteArray & data
|
||||
|
||||
// Erase
|
||||
else if (model->action(index) == Actions::Erase) {
|
||||
data.fill('\xFF', model->header(index).size() + model->body(index).size());
|
||||
data = model->header(index).append(model->body(index));
|
||||
if (erase(index, data))
|
||||
msg(tr("buildNonUefiData: erase failed, original item data used"), index);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
@ -255,6 +442,11 @@ STATUS FfsBuilder::buildFreeSpace(const QModelIndex & index, QByteArray & freeSp
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::buildVolume(const QModelIndex & index, QByteArray & volume)
|
||||
{
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::buildPadFile(const QModelIndex & index, QByteArray & padFile)
|
||||
{
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
@ -270,3 +462,7 @@ STATUS FfsBuilder::buildSection(const QModelIndex & index, QByteArray & section)
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
STATUS FfsBuilder::build(const QModelIndex & root, QByteArray & image)
|
||||
{
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
||||
|
||||
#include "../common/basetypes.h"
|
||||
#include "../common/treemodel.h"
|
||||
#include "../common/descriptor.h"
|
||||
#include "../common/ffs.h"
|
||||
#include "../common/utility.h"
|
||||
|
||||
@ -44,17 +45,19 @@ private:
|
||||
|
||||
// UEFI standard structures
|
||||
STATUS buildCapsule(const QModelIndex & index, QByteArray & capsule);
|
||||
STATUS buildImage(const QModelIndex & index, QByteArray & intelImage);
|
||||
STATUS buildIntelImage(const QModelIndex & index, QByteArray & intelImage);
|
||||
STATUS buildRegion(const QModelIndex & index, QByteArray & region);
|
||||
STATUS buildRawArea(const QModelIndex & index, QByteArray & rawArea);
|
||||
STATUS buildVolume(const QModelIndex & index, QByteArray & volume);
|
||||
STATUS buildRawArea(const QModelIndex & index, QByteArray & rawArea, bool addHeader = true);
|
||||
STATUS buildPadding(const QModelIndex & index, QByteArray & padding);
|
||||
STATUS buildVolume(const QModelIndex & index, QByteArray & volume);
|
||||
STATUS buildNonUefiData(const QModelIndex & index, QByteArray & data);
|
||||
STATUS buildFreeSpace(const QModelIndex & index, QByteArray & freeSpace);
|
||||
STATUS buildPadFile(const QModelIndex & index, QByteArray & padFile);
|
||||
STATUS buildFile(const QModelIndex & index, QByteArray & file);
|
||||
STATUS buildSection(const QModelIndex & index, QByteArray & section);
|
||||
|
||||
// Utility functions
|
||||
STATUS erase(const QModelIndex & index, QByteArray & erased);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -153,7 +153,19 @@ STATUS FfsParser::parseImageFile(const QByteArray & buffer, const QModelIndex &
|
||||
QModelIndex biosIndex = model->addItem(Types::Image, Subtypes::UefiImage, name, QString(), info, QByteArray(), flashImage, parsingDataToQByteArray(pdata), index);
|
||||
|
||||
// Parse the image
|
||||
return parseRawArea(flashImage, biosIndex);
|
||||
result = parseRawArea(flashImage, biosIndex);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
// Check if the last VTF is found
|
||||
if (!lastVtf.isValid()) {
|
||||
msg(tr("parseImageFile: not a single Volume Top File is found, physical memory addresses can't be calculated"), biosIndex);
|
||||
}
|
||||
else {
|
||||
return addMemoryAddressesInfo(biosIndex);
|
||||
}
|
||||
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
STATUS FfsParser::parseIntelImage(const QByteArray & intelImage, const QModelIndex & parent, QModelIndex & index)
|
||||
@ -397,6 +409,14 @@ STATUS FfsParser::parseIntelImage(const QByteArray & intelImage, const QModelInd
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if the last VTF is found
|
||||
if (!lastVtf.isValid()) {
|
||||
msg(tr("parseIntelImage: not a single Volume Top File is found, physical memory addresses can't be calculated"), index);
|
||||
}
|
||||
else {
|
||||
return addMemoryAddressesInfo(index);
|
||||
}
|
||||
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
@ -595,6 +615,7 @@ STATUS FfsParser::parseRawArea(const QByteArray & data, const QModelIndex & inde
|
||||
|
||||
// Construct parsing data
|
||||
pdata.fixed = TRUE;
|
||||
pdata.offset = offset + headerSize;
|
||||
if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset));
|
||||
|
||||
// Add tree item
|
||||
@ -1216,6 +1237,17 @@ STATUS FfsParser::parseFileHeader(const QByteArray & file, const UINT32 parentOf
|
||||
.hexarg(body.size()).arg(body.size())
|
||||
.hexarg2(fileHeader->State, 2);
|
||||
|
||||
// Check if the file is a Volume Top File
|
||||
QString text;
|
||||
bool isVtf = false;
|
||||
if (EFI_FFS_VOLUME_TOP_FILE_GUID == header.left(sizeof(EFI_GUID))) {
|
||||
// Mark it as the last VTF
|
||||
// This information will later be used to determine memory addresses of uncompressed image elements
|
||||
// Because the last byte of the last VFT is mapped to 0xFFFFFFFF physical memory address
|
||||
isVtf = true;
|
||||
text = tr("Volume Top File");
|
||||
}
|
||||
|
||||
// Construct parsing data
|
||||
pdata.fixed = fileHeader->Attributes & FFS_ATTRIB_FIXED;
|
||||
pdata.offset += parentOffset;
|
||||
@ -1224,7 +1256,12 @@ STATUS FfsParser::parseFileHeader(const QByteArray & file, const UINT32 parentOf
|
||||
if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset));
|
||||
|
||||
// Add tree item
|
||||
index = model->addItem(Types::File, fileHeader->Type, name, "", info, header, body, parsingDataToQByteArray(pdata), parent);
|
||||
index = model->addItem(Types::File, fileHeader->Type, name, text, info, header, body, parsingDataToQByteArray(pdata), parent);
|
||||
|
||||
// Overwrite lastVtf, if needed
|
||||
if (isVtf) {
|
||||
lastVtf = index;
|
||||
}
|
||||
|
||||
// Show messages
|
||||
if (msgUnalignedFile)
|
||||
@ -1435,9 +1472,9 @@ STATUS FfsParser::parseSectionHeader(const QByteArray & section, const UINT32 pa
|
||||
case EFI_SECTION_DXE_DEPEX:
|
||||
case EFI_SECTION_PEI_DEPEX:
|
||||
case EFI_SECTION_SMM_DEPEX:
|
||||
case EFI_SECTION_TE:
|
||||
case EFI_SECTION_PE32:
|
||||
case EFI_SECTION_PIC:
|
||||
case EFI_SECTION_TE:
|
||||
case EFI_SECTION_COMPATIBILITY16:
|
||||
case EFI_SECTION_USER_INTERFACE:
|
||||
case EFI_SECTION_FIRMWARE_VOLUME_IMAGE:
|
||||
@ -1732,6 +1769,7 @@ STATUS FfsParser::parsePostcodeSectionHeader(const QByteArray & section, const U
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
STATUS FfsParser::parseSectionBody(const QModelIndex & index)
|
||||
{
|
||||
// Sanity check
|
||||
@ -1751,9 +1789,9 @@ STATUS FfsParser::parseSectionBody(const QModelIndex & index)
|
||||
case EFI_SECTION_DXE_DEPEX:
|
||||
case EFI_SECTION_PEI_DEPEX:
|
||||
case EFI_SECTION_SMM_DEPEX: return parseDepexSectionBody(index);
|
||||
case EFI_SECTION_TE: return ERR_SUCCESS;//return parseTeSectionBody(index);
|
||||
case EFI_SECTION_TE: return parseTeImageSectionBody(index);
|
||||
case EFI_SECTION_PE32:
|
||||
case EFI_SECTION_PIC: return ERR_SUCCESS;//return parsePeSectionBody(index);
|
||||
case EFI_SECTION_PIC: return parsePeImageSectionBody(index);
|
||||
case EFI_SECTION_USER_INTERFACE: return parseUiSectionBody(index);
|
||||
case EFI_SECTION_FIRMWARE_VOLUME_IMAGE: return parseRawArea(model->body(index), index);
|
||||
case EFI_SECTION_RAW: return parseRawSectionBody(index);
|
||||
@ -2109,37 +2147,29 @@ STATUS FfsParser::parseRawSectionBody(const QModelIndex & index)
|
||||
return parseRawArea(model->body(index), index);
|
||||
}
|
||||
|
||||
/*
|
||||
STATUS FfsEngine::parsePeSectionHeader(const QByteArray & section, const UINT32 parentOffset, const QModelIndex & parent, QModelIndex & index)
|
||||
{
|
||||
const EFI_COMMON_SECTION_HEADER* sectionHeader = (const EFI_COMMON_SECTION_HEADER*)(section.constData());
|
||||
UINT32 headerSize = sizeOfSectionHeader(sectionHeader);
|
||||
QByteArray header = section.left(headerSize);
|
||||
QByteArray body = section.mid(headerSize);
|
||||
|
||||
// Get standard info
|
||||
QString name = sectionTypeToQString(sectionHeader->Type) + tr(" section");
|
||||
QString info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
|
||||
.hexarg2(sectionHeader->Type, 2)
|
||||
.hexarg(section.size()).arg(section.size())
|
||||
.hexarg(header.size()).arg(header.size())
|
||||
.hexarg(body.size()).arg(body.size());
|
||||
STATUS FfsParser::parsePeImageSectionBody(const QModelIndex & index)
|
||||
{
|
||||
// Sanity check
|
||||
if (!index.isValid())
|
||||
return ERR_INVALID_PARAMETER;
|
||||
|
||||
// Get section body
|
||||
QByteArray body = model->body(index);
|
||||
|
||||
// Get PE info
|
||||
bool msgInvalidDosSignature = false;
|
||||
bool msgInvalidPeSignature = false;
|
||||
bool msgUnknownOptionalHeaderSignature = false;
|
||||
QByteArray info;
|
||||
|
||||
const EFI_IMAGE_DOS_HEADER* dosHeader = (const EFI_IMAGE_DOS_HEADER*)body.constData();
|
||||
if (dosHeader->e_magic != EFI_IMAGE_DOS_SIGNATURE) {
|
||||
info += tr("\nDOS signature: %1h, invalid").hexarg2(dosHeader->e_magic, 4);
|
||||
msgInvalidDosSignature = true;
|
||||
msg(tr("parsePeImageSectionBody: PE32 image with invalid DOS signature"), index);
|
||||
}
|
||||
else {
|
||||
const EFI_IMAGE_PE_HEADER* peHeader = (EFI_IMAGE_PE_HEADER*)(body.constData() + dosHeader->e_lfanew);
|
||||
if (peHeader->Signature != EFI_IMAGE_PE_SIGNATURE) {
|
||||
info += tr("\nPE signature: %1h, invalid").hexarg2(peHeader->Signature, 8);
|
||||
msgInvalidPeSignature = true;
|
||||
msg(tr("parsePeImageSectionBody: PE32 image with invalid PE signature"), index);
|
||||
}
|
||||
else {
|
||||
const EFI_IMAGE_FILE_HEADER* imageFileHeader = (const EFI_IMAGE_FILE_HEADER*)(peHeader + 1);
|
||||
@ -2153,82 +2183,53 @@ STATUS FfsEngine::parsePeSectionHeader(const QByteArray & section, const UINT32
|
||||
EFI_IMAGE_OPTIONAL_HEADER_POINTERS_UNION optionalHeader;
|
||||
optionalHeader.H32 = (const EFI_IMAGE_OPTIONAL_HEADER32*)(imageFileHeader + 1);
|
||||
if (optionalHeader.H32->Magic == EFI_IMAGE_PE_OPTIONAL_HDR32_MAGIC) {
|
||||
info += tr("\nOptional header signature: %1h\nSubsystem: %2h\nRelativeEntryPoint: %3h\nBaseOfCode: %4h\nImageBase: %5h\nEntryPoint: %6h")
|
||||
info += tr("\nOptional header signature: %1h\nSubsystem: %2h\nAddress of entryPoint: %3h\nBase of code: %4h\nImage base: %5h")
|
||||
.hexarg2(optionalHeader.H32->Magic, 4)
|
||||
.hexarg2(optionalHeader.H32->Subsystem, 4)
|
||||
.hexarg(optionalHeader.H32->AddressOfEntryPoint)
|
||||
.hexarg(optionalHeader.H32->BaseOfCode)
|
||||
.hexarg(optionalHeader.H32->ImageBase)
|
||||
.hexarg(optionalHeader.H32->ImageBase + optionalHeader.H32->AddressOfEntryPoint);
|
||||
.hexarg(optionalHeader.H32->ImageBase);
|
||||
}
|
||||
else if (optionalHeader.H32->Magic == EFI_IMAGE_PE_OPTIONAL_HDR64_MAGIC) {
|
||||
info += tr("\nOptional header signature: %1h\nSubsystem: %2h\nRelativeEntryPoint: %3h\nBaseOfCode: %4h\nImageBase: %5h\nEntryPoint: %6h")
|
||||
info += tr("\nOptional header signature: %1h\nSubsystem: %2h\nAddress of entryPoint: %3h\nBase of code: %4h\nImage base: %5h")
|
||||
.hexarg2(optionalHeader.H64->Magic, 4)
|
||||
.hexarg2(optionalHeader.H64->Subsystem, 4)
|
||||
.hexarg(optionalHeader.H64->AddressOfEntryPoint)
|
||||
.hexarg(optionalHeader.H64->BaseOfCode)
|
||||
.hexarg(optionalHeader.H64->ImageBase)
|
||||
.hexarg(optionalHeader.H64->ImageBase + optionalHeader.H64->AddressOfEntryPoint);
|
||||
.hexarg(optionalHeader.H64->ImageBase);
|
||||
}
|
||||
else {
|
||||
info += tr("\nOptional header signature: %1h, unknown").hexarg2(optionalHeader.H64->Magic, 4);
|
||||
msgUnknownOptionalHeaderSignature = true;
|
||||
msg(tr("parsePeImageSectionBody: PE32 image with invalid optional PE header signature"), index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add tree item
|
||||
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, QByteArray(), parent, mode);
|
||||
// Add PE info
|
||||
model->addInfo(index, info);
|
||||
|
||||
// Show messages
|
||||
if (msgInvalidDosSignature) {
|
||||
msg("parseSection: PE32 image with invalid DOS signature", index);
|
||||
}
|
||||
if (msgInvalidPeSignature) {
|
||||
msg("parseSection: PE32 image with invalid PE signature", index);
|
||||
}
|
||||
if (msgUnknownOptionalHeaderSignature) {
|
||||
msg("parseSection: PE32 image with unknown optional header signature", index);
|
||||
}
|
||||
|
||||
// Special case of PEI Core
|
||||
QModelIndex core = model->findParentOfType(index, Types::File);
|
||||
if (core.isValid() && model->subtype(core) == EFI_FV_FILETYPE_PEI_CORE
|
||||
&& oldPeiCoreEntryPoint == 0) {
|
||||
result = getEntryPoint(model->body(index), oldPeiCoreEntryPoint);
|
||||
if (result)
|
||||
msg(tr("parseSection: can't get original PEI core entry point"), index);
|
||||
}
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
STATUS FfsEngine::parseTeSectionHeader(const QByteArray & section, const UINT32 parentOffset, const QModelIndex & parent, QModelIndex & index)
|
||||
|
||||
STATUS FfsParser::parseTeImageSectionBody(const QModelIndex & index)
|
||||
{
|
||||
const EFI_COMMON_SECTION_HEADER* sectionHeader = (const EFI_COMMON_SECTION_HEADER*)(section.constData());
|
||||
UINT32 headerSize = sizeOfSectionHeader(sectionHeader);
|
||||
QByteArray header = section.left(headerSize);
|
||||
QByteArray body = section.mid(headerSize);
|
||||
// Sanity check
|
||||
if (!index.isValid())
|
||||
return ERR_INVALID_PARAMETER;
|
||||
|
||||
// Get standard info
|
||||
QString name = sectionTypeToQString(sectionHeader->Type) + tr(" section");
|
||||
QString info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
|
||||
.hexarg2(sectionHeader->Type, 2)
|
||||
.hexarg(section.size()).arg(section.size())
|
||||
.hexarg(header.size()).arg(header.size())
|
||||
.hexarg(body.size()).arg(body.size());
|
||||
// Get section body
|
||||
QByteArray body = model->body(index);
|
||||
|
||||
// Get TE info
|
||||
bool msgInvalidSignature = false;
|
||||
QByteArray info;
|
||||
const EFI_IMAGE_TE_HEADER* teHeader = (const EFI_IMAGE_TE_HEADER*)body.constData();
|
||||
UINT32 teFixup = teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER);
|
||||
if (teHeader->Signature != EFI_IMAGE_TE_SIGNATURE) {
|
||||
info += tr("\nSignature: %1h, invalid").hexarg2(teHeader->Signature, 4);
|
||||
msgInvalidSignature = true;
|
||||
msg(tr("parseTeImageSectionBody: TE image with invalid TE signature"), index);
|
||||
}
|
||||
else {
|
||||
info += tr("\nSignature: %1h\nMachine type: %2\nNumber of sections: %3\nSubsystem: %4h\nStrippedSize: %5h (%6)\nBaseOfCode: %7h\nRelativeEntryPoint: %8h\nImageBase: %9h\nEntryPoint: %10h")
|
||||
info += tr("\nSignature: %1h\nMachine type: %2\nNumber of sections: %3\nSubsystem: %4h\nStripped size: %5h (%6)\nBase of code: %7h\nAddress of entry point: %8h\nImage base: %9h\nAdjusted image base: %10h")
|
||||
.hexarg2(teHeader->Signature, 4)
|
||||
.arg(machineTypeToQString(teHeader->Machine))
|
||||
.arg(teHeader->NumberOfSections)
|
||||
@ -2237,51 +2238,95 @@ STATUS FfsEngine::parseTeSectionHeader(const QByteArray & section, const UINT32
|
||||
.hexarg(teHeader->BaseOfCode)
|
||||
.hexarg(teHeader->AddressOfEntryPoint)
|
||||
.hexarg(teHeader->ImageBase)
|
||||
.hexarg(teHeader->ImageBase + teHeader->AddressOfEntryPoint - teFixup);
|
||||
}
|
||||
// Add tree item
|
||||
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, QByteArray(), parent, mode);
|
||||
|
||||
// Show messages
|
||||
if (msgInvalidSignature) {
|
||||
msg("parseSection: TE image with invalid TE signature", index);
|
||||
.hexarg(teHeader->ImageBase + teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER));
|
||||
}
|
||||
|
||||
// Special case of PEI Core
|
||||
QModelIndex core = model->findParentOfType(index, Types::File);
|
||||
if (core.isValid() && model->subtype(core) == EFI_FV_FILETYPE_PEI_CORE
|
||||
&& oldPeiCoreEntryPoint == 0) {
|
||||
result = getEntryPoint(model->body(index), oldPeiCoreEntryPoint);
|
||||
if (result)
|
||||
msg(tr("parseSection: can't get original PEI core entry point"), index);
|
||||
}
|
||||
// Get data from parsing data
|
||||
PARSING_DATA pdata = parsingDataFromQByteArray(index);
|
||||
pdata.section.teImage.imageBase = teHeader->ImageBase;
|
||||
pdata.section.teImage.adjustedImageBase = teHeader->ImageBase + teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER);
|
||||
|
||||
// Update parsing data
|
||||
model->setParsingData(index, parsingDataToQByteArray(pdata));
|
||||
|
||||
// Add TE info
|
||||
model->addInfo(index, info);
|
||||
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
STATUS FfsEngine::parseFirmwareVolumeImageSectionHeader(const QByteArray & section, const UINT32 parentOffset, const QModelIndex & parent, QModelIndex & index)
|
||||
|
||||
STATUS FfsParser::addMemoryAddressesInfo(const QModelIndex & index)
|
||||
{
|
||||
const EFI_COMMON_SECTION_HEADER* sectionHeader = (const EFI_COMMON_SECTION_HEADER*)(section.constData());
|
||||
QByteArray header = section.left(sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
|
||||
QByteArray body = section.mid(sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
|
||||
// Sanity check
|
||||
if (!index.isValid() || !lastVtf.isValid())
|
||||
return ERR_INVALID_PARAMETER;
|
||||
|
||||
// Get info
|
||||
QString name = sectionTypeToQString(sectionHeader->Type) + tr(" section");
|
||||
QString info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
|
||||
.hexarg2(sectionHeader->Type, 2)
|
||||
.hexarg(section.size()).arg(section.size())
|
||||
.hexarg(header.size()).arg(header.size())
|
||||
.hexarg(body.size()).arg(body.size());
|
||||
|
||||
// Add tree item
|
||||
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, QByteArray(), parent, mode);
|
||||
|
||||
// Parse section body as BIOS space
|
||||
result = parseBios(body, index);
|
||||
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME) {
|
||||
msg(tr("parseSection: parsing firmware volume image section as BIOS failed with error \"%1\"").arg(errorMessage(result)), index);
|
||||
return result;
|
||||
}
|
||||
// Get parsing data for the last VTF
|
||||
PARSING_DATA pdata = parsingDataFromQByteArray(lastVtf);
|
||||
if (!pdata.isOnFlash) {
|
||||
msg(tr("addPhysicalAddressInfo: the last VTF appears inside compressed item, the image may be damaged"), lastVtf);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
*/
|
||||
|
||||
// Calculate address difference
|
||||
const UINT32 vtfSize = model->header(lastVtf).size() + model->body(lastVtf).size() + (pdata.file.hasTail ? sizeof(UINT16) : 0);
|
||||
const UINT32 diff = 0xFFFFFFFF - pdata.offset - vtfSize + 1;
|
||||
|
||||
// Apply address information to index and all it's child items
|
||||
return addMemoryAddressesRecursive(index, diff);
|
||||
}
|
||||
|
||||
STATUS FfsParser::addMemoryAddressesRecursive(const QModelIndex & index, const UINT32 diff)
|
||||
{
|
||||
// Sanity check
|
||||
if (!index.isValid())
|
||||
return ERR_SUCCESS;
|
||||
|
||||
// Get parsing data for the current item
|
||||
PARSING_DATA pdata = parsingDataFromQByteArray(index);
|
||||
|
||||
// Set address value for non-compressed data
|
||||
if (pdata.isOnFlash) {
|
||||
// Check address sanity
|
||||
if ((const UINT64)diff + pdata.offset <= 0xFFFFFFFF) {
|
||||
// Update info
|
||||
pdata.address = diff + pdata.offset;
|
||||
UINT32 headerSize = model->header(index).size();
|
||||
if (headerSize) {
|
||||
model->addInfo(index, tr("\nHeader memory address: %1h").hexarg2(pdata.address, 8));
|
||||
model->addInfo(index, tr("\nData memory address: %1h").hexarg2(pdata.address + headerSize, 8));
|
||||
}
|
||||
else {
|
||||
model->addInfo(index, tr("\nMemory address: %1h").hexarg2(pdata.address, 8));
|
||||
}
|
||||
|
||||
// Special case of uncompressed TE image sections
|
||||
if (model->type(index) == Types::Section && model->subtype(index) == EFI_SECTION_TE && pdata.isOnFlash) {
|
||||
// Check data memory address to be equal to either ImageBase or AdjustedImageBase
|
||||
if (pdata.section.teImage.imageBase == pdata.address + headerSize) {
|
||||
pdata.section.teImage.revision = 1;
|
||||
model->addInfo(index, tr("\nTE image format revision: %1").arg(pdata.section.teImage.revision));
|
||||
}
|
||||
else if (pdata.section.teImage.adjustedImageBase == pdata.address + headerSize) {
|
||||
pdata.section.teImage.revision = 2;
|
||||
model->addInfo(index, tr("\nTE image format revision: %1").arg(pdata.section.teImage.revision));
|
||||
}
|
||||
else {
|
||||
msg(tr("addMemoryAddressesRecursive: image base is nether original nor adjusted, the image is either damaged or a part of backup PEI volume"), index);
|
||||
pdata.section.teImage.revision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Set modified parsing data
|
||||
model->setParsingData(index, parsingDataToQByteArray(pdata));
|
||||
}
|
||||
}
|
||||
|
||||
// Process child items
|
||||
for (int i = 0; i < model->rowCount(index); i++) {
|
||||
addMemoryAddressesRecursive(index.child(i, 0), diff);
|
||||
}
|
||||
|
||||
return ERR_SUCCESS;
|
||||
}
|
@ -53,13 +53,10 @@ public:
|
||||
STATUS parseSectionHeader(const QByteArray & section, const UINT32 parentOffset, const QModelIndex & parent, QModelIndex & index);
|
||||
STATUS parseSectionBody(const QModelIndex & index);
|
||||
|
||||
/*// Search routines TODO: move to another class
|
||||
// Extract routine TODO: move to another class
|
||||
STATUS extract(const QModelIndex & index, QString & name, QByteArray & extracted, const UINT8 mode);*/
|
||||
|
||||
private:
|
||||
TreeModel *model;
|
||||
QVector<QPair<QString, QModelIndex> > messagesVector;
|
||||
QModelIndex lastVtf;
|
||||
|
||||
STATUS parseIntelImage(const QByteArray & intelImage, const QModelIndex & parent, QModelIndex & root);
|
||||
STATUS parseGbeRegion(const QByteArray & gbe, const UINT32 parentOffset, const QModelIndex & parent, QModelIndex & index);
|
||||
@ -83,6 +80,8 @@ private:
|
||||
STATUS parseDepexSectionBody(const QModelIndex & index);
|
||||
STATUS parseUiSectionBody(const QModelIndex & index);
|
||||
STATUS parseRawSectionBody(const QModelIndex & index);
|
||||
STATUS parsePeImageSectionBody(const QModelIndex & index);
|
||||
STATUS parseTeImageSectionBody(const QModelIndex & index);
|
||||
|
||||
UINT8 getPaddingType(const QByteArray & padding);
|
||||
STATUS parseAprioriRawSection(const QByteArray & body, QString & parsed);
|
||||
@ -91,6 +90,9 @@ private:
|
||||
UINT32 getFileSize(const QByteArray & volume, const UINT32 fileOffset, const UINT8 ffsVersion);
|
||||
UINT32 getSectionSize(const QByteArray & file, const UINT32 sectionOffset, const UINT8 ffsVersion);
|
||||
|
||||
STATUS addMemoryAddressesInfo(const QModelIndex & index);
|
||||
STATUS addMemoryAddressesRecursive(const QModelIndex & index, const UINT32 diff);
|
||||
|
||||
// Internal operations
|
||||
BOOLEAN hasIntersection(const UINT32 begin1, const UINT32 end1, const UINT32 begin2, const UINT32 end2);
|
||||
|
||||
|
@ -61,11 +61,18 @@ typedef struct _FREEFORM_GUIDED_SECTION_PARSING_DATA {
|
||||
EFI_GUID guid;
|
||||
} FREEFORM_GUIDED_SECTION_PARSING_DATA;
|
||||
|
||||
typedef struct _TE_IMAGE_SECTION_PARSING_DATA {
|
||||
UINT32 imageBase;
|
||||
UINT32 adjustedImageBase;
|
||||
UINT8 revision;
|
||||
} TE_IMAGE_SECTION_PARSING_DATA;
|
||||
|
||||
typedef struct _SECTION_PARSING_DATA {
|
||||
union {
|
||||
COMPRESSED_SECTION_PARSING_DATA compressed;
|
||||
GUIDED_SECTION_PARSING_DATA guidDefined;
|
||||
FREEFORM_GUIDED_SECTION_PARSING_DATA freeformSubtypeGuid;
|
||||
TE_IMAGE_SECTION_PARSING_DATA teImage;
|
||||
};
|
||||
} SECTION_PARSING_DATA;
|
||||
|
||||
@ -75,7 +82,7 @@ typedef struct _PARSING_DATA {
|
||||
UINT8 emptyByte;
|
||||
UINT8 ffsVersion;
|
||||
UINT32 offset;
|
||||
UINT64 address;
|
||||
UINT32 address;
|
||||
union {
|
||||
//CAPSULE_PARSING_DATA capsule;
|
||||
//IMAGE_PARSING_DATA image;
|
||||
|
Loading…
Reference in New Issue
Block a user