/* nvramparser.cpp
 
 Copyright (c) 2016, 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
 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.
 
 */

#ifdef U_ENABLE_NVRAM_PARSING_SUPPORT
#include <map>

#include "nvramparser.h"
#include "parsingdata.h"
#include "ustring.h"
#include "utility.h"
#include "nvram.h"
#include "ffs.h"
#include "intel_microcode.h"

#include "umemstream.h"
#include "kaitai/kaitaistream.h"
#include "generated/ami_nvar.h"

USTATUS NvramParser::parseNvarStore(const UModelIndex & index)
{
    // Sanity check
    if (!index.isValid())
        return U_INVALID_PARAMETER;

    UByteArray nvar = model->body(index);

    // Nothing to parse in an empty store
    if (nvar.isEmpty())
        return U_SUCCESS;

    try {
        const UINT32 localOffset = (UINT32)model->header(index).size();
        umemstream is(nvar.constData(), nvar.size());
        kaitai::kstream ks(&is);
        ami_nvar_t parsed(&ks);

        UINT16 guidsInStore = 0;
        UINT32 currentEntryIndex = 0;
        for (const auto & entry : *parsed.entries()) {
            UINT8 subtype = Subtypes::FullNvarEntry;
            UString name;
            UString text;
            UString info;
            UString guid;
            UByteArray header;
            UByteArray body;
            UByteArray tail;

            // This is a terminating entry, needs special processing
            if (entry->_is_null_signature_rest()) {
                UINT32 guidAreaSize = guidsInStore * sizeof(EFI_GUID);
                UINT32 unparsedSize = (UINT32)nvar.size() - entry->offset() - guidAreaSize;

                // Check if the data left is a free space or a padding
                UByteArray padding = nvar.mid(entry->offset(), unparsedSize);

                // Get info
                UString info = usprintf("Full size: %Xh (%u)", (UINT32)padding.size(), (UINT32)padding.size());

                if ((UINT32)padding.count(0xFF) == unparsedSize) { // Free space
                    // Add tree item
                    model->addItem(localOffset + entry->offset(), Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
                }
                else {
                    // Nothing is parsed yet, but the file is not empty
                    if (entry->offset() == 0) {
                        msg(usprintf("%s: file can't be parsed as NVAR variable store", __FUNCTION__), index);
                        return U_SUCCESS;
                    }

                    // Add tree item
                    model->addItem(localOffset + entry->offset(), Types::Padding, getPaddingType(padding), UString("Padding"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
                }

                // Add GUID store area
                UByteArray guidArea = nvar.right(guidAreaSize);
                // Get info
                name = UString("GUID store");
                info = usprintf("Full size: %Xh (%u)\nGUIDs in store: %u",
                                (UINT32)guidArea.size(), (UINT32)guidArea.size(),
                                guidsInStore);
                // Add tree item
                model->addItem((UINT32)(localOffset + entry->offset() + padding.size()), Types::NvarGuidStore, 0, name, UString(), info, UByteArray(), guidArea, UByteArray(), Fixed, index);

                return U_SUCCESS;
            }

            // This is a normal entry
            const auto entry_body = entry->body();

            // Set default next to predefined last value
            NVAR_ENTRY_PARSING_DATA pdata = {};
            pdata.emptyByte = 0xFF;
            pdata.next = 0xFFFFFF;
            pdata.isValid = TRUE;

            // Check for invalid entry
            if (!entry->attributes()->valid()) {
                subtype = Subtypes::InvalidNvarEntry;
                name = UString("Invalid");
                pdata.isValid = FALSE;
                goto processing_done;
            }

            // Check for link entry
            if (entry->next() != 0xFFFFFF) {
                subtype = Subtypes::LinkNvarEntry;
                pdata.next = (UINT32)entry->next();
            }

            // Check for data-only entry (nameless and GUIDless entry or link)
            if (entry->attributes()->data_only()) {
                // Search backwards for a previous entry with a link to this variable
                UModelIndex prevEntryIndex;
                if (currentEntryIndex > 0) {
                    for (UINT32 i = currentEntryIndex - 1; i > 0; i--) {
                        const auto & previousEntry = parsed.entries()->at(i);

                        if (previousEntry == entry)
                            break;

                        if ((UINT32)previousEntry->next() + (UINT32)previousEntry->offset() == (UINT32)entry->offset()) { // Previous link is present and valid
                            prevEntryIndex = index.model()->index(i, 0, index);
                            // Make sure that we are linking to a valid entry
                            NVAR_ENTRY_PARSING_DATA pd = readUnaligned((NVAR_ENTRY_PARSING_DATA*)model->parsingData(prevEntryIndex).constData());
                            if (!pd.isValid) {
                                prevEntryIndex = UModelIndex();
                            }
                            break;
                        }
                    }
                }
                // Check if the link is valid
                if (prevEntryIndex.isValid()) {
                    // Use the name and text of the previous entry
                    name = model->name(prevEntryIndex);
                    text = model->text(prevEntryIndex);

                    if (entry->next() == 0xFFFFFF)
                        subtype = Subtypes::DataNvarEntry;
                }
                else {
                    subtype = Subtypes::InvalidLinkNvarEntry;
                    name = UString("InvalidLink");
                    pdata.isValid = FALSE;
                }
                goto processing_done;
            }

            // Obtain text
            if (!entry_body->_is_null_ascii_name()) {
                text = entry_body->ascii_name().c_str();
            }
            else if (!entry_body->_is_null_ucs2_name()) {
                UByteArray temp;
                for (const auto & ch : *entry_body->ucs2_name()->ucs2_chars()) {
                    temp += UByteArray((const char*)&ch, sizeof(ch));
                }
                text = uFromUcs2(temp.constData());
            }

            // Obtain GUID
            if (!entry_body->_is_null_guid()) { // GUID is stored in the entry itself
                const EFI_GUID g = readUnaligned((EFI_GUID*)entry_body->guid().c_str());
                name = guidToUString(g);
                guid = guidToUString(g, false);
            }
            else { // GUID is stored in GUID store at the end of the NVAR store
                // Grow the GUID store if needed
                if (guidsInStore < entry_body->guid_index() + 1)
                    guidsInStore = entry_body->guid_index() + 1;

                // The list begins at the end of the store and goes backwards
                const EFI_GUID g = readUnaligned((EFI_GUID*)(nvar.constData() + nvar.size()) - (entry_body->guid_index() + 1));
                name = guidToUString(g);
                guid = guidToUString(g, false);
            }

processing_done:
            // This feels hacky, but I haven't found a way to ask Kaitai for raw bytes
            header = nvar.mid(entry->offset(), sizeof(NVAR_ENTRY_HEADER) + entry_body->data_start_offset());
            body = nvar.mid(entry->offset() + sizeof(NVAR_ENTRY_HEADER) + entry_body->data_start_offset(), entry_body->data_size());
            tail = nvar.mid(entry->end_offset() - entry_body->extended_header_size(), entry_body->extended_header_size());

            // Add GUID info for valid entries
            if (!guid.isEmpty())
                info += UString("Variable GUID: ") + guid + "\n";

            // Add GUID index information
            if (!entry_body->_is_null_guid_index())
                info += usprintf("GUID index: %u\n", entry_body->guid_index());

            // Add header, body and extended data info
            info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nTail size: %Xh (%u)",
                             entry->size(), entry->size(),
                             (UINT32)header.size(), (UINT32)header.size(),
                             (UINT32)body.size(), (UINT32)body.size(),
                             (UINT32)tail.size(), (UINT32)tail.size());

            // Add attributes info
            const NVAR_ENTRY_HEADER entryHeader = readUnaligned((NVAR_ENTRY_HEADER*)header.constData());
            info += usprintf("\nAttributes: %02Xh", entryHeader.Attributes);

            // Translate attributes to text
            if (entryHeader.Attributes != 0x00 && entryHeader.Attributes != 0xFF)
                info += UString(" (") + nvarAttributesToUString(entryHeader.Attributes) + UString(")");

            // Add next node info
            if (entry->next() != 0xFFFFFF)
                info += usprintf("\nNext node at offset: %Xh", localOffset + entry->offset() + (UINT32)entry->next());

            // Add extended header info
            if (entry_body->extended_header_size() > 0) {
                info += usprintf("\nExtended header size: %Xh (%u)",
                                 entry_body->extended_header_size(), entry_body->extended_header_size());

                const UINT8 extendedAttributes = *tail.constData();
                info += usprintf("\nExtended attributes: %02Xh (", extendedAttributes) + nvarExtendedAttributesToUString(extendedAttributes) + UString(")");

                // Add checksum
                if (!entry_body->_is_null_extended_header_checksum()) {
                    UINT8 calculatedChecksum = 0;
                    UByteArray wholeBody = body + tail;

                    // Include entry body
                    UINT8* start = (UINT8*)wholeBody.constData();
                    for (UINT8* p = start; p < start + wholeBody.size(); p++) {
                        calculatedChecksum += *p;
                    }
                    // Include entry size and flags
                    start = (UINT8*)&entryHeader.Size;
                    for (UINT8*p = start; p < start + sizeof(UINT16); p++) {
                        calculatedChecksum += *p;
                    }
                    // Include entry attributes
                    calculatedChecksum += entryHeader.Attributes;
                    info += usprintf("\nChecksum: %02Xh, ", entry_body->extended_header_checksum())
                     + (calculatedChecksum ? usprintf(", invalid, should be %02Xh", 0x100 - calculatedChecksum) : UString(", valid"));
                }

                // Add timestamp
                if (!entry_body->_is_null_extended_header_timestamp())
                    info += usprintf("\nTimestamp: %" PRIX64 "h", entry_body->extended_header_timestamp());

                // Add hash
                if (!entry_body->_is_null_extended_header_hash()) {
                    UByteArray hash = UByteArray(entry_body->extended_header_hash().c_str(), entry_body->extended_header_hash().size());
                    info += UString("\nHash: ") + UString(hash.toHex().constData());
                }
            }

            // Add tree item
            UModelIndex varIndex = model->addItem(localOffset + entry->offset(), Types::NvarEntry, subtype, name, text, info, header, body, tail, Fixed, index);
            currentEntryIndex++;

            // Set parsing data
            model->setParsingData(varIndex, UByteArray((const char*)&pdata, sizeof(pdata)));

            // Try parsing the entry data as NVAR storage if it begins with NVAR signature
            if ((subtype == Subtypes::DataNvarEntry || subtype == Subtypes::FullNvarEntry)
                && body.size() >= 4 && readUnaligned((const UINT32*)body.constData()) == NVRAM_NVAR_ENTRY_SIGNATURE)
                (void)parseNvarStore(varIndex);
        }
    }
    catch (...) {
        msg(usprintf("%s: unable to parse AMI NVAR storage", __FUNCTION__), index);
        return U_INVALID_STORE;
    }

    return U_SUCCESS;
}

USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
{
    // Sanity check
    if (!index.isValid())
        return U_INVALID_PARAMETER;
    
    // Obtain required fields from parsing data
    UINT8 emptyByte = 0xFF;
    if (model->hasEmptyParsingData(index) == false) {
        UByteArray data = model->parsingData(index);
        const VOLUME_PARSING_DATA* pdata = (const VOLUME_PARSING_DATA*)data.constData();
        emptyByte = pdata->emptyByte;
    }
    
    // Get local offset
    UINT32 localOffset = (UINT32)model->header(index).size();
    
    // Get item data
    UByteArray data = model->body(index);
    
    // Search for first store
    USTATUS result;
    UINT32 prevStoreOffset;
    result = findNextStore(index, data, localOffset, 0, prevStoreOffset);
    if (result)
        return result;
    
    // First store is not at the beginning of volume body
    UString name;
    UString info;
    if (prevStoreOffset > 0) {
        // Get info
        UByteArray padding = data.left(prevStoreOffset);
        name = UString("Padding");
        info = usprintf("Full size: %Xh (%u)", (UINT32)padding.size(), (UINT32)padding.size());
        
        // Add tree item
        model->addItem(localOffset, Types::Padding, getPaddingType(padding), name, UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
    }
    
    // Search for and parse all stores
    UINT32 storeOffset = prevStoreOffset;
    UINT32 prevStoreSize = 0;
    
    while (!result) {
        // Padding between stores
        if (storeOffset > prevStoreOffset + prevStoreSize) {
            UINT32 paddingOffset = prevStoreOffset + prevStoreSize;
            UINT32 paddingSize = storeOffset - paddingOffset;
            UByteArray padding = data.mid(paddingOffset, paddingSize);
            
            // Get info
            name = UString("Padding");
            info = usprintf("Full size: %Xh (%u)", (UINT32)padding.size(), (UINT32)padding.size());
            
            // Add tree item
            model->addItem(localOffset + paddingOffset, Types::Padding, getPaddingType(padding), name, UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
        }
        
        // Get store size
        UINT32 storeSize = 0;
        result = getStoreSize(data, storeOffset, storeSize);
        if (result) {
            msg(usprintf("%s: getStoreSize failed with error ", __FUNCTION__) + errorCodeToUString(result), index);
            return result;
        }
        
        // Check that current store is fully present in input
        if (storeSize > (UINT32)data.size() || storeOffset + storeSize > (UINT32)data.size()) {
            // Mark the rest as padding and finish parsing
            UByteArray padding = data.mid(storeOffset);
            
            // Get info
            name = UString("Padding");
            info = usprintf("Full size: %Xh (%u)", (UINT32)padding.size(), (UINT32)padding.size());
            
            // Add tree item
            UModelIndex paddingIndex = model->addItem(localOffset + storeOffset, Types::Padding, getPaddingType(padding), name, UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
            msg(usprintf("%s: one of stores inside overlaps the end of data", __FUNCTION__), paddingIndex);
            
            // Update variables
            prevStoreOffset = storeOffset;
            prevStoreSize = (UINT32)padding.size();
            break;
        }
        
        // Parse current store header
        UModelIndex storeIndex;
        UByteArray store = data.mid(storeOffset, storeSize);
        result = parseStoreHeader(store, localOffset + storeOffset, index, storeIndex);
        if (result)
            msg(usprintf("%s: store header parsing failed with error ", __FUNCTION__) + errorCodeToUString(result), index);
        
        // Go to next store
        prevStoreOffset = storeOffset;
        prevStoreSize = storeSize;
        result = findNextStore(index, data, localOffset, storeOffset + prevStoreSize, storeOffset);
    }
    
    // Padding/free space at the end
    storeOffset = prevStoreOffset + prevStoreSize;
    if ((UINT32)data.size() > storeOffset) {
        UByteArray padding = data.mid(storeOffset);
        // Add info
        info = usprintf("Full size: %Xh (%u)", (UINT32)padding.size(), (UINT32)padding.size());
        
        if (padding.count(emptyByte) == padding.size()) { // Free space
            // Add tree item
            model->addItem(localOffset + storeOffset, Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
        }
        else {
            // Nothing is parsed yet, but the file is not empty
            if (!storeOffset) {
                msg(usprintf("%s: can't be parsed as NVRAM volume", __FUNCTION__), index);
                return U_SUCCESS;
            }
            
            // Add tree item
            model->addItem(localOffset + storeOffset, Types::Padding, getPaddingType(padding), UString("Padding"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
        }
    }
    
    // Parse bodies
    for (int i = 0; i < model->rowCount(index); i++) {
        UModelIndex current = index.model()->index(i, 0, index);
        
        switch (model->type(current)) {
            case Types::FdcStore:
                parseFdcStoreBody(current);
                break;
            case Types::VssStore:
                parseVssStoreBody(current, 0);
                break;
            case Types::Vss2Store:
                parseVssStoreBody(current, 4);
                break;
            case Types::FsysStore:
                parseFsysStoreBody(current);
                break;
            case Types::EvsaStore:
                parseEvsaStoreBody(current);
                break;
            case Types::FlashMapStore:
                parseFlashMapBody(current);
                break;
            default:
                // Ignore unknown!
                break;
        }
    }
    
    return U_SUCCESS;
}

USTATUS NvramParser::findNextStore(const UModelIndex & index, const UByteArray & volume, const UINT32 localOffset, const UINT32 storeOffset, UINT32 & nextStoreOffset)
{
    UINT32 dataSize = (UINT32)volume.size();
    
    if (dataSize < sizeof(UINT32))
        return U_STORES_NOT_FOUND;
    
    // TODO: add checks for restSize
    // TODO: remove misaligned access by doing the signature checks differently, the current way is UB is C++
    // TODO: rewrite this all as Kaitai-based parser
    UINT32 offset = storeOffset;
    for (; offset < dataSize - sizeof(UINT32); offset++) {
        const UINT32* currentPos = (const UINT32*)(volume.constData() + offset);
        if (readUnaligned(currentPos) == NVRAM_VSS_STORE_SIGNATURE 
            || readUnaligned(currentPos) == NVRAM_APPLE_SVS_STORE_SIGNATURE 
            || readUnaligned(currentPos) == NVRAM_APPLE_NSS_STORE_SIGNATURE) { // $VSS, $SVS or $NSS signatures found, perform checks
            const VSS_VARIABLE_STORE_HEADER* vssHeader = (const VSS_VARIABLE_STORE_HEADER*)currentPos;
            if (vssHeader->Format != NVRAM_VSS_VARIABLE_STORE_FORMATTED) {
                msg(usprintf("%s: VSS store candidate at offset %Xh skipped, has invalid format %02Xh", __FUNCTION__, localOffset + offset, vssHeader->Format), index);
                continue;
            }
            if (vssHeader->Size == 0 || vssHeader->Size == 0xFFFFFFFF) {
                msg(usprintf("%s: VSS store candidate at offset %Xh skipped, has invalid size %Xh", __FUNCTION__, localOffset + offset, vssHeader->Size), index);
                continue;
            }
            // All checks passed, store found
            break;
        }
        else if (readUnaligned(currentPos) == NVRAM_VSS2_AUTH_VAR_KEY_DATABASE_GUID_PART1 
            || readUnaligned(currentPos) == NVRAM_VSS2_STORE_GUID_PART1) { // VSS2 store signatures found, perform checks
            UByteArray guid = UByteArray(volume.constData() + offset, sizeof(EFI_GUID));
            if (guid != NVRAM_VSS2_AUTH_VAR_KEY_DATABASE_GUID && guid != NVRAM_VSS2_STORE_GUID) // Check the whole signature
                continue;
            
            const VSS2_VARIABLE_STORE_HEADER* vssHeader = (const VSS2_VARIABLE_STORE_HEADER*)currentPos;
            if (vssHeader->Format != NVRAM_VSS_VARIABLE_STORE_FORMATTED) {
                msg(usprintf("%s: VSS2 store candidate at offset %Xh skipped, has invalid format %02Xh", __FUNCTION__, localOffset + offset, vssHeader->Format), index);
                continue;
            }
            if (vssHeader->Size == 0 || vssHeader->Size == 0xFFFFFFFF) {
                msg(usprintf("%s: VSS2 store candidate at offset %Xh skipped, has invalid size %Xh", __FUNCTION__, localOffset + offset, vssHeader->Size), index);
                continue;
            }
            // All checks passed, store found
            break;
        }
        else if (readUnaligned(currentPos) == NVRAM_FDC_VOLUME_SIGNATURE) { // FDC signature found
            const FDC_VOLUME_HEADER* fdcHeader = (const FDC_VOLUME_HEADER*)currentPos;
            if (fdcHeader->Size == 0 || fdcHeader->Size == 0xFFFFFFFF) {
                msg(usprintf("%s: FDC store candidate at offset %Xh skipped, has invalid size %Xh", __FUNCTION__, localOffset + offset, fdcHeader->Size), index);
                continue;
            }
            // All checks passed, store found
            break;
        }
        else if (readUnaligned(currentPos) == NVRAM_APPLE_FSYS_STORE_SIGNATURE 
            || readUnaligned(currentPos) == NVRAM_APPLE_GAID_STORE_SIGNATURE) { // Fsys or Gaid signature found
            const APPLE_FSYS_STORE_HEADER* fsysHeader = (const APPLE_FSYS_STORE_HEADER*)currentPos;
            if (fsysHeader->Size == 0 || fsysHeader->Size == 0xFFFF) {
                msg(usprintf("%s: Fsys store candidate at offset %Xh skipped, has invalid size %Xh", __FUNCTION__, localOffset + offset, fsysHeader->Size), index);
                continue;
            }
            // All checks passed, store found
            break;
        }
        else if (readUnaligned(currentPos) == NVRAM_EVSA_STORE_SIGNATURE) { //EVSA signature found
            if (offset < sizeof(UINT32))
                continue;
            
            const EVSA_STORE_ENTRY* evsaHeader = (const EVSA_STORE_ENTRY*)(currentPos - 1);
            if (evsaHeader->Header.Type != NVRAM_EVSA_ENTRY_TYPE_STORE) {
                msg(usprintf("%s: EVSA store candidate at offset %Xh skipped, has invalid type %02Xh", __FUNCTION__, localOffset + offset - 4, evsaHeader->Header.Type), index);
                continue;
            }
            if (evsaHeader->StoreSize == 0 || evsaHeader->StoreSize == 0xFFFFFFFF) {
                msg(usprintf("%s: EVSA store candidate at offset %Xh skipped, has invalid size %Xh", __FUNCTION__, localOffset + offset, evsaHeader->StoreSize), index);
                continue;
            }
            // All checks passed, store found
            offset -= sizeof(UINT32);
            break;
        }
        else if (readUnaligned(currentPos) == NVRAM_MAIN_STORE_VOLUME_GUID_DATA1 
            || readUnaligned(currentPos) == EDKII_WORKING_BLOCK_SIGNATURE_GUID_DATA1) { // Possible FTW block signature found
            UByteArray guid = UByteArray(volume.constData() + offset, sizeof(EFI_GUID));
            if (guid != NVRAM_MAIN_STORE_VOLUME_GUID && guid != EDKII_WORKING_BLOCK_SIGNATURE_GUID && guid != VSS2_WORKING_BLOCK_SIGNATURE_GUID) // Check the whole signature
                continue;
            
            // Detect header variant based on WriteQueueSize
            const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32* ftwHeader = (const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32*)currentPos;
            if (ftwHeader->WriteQueueSize % 0x10 == 0x04) { // Header with 32 bit WriteQueueSize
                if (ftwHeader->WriteQueueSize == 0 || ftwHeader->WriteQueueSize == 0xFFFFFFFF) {
                    msg(usprintf("%s: FTW block candidate at offset %Xh skipped, has invalid body size %Xh", __FUNCTION__, localOffset + offset, ftwHeader->WriteQueueSize), index);
                    continue;
                }
            }
            else if (ftwHeader->WriteQueueSize % 0x10 == 0x00) { // Header with 64 bit WriteQueueSize
                const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64* ftw64Header = (const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64*)currentPos;
                if (ftw64Header->WriteQueueSize == 0 || ftw64Header->WriteQueueSize >= 0xFFFFFFFF) {
                    msg(usprintf("%s: FTW block candidate at offset %Xh skipped, has invalid body size %" PRIX64 "h", __FUNCTION__, localOffset + offset, ftw64Header->WriteQueueSize), index);
                    continue;
                }
            }
            else // Unknown header
                continue;
            
            // All checks passed, store found
            break;
        }
        else if (readUnaligned(currentPos) == NVRAM_PHOENIX_FLASH_MAP_SIGNATURE_PART1) {// Phoenix SCT flash map
            UByteArray signature = UByteArray(volume.constData() + offset, NVRAM_PHOENIX_FLASH_MAP_SIGNATURE_LENGTH);
            if (signature != NVRAM_PHOENIX_FLASH_MAP_SIGNATURE) // Check the whole signature
                continue;
            
            // All checks passed, store found
            break;
        }
        else if (readUnaligned(currentPos) == NVRAM_PHOENIX_CMDB_HEADER_SIGNATURE) { // Phoenix SCT CMDB store
            const PHOENIX_CMDB_HEADER* cmdbHeader = (const PHOENIX_CMDB_HEADER*)currentPos;
            
            // Check size
            if (cmdbHeader->HeaderSize != sizeof(PHOENIX_CMDB_HEADER))
                continue;
            
            // All checks passed, store found
            break;
        }
        else if (readUnaligned(currentPos) == INTEL_MICROCODE_HEADER_VERSION_1) {// Intel microcode
            const INTEL_MICROCODE_HEADER* ucodeHeader = (const INTEL_MICROCODE_HEADER*)currentPos;
            
            // TotalSize is greater then DataSize and is multiple of 1024
            if (FALSE == ffsParser->microcodeHeaderValid(ucodeHeader)) {
                continue;
            }
            
            // All checks passed, store found
            break;
        }
        else if (readUnaligned(currentPos) == OEM_ACTIVATION_PUBKEY_MAGIC) { // SLIC pubkey
            if (offset < 4 * sizeof(UINT32))
                continue;
            
            const OEM_ACTIVATION_PUBKEY* pubkeyHeader = (const OEM_ACTIVATION_PUBKEY*)(currentPos - 4);
            // Check type
            if (pubkeyHeader->Type != OEM_ACTIVATION_PUBKEY_TYPE)
                continue;
            
            // All checks passed, store found
            offset -= 4 * sizeof(UINT32);
            break;
        }
        else if (readUnaligned(currentPos) == OEM_ACTIVATION_MARKER_WINDOWS_FLAG_PART1) { // SLIC marker
            if (offset < 26
                || offset >= dataSize - sizeof(UINT64)
                || *(const UINT64*)currentPos != OEM_ACTIVATION_MARKER_WINDOWS_FLAG) // Check full windows flag and structure size
                continue;
            
            const OEM_ACTIVATION_MARKER* markerHeader = (const OEM_ACTIVATION_MARKER*)(volume.constData() + offset - 26);
            // Check reserved bytes
            bool reservedBytesValid = true;
            for (UINT32 i = 0; i < sizeof(markerHeader->Reserved); i++)
                if (markerHeader->Reserved[i] != OEM_ACTIVATION_MARKER_RESERVED_BYTE) {
                    reservedBytesValid = false;
                    break;
                }
            if (!reservedBytesValid)
                continue;
            
            // All checks passed, store found
            offset -= 26;
            break;
        }
    }
    // No more stores found
    if (offset >= dataSize - sizeof(UINT32))
        return U_STORES_NOT_FOUND;
    
    nextStoreOffset = offset;
    
    return U_SUCCESS;
}

USTATUS NvramParser::getStoreSize(const UByteArray & data, const UINT32 storeOffset, UINT32 & storeSize)
{
    const UINT32* signature = (const UINT32*)(data.constData() + storeOffset);
    if (*signature == NVRAM_VSS_STORE_SIGNATURE || *signature == NVRAM_APPLE_SVS_STORE_SIGNATURE || *signature == NVRAM_APPLE_NSS_STORE_SIGNATURE) {
        const VSS_VARIABLE_STORE_HEADER* vssHeader = (const VSS_VARIABLE_STORE_HEADER*)signature;
        storeSize = vssHeader->Size;
    }
    else if (*signature == NVRAM_VSS2_AUTH_VAR_KEY_DATABASE_GUID_PART1 || *signature == NVRAM_VSS2_STORE_GUID_PART1) {
        const VSS2_VARIABLE_STORE_HEADER* vssHeader = (const VSS2_VARIABLE_STORE_HEADER*)signature;
        storeSize = vssHeader->Size;
    }
    else if (*signature == NVRAM_FDC_VOLUME_SIGNATURE) {
        const FDC_VOLUME_HEADER* fdcHeader = (const FDC_VOLUME_HEADER*)signature;
        storeSize = fdcHeader->Size;
    }
    else if (*signature == NVRAM_APPLE_FSYS_STORE_SIGNATURE || *signature == NVRAM_APPLE_GAID_STORE_SIGNATURE) {
        const APPLE_FSYS_STORE_HEADER* fsysHeader = (const APPLE_FSYS_STORE_HEADER*)signature;
        storeSize = fsysHeader->Size;
    }
    else if (*(signature + 1) == NVRAM_EVSA_STORE_SIGNATURE) {
        const EVSA_STORE_ENTRY* evsaHeader = (const EVSA_STORE_ENTRY*)signature;
        storeSize = evsaHeader->StoreSize;
    }
    else if (*signature == NVRAM_MAIN_STORE_VOLUME_GUID_DATA1 || *signature == EDKII_WORKING_BLOCK_SIGNATURE_GUID_DATA1) {
        const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32* ftwHeader = (const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32*)signature;
        if (ftwHeader->WriteQueueSize % 0x10 == 0x04) { // Header with 32 bit WriteQueueSize
            storeSize = sizeof(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32) + ftwHeader->WriteQueueSize;
        }
        else { //  Header with 64 bit WriteQueueSize
            const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64* ftw64Header = (const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64*)signature;
            storeSize = sizeof(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64) + (UINT32)ftw64Header->WriteQueueSize;
        }
    }
    else if (*signature == NVRAM_PHOENIX_FLASH_MAP_SIGNATURE_PART1) { // Phoenix SCT flash map
        const PHOENIX_FLASH_MAP_HEADER* flashMapHeader = (const PHOENIX_FLASH_MAP_HEADER*)signature;
        storeSize = sizeof(PHOENIX_FLASH_MAP_HEADER) + sizeof(PHOENIX_FLASH_MAP_ENTRY) * flashMapHeader->NumEntries;
    }
    else if (*signature == NVRAM_PHOENIX_CMDB_HEADER_SIGNATURE) { // Phoenix SCT CMDB store
        storeSize = NVRAM_PHOENIX_CMDB_SIZE; // It's a predefined max size, no need to calculate
    }
    else if (*(signature + 4) == OEM_ACTIVATION_PUBKEY_MAGIC) { // SLIC pubkey
        const OEM_ACTIVATION_PUBKEY* pubkeyHeader = (const OEM_ACTIVATION_PUBKEY*)signature;
        storeSize = pubkeyHeader->Size;
    }
    else if (*(const UINT64*)(data.constData() + storeOffset + 26) == OEM_ACTIVATION_MARKER_WINDOWS_FLAG) { // SLIC marker
        const OEM_ACTIVATION_MARKER* markerHeader = (const OEM_ACTIVATION_MARKER*)signature;
        storeSize = markerHeader->Size;
    }
    else if (*signature == INTEL_MICROCODE_HEADER_VERSION_1) { // Intel microcode, must be checked after SLIC marker because of the same *signature values
        const INTEL_MICROCODE_HEADER* ucodeHeader = (const INTEL_MICROCODE_HEADER*)signature;
        storeSize = ucodeHeader->TotalSize;
    } else {
        return U_INVALID_PARAMETER; // Unreachable
    }
    return U_SUCCESS;
}

USTATUS NvramParser::parseVssStoreHeader(const UByteArray & store, const UINT32 localOffset, const bool sizeOverride, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check store size
    if (dataSize < sizeof(VSS_VARIABLE_STORE_HEADER)) {
        msg(usprintf("%s: volume body is too small even for VSS store header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Get VSS store header
    const VSS_VARIABLE_STORE_HEADER* vssStoreHeader = (const VSS_VARIABLE_STORE_HEADER*)store.constData();
    
    // Check for size override
    UINT32 storeSize = vssStoreHeader->Size;
    if (sizeOverride) {
        storeSize = dataSize;
    }
    
    // Check store size
    if (dataSize < storeSize) {
        msg(usprintf("%s: VSS store size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     storeSize, storeSize,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UByteArray header = store.left(sizeof(VSS_VARIABLE_STORE_HEADER));
    UByteArray body = store.mid(sizeof(VSS_VARIABLE_STORE_HEADER), storeSize - sizeof(VSS_VARIABLE_STORE_HEADER));
    
    // Add info
    UString name;
    if (vssStoreHeader->Signature == NVRAM_APPLE_SVS_STORE_SIGNATURE) {
        name = UString("SVS store");
    }
    else if (vssStoreHeader->Signature == NVRAM_APPLE_NSS_STORE_SIGNATURE) {
        name = UString("NSS store");
    }
    else {
        name = UString("VSS store");
    }
    
    UString info = usprintf("Signature: %Xh\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nFormat: %02Xh\nState: %02Xh\nUnknown: %04Xh",
                            vssStoreHeader->Signature,
                            storeSize, storeSize,
                            (UINT32)header.size(), (UINT32)header.size(),
                            (UINT32)body.size(), (UINT32)body.size(),
                            vssStoreHeader->Format,
                            vssStoreHeader->State,
                            vssStoreHeader->Unknown);
    
    // Add tree item
    index = model->addItem(localOffset, Types::VssStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseVss2StoreHeader(const UByteArray & store, const UINT32 localOffset, const bool sizeOverride, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check store size
    if (dataSize < sizeof(VSS2_VARIABLE_STORE_HEADER)) {
        msg(usprintf("%s: volume body is too small even for VSS2 store header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Get VSS2 store header
    const VSS2_VARIABLE_STORE_HEADER* vssStoreHeader = (const VSS2_VARIABLE_STORE_HEADER*)store.constData();
    
    // Check for size override
    UINT32 storeSize = vssStoreHeader->Size;
    if (sizeOverride) {
        storeSize = dataSize;
    }
    
    // Check store size
    if (dataSize < storeSize) {
        msg(usprintf("%s: VSS2 store size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     storeSize, storeSize,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UByteArray header = store.left(sizeof(VSS2_VARIABLE_STORE_HEADER));
    UByteArray body = store.mid(sizeof(VSS2_VARIABLE_STORE_HEADER), storeSize - sizeof(VSS2_VARIABLE_STORE_HEADER));
    
    // Add info
    UString name = UString("VSS2 store");
    UString info = UString("Signature: ") + guidToUString(vssStoreHeader->Signature, false) +
    usprintf("\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nFormat: %02Xh\nState: %02Xh\nUnknown: %04Xh",
             storeSize, storeSize,
             (UINT32)header.size(), (UINT32)header.size(),
             (UINT32)body.size(), (UINT32)body.size(),
             vssStoreHeader->Format,
             vssStoreHeader->State,
             vssStoreHeader->Unknown);
    
    // Add tree item
    index = model->addItem(localOffset, Types::Vss2Store, 0, name, UString(), info, header, body, UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseFtwStoreHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check store size
    if (dataSize < sizeof(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64)) {
        msg(usprintf("%s: volume body is too small even for FTW store header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Obtain required information from parent volume
    UINT8 emptyByte = 0xFF;
    UModelIndex parentVolumeIndex = model->findParentOfType(parent, Types::Volume);
    if (parentVolumeIndex.isValid() && model->hasEmptyParsingData(parentVolumeIndex) == false) {
        UByteArray data = model->parsingData(parentVolumeIndex);
        const VOLUME_PARSING_DATA* pdata = (const VOLUME_PARSING_DATA*)data.constData();
        emptyByte = pdata->emptyByte;
    }
    
    // Get FTW block headers
    const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32* ftw32BlockHeader = (const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32*)store.constData();
    const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64* ftw64BlockHeader = (const EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64*)store.constData();
    
    // Check store size
    UINT32 ftwBlockSize;
    bool has32bitHeader;
    if (ftw32BlockHeader->WriteQueueSize % 0x10 == 0x04) { // Header with 32 bit WriteQueueSize
        ftwBlockSize = sizeof(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32) + ftw32BlockHeader->WriteQueueSize;
        has32bitHeader = true;
    }
    else { // Header with 64 bit WriteQueueSize
        ftwBlockSize = sizeof(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64) + (UINT32)ftw64BlockHeader->WriteQueueSize;
        has32bitHeader = false;
    }
    if (dataSize < ftwBlockSize) {
        msg(usprintf("%s: FTW store size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     ftwBlockSize, ftwBlockSize,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UINT32 headerSize = has32bitHeader ? sizeof(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32) : sizeof(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64);
    UByteArray header = store.left(headerSize);
    UByteArray body = store.mid(headerSize, ftwBlockSize - headerSize);
    
    // Check block header checksum
    UByteArray crcHeader = header;
    EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32* crcFtwBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32*)header.data();
    crcFtwBlockHeader->Crc = emptyByte ? 0xFFFFFFFF : 0;
    crcFtwBlockHeader->State = emptyByte ? 0xFF : 0;
    UINT32 calculatedCrc = (UINT32)crc32(0, (const UINT8*)crcFtwBlockHeader, headerSize);
    
    // Add info
    UString name("FTW store");
    UString info = UString("Signature: ") + guidToUString(ftw32BlockHeader->Signature, false) +
    usprintf("\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nState: %02Xh\nHeader CRC32: %08Xh",
             ftwBlockSize, ftwBlockSize,
             headerSize, headerSize,
             (UINT32)body.size(), (UINT32)body.size(),
             ftw32BlockHeader->State,
             ftw32BlockHeader->Crc) +
    (ftw32BlockHeader->Crc != calculatedCrc ? usprintf(", invalid, should be %08Xh", calculatedCrc) : UString(", valid"));
    
    // Add tree item
    index = model->addItem(localOffset, Types::FtwStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseFdcStoreHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check store size
    if (dataSize < sizeof(FDC_VOLUME_HEADER)) {
        msg(usprintf("%s: volume body is too small even for FDC store header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Get Fdc store header
    const FDC_VOLUME_HEADER* fdcStoreHeader = (const FDC_VOLUME_HEADER*)store.constData();
    
    // Check store size
    if (dataSize < fdcStoreHeader->Size) {
        msg(usprintf("%s: FDC store size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     fdcStoreHeader->Size, fdcStoreHeader->Size,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UByteArray header = store.left(sizeof(FDC_VOLUME_HEADER));
    UByteArray body = store.mid(sizeof(FDC_VOLUME_HEADER), fdcStoreHeader->Size - sizeof(FDC_VOLUME_HEADER));
    
    // Add info
    UString name("FDC store");
    UString info = usprintf("Signature: _FDC\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)",
                            fdcStoreHeader->Size, fdcStoreHeader->Size,
                            (UINT32)header.size(), (UINT32)header.size(),
                            (UINT32)body.size(), (UINT32)body.size());
    
    // Add tree item
    index = model->addItem(localOffset, Types::FdcStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseFsysStoreHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check store size
    if (dataSize < sizeof(APPLE_FSYS_STORE_HEADER)) {
        msg(usprintf("%s: volume body is too small even for Fsys store header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Get Fsys store header
    const APPLE_FSYS_STORE_HEADER* fsysStoreHeader = (const APPLE_FSYS_STORE_HEADER*)store.constData();
    
    // Check store size
    if (dataSize < fsysStoreHeader->Size) {
        msg(usprintf("%s: Fsys store size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     fsysStoreHeader->Size, fsysStoreHeader->Size,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UByteArray header = store.left(sizeof(APPLE_FSYS_STORE_HEADER));
    UByteArray body = store.mid(sizeof(APPLE_FSYS_STORE_HEADER), fsysStoreHeader->Size - sizeof(APPLE_FSYS_STORE_HEADER) - sizeof(UINT32));
    
    // Check store checksum
    UINT32 storedCrc = *(UINT32*)store.right(sizeof(UINT32)).constData();
    UINT32 calculatedCrc = (UINT32)crc32(0, (const UINT8*)store.constData(), (UINT32)store.size() - sizeof(UINT32));
    
    // Add info
    bool isGaidStore = (fsysStoreHeader->Signature == NVRAM_APPLE_GAID_STORE_SIGNATURE);
    UString name = isGaidStore ? UString("Gaid store") : UString("Fsys store");
    UString info = usprintf("Signature: %s\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nUnknown0: %02Xh\nUnknown1: %08Xh\nCRC32: %08Xh",
                            isGaidStore ? "Gaid" : "Fsys",
                            fsysStoreHeader->Size, fsysStoreHeader->Size,
                            (UINT32)header.size(), (UINT32)header.size(),
                            (UINT32)body.size(), (UINT32)body.size(),
                            fsysStoreHeader->Unknown0,
                            fsysStoreHeader->Unknown1,
                            storedCrc)
    + (storedCrc != calculatedCrc ? usprintf(", invalid, should be %08Xh", calculatedCrc) : UString(", valid"));
    
    // Add tree item
    index = model->addItem(localOffset, Types::FsysStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseEvsaStoreHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check dataSize
    if (dataSize < sizeof(EVSA_STORE_ENTRY)) {
        msg(usprintf("%s: volume body is too small even for EVSA store header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Get EVSA store header
    const EVSA_STORE_ENTRY* evsaStoreHeader = (const EVSA_STORE_ENTRY*)store.constData();
    
    // Check store size
    if (dataSize < evsaStoreHeader->StoreSize) {
        msg(usprintf("%s: EVSA store size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     evsaStoreHeader->StoreSize, evsaStoreHeader->StoreSize,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UByteArray header = store.left(evsaStoreHeader->Header.Size);
    UByteArray body = store.mid(evsaStoreHeader->Header.Size, evsaStoreHeader->StoreSize - evsaStoreHeader->Header.Size);
    
    // Recalculate checksum
    UINT8 calculated = calculateChecksum8(((const UINT8*)evsaStoreHeader) + 2, evsaStoreHeader->Header.Size - 2);
    
    // Add info
    UString name("EVSA store");
    UString info = usprintf("Signature: EVSA\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nType: %02Xh\nAttributes: %08Xh\nChecksum: %02Xh",
                            evsaStoreHeader->StoreSize, evsaStoreHeader->StoreSize,
                            (UINT32)header.size(), (UINT32)header.size(),
                            (UINT32)body.size(), (UINT32)body.size(),
                            evsaStoreHeader->Header.Type,
                            evsaStoreHeader->Attributes,
                            evsaStoreHeader->Header.Checksum) +
    (evsaStoreHeader->Header.Checksum != calculated ? usprintf(", invalid, should be %02Xh", calculated) : UString(", valid"));
    
    // Add tree item
    index = model->addItem(localOffset, Types::EvsaStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseFlashMapStoreHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check data size
    if (dataSize < sizeof(PHOENIX_FLASH_MAP_HEADER)) {
        msg(usprintf("%s: volume body is too small even for FlashMap block header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Get FlashMap block header
    const PHOENIX_FLASH_MAP_HEADER* flashMapHeader = (const PHOENIX_FLASH_MAP_HEADER*)store.constData();
    
    // Check store size
    UINT32 flashMapSize = sizeof(PHOENIX_FLASH_MAP_HEADER) + flashMapHeader->NumEntries * sizeof(PHOENIX_FLASH_MAP_ENTRY);
    if (dataSize < flashMapSize) {
        msg(usprintf("%s: FlashMap block size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     flashMapSize, flashMapSize,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UByteArray header = store.left(sizeof(PHOENIX_FLASH_MAP_HEADER));
    UByteArray body = store.mid(sizeof(PHOENIX_FLASH_MAP_HEADER), flashMapSize - sizeof(PHOENIX_FLASH_MAP_HEADER));
    
    // Add info
    UString name("Phoenix SCT flash map");
    UString info = usprintf("Signature: _FLASH_MAP\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nNumber of entries: %u",
                            flashMapSize, flashMapSize,
                            (UINT32)header.size(), (UINT32)header.size(),
                            (UINT32)body.size(), (UINT32)body.size(),
                            flashMapHeader->NumEntries);
    
    // Add tree item
    index = model->addItem(localOffset, Types::FlashMapStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseCmdbStoreHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check store size
    if (dataSize < sizeof(PHOENIX_CMDB_HEADER)) {
        msg(usprintf("%s: volume body is too small even for CMDB store header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    UINT32 cmdbSize = NVRAM_PHOENIX_CMDB_SIZE;
    if (dataSize < cmdbSize) {
        msg(usprintf("%s: CMDB store size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     cmdbSize, cmdbSize,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Get store header
    const PHOENIX_CMDB_HEADER* cmdbHeader = (const PHOENIX_CMDB_HEADER*)store.constData();
    
    // Construct header and body
    UByteArray header = store.left(cmdbHeader->TotalSize);
    UByteArray body = store.mid(cmdbHeader->TotalSize, cmdbSize - cmdbHeader->TotalSize);
    
    // Add info
    UString name("CMDB store");
    UString info = usprintf("Signature: CMDB\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)",
                            cmdbSize, cmdbSize,
                            (UINT32)header.size(), (UINT32)header.size(),
                            (UINT32)body.size(), (UINT32)body.size());
    
    // Add tree item
    index = model->addItem(localOffset, Types::CmdbStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseSlicPubkeyHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check data size
    if (dataSize < sizeof(OEM_ACTIVATION_PUBKEY)) {
        msg(usprintf("%s: volume body is too small even for SLIC pubkey header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Get SLIC pubkey header
    const OEM_ACTIVATION_PUBKEY* pubkeyHeader = (const OEM_ACTIVATION_PUBKEY*)store.constData();
    
    // Check store size
    if (dataSize < pubkeyHeader->Size) {
        msg(usprintf("%s: SLIC pubkey size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     pubkeyHeader->Size, pubkeyHeader->Size,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UByteArray header = store.left(sizeof(OEM_ACTIVATION_PUBKEY));
    
    // Add info
    UString name("SLIC pubkey");
    UString info = usprintf("Type: 0h\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: 0h (0)\n"
                            "Key type: %02Xh\nVersion: %02Xh\nAlgorithm: %08Xh\nMagic: RSA1\nBit length: %08Xh\nExponent: %08Xh",
                            pubkeyHeader->Size, pubkeyHeader->Size,
                            (UINT32)header.size(), (UINT32)header.size(),
                            pubkeyHeader->KeyType,
                            pubkeyHeader->Version,
                            pubkeyHeader->Algorithm,
                            pubkeyHeader->BitLength,
                            pubkeyHeader->Exponent);
    
    // Add tree item
    index = model->addItem(localOffset, Types::SlicData, Subtypes::PubkeySlicData, name, UString(), info, header, UByteArray(), UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseSlicMarkerHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    
    // Check data size
    if (dataSize < sizeof(OEM_ACTIVATION_MARKER)) {
        msg(usprintf("%s: volume body is too small even for SLIC marker header", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Get SLIC marker header
    const OEM_ACTIVATION_MARKER* markerHeader = (const OEM_ACTIVATION_MARKER*)store.constData();
    
    // Check store size
    if (dataSize < markerHeader->Size) {
        msg(usprintf("%s: SLIC marker size %Xh (%u) is greater than volume body size %Xh (%u)", __FUNCTION__,
                     markerHeader->Size, markerHeader->Size,
                     dataSize, dataSize), parent);
        return U_SUCCESS;
    }
    
    // Construct header and body
    UByteArray header = store.left(sizeof(OEM_ACTIVATION_MARKER));
    
    // Add info
    UString name("SLIC marker");
    UString info = usprintf("Type: 1h\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: 0h (0)\n"
                            "Version: %08Xh\nOEM ID: %s\nOEM table ID: %s\nWindows flag: WINDOWS\nSLIC version: %08Xh",
                            markerHeader->Size, markerHeader->Size,
                            (UINT32)header.size(), (UINT32)header.size(),
                            markerHeader->Version,
                            (const char*)UString((const char*)&(markerHeader->OemId)).left(6).toLocal8Bit(),
                            (const char*)UString((const char*)&(markerHeader->OemTableId)).left(8).toLocal8Bit(),
                            markerHeader->SlicVersion);
    
    
    // Add tree item
    index = model->addItem(localOffset, Types::SlicData, Subtypes::MarkerSlicData, name, UString(), info, header, UByteArray(), UByteArray(), Fixed, parent);
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseStoreHeader(const UByteArray & store, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index)
{
    const UINT32 dataSize = (UINT32)store.size();
    const UINT32* signature = (const UINT32*)store.constData();
    // Check store size
    if (dataSize < sizeof(UINT32)) {
        msg(usprintf("%s: volume body is too small even for a store signature", __FUNCTION__), parent);
        return U_SUCCESS;
    }
    
    // Check signature and run parser function needed
    // VSS/SVS/NSS store
    if (*signature == NVRAM_VSS_STORE_SIGNATURE || *signature == NVRAM_APPLE_SVS_STORE_SIGNATURE || *signature == NVRAM_APPLE_NSS_STORE_SIGNATURE)
        return parseVssStoreHeader(store, localOffset, false, parent, index);
    // VSS2 store
    if (*signature == NVRAM_VSS2_AUTH_VAR_KEY_DATABASE_GUID_PART1 || *signature == NVRAM_VSS2_STORE_GUID_PART1)
        return parseVss2StoreHeader(store, localOffset, false, parent, index);
    // FTW store
    else if (*signature == NVRAM_MAIN_STORE_VOLUME_GUID_DATA1 || *signature == EDKII_WORKING_BLOCK_SIGNATURE_GUID_DATA1)
        return parseFtwStoreHeader(store, localOffset, parent, index);
    // FDC store
    else if (*signature == NVRAM_FDC_VOLUME_SIGNATURE)
        return parseFdcStoreHeader(store, localOffset, parent, index);
    // Apple Fsys/Gaid store
    else if (*signature == NVRAM_APPLE_FSYS_STORE_SIGNATURE || *signature == NVRAM_APPLE_GAID_STORE_SIGNATURE)
        return parseFsysStoreHeader(store, localOffset, parent, index);
    // EVSA store
    else if (dataSize >= 2 * sizeof(UINT32) && *(signature + 1) == NVRAM_EVSA_STORE_SIGNATURE)
        return parseEvsaStoreHeader(store, localOffset, parent, index);
    // Phoenix SCT flash map
    else if (*signature == NVRAM_PHOENIX_FLASH_MAP_SIGNATURE_PART1)
        return parseFlashMapStoreHeader(store, localOffset, parent, index);
    // Phoenix CMDB store
    else if (*signature == NVRAM_PHOENIX_CMDB_HEADER_SIGNATURE)
        return parseCmdbStoreHeader(store, localOffset, parent, index);
    // SLIC pubkey
    else if (dataSize >= 5 * sizeof(UINT32) && *(signature + 4) == OEM_ACTIVATION_PUBKEY_MAGIC)
        return parseSlicPubkeyHeader(store, localOffset, parent, index);
    // SLIC marker
    else if (dataSize >= 34 && *(const UINT64*)(store.constData() + 26) == OEM_ACTIVATION_MARKER_WINDOWS_FLAG)
        return parseSlicMarkerHeader(store, localOffset, parent, index);
    // Intel microcode
    // Must be checked after SLIC marker because of the same *signature values
    else if (*signature == INTEL_MICROCODE_HEADER_VERSION_1)
        return ffsParser->parseIntelMicrocodeHeader(store, localOffset, parent, index);
    
    msg(usprintf("parseStoreHeader: don't know how to parse a header with signature %08Xh", *signature), parent);
    return U_SUCCESS;
}

USTATUS NvramParser::parseFdcStoreBody(const UModelIndex & index)
{
    // Sanity check
    if (!index.isValid())
        return U_INVALID_PARAMETER;
    
    // Get item data
    const UByteArray data = model->body(index);
    
    // Get local offset
    UINT32 localOffset = (UINT32)model->header(index).size();
    
    // The body is a firmware volume with either a VSS or VSS2 store
    UModelIndex volumeIndex;
    USTATUS status = ffsParser->parseVolumeHeader(data, localOffset, index, volumeIndex);
    if (status || !volumeIndex.isValid()) {
        msg(usprintf("%s: store can't be parsed as FDC store", __FUNCTION__), index);
        return U_SUCCESS;
    }
    
    // Determine if it's a VSS or VSS2 store inside
    UByteArray store = model->body(volumeIndex);
    if ((UINT32)store.size() >= sizeof(UINT32) && *(const UINT32*)store.constData() == NVRAM_VSS_STORE_SIGNATURE) {
        UModelIndex vssIndex;
        status = parseVssStoreHeader(store, (UINT32)(localOffset + model->header(volumeIndex).size()), true, volumeIndex, vssIndex);
        if (status)
            return status;
        return parseVssStoreBody(vssIndex, 0);
    }
    else if ((UINT32)store.size() >= sizeof(EFI_GUID) && store.left(sizeof(EFI_GUID)) == NVRAM_FDC_STORE_GUID) {
        UModelIndex vss2Index;
        status = parseVss2StoreHeader(store, (UINT32)(localOffset + model->header(volumeIndex).size()), true, volumeIndex, vss2Index);
        if (status)
            return status;
        return parseVssStoreBody(vss2Index, 0);
    }
    else {
        msg(usprintf("%s: internal volume can't be parsed as VSS/VSS2 store", __FUNCTION__), index);
        return U_SUCCESS;
    }
    
}

USTATUS NvramParser::parseVssStoreBody(const UModelIndex & index, UINT8 alignment)
{
    // Sanity check
    if (!index.isValid())
        return U_INVALID_PARAMETER;
    
    // Obtain required information from parent volume
    UINT8 emptyByte = 0xFF;
    UModelIndex parentVolumeIndex = model->findParentOfType(index, Types::Volume);
    if (parentVolumeIndex.isValid() && model->hasEmptyParsingData(parentVolumeIndex) == false) {
        UByteArray data = model->parsingData(parentVolumeIndex);
        const VOLUME_PARSING_DATA* pdata = (const VOLUME_PARSING_DATA*)data.constData();
        emptyByte = pdata->emptyByte;
    }
    
    // Get local offset
    UINT32 localOffset = (UINT32)model->header(index).size();
    
    // Get item data
    const UByteArray data = model->body(index);
    
    // Check that the is enough space for variable header
    const UINT32 dataSize = (UINT32)data.size();
    if (dataSize < sizeof(VSS_VARIABLE_HEADER)) {
        msg(usprintf("%s: store body is too small even for VSS variable header", __FUNCTION__), index);
        return U_SUCCESS;
    }
    
    UINT32 offset = 0;
    
    // Parse all variables
    while (1) {
        bool isInvalid = true;
        bool isAuthenticated = false;
        bool isAppleCrc32 = false;
        bool isIntelSpecial = false;
        
        UINT32 storedCrc32 = 0;
        UINT32 calculatedCrc32 = 0;
        UINT64 monotonicCounter = 0;
        EFI_TIME timestamp = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        UINT32 pubKeyIndex = 0;
        
        UINT8 subtype = 0;
        UString name;
        UString text;
        EFI_GUID* variableGuid = NULL;
        CHAR16*   variableName = (CHAR16*)L"";
        UByteArray header;
        UByteArray body;
        
        UINT32 unparsedSize = dataSize - offset;
        
        // Get variable header
        const VSS_VARIABLE_HEADER* variableHeader = (const VSS_VARIABLE_HEADER*)(data.constData() + offset);
        
        // Check variable header to fit in still unparsed data
        UINT32 variableSize = 0;
        if (unparsedSize >= sizeof(VSS_VARIABLE_HEADER)
            && variableHeader->StartId == NVRAM_VSS_VARIABLE_START_ID) {
            // Apple VSS variable with CRC32 of the data
            if (variableHeader->Attributes & NVRAM_VSS_VARIABLE_APPLE_DATA_CHECKSUM) {
                isAppleCrc32 = true;
                if (unparsedSize < sizeof(VSS_APPLE_VARIABLE_HEADER)) {
                    variableSize = 0;
                }
                else {
                    const VSS_APPLE_VARIABLE_HEADER* appleVariableHeader = (const VSS_APPLE_VARIABLE_HEADER*)variableHeader;
                    variableSize = sizeof(VSS_APPLE_VARIABLE_HEADER) + appleVariableHeader->NameSize + appleVariableHeader->DataSize;
                    variableGuid = (EFI_GUID*)&appleVariableHeader->VendorGuid;
                    variableName = (CHAR16*)(appleVariableHeader + 1);
                    
                    header = data.mid(offset, sizeof(VSS_APPLE_VARIABLE_HEADER) + appleVariableHeader->NameSize);
                    body = data.mid(offset + header.size(), appleVariableHeader->DataSize);
                    
                    // Calculate CRC32 of the variable data
                    storedCrc32 = appleVariableHeader->DataCrc32;
                    calculatedCrc32 = (UINT32)crc32(0, (const UINT8*)body.constData(), (uInt)body.size());
                }
            }
            
            // Authenticated variable
            else if ((variableHeader->Attributes & NVRAM_VSS_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
                     || (variableHeader->Attributes & NVRAM_VSS_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
                     || (variableHeader->Attributes & NVRAM_VSS_VARIABLE_APPEND_WRITE)
                     || (variableHeader->NameSize == 0 && variableHeader->DataSize == 0)) { // If both NameSize and DataSize are zeros, it's auth variable with zero montonic counter
                isAuthenticated = true;
                if (unparsedSize < sizeof(VSS_AUTH_VARIABLE_HEADER)) {
                    variableSize = 0;
                }
                else {
                    const VSS_AUTH_VARIABLE_HEADER* authVariableHeader = (const VSS_AUTH_VARIABLE_HEADER*)variableHeader;
                    variableSize = sizeof(VSS_AUTH_VARIABLE_HEADER) + authVariableHeader->NameSize + authVariableHeader->DataSize;
                    variableGuid = (EFI_GUID*)&authVariableHeader->VendorGuid;
                    variableName = (CHAR16*)(authVariableHeader + 1);
                    
                    header = data.mid(offset, sizeof(VSS_AUTH_VARIABLE_HEADER) + authVariableHeader->NameSize);
                    body = data.mid(offset + header.size(), authVariableHeader->DataSize);
                    
                    monotonicCounter = authVariableHeader->MonotonicCounter;
                    timestamp = authVariableHeader->Timestamp;
                    pubKeyIndex = authVariableHeader->PubKeyIndex;
                }
            }
            
            // Intel special variable
            else if (variableHeader->State == NVRAM_VSS_INTEL_VARIABLE_VALID
                     || variableHeader->State == NVRAM_VSS_INTEL_VARIABLE_INVALID) {
                isIntelSpecial = true;
                const VSS_INTEL_VARIABLE_HEADER* intelVariableHeader = (const VSS_INTEL_VARIABLE_HEADER*)variableHeader;
                variableSize = intelVariableHeader->TotalSize;
                variableGuid = (EFI_GUID*)&intelVariableHeader->VendorGuid;
                variableName = (CHAR16*)(intelVariableHeader + 1);
                
                UINT32 i = 0;
                while (variableName[i] != 0) ++i;
                
                i = sizeof(VSS_INTEL_VARIABLE_HEADER) + 2 * (i + 1);
                i = i < variableSize ? i : variableSize;
                
                header = data.mid(offset, i);
                body = data.mid(offset + header.size(), variableSize - i);
            }
            
            // Normal VSS variable
            else {
                variableSize = sizeof(VSS_VARIABLE_HEADER) + variableHeader->NameSize + variableHeader->DataSize;
                variableGuid = (EFI_GUID*)&variableHeader->VendorGuid;
                variableName = (CHAR16*)(variableHeader + 1);
                
                header = data.mid(offset, sizeof(VSS_VARIABLE_HEADER) + variableHeader->NameSize);
                body = data.mid(offset + header.size(), variableHeader->DataSize);
            }
            
            // Check variable state
            if (variableHeader->State == NVRAM_VSS_INTEL_VARIABLE_VALID
                || variableHeader->State == NVRAM_VSS_VARIABLE_ADDED
                || variableHeader->State == NVRAM_VSS_VARIABLE_HEADER_VALID) {
                isInvalid = false;
            }
            
            // Check variable size
            if (variableSize > unparsedSize) {
                variableSize = 0;
            }
        }
        
        // Can't parse further, add the last element and break the loop
        if (!variableSize) {
            // Check if the data left is a free space or a padding
            UByteArray padding = data.mid(offset, unparsedSize);
            // Get info
            UString info = usprintf("Full size: %Xh (%u)", (UINT32)padding.size(), (UINT32)padding.size());
            
            if (padding.count(emptyByte) == padding.size()) { // Free space
                // Add tree item
                model->addItem(localOffset + offset, Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
            }
            else { // Padding
                // Nothing is parsed yet, but the store is not empty
                if (!offset) {
                    msg(usprintf("%s: store can't be parsed as VSS store", __FUNCTION__), index);
                    return U_SUCCESS;
                }
                
                // Add tree item
                model->addItem(localOffset + offset, Types::Padding, getPaddingType(padding), UString("Padding"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index);
            }
            
            return U_SUCCESS;
        }
        
        UString info;
        
        // Rename invalid variables
        if (isInvalid || !variableGuid) {
            isInvalid = true;
            name = UString("Invalid");
        }
        else { // Add GUID and text for valid variables
            name = guidToUString(readUnaligned(variableGuid));
            info += UString("Variable GUID: ") + guidToUString(readUnaligned(variableGuid), false) + "\n";
            text = uFromUcs2((const char*)variableName);
        }
        
        // Add info
        info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nState: %02Xh\nReserved: %02Xh\nAttributes: %08Xh (",
                         variableSize, variableSize,
                         (UINT32)header.size(), (UINT32)header.size(),
                         (UINT32)body.size(), (UINT32)body.size(),
                         variableHeader->State,
                         variableHeader->Reserved,
                         variableHeader->Attributes) + vssAttributesToUString(variableHeader->Attributes) + UString(")");
        
        // Set subtype and add related info
        if (isInvalid)
            subtype = Subtypes::InvalidVssEntry;
        else if (isAuthenticated) {
            subtype = Subtypes::AuthVssEntry;
            info += usprintf("\nMonotonic counter: %" PRIX64 "h\nTimestamp: ", monotonicCounter) + efiTimeToUString(timestamp)
            + usprintf("\nPubKey index: %u", pubKeyIndex);
        }
        else if (isAppleCrc32) {
            subtype = Subtypes::AppleVssEntry;
            info += usprintf("\nData checksum: %08Xh", storedCrc32) +
            (storedCrc32 != calculatedCrc32 ? usprintf(", invalid, should be %08Xh", calculatedCrc32) : UString(", valid"));
        }
        else if (isIntelSpecial) {
            subtype = Subtypes::IntelVssEntry;
        }
        else {
            subtype = Subtypes::StandardVssEntry;
        }
        
        // Add tree item
        model->addItem(localOffset + offset, Types::VssEntry, subtype, name, text, info, header, body, UByteArray(), Fixed, index);
        
        // Apply alignment, if needed
        if (alignment) {
            variableSize = ((variableSize + alignment - 1) & (~(alignment - 1)));
        }
        
        // Move to next variable
        offset += variableSize;
    }
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseFsysStoreBody(const UModelIndex & index)
{
    // Sanity check
    if (!index.isValid())
        return U_INVALID_PARAMETER;
    
    // Get local offset
    UINT32 localOffset = (UINT32)model->header(index).size();
    
    // Get item data
    const UByteArray data = model->body(index);
    
    // Check that the is enough space for variable header
    const UINT32 storeDataSize = (UINT32)data.size();
    UINT32 offset = 0;
    
    // Parse all variables
    while (1) {
        UINT32 unparsedSize = storeDataSize - offset;
        UINT32 variableSize = 0;
        
        // Get nameSize and name of the variable
        UINT8 nameSize = *(UINT8*)(data.constData() + offset);
        bool valid = !(nameSize & 0x80); // Last bit is a validity bit, 0 means valid
        nameSize &= 0x7F;
        
        // Check sanity
        if (unparsedSize >= nameSize + sizeof(UINT8)) {
            variableSize = nameSize + sizeof(UINT8);
        }
        
        UByteArray name;
        if (variableSize) {
            name = data.mid(offset + sizeof(UINT8), nameSize);
            // Check for EOF variable
            if (nameSize == 3 && name[0] == 'E' && name[1] == 'O' && name[2] == 'F') {
                // There is no data afterward, add EOF variable and free space and return
                UByteArray header = data.mid(offset, sizeof(UINT8) + nameSize);
                UString info = usprintf("Full size: %Xh (%u)", (UINT32)header.size(), (UINT32)header.size());
                
                // Add EOF tree item
                model->addItem(localOffset + offset, Types::FsysEntry, Subtypes::NormalFsysEntry, UString("EOF"), UString(), info, header, UByteArray(), UByteArray(), Fixed, index);
                
                // Add free space
                offset += (UINT32)header.size();
                UByteArray body = data.mid(offset);
                info = usprintf("Full size: %Xh (%u)", (UINT32)body.size(), (UINT32)body.size());
                
                // Add free space tree item
                model->addItem(localOffset + offset, Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), body, UByteArray(), Fixed, index);
                
                return U_SUCCESS;
            }
        }
        
        // Get dataSize and data of the variable
        const UINT16 dataSize = *(UINT16*)(data.constData() + offset + sizeof(UINT8) + nameSize);
        if (unparsedSize >= sizeof(UINT8) + nameSize + sizeof(UINT16) + dataSize) {
            variableSize = sizeof(UINT8) + nameSize + sizeof(UINT16) + dataSize;
        }
        else {
            // Last variable is bad, add the rest as padding and return
            UByteArray body = data.mid(offset);
            UString info = usprintf("Full size: %Xh (%u)", (UINT32)body.size(), (UINT32)body.size());
            
            // Add padding tree item
            model->addItem(localOffset + offset, Types::Padding, getPaddingType(body), UString("Padding"), UString(), info, UByteArray(), body, UByteArray(), Fixed, index);
            
            // Show message
            msg(usprintf("%s: next variable appears too big, added as padding", __FUNCTION__), index);
            
            return U_SUCCESS;
        }
        
        // Construct header and body
        UByteArray header = data.mid(offset, sizeof(UINT8) + nameSize + sizeof(UINT16));
        UByteArray body = data.mid(offset + sizeof(UINT8) + nameSize + sizeof(UINT16), dataSize);
        
        // Add info
        UString info = usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)",
                                variableSize, variableSize,
                                (UINT32)header.size(), (UINT32)header.size(),
                                (UINT32)body.size(), (UINT32)body.size());
        
        // Add tree item
        model->addItem(localOffset + offset, Types::FsysEntry, valid ? Subtypes::NormalFsysEntry : Subtypes::InvalidFsysEntry, UString(name.constData()), UString(), info, header, body, UByteArray(), Fixed, index);
        
        // Move to next variable
        offset += variableSize;
    }
    
    return U_SUCCESS;
}

USTATUS NvramParser::parseEvsaStoreBody(const UModelIndex & index)
{
    // Sanity check
    if (!index.isValid())
        return U_INVALID_PARAMETER;
    
    // Obtain required information from parent volume
    UINT8 emptyByte = 0xFF;
    UModelIndex parentVolumeIndex = model->findParentOfType(index, Types::Volume);
    if (parentVolumeIndex.isValid() && model->hasEmptyParsingData(parentVolumeIndex) == false) {
        UByteArray data = model->parsingData(parentVolumeIndex);
        const VOLUME_PARSING_DATA* pdata = (const VOLUME_PARSING_DATA*)data.constData();
        emptyByte = pdata->emptyByte;
    }
    
    // Get local offset
    UINT32 localOffset = (UINT32)model->header(index).size();
    
    // Get item data
    const UByteArray data = model->body(index);
    
    // Check that the is enough space for entry header
    const UINT32 storeDataSize = (UINT32)data.size();
    UINT32 offset = 0;
    
    std::map<UINT16, EFI_GUID> guidMap;
    std::map<UINT16, UString> nameMap;
    
    // Parse all entries
    UINT32 unparsedSize = storeDataSize;
    while (unparsedSize) {
        UINT32 variableSize = 0;
        UString name;
        UString info;
        UByteArray header;
        UByteArray body;
        UINT8 subtype;
        UINT8 calculated;
        
        const EVSA_ENTRY_HEADER* entryHeader = (const EVSA_ENTRY_HEADER*)(data.constData() + offset);
        
        // Check entry size
        variableSize = sizeof(EVSA_ENTRY_HEADER);
        if (unparsedSize < variableSize || unparsedSize < entryHeader->Size || entryHeader->Size < 2) {
            body = data.mid(offset);
            info = usprintf("Full size: %Xh (%u)", (UINT32)body.size(), (UINT32)body.size());
            
            if (body.count(emptyByte) == body.size()) { // Free space
                // Add free space tree item
                model->addItem(localOffset + offset, Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), body, UByteArray(), Fixed, index);
            }
            else {
                // Add padding tree item
                UModelIndex itemIndex = model->addItem(localOffset + offset, Types::Padding, getPaddingType(body), UString("Padding"), UString(), info, UByteArray(), body, UByteArray(), Fixed, index);
                
                // Show message
                msg(usprintf("%s: variable parsing failed, the rest of unparsed store added as padding", __FUNCTION__), itemIndex);
            }
            break;
        }
        variableSize = entryHeader->Size;
        
        // Recalculate entry checksum
        calculated = calculateChecksum8(((const UINT8*)entryHeader) + 2, entryHeader->Size - 2);
        
        // GUID entry
        if (entryHeader->Type == NVRAM_EVSA_ENTRY_TYPE_GUID1 ||
            entryHeader->Type == NVRAM_EVSA_ENTRY_TYPE_GUID2) {
            const EVSA_GUID_ENTRY* guidHeader = (const EVSA_GUID_ENTRY*)entryHeader;
            header = data.mid(offset, sizeof(EVSA_GUID_ENTRY));
            body = data.mid(offset + sizeof(EVSA_GUID_ENTRY), guidHeader->Header.Size - sizeof(EVSA_GUID_ENTRY));
            EFI_GUID guid = *(EFI_GUID*)body.constData();
            name = guidToUString(guid);
            info = UString("GUID: ") + guidToUString(guid, false)
            + usprintf("\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nType: %02Xh\nChecksum: %02Xh",
                       variableSize, variableSize,
                       (UINT32)header.size(), (UINT32)header.size(),
                       (UINT32)body.size(), (UINT32)body.size(),
                       guidHeader->Header.Type,
                       guidHeader->Header.Checksum)
            + (guidHeader->Header.Checksum != calculated ? usprintf(", invalid, should be %02Xh", calculated) : UString(", valid"))
            + usprintf("\nGuidId: %04Xh", guidHeader->GuidId);
            subtype = Subtypes::GuidEvsaEntry;
            guidMap.insert(std::pair<UINT16, EFI_GUID>(guidHeader->GuidId, guid));
        }
        // Name entry
        else if (entryHeader->Type == NVRAM_EVSA_ENTRY_TYPE_NAME1 ||
                 entryHeader->Type == NVRAM_EVSA_ENTRY_TYPE_NAME2) {
            const EVSA_NAME_ENTRY* nameHeader = (const EVSA_NAME_ENTRY*)entryHeader;
            header = data.mid(offset, sizeof(EVSA_NAME_ENTRY));
            body = data.mid(offset + sizeof(EVSA_NAME_ENTRY), nameHeader->Header.Size - sizeof(EVSA_NAME_ENTRY));
            name = uFromUcs2(body.constData());
            info = UString("Name: ") + name
            + usprintf("\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nType: %02Xh\nChecksum: %02Xh",
                       variableSize, variableSize,
                       (UINT32)header.size(), (UINT32)header.size(),
                       (UINT32)body.size(), (UINT32)body.size(),
                       nameHeader->Header.Type,
                       nameHeader->Header.Checksum)
            + (nameHeader->Header.Checksum != calculated ? usprintf(", invalid, should be %02Xh", calculated) : UString(", valid"))
            + usprintf("\nVarId: %04Xh", nameHeader->VarId);
            subtype = Subtypes::NameEvsaEntry;
            nameMap.insert(std::pair<UINT16, UString>(nameHeader->VarId, name));
        }
        // Data entry
        else if (entryHeader->Type == NVRAM_EVSA_ENTRY_TYPE_DATA1 ||
                 entryHeader->Type == NVRAM_EVSA_ENTRY_TYPE_DATA2 ||
                 entryHeader->Type == NVRAM_EVSA_ENTRY_TYPE_DATA_INVALID) {
            const EVSA_DATA_ENTRY* dataHeader = (const EVSA_DATA_ENTRY*)entryHeader;
            // Check for extended header
            UINT32 headerSize = sizeof(EVSA_DATA_ENTRY);
            UINT32 dataSize = dataHeader->Header.Size - sizeof(EVSA_DATA_ENTRY);
            if (dataHeader->Attributes & NVRAM_EVSA_DATA_EXTENDED_HEADER) {
                const EVSA_DATA_ENTRY_EXTENDED* dataHeaderExtended = (const EVSA_DATA_ENTRY_EXTENDED*)entryHeader;
                headerSize = sizeof(EVSA_DATA_ENTRY_EXTENDED);
                dataSize = dataHeaderExtended->DataSize;
                variableSize = headerSize + dataSize;
            }
            
            header = data.mid(offset, headerSize);
            body = data.mid(offset + headerSize, dataSize);
            name = UString("Data");
            info = usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nType: %02Xh\nChecksum: %02Xh",
                            variableSize, variableSize,
                            headerSize, headerSize,
                            dataSize, dataSize,
                            dataHeader->Header.Type,
                            dataHeader->Header.Checksum)
            + (dataHeader->Header.Checksum != calculated ? usprintf(", invalid, should be %02Xh", calculated) : UString(", valid"))
            + usprintf("\nVarId: %04Xh\nGuidId: %04Xh\nAttributes: %08Xh (",
                       dataHeader->VarId,
                       dataHeader->GuidId,
                       dataHeader->Attributes)
            + evsaAttributesToUString(dataHeader->Attributes) + UString(")");
            subtype = Subtypes::DataEvsaEntry;
        }
        // Unknown entry or free space
        else {
            body = data.mid(offset);
            info = usprintf("Full size: %Xh (%u)", (UINT32)body.size(), (UINT32)body.size());
            
            if (body.count(emptyByte) == body.size()) { // Free space
                // Add free space tree item
                model->addItem(localOffset + offset, Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), body, UByteArray(), Fixed, index);
            }
            else {
                // Add padding tree item
                UModelIndex itemIndex = model->addItem(localOffset + offset, Types::Padding, getPaddingType(body), UString("Padding"), UString(), info, UByteArray(), body, UByteArray(), Fixed, index);
                
                // Show message
                msg(usprintf("%s: unknown variable of type %02Xh found at offset %Xh, the rest of unparsed store added as padding", __FUNCTION__, entryHeader->Type, offset), itemIndex);
            }
            break;
        }
        
        // Add tree item
        model->addItem(localOffset + offset, Types::EvsaEntry, subtype, name, UString(), info, header, body, UByteArray(), Fixed, index);
        
        // Move to next variable
        offset += variableSize;
        unparsedSize = storeDataSize - offset;
    }
    
    // Reparse all data variables to detect invalid ones and assign name and test to valid ones
    for (int i = 0; i < model->rowCount(index); i++) {
        UModelIndex current = index.model()->index(i, 0, index);
        
        if (model->subtype(current) == Subtypes::DataEvsaEntry) {
            UByteArray header = model->header(current);
            const EVSA_DATA_ENTRY* dataHeader = (const EVSA_DATA_ENTRY*)header.constData();
            UString guid;
            if (guidMap.count(dataHeader->GuidId))
                guid = guidToUString(guidMap[dataHeader->GuidId], false);
            UString name;
            if (nameMap.count(dataHeader->VarId))
                name = nameMap[dataHeader->VarId];
            
            // Check for variable validity
            if (guid.isEmpty() && name.isEmpty()) { // Both name and guid aren't found
                model->setSubtype(current, Subtypes::InvalidEvsaEntry);
                model->setName(current, UString("Invalid"));
                msg(usprintf("%s: data variable with invalid GuidId and invalid VarId", __FUNCTION__), current);
            }
            else if (guid.isEmpty()) { // Guid not found
                model->setSubtype(current, Subtypes::InvalidEvsaEntry);
                model->setName(current, UString("Invalid"));
                msg(usprintf("%s: data variable with invalid GuidId", __FUNCTION__), current);
            }
            else if (name.isEmpty()) { // Name not found
                model->setSubtype(current, Subtypes::InvalidEvsaEntry);
                model->setName(current, UString("Invalid"));
                msg(usprintf("%s: data variable with invalid VarId", __FUNCTION__), current);
            }
            else { // Variable is OK, rename it
                if (dataHeader->Header.Type == NVRAM_EVSA_ENTRY_TYPE_DATA_INVALID) {
                    model->setSubtype(current, Subtypes::InvalidEvsaEntry);
                    model->setName(current, UString("Invalid"));
                }
                else {
                    model->setName(current, guid);
                }
                model->setText(current, name);
                model->addInfo(current, UString("GUID: ") + guid + UString("\nName: ") + name + "\n", false);
            }
        }
    }
    
    return U_SUCCESS;
}


USTATUS NvramParser::parseFlashMapBody(const UModelIndex & index)
{
    // Sanity check
    if (!index.isValid())
        return U_INVALID_PARAMETER;
    
    // Get parsing data for the current item
    UINT32 localOffset = (UINT32)model->header(index).size();
    const UByteArray data = model->body(index);
    
    
    const UINT32 dataSize = (UINT32)data.size();
    UINT32 offset = 0;
    UINT32 unparsedSize = dataSize;
    // Parse all entries
    while (unparsedSize) {
        const PHOENIX_FLASH_MAP_ENTRY* entryHeader = (const PHOENIX_FLASH_MAP_ENTRY*)(data.constData() + offset);
        
        // Check entry size
        if (unparsedSize < sizeof(PHOENIX_FLASH_MAP_ENTRY)) {
            // Last variable is bad, add the rest as padding and return
            UByteArray body = data.mid(offset);
            UString info = usprintf("Full size: %Xh (%u)", (UINT32)body.size(), (UINT32)body.size());
            
            // Add padding tree item
            model->addItem(localOffset + offset, Types::Padding, getPaddingType(body), UString("Padding"), UString(), info, UByteArray(), body, UByteArray(), Fixed, index);
            
            // Show message
            if (unparsedSize < entryHeader->Size)
                msg(usprintf("%s: next entry appears too big, added as padding", __FUNCTION__), index);
            
            break;
        }
        
        UString name = guidToUString(entryHeader->Guid);
        
        // Construct header
        UByteArray header = data.mid(offset, sizeof(PHOENIX_FLASH_MAP_ENTRY));
        
        // Add info
        UString info = UString("Entry GUID: ") + guidToUString(entryHeader->Guid, false) +
        usprintf("\nFull size: 24h (36)\nHeader size: 24h (36)\nBody size: 0h (0)\n"
                 "Entry type: %04Xh\nData type: %04Xh\nMemory address: %08Xh\nSize: %08Xh\nOffset: %08Xh",
                 entryHeader->EntryType,
                 entryHeader->DataType,
                 (UINT32)entryHeader->PhysicalAddress,
                 entryHeader->Size,
                 entryHeader->Offset);
        
        // Determine subtype
        UINT8 subtype = 0;
        switch (entryHeader->DataType) {
            case NVRAM_PHOENIX_FLASH_MAP_ENTRY_TYPE_VOLUME:
                subtype = Subtypes::VolumeFlashMapEntry;
                break;
            case NVRAM_PHOENIX_FLASH_MAP_ENTRY_TYPE_DATA_BLOCK:
                subtype = Subtypes::DataFlashMapEntry;
                break;
        }
        
        // Add tree item
        model->addItem(localOffset + offset, Types::FlashMapEntry, subtype, name, flashMapGuidToUString(entryHeader->Guid), info, header, UByteArray(), UByteArray(), Fixed, index);
        
        // Move to next variable
        offset += sizeof(PHOENIX_FLASH_MAP_ENTRY);
        unparsedSize = dataSize - offset;
    }
    
    return U_SUCCESS;
}
#endif // U_ENABLE_NVRAM_PARSING_SUPPORT