from filetypes.base import *
import malcat
import struct
from filetypes.CFB_office import SummaryProperty
from filetypes.BMP import RGBA




PROPERTY_STORAGE_ENUMS = {
        "B725F130-47EF-101A-A5F1-02608C9EEBAC": [
            ("System.ItemFolderNameDisplay", 0x00000002),
            ("ClassId", 0x00000003),
            ("System.ItemTypeText", 0x00000004),
            ("FileIndex", 0x00000008),
            ("USN", 0x00000009),
            ("System.ItemNameDisplay", 0x0000000A),
            ("Path", 0x0000000B),
            ("System.Size", 0x0000000C),
            ("System.FileAttributes", 0x0000000D),
            ("System.DateModified", 0x0000000E),
            ("System.DateCreated", 0x0000000F),
            ("System.DateAccessed", 0x00000010),
            ("AllocSize", 0x00000012),
            ("ShortFilename", 0x00000014),
        ],
        "28636AA6-953D-11D2-B5D6-00C04FD918D0": [
            ("ComputerName", 5),
            ("ContainedItems", 29),
            ("FileCount", 12),
            ("FindData", 0),
            ("IsSendToTarget", 33),
            ("ItemPathDisplayNarrow", 8),
            ("ItemSubType", 37),
            ("ItemType", 11),
            ("ParsingName", 24),
            ("ParsingPath", 30),
            ("PerceivedType", 9),
            ("SFGAOFlags", 25),
            ("TotalFileSize", 14),
            ("DescriptionID", 2),
            ("NamespaceCLSID", 6),
            ("PKEY_ComputerName", 5),
        ],
        "E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD": [
            ("FolderPath", 6),
            ("SearchRanking", 3),
        ],
        "446D16B1-8DAD-4870-A748-402EA43D788C": [
            ("ThumbnailCacheId", 100),
            ("VolumeId", 104),
        ]
} 

SPECIAL_FOLDERS = [
    ("Administrative Tools", "D20EA4E1-3957-11D2-A40B-0C5020524153"),
    ("Briefcase", "85BBD920-42A0-1069-A2E4-08002B30309D"),
    ("Control Panel", "21EC2020-3AEA-1069-A2DD-08002B30309D"),
    ("Fonts", "D20EA4E1-3957-11D2-A40B-0C5020524152"),
    ("History", "FF393560-C2A7-11CF-BFF4-444553540000"),
    ("Inbox", "00020D75-0000-0000-C000-000000000046"),
    ("Microsoft Network", "00028B00-0000-0000-C000-000000000046"),
    ("My Computer", "20D04FE0-3AEA-1069-A2D8-08002B30309D"),
    ("My Documents", "450D8FBA-AD25-11D0-98A8-0800361B1103"),
    ("My Network Places", "208D2C60-3AEA-1069-A2D7-08002B30309D"),
    ("Network Computers", "1F4DE370-D627-11D1-BA4F-00A0C91EEDBA"),
    ("Network Connections", "7007ACC7-3202-11D1-AAD2-00805FC1270E"),
    ("Printers and Faxes", "2227A280-3AEA-1069-A2DE-08002B30309D"),
    ("Programs Folder", "7BE9D83C-A729-4D97-B5A7-1B7313C39E0A"),
    ("Recycle Bin", "645FF040-5081-101B-9F08-00AA002F954E"),
    ("Scanners and Cameras", "E211B736-43FD-11D1-9EFB-0000F8757FCD"),
    ("Scheduled Tasks", "D6277990-4C6A-11CF-8D87-00AA0060F5BF"),
    ("Start Menu Folder", "48E7CAAB-B918-4E58-A94D-505519C795DC"),
    ("Temporary Internet Files", "7BD29E00-76C1-11CF-9DD0-00A0C9034933"),
    ("Web Folder", "BDEADF00-C265-11D0-BCED-00A0C90AB50F"),
]


KNOWN_FOLDERS = [
    ("AccountPictures", "008CA0B1-55B4-4C56-B8A8-4DE4B299D3BE"),
    ("AddNewPrograms", "DE61D971-5EBC-4F02-A3A9-6C82895E5C04"),
    ("AdminTools", "724EF170-A42D-4FEF-9F26-B60E846FBA4F"),
    ("AppDataDesktop", "B2C5E279-7ADD-439F-B28C-C41FE1BBF672"),
    ("AppDataDocuments", "7BE16610-1F7F-44AC-BFF0-83E15F2FFCA1"),
    ("AppDataFavorites", "7CFBEFBC-DE1F-45AA-B843-A542AC536CC9"),
    ("AppDataProgramData", "559D40A3-A036-40FA-AF61-84CB430A4D34"),
    ("ApplicationShortcuts", "A3918781-E5F2-4890-B3D9-A7E54332328C"),
    ("AppsFolder", "1E87508D-89C2-42F0-8A7E-645A0F50CA58"),
    ("AppUpdates", "A305CE99-F527-492B-8B1A-7E76FA98D6E4"),
    ("CameraRoll", "AB5FB87B-7CE2-4F83-915D-550846C9537B"),
    ("CDBurning", "9E52AB10-F80D-49DF-ACB8-4330F5687855"),
    ("ChangeRemovePrograms", "DF7266AC-9274-4867-8D55-3BD661DE872D"),
    ("CommonAdminTools", "D0384E7D-BAC3-4797-8F14-CBA229B392B5"),
    ("CommonOEMLinks", "C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D"),
    ("CommonPrograms", "0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8"),
    ("CommonStartMenu", "A4115719-D62E-491D-AA7C-E74B8BE3B067"),
    ("CommonStartup", "82A5EA35-D9CD-47C5-9629-E15D2F714E6E"),
    ("CommonTemplates", "B94237E7-57AC-4347-9151-B08C6C32D1F7"),
    ("ComputerFolder", "0AC0837C-BBF8-452A-850D-79D08E667CA7"),
    ("ConflictFolder", "4BFEFB45-347D-4006-A5BE-AC0CB0567192"),
    ("ConnectionsFolder", "6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD"),
    ("Contacts", "56784854-C6CB-462B-8169-88E350ACB882"),
    ("ControlPanelFolder", "82A74AEB-AEB4-465C-A014-D097EE346D63"),
    ("Cookies", "2B0F765D-C0E9-4171-908E-08A611B84FF6"),
    ("Desktop", "B4BFCC3A-DB2C-424C-B029-7FE99A87C641"),
    ("DeviceMetadataStore", "5CE4A5E9-E4EB-479D-B89F-130C02886155"),
    ("Documents", "FDD39AD0-238F-46AF-ADB4-6C85480369C7"),
    ("DocumentsLibrary", "7B0DB17D-9CD2-4A93-9733-46CC89022E7C"),
    ("Downloads", "374DE290-123F-4565-9164-39C4925E467B"),
    ("Favorites", "1777F761-68AD-4D8A-87BD-30B759FA33DD"),
    ("Fonts", "FD228CB7-AE11-4AE3-864C-16F3910AB8FE"),
    ("Games", "OTE"),
    ("GameTasks", "054FAE61-4DD8-4787-80B6-090220C4B700"),
    ("History", "D9DC8A3B-B784-432E-A781-5A1130A75963"),
    ("HomeGroup", "52528A6B-B9E3-4ADD-B60D-588C2DBA842D"),
    ("HomeGroupCurrentUser", "9B74B6A3-0DFD-4F11-9E78-5F7800F2E772"),
    ("ImplicitAppShortcuts", "BCB5256F-79F6-4CEE-B725-DC34E402FD46"),
    ("InternetCache", "352481E8-33BE-4251-BA85-6007CAEDCF9D"),
    ("InternetFolder", "4D9F7874-4E0C-4904-967B-40B0D20C3E4B"),
    ("Libraries", "1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE"),
    ("Links", "BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968"),
    ("LocalAppData", "F1B32785-6FBA-4FCF-9D55-7B8E7F157091"),
    ("LocalAppDataLow", "A520A1A4-1780-4FF6-BD18-167343C5AF16"),
    ("LocalizedResourcesDir", "2A00375E-224C-49DE-B8D1-440DF7EF3DDC"),
    ("Music", "4BD8D571-6D19-48D3-BE97-422220080E43"),
    ("MusicLibrary", "2112AB0A-C86A-4FFE-A368-0DE96E47012E"),
    ("NetHood", "C5ABBF53-E17F-4121-8900-86626FC2C973"),
    ("NetworkFolder", "D20BEEC4-5CA8-4905-AE3B-BF251EA09B53"),
    ("Objects3D", "31C0DD25-9439-4F12-BF41-7FF4EDA38722"),
    ("OriginalImages", "2C36C0AA-5812-4B87-BFD0-4CD0DFB19B39"),
    ("PhotoAlbums", "69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C"),
    ("PicturesLibrary", "A990AE9F-A03B-4E80-94BC-9912D7504104"),
    ("Pictures", "33E28130-4E1E-4676-835A-98395C3BC3BB"),
    ("Playlists", "DE92C1C7-837F-4F69-A3BB-86E631204A23"),
    ("PrintersFolder", "76FC4E2D-D6AD-4519-A663-37BD56068185"),
    ("PrintHood", "9274BD8D-CFD1-41C3-B35E-B13F55A758F4"),
    ("Profile", "5E6C858F-0E22-4760-9AFE-EA3317B67173"),
    ("ProgramData", "62AB5D82-FDC1-4DC3-A9DD-070D1D495D97"),
    ("ProgramFiles", "905E63B6-C1BF-494E-B29C-65B732D3D21A"),
    ("ProgramFilesX64", "6D809377-6AF0-444B-8957-A3773F02200E"),
    ("ProgramFilesX86", "7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E"),
    ("ProgramFilesCommon", "F7F1ED05-9F6D-47A2-AAAE-29D317C6F066"),
    ("ProgramFilesCommonX64", "6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D"),
    ("ProgramFilesCommonX86", "DE974D24-D9C6-4D3E-BF91-F4455120B917"),
    ("Programs", "A77F5D77-2E2B-44C3-A6A2-ABA601054A51"),
    ("Public", "DFDF76A2-C82A-4D63-906A-5644AC457385"),
    ("PublicDesktop", "C4AA340D-F20F-4863-AFEF-F87EF2E6BA25"),
    ("PublicDocuments", "ED4824AF-DCE4-45A8-81E2-FC7965083634"),
    ("PublicDownloads", "3D644C9B-1FB8-4F30-9B45-F670235F79C0"),
    ("PublicGameTasks", "DEBF2536-E1A8-4C59-B6A2-414586476AEA"),
    ("PublicLibraries", "48DAF80B-E6CF-4F4E-B800-0E69D84EE384"),
    ("PublicMusic", "3214FAB5-9757-4298-BB61-92A9DEAA44FF"),
    ("PublicPictures", "B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5"),
    ("PublicRingtones", "E555AB60-153B-4D17-9F04-A5FE99FC15EC"),
    ("PublicUserTiles", "0482AF6C-08F1-4C34-8C90-E17EC98B1E17"),
    ("PublicVideos", "2400183A-6185-49FB-A2D8-4A392A602BA3"),
    ("QuickLaunch", "52A4F021-7B75-48A9-9F6B-4B87A210BC8F"),
    ("Recent", "AE50C081-EBD2-438A-8655-8A092E34987A"),
    ("RecordedTVLibrary", "1A6FDBA2-F42D-4358-A798-B74D745926C5"),
    ("RecycleBinFolder", "B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC"),
    ("ResourceDir", "8AD10C31-2ADB-4296-A8F7-E4701232C972"),
    ("Ringtones", "C870044B-F49E-4126-A9C3-B52A1FF411E8"),
    ("RoamingAppData", "3EB685DB-65F9-4CF6-A03A-E3EF65729F3D"),
    ("RoamedTileImages", "AAA8D5A5-F1D6-4259-BAA8-78E7EF60835E"),
    ("RoamingTiles", "00BCFC5A-ED94-4E48-96A1-3F6217F21990"),
    ("SampleMusic", "B250C668-F57D-4EE1-A63C-290EE7D1AA1F"),
    ("SamplePictures", "C4900540-2379-4C75-844B-64E6FAF8716B"),
    ("SamplePlaylists", "15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5"),
    ("SampleVideos", "859EAD94-2E85-48AD-A71A-0969CB56A6CD"),
    ("SavedGames", "4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4"),
    ("SavedPictures", "3B193882-D3AD-4EAB-965A-69829D1FB59F"),
    ("SavedPicturesLibrary", "E25B5812-BE88-4BD9-94B0-29233477B6C3"),
    ("SavedSearches", "7D1D3A04-DEBB-4115-95CF-2F29DA2920DA"),
    ("Screenshots", "B7BEDE81-DF94-4682-A7D8-57A52620B86F"),
    ("SEARCH_CSC", "EE32E446-31CA-4ABA-814F-A5EBD2FD6D5E"),
    ("SearchHistory", "0D4C3DB6-03A3-462F-A0E6-08924C41B5D4"),
    ("SearchHome", "190337D1-B8CA-4121-A639-6D472D16972A"),
    ("SEARCH_MAPI", "98EC0E18-2098-4D44-8644-66979315A281"),
    ("SearchTemplates", "7E636BFE-DFA9-4D5E-B456-D7B39851D8A9"),
    ("SendTo", "8983036C-27C0-404B-8F08-102D10DCFD74"),
    ("SidebarDefaultParts", "7B396E54-9EC5-4300-BE0A-2482EBAE1A26"),
    ("SidebarParts", "A75D362E-50FC-4FB7-AC2C-A8BEAA314493"),
    ("SkyDrive", "A52BBA46-E9E1-435F-B3D9-28DAA648C0F6"),
    ("SkyDriveCameraRoll", "767E6811-49CB-4273-87C2-20F355E1085B"),
    ("SkyDriveDocuments", "24D89E24-2F19-4534-9DDE-6A6671FBB8FE"),
    ("SkyDrivePictures", "339719B5-8C47-4894-94C2-D8F77ADD44A6"),
    ("StartMenu", "625B53C3-AB48-4EC1-BA1F-A1EF4146FC19"),
    ("Startup", "B97D20BB-F46A-4C97-BA10-5E3608430854"),
    ("SyncManagerFolder", "43668BF8-C14E-49B2-97C9-747784D784B7"),
    ("SyncResultsFolder", "289A9A43-BE44-4057-A41B-587A76D7E7F9"),
    ("SyncSetupFolder", "0F214138-B1D3-4A90-BBA9-27CBC0C5389A"),
    ("System", "1AC14E77-02E7-4E5D-B744-2EB1AE5198B7"),
    ("SystemX86", "D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27"),
    ("Templates", "A63293E8-664E-48DB-A079-DF759E0509F7"),
    ("UserPinned", "9E3995AB-1F9C-4F13-B827-48B24B6C7174"),
    ("UserProfiles", "0762D272-C50A-4BB0-A382-697DCD729B80"),
    ("UserProgramFiles", "5CD7AEE2-2219-4A67-B85D-6C9CE15660CB"),
    ("UserProgramFilesCommon", "BCBD3057-CA5C-4622-B42D-BC56DB0AE516"),
    ("UsersFiles", "F3CE0F7C-4901-4ACC-8648-D5D44B04EF8F"),
    ("UsersLibraries", "A302545D-DEFF-464B-ABE8-61C8648D939B"),
    ("Videos", "18989B1D-99B5-455B-841C-AB7C74E4DDFC"),
    ("VideosLibrary", "491E922F-5643-4AF4-A7EB-4E7A138D8174"),
    ("Windows", "F38BF404-1D43-42F2-9305-67DE0B28FC23"),
]

class ShellLinkHeader(Struct):

    def parse(self):
        yield UInt32(name="HeaderSize", comment="size of header")
        yield GUID(name="LinkCLSID ", comment="LNK guid")
        yield BitsField(
            Bit(name="HasLinkTargetIDList", comment="shell link is saved with an item ID list (IDList)"),
            Bit(name="HasLinkInfo", comment="shell link is saved with link information"),
            Bit(name="HasName", comment="shell link is saved with a name string"),
            Bit(name="HasRelativePath", comment="shell link is saved with a relative path string"),
            Bit(name="HasWorkingDir", comment="shell link is saved with a working directory string"),
            Bit(name="HasArguments", comment="shell link is saved with command line arguments"),
            Bit(name="HasIconLocation", comment="shell link is saved with an icon location string"),
            Bit(name="IsUnicode", comment="shell link contains Unicode encoded strings. This bit SHOULD be set. If this bit is set, the StringData section contains Unicode-encoded strings; otherwise, it contains strings that are encoded using the system default code page"),
            Bit(name="ForceNoLinkInfo", comment="LinkInfo structure is ignored"),
            Bit(name="HasExpString", comment="shell link is saved with an EnvironmentVariableDataBlock"),
            Bit(name="RunInSeparateProcess", comment="target is run in a separate virtual machine when launching a link target that is a 16-bit application"),
            NullBits(1),
            Bit(name="HasDarwinID", comment="shell link is saved with a DarwinDataBlock"),
            Bit(name="RunAsUser", comment="application is run as a different user when the target of the shell link is activated"),
            Bit(name="HasExpIcon", comment="shell link is saved with an IconEnvironmentDataBlock"),
            Bit(name="NoPidlAlias", comment="file system location is represented in the shell namespace when the path to an item is parsed into an IDList"),
            NullBits(1),
            Bit(name="RunWithShimLayer", comment="shell link is saved with a ShimDataBlock"),
            Bit(name="ForceNoLinkTrack", comment="TrackerDataBlock section is ignored"),
            Bit(name="EnableTargetMetadata", comment="shell link attempts to collect target properties and store them in the PropertyStoreDataBlock"),
            Bit(name="DisableLinkPathTracking", comment="EnvironmentVariableDataBlock is ignored"),
            Bit(name="DisableKnownFolderTracking", comment="SpecialFolderDataBlock section and the KnownFolderDataBlock section are ignored when loading the shell link"),
            Bit(name="DisableKnownFolderAlias", comment="if the link has a KnownFolderDataBlock (section 2.5.6), the unaliased form of the known folder IDList SHOULD be used when translating the target IDList at the time that the link is loaded"),
            Bit(name="AllowLinkToLink", comment="creating a link that references another link is enabled. Otherwise, specifying a link as the target IDList SHOULD NOT be allowed"),
            Bit(name="UnaliasOnSave", comment="saving a link for which the target IDList is under a known folder, either the unaliased form of that known folder or the target IDList SHOULD be used"),
            Bit(name="PreferEnvironmentPath", comment="target IDList SHOULD NOT be stored; instead, the path specified in the EnvironmentVariableDataBlock section) SHOULD be used to refer to the target"),
            Bit(name="KeepLocalIDListForUNCTarget", comment="when the target is a UNC name that refers to a location on a local machine, the local path IDList in the PropertyStoreDataBlock section SHOULD be stored, so it can be used when the link is loaded on the local machine"),
            NullBits(1),
            name="LinkFlags", comment="specifies information about the shell link and the presence of optional portions of the structure")
        yield BitsField(
            Bit(name="ReadOnly", comment="file or directory is read-only"),
            Bit(name="Hidden", comment="file or directory is hidden"),
            Bit(name="System", comment="file or directory is part of the operating system or is used exclusively by the operating system"),
            NullBits(1),
            Bit(name="Directory", comment="link target is a directory instead of a file"),
            Bit(name="Archive", comment="file or directory is an archive file"),
            NullBits(1),
            Bit(name="Normal", comment="file or directory has no other flags set. If this bit is 1, all other bits in this structure MUST be clear"),
            Bit(name="Temporary", comment="file is being used for temporary storage"),
            Bit(name="Sparse", comment="file is a sparse file"),
            Bit(name="ReparsePoint", comment="file or directory has an associated reparse point"),
            Bit(name="Compressed", comment="file or directory is compressed"),
            Bit(name="Offline", comment="data of the file is not immediately available"),
            Bit(name="NotContentIndexed", comment="the contents of the file need to be indexed"),
            Bit(name="Encrypted", comment="file or directory is encrypted"),
            Bit(name="IntegrityStream", comment="directory or user data stream is configured with integrity (only supported on ReFS volumes)"),
            Bit(name="Virtual", comment="file or directory is virtual"),
            Bit(name="NoScrubData", comment="user data stream not to be read by the background data integrity scanner"),
            NullBits(14),
            name="FileAttributesFlags", comment="specifies information about the link target")
        yield Filetime(name="CreationTime", comment="creation time of the link target in UTC")
        yield Filetime(name="AccessTime", comment="access time of the link target in UTC")
        yield Filetime(name="WriteTime", comment="write time of the link target in UTC")
        yield UInt32(name="FileSize", comment="size, in bytes, of the link target")
        yield UInt32(name="IconIndex", comment="index of an icon within a given icon location")
        yield UInt32(name="ShowCommand", comment="expected window state of an application launched by the link", values=[
            ("SW_SHOWNORMAL", 1),
            ("SW_SHOWMAXIMIZED", 3),
            ("SW_SHOWMINNOACTIVE", 7),
        ])
        yield UInt16(name="HotKey")
        yield Unused(10)


# https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc

class ShellItem(Struct):

    def parse(self):
        sz = yield UInt16(name="ItemIDSize", comment="size, in bytes, of the ItemID structure, including the ItemIDSize")
        if sz < 2:
            raise FatalError("Invalid ItemIDSize")
        if sz - 2:
            yield Bytes(sz-2, name="Data", comment="shell data source-defined data that specifies an item") 


class RootFolderShellItem(Struct):

    def parse(self):
        sz = yield UInt16(name="ItemIDSize", comment="size, in bytes, of the ItemID structure, including the ItemIDSize")
        if sz < 2:
            raise FatalError("Invalid ItemIDSize")
        flags = yield UInt8(name="ShellItemClass")
        yield UInt8(name="SortIndex", comment="")
        yield GUID(name="Identifier", values=SPECIAL_FOLDERS)
        while len(self) < sz:
            yield ExtensionBlock()


class VolumeShellItem(Struct):

    def parse(self):
        sz = yield UInt16(name="ItemIDSize", comment="size, in bytes, of the ItemID structure, including the ItemIDSize")
        if sz < 2:
            raise FatalError("Invalid ItemIDSize")
        flags = yield UInt8(name="ShellItemClass")
        if flags & 0x1:
            yield CString(name="Path")
        else:
            yield UInt8(name="Flag", comment="")
        if len(self) < sz:
            yield Unused(sz - len(self))


class FileEntryShellItem(Struct):

    def parse(self):
        sz = yield UInt16(name="ItemIDSize", comment="size, in bytes, of the ItemID structure, including the ItemIDSize")
        if sz < 2:
            raise FatalError("Invalid ItemIDSize")
        flags = yield UInt8(name="ShellItemClass")
        flags = flags & 0x8f
        yield Unused(1)
        yield UInt32(name="Filesize")
        yield DosDate(name="ModificationDate")
        yield DosTime(name="ModificationTime")
        yield BitsField(
            Bit(name="ReadOnly", comment="file or directory is read-only"),
            Bit(name="Hidden", comment="file or directory is hidden"),
            Bit(name="System", comment="file or directory is part of the operating system or is used exclusively by the operating system"),
            NullBits(1),
            Bit(name="Directory", comment="link target is a directory instead of a file"),
            Bit(name="Archive", comment="file or directory is an archive file"),
            NullBits(1),
            Bit(name="Normal", comment="file or directory has no other flags set. If this bit is 1, all other bits in this structure MUST be clear"),
            Bit(name="Temporary", comment="file is being used for temporary storage"),
            Bit(name="Sparse", comment="file is a sparse file"),
            Bit(name="ReparsePoint", comment="file or directory has an associated reparse point"),
            Bit(name="Compressed", comment="file or directory is compressed"),
            Bit(name="Offline", comment="data of the file is not immediately available"),
            Bit(name="NotContentIndexed", comment="the contents of the file need to be indexed"),
            Bit(name="Encrypted", comment="file or directory is encrypted"),
            Bit(name="IntegrityStream", comment="directory or user data stream is configured with integrity (only supported on ReFS volumes)"),
            name="FileAttributesFlags", comment="specifies information about the link target")
        if flags & 4:
            yield CStringUtf16le(name="PrimaryName", comment="path")
        else:
            s = yield CString(name="PrimaryName", comment="path")
            if len(s) % 2 == 0 and len(self) < sz:
                yield Unused(1, name="Padding")
        while len(self) + 8 <= sz and self.look_ahead(8)[6:] == b"\xef\xbe":
            yield ExtensionBlock(name="Extension")
        if len(self) < sz:
            yield Unused(sz - len(self))

class LinkTargetIDList(Struct):

    def parse(self):
        sz = yield UInt16(name="IDListSize", comment="size, in bytes, of the IDList field")
        if sz < 2:
            raise FatalError("Invalid list size")
        while len(self) + 2 < sz:
            if len(self) + 3 < sz:
                itemclass, = struct.unpack("<B", self.look_ahead(3)[2:])
            else:
                itemclass = 0
            if itemclass & 0x70 == 0x30:
                yield FileEntryShellItem()
            elif itemclass & 0x70 == 0x20:
                yield VolumeShellItem()
            elif itemclass == 0x1f:
                yield RootFolderShellItem()
            else:
                yield ShellItem()
        terminator = yield UInt16(name="Terminator", comment="list terminator")
        if terminator:
            raise FatalError("Terminator not null")


class ExtensionBlock(Struct):

    def parse(self):
        sz = yield UInt16(name="BlockSize", comment="size, in bytes, of the extension block")
        if sz < 2:
            return
        ver = yield UInt16(name="ExtensionVersion", values=[
            ("WinXP", 3),
            ("Vista", 7),
            ("Win7/8", 8),
            ("Win8.1/10", 9),
            ])
        typ = yield UInt32(name="Signature")
        if typ >> 16 != 0xbeef:
            raise FatalError("Invalid block signature")
        if typ == 0xbeef0003:
            yield GUID(name="ShellFolder", comment="Shell folder identifier")
            yield UInt16(name="FirstExtensionOffset", comment="first extension block version offset")

        elif typ == 0xbeef0004:
            # https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc#extension_block_0xbeef004
            yield DosDate(name="CreationDate")
            yield DosTime(name="CreationTime")
            yield DosDate(name="AccessDate")
            yield DosTime(name="AccessTime")
            yield Unused(2)
            if ver >= 7:
                yield Unused(2)
                yield FileReference()
                yield Unused(8)
            if ver >= 3:
                lssz = yield UInt16(name="LongStringSize", comment="size of long name and localized name or 0 if no localized name is present")
            if ver >= 9:
                yield Unused(4)
            if ver >= 8:
                yield Unused(4)
            if ver >= 3:
                yield CStringUtf16le(name="LongName", comment="path")
            if lssz:
                if ver >= 7:
                    yield CStringUtf16le(name="LocalName", comment="path")
                elif ver >= 3:
                    yield CString(name="LocalName", comment="path")
            if ver >= 3:
                yield UInt16(name="FirstExtensionOffset", comment="first extension block version offset")
        if len(self) < sz:
            yield Unused(sz - len(self))


class FileReference(Struct):

    def parse(self):
        yield Bytes(6, name="MftIndex", comment="MFT entry index")
        yield UInt16(name="SeqNum", comment="Sequence number")

class VolumeId(Struct):

    def parse(self):
        sz = yield UInt32(name="VolumeIDSize", comment="size, in bytes, of this structure")
        yield UInt32(name="DriveType", comment="type of drive the link target is stored on", values=[
            ("DRIVE_UNKNOWN", 0),
            ("DRIVE_NO_ROOT_DIR", 1),
            ("DRIVE_REMOVABLE", 2),
            ("DRIVE_FIXED", 3),
            ("DRIVE_REMOTE", 4),
            ("DRIVE_CDROM", 5),
            ("DRIVE_RAMDISK", 6),
            ])
        yield UInt32(name="DriveSerialNumber", comment="drive serial number of the volume the link target is stored on")
        off = yield Offset32(name="VolumeLabelOffset", base=self.offset, comment="location of a string that contains the volume label of the drive that the link target is stored on")
        if off == 0x14:
            yield Offset32(name="VolumeLabelOffsetUnicode", base=self.offset, comment="location of a unicode string that contains the volume label of the drive that the link target is stored on")
            rest = sz - len(self)
            if rest > 0:
                yield StringUtf16le(rest // 2, zero_terminated=True, name="Data")
        else:
            rest = sz - len(self)
            if rest > 0:
                yield String(rest, zero_terminated=True, name="Data")
        if len(self) < sz:
            yield Unused(sz - len(self), name="Padding")



class CommonNetworkRelativeLink(Struct):

    def parse(self):
        sz = yield UInt32(name="CommonNetworkRelativeLinkSize", comment="size, in bytes, of this structure")
        flags = yield BitsField(
            Bit(name="ValidDevice", comment="DeviceNameOffset field contains an offset to the device name"),
            Bit(name="ValidNetType", comment="the NetProviderType field contains the network provider type"),
            NullBits(30),
            name="Flags")
        nnoff = yield Offset32(name="NetNameOffset", base=self.offset, comment="location of the NetName field")
        yield Offset32(name="DeviceNameOffset", base=self.offset, comment="location of the DeviceNameOffset field")
        yield UInt32(name="NetworkProviderType", comment="specifies the type of network provider", values=[
            ("WNNC_NET_AVID", 0x001A0000), 
            ("WNNC_NET_DOCUSPACE", 0x001B0000), 
            ("WNNC_NET_MANGOSOFT", 0x001C0000), 
            ("WNNC_NET_SERNET", 0x001D0000), 
            ("WNNC_NET_RIVERFRONT1", 0x001E0000), 
            ("WNNC_NET_RIVERFRONT2", 0x001F0000), 
            ("WNNC_NET_DECORB", 0x00200000), 
            ("WNNC_NET_PROTSTOR", 0x00210000), 
            ("WNNC_NET_FJ_REDIR", 0x00220000), 
            ("WNNC_NET_DISTINCT", 0x00230000), 
            ("WNNC_NET_TWINS", 0x00240000), 
            ("WNNC_NET_RDR2SAMPLE", 0x00250000), 
            ("WNNC_NET_CSC", 0x00260000), 
            ("WNNC_NET_3IN1", 0x00270000), 
            ("WNNC_NET_EXTENDNET", 0x00290000), 
            ("WNNC_NET_STAC", 0x002A0000), 
            ("WNNC_NET_FOXBAT", 0x002B0000), 
            ("WNNC_NET_YAHOO", 0x002C0000), 
            ("WNNC_NET_EXIFS", 0x002D0000), 
            ("WNNC_NET_DAV", 0x002E0000), 
            ("WNNC_NET_KNOWARE", 0x002F0000), 
            ("WNNC_NET_OBJECT_DIRE", 0x00300000), 
            ("WNNC_NET_MASFAX", 0x00310000), 
            ("WNNC_NET_HOB_NFS", 0x00320000), 
            ("WNNC_NET_SHIVA", 0x00330000), 
            ("WNNC_NET_IBMAL", 0x00340000), 
            ("WNNC_NET_LOCK", 0x00350000), 
            ("WNNC_NET_TERMSRV", 0x00360000), 
            ("WNNC_NET_SRT", 0x00370000), 
            ("WNNC_NET_QUINCY", 0x00380000), 
            ("WNNC_NET_OPENAFS", 0x00390000), 
            ("WNNC_NET_AVID1", 0X003A0000), 
            ("WNNC_NET_DFS", 0x003B0000), 
            ("WNNC_NET_KWNP", 0x003C0000), 
            ("WNNC_NET_ZENWORKS", 0x003D0000), 
            ("WNNC_NET_DRIVEONWEB", 0x003E0000), 
            ("WNNC_NET_VMWARE", 0x003F0000), 
            ("WNNC_NET_RSFX", 0x00400000), 
            ("WNNC_NET_MFILES", 0x00410000), 
            ("WNNC_NET_MS_NFS", 0x00420000), 
            ("WNNC_NET_GOOGLE", 0x00430000), 
        ])
        if nnoff > 0x14:
            yield Offset32(name="NetNameOffsetUnicode", base=self.offset, comment="location of the NetName field")
            yield Offset32(name="DeviceNameOffsetUnicode", base=self.offset, comment="location of the DeviceName field")
        yield CString(name="NetName", comment="NULL–terminated string, as defined by the system default code page, which specifies a server share path")
        yield CString(name="DeviceName", comment="NULL–terminated string, as defined by the system default code page, which specifies a device")
        if nnoff > 0x14:
            yield CStringUtf16le(name="NetNameUnicode", comment="NULL–terminated string, as defined by the system default code page, which specifies a server share path")
            yield CStringUtf16le(name="DeviceNameUnicode", comment="NULL–terminated string, as defined by the system default code page, which specifies a device")
        if len(self) < sz:
            yield Unused(sz - len(self), name="Padding")


class LinkInfo(Struct):

    def parse(self):
        sz = yield UInt32(name="LinkInfoSize", comment="size, in bytes, of the LinkInfo structure")
        hdrsz = yield UInt32(name="LinkInfoHeaderSize", comment="size, in bytes, of the LinkInfo header section, which is composed of the LinkInfoSize, LinkInfoHeaderSize, LinkInfoFlags, VolumeIDOffset, LocalBasePathOffset, CommonNetworkRelativeLinkOffset, CommonPathSuffixOffset fields, and, if included, the LocalBasePathOffsetUnicode and CommonPathSuffixOffsetUnicode fields")
        flags = yield BitsField(
            Bit(name="VolumeIDAndLocalBasePath", comment="the VolumeID and LocalBasePath fields are present, and their locations are specified by the values of the VolumeIDOffset and LocalBasePathOffset fields, respectively"),
            Bit(name="CommonNetworkRelativeLinkAndPathSuffix", comment="the CommonNetworkRelativeLink field is present, and its location is specified by the value of the CommonNetworkRelativeLinkOffset field"),
            NullBits(30),
            name="Flags")
        yield Offset32(name="VolumeIDOffset", base=self.offset, comment="location of the VolumeID field")
        yield Offset32(name="LocalBasePathOffset", base=self.offset, comment="location of the LocalBasePath field")
        yield Offset32(name="CommonNetworkRelativeLinkOffset", base=self.offset, comment="location of the CommonNetworkRelativeLink field")
        yield Offset32(name="CommonPathSuffixOffset", base=self.offset, comment="location of the CommonPathSuffix field")
        if hdrsz >= 0x24:
            lbpuoff = yield Offset32(name="LocalBasePathOffsetUnicode", base=self.offset, comment="location of the LocalBasePathUnicode field")
            cpsuoff = yield Offset32(name="CommonPathSuffixOffsetUnicode", base=self.offset, comment="location of the CommonPathSuffixUnicode field")
        if flags["VolumeIDAndLocalBasePath"]:
            yield VolumeId()
            yield CString(name="LocalBasePath", comment="optional, NULL–terminated string, defined by the system default code page, which is used to construct the full path to the link item or link target by appending the string in the CommonPathSuffix field")
        if flags["CommonNetworkRelativeLinkAndPathSuffix"]:
            yield CommonNetworkRelativeLink()
            yield CString(name="CommonPathSuffix", comment="NULL–terminated string, defined by the system default code page, which is used to construct the full path to the link item or link target by being appended to the string in the LocalBasePath field")
        if flags["VolumeIDAndLocalBasePath"] and hdrsz >= 0x24 and lbpuoff:
            yield CStringUtf16le(name="LocalBasePathUnicode", comment="optional, NULL–terminated string, defined by the system default code page, which is used to construct the full path to the link item or link target by appending the string in the CommonPathSuffix field")
        if hdrsz >= 0x24 and cpsuoff:
            yield CStringUtf16le(name="CommonPathSuffixUnicode", comment="optional, NULL–terminated, Unicode string that is used to construct the full path to the link item or link target by being appended to the string in the LocalBasePathUnicode field")
        if len(self) < sz:
            yield Unused(sz - len(self), name="Padding")



class DataString(Struct):

    def parse(self):
        sz = yield UInt16(name="Size", comment="string size")
        if sz:
            yield String(sz, zero_terminated=False, name="String")

class DataStringUnicode(Struct):

    def parse(self):
        sz = yield UInt16(name="Size", comment="string size")
        if sz:
            yield StringUtf16le(sz, zero_terminated=False, name="String")     


class UnknownBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz < 8:
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        if sz - 8 > 0:
            yield Bytes(sz-8, name="Data", comment="block data") 

class EnvironmentVariableDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz not in (260 + 8, 260 + 8 + 520):
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        yield String(260, name="TargetAnsi", zero_terminated=True, comment="NULL-terminated string, defined by the system default code page, which specifies a path to environment variable information")
        if sz > 260 + 8:
            yield StringUtf16le(260, name="TargetUnicode", zero_terminated=True, comment="NULL-terminated string, defined by the system default code page, which specifies a path to environment variable information")


class IconEnvironmentDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz not in (260 + 8, 260 + 8 + 520):
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        yield String(260, name="TargetAnsi", zero_terminated=True, comment="NULL-terminated string, defined by the system default code page, which specifies a path to environment variable information")
        if sz > 260 + 8:
            yield StringUtf16le(260, name="TargetUnicode", zero_terminated=True, comment="NULL-terminated string, defined by the system default code page, which specifies a path to environment variable information")



class KnownFolderDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz != 0x1c:
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        yield GUID(name="Folder", values=KNOWN_FOLDERS)
        yield UInt32(name="Offset", comment="location of the ItemID of the first child segment of the IDList specified by KnownFolderID")


class SpecialFolderDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz != 0x10:
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        yield UInt32(name="SpecialFolderID", comment="integer that specifies the folder integer ID")
        yield UInt32(name="Offset", comment="location of the ItemID of the first child segment of the IDList specified by KnownFolderID")


class ShimDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz < 0x88:
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        yield StringUtf16le((sz - 8) // 2, zero_terminated=True, name="LayerName", comment="Unicode string that specifies the name of a shim layer to apply to a link target when it is being activated")


class PropertyStorage(Struct):

    def parse(self):
        sz = yield UInt32(name="StorageSize", comment="size, in bytes, of the storage structure")
        yield String(4, zero_terminated=False, name="Version", comment="equal to 0x53505331")
        guid = yield GUID(name="Format", comment="GUID that specifies the semantics and expected usage of the properties contained in this Serialized Property Storage structure", values=[
            ("STORAGE", "B725F130-47EF-101A-A5F1-02608C9EEBAC"),
            ("SHELL_DETAILS", "28636AA6-953D-11D2-B5D6-00C04FD918D0"),
            ("CACHE", "446D16B1-8DAD-4870-A748-402EA43D788C"),
            ("SEARCH", "E3E0584C-B788-4A5A-BB20-7F5A44C9ACDD"),
        ])
        is_string = guid.upper() == "D5CDD505-2E9C-101B-9397-08002B2CF9AE"
        enums = PROPERTY_STORAGE_ENUMS.get(guid, [])
        while len(self) + 4 < sz - 4:
            value_size, = struct.unpack("<I", self.look_ahead(4))
            if value_size == 0:
                break
            if is_string:
                yield PropertyNamedValue()
            else:
                yield PropertyValue(enums)
        yield Unused(4, name="Terminator")
        if len(self) < sz:
            yield Unused(sz - len(self), name="Padding")
        

class PropertyValue(Struct):
    def __init__(self, enums, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.__enums = enums
    
    def parse(self):
        sz = yield UInt32(name="ValueSize", comment="size, in bytes, of the storage structure")
        yield UInt32(name="Id", comment="identity of the property", values=self.__enums)
        yield Unused(1)
        yield SummaryProperty(sz - len(self), name="TypedValue")
        if len(self) < sz:
            yield Unused(sz - len(self), name="Padding")


class PropertyNamedValue(Struct):

    def parse(self):
        sz = yield UInt32(name="ValueSize", comment="size, in bytes, of the storage structure")
        namesz = yield UInt32(name="NameSize", comment="size, in bytes, of the structure name")
        yield Unused(1)
        yield String(namesz, zero_terminated=True, name="Name", comment="property name")
        yield SummaryProperty(sz - len(self), name="TypedValue")
        if len(self) < sz:
            yield Unused(sz - len(self), name="Padding")

class PropertyStoreDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz < 0xC:
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        while len(self) < sz - 4:
            storage_size, = struct.unpack("<I", self.look_ahead(4))
            if storage_size == 0:
                break
            yield PropertyStorage()
        yield Unused(4, name="Terminator")
        if len(self) < sz:
            yield Unused(sz - len(self), name="Padding")



class TrackerDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz != 0x60:
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        sz = yield UInt32(name="Length", comment="size of the rest of the TrackerDataBlock structure, including this Length field")
        if sz != 0x58:
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="Version", comment="must be 0")
        yield String(16, zero_terminated=True, name="MachineID", comment="NULL–terminated character string, as defined by the system default code page, which specifies the NetBIOS name of the machine where the link target was last known to reside")
        yield Array(2, GUID(), name="Droid", comment="Two values in GUID packet representation ([MS-DTYP] section 2.3.4.2) that are used to find the link target with the Link Tracking service, as described in [MS-DLTW]")
        yield Array(2, GUID(), name="DroidBirth", comment="Two values in GUID packet representation that are used to find the link target with the Link Tracking service")


class ConsoleDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        if sz != 0xCC:
            raise FatalError("Invalid BlockSize")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        yield UInt16(name="FillAttributes", comment="fill attributes that control the foreground and background text colors in the console window (index into ColorTable)")
        yield UInt16(name="PopupFillAttributes", comment="fill attributes that control the foreground and background text color in the console window popup  (index into ColorTable)")
        yield UInt16(name="ScreenBufferSizeX", comment="horizontal size (X axis), in characters, of the console window buffer")
        yield UInt16(name="ScreenBufferSizeY", comment="vertical size (Y axis), in characters, of the console window buffer")
        yield UInt16(name="WindowSizeX", comment="horizontal size (X axis), in characters, of the console window")
        yield UInt16(name="WindowSizeY", comment="vertical size (Y axis), in characters, of the console window")
        yield UInt16(name="WindowOriginX", comment="horizontal coordinate (X axis), in pixels, of the console window origin")
        yield UInt16(name="WindowOriginY", comment="vertical coordinate (Y axis), in pixels, of the console window origin")
        yield Unused(8)
        yield UInt32(name="FontSize", comment="size, in pixels, of the font used in the console window. The two most significant bytes contain the font height and the two least significant bytes contain the font width. For vector fonts, the width is set to zero")
        yield UInt32(name="FontFamily", comment="family of the font used in the console window")
        yield UInt32(name="FontWeight", comment="font weight")
        yield StringUtf16le(32, zero_terminated=True, name="FontName", comment="font name")
        yield UInt32(name="CursorSize", comment="size of the cursor, in pixels, used in the console window")
        yield UInt32(name="FullScreen", comment="whether to open the console window in full-screen mode (> 0)")
        yield UInt32(name="QuickEdit", comment="whether to open the console window in quick-edit mode (> 0)")
        yield UInt32(name="InsertMode", comment="whether to open the console window in insert mode (> 0)")
        yield UInt32(name="AutoPosition", comment="whether to open the automatically position the window (> 0)")
        yield UInt32(name="HistoryBufferSize", comment="size, in characters, of the buffer that is used to store a history of user input into the console window")
        yield UInt32(name="NumberOfHistoryBuffers", comment="the number of history buffers to use")
        yield UInt32(name="HistoryNoDup", comment="whether to remove duplicates in the history buffer (> 0)")
        yield Array(16, RGBA(), name="ColorTable")

class VistaAndAboveIDListDataBlock(Struct):

    def parse(self):
        sz = yield UInt32(name="BlockSize", comment="size, in bytes, of the block structure")
        yield UInt32(name="BlockClass", values=EXTRA_BLOCK_enum)
        while True:
            if self.look_ahead(2) == b"\x00\x00":
                yield UInt16(name="Terminator")
                break
            itemclass = self.look_ahead(3)[2]
            if itemclass & 0x70 == 0x30:
                yield FileEntryShellItem()
            elif itemclass & 0x70 == 0x20:
                yield VolumeShellItem()
            elif itemclass == 0x1f:
                yield RootFolderShellItem()
            else:
                yield ShellItem()

EXTRA_BLOCK = {
        0xA0000001: EnvironmentVariableDataBlock,
        0xA0000002: ConsoleDataBlock,
        0xA0000003: TrackerDataBlock,
        0xA0000005: SpecialFolderDataBlock,
        0xA0000007: IconEnvironmentDataBlock,
        0xA0000008: ShimDataBlock,
        0xA0000009: PropertyStoreDataBlock,
        0xA000000B: KnownFolderDataBlock,
        0xA000000C: VistaAndAboveIDListDataBlock,
}
EXTRA_BLOCK_enum = [(v.__name__.replace("Block", ""), k) for k,v in EXTRA_BLOCK.items()]


class LnkAnalyzer(FileTypeAnalyzer):
    category = malcat.FileType.FILESYSTEM
    name = "LNK"
    regexp = r"\x4C\x00\x00\x00\x01\x14\x02\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x00\x00\x46"


    def parse(self, hint):
        hdr = yield ShellLinkHeader()
        self.confirm()

        if hdr["LinkFlags"]["HasLinkTargetIDList"]:
            yield LinkTargetIDList()

        if hdr["LinkFlags"]["HasLinkInfo"]:
            yield LinkInfo()

        string_data_start = self.tell()
        if hdr["LinkFlags"]["IsUnicode"]:
            str_class = DataStringUnicode
        else:
            str_class = DataString
        if hdr["LinkFlags"]["HasName"]:
            yield str_class(name="NameString", category=Type.DATA)
        if hdr["LinkFlags"]["HasRelativePath"]:
            yield str_class(name="RelativePath", category=Type.DATA)
        if hdr["LinkFlags"]["HasWorkingDir"]:
            yield str_class(name="WorkingDir", category=Type.DATA)
        if hdr["LinkFlags"]["HasArguments"]:
            yield str_class(name="CommandLineArguments", category=Type.DATA)
        if hdr["LinkFlags"]["HasIconLocation"]:
            yield str_class(name="IconLocation", category=Type.DATA)

        string_data_sz= self.tell() - string_data_start
        self.add_section("StringData", string_data_start, string_data_sz)

        extra_data_start = self.tell()
        while self.remaining() >= 8:
            block_sz, block_sig = struct.unpack("<II", self.read(self.tell(), 8))
            if block_sz < 4:
                break
            block_class = EXTRA_BLOCK.get(block_sig, UnknownBlock)
            yield block_class(category=Type.DATA)
        if self.remaining() >= 4:
            yield Unused(4, name="TerminatorBlock")
        extra_data_sz= self.tell() - extra_data_start
        self.add_section("ExtraData", extra_data_start, extra_data_sz)
