diff --git a/AppCUI b/AppCUI index 13fdd6e9..fb683857 160000 --- a/AppCUI +++ b/AppCUI @@ -1 +1 @@ -Subproject commit 13fdd6e9f422e9f1f3e679c051d85d0ca646971b +Subproject commit fb6838578a749adaf10e7a8ad65d6c8cca308692 diff --git a/CMakeLists.txt b/CMakeLists.txt index 98f40a24..679fbf80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,7 +198,8 @@ if(NOT DEFINED CMAKE_TESTING_ENABLED) add_subdirectory(Types/SQLite) add_subdirectory(Types/JCLASS) add_subdirectory(Types/EML) - add_subdirectory(Types/JPG) + add_subdirectory(Types/DOC) + add_subdirectory(Types/JPG) # Generic plugins supported by GView add_subdirectory(GenericPlugins/CharacterTable) diff --git a/GViewCore/include/GView.hpp b/GViewCore/include/GView.hpp index e850cc33..c0e291c9 100644 --- a/GViewCore/include/GView.hpp +++ b/GViewCore/include/GView.hpp @@ -1068,15 +1068,15 @@ namespace View return u16string_view{ text + start, (size_t) (end - start) }; return u16string_view(); } - uint32 ParseUntillEndOfLine(uint32 index) const; - uint32 ParseUntillStartOfNextLine(uint32 index) const; + uint32 ParseUntilEndOfLine(uint32 index) const; + uint32 ParseUntilStartOfNextLine(uint32 index) const; uint32 Parse(uint32 index, bool (*validate)(char16 character)) const; uint32 ParseBackwards(uint32 index, bool (*validate)(char16 character)) const; uint32 ParseSameGroupID(uint32 index, uint32 (*charToID)(char16 character)) const; uint32 ParseSpace(uint32 index, SpaceType type = SpaceType::SpaceAndTabs) const; uint32 ParseString(uint32 index, StringFormat format = StringFormat::All) const; uint32 ParseNumber(uint32 index, NumberFormat format = NumberFormat::All) const; - uint32 ParseUntillText(uint32 index, string_view textToFind, bool ignoreCase) const; + uint32 ParseUntilText(uint32 index, string_view textToFind, bool ignoreCase) const; uint32 ParseUntilNextCharacterAfterText(uint32 index, string_view textToFind, bool ignoreCase) const; uint64 ComputeHash64(uint32 start, uint32 end, bool ignoreCase) const; uint32 ComputeHash32(uint32 start, uint32 end, bool ignoreCase) const; @@ -1493,6 +1493,7 @@ namespace Unpack namespace Base64 { CORE_EXPORT void Encode(BufferView view, Buffer& output); + CORE_EXPORT bool Decode(BufferView view, Buffer& output, bool& hasWarning, String& warningMessage); CORE_EXPORT bool Decode(BufferView view, Buffer& output); } // namespace Base64 namespace QuotedPrintable diff --git a/GViewCore/src/Unpack/Base64.cpp b/GViewCore/src/Unpack/Base64.cpp index 949c104d..6c9a083c 100644 --- a/GViewCore/src/Unpack/Base64.cpp +++ b/GViewCore/src/Unpack/Base64.cpp @@ -44,14 +44,16 @@ void Encode(BufferView view, Buffer& output) output.AddMultipleTimes(string_view("=", 1), (3 - sequenceIndex) % 3); } -bool Decode(BufferView view, Buffer& output) +bool Decode(BufferView view, Buffer& output, bool& hasWarning, String& warningMessage) { uint32 sequence = 0; uint32 sequenceIndex = 0; char lastEncoded = 0; + uint8 paddingCount = 0; + hasWarning = false; + output.Reserve((view.GetLength() / 4) * 3); - for (uint32 i = 0; i < view.GetLength(); ++i) - { + for (uint32 i = 0; i < view.GetLength(); ++i) { char encoded = view[i]; CHECK(encoded < sizeof(BASE64_DECODE_TABLE) / sizeof(*BASE64_DECODE_TABLE), false, ""); @@ -60,7 +62,8 @@ bool Decode(BufferView view, Buffer& output) } if (lastEncoded == '=' && sequenceIndex == 0) { - AppCUI::Dialogs::MessageBox::ShowError("Warning!", "Ignoring extra bytes after the end of buffer"); + hasWarning = true; + warningMessage = "Ignoring extra bytes after the end of buffer"; break; } @@ -69,6 +72,7 @@ bool Decode(BufferView view, Buffer& output) if (encoded == '=') { // padding decoded = 0; + paddingCount++; } else { decoded = BASE64_DECODE_TABLE[encoded]; CHECK(decoded != -1, false, ""); @@ -79,6 +83,7 @@ bool Decode(BufferView view, Buffer& output) if (sequenceIndex % 4 == 0) { char* buffer = (char*) &sequence; + output.Add(string_view(buffer + 3, 1)); output.Add(string_view(buffer + 2, 1)); output.Add(string_view(buffer + 1, 1)); @@ -90,6 +95,19 @@ bool Decode(BufferView view, Buffer& output) lastEncoded = encoded; } + // trim the trailing bytes + CHECK(paddingCount < 3, false, ""); + output.Resize(output.GetLength() - paddingCount); + return true; } + +bool Decode(BufferView view, Buffer& output) +{ + bool tempHasWarning; + String tempWarningMessage; + + return Decode(view, output, tempHasWarning, tempWarningMessage); } + +} // namespace GView::Unpack::Base64 diff --git a/GViewCore/src/View/LexicalViewer/TextParser.cpp b/GViewCore/src/View/LexicalViewer/TextParser.cpp index 2193b254..c3749918 100644 --- a/GViewCore/src/View/LexicalViewer/TextParser.cpp +++ b/GViewCore/src/View/LexicalViewer/TextParser.cpp @@ -102,7 +102,7 @@ TextParser::TextParser(u16string_view _text) if (this->text == nullptr) this->size = 0; // sanity check } -uint32 TextParser::ParseUntillEndOfLine(uint32 index) const +uint32 TextParser::ParseUntilEndOfLine(uint32 index) const { if (index >= size) return size; @@ -114,7 +114,7 @@ uint32 TextParser::ParseUntillEndOfLine(uint32 index) const } return index; } -uint32 TextParser::ParseUntillStartOfNextLine(uint32 index) const +uint32 TextParser::ParseUntilStartOfNextLine(uint32 index) const { if (index >= size) return size; @@ -225,7 +225,7 @@ uint32 TextParser::ParseSpace(uint32 index, SpaceType type) const } return index; } -uint32 TextParser::ParseUntillText(uint32 index, string_view textToFind, bool ignoreCase) const +uint32 TextParser::ParseUntilText(uint32 index, string_view textToFind, bool ignoreCase) const { if (index >= size) return size; @@ -281,7 +281,7 @@ uint32 TextParser::ParseUntillText(uint32 index, string_view textToFind, bool ig } uint32 TextParser::ParseUntilNextCharacterAfterText(uint32 index, string_view textToFind, bool ignoreCase) const { - auto pos = ParseUntillText(index, textToFind, ignoreCase); + auto pos = ParseUntilText(index, textToFind, ignoreCase); if (pos >= size) return size; return pos + (uint32) textToFind.size(); diff --git a/GViewCore/src/ZIP/zip.cpp b/GViewCore/src/ZIP/zip.cpp index e04fdae5..1acde3f7 100644 --- a/GViewCore/src/ZIP/zip.cpp +++ b/GViewCore/src/ZIP/zip.cpp @@ -379,7 +379,7 @@ bool GetInfo(std::u16string_view path, Info& info) size_t entryIndex = internalInfo->entries.size(); auto& entry = internalInfo->entries.emplace_back(); - + ConvertZipFileInfoToEntry(zipFile, entry); std::u8string_view filename = entry.filename; @@ -388,7 +388,7 @@ bool GetInfo(std::u16string_view path, Info& info) } size_t offset = 0; - + while (true) { size_t pos = filename.find_first_of('/', offset); @@ -399,7 +399,7 @@ bool GetInfo(std::u16string_view path, Info& info) auto parentFilename = entry.filename.substr(0, pos + 1); auto it = std::find_if( - internalInfo->entries.begin(), internalInfo->entries.end(), [&](const _Entry& e) -> bool { return e.filename == parentFilename; }); + internalInfo->entries.begin(), internalInfo->entries.end(), [&](const _Entry& e) -> bool { return e.filename == parentFilename; }); if (it == internalInfo->entries.end()) { auto& parentEntry = internalInfo->entries.emplace_back(); parentEntry.filename = parentFilename; @@ -449,9 +449,41 @@ bool GetInfo(Utils::DataCache& cache, Info& info) CHECKBK(mz_zip_reader_entry_get_info(internalInfo->reader.value, &zipFile) == MZ_OK, ""); mz_zip_reader_set_pattern(internalInfo->reader.value, nullptr, 1); // do we need a pattern? + size_t entryIndex = internalInfo->entries.size(); auto& entry = internalInfo->entries.emplace_back(); + ConvertZipFileInfoToEntry(zipFile, entry); + std::u8string_view filename = entry.filename; + if (entry.type == EntryType::Directory && filename[filename.size() - 1] == '/') { + filename = { filename.data(), filename.size() - 1 }; + } + + size_t offset = 0; + + while (true) { + size_t pos = filename.find_first_of('/', offset); + + CHECKBK(pos != std::string::npos, ""); + + // add the parent as well if not already present + auto& entry = internalInfo->entries[entryIndex]; + auto parentFilename = entry.filename.substr(0, pos + 1); + + auto it = std::find_if( + internalInfo->entries.begin(), internalInfo->entries.end(), [&](const _Entry& e) -> bool { return e.filename == parentFilename; }); + if (it == internalInfo->entries.end()) { + auto& parentEntry = internalInfo->entries.emplace_back(); + parentEntry.filename = parentFilename; + parentEntry.filename_size = parentFilename.size(); + parentEntry.type = EntryType::Directory; + parentEntry.version_madeby = entry.version_madeby; + parentEntry.version_needed = entry.version_needed; + } + + offset = pos + 1; + } + CHECKBK(mz_zip_reader_goto_next_entry(internalInfo->reader.value) == MZ_OK, ""); } while (true); diff --git a/GenericPlugins/Unpacker/src/Unpacker.cpp b/GenericPlugins/Unpacker/src/Unpacker.cpp index 32cb06c7..ccbc85f5 100644 --- a/GenericPlugins/Unpacker/src/Unpacker.cpp +++ b/GenericPlugins/Unpacker/src/Unpacker.cpp @@ -18,17 +18,8 @@ namespace GView::GenericPlugins::Unpackers using namespace AppCUI::Graphics; using namespace GView::View; -constexpr char BASE64_ENCODE_TABLE[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', - 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; -constexpr char BASE64_DECODE_TABLE[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, - 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; - -Plugin::Plugin() : Window("Unpacker", "d:c,w:140,h:40", WindowFlags::FixedPosition) +Plugin::Plugin() : Window("Unpackers", "d:c,w:140,h:40", WindowFlags::FixedPosition) { sync = Factory::CheckBox::Create(this, "&Unpackers", "x:2%,y:1,w:30"); sync->SetChecked(false); @@ -70,7 +61,7 @@ void Plugin::Update() } list->DeleteAllItems(); - auto item = list->AddItem({ "Cosmin", "ViewName", "CevaFormat", "Base64" }); + auto item = list->AddItem({ "Ceva", "ViewName", "CevaFormat", "Base64" }); // auto desktop = AppCUI::Application::GetDesktop(); // const auto windowsNo = desktop->GetChildrenCount(); @@ -119,89 +110,6 @@ void Plugin::Update() //} } -void Plugin::Base64Encode(BufferView view, Buffer& output) -{ - uint32 sequence = 0; - uint32 sequenceIndex = 0; - - // TODO: same as before, pass something that doesn't need extra preprocessing - for (uint32 i = 0; i < view.GetLength(); i += 2) { - char decoded = view[i]; - - sequence |= decoded << ((3 - sequenceIndex) * 8); - sequenceIndex++; - - if (sequenceIndex % 3 == 0) { - // get 4 encoded components out of this one - // 0x3f -> 0b00111111 - - char buffer[] = { - BASE64_ENCODE_TABLE[(sequence >> 26) & 0x3f], - BASE64_ENCODE_TABLE[(sequence >> 20) & 0x3f], - BASE64_ENCODE_TABLE[(sequence >> 14) & 0x3f], - BASE64_ENCODE_TABLE[(sequence >> 8) & 0x3f], - }; - - output.Add(string_view(buffer, 4)); - - sequence = 0; - sequenceIndex = 0; - } - } - - output.AddMultipleTimes(string_view("=", 1), (3 - sequenceIndex) % 3); -} - -bool Plugin::Base64Decode(BufferView view, Buffer& output) -{ - uint32 sequence = 0; - uint32 sequenceIndex = 0; - char lastEncoded = 0; - - // TODO: pass something else as a parameter, not needing extra pasing in the function - for (uint32 i = 0; i < view.GetLength(); i += 2) // skip the second byte in the character - { - char encoded = view[i]; - CHECK(encoded < sizeof(BASE64_DECODE_TABLE) / sizeof(*BASE64_DECODE_TABLE), false, ""); - - if (encoded == '\r' || encoded == '\n') { - continue; - } - - if (lastEncoded == '=' && sequenceIndex == 0) { - AppCUI::Dialogs::MessageBox::ShowError("Warning!", "Ignoring extra bytes after the end of buffer"); - break; - } - - uint32 decoded; - - if (encoded == '=') { - // padding - decoded = 0; - } else { - decoded = BASE64_DECODE_TABLE[encoded]; - CHECK(decoded != -1, false, ""); - } - - sequence |= decoded << (2 + (4 - sequenceIndex) * 6); - sequenceIndex++; - - if (sequenceIndex % 4 == 0) { - char* buffer = (char*) &sequence; - output.Add(string_view(buffer + 3, 1)); - output.Add(string_view(buffer + 2, 1)); - output.Add(string_view(buffer + 1, 1)); - - sequence = 0; - sequenceIndex = 0; - } - - lastEncoded = encoded; - } - - return true; -} - // you're passing the callbacks - this needs to be statically allocated // but you should lazy initialize it - so make it a pointer static std::unique_ptr plugin{ nullptr }; diff --git a/Types/CPP/src/CPPFile.cpp b/Types/CPP/src/CPPFile.cpp index 7c1b34fa..5f824763 100644 --- a/Types/CPP/src/CPPFile.cpp +++ b/Types/CPP/src/CPPFile.cpp @@ -652,7 +652,7 @@ uint32 CPPFile::TokenizeOperator(const GView::View::LexicalViewer::TextParser& t } uint32 CPPFile::TokenizePreprocessDirective(const TextParser& text, TokensList& list, BlocksList& blocks, uint32 pos) { - auto eol = text.ParseUntillEndOfLine(pos); + auto eol = text.ParseUntilEndOfLine(pos); auto start = pos; pos = text.ParseSpace(pos + 1, SpaceType::SpaceAndTabs); if ((CharType::GetCharType(text[pos])) != CharType::Word) @@ -742,7 +742,7 @@ void CPPFile::Tokenize(uint32 start, uint32 end, const TextParser& text, TokensL idx = text.ParseSpace(idx, SpaceType::All); break; case CharType::SingleLineComment: - next = text.ParseUntillEndOfLine(idx); + next = text.ParseUntilEndOfLine(idx); tokenList.Add( TokenType::Comment, idx, diff --git a/Types/DOC/CMakeLists.txt b/Types/DOC/CMakeLists.txt new file mode 100644 index 00000000..8da56abd --- /dev/null +++ b/Types/DOC/CMakeLists.txt @@ -0,0 +1,2 @@ +include(type) +create_type(DOC) diff --git a/Types/DOC/include/doc.hpp b/Types/DOC/include/doc.hpp new file mode 100644 index 00000000..4f35c56c --- /dev/null +++ b/Types/DOC/include/doc.hpp @@ -0,0 +1,262 @@ +#pragma once + +#include "GView.hpp" + +#define NOSTREAM 0xffffffff + +namespace GView::Type::DOC +{ +namespace Panels +{ + class Information; +} + +class ByteStream +{ + private: + void* ptr; + size_t size; + size_t cursor; + + public: + ByteStream(void* ptr, size_t size) : ptr(ptr), size(size), cursor(0){}; + ByteStream(BufferView view) : ptr((void*) view.GetData()), size(view.GetLength()), cursor(0){}; + + BufferView Read(size_t count); + template + T ReadAs() + { + size_t count = sizeof(T); + if (cursor + count > size) { + count = size - cursor; + } + T value = *(T*) ((uint8*) ptr + cursor); + cursor += count; + return value; + } + + ByteStream& Seek(size_t count); + + size_t GetCursor() + { + return cursor; + }; + + size_t GetSize() + { + return size; + } +}; + +#pragma pack(1) +struct CFDirEntry_Data { + uint8 nameUnicode[64]; + uint16 nameLength; + uint8 objectType; + uint8 colorFlag; // 0x00 (red) or 0x01 (black) + uint32 leftSiblingId; + uint32 rightSiblingId; + uint32 childId; + uint8 clsid[16]; + uint32 stateBits; + uint64 creationTime; + uint64 modifiedTime; + uint32 startingSectorLocation; + uint64 streamSize; +}; + +class CFDirEntry +{ + private: + void AppendChildren(uint32 childId); + + public: + CFDirEntry(); + CFDirEntry(BufferView _directoryData, uint32 _entryId); + + bool Load(BufferView _directoryData, uint32 _entryId); + void BuildStorageTree(); + bool FindChildByName(std::u16string_view entryName, CFDirEntry& entry); + + private: + BufferView directoryData; + bool initialized = false; + + public: + uint32 entryId{}; + CFDirEntry_Data data{}; + std::vector children; +}; + +// REFERENCE records +struct REFERENCECONTROL_Record { + uint32 recordIndex; + String libidTwiddled; + String nameRecordExtended; + String libidExtended; + BufferView originalTypeLib; + uint32 cookie; +}; + +struct REFERENCEORIGINAL_Record { + uint32 recordIndex; + String libidOriginal; + REFERENCECONTROL_Record referenceControl; +}; + +struct REFERENCEREGISTERED_Record { + uint32 recordIndex; + String libid; +}; + +struct REFERENCEPROJECT_Record { + uint32 recordIndex; + String libidAbsolute; + String libidRelative; + uint32 majorVersion; + uint16 minorVersion; +}; + +struct MODULE_Record { + String moduleName; + String streamName; + String docString; + uint32 textOffset; + uint32 helpContext; +}; + +enum SysKind { Win16Bit = 0, Win32Bit, Macintosh, Win64Bit }; + +class DOCFile : public TypeInterface, public View::ContainerViewer::EnumerateInterface, public View::ContainerViewer::OpenItemInterface +{ + private: + constexpr static uint8 CF_HEADER_SIGNATURE[] = { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 }; + constexpr static size_t DIFAT_LOCATIONS_COUNT = 109; + + friend class Panels::Information; + + // displayed info about the file + uint16 cfMinorVersion; + uint16 cfMajorVersion; + uint32 transactionSignatureNumber; + uint32 numberOfFatSectors; + uint32 numberOfMiniFatSectors; + uint32 numberOfDifatSectors; + uint32 firstDirectorySectorLocation; + uint32 firstMiniFatSectorLocation; + uint32 firstDifatSectorLocation; + + uint32 dirMajorVersion; + uint16 dirMinorVersion; + SysKind sysKind; + String projectName; + String docString; + String helpFile; + String constants; + uint16 modulesCount; + + // compound files (vbaProject.bin) helper member variables + AppCUI::Utils::Buffer vbaProjectBuffer; + AppCUI::Utils::Buffer FAT; + AppCUI::Utils::Buffer miniStream; + AppCUI::Utils::Buffer miniFAT; + + public: + uint16 sectorSize{}; + uint16 miniSectorSize{}; + uint16 miniStreamCutoffSize{}; + + private: + std::u16string modulesPath; + CFDirEntry root; + + // VBA streams helper member variables + std::vector referenceControlRecords; + std::vector referenceOriginalRecords; + std::vector referenceRegisteredRecords; + std::vector referenceProjectRecords; + + std::vector moduleRecords; + uint32 moduleRecordIndex = 0; + + public: + DOCFile(); + virtual ~DOCFile() override + { + } + + virtual std::string_view GetTypeName() override + { + return "DOC"; + } + virtual void RunCommand(std::string_view command) override + { + // here + } + + public: + bool ProcessData(); + Reference selectionZoneInterface; + + uint32 GetSelectionZonesCount() override + { + CHECK(selectionZoneInterface.IsValid(), 0, ""); + return selectionZoneInterface->GetSelectionZonesCount(); + } + + TypeInterface::SelectionZone GetSelectionZone(uint32 index) override + { + static auto d = TypeInterface::SelectionZone{ 0, 0 }; + CHECK(selectionZoneInterface.IsValid(), d, ""); + CHECK(index < selectionZoneInterface->GetSelectionZonesCount(), d, ""); + + return selectionZoneInterface->GetSelectionZone(index); + } + + // View::ContainerViewer::EnumerateInterface + virtual bool BeginIteration(std::u16string_view path, AppCUI::Controls::TreeViewItem parent) override; + virtual bool PopulateItem(AppCUI::Controls::TreeViewItem item) override; + + // View::ContainerViewer::OpenItemInterface + virtual void OnOpenItem(std::u16string_view path, AppCUI::Controls::TreeViewItem item) override; + + // compound files (vbaProject.bin) helper methods + bool ParseVBAProject(); + Buffer OpenCFStream(const CFDirEntry& entry); + Buffer OpenCFStream(uint32 sect, uint32 size, bool useMiniFAT); + void DisplayAllVBAProjectFiles(CFDirEntry& entry); + + // VBA streams helper methods + bool DecompressStream(BufferView bv, Buffer& decompressed); + bool ParseUncompressedDirStream(BufferView bv); + bool ParseModuleStream(BufferView bv, const MODULE_Record& moduleRecord, Buffer& text); + bool FindModulesPath(const CFDirEntry& entry, UnicodeStringBuilder& path); +}; + +namespace Panels +{ + class Information : public AppCUI::Controls::TabPage + { + Reference doc; + Reference general; + Reference compoundFileInfo; + Reference vbaStreamsInfo; + + inline static const auto dec = NumericFormat{ NumericFormatFlags::None, 10, 3, ',' }; + inline static const auto hex = NumericFormat{ NumericFormatFlags::HexPrefix, 16 }; + + void UpdateGeneralInformation(); + void UpdateIssues(); + void RecomputePanelsPositions(); + + public: + Information(Reference doc); + + void Update(); + virtual void OnAfterResize(int newWidth, int newHeight) override + { + RecomputePanelsPositions(); + } + }; +}; // namespace Panels +} // namespace GView::Type::DOC diff --git a/Types/DOC/src/ByteStream.cpp b/Types/DOC/src/ByteStream.cpp new file mode 100644 index 00000000..858427b9 --- /dev/null +++ b/Types/DOC/src/ByteStream.cpp @@ -0,0 +1,25 @@ +#include "doc.hpp" + +namespace GView::Type::DOC +{ +BufferView ByteStream::Read(size_t count) +{ + if (cursor + count > size) { + count = size - cursor; + } + + BufferView view((uint8*) ptr + cursor, count); + cursor += count; + + return view; +} + +ByteStream& ByteStream::Seek(size_t count) +{ + if (cursor + count > size) { + count = size - cursor; + } + cursor += count; + return *this; +} +} // namespace GView::Type::DOC diff --git a/Types/DOC/src/CFDirEntry.cpp b/Types/DOC/src/CFDirEntry.cpp new file mode 100644 index 00000000..a9776517 --- /dev/null +++ b/Types/DOC/src/CFDirEntry.cpp @@ -0,0 +1,79 @@ +#include "doc.hpp" + +namespace GView::Type::DOC +{ +CFDirEntry::CFDirEntry() +{ +} + +CFDirEntry::CFDirEntry(BufferView _directoryData, uint32 _entryId) +{ + Load(_directoryData, _entryId); +} + +void CFDirEntry::AppendChildren(uint32 childId) +{ + if (childId == NOSTREAM) { + return; + } + + CFDirEntry child(directoryData, childId); + + AppendChildren(child.data.leftSiblingId); + size_t childIndex = children.size(); + children.emplace_back(); + AppendChildren(child.data.rightSiblingId); + + child.BuildStorageTree(); + + children[childIndex] = child; +}; + +bool CFDirEntry::Load(BufferView _directoryData, uint32 _entryId) +{ + CHECK(!initialized, false, "already initialized"); + initialized = true; + + directoryData = _directoryData; + entryId = _entryId; + data = ByteStream(directoryData).Seek(entryId * sizeof(CFDirEntry_Data)).ReadAs(); + + CHECK(data.nameLength % 2 == 0, false, "nameLength"); + CHECK(data.objectType == 0x00 || data.objectType == 0x01 || data.objectType == 0x02 || data.objectType == 0x05, false, "objectType"); + CHECK(data.colorFlag == 0x00 || data.colorFlag == 0x01, false, "colorFlag"); + + return true; +} + +void CFDirEntry::BuildStorageTree() +{ + if (data.childId == NOSTREAM) { + return; + } + + // add children + AppendChildren(data.childId); +} + +bool CFDirEntry::FindChildByName(std::u16string_view entryName, CFDirEntry& entry) +{ + std::u16string_view currentEntryName((char16_t*) this->data.nameUnicode, this->data.nameLength / 2 - 1); + if (!entryName.starts_with(currentEntryName)) { + return false; + } + + auto pos = entryName.find_first_of(u'/'); + if (pos == std::u16string::npos) { + entry = *this; + return true; + } + + for (CFDirEntry& child : children) { + std::u16string_view newEntryName = entryName.substr(pos + 1); + if (child.FindChildByName(newEntryName, entry)) { + return true; + } + } + return false; +} +} // namespace GView::Type::DOC diff --git a/Types/DOC/src/CMakeLists.txt b/Types/DOC/src/CMakeLists.txt new file mode 100644 index 00000000..14d643bd --- /dev/null +++ b/Types/DOC/src/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(DOC PRIVATE + doc.cpp + DOCFile.cpp + PanelInformation.cpp + ByteStream.cpp + CFDirEntry.cpp) \ No newline at end of file diff --git a/Types/DOC/src/DOCFile.cpp b/Types/DOC/src/DOCFile.cpp new file mode 100644 index 00000000..98fe80ba --- /dev/null +++ b/Types/DOC/src/DOCFile.cpp @@ -0,0 +1,675 @@ +#include "doc.hpp" +#include + +namespace GView::Type::DOC +{ +using namespace GView::View::LexicalViewer; + +#define ENDOFCHAIN 0xfffffffe +#define FREESECT 0xffffffff +#define FATSECT 0xfffffffd +#define DIFSECT 0xfffffffc + +DOCFile::DOCFile() +{ +} + +bool DOCFile::DecompressStream(BufferView bv, Buffer& decompressed) +{ + // TODO: document the compression algorithm and expose it into the core + + CHECK(bv[0] == 0x01, false, ""); // signature byte + + size_t index = 1; + + while (index < bv.GetLength()) { + // loop over chunks + + size_t chunkStartIndex = index; + + uint16 header = bv[index] + (bv[index + 1] << 8); + index += 2; + + uint16 chunkLength = header & 0x0fff; // + 3, for total size + bool isCompressed = header & 0x8000; // most significant bit + + uint8 headerSignature = (header & 0x7000) >> 12; + CHECK(headerSignature == 0b011, false, ""); // fixed value + + if (!isCompressed) { + CHECK(index + 4096 < bv.GetLength(), false, ""); + decompressed.Add(BufferView(bv.GetData() + index, 4096)); + index += 4096; + continue; + } + + // Token Sequence series + size_t end = chunkStartIndex + chunkLength + 3; + size_t decompressedChunkStart = decompressed.GetLength(); + while (index < end) { + unsigned char flags = bv[index++]; + for (int i = 0; i < 8; ++i) { + if (index >= end) { + break; + } + + if (flags & 0x01) { + // 2 bytes (Copy Token) + + int offsetBits = ceil(log2(decompressed.GetLength() - decompressedChunkStart)); // number of bits used for the offset value + + if (offsetBits < 4) { + offsetBits = 4; + } else if (offsetBits > 12) { + offsetBits = 12; + } + + uint16 token = bv[index] + (bv[index + 1] << 8); + uint16 offsetMask = 0xffff << (16 - offsetBits); + + int offset = ((token & offsetMask) >> (16 - offsetBits)) + 1; // negative offset from the current decompressed position + int length = (token & ~offsetMask) + 3; // the stored value is 3 less than the actual value + + // tail copy bytes may be written to the decompressed buffer while starting to copy the chunk + size_t startOffset = decompressed.GetLength() - offset; + for (size_t cursor = startOffset; cursor < startOffset + length; ++cursor) { + unsigned char byte = decompressed[cursor]; + decompressed.Add(BufferView(&byte, 1)); + } + + index += 2; + } else { + // 1 byte (Literal token) + + unsigned char byte = bv[index]; + decompressed.Add(BufferView(&byte, 1)); + index++; + } + + flags >>= 1; + } + } + } + + return true; +} + +bool DOCFile::ParseUncompressedDirStream(BufferView bv) +{ + ByteStream stream((void*) bv.GetData(), bv.GetLength()); + uint16 check; + + // PROJECTINFORMATION + CHECK(stream.ReadAs() == 0x01, false, "projectsyskind_id"); + CHECK(stream.ReadAs() == 0x04, false, "projectsyskind_size"); + + sysKind = (SysKind) stream.ReadAs(); + + check = stream.ReadAs(); + if (check == 0x4a) { + // PROJECTCOMPATVERSION + CHECK(stream.ReadAs() == 0x04, false, "projectcompat_size"); + stream.Seek(sizeof(uint32)); // compatVersion skipped for now + check = stream.ReadAs(); + } + + CHECK(check == 0x02, false, "projectlcid_id"); + CHECK(stream.ReadAs() == 0x04, false, "projectlcid_size"); + CHECK(stream.ReadAs() == 0x0409, false, "projectlcid_lcid"); + + CHECK(stream.ReadAs() == 0x14, false, "projectlcidinvoke_id"); + CHECK(stream.ReadAs() == 0x04, false, "projectlcidinvoke_size"); + CHECK(stream.ReadAs() == 0x0409, false, "lcidinvoke"); + + CHECK(stream.ReadAs() == 0x03, false, "projectcodepage_id"); + CHECK(stream.ReadAs() == 0x02, false, "projectcodepage_size"); + auto codePage = stream.ReadAs(); + + CHECK(stream.ReadAs() == 0x04, false, "projectname_id"); + auto projectName_size = stream.ReadAs(); + CHECK(projectName_size >= 1 && projectName_size <= 128, false, "projectname_size"); + projectName = String(stream.Read(projectName_size)); + + CHECK(stream.ReadAs() == 0x05, false, "projectdocstring_id"); + auto projectDocString_size = stream.ReadAs(); + CHECK(projectDocString_size <= 2000, false, "projectdocstring_size"); + docString = String(stream.Read(projectDocString_size)); + + CHECK(stream.ReadAs() == 0x40, false, "reserved"); + auto projectDocStringUnicode_size = stream.ReadAs(); + CHECK(projectDocStringUnicode_size % 2 == 0, false, "projectDocStringUnicode_size"); + UnicodeStringBuilder projectDocStringUnicode(stream.Read(projectDocStringUnicode_size)); + + CHECK(stream.ReadAs() == 0x06, false, "helpFile1_id"); + auto helpFile1_size = stream.ReadAs(); + CHECK(helpFile1_size <= 260, false, "helpFile1_size"); + String helpFile1(stream.Read(helpFile1_size)); + CHECK(stream.ReadAs() == 0x3d, false, "reserved"); + auto helpFile2_size = stream.ReadAs(); + CHECK(helpFile2_size == helpFile1_size, false, "helpFile2_size"); + String helpFile2(stream.Read(helpFile2_size)); + for (uint32 i = 0; i < helpFile1_size; ++i) { + CHECK(helpFile1[i] == helpFile2[i], false, "helpFiles"); + } + + helpFile = helpFile1; + + CHECK(stream.ReadAs() == 0x07, false, "projectHelpContext_id"); + CHECK(stream.ReadAs() == 0x04, false, "projectHelpContext_size"); + auto projectHelpContext = stream.ReadAs(); + + CHECK(stream.ReadAs() == 0x08, false, "projectLibFlags_id"); + CHECK(stream.ReadAs() == 0x04, false, "projectLibFlags_size"); + CHECK(stream.ReadAs() == 0x00, false, "projectLibFlags"); + + CHECK(stream.ReadAs() == 0x09, false, "projectVersoin_id"); + CHECK(stream.ReadAs() == 0x04, false, "reserved"); + dirMajorVersion = stream.ReadAs(); + dirMinorVersion = stream.ReadAs(); + + CHECK(stream.ReadAs() == 0x0c, false, "projectConstants_id"); + auto projectConstants_size = stream.ReadAs(); + CHECK(projectConstants_size <= 1015, false, "projectConstants_size"); + + constants = String(stream.Read(projectConstants_size)); + CHECK(stream.ReadAs() == 0x3c, false, "reserved"); + + auto projectConstantsUnicode_size = stream.ReadAs(); + CHECK(projectConstantsUnicode_size % 2 == 0, false, "projectConstantsUnicode_size"); + UnicodeStringBuilder constantsUnicode(stream.Read(projectConstantsUnicode_size)); + + uint32 recordIndex = 0; + + // PROJECTREFERENCES + while (true) { + // NameRecord + auto referenceName_id = stream.ReadAs(); + if (referenceName_id == 0x0f) { + // end of Records array and beginning of PROJECTMODULES Record + break; + } + + CHECK(referenceName_id == 0x16, false, "referenceName_id"); + auto referenceName_size = stream.ReadAs(); + String referenceName(stream.Read(referenceName_size)); + CHECK(stream.ReadAs() == 0x3e, false, "reserved"); + auto referenceNameUnicode_size = stream.ReadAs(); + UnicodeStringBuilder referenceNameUnicode(stream.Read(referenceNameUnicode_size)); + + // ReferenceRecord + auto referenceRecord_type = stream.ReadAs(); + switch (referenceRecord_type) { + case 0x2f: + { + // REFERENCECONTROL Record + + auto& record = referenceControlRecords.emplace_back(); + record.recordIndex = recordIndex; + + stream.Seek(sizeof(uint32)); // SizeTwiddled + auto sizeOfLibidTwiddled = stream.ReadAs(); + record.libidTwiddled = String(stream.Read(sizeOfLibidTwiddled)); + CHECK(stream.ReadAs() == 0x00, false, "reserved1"); + CHECK(stream.ReadAs() == 0x00, false, "reserved2"); + + check = stream.ReadAs(); + + if (check == 0x16) { + // optional NameRecordExtended + auto sizeOfName = stream.ReadAs(); + record.nameRecordExtended = String(stream.Read(sizeOfName)); + CHECK(stream.ReadAs() == 0x3e, false, "reserved"); + auto sizeOfNameUnicode = stream.ReadAs(); + UnicodeStringBuilder nameUnicode(stream.Read(sizeOfNameUnicode)); + check = stream.ReadAs(); + } + + CHECK(check == 0x30, false, "reserved3"); + stream.Seek(sizeof(uint32)); // SizeExtended + auto sizeOfLibidExtended = stream.ReadAs(); + record.libidExtended = String(stream.Read(sizeOfLibidExtended)); + CHECK(stream.ReadAs() == 0x00, false, "reserved4"); + CHECK(stream.ReadAs() == 0x00, false, "reserved5"); + record.originalTypeLib = BufferView(stream.Read(16)); + record.cookie = stream.ReadAs(); + + break; + } + case 0x33: { + // REFERENCEORIGINAL Record + + auto& record = referenceOriginalRecords.emplace_back(); + record.recordIndex = recordIndex; + + auto sizeOfLibidOriginal = stream.ReadAs(); + record.libidOriginal = String(stream.Read(sizeOfLibidOriginal)); + CHECK(stream.ReadAs() == 0x2f, false, "referenceControl_id"); + + stream.Seek(sizeof(uint32)); // SizeTwiddled + auto sizeOfLibidTwiddled = stream.ReadAs(); + record.referenceControl.libidTwiddled = String(stream.Read(sizeOfLibidTwiddled)); + CHECK(stream.ReadAs() == 0x00, false, "reserved1"); + CHECK(stream.ReadAs() == 0x00, false, "reserved2"); + + check = stream.ReadAs(); + + if (check == 0x16) { + // optional NameRecordExtended + auto sizeOfName = stream.ReadAs(); + record.referenceControl.nameRecordExtended = String(stream.Read(sizeOfName)); + CHECK(stream.ReadAs() == 0x3e, false, "reserved"); + auto sizeOfNameUnicode = stream.ReadAs(); + UnicodeStringBuilder nameUnicode(stream.Read(sizeOfNameUnicode)); + check = stream.ReadAs(); + } + + CHECK(check == 0x30, false, "reserved3"); + stream.Seek(sizeof(uint32)); // SizeExtended + auto sizeOfLibidExtended = stream.ReadAs(); + record.referenceControl.libidExtended = String(stream.Read(sizeOfLibidExtended)); + CHECK(stream.ReadAs() == 0x00, false, "reserved4"); + CHECK(stream.ReadAs() == 0x00, false, "reserved5"); + record.referenceControl.originalTypeLib = BufferView(stream.Read(16)); + record.referenceControl.cookie = stream.ReadAs(); + + break; + } + case 0x0d: { + // REFERENCEREGISTERED Record + + auto& record = referenceRegisteredRecords.emplace_back(); + record.recordIndex = recordIndex; + + stream.Seek(sizeof(uint32)); // ignored Size + + auto sizeOfLibid = stream.ReadAs(); + record.libid = String(stream.Read(sizeOfLibid)); + + CHECK(stream.ReadAs() == 0x00, false, "reserved1"); + CHECK(stream.ReadAs() == 0x00, false, "reserved2"); + + break; + } + case 0x0e: { + // REFERENCEPROJECT Record + + auto& record = referenceProjectRecords.emplace_back(); + record.recordIndex = recordIndex; + + stream.Seek(sizeof(uint32)); // ignored Size + auto sizeOfLibidAbsolute = stream.ReadAs(); + record.libidAbsolute = String(stream.Read(sizeOfLibidAbsolute)); + auto sizeOfLibidRelative = stream.ReadAs(); + record.libidRelative = String(stream.Read(sizeOfLibidRelative)); + + record.majorVersion = stream.ReadAs(); + record.minorVersion = stream.ReadAs(); + + break; + } + default: + return false; + } + + recordIndex++; + } + + // PROJECTMODULES + CHECK(stream.ReadAs() == 0x02, false, "size"); + modulesCount = stream.ReadAs(); + CHECK(stream.ReadAs() == 0x13, false, "projectCookie_id"); + CHECK(stream.ReadAs() == 0x02, false, "projectCookie_size"); + stream.Seek(sizeof(uint16)); // ignored Cookie + + moduleRecords.reserve(modulesCount); + + // array of MODULE records + for (uint32 moduleIndex = 0; moduleIndex < modulesCount; ++moduleIndex) { + // TODO: check this - MUST have a corresponding specified in PROJECT Stream + MODULE_Record& moduleRecord = moduleRecords.emplace_back(); + + CHECK(stream.ReadAs() == 0x19, false, "moduleName_id"); + auto sizeOfModuleName = stream.ReadAs(); + + moduleRecord.moduleName = String(stream.Read(sizeOfModuleName)); + + CHECK(stream.ReadAs() == 0x47, false, "moduleNameUnicode_id"); + auto sizeOfModuleNameUnicode = stream.ReadAs(); + CHECK(sizeOfModuleNameUnicode % 2 == 0, false, "sizeOfModuleNameUnicode"); + UnicodeStringBuilder moduleNameUnicode(stream.Read(sizeOfModuleNameUnicode)); + + CHECK(stream.ReadAs() == 0x1a, false, "moduleStreamName_id"); + auto sizeOfStreamName = stream.ReadAs(); + moduleRecord.streamName = String(stream.Read(sizeOfStreamName)); + CHECK(stream.ReadAs() == 0x32, false, "reserved"); + + auto sizeOfStreamNameUnicode = stream.ReadAs(); + CHECK(sizeOfStreamNameUnicode % 2 == 0, false, "sizeOfStreamNameUnicode"); + String streamNameUnicode(stream.Read(sizeOfStreamNameUnicode)); + + CHECK(stream.ReadAs() == 0x1c, false, "moduleDocString_id"); + auto sizeOfDocString = stream.ReadAs(); + + moduleRecord.docString = String(stream.Read(sizeOfDocString)); + CHECK(stream.ReadAs() == 0x48, false, "reserved"); + auto sizeOfDocStringUnicode = stream.ReadAs(); + CHECK(sizeOfDocStringUnicode % 2 == 0, false, "sizeOfDocStringUnicode"); + UnicodeStringBuilder docStringUnicode(stream.Read(sizeOfDocStringUnicode)); + + CHECK(stream.ReadAs() == 0x31, false, "moduleOffset_id"); + CHECK(stream.ReadAs() == 0x04, false, "moduleOffset_size"); + moduleRecord.textOffset = stream.ReadAs(); + + CHECK(stream.ReadAs() == 0x1e, false, "moduleHelpContext_id"); + CHECK(stream.ReadAs() == 0x04, false, "moduleHelpContext_size"); + moduleRecord.helpContext = stream.ReadAs(); + + CHECK(stream.ReadAs() == 0x2c, false, "moduleCookie_id"); + CHECK(stream.ReadAs() == 0x02, false, "moduleCookie_size"); + stream.Seek(sizeof(uint16)); // ignored Cookie + + auto moduleType_id = stream.ReadAs(); + CHECK(moduleType_id == 0x21 || moduleType_id == 0x22, false, "moduleType_id"); + stream.Seek(sizeof(uint32)); // ignored Reserved + + check = stream.ReadAs(); + if (check == 0x25) { + // optional MODULEREADONLY + stream.Seek(sizeof(uint32)); // ignored Reserved + check = stream.ReadAs(); + } + + if (check == 0x28) { + // optional MODULEPRIVATE + stream.Seek(sizeof(uint32)); // ignored Reserved + check = stream.ReadAs(); + } + + auto terminator = check; + CHECK(terminator == 0x2b, false, "terminator"); + stream.Seek(sizeof(uint32)); // ignored Reserved + } + + CHECK(stream.ReadAs() == 0x10, false, "terminator"); + stream.Seek(sizeof(uint32)); // ignored Reserved + + CHECK(stream.GetCursor() == stream.GetSize(), false, "buffer still available"); + return true; +} + +bool DOCFile::ParseModuleStream(BufferView bv, const MODULE_Record& moduleRecord, Buffer& text) +{ + size_t moduleTextOffset = moduleRecord.textOffset; + ByteStream stream(bv); + stream.Seek(moduleTextOffset); + auto compressed = stream.Read(stream.GetSize() - stream.GetCursor()); + CHECK(DecompressStream(compressed, text), false, "decompress"); + + return true; +} + + +Buffer DOCFile::OpenCFStream(const CFDirEntry& entry) +{ + CHECK(entry.data.objectType == 0x02, Buffer(), "incorrect entry"); + + auto sect = entry.data.startingSectorLocation; + auto size = entry.data.streamSize; + bool useMiniFAT = size < miniStreamCutoffSize; + + return OpenCFStream(sect, size, useMiniFAT); +} + +Buffer DOCFile::OpenCFStream(uint32 sect, uint32 size, bool useMiniFAT) +{ + BufferView stream; + BufferView fat; + uint32 usedSectorSize; + uint32 offset; + + if (useMiniFAT) { + // use miniFAT + stream = miniStream; + fat = miniFAT; + usedSectorSize = miniSectorSize; + offset = 0; + } else { + // use FAT + stream = vbaProjectBuffer; + fat = FAT; + usedSectorSize = sectorSize; + offset = usedSectorSize; + } + + Buffer data; + uint16 actualNumberOfSectors = ((size + usedSectorSize - 1) / usedSectorSize); + for (uint32 i = 0; i < actualNumberOfSectors; ++i) { + if (sect == ENDOFCHAIN) { + // end of sector chain + break; + } + + data.Add(ByteStream(stream).Seek(offset + usedSectorSize * sect).Read(usedSectorSize)); + + if (sect * sizeof(uint32) >= fat.GetLength()) { + return Buffer(); + } + sect = *(((uint32*) fat.GetData()) + sect); // get the next sect + } + + if (data.GetLength() > size) { + data.Resize(size); + } + + return data; +} + + +void DOCFile::DisplayAllVBAProjectFiles(CFDirEntry& entry) +{ + auto type = entry.data.objectType; + char16* name = (char16*) entry.data.nameUnicode; + + if (type == 0x02) { + Buffer entryBuffer = DOCFile::OpenCFStream(entry); + + GView::App::OpenBuffer(entryBuffer, name, "", GView::App::OpenMethod::BestMatch, "bin"); + } + + for (auto& child : entry.children) { + DisplayAllVBAProjectFiles(child); + } +} + + +bool DOCFile::FindModulesPath(const CFDirEntry& entry, UnicodeStringBuilder& path) +{ + std::u16string_view name((char16*) entry.data.nameUnicode, entry.data.nameLength / 2 - 1); // take into account the null character + + if (!entry.children.size()) { + return name == u"dir"; + } + + for (const CFDirEntry& child : entry.children) { + UnicodeStringBuilder pathPart; + if (FindModulesPath(child, pathPart)) { + path.Add(name); + path.Add("/"); + path.Add(pathPart); + return true; + } + } + + return false; +} + + +bool DOCFile::ParseVBAProject() +{ + ByteStream stream(vbaProjectBuffer); + + for (uint32 i = 0; i < ARRAY_LEN(CF_HEADER_SIGNATURE); ++i) { + CHECK(stream.ReadAs() == CF_HEADER_SIGNATURE[i], false, "headerSignature"); + } + + CHECK(stream.ReadAs() == 0, false, "headerCLSID"); + CHECK(stream.ReadAs() == 0, false, "headerCLSID"); + + cfMinorVersion = stream.ReadAs(); // TODO: This field SHOULD be set to 0x003E if the major version field is either 0x0003 or 0x0004. + cfMajorVersion = stream.ReadAs(); + CHECK(cfMajorVersion == 0x03 || cfMajorVersion == 0x04, false, "majorVersion"); + + CHECK(stream.ReadAs() == 0xfffe, false, "byteOrder"); + auto sectorShift = stream.ReadAs(); + CHECK((cfMajorVersion == 0x03 && sectorShift == 0x09) || (cfMajorVersion == 0x04 && sectorShift == 0x0c), false, "sectorShift"); + sectorSize = 1 << sectorShift; + + auto miniSectorShift = stream.ReadAs(); + CHECK(miniSectorShift == 0x06, false, "miniSectorShift"); + miniSectorSize = 1 << miniSectorShift; + + CHECK(stream.ReadAs() == 0x00, false, "reserved"); + CHECK(stream.ReadAs() == 0x00, false, "reserved"); + + auto numberOfDirectorySectors = stream.ReadAs(); + if (cfMajorVersion == 0x03) { + CHECK(numberOfDirectorySectors == 0x00, false, "numberOfDirectorySectors"); + } + + numberOfFatSectors = stream.ReadAs(); + firstDirectorySectorLocation = stream.ReadAs(); + transactionSignatureNumber = stream.ReadAs(); // incremented every time the file is saved + + miniStreamCutoffSize = stream.ReadAs(); + CHECK(miniStreamCutoffSize == 0x1000, false, "miniStreamCutoffSize"); + + firstMiniFatSectorLocation = stream.ReadAs(); + numberOfMiniFatSectors = stream.ReadAs(); + firstDifatSectorLocation = stream.ReadAs(); + numberOfDifatSectors = stream.ReadAs(); + + uint32 DIFAT[DIFAT_LOCATIONS_COUNT]; // the first DIFAT sector locations of the compound file + { + auto difatBv = stream.Read(DIFAT_LOCATIONS_COUNT * sizeof(*DIFAT)); + memcpy(DIFAT, (void*) difatBv.GetData(), difatBv.GetLength()); + } + + if (cfMajorVersion == 0x04) { + // check if the next 3584 bytes are 0 + while (stream.GetCursor() < sectorSize) { + CHECK(stream.ReadAs() == 0x00, false, "zeroCheck"); + } + } + + // load FAT + for (size_t locationIndex = 0; locationIndex < DIFAT_LOCATIONS_COUNT; ++locationIndex) { + uint32 sect = DIFAT[locationIndex]; + if (sect == ENDOFCHAIN || sect == FREESECT) { + // end of sector chain + break; + } + + // get the sector data + size_t byteOffset = sectorSize * (sect + 1ULL); + BufferView sector(vbaProjectBuffer.GetData() + byteOffset, sectorSize); + FAT.Add(sector); + } + + uint16 actualNumberOfSectors = ((vbaProjectBuffer.GetLength() + sectorSize - 1) / sectorSize) - 1; + if (FAT.GetLength() > actualNumberOfSectors * sizeof(uint32)) { + FAT.Resize(actualNumberOfSectors * sizeof(uint32)); + } + + // load directory + Buffer directoryData = OpenCFStream(firstDirectorySectorLocation, vbaProjectBuffer.GetLength(), false); + + // parse dir entries, starting with root entry + root = CFDirEntry(directoryData, 0); + root.BuildStorageTree(); + + uint32 streamSize = numberOfMiniFatSectors * sectorSize; + uint16 actualNumberOfMinisectors = (root.data.streamSize + miniSectorSize - 1) / miniSectorSize; + + // load miniFAT + miniFAT = OpenCFStream(firstMiniFatSectorLocation, streamSize, false); // will be interpreted as uint32* + if (miniFAT.GetLength() > actualNumberOfMinisectors * sizeof(uint32)) { + miniFAT.Resize(actualNumberOfMinisectors * sizeof(uint32)); + } + + // load ministream + uint32 miniStreamSize = root.data.streamSize; + miniStream = OpenCFStream(root.data.startingSectorLocation, miniStreamSize, false); + + // find file + UnicodeStringBuilder modulesPathUsb; + CHECK(FindModulesPath(root, modulesPathUsb), false, "modulesPath"); + modulesPath = modulesPathUsb; + + CFDirEntry dir; + CHECK(root.FindChildByName(modulesPath + u"dir", dir), false, ""); + Buffer dirData = OpenCFStream(dir); + + Buffer decompressedDirData; + CHECK(DecompressStream(dirData, decompressedDirData), false, "decompress dir stream"); + CHECK(ParseUncompressedDirStream(decompressedDirData), false, "parse dir stream"); + + return true; +} + +bool DOCFile::ProcessData() +{ + vbaProjectBuffer = obj->GetData().GetEntireFile(); + CHECK(ParseVBAProject(), false, ""); + return true; +} + +bool DOCFile::BeginIteration(std::u16string_view path, AppCUI::Controls::TreeViewItem parent) +{ + moduleRecordIndex = 0; + return true; +} + +bool DOCFile::PopulateItem(AppCUI::Controls::TreeViewItem item) +{ + MODULE_Record& moduleRecord = moduleRecords[moduleRecordIndex]; + + item.SetText(0, moduleRecord.moduleName); + item.SetText(1, moduleRecord.streamName); + + std::u16string absoluteStreamName = modulesPath; + absoluteStreamName.append(UnicodeStringBuilder(moduleRecord.streamName)); + CFDirEntry moduleEntry; + CHECK(root.FindChildByName(absoluteStreamName, moduleEntry), false, ""); + Buffer moduleBuffer = OpenCFStream(moduleEntry); + Buffer decompressed; + ParseModuleStream(moduleBuffer, moduleRecord, decompressed); + + // TODO: add the creation time and modified time of the module stream + + item.SetText(2, String().Format("%llu", decompressed.GetLength())); + + item.SetText(3, moduleRecord.docString); + + item.SetData(&moduleRecord); + + moduleRecordIndex++; + return moduleRecordIndex < moduleRecords.size(); +} + +void DOCFile::OnOpenItem(std::u16string_view path, AppCUI::Controls::TreeViewItem item) +{ + auto moduleRecord = item.GetData(); + + std::u16string absoluteStreamName = modulesPath; + absoluteStreamName.append(UnicodeStringBuilder(moduleRecord->streamName)); + CFDirEntry moduleEntry; + CHECKRET(root.FindChildByName(absoluteStreamName, moduleEntry), ""); + Buffer moduleBuffer = OpenCFStream(moduleEntry); + + Buffer decompressed; + if (!ParseModuleStream(moduleBuffer, moduleRecord, decompressed)) { + AppCUI::Dialogs::MessageBox::ShowError("Error", "Module parse error!"); + } + GView::App::OpenBuffer(decompressed, moduleRecord->streamName, "", GView::App::OpenMethod::ForceType, "VBA"); +} +} // namespace GView::Type::DOC diff --git a/Types/DOC/src/PanelInformation.cpp b/Types/DOC/src/PanelInformation.cpp new file mode 100644 index 00000000..bb56cb83 --- /dev/null +++ b/Types/DOC/src/PanelInformation.cpp @@ -0,0 +1,138 @@ +#include "doc.hpp" + +using namespace GView::Type::DOC; +using namespace AppCUI::Controls; + +Panels::Information::Information(Reference _doc) : TabPage("&Information") +{ + doc = _doc; + general = Factory::ListView::Create(this, "x:0,y:0,w:100%,h:5", { "n:Field,w:16", "n:Value,w:100" }, ListViewFlags::None); + compoundFileInfo = Factory::ListView::Create(this, "x:0,y:5,w:100%,h:10", { "n:Field,w:16", "n:Value,w:10000" }, ListViewFlags::None); + vbaStreamsInfo = Factory::ListView::Create(this, "x:0,y:15,w:100%,h:20", { "n:Field,w:16", "n:Value,w:10000" }, ListViewFlags::None); + + this->Update(); +} +void Panels::Information::UpdateGeneralInformation() +{ + general->DeleteAllItems(); + + general->AddItem("File"); + // size + { + LocalString<256> tempStr; + auto sizeString = NumericFormatter().ToString(doc->obj->GetData().GetSize(), { NumericFormatFlags::None, 10, 3, ',' }).data(); + auto value = tempStr.Format("%s bytes", sizeString); + general->AddItem({ "Size", value }); + } + + NumericFormatter nf; + + compoundFileInfo->AddItem("Compound file"); + vbaStreamsInfo->AddItem({ "Project name", doc->projectName }); + compoundFileInfo->AddItem({ "Minor version", nf.ToString(doc->cfMinorVersion, hex) }); + compoundFileInfo->AddItem({ "Major version", nf.ToString(doc->cfMajorVersion, hex) }); + compoundFileInfo->AddItem({ "Transaction signature number", nf.ToString(doc->transactionSignatureNumber, dec) }); + compoundFileInfo->AddItem({ "FAT sectors count", nf.ToString(doc->numberOfFatSectors, dec) }); + compoundFileInfo->AddItem({ "MiniFAT sectors count", nf.ToString(doc->numberOfMiniFatSectors, dec) }); + compoundFileInfo->AddItem({ "DIFAT sectors count", nf.ToString(doc->numberOfDifatSectors, dec) }); + compoundFileInfo->AddItem({ "First directory sector", nf.ToString(doc->firstDirectorySectorLocation, hex) }); + compoundFileInfo->AddItem({ "First MiniFAT sector", nf.ToString(doc->firstMiniFatSectorLocation, hex) }); + compoundFileInfo->AddItem({ "First DIFAT sector", nf.ToString(doc->firstDifatSectorLocation, hex) }); + + vbaStreamsInfo->AddItem("VBA streams"); + vbaStreamsInfo->AddItem({ "Major version", nf.ToString(doc->dirMajorVersion, hex) }); + vbaStreamsInfo->AddItem({ "Minor version", nf.ToString(doc->dirMinorVersion, hex) }); + vbaStreamsInfo->AddItem({ "Modules path", doc->modulesPath }); + + switch (doc->sysKind) { + case Win16Bit: + vbaStreamsInfo->AddItem({ "System kind", "Win16Bit" }); + break; + case Win32Bit: + vbaStreamsInfo->AddItem({ "System kind", "Win32Bit" }); + break; + case Macintosh: + vbaStreamsInfo->AddItem({ "System kind", "Macintosh" }); + break; + case Win64Bit: + vbaStreamsInfo->AddItem({ "System kind", "Win64Bit" }); + break; + default: + vbaStreamsInfo->AddItem({ "System kind", "Unknown" }); + break; + } + + vbaStreamsInfo->AddItem({ "Doc string", doc->docString }); + vbaStreamsInfo->AddItem({ "Help file", doc->helpFile }); + vbaStreamsInfo->AddItem({ "Constants", doc->constants }); + vbaStreamsInfo->AddItem({ "Modules count", nf.ToString(doc->modulesCount, dec) }); + + LocalString<256> header; + uint32 index; + + index = 0; + for (const auto& record : doc->referenceControlRecords) { + vbaStreamsInfo->AddItem(""); + header.Format("Reference control record #%s", nf.ToString(index++, dec).data()); + vbaStreamsInfo->AddItem(header); + + vbaStreamsInfo->AddItem({ "Libid twiddled", record.libidTwiddled }); + vbaStreamsInfo->AddItem({ "Name record extended", record.nameRecordExtended }); + vbaStreamsInfo->AddItem({ "Libid extended", record.libidExtended }); + vbaStreamsInfo->AddItem({ "Cookie", nf.ToString(record.cookie, hex) }); + } + + index = 0; + for (const auto& record : doc->referenceOriginalRecords) { + vbaStreamsInfo->AddItem(""); + header.Format("Reference original record #%s", nf.ToString(index++, dec).data()); + vbaStreamsInfo->AddItem(header); + + vbaStreamsInfo->AddItem({ "Libid original", record.libidOriginal }); + vbaStreamsInfo->AddItem({ "Libid twiddled", record.referenceControl.libidTwiddled }); + vbaStreamsInfo->AddItem({ "Name record extended", record.referenceControl.nameRecordExtended }); + vbaStreamsInfo->AddItem({ "Libid extended", record.referenceControl.libidExtended }); + vbaStreamsInfo->AddItem({ "Cookie", nf.ToString(record.referenceControl.cookie, hex) }); + } + + index = 0; + for (const auto& record : doc->referenceRegisteredRecords) { + vbaStreamsInfo->AddItem(""); + header.Format("Reference registered record #%s", nf.ToString(index++, dec).data()); + vbaStreamsInfo->AddItem(header); + + vbaStreamsInfo->AddItem({ "Libid", record.libid }); + } + + index = 0; + for (const auto& record : doc->referenceProjectRecords) { + vbaStreamsInfo->AddItem(""); + header.Format("Reference absolute record #%s", nf.ToString(index++, dec).data()); + vbaStreamsInfo->AddItem(header); + + vbaStreamsInfo->AddItem({ "Libid absolute", record.libidAbsolute }); + vbaStreamsInfo->AddItem({ "Libid relative", record.libidRelative }); + vbaStreamsInfo->AddItem({ "Major version", nf.ToString(record.majorVersion, hex) }); + vbaStreamsInfo->AddItem({ "Minor version", nf.ToString(record.minorVersion, hex) }); + } +} + +void Panels::Information::UpdateIssues() +{ +} +void Panels::Information::RecomputePanelsPositions() +{ + int w = this->GetWidth(); + int h = this->GetHeight(); + + if (!general.IsValid()) + return; + + this->general->Resize(w, h); +} +void Panels::Information::Update() +{ + UpdateGeneralInformation(); + UpdateIssues(); + RecomputePanelsPositions(); +} diff --git a/Types/DOC/src/doc.cpp b/Types/DOC/src/doc.cpp new file mode 100644 index 00000000..9b72eaf5 --- /dev/null +++ b/Types/DOC/src/doc.cpp @@ -0,0 +1,93 @@ +#include "doc.hpp" +#include +#include +#include + +using namespace AppCUI; +using namespace AppCUI::Utils; +using namespace AppCUI::Application; +using namespace AppCUI::Controls; +using namespace GView::Utils; +using namespace GView::Type; +using namespace GView; +using namespace GView::View; + + +constexpr string_view DOC_ICON = "1111111111111111" // 5 + "1wwwwwwwww111111" // 6 + "1w11111111w11111" // 7 + "1w111111111w1111" // 8 + "1w1111111111w111" // 9 + "1w11111111111w11" // 9 + "1w111111111111w1" // 9 + "1w1www1www1111w1" // 9 + "1w111111111111w1" // 10 + "1w1wwwww1wwww1w1" // 11 + "1w111111111111w1" // 12 + "1w1wwwwwwwww11w1" // 12 + "1w111111111111w1" // 13 + "1w111111111111w1" // 14 + "1wwwwwwwwwwwwww1" // 15 + "1111111111111111"; // 16 + +void CreateContainerView(Reference win, Reference doc) +{ + ContainerViewer::Settings settings; + + const auto hex = NumericFormat{ NumericFormatFlags::HexPrefix, 16 }; + + NumericFormatter nf; // should not use the same numerical formatter for multiple operations, but AddProperty owns the given string so it's fine + settings.AddProperty("Sector size", nf.ToString(doc->sectorSize, hex)); + settings.AddProperty("Mini sector size", nf.ToString(doc->miniSectorSize, hex)); + settings.AddProperty("Mini stream cutoff", nf.ToString(doc->miniStreamCutoffSize, hex)); + + settings.SetIcon(DOC_ICON); + settings.SetColumns({ + "n:&Module name,a:l,w:30", + "n:&Stream name,a:c,w:40", + "n:&Size,a:c,w:15", + "n:&Doc String,a:c,w:100", + }); + + settings.SetEnumerateCallback(win->GetObject()->GetContentType().ToObjectRef()); + settings.SetOpenItemCallback(win->GetObject()->GetContentType().ToObjectRef()); + + win->CreateViewer(settings); +} + +extern "C" { +PLUGIN_EXPORT bool Validate(const AppCUI::Utils::BufferView& buf, const std::string_view& extension) +{ + return true; +} +PLUGIN_EXPORT TypeInterface* CreateInstance() +{ + return new DOC::DOCFile(); +} + +PLUGIN_EXPORT bool PopulateWindow(Reference win) +{ + auto doc = win->GetObject()->GetContentType(); + + if (!doc->ProcessData()) { + AppCUI::Dialogs::MessageBox::ShowError("Error", "Incorrect format!"); + return false; + } + + CreateContainerView(win, doc); + win->AddPanel(Pointer(new DOC::Panels::Information(doc)), true); + + return true; +} +PLUGIN_EXPORT void UpdateSettings(IniSection sect) +{ + sect["Pattern"] = "magic:D0 CF 11 E0 A1 B1 1A E1"; + sect["Priority"] = 1; + sect["Description"] = "Compound file containing VBA macros (vbaProject.bin)"; +} +} + +int main() +{ + return 0; +} diff --git a/Types/EML/include/eml.hpp b/Types/EML/include/eml.hpp index 58db0f33..781cf86c 100644 --- a/Types/EML/include/eml.hpp +++ b/Types/EML/include/eml.hpp @@ -2,11 +2,10 @@ #include "GView.hpp" -struct EML_Item_Record -{ - uint32 parentStartIndex; - uint32 startIndex; +struct EML_Item_Record { + uint32 startOffset; uint32 dataLength; + uint32 partIndex; std::u16string contentType; std::u16string identifier; bool leafNode; @@ -45,10 +44,14 @@ namespace Type void ParseHeaders(GView::View::LexicalViewer::TextParser text, uint32& index); uint32 ParseHeaderFieldBody(GView::View::LexicalViewer::TextParser text, uint32 index); std::u16string ExtractContentType(GView::View::LexicalViewer::TextParser text, uint32 start, uint32 end); + void ExtractFieldNameAndBody( + GView::View::LexicalViewer::TextParser text, uint32& start, uint32& end, std::u16string& fieldName, std::u16string& fieldBody); public: EMLFile(); - virtual ~EMLFile() override {} + virtual ~EMLFile() override + { + } virtual std::string_view GetTypeName() override { @@ -58,6 +61,7 @@ namespace Type { // here } + public: bool ProcessData(); @@ -88,7 +92,6 @@ namespace Type // View::ContainerViewer::OpenItemInterface virtual void OnOpenItem(std::u16string_view path, AppCUI::Controls::TreeViewItem item) override; - }; namespace Panels diff --git a/Types/EML/src/EMLFile.cpp b/Types/EML/src/EMLFile.cpp index 2c679583..9d777d50 100644 --- a/Types/EML/src/EMLFile.cpp +++ b/Types/EML/src/EMLFile.cpp @@ -22,7 +22,7 @@ bool EMLFile::ProcessData() uint32 modeNr = 1, attachmentsNr = 1; for (auto& itemData : items) { - itemData.contentType = itemData.leafNode ? contentType : ExtractContentType(text, itemData.startIndex, itemData.startIndex + itemData.dataLength); + itemData.contentType = itemData.leafNode ? contentType : ExtractContentType(text, itemData.startOffset, itemData.startOffset + itemData.dataLength); itemData.identifier = GetIdentifierFromContentType(itemData.contentType); if (!itemData.identifier.empty()) continue; @@ -48,24 +48,37 @@ bool EMLFile::ProcessData() return true; } -std::u16string EMLFile::ExtractContentType(TextParser text, uint32 start, uint32 end) +void EMLFile::ExtractFieldNameAndBody(TextParser text, uint32& start, uint32& end, std::u16string& fieldName, std::u16string& fieldBody) { - start = text.ParseUntillText(start, "content-type", true); + // header-field name + end = text.ParseUntilText(start, ":", false); - if (start >= text.Len()) { - return u""; - } + fieldName = text.GetSubString(start, end); - // TODO: make a function that extracts both the header and the field body - start = text.ParseUntillText(start, ":", false); - start = text.ParseSpace(start + 1, SpaceType::All); - end = ParseHeaderFieldBody(text, start); + // ltrim + start = end = text.ParseSpace(end + 1, SpaceType::SpaceAndTabs); - std::u16string fieldBody(text.GetSubString(start, end)); + // header-field body + end = ParseHeaderFieldBody(text, start); + fieldBody = text.GetSubString(start, end); + + // remove CRLF size_t pos = 0; while ((pos = fieldBody.find(u"\r\n", pos)) != std::u16string::npos) fieldBody.replace(pos, 2, u""); +} + +std::u16string EMLFile::ExtractContentType(TextParser text, uint32 start, uint32 end) +{ + start = text.ParseUntilText(start, "content-type", true); + + if (start >= text.Len()) { + return u""; + } + + std::u16string fieldName, fieldBody; + ExtractFieldNameAndBody(text, start, end, fieldName, fieldBody); return fieldBody.substr(0, fieldBody.find(u';')); } @@ -81,12 +94,19 @@ bool EMLFile::PopulateItem(AppCUI::Controls::TreeViewItem item) EML_Item_Record& itemData = items[itemsIndex]; TextParser text(unicodeString.ToStringView()); - std::u16string contentTypeNode = itemData.leafNode ? contentType : ExtractContentType(text, itemData.startIndex, itemData.startIndex + itemData.dataLength); + item.SetText(0, String().Format("%u", itemData.partIndex)); + + if (itemData.leafNode) { + TextParser contentTypeParser(contentType); + uint32 typeEnd = contentTypeParser.ParseUntilText(0, ";", false); + u16string_view type = contentTypeParser.GetSubString(0, typeEnd); + item.SetText(1, type); + } else { + item.SetText(1, ExtractContentType(text, itemData.startOffset, itemData.startOffset + itemData.dataLength)); + } - item.SetText(0, itemData.identifier); - item.SetText(1, itemData.contentType); item.SetText(2, String().Format("%u", itemData.dataLength)); - item.SetText(3, String().Format("%u", itemData.startIndex + itemData.parentStartIndex)); + item.SetText(3, String().Format("%u", itemData.startOffset)); item.SetData(&itemData); @@ -99,10 +119,10 @@ void EMLFile::OnOpenItem(std::u16string_view path, AppCUI::Controls::TreeViewIte auto itemData = item.GetData(); TextParser text(unicodeString.ToStringView()); - auto currentContentType = ExtractContentType(text, itemData->startIndex, itemData->startIndex + itemData->dataLength); + auto currentContentType = ExtractContentType(text, itemData->startOffset, itemData->startOffset + itemData->dataLength); auto bufferView = obj->GetData().GetEntireFile(); - BufferView itemBufferView(bufferView.GetData() + itemData->startIndex, itemData->dataLength); + BufferView itemBufferView(bufferView.GetData() + itemData->startOffset, itemData->dataLength); if (!itemData->leafNode) { auto bufferName = GetGViewFileName(currentContentType, itemData->identifier); @@ -116,8 +136,16 @@ void EMLFile::OnOpenItem(std::u16string_view path, AppCUI::Controls::TreeViewIte if (encodingHeader != headerFields.end()) { Buffer output; + if (encodingHeader->second == u"base64") { - if (GView::Unpack::Base64::Decode(itemBufferView, output)) { + bool hasWarning; + String warningMessage; + + if (GView::Unpack::Base64::Decode(itemBufferView, output, hasWarning, warningMessage)) { + if (hasWarning) { + AppCUI::Dialogs::MessageBox::ShowError("Warning!", warningMessage); + } + GView::App::OpenBuffer(output, bufferName, path, GView::App::OpenMethod::BestMatch); } else { AppCUI::Dialogs::MessageBox::ShowError("Error!", "Malformed base64 buffer!"); @@ -140,14 +168,14 @@ void EMLFile::OnOpenItem(std::u16string_view path, AppCUI::Controls::TreeViewIte uint32 EMLFile::ParseHeaderFieldBody(TextParser text, uint32 start) { - uint32 end = text.ParseUntillText(start, "\r\n", false); + uint32 end = text.ParseUntilText(start, "\r\n", false); while (end + 2 < text.Len()) { auto ch = text[end + 2]; if (ch != ' ' && ch != '\t') break; - end = text.ParseUntillText(end + 2, "\r\n", false); + end = text.ParseUntilText(end + 2, "\r\n", false); } return end; @@ -164,24 +192,9 @@ void EMLFile::ParseHeaders(GView::View::LexicalViewer::TextParser text, uint32& break; } - // header-field name - end = text.ParseUntillText(start, ":", false); - - std::u16string fieldName(text.GetSubString(start, end)); - - // ltrim - start = end = text.ParseSpace(end + 1, SpaceType::All); - - // header-field body - end = ParseHeaderFieldBody(text, start); - - std::u16string fieldBody(text.GetSubString(start, end)); - - // remove CRLF - size_t pos = 0; - while ((pos = fieldBody.find(u"\r\n", pos)) != std::u16string::npos) - fieldBody.replace(pos, 2, u""); - + std::u16string fieldName, fieldBody; + ExtractFieldNameAndBody(text, start, end, fieldName, fieldBody); + if (fieldName == u"Content-Type") { contentType = fieldBody; } @@ -255,7 +268,7 @@ void EMLFile::ParsePart(GView::View::LexicalViewer::TextParser text, uint32 star TextParser contentTypeParser(contentType); - uint32 typeEnd = contentTypeParser.ParseUntillText(0, "/", false); + uint32 typeEnd = contentTypeParser.ParseUntilText(0, "/", false); CHECKRET(typeEnd != contentTypeParser.Len(), ""); u16string_view type = contentTypeParser.GetSubString(0, typeEnd); @@ -272,9 +285,9 @@ void EMLFile::ParsePart(GView::View::LexicalViewer::TextParser text, uint32 star if (contentTypeParser[boundaryStart] == '"') { // the boundary is enclosed in quotes boundaryStart++; - boundaryEnd = contentTypeParser.ParseUntillText(boundaryStart, "\"", false); + boundaryEnd = contentTypeParser.ParseUntilText(boundaryStart, "\"", false); } else { - boundaryEnd = contentTypeParser.ParseUntillText(boundaryStart, ";", false); + boundaryEnd = contentTypeParser.ParseUntilText(boundaryStart, ";", false); } boundary = "--"; @@ -289,15 +302,14 @@ void EMLFile::ParsePart(GView::View::LexicalViewer::TextParser text, uint32 star partStart = text.ParseUntilNextCharacterAfterText(partStart, boundary, false); partStart = text.ParseSpace(partStart, SpaceType::All); - if (text.ParseUntillText(partStart, "--", false) == partStart) { + if (text.ParseUntilText(partStart, "--", false) == partStart) { // end of part break; } - partEnd = text.ParseUntillText(partStart, boundary, false); + partEnd = text.ParseUntilText(partStart, boundary, false); - // TODO: get the parent's index - items.emplace_back(EML_Item_Record{ .parentStartIndex = 0, .startIndex = partStart, .dataLength = partEnd - partStart, .leafNode = false }); + items.emplace_back(EML_Item_Record{.startOffset = partStart, .dataLength = partEnd - partStart, .partIndex = (uint32) items.size(), .leafNode = false }); partStart = partEnd; } while (partEnd < end); @@ -306,14 +318,13 @@ void EMLFile::ParsePart(GView::View::LexicalViewer::TextParser text, uint32 star } if (type == u"message") { - items.emplace_back(EML_Item_Record{ .parentStartIndex = 0, .startIndex = start, .dataLength = end - start, .leafNode = false }); + items.emplace_back(EML_Item_Record{.startOffset = start, .dataLength = end - start, .partIndex = (uint32) items.size(), .leafNode = false }); return; } // base case // simple type (text|application|...) - items.emplace_back(EML_Item_Record{ .parentStartIndex = 0, .startIndex = start, .dataLength = end - start, .leafNode = true }); - + items.emplace_back(EML_Item_Record{ .startOffset = start, .dataLength = end - start, .partIndex = (uint32) items.size(), .leafNode = true }); return; } } // namespace GView::Type::EML diff --git a/Types/EML/src/PanelInformation.cpp b/Types/EML/src/PanelInformation.cpp index f3003b23..8e4fcabc 100644 --- a/Types/EML/src/PanelInformation.cpp +++ b/Types/EML/src/PanelInformation.cpp @@ -5,7 +5,7 @@ using namespace AppCUI::Controls; Panels::Information::Information(Reference _eml) : TabPage("&Information") { - eml = _eml; + eml = _eml; general = Factory::ListView::Create(this, "x:0,y:0,w:100%,h:10", { "n:Field,w:12", "n:Value,w:100" }, ListViewFlags::None); headers = Factory::ListView::Create(this, "x:0,y:10,w:100%,h:20", { "n:Field,w:12", "n:Value,w:10000" }, ListViewFlags::None); @@ -25,8 +25,7 @@ void Panels::Information::UpdateGeneralInformation() } headers->AddItem("Headers"); - for (const auto& itr : eml->headerFields) - { + for (const auto& itr : eml->headerFields) { headers->AddItem({ itr.first, itr.second }); } } @@ -36,8 +35,8 @@ void Panels::Information::UpdateIssues() } void Panels::Information::RecomputePanelsPositions() { - int w = this->GetWidth(); - int h = this->GetHeight(); + int w = this->GetWidth(); + int h = this->GetHeight(); if (!general.IsValid()) return; diff --git a/Types/EML/src/eml.cpp b/Types/EML/src/eml.cpp index ed00cc61..2d6f8ecb 100644 --- a/Types/EML/src/eml.cpp +++ b/Types/EML/src/eml.cpp @@ -46,19 +46,16 @@ void CreateContainerView(Reference win, Reference< const auto& headers = eml->GetHeaders(); for (const auto& [name, value] : headers) { - if (name == u"Cc") // TODO: to be removed when issues https://github.com/gdt050579/GView/issues/301 is fixed - continue; - std::string nameStr = toUTF8(name); settings.AddProperty(nameStr, value); } settings.SetIcon(EML_ICON); settings.SetColumns({ - "n:&Identifier,a:l,w:30", - "n:&Content-Type,a:c,w:40", - "n:&Size,a:c,w:15", - "n:&OffsetInFile,a:c,w:15", + "n:&Index,a:r,w:50", + "n:&Content-Type,a:r,w:50", + "n:&Size,a:r,w:20", + "n:&Offset,a:r,w:20", }); settings.SetEnumerateCallback(win->GetObject()->GetContentType().ToObjectRef()); diff --git a/Types/INI/src/INIFile.cpp b/Types/INI/src/INIFile.cpp index aa5dec19..d5182105 100644 --- a/Types/INI/src/INIFile.cpp +++ b/Types/INI/src/INIFile.cpp @@ -143,7 +143,7 @@ struct ParserData switch (chType) { case CharType::Comment: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add( TokenType::Comment, pos, @@ -187,7 +187,7 @@ struct ParserData state = ParserState::ExpectEqual; break; default: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add(TokenType::Invalid, pos, next, TokenColor::Word) .SetError("Invalid character (expecting either a key or a section)"); pos = next; @@ -200,7 +200,7 @@ struct ParserData switch (chType) { case CharType::Comment: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add( TokenType::Comment, pos, @@ -220,7 +220,7 @@ struct ParserData state = ParserState::ExpectKeyValueOrSection; break; case CharType::Invalid: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add(TokenType::Invalid, pos, next, TokenColor::Word) .SetError("Invalid character (expecting either a avlue or an array)"); pos = next; @@ -256,7 +256,7 @@ struct ParserData switch (chType) { case CharType::Comment: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add( TokenType::Comment, pos, @@ -281,7 +281,7 @@ struct ParserData state = ParserState::ExpectValueOrArray; break; default: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add(TokenType::Invalid, pos, next, TokenColor::Word).SetError("Invalid character (expecting either ':' or '=')"); pos = next; state = ParserState::ExpectKeyValueOrSection; @@ -294,7 +294,7 @@ struct ParserData switch (chType) { case CharType::Comment: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add( TokenType::Comment, pos, @@ -348,7 +348,7 @@ struct ParserData switch (chType) { case CharType::Comment: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add( TokenType::Comment, pos, @@ -367,7 +367,7 @@ struct ParserData state = ParserState::ExpectCommaOrEndOfArray; break; case CharType::Invalid: - next = text.ParseUntillEndOfLine(pos); + next = text.ParseUntilEndOfLine(pos); tokenList.Add(TokenType::Invalid, pos, next, TokenColor::Word) .SetError("Invalid character (expecting either a avlue or an array)"); pos = next; diff --git a/Types/JS/src/JSFile.cpp b/Types/JS/src/JSFile.cpp index b0c95c7d..fac958e8 100644 --- a/Types/JS/src/JSFile.cpp +++ b/Types/JS/src/JSFile.cpp @@ -724,7 +724,7 @@ uint32 JSFile::TokenizeList(const TextParser& text, TokensList& tokenList, uint3 } uint32 JSFile::TokenizePreprocessDirective(const TextParser& text, TokensList& list, BlocksList& blocks, uint32 pos) { - auto eol = text.ParseUntillEndOfLine(pos); + auto eol = text.ParseUntilEndOfLine(pos); auto start = pos; pos = text.ParseSpace(pos + 1, SpaceType::SpaceAndTabs); if ((CharType::GetCharType(text[pos])) != CharType::Word) @@ -834,7 +834,7 @@ void JSFile::Tokenize(uint32 start, uint32 end, const TextParser& text, TokensLi idx = text.ParseSpace(idx, SpaceType::SpaceAndTabs); break; case CharType::SingleLineComment: - next = text.ParseUntillEndOfLine(idx); + next = text.ParseUntilEndOfLine(idx); tokenList.Add( TokenType::Comment, idx, diff --git a/Types/VBA/src/VBAFile.cpp b/Types/VBA/src/VBAFile.cpp index 92317d1c..8db15688 100644 --- a/Types/VBA/src/VBAFile.cpp +++ b/Types/VBA/src/VBAFile.cpp @@ -18,10 +18,114 @@ void VBAFile::GetTokenIDStringRepresentation(uint32 id, AppCUI::Utils::String& s CHECKRET(str.SetFormat("Unknown: 0x%08X", id), ""); } + +uint32 ParseString(GView::View::LexicalViewer::TextParser text, uint32 index) +{ + uint32 end = text.Parse(index + 1, [](char16 c) { return c != '"'; }); + return end + 1; +} + +UnicodeStringBuilder KEYWORDS[] = { UnicodeStringBuilder("Attribute"), UnicodeStringBuilder("Sub"), UnicodeStringBuilder("Private"), + UnicodeStringBuilder("Public"), UnicodeStringBuilder("As"), UnicodeStringBuilder("Dim"), + UnicodeStringBuilder("End"), UnicodeStringBuilder("Const"), + UnicodeStringBuilder("ByVal"), UnicodeStringBuilder("Set"), UnicodeStringBuilder("While"), + UnicodeStringBuilder("Wend"), UnicodeStringBuilder("If"), UnicodeStringBuilder("Then") }; + +UnicodeStringBuilder KEYWORDS2[] = { UnicodeStringBuilder("True"), UnicodeStringBuilder("False") }; + +const char operators[] = "=(),._&$+-*/<>#\\:"; + void VBAFile::AnalyzeText(GView::View::LexicalViewer::SyntaxManager& syntax) { - syntax.tokens.Add(1, 0, 5, TokenColor::Keyword); - syntax.tokens.Add(1, 5, 10, TokenColor::String, TokenAlignament::StartsOnNewLine); + uint32 start = 0; + uint32 end = 0; + + TokenAlignament presetAlignament = TokenAlignament::None; + + while (start < syntax.text.Len()) { + + auto c = syntax.text[start]; + + if (c == ' ') { + end = syntax.text.ParseSpace(end, SpaceType::Space); + if ((uint32) presetAlignament & (uint32) TokenAlignament::StartsOnNewLine) { + syntax.tokens.Add(1, start, end, TokenColor::Word, presetAlignament); + presetAlignament = TokenAlignament::None; + } + start = end; + continue; + } + + bool parseSpace = false; + if (isalpha(c)) { + end = syntax.text.Parse(start, [](char16 c) { return (bool) isalnum(c) || c == '_'; }); + + TokenColor color = TokenColor::Word; + for (auto keyword : KEYWORDS) { + if (syntax.text.GetSubString(start, end) == keyword) { + color = TokenColor::Keyword; + break; + } + } + + for (auto keyword : KEYWORDS2) { + if (syntax.text.GetSubString(start, end) == keyword) { + color = TokenColor::Keyword2; + break; + } + } + + syntax.tokens.Add(1, start, end, color, presetAlignament); + parseSpace = true; + } + + if (isdigit(c)) { + end = syntax.text.Parse(start, [](char16 c) { return (bool) isdigit(c); }); + syntax.tokens.Add(1, start, end, TokenColor::Number, presetAlignament); + parseSpace = true; + } + + for (char op : operators) { + if (c == op) { + end = start + 1; + syntax.tokens.Add(1, start, end, TokenColor::Operator, presetAlignament); + parseSpace = true; + break; + } + } + + if (c == '"') { + end = ParseString(syntax.text, start); + syntax.tokens.Add(1, start, end, TokenColor::String, presetAlignament); + parseSpace = true; + } + + if (parseSpace) { + start = syntax.text.ParseSpace(end, SpaceType::Space); + + if (start > end) { + presetAlignament = TokenAlignament::AddSpaceBefore; + } else { + presetAlignament = TokenAlignament::None; + } + continue; + } + + if (c == '\r' || c == '\n') { + end = syntax.text.ParseUntilStartOfNextLine(start); + presetAlignament = TokenAlignament::StartsOnNewLine; + start = end; + continue; + } + + if (c == '\'') { + end = syntax.text.ParseUntilEndOfLine(start); + syntax.tokens.Add(1, start, end, TokenColor::Comment, presetAlignament | TokenAlignament::NewLineAfter); + start = syntax.text.ParseUntilStartOfNextLine(end); + continue; + } + break; + } } bool VBAFile::StringToContent(std::u16string_view string, AppCUI::Utils::UnicodeStringBuilder& result)