From 6f9dc0ab885a34330ef361c61226994be44943c4 Mon Sep 17 00:00:00 2001 From: Nikolaj Schlej Date: Thu, 16 Feb 2023 22:11:39 -0800 Subject: [PATCH] Fix Unicode search --- UEFIFind/CMakeLists.txt | 4 - UEFIFind/uefifind.cpp | 74 +++++++++- UEFIFind/uefifind.h | 2 + UEFITool/CMakeLists.txt | 1 - UEFITool/ffsfinder.cpp | 303 ++++++++++++++++++++-------------------- UEFITool/uefitool.pro | 2 - common/ffsutils.cpp | 88 ------------ common/ffsutils.h | 30 ---- common/meson.build | 1 - 9 files changed, 222 insertions(+), 283 deletions(-) delete mode 100644 common/ffsutils.cpp delete mode 100644 common/ffsutils.h diff --git a/UEFIFind/CMakeLists.txt b/UEFIFind/CMakeLists.txt index c79b95a..b1b39e2 100644 --- a/UEFIFind/CMakeLists.txt +++ b/UEFIFind/CMakeLists.txt @@ -17,8 +17,6 @@ SET(PROJECT_SOURCES ../common/nvramparser.cpp ../common/ffsparser.cpp ../common/fitparser.cpp - ../common/ffsreport.cpp - ../common/ffsutils.cpp ../common/peimage.cpp ../common/treeitem.cpp ../common/treemodel.cpp @@ -59,8 +57,6 @@ SET(PROJECT_SOURCES ../common/zlib/zutil.c ) -ADD_DEFINITIONS(-DU_ENABLE_NVRAM_PARSING_SUPPORT -DU_ENABLE_FIT_PARSING_SUPPORT -DU_ENABLE_GUID_DATABASE_SUPPORT) - ADD_EXECUTABLE(UEFIFind ${PROJECT_SOURCES}) IF(UNIX) diff --git a/UEFIFind/uefifind.cpp b/UEFIFind/uefifind.cpp index 29e96ca..4e7412a 100644 --- a/UEFIFind/uefifind.cpp +++ b/UEFIFind/uefifind.cpp @@ -12,9 +12,9 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. */ #include "uefifind.h" -#include "../common/ffsutils.h" - #include +#include + UEFIFind::UEFIFind() { @@ -46,6 +46,74 @@ USTATUS UEFIFind::init(const UString & path) return U_SUCCESS; } +USTATUS UEFIFind::findFileRecursive(const UModelIndex index, const UString & hexPattern, const UINT8 mode, std::set > & files) +{ + if (!index.isValid()) + return U_SUCCESS; + + if (hexPattern.isEmpty()) + return U_INVALID_PARAMETER; + + const char *hexPatternRaw = hexPattern.toLocal8Bit(); + std::vector pattern, patternMask; + if (!makePattern(hexPatternRaw, pattern, patternMask)) + return U_INVALID_PARAMETER; + + // Check for "all substrings" pattern + size_t count = 0; + for (size_t i = 0; i < patternMask.size(); i++) + if (patternMask[i] == 0) + count++; + if (count == patternMask.size()) + return U_SUCCESS; + + bool hasChildren = (model->rowCount(index) > 0); + for (int i = 0; i < model->rowCount(index); i++) { + findFileRecursive(index.model()->index(i, index.column(), index), hexPattern, mode, files); + } + + // TODO: handle a case where an item has both compressed and uncompressed bodies + UByteArray data; + if (hasChildren) { + if (mode == SEARCH_MODE_HEADER) + data = model->header(index); + else if (mode == SEARCH_MODE_ALL) + data = model->header(index) + model->body(index); + } + else { + if (mode == SEARCH_MODE_HEADER) + data = model->header(index); + else if (mode == SEARCH_MODE_BODY) + data = model->body(index); + else + data = model->header(index) + model->body(index); + } + + const UINT8 *rawData = reinterpret_cast(data.constData()); + INTN offset = findPattern(pattern.data(), patternMask.data(), pattern.size(), rawData, data.size(), 0); + + // For patterns that cross header|body boundary, skip patterns entirely located in body, since + // children search above has already found them. + if (hasChildren && mode == SEARCH_MODE_ALL && offset >= model->header(index).size()) { + offset = -1; + } + + if (offset >= 0) { + if (model->type(index) != Types::File) { + UModelIndex parentFile = model->findParentOfType(index, Types::File); + if (model->type(index) == Types::Section && model->subtype(index) == EFI_SECTION_FREEFORM_SUBTYPE_GUID) + files.insert(std::pair(parentFile, index)); + else + files.insert(std::pair(parentFile, UModelIndex())); + } + else { + files.insert(std::pair(index, UModelIndex())); + } + } + + return U_SUCCESS; +} + USTATUS UEFIFind::find(const UINT8 mode, const bool count, const UString & hexPattern, UString & result) { UModelIndex root = model->index(0, 0); @@ -53,7 +121,7 @@ USTATUS UEFIFind::find(const UINT8 mode, const bool count, const UString & hexPa result.clear(); - USTATUS returned = FfsUtils::findFileRecursive(model, root, hexPattern, mode, files); + USTATUS returned = findFileRecursive(root, hexPattern, mode, files); if (returned) return returned; diff --git a/UEFIFind/uefifind.h b/UEFIFind/uefifind.h index ff6b8f6..6b572bb 100644 --- a/UEFIFind/uefifind.h +++ b/UEFIFind/uefifind.h @@ -34,6 +34,8 @@ public: USTATUS find(const UINT8 mode, const bool count, const UString & hexPattern, UString & result); private: + USTATUS findFileRecursive(const UModelIndex index, const UString & hexPattern, const UINT8 mode, std::set > & files); + FfsParser* ffsParser; TreeModel* model; bool initDone; diff --git a/UEFITool/CMakeLists.txt b/UEFITool/CMakeLists.txt index 1b08428..9c2cfd0 100644 --- a/UEFITool/CMakeLists.txt +++ b/UEFITool/CMakeLists.txt @@ -52,7 +52,6 @@ SET(PROJECT_SOURCES ../common/ffsbuilder.cpp ../common/ffsparser.cpp ../common/ffsreport.cpp - ../common/ffsutils.cpp ../common/treeitem.cpp ../common/treemodel.cpp ../common/LZMA/LzmaCompress.c diff --git a/UEFITool/ffsfinder.cpp b/UEFITool/ffsfinder.cpp index 588e8d5..8cab34d 100644 --- a/UEFITool/ffsfinder.cpp +++ b/UEFITool/ffsfinder.cpp @@ -1,5 +1,5 @@ /* ffsfinder.cpp - + Copyright (c) 2015, Nikolaj Schlej. All rights reserved. This program and the accompanying materials are licensed and made available under the terms and conditions of the BSD License @@ -21,8 +21,6 @@ USTATUS FfsFinder::findHexPattern(const UModelIndex & index, const UByteArray & hexPattern, const UINT8 mode) { - // TODO: use FfsUtils. - if (!index.isValid()) return U_SUCCESS; @@ -57,115 +55,20 @@ USTATUS FfsFinder::findHexPattern(const UModelIndex & index, const UByteArray & UString hexBody = UString(data.toHex()); #if QT_VERSION_MAJOR >= 6 QRegularExpression regexp = QRegularExpression(UString(hexPattern)); - regexp.setPatternOptions((QRegularExpression::PatternOptions)0x1); + regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatch regexpmatch; - INT32 offset = 0; - while ((offset = (INT32)hexBody.indexOf(regexp, (qsizetype)offset, ®expmatch)) != -1) { + INT32 offset = (INT32)hexBody.indexOf(regexp, 0, ®expmatch); #else QRegExp regexp = QRegExp(UString(hexPattern), Qt::CaseInsensitive); - INT32 offset = regexp.indexIn(hexBody); + INT32 offset = regexp.indexIn(hexBody); +#endif while (offset >= 0) { -#endif - - if (offset % 2 == 0) { - // For patterns that cross header|body boundary, skip patterns entirely located in body, since - // children search above has already found them. - if (!(hasChildren && mode == SEARCH_MODE_ALL && offset/2 >= model->header(index).size())) { - UModelIndex parentFileIndex = model->findParentOfType(index, Types::File); - UString name = model->name(index); - if (model->parent(index) == parentFileIndex) { - name = model->name(parentFileIndex) + UString("/") + name; - } - else if (parentFileIndex.isValid()) { - name = model->name(parentFileIndex) + UString("/.../") + name; - } - - msg(UString("Hex pattern \"") + UString(hexPattern) - + UString("\" found as \"") + hexBody.mid(offset, hexPattern.length()).toUpper() - + UString("\" in ") + name - + usprintf(" at %s-offset %02Xh", mode == SEARCH_MODE_BODY ? "body" : "header", offset / 2), - index); - } - } - -#if QT_VERSION_MAJOR >= 6 - offset += 1; -#else - offset = regexp.indexIn(hexBody, offset + 1); -#endif - } - - return U_SUCCESS; - } - - USTATUS FfsFinder::findGuidPattern(const UModelIndex & index, const UByteArray & guidPattern, const UINT8 mode) - { - if (guidPattern.isEmpty()) - return U_INVALID_PARAMETER; - - if (!index.isValid()) - return U_SUCCESS; - - bool hasChildren = (model->rowCount(index) > 0); - for (int i = 0; i < model->rowCount(index); i++) { - findGuidPattern(index.model()->index(i, index.column(), index), guidPattern, mode); - } - - UByteArray data; - if (hasChildren) { - if (mode != SEARCH_MODE_BODY) - data = model->header(index); - } - else { - if (mode == SEARCH_MODE_HEADER) - data.append(model->header(index)); - else if (mode == SEARCH_MODE_BODY) - data.append(model->body(index)); - else - data.append(model->header(index)).append(model->body(index)); - } - - UString hexBody = UString(data.toHex()); - QList list = guidPattern.split('-'); - if (list.count() != 5) - return U_INVALID_PARAMETER; - - UByteArray hexPattern; - // Reverse first GUID block - hexPattern.append(list.at(0).mid(6, 2)); - hexPattern.append(list.at(0).mid(4, 2)); - hexPattern.append(list.at(0).mid(2, 2)); - hexPattern.append(list.at(0).mid(0, 2)); - // Reverse second GUID block - hexPattern.append(list.at(1).mid(2, 2)); - hexPattern.append(list.at(1).mid(0, 2)); - // Reverse third GUID block - hexPattern.append(list.at(2).mid(2, 2)); - hexPattern.append(list.at(2).mid(0, 2)); - // Append fourth and fifth GUID blocks as is - hexPattern.append(list.at(3)).append(list.at(4)); - - // Check for "all substrings" pattern - if (hexPattern.count('.') == hexPattern.length()) - return U_SUCCESS; - -#if QT_VERSION_MAJOR >= 6 - QRegularExpression regexp((QString)UString(hexPattern)); - regexp.setPatternOptions((QRegularExpression::PatternOptions)0x1); - QRegularExpressionMatch regexpmatch; - - INT32 offset = 0; - offset = (INT32)hexBody.indexOf(regexp, (qsizetype)offset, ®expmatch); -#else - QRegExp regexp(UString(hexPattern), Qt::CaseInsensitive); - - INT32 offset = regexp.indexIn(hexBody); -#endif - - while (offset >= 0) { - if (offset % 2 == 0) { + if (offset % 2 == 0) { + // For patterns that cross header|body boundary, skip patterns entirely located in body, since + // children search above has already found them. + if (!(hasChildren && mode == SEARCH_MODE_ALL && offset/2 >= model->header(index).size())) { UModelIndex parentFileIndex = model->findParentOfType(index, Types::File); UString name = model->name(index); if (model->parent(index) == parentFileIndex) { @@ -175,59 +78,88 @@ USTATUS FfsFinder::findHexPattern(const UModelIndex & index, const UByteArray & name = model->name(parentFileIndex) + UString("/.../") + name; } - msg(UString("GUID pattern \"") + UString(guidPattern) + msg(UString("Hex pattern \"") + UString(hexPattern) + UString("\" found as \"") + hexBody.mid(offset, hexPattern.length()).toUpper() + UString("\" in ") + name + usprintf(" at %s-offset %02Xh", mode == SEARCH_MODE_BODY ? "body" : "header", offset / 2), index); } - + } + #if QT_VERSION_MAJOR >= 6 - offset = (INT32)hexBody.indexOf(regexp, (qsizetype)offset + 1, ®expmatch); + offset = (INT32)hexBody.indexOf(regexp, (qsizetype)offset + 1, ®expmatch); #else - offset = regexp.indexIn(hexBody, offset + 1); + offset = regexp.indexIn(hexBody, offset + 1); #endif - } - - return U_SUCCESS; } - - USTATUS FfsFinder::findTextPattern(const UModelIndex & index, const UString & pattern, const UINT8 mode, const bool unicode, const Qt::CaseSensitivity caseSensitive) - { - if (pattern.isEmpty()) - return U_INVALID_PARAMETER; - - if (!index.isValid()) - return U_SUCCESS; - - bool hasChildren = (model->rowCount(index) > 0); - for (int i = 0; i < model->rowCount(index); i++) { - findTextPattern(index.model()->index(i, index.column(), index), pattern, mode, unicode, caseSensitive); - } - - UByteArray body; - if (hasChildren) { - if (mode != SEARCH_MODE_BODY) - body = model->header(index); - } - else { - if (mode == SEARCH_MODE_HEADER) - body.append(model->header(index)); - else if (mode == SEARCH_MODE_BODY) - body.append(model->body(index)); - else - body.append(model->header(index)).append(model->body(index)); - } - - UString data; - if (unicode) - data = uFromUcs2(body.constData(), body.length() / 2); + + return U_SUCCESS; +} + +USTATUS FfsFinder::findGuidPattern(const UModelIndex & index, const UByteArray & guidPattern, const UINT8 mode) +{ + if (guidPattern.isEmpty()) + return U_INVALID_PARAMETER; + + if (!index.isValid()) + return U_SUCCESS; + + bool hasChildren = (model->rowCount(index) > 0); + for (int i = 0; i < model->rowCount(index); i++) { + findGuidPattern(index.model()->index(i, index.column(), index), guidPattern, mode); + } + + UByteArray data; + if (hasChildren) { + if (mode != SEARCH_MODE_BODY) + data = model->header(index); + } + else { + if (mode == SEARCH_MODE_HEADER) + data.append(model->header(index)); + else if (mode == SEARCH_MODE_BODY) + data.append(model->body(index)); else - data = UString::fromLatin1((const char*)body.constData(), body.length()); - - int offset = -1; - while ((offset = (int)data.indexOf(pattern, (int)(offset + 1), caseSensitive)) >= 0) { - + data.append(model->header(index)).append(model->body(index)); + } + + UString hexBody = UString(data.toHex()); + QList list = guidPattern.split('-'); + if (list.count() != 5) + return U_INVALID_PARAMETER; + + UByteArray hexPattern; + // Reverse first GUID block + hexPattern.append(list.at(0).mid(6, 2)); + hexPattern.append(list.at(0).mid(4, 2)); + hexPattern.append(list.at(0).mid(2, 2)); + hexPattern.append(list.at(0).mid(0, 2)); + // Reverse second GUID block + hexPattern.append(list.at(1).mid(2, 2)); + hexPattern.append(list.at(1).mid(0, 2)); + // Reverse third GUID block + hexPattern.append(list.at(2).mid(2, 2)); + hexPattern.append(list.at(2).mid(0, 2)); + // Append fourth and fifth GUID blocks as is + hexPattern.append(list.at(3)).append(list.at(4)); + + // Check for "all substrings" pattern + if (hexPattern.count('.') == hexPattern.length()) + return U_SUCCESS; + +#if QT_VERSION_MAJOR >= 6 + QRegularExpression regexp((QString)UString(hexPattern)); + regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch regexpmatch; + + INT32 offset = (INT32)hexBody.indexOf(regexp, 0, ®expmatch); +#else + QRegExp regexp(UString(hexPattern), Qt::CaseInsensitive); + + INT32 offset = regexp.indexIn(hexBody); +#endif + while (offset >= 0) { + if (offset % 2 == 0) { UModelIndex parentFileIndex = model->findParentOfType(index, Types::File); UString name = model->name(index); if (model->parent(index) == parentFileIndex) { @@ -237,11 +169,74 @@ USTATUS FfsFinder::findHexPattern(const UModelIndex & index, const UByteArray & name = model->name(parentFileIndex) + UString("/.../") + name; } - msg((unicode ? UString("Unicode") : UString("ASCII")) + UString(" text \"") + UString(pattern) - + UString("\" found in ") + name - + usprintf(" at %s-offset %02Xh", mode == SEARCH_MODE_BODY ? "body" : "header", (unicode ? offset * 2 : offset)), + msg(UString("GUID pattern \"") + UString(guidPattern) + + UString("\" found as \"") + hexBody.mid(offset, hexPattern.length()).toUpper() + + UString("\" in ") + name + + usprintf(" at %s-offset %02Xh", mode == SEARCH_MODE_BODY ? "body" : "header", offset / 2), index); } - - return U_SUCCESS; + +#if QT_VERSION_MAJOR >= 6 + offset = (INT32)hexBody.indexOf(regexp, (qsizetype)offset + 1, ®expmatch); +#else + offset = regexp.indexIn(hexBody, offset + 1); +#endif } + + return U_SUCCESS; +} + +USTATUS FfsFinder::findTextPattern(const UModelIndex & index, const UString & pattern, const UINT8 mode, const bool unicode, const Qt::CaseSensitivity caseSensitive) +{ + if (pattern.isEmpty()) + return U_INVALID_PARAMETER; + + if (!index.isValid()) + return U_SUCCESS; + + bool hasChildren = (model->rowCount(index) > 0); + for (int i = 0; i < model->rowCount(index); i++) { + findTextPattern(index.model()->index(i, index.column(), index), pattern, mode, unicode, caseSensitive); + } + + UByteArray body; + if (hasChildren) { + if (mode != SEARCH_MODE_BODY) + body = model->header(index); + } + else { + if (mode == SEARCH_MODE_HEADER) + body.append(model->header(index)); + else if (mode == SEARCH_MODE_BODY) + body.append(model->body(index)); + else + body.append(model->header(index)).append(model->body(index)); + } + + UString data = UString::fromLatin1((const char*)body.constData(), body.length()); + + UString searchPattern; + if (unicode) + searchPattern = UString::fromLatin1((const char*)pattern.utf16(), pattern.length() * 2); + else + searchPattern = pattern; + + int offset = -1; + while ((offset = (int)data.indexOf(searchPattern, (int)(offset + 1), caseSensitive)) >= 0) { + UModelIndex parentFileIndex = model->findParentOfType(index, Types::File); + UString name = model->name(index); + if (model->parent(index) == parentFileIndex) { + name = model->name(parentFileIndex) + UString("/") + name; + } + else if (parentFileIndex.isValid()) { + name = model->name(parentFileIndex) + UString("/.../") + name; + } + + msg((unicode ? UString("Unicode") : UString("ASCII")) + UString(" text \"") + UString(pattern) + + UString("\" found in ") + name + + usprintf(" at %s-offset %02Xh", mode == SEARCH_MODE_BODY ? "body" : "header", offset), + index); + } + + return U_SUCCESS; +} diff --git a/UEFITool/uefitool.pro b/UEFITool/uefitool.pro index c8e9fdb..2dc54a4 100644 --- a/UEFITool/uefitool.pro +++ b/UEFITool/uefitool.pro @@ -37,7 +37,6 @@ HEADERS += uefitool.h \ ../common/ffsparser.h \ ../common/ffsreport.h \ ../common/treeitem.h \ - ../common/ffsutils.h \ ../common/intel_fit.h \ ../common/intel_microcode.h \ ../common/treemodel.h \ @@ -86,7 +85,6 @@ SOURCES += uefitool_main.cpp \ ../common/ffsbuilder.cpp \ ../common/ffsparser.cpp \ ../common/ffsreport.cpp \ - ../common/ffsutils.cpp \ ../common/treeitem.cpp \ ../common/treemodel.cpp \ ../common/LZMA/LzmaCompress.c \ diff --git a/common/ffsutils.cpp b/common/ffsutils.cpp deleted file mode 100644 index 698dd2b..0000000 --- a/common/ffsutils.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* ffsutils.cpp - - Copyright (c) 2019, LongSoft. All rights reserved. - This program and the accompanying materials - are licensed and made available under the terms and conditions of the BSD License - which accompanies this distribution. The full text of the license may be found at - http://opensource.org/licenses/bsd-license.php - - THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, - WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. - - */ - -#include "ffsutils.h" -#include "utility.h" -#include "ffs.h" - -namespace FfsUtils { - -USTATUS findFileRecursive(TreeModel *model, const UModelIndex index, const UString & hexPattern, const UINT8 mode, std::set > & files) -{ - if (!index.isValid()) - return U_SUCCESS; - - if (hexPattern.isEmpty()) - return U_INVALID_PARAMETER; - - const char *hexPatternRaw = hexPattern.toLocal8Bit(); - std::vector pattern, patternMask; - if (!makePattern(hexPatternRaw, pattern, patternMask)) - return U_INVALID_PARAMETER; - - // Check for "all substrings" pattern - size_t count = 0; - for (size_t i = 0; i < patternMask.size(); i++) - if (patternMask[i] == 0) - count++; - if (count == patternMask.size()) - return U_SUCCESS; - - bool hasChildren = (model->rowCount(index) > 0); - for (int i = 0; i < model->rowCount(index); i++) { - findFileRecursive(model, index.model()->index(i, index.column(), index), hexPattern, mode, files); - } - - UByteArray data; - if (hasChildren) { - if (mode == SEARCH_MODE_HEADER) - data = model->header(index); - else if (mode == SEARCH_MODE_ALL) - data = model->header(index) + model->body(index); - } - else { - if (mode == SEARCH_MODE_HEADER) - data = model->header(index); - else if (mode == SEARCH_MODE_BODY) - data = model->body(index); - else - data = model->header(index) + model->body(index); - } - - const UINT8 *rawData = reinterpret_cast(data.constData()); - INTN offset = findPattern(pattern.data(), patternMask.data(), pattern.size(), rawData, data.size(), 0); - - // For patterns that cross header|body boundary, skip patterns entirely located in body, since - // children search above has already found them. - if (hasChildren && mode == SEARCH_MODE_ALL && offset >= model->header(index).size()) { - offset = -1; - } - - if (offset >= 0) { - if (model->type(index) != Types::File) { - UModelIndex ffs = model->findParentOfType(index, Types::File); - if (model->type(index) == Types::Section && model->subtype(index) == EFI_SECTION_FREEFORM_SUBTYPE_GUID) - files.insert(std::pair(ffs, index)); - else - files.insert(std::pair(ffs, UModelIndex())); - } - else { - files.insert(std::pair(index, UModelIndex())); - } - - } - - return U_SUCCESS; -} - -}; diff --git a/common/ffsutils.h b/common/ffsutils.h deleted file mode 100644 index 7807c89..0000000 --- a/common/ffsutils.h +++ /dev/null @@ -1,30 +0,0 @@ -/* fssreport.h - -Copyright (c) 2019, LongSoft. All rights reserved. -This program and the accompanying materials -are licensed and made available under the terms and conditions of the BSD License -which accompanies this distribution. The full text of the license may be found at -http://opensource.org/licenses/bsd-license.php - -THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, -WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. - -*/ - -#ifndef FFSUTILS_H -#define FFSUTILS_H - -#include - -#include "basetypes.h" -#include "ubytearray.h" -#include "ustring.h" -#include "treemodel.h" - -namespace FfsUtils { - -USTATUS findFileRecursive(TreeModel *model, const UModelIndex index, const UString & hexPattern, const UINT8 mode, std::set > & files); - -}; - -#endif // FFSUTILS_H diff --git a/common/meson.build b/common/meson.build index 0521c50..3d6c6cd 100644 --- a/common/meson.build +++ b/common/meson.build @@ -26,7 +26,6 @@ uefitoolcommon = static_library('uefitoolcommon', 'fitparser.cpp', 'ffsparser.cpp', 'ffsreport.cpp', - 'ffsutils.cpp', 'peimage.cpp', 'treeitem.cpp', 'treemodel.cpp',