from filetypes.base import *
from filetypes.PE import Resource
import re
import struct
import math
import types

# most of the info comes from https://www.codeproject.com/Articles/12585/The-NET-File-Format and 
# https://www.ecma-international.org/wp-content/uploads/ECMA-335_5th_edition_december_2010.pdf


TABLES_NAMES = {
        0: "Module",
        1: "TypeRef",
        2: "TypeDef",
        3: "FieldPtr",
        4: "Field",
        5: "MethodDefPtr",
        6: "MethodDef",
        7: "ParamPtr",
        8: "Param",
        9: "InterfaceImpl",
        10: "MemberRef",
        11: "Constant",
        12: "CustomAttribute",
        13: "FieldMarshal",
        14: "DeclSecurity",
        15: "ClassLayout",
        16: "FieldLayout",
        17: "StandAloneSig",
        18: "EventMap",
        19: "EventPtr",
        20: "Event",
        21: "PropertyMap",
        22: "PropertyPtr",
        23: "Property",
        24: "MethodSemantics",
        25: "MethodImpl",
        26: "ModuleRef",
        27: "TypeSpec",
        28: "ImplMap",
        29: "FieldRVA",
        30: "EncLog",
        31: "EncMap",
        32: "Assembly",
        33: "AssemblyProcessor",
        34: "AssemblyOS",
        35: "AssemblyRef",
        36: "AssemblyRefProcessor",
        37: "AssemblyRefOS",
        38: "File",
        39: "ExportedType",
        40: "ManifestResource",
        41: "NestedClass",
        42: "GenericParam",
        43: "MethodSpec",
        44: "GenericParamConstraint",
        }

COMPOSED_INDEXES = {
        "ResolutionScope": (2, {
            "Module":0,
            "ModuleRef":1, 
            "AssemblyRef":2, 
            "TypeRef":3 }),
        "TypeDefOrRef": (2, {
            "TypeDef":0, 
            "TypeRef":1, 
            "TypeSpec":2 }),
        "MemberRefParent": (3, {
            "TypeDef":0, 
            "TypeRef":1, 
            "ModuleRef":2, 
            "MethodDef":3, 
            "TypeSpec":4 }),
        "HasConstant": (2, {
            "Field":0, 
            "Param":1, 
            "Property":2 }),
        "HasCustomAttribute": (5, {
            "MethodDef":0, 
            "Field":1,
            "TypeRef":2, 
            "TypeDef":3, 
            "Param":4, 
            "InterfaceImpl":5,
            "MemberRef":6, 
            "Module":7, 
            "Permission":8, 
            "Property":9, 
            "Event":10, 
            "StandAloneSig":11, 
            "ModuleRef":12,
            "TypeSpec":13, 
            "Assembly":14, 
            "AssemblyRef":15, 
            "File":16, 
            "ExportedType":17, 
            "ManifestResource":18,
            "GenericParam": 19,
            "GenericParamConstraint": 20,
            "MethodSpec": 21,
            }),
        "CustomAttributeType": (3, {
            "MethodDef":2,
            "MemberRef":3 }),
        "HasFieldMarshal": (1, {
            "Field":0,
            "Param":1 }),
        "HasDeclSecurity": (2, {
            "TypeDef":0,
            "MethodDef":1,
            "Assembly":2 }),
        "HasSemantics": (1, {
            "Event":0,
            "Property":1 }),
        "MemberForwarded": (1, {
            "Field":0,
            "MethodDef":1 }),
        "MethodDefOrRef": (1, {
            "MethodDef":0,
            "MemberRef":1 }),
        "Implementation": (2, {
            "File":0,
            "AssemblyRef":1,
            "ExportedType": 2}),
        "TypeOrMethodDef": (1, {
            "TypeDef":0,
            "MethodDef":1 }),
        }

ELEMENT_TYPE = {
        0x01: "void", 
        0x02: "bool", 
        0x03: "char", 
        0x04: "int8", 
        0x05: "uint8", 
        0x06: "int16", 
        0x07: "uint16", 
        0x08: "int32", 
        0x09: "uint32", 
        0x0a: "int64", 
        0x0b: "uint64", 
        0x0c: "float", 
        0x0d: "double", 
        0x0e: "string", 
        0x18: "System.IntPtr", 
        0x19: "System.UIntPtr", 
        0x1c: "System.Object", 
        }

def align(val, what):
    if val % what:
        val += what - (val % what)
    return val

def toprintable(s):
    return s.encode("ascii", errors="backslashreplace").decode("ascii") 

class InfoDataDirectory(Struct):

    def parse(self):
        yield Rva(name="Rva")
        yield UInt32(name="Size")


class DotnetResource(Struct):

    def __init__(self, max_size, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.max_size = max_size

    def parse(self):
        # https://referencesource.microsoft.com/#mscorlib/system/resources/resourcetypecode.cs,77874335894760b1
        ti = yield UInt8(name="ResourceTypeCode", values=[
            ("Null", 0),
            ("String", 1),
            ("Boolean", 2),
            ("Char", 3),
            ("Byte", 4),
            ("SByte", 5),
            ("Int16", 6),
            ("UInt16", 7),
            ("Int32", 8),
            ("UInt32", 9),
            ("Int64", 0xa),
            ("UInt64", 0xb),
            ("Single", 0xc),
            ("Double", 0xd),
            ("Decimal", 0xe),
            ("DateTime", 0xf),
            ("TimeSpan", 0x10),
            ("BytesArray", 0x20),
            ("Stream", 0x21),
        ])
        if ti in (0x20, 0x21):
            resource_size = yield UInt32(name="ResourceSize")
            yield Bytes(min(self.max_size - len(self), resource_size), name="Data")
        else:
            yield Bytes(self.max_size - len(self), name="Data")

class CLIHeader(Struct):
    def parse(self):
        soh = yield UInt32(name="HeaderSize", comment="size of header")
        if soh != 0x48:
            raise FatalError("Invalid CLR Header size")
        yield UInt16(name="CLRMajor", comment="CLR major version")
        yield UInt16(name="CLRMinor", comment="CLR minjor version")
        yield InfoDataDirectory(name="Metadata", comment="metadata directory")
        flags = yield BitsField(
            Bit(name="ILOnly", comment="contains only IL code so that it is not required to run on a specific CPU"),
            Bit(name="32Bits", comment="may only be loaded into a 32-bit process"),
            Bit(name="ILLibrary", comment=""),
            Bit(name="StrongNamesSigned", comment=""),
            Bit(name="NativeEntryPoint", comment=""),
            NullBits(11),
            Bit(name="TrackDebugData", comment="runtime and JIT are required to track debugging information about methods"),
            NullBits(15),
            name="Flags", comment="flags")
        if flags["NativeEntryPoint"]:
            yield Rva(name="EntryPointRva", comment="native entry point")
        else:
            yield UInt32(name="EntryPointToken", comment="entry point token")
        yield InfoDataDirectory(name="Resources", comment=".net resources")
        yield InfoDataDirectory(name="StrongNameSignature", comment="strong names info")
        yield InfoDataDirectory(name="CodeManagerTable", comment="")
        yield InfoDataDirectory(name="VTableFixups", comment="")
        yield InfoDataDirectory(name="ExportAddressTableJumps", comment="")
        yield InfoDataDirectory(name="ManagedNativeHeader", comment="precompiled image info (internal use only - set to zero)")


class MetadataHeader(Struct):

    def parse(self):
        sig = yield UInt32(name="Signature", comment="always 0x424a5342")
        if sig != 0x424a5342:
            raise FatalError("Invalid MetadataHeder signature")
        yield UInt16(name="Major", comment="metadata major version")
        yield UInt16(name="Minor", comment="metadata minor version")
        yield UInt32(name="Reserved")
        vl = yield UInt32(name="VersionLength", comment="length of following field")
        yield String(vl, zero_terminated=False, name="Version")
        yield UInt16(name="Flags", comment="should be zero")
        nos = yield UInt16(name="NumberOfStreams", comment="number of streams following")
        for i in range(nos):
            yield StreamHeader(name="StreamHeader[{}]".format(i))


class StreamHeader(Struct):

    def parse(self):
        yield UInt32(name="Offset", comment="offset of stream, from metadata header")
        yield UInt32(name="Size", comment="size of stream, from metadata header")
        yield CString(name="Name", comment="stream name")
        yield Align(4)



class TablesHeader(Struct):

    def parse(self):
        res = yield UInt32(name="Reserved", comment="always 0")
        if res != 0:
            raise FatalError("Reserved1 is not 0")
        yield UInt8(name="Major", comment="tables major version")
        yield UInt8(name="Minor", comment="tables minor version")
        bf = yield BitsField(
                Bit(name="HugeStringStream", comment="size of #String stream >= 2^16"),
                Bit(name="HugeGuidStream", comment="size of #GUID stream >= 2^16"),
                Bit(name="HugeBlobStream", comment="size of #Blob stream >= 2^16"),
                NullBits(3),
                Bit(name="ExtraDword", comment="extra dword appended after Items table (Confuser)"),
                name="HeapOffsetSizes", comment="encodes how wide indexes into the various heaps are")
        res = yield UInt8(name="Reserved2", comment="always 1")
        # prepare bitfield
        bits = []
        for i in range(64):
            name = TABLES_NAMES.get(i, None)
            if name is None:
                bits.append(NullBits(1))
                #bits.append(Bit(name="Reserved.{}".format(i)))
            else:
                bits.append(Bit(name=name))
        valid = yield BitsField(
            *bits,
            name="Valid", comment="bitfield indicating which tables are present")
        yield BitsField(
            *bits,
            name="Sorted", comment="bitfield indicating which tables are sorted")
        for i in range(64):
            if valid[i]:
                if not i in TABLES_NAMES:
                    raise KeyError("Unknown table {}".format(i))
                name = TABLES_NAMES.get(i, "Reserved{}".format(i))
                yield UInt32(name="{}Items".format(name), comment="number of items in table {}".format(name))
        if bf["ExtraDword"]:
            yield Unused(4, name="ExtraDword", comment="???")


class Module(Struct):

    def parse(self):
        yield UInt16(name="Generation", comment="reserved, shall be zero")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="module name, index into the #Strings heap")
        yield self.parser.index_tables_net["#GUID"](name="Mvid", comment="a GUID used to distinguish between two versions of the same module")
        yield self.parser.index_tables_net["#GUID"](name="EncId", comment="reserved, shall be zero")
        yield self.parser.index_tables_net["#GUID"](name="EncBaseId", comment="reserved, shall be zero")
       

class TypeRef(Struct):

    def parse(self):
        yield self.parser.index_tables_net["ResolutionScope"](name="Scope", comment="index into Module, ModuleRef, AssemblyRef or TypeRef tables")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="type name")
        yield self.parser.index_tables_net["#Strings"](name="TypeNamespace", comment="type namespace")


class TypeDef(Struct):

    def parse(self):
        yield BitsField(
                Bit(name="Public"),
                Bit(name="Nested"),
                Bit(name="Family"),
                Bit(name="SequentialLayout", comment="class fields are laid out sequentially"),
                Bit(name="ExplicitLayout", comment="class layout is supplied explicitly"),
                Bit(name="Interface", comment="type is an interface"),
                NullBits(1),
                Bit(name="Abstract", comment="class is abstract"),
                Bit(name="Sealed", comment="class is concrete and may not be extended"),
                NullBits(1),
                Bit(name="SpecialName", comment="class name is special"),
                Bit(name="RTSpecialName", comment="Runtime should check name encoding"),
                Bit(name="Imported", comment="type/class is imported"),
                Bit(name="Serializable", comment="type/class is serializable"),
                NullBits(2),
                Bit(name="Unicode", comment="LPTSTR is interpreted as UNICODE"),
                Bit(name="Auto", comment="LPTSTR is interpreted automatically"),
                Bit(name="HasSecurity", comment="class has security associated with it"),
                NullBits(1),
                Bit(name="BeforeFieldInit", comment="initialize the class any time before first static field access"),
                Bit(name="Forwarder", comment="type is a forwarder"),
                NullBits(10),
                name="TypeAttributes", comment="type attributes")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="type name")
        yield self.parser.index_tables_net["#Strings"](name="TypeNamespace", comment="type namespace")
        yield self.parser.index_tables_net["TypeDefOrRef"](name="Extends", comment="TypeDefOrRef")
        yield self.parser.index_tables_net["Field"](name="FieldList", comment="index into Field table; it marks the first of a continuous run of fields owned by this type). The run continues to the smaller of 1-the last row of the Field table 2-the next run of Fields, found by inspecting the FieldList of the next row in this TypeDef table")
        yield self.parser.index_tables_net["MethodDef"](name="MethodList", comment="index into MethodDef table; it marks the first of a continuous run of methods owned by this type). The run continues to the smaller of 1-the last row of the MethodDef table 2-the next run of Methods, found by inspecting the MethodList of the next row in this TypeDef table")


class FieldPtr(Struct):

    def parse(self):
        yield self.parser.index_tables_net["Field"](name="Ref", comment="reference")


class Field(Struct):

    def parse(self):
        yield BitsField(
                Bit(name="Access1", comment="field visibility bits"),
                Bit(name="Access2", comment="field visibility bits"),
                Bit(name="Access3", comment="field visibility bits"),
                NullBits(1),
                Bit(name="Static", comment="field is static"),
                Bit(name="InitOnly", comment="may only be initialized, not written to after init"),
                Bit(name="Literal", comment="compile time constant"),
                Bit(name="NotSerialized", comment="does not have to be serialized when type is remoted"),
                Bit(name="HasRVA", comment="field has RVA"),
                Bit(name="SpecialName", comment="field is special, name describes how"),
                Bit(name="RTSpecialName", comment="Runtime(metadata internal APIs) should check name encoding"),
                NullBits(1),
                Bit(name="HasMarshal", comment="has FieldMarshal information"),
                NullBits(1),
                Bit(name="HasDefault", comment="has default"),
                name="FieldAttributes", comment="field attributes")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="field name, index into #Strings")
        yield self.parser.index_tables_net["#Blob"](name="Signature", comment="index into Blob heap")



class MethodDefPtr(Struct):

    def parse(self):
        yield self.parser.index_tables_net["MethodDef"](name="Ref", comment="reference")


class MethodDef(Struct):

    def parse(self):
        yield Rva(name="Rva", comment="method rva")
        yield BitsField(
                Bit(name="Native", comment="method is native"),
                Bit(name="OPTIL", comment="method is OPTIL"),
                Bit(name="Unmanaged", comment="method is unmanaged"),
                Bit(name="NoInlining", comment="may not be inlined"),
                Bit(name="ForwardRef", comment="method is defined; used primarily in merge scenarios"),
                Bit(name="Synchronized", comment="method is single threaded through the body"),
                NullBits(1),
                Bit(name="PreserveSig", comment="method sig is not to be mangled to do HRESULT conversion"),
                NullBits(4),
                Bit(name="InternalCall", comment="for internal use"),
                name="ImplFlags", comment="method implementation attributes")
        yield BitsField(
                Bit(name="Access1", comment="field visibility bits"),
                Bit(name="Access2", comment="field visibility bits"),
                Bit(name="Access3", comment="field visibility bits"),
                Bit(name="UnmanagedExport", comment="method exported via thunk to unmanaged code"),
                Bit(name="Static", comment="method is static"),
                Bit(name="Final", comment="may not be overridden"),
                Bit(name="Virtual", comment="method is virtual"),
                Bit(name="HideBySig", comment="method hides by name+sig"),
                Bit(name="NewSlot", comment="always gets a new slot in the vtable"),
                Bit(name="CheckAccessOnOverride", comment="Overridability is the same as the visibility"),
                Bit(name="Abstract", comment="does not provide an implementation"),
                Bit(name="SpecialName", comment="method is special, name describes how"),
                Bit(name="RTSpecialName", comment="Runtime(metadata internal APIs) should check name encoding"),
                Bit(name="PinvokeImpl", comment="implementation is forwarded through pinvoke"),
                Bit(name="HasSecurity", comment="has security associate with it"),
                Bit(name="RequireSecObject", comment="calls another method containing security code"),
                name="Flags", comment="method attributes")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="method name, index into #Strings")
        yield self.parser.index_tables_net["#Blob"](name="Signature", comment="index into Blob heap")
        yield self.parser.index_tables_net["Param"](name="ParamList", comment="index into the Param table. It marks the first of a contiguous run of Parameters owned by this method. The run continues to the smaller of 1-the last row of the Param table 2-the next run of Parameters, found by inspecting the ParamList of the next row in the MethodDef table")


class Param(Struct):

    def parse(self):
        yield BitsField(
                Bit(name="In", comment="input parameter"),
                Bit(name="Out", comment="output parameter"),
                NullBits(2),
                Bit(name="Optional", comment="optional parameter"),
                NullBits(7),
                Bit(name="HasDefault", comment="has default value"),
                Bit(name="HasMarshal", comment="has FieldMarshal infos"),
                name="Flags", comment="parameter attributes")
        yield UInt16(name="Sequence", comment="2-byte constant")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="parameter name, index into #Strings")


class ParamPtr(Struct):

    def parse(self):
        yield self.parser.index_tables_net["Param"](name="Ref", comment="reference")


class InterfaceImpl(Struct):

    def parse(self):
        yield self.parser.index_tables_net["TypeDef"](name="Class", comment="index into the TypeDef table")
        yield self.parser.index_tables_net["TypeDefOrRef"](name="Interface", comment="index into the TypeDef, TypeRef or TypeSpec table; more precisely, a TypeDefOrRef coded index")


class MemberRef(Struct):

    def parse(self):
        yield self.parser.index_tables_net["MemberRefParent"](name="Class", comment="index into the TypeRef, ModuleRef, MethodDef, TypeSpec, or TypeDef tables; more precisely, a MemberRefParent coded index")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="index into String heap")
        yield self.parser.index_tables_net["#Blob"](name="Signature", comment="index into Blob heap")


class Constant(Struct):

    def parse(self):
        yield UInt8(name="Type", comment="constant type")
        yield Unused(1)
        yield self.parser.index_tables_net["HasConstant"](name="Parent", comment="index into the Param or Field or Property table, more precisely, a HasConstant coded index")
        yield self.parser.index_tables_net["#Blob"](name="Value", comment="index into Blob heap")


class CustomAttribute(Struct):

    def parse(self):
        yield self.parser.index_tables_net["HasCustomAttribute"](name="Parent", comment="index into any metadata table, except the CustomAttribute table itself; more precisely, a HasCustomAttribute coded index")
        yield self.parser.index_tables_net["CustomAttributeType"](name="Type", comment="index into the MethodDef or MethodRef table; more precisely, a CustomAttributeType coded index")
        yield self.parser.index_tables_net["#Blob"](name="Value", comment="index into Blob heap")


class FieldMarshal(Struct):

    def parse(self):
        yield self.parser.index_tables_net["HasFieldMarshal"](name="Parent", comment="index into Field or Param table; more precisely, a HasFieldMarshal coded index")
        yield self.parser.index_tables_net["#Blob"](name="NativeType", comment="index into Blob heap")


class DeclSecurity(Struct):

    def parse(self):
        yield UInt16(name="Action", comment="")
        yield self.parser.index_tables_net["HasDeclSecurity"](name="Parent", comment="index into the TypeDef, MethodDef, or Assembly table; more precisely, a HasDeclSecurity coded index")
        yield self.parser.index_tables_net["#Blob"](name="PermissionSet", comment="index into Blob heap")


class ClassLayout(Struct):

    def parse(self):
        yield UInt16(name="PackingSize", comment="struct packing")
        yield UInt32(name="ClassSize", comment="struct size")
        yield self.parser.index_tables_net["TypeDef"](name="Parent", comment="index into TypeDef table")


class FieldLayout(Struct):

    def parse(self):
        yield UInt32(name="Offset", comment="field offset")
        yield self.parser.index_tables_net["Field"](name="Field", comment="index into Field table")


class StandAloneSig(Struct):

    def parse(self):
        yield self.parser.index_tables_net["#Blob"](name="Signature", comment="index into Blob heap")


class EventMap(Struct):

    def parse(self):
        yield self.parser.index_tables_net["TypeDef"](name="Parent", comment="index into TypeDef table")
        yield self.parser.index_tables_net["Event"](name="EventList", comment="index into Event table. It marks the first of a contiguous run of Events owned by this Type. The run continues to the smaller of 1- the last row of the Event table 2- the next run of Events, found by inspecting the EventList of the next row in the EventMap table")        

class EventPtr(Struct):

    def parse(self):
        yield self.parser.index_tables_net["Event"](name="Ref", comment="reference")        

class Event(Struct):

    def parse(self):
        yield BitsField(
                NullBits(9),
                Bit(name="SpecialName", comment="event is special. Name describes how"),
                Bit(name="RTSpecialName", comment="Runtime(metadata internal APIs) should check name encoding"),
                name="Flags", comment="event attributes")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="index into String heap")
        yield self.parser.index_tables_net["TypeDefOrRef"](name="EventType", comment="index into TypeDef, TypeRef, or TypeSpec tables; more precisely, a TypeDefOrRef coded index) [this corresponds to the Type of the Event; it is not the Type that owns this event]")


class PropertyMap(Struct):

    def parse(self):
        yield self.parser.index_tables_net["TypeDef"](name="Parent", comment="index into TypeDef table")
        yield self.parser.index_tables_net["Property"](name="PropertyList", comment="index into Property table. It marks the first of a contiguous run of Property owned by this Type. The run continues to the smaller of 1- the last row of the Property table 2- the next run of Properties, found by inspecting the EventList of the next row in the PropertyMap table")  


class PropertyPtr(Struct):

    def parse(self):
        yield self.parser.index_tables_net["Property"](name="Ref", comment="reference")        


class Property(Struct):

    def parse(self):
        yield BitsField(
                NullBits(9),
                Bit(name="SpecialName", comment="event is special. Name describes how"),
                Bit(name="RTSpecialName", comment="Runtime(metadata internal APIs) should check name encoding"),
                Bit(name="HasDefault", comment="property has default"),
                name="Flags", comment="property attributes")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="index into String heap")
        yield self.parser.index_tables_net["#Blob"](name="Type", comment="index into Blob heap. The name of this column is misleading. It does not index a TypeDef or TypeRef table instead it indexes the signature in the Blob heap of the Property")


class MethodSemantics(Struct):

    def parse(self):
        yield BitsField(
                Bit(name="Setter", comment="setter for property"),
                Bit(name="Getter", comment="getter for property"),
                Bit(name="Other", comment="other method for property or event"),
                Bit(name="AddOn", comment="AddOn method for event"),
                Bit(name="RemoveOn", comment="RemoveOn method for event"),
                Bit(name="Fire", comment="Fire method for event"),
                NullBits(10),
                name="Semantics", comment="method semantics")
        yield self.parser.index_tables_net["MethodDef"](name="Method", comment="index into the MethodDef table")
        yield self.parser.index_tables_net["HasSemantics"](name="Association", comment="index into the Event or Property table, more precisely, a HasSemantics coded index")


class MethodImpl(Struct):

    def parse(self):
        yield self.parser.index_tables_net["TypeDef"](name="Class", comment="index into the TypeDef table")
        yield self.parser.index_tables_net["MethodDefOrRef"](name="MethodBody", comment="index into MethodDef or MemberRef table, more precisely, a MethodDefOrRef coded index")
        yield self.parser.index_tables_net["MethodDefOrRef"](name="MethodDeclaration", comment="index into MethodDef or MemberRef table, more precisely, a MethodDefOrRef coded index")


class ModuleRef(Struct):

    def parse(self):
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="index into String heap")


class TypeSpec(Struct):

    def parse(self):
        yield self.parser.index_tables_net["#Blob"](name="SpecSignature", comment="index into Blob heap")


class ImplMap(Struct):

    def parse(self):
        yield UInt16(name="Flags")  #TODO
        yield self.parser.index_tables_net["MemberForwarded"](name="Member", comment="index into the Field or MethodDef table; more precisely, a MemberForwarded coded index. However, it only ever indexes the MethodDef table, since Field export is not supported")
        yield self.parser.index_tables_net["#Strings"](name="ImportName", comment="import name")
        yield self.parser.index_tables_net["ModuleRef"](name="ImportScope", comment="scope")



class FieldRVA(Struct):

    def parse(self):
        yield Rva(name="Rva", comment="rva pointing to field's initial value")  
        yield self.parser.index_tables_net["Field"](name="Field", comment="scope")


class EncLog(Struct):

    def parse(self):
        yield UInt32(name="Token")
        yield UInt32(name="FuncCode")


class EncMap(Struct):

    def parse(self):
        yield UInt32(name="Token")


class Assembly(Struct):

    def parse(self):
        yield UInt32(name="HashAlgId", comment="a 4-byte constant of type AssemblyHashAlgorithm")  
        yield UInt16(name="MajorVersion")
        yield UInt16(name="MinorVersion")
        yield UInt16(name="BuildNumber")
        yield UInt16(name="RevisionNumber")
        yield UInt32(name="Flags")  #TODO
        yield self.parser.index_tables_net["#Blob"](name="PublicKey", comment="index into Blob heap")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="index into String heap")
        yield self.parser.index_tables_net["#Strings"](name="Culture", comment="index into String heap")


class AssemblyProcessor(Struct):

    def parse(self):
        yield UInt32(name="Processor", comment="a 4-byte constant")


class AssemblyOS(Struct):

    def parse(self):
        yield UInt32(name="OSPlatformID", comment="a 4-byte constant")                 
        yield UInt32(name="OSMajorVersion", comment="a 4-byte constant")                 
        yield UInt32(name="OSMinorVersion", comment="a 4-byte constant")                 


class AssemblyRef(Struct):

    def parse(self):
        yield UInt16(name="MajorVersion")
        yield UInt16(name="MinorVersion")
        yield UInt16(name="BuildNumber")
        yield UInt16(name="RevisionNumber")
        yield UInt32(name="Flags")  #TODO
        yield self.parser.index_tables_net["#Blob"](name="PublicKeyOrToken", comment="index into Blob heap – the public key or token that identifies the author of this Assembly")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="index into String heap")
        yield self.parser.index_tables_net["#Strings"](name="Culture", comment="index into String heap")
        yield self.parser.index_tables_net["#Blob"](name="HashValue", comment="index into Blob heap")


class AssemblyRefProcessor(Struct):

    def parse(self):
        yield UInt32(name="Processor", comment="constant")
        yield self.parser.index_tables_net["AssemblyRef"](name="Assembly", comment="index into the AssemblyRef table")


class AssemblyRefOS(Struct):

    def parse(self):
        yield UInt32(name="OSPlateformId")
        yield UInt32(name="OSMajorVersion")
        yield UInt32(name="OSMinorVersion")
        yield self.parser.index_tables_net["AssemblyRef"](name="Assembly", comment="index into the AssemblyRef table")


class File(Struct):

    def parse(self):
        yield BitsField(
                Bit(name="NoMetadata", comment="is a resource file or other non-metadata-containing file"),
                NullBits(31),
                name="Flags", comment="file attributes")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="index into String heap")
        yield self.parser.index_tables_net["#Blob"](name="HashValue", comment="index into Blob heap")


class ExportedType(Struct):

    def parse(self):
        yield BitsField(
                Bit(name="Public"),
                Bit(name="Nested"),
                Bit(name="Family"),
                Bit(name="SequentialLayout", comment="class fields are laid out sequentially"),
                Bit(name="ExplicitLayout", comment="class layout is supplied explicitly"),
                Bit(name="Interface", comment="type is an interface"),
                NullBits(1),
                Bit(name="Abstract", comment="class is abstract"),
                Bit(name="Sealed", comment="class is concrete and may not be extended"),
                NullBits(1),
                Bit(name="SpecialName", comment="class name is special"),
                Bit(name="RTSpecialName", comment="Runtime should check name encoding"),
                Bit(name="Imported", comment="type/class is imported"),
                Bit(name="Serializable", comment="type/class is serializable"),
                NullBits(2),
                Bit(name="Unicode", comment="LPTSTR is interpreted as UNICODE"),
                Bit(name="Auto", comment="LPTSTR is interpreted automatically"),
                Bit(name="HasSecurity", comment="class has security associated with it"),
                NullBits(1),
                Bit(name="BeforeFieldInit", comment="initialize the class any time before first static field access"),
                Bit(name="Forwarder", comment="type is a forwarder"),
                NullBits(10),
                name="TypeAttributes", comment="type attributes")
        yield UInt32(name="TypeDefId", comment="(4-byte index into a TypeDef table of another module in this Assembly). This field is used as a hint only. If the entry in the target TypeDef table matches the TypeName and TypeNamespace entries in this table, resolution has succeeded. But if there is a mismatch, the CLI shall fall back to a search of the target TypeDef table")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="type name")
        yield self.parser.index_tables_net["#Strings"](name="TypeNamespace", comment="type namespace")
        yield self.parser.index_tables_net["Implementation"](name="Implementation", comment="implementation")


class ManifestResource(Struct):

    def parse(self):
        off_rsrc = self.parser.rva2off(self.parser["CLR.Header"]["Resources"]["Rva"]) or 0
        yield Offset32(name="Offset", base=off_rsrc, comment="offset of resource")
        yield BitsField(
                Bit(name="Public", comment="resource is exported"),
                Bit(name="Private", comment="resource is private"),
                NullBits(30),
                name="Flags", comment="resource attributes")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="index into String heap")
        yield self.parser.index_tables_net["Implementation"](name="Implementation", comment="implementation")


class NestedClass(Struct):

    def parse(self):
        yield self.parser.index_tables_net["TypeDef"](name="NestedClass", comment="index into the TypeDef table")
        yield self.parser.index_tables_net["TypeDef"](name="EnclosingClass", comment="index into the TypeDef table of the enclosing class")


class GenericParam(Struct):

    def parse(self):
        yield UInt16(name="Number", comment="the 2-byte index of the generic parameter, numbered left-to-right, from zero")
        yield BitsField(
                Bit(name="Covariant"),
                Bit(name="Contravariant"),
                Bit(name="ReferenceType", comment="generic parameter has the class special constraint"),
                Bit(name="NotNullableValueType", comment="generic parameter has the valuetype special constraint"),
                Bit(name="DefaultConstructor", comment="generic parameter has the .ctor special constraint"),
                NullBits(11),
                name="Flags")
        yield self.parser.index_tables_net["TypeOrMethodDef"](name="Owner", comment="index into the TypeDef or MethodDef table, specifying the Type or Method to which this generic parameter applies")
        yield self.parser.index_tables_net["#Strings"](name="Name", comment="parameter name")
        

class MethodSpec(Struct):

    def parse(self):
        yield self.parser.index_tables_net["MethodDefOrRef"](name="Method", comment="index into the MethodDef or MemberRef table, specifying to which generic method this row refers; that is, which generic method this row is an instantiation of")
        yield self.parser.index_tables_net["#Blob"](name="Instantiation", comment="index into Blob heap holding the signature of this instantiation")


class GenericParamConstraint(Struct):

    def parse(self):
        yield self.parser.index_tables_net["GenericParam"](name="Owner", comment="index into the GenericParam table, specifying to which generic parameter this row refers")
        yield self.parser.index_tables_net["TypeDefOrRef"](name="Constraint", comment="index into the TypeDef, TypeRef, or TypeSpec tables, specifying from which class this generic parameter is constrained to derive; or which interface this generic parameter is constrained to implement")


class CompiledResourceHeader(Struct):

    def parse(self):
        yield UInt32(name="Signature", comment="should be 0xBEEFCACE")
        yield UInt32(name="NumberOfReaderTypes", comment="")        
        sz = yield UInt32(name="SizeOfReaderTypes", comment="")        
        yield String(sz, zero_terminated=False, name="ReaderTypes")
        yield UInt32(name="Version", comment="either 1 or 2")        
        numres = yield UInt32(name="NumberOfResources", comment="number of actual resources in the entry")
        numtypes = yield UInt32(name="NumberOfResourcesTypes", comment="number of different resources typesmm")
        for i in range(numtypes):
            sizelen, size = read_var_int(self.parser)
            if sizelen == 1:
                t = UInt8
            elif sizelen == 2:
                t = UInt16
            else:
                t = UInt32
            yield t(name="Type[{}].size".format(i), comment="varint")
            yield String(size, zero_terminated=False,  name="Type[{}]".format(i))
        modulo = len(self) % 8
        if modulo:
            yield Unused(8 - modulo)
        if numres:
            yield Array(numres, UInt32(), name="ResourcesHash", comment="hash of resources")
            yield Array(numres, Offset32(base=self.offset + len(self) + 4 * numres + 4), name="ResourcesInfos", comment="infos on the resource (name + offset)")
        yield Offset32(name="DataSection", base=self.offset)
         

class ResourceDescriptor(Struct):

    def __init__(self, rsrc_section_start, **kwargs):
        Struct.__init__(self, **kwargs)
        self.rsrc_section_start = rsrc_section_start

    def parse(self):
        sz = yield VarUInt64(name="NameLength")
        yield StringUtf16le(sz // 2, name="Name")
        yield Offset32(name="Start", base=self.rsrc_section_start, comment="start of resource, relative to end of resource header")

def get_composed_row(value, index_name, pe):
    table_bits, table_map = COMPOSED_INDEXES[index_name]
    p2tb = pow(2, table_bits)
    table = value & (p2tb - 1)
    index = value // p2tb
    table_name = None
    for name, bit in table_map.items():
        if bit == table:
            table_name = name + "Table"
            break
    if table_name is None or not table_name in pe:
        raise FatalError("Unknown table {}".format(table_name))
    if index == 0:
        return None
    index -= 1
    if index >= pe[table_name].count:
        raise FatalError("invalid index {} for table {}: {:x} ({})".format(index, table_name, value, index_name))
    return pe[table_name][index]


def read_var_int(pe, off=None):
    if off is None:
        off = pe.tell()
    first, = struct.unpack("<B", pe.read(off, 1))
    if (first & 0xC0) == 0xC0:
        return parse_var_int(pe.read(off, 4))
    elif (first & 0x80) != 0:
        return parse_var_int(pe.read(off, 2))
    else:
        return parse_var_int(struct.pack("B", first))


def parse_var_int(b):
    first, = struct.unpack_from("B", b)
    if (first & 0xC0) == 0xC0:
        x, y, z = struct.unpack_from("BBB", b, offset=1)
        return 4, ((first & 0x1F) << 24) + (x << 16) + (y << 8) + z
    elif (first & 0x80) != 0:
        x, = struct.unpack_from("B", b, offset=1)
        return 2, ((first & 0x3F) << 8) + x
    else:
        return 1, first & 0x7f


def get_string(index, pe):
    if pe.stream_strings_offset is None:
        raise FatalError("No #Strings stream")
    if index > pe.stream_strings_size:
        print("Invalid #Strings index {} vs {}".format(index, pe.stream_strings_size))
        return None
    return pe.read_cstring_utf8(pe.stream_strings_offset + index, pe.stream_strings_size - index)


def get_signature(blob_index, pe):
    if pe.stream_blob_offset is None:
        raise FatalError("No #Blob stream")
    if blob_index > pe.stream_blob_size:
        print("Invalid #Blob index {} vs {}".format(blob_index, pe.stream_blob_size))
        return None
    read, sig_sz = read_var_int(pe, pe.stream_blob_offset + blob_index)
    if blob_index + read + sig_sz > pe.stream_blob_size:
        print("Invalid #Blob index+size {} vs {}".format(blob_index + read + sig_sz, pe.stream_blob_size))
        return None
    return pe.read(pe.stream_blob_offset + blob_index + read, sig_sz)


def get_signature_type_len(sig, pe, class_layouts={}):    
    if not sig:
        return None
    typ = sig[0]
    if typ in (2, 3, 4, 5):
        return 1
    elif typ in (6, 7):
        return 2
    elif typ in (8, 9, 0xd):
        return 4
    elif typ in (0xa, 0xb, 0xd):
        return 8
    if typ == 0x11:
        TypeDefOrRefOrSpecEncoded_sz, encoded = parse_var_int(sig[1:])
        if TypeDefOrRefOrSpecEncoded_sz != len(sig) - 1:
            raise FatalError("overlay in sig for TypeDefOrRefOrSpecEncoded value")
        if (encoded & 3) != 0:
            return None # only support typedefs
        encoded = encoded >> 2
        if encoded == 0:
            return None
        # ok we have a typedef, look for a class layout
        encoded -= 1
        if encoded >= pe["TypeDefTable"].count:
            raise FatalError("Invalid typedef index {} vs {} total".format(encoded, pe["TypeDefTable"].count))
        if encoded in class_layouts:
            return class_layouts[encoded]
    return None # no support for the rest


def resolve_symbol_name(sym, pe):
    if sym is None:
        return "?"
    if "SpecSignature" in sym:
        # TypeSpec
        delta = sym["SpecSignature"]
        if delta >= pe.stream_blob_offset:
            raise FatalError("Signature out of bound")
        off = pe.stream_blob_offset + delta
        left = pe.stream_blob_size - delta
        if left > 3:
            prefix = pe.read(off, 3)
            if prefix[1] == 0x15 and (prefix[2] == 0x11 or prefix[2] == 0x12):
                # resolve GENERICINST TypeSpec
                sz, value = read_var_int(pe, off + 3)
                parent = get_composed_row(value, "TypeDefOrRef", pe)
                base_type = resolve_symbol_name(parent, pe)
                templates = []
                if base_type:
                    off += sz + 3
                    sz, gen_args_count = read_var_int(pe, off)
                    off += sz
                    keep = False
                    for i in range(min(gen_args_count, 16)):
                        if not keep:
                            t = ""
                        keep = False
                        prefix = pe.read(off, 1)[0]
                        off += 1
                        if prefix in ELEMENT_TYPE:
                            t = ELEMENT_TYPE[prefix]
                        elif prefix == 0xf:
                            t = "*" + t
                            keep = True
                        elif prefix == 0x10:
                            t = "&" + t
                            keep = True
                        elif prefix == 0x11 or prefix == 0x12:
                            sz, value = read_var_int(pe, off)
                            off += sz
                            typ = get_composed_row(value, "TypeDefOrRef", pe)
                            t = resolve_symbol_name(typ, pe) + t
                        elif prefix == 0x13:
                            sz, value = read_var_int(pe, off)
                            t = "Gen{:d}".format(value) + t
                            off += sz
                        elif prefix == 0x1d:
                            t = "[]" + t
                        else: 
                            break
                        if not keep:
                            templates.append(t)
                return "{}<{}>".format(base_type, ",".join(templates))
    if not "Name" in sym:
        return ""
    r = get_string(sym["Name"], pe)
    if not r:
        return ""
    if r[0] == ".":
        r = r[1:]
    if "TypeNamespace" in sym:
        # TypeRef
        ns = get_string(sym["TypeNamespace"], pe)
        if ns:
            r = "{}.{}".format(ns, r)
        elif "Scope" in sym:
            parent = get_composed_row(sym["Scope"], "ResolutionScope", pe)
            parent_name = resolve_symbol_name(parent, pe)
            if parent_name:
                r = "{}.{}".format(parent_name, r)
    if "Class" in sym:
        # MemberRef
        parent = get_composed_row(sym["Class"], "MemberRefParent", pe)
        parent_name = resolve_symbol_name(parent, pe)
        if parent_name:
            r = "{}.{}".format(parent_name, r)
    return r

def parse_net(pe):
    dd = pe["OptionalHeader"]["DataDirectory"]
    if dd.count < 15 or dd[14]["Rva"] == 0 or dd[14]["Size"] not in (0x48, 0x50):
        return
    off = pe.rva2off(dd[14]["Rva"])
    if not off or off >= pe.size():
        raise FatalError("CLR header not in file")
    pe.jump(off)
    clihdr = yield CLIHeader(name="CLR.Header", category=Type.HEADER)
    meta_off = pe.rva2off(clihdr["Metadata"]["Rva"])
    if not meta_off or meta_off >= pe.size():
        raise FatalError("Metadata header not in file")
    pe.jump(meta_off)
    mh = yield MetadataHeader(name="CLR.Metadata", category=Type.HEADER)
    # build streams index
    streams = {}
    pe.streams_net = {} 
    for i in range(mh["NumberOfStreams"]):
        stream_header = mh["StreamHeader[{}]".format(i)]
        foff = meta_off + stream_header["Offset"]
        name = stream_header["Name"]
        if not name in streams:
            # some obfuscator put duplicated stream names to fool parsers
            streams[name] = stream_header

    # parse #~ stream
    hdr = streams.get("#~", None)
    if hdr is None:
        hdr = streams.get("#-", None)
        if hdr is None:
            raise FatalError("Stream #~ missing")
    pe.jump(meta_off + hdr["Offset"])
    th = yield TablesHeader(name="#~", category=Type.HEADER)

    
    # tag other streams
    for k, hdr in streams.items():
        if k not in ("#~", "#-"):
            pe.jump(meta_off + hdr["Offset"])
            yield Bytes(hdr["Size"], name=hdr["Name"], category=Type.HEADER)
            pe.streams_net[hdr["Name"]] = (pe.tell(), hdr["Size"])
    pe.stream_blob_offset = 0
    pe.stream_blob_size = 0
    if "#Blob" in pe:
        pe.stream_blob_offset = pe.at("#Blob").offset
        pe.stream_blob_size = pe.at("#Blob").size
    pe.stream_strings_offset = 0
    pe.stream_strings_size = 0
    if "#Strings" in pe:
        pe.stream_strings_offset = pe.at("#Strings").offset
        pe.stream_strings_size = pe.at("#Strings").size

    # compute index sizes
    pe.index_tables_net = {}
    for k,v in TABLES_NAMES.items(): 
        fn = "{}Items".format(v)
        if fn in th:
            pe.index_tables_net[v] = th[fn] > 0xFFFF and UInt32 or UInt16
        else:
            pe.index_tables_net[v] = UInt16
    pe.index_tables_net["#Strings"] = pe["#~"]["HeapOffsetSizes"]["HugeStringStream"] and (lambda **kwargs: Offset32(base=pe.stream_strings_offset, hint=StringUtf8(0, True), zero_is_invalid=True, **kwargs)) or (lambda **kwargs: Offset16(base=pe.stream_strings_offset, hint=StringUtf8(0, True), zero_is_invalid=True, **kwargs))
    if "#GUID" in pe:
        guid_base = pe.at("#GUID").offset
    else:
        guid_base = 1
    pe.index_tables_net["#GUID"] = pe["#~"]["HeapOffsetSizes"]["HugeGuidStream"] and (lambda **kwargs: Offset32(base=guid_base - 1, hint=GUID(), zero_is_invalid=True, **kwargs)) or (lambda **kwargs: Offset16(base=guid_base - 1, hint=GUID(), zero_is_invalid=True, **kwargs))
    pe.index_tables_net["#Blob"] = pe["#~"]["HeapOffsetSizes"]["HugeBlobStream"] and (lambda **kwargs: Offset32(base= pe.stream_blob_offset, zero_is_invalid=True, **kwargs)) or (lambda **kwargs: Offset16(base=pe.stream_blob_offset, zero_is_invalid=True, **kwargs))
    for name, el in COMPOSED_INDEXES.items():
        tag_bits, tables = el    #math.floor(math.log2(len(tables)))
        max_size = int(math.pow(2, 16-tag_bits))
        word = True
        for t in tables:
            ttn = "{}Items".format(t)
            if ttn in th and th[ttn] >= max_size:
                word = False
        pe.index_tables_net[name] = word and UInt16 or UInt32
    

    pe.jump(th.offset + th.size)
    # tables ...
    for table_name in TABLES_NAMES.values():
        nbitemname = "{}Items".format(table_name)
        if nbitemname in th:
            if table_name in globals():
                yield Array(th[nbitemname], globals()[table_name](), parent=th, name="{}Table".format(table_name), category=Type.HEADER)
            else:
                raise KeyError("Unknown table {}".format(table_name))

    # metadata
    if pe["ModuleTable"].count:
        main_module = pe["ModuleTable"][0]
        pe.add_metadata("Module name", get_string(main_module["Name"], pe), category="DotNet")
    pe.set_architecture(malcat.Architecture.DOTNET)

    # resources
    try:
        pe.resources_net = {}
        if "ManifestResourceTable" in pe:
            rsrc_rva = clihdr["Resources"]["Rva"]
            rsrc_off = pe.rva2off(rsrc_rva)
            if rsrc_off is None:
                raise ParsingError("Invalid resource RVA")
            mrt = pe["ManifestResourceTable"]
            for rsrc in mrt:
                off = rsrc["Offset"] + rsrc_off
                rva = rsrc["Offset"] + rsrc_rva
                rsrc_size, = struct.unpack("<I", pe.read(off, 4))
                rsrc_name = get_string(rsrc["Name"], pe)
                if rsrc_name is None:
                    rsrc_name = "!invalid rsrc name!"
                rsrc_name = toprintable(rsrc_name)
                pe.jump(off + 4)
                if rsrc_size >= 4 and pe.read(off + 4, 4) == b"\xce\xca\xef\xbe":
                    # metadata rsrc
                    data_section_base = pe.tell()
                    data_section_base_rva = rva + 4
                    hdr = yield CompiledResourceHeader(name=rsrc_name, category=Type.HEADER, parent=mrt)
                    data_section = hdr["DataSection"]
                    infos_base = pe.tell()
                    subrsrces = []
                    if hdr["NumberOfResources"] == 0:
                        continue
                    for i in range(hdr["NumberOfResources"]):
                        subrsrc_off = infos_base + hdr["ResourcesInfos"][i]
                        pe.jump(subrsrc_off)
                        infos = yield ResourceDescriptor(data_section_base + data_section, parent=hdr)
                        subrsrc_name = toprintable(infos["Name"])
                        subrsrc_start = data_section_base + data_section + infos["Start"]
                        # TODO: how to properly get size of resource ?!?
                        subrsrc_size = None
                        subrsrces.append((subrsrc_start, subrsrc_name, subrsrc_size))
                    subrsrces = sorted(subrsrces, key=lambda x: x[0])
                    # heuristic for rsrc sizes
                    for i in range(len(subrsrces) - 1):
                        subrsrc_start, subrsrc_name, subrsrc_size = subrsrces[i]
                        if not subrsrc_size:
                            subrsrc_size = subrsrces[i+1][0] - subrsrc_start
                            subrsrces[i] = (subrsrc_start, subrsrc_name, subrsrc_size)
                    subrsrc_start, subrsrc_name, subrsrc_size = subrsrces[-1]
                    if not subrsrc_size:
                        subrsrc_size = 4 + off + rsrc_size - subrsrc_start
                        subrsrces[-1] = (subrsrc_start, subrsrc_name, subrsrc_size)
                    # parse resources
                    for subrsrc_start, subrsrc_name, subrsrc_size in subrsrces:
                        if subrsrc_size <= 0:
                            continue
                        pe.jump(subrsrc_start)
                        rsrc_fullname = "{}/{}".format(rsrc_name, subrsrc_name)
                        dnr = yield DotnetResource(min(subrsrc_size, pe.remaining()), name=rsrc_fullname, parent=hdr, category=Type.RESOURCE)
                        pe.resources_net[rsrc_fullname] = Resource("", subrsrc_name, "", dnr.Data.offset + data_section_base_rva - data_section_base, dnr.Data.size)
                else:
                    pe.resources_net[rsrc_name] = Resource("", rsrc_name, "", rva + 4, rsrc_size)
                    yield Bytes(rsrc_size, name=rsrc_name, category=Type.RESOURCE, parent=mrt)
    except ParsingError: 
        import traceback
        traceback.print_exc()
    for k, v in pe.resources_net.items():
        pe.add_file("/".join([".NET", k]), v.size, "open_rsrc_net")

    # EP
    if "MethodDefTable" in pe:
        mdt = pe["MethodDefTable"]
        if "EntryPointToken" in clihdr:
            eptoken = clihdr["EntryPointToken"]
            if eptoken & 0xff000000 == 0x06000000:
                ep_idx = (eptoken & 0x00ffffff ) - 1
                if ep_idx >=  0 and ep_idx < mdt.count:
                    mdef = mdt[ep_idx]
                    mstart = pe.rva2off(mdef["Rva"])
                    # read method header
                    first_byte = pe.read(mstart, 1)[0]
                    if first_byte & 3 == 3:
                        delta = 12
                    elif first_byte & 3 == 2:
                        delta = 1
                    else:
                        delta = None
                    if delta is not None:
                        pe.add_symbol(pe.imagebase + mdef["Rva"] + delta, "DotNetEntryPoint", malcat.FileSymbol.ENTRY)

    # native imports
    if "ImplMapTable" in pe and "MethodDefTable" in pe:
        mdt = pe["MethodDefTable"]
        if "ModuleRefTable" in pe:
            mrt = pe["ModuleRefTable"]
        else:
            mrt = None
        for im in pe["ImplMapTable"]:
            access = get_composed_row(im["Member"], "MemberForwarded", pe)
            if not access:
                continue
            symname = get_string(im["ImportName"], pe)
            if not symname:
                continue
            module_idx = im["ImportScope"]
            if module_idx > 0 and mrt is not None and module_idx <= mrt.count:
                module = get_string(mrt[module_idx - 1]["Name"], pe).lower().split(".")[0]
                if module:
                    symname = "{}.{}".format(module, symname)
            pe.add_symbol(pe.off2va(im.offset), symname, malcat.FileSymbol.IMPORT)


    # imports
    if "MemberRefTable" in pe:
        for mref in pe["MemberRefTable"]:
            pe.add_symbol(pe.off2va(mref.offset), resolve_symbol_name(mref, pe), malcat.FileSymbol.IMPORT)

    # layout
    layouts = {}
    if "ClassLayoutTable" in pe:
        for cl in pe["ClassLayoutTable"]:
            if cl["Parent"]:
                layouts[cl["Parent"] - 1] = cl["ClassSize"]

    # parse static field
    if "FieldRVATable" in pe and True:
        pe.static_fields_net = []
        for field_rva in pe["FieldRVATable"]:
            try:
                off = pe.rva2off(field_rva["Rva"])
                if off is None:
                    continue
                field_index = field_rva["Field"]
                if field_index == 0 or field_index - 1 >= pe["FieldTable"].count:
                    raise FatalError("Invalid field index {}".format(field_index))
                field = pe["FieldTable"][field_index - 1]
                sig = get_signature(field["Signature"], pe)
                if sig is None or len(sig) < 2 or sig[0] != 6:
                    raise FatalError("error getting field signature")
                size = get_signature_type_len(sig[1:], pe, class_layouts=layouts)
                if not size:
                    raise FatalError("Cannot get type size")
                field_name = get_string(field["Name"], pe)
                if not field_name:
                    name = "anonymous_field"
                pe.jump(off)
                pe.add_symbol(pe.off2va(off), f"Field[{field_index - 1}].Data", malcat.FileSymbol.DATA)
                display_name = "Field[{}].StaticData".format(field_index - 1)
                b = yield Bytes(size, name=display_name, category=Type.DATA)
                pe.static_fields_net.append((display_name, off, size, field.offset))
            except FatalError as e:
                print(e)
            


###############################################################################################################


class AOTDebugTypeEntry(Struct):

    def parse(self):
        yield 


class DotNetAOTHeader(Struct):

    def parse(self):
        yield String(4, name="Signature", zero_terminated=False)
        yield UInt16(name="VersionMajor")
        yield UInt16(name="VersionMinor")
        flags = yield BitsField(
            Bit(name="LargePointer", comment="pointer size is 8 bytes (otherwise 4 bytes)"),
            Bit(name="BigEndian", comment="machien is big endian"),
            NullBits(30),
            name="Flags", comment="flags")
        yield Unused(4)
        if flags["LargePointer"]:
            if flags["BigEndian"]:
                pointer = Va64BE
            else:
                pointer = Va64
        else:
            if flags["BigEndian"]:
                pointer = Va32BE
            else:
                pointer = Va32
        yield pointer(name="DebugTypeEntries")
        yield pointer(name="GlobalEntries")
        

def parse_aot(pe, aot_offset):

    if pe.read(aot_offset, 4) != b"DNDH":
        return
    pe.jump(aot_offset)
    aoth = yield DotNetAOTHeader()

   
###############################################################################################################

class DotNetSingleFileBundleHeader(Struct):

    def parse(self):
        yield Offset64(name="ManifestOffset")
        yield Bytes(32, name="Signature", comment="sha256 for '.net core bundle'")


class DotNetSingleFileBundleManifest(Struct):

    def parse(self):
        major = yield UInt32(name="MajorVersion")
        yield UInt32(name="MinorVersion")
        yield UInt32(name="FileCount")
        size_len, size = read_var_int(self.parser)
        if size_len == 1:
            cls = UInt8
        elif size_len == 2:
            cls = UInt16
        elif size_len == 4:
            cls = UInt32
        yield cls(name="BundleNameSize")
        yield StringUtf8(size, name="BundleName")
        if major >= 2:
            yield Offset64(name="DependenciesOffset")
            yield UInt64(name="DependenciesSize")
            yield Offset64(name="RuntimeConfigOffset")
            yield UInt64(name="RuntimeConfigSize")
            yield UInt64(name="Flags")

class DotNetSingleFileBundleManifest(Struct):

    def parse(self):
        major = yield UInt32(name="MajorVersion")
        yield UInt32(name="MinorVersion")
        count = yield UInt32(name="FileCount")
        yield DotnetString(name="BundleName")
        if major >= 2:
            yield Offset64(name="DependenciesOffset")
            yield UInt64(name="DependenciesSize")
            yield Offset64(name="RuntimeConfigOffset")
            yield UInt64(name="RuntimeConfigSize")
            yield UInt64(name="Flags")

        if major >= 6:
            cls = DotNetSingleFileEntry6
        else:
            cls = DotNetSingleFileEntry

        yield VariableArray(count, cls, name="Files")


class DotNetSingleFileEntry(Struct):
    #optim
    fields1 = [
        Offset64(name="Offset", comment="file offset"),
        UInt64(name="Size", comment="file size"),
        UInt8(name="Type", values=[
            ("Unknown", 0),
            ("Assembly", 1),
            ("NativeBinary", 2),
            ("DepsJson", 3),
            ("RuntimeConfigJson", 4),
            ("Symbols", 5),
        ])
    ]

    def parse(self):
        for f in DotNetSingleFileEntry.fields1:
            yield f
        yield DotnetString(name="RelativePath")


class DotNetSingleFileEntry6(Struct):
    #optim
    fields1 = [
        Offset64(name="Offset", comment="file offset"),
        UInt64(name="Size", comment="file size"),
        UInt64(name="CompressedSize"),
        UInt8(name="Type", values=[
            ("Unknown", 0),
            ("Assembly", 1),
            ("NativeBinary", 2),
            ("DepsJson", 3),
            ("RuntimeConfigJson", 4),
            ("Symbols", 5),
        ])
    ]

    def parse(self):
        for f in DotNetSingleFileEntry6.fields1:
            yield f
        yield DotnetString(name="RelativePath")                  
            


def extract_bundle_file(self, vfile, password=""):
    import zlib
    if not vfile.path in self.dotnet_bundle_filesystem:
        raise KeyError("unknown file")
    offset, size, compressed_size = self.dotnet_bundle_filesystem[vfile.path]
    data = self.read(offset, compressed_size or size)
    if compressed_size:
        data = zlib.decompress(data, wbits=-15)
    return data

def parse_single_file_bundle(self):    
    for section in self.regions:
        if section.name == ".data":
            data_section = section
            break
    else:
        return
    magic_offset, _ = self.search(r"\x8B\x12\x02\xB9\x6A\x61\x20\x38\x72\x7B\x93\x02\x14\xD7\xA0\x32\x13\xF5\xB9\xE6\xEF\xAE\x33\x18\xEE\x3B\x2D\xCE\x24\xB3\x6A\xAE", data_section.foff, data_section.fsize)
    if magic_offset:
        self.jump(magic_offset - 8)
        hdr = yield DotNetSingleFileBundleHeader()
        manifest_offset = hdr["ManifestOffset"]
        self.jump(manifest_offset)
        manifest = yield DotNetSingleFileBundleManifest(parent=hdr)
        self.add_metadata("BundleID", manifest["BundleName"]["String"], ".NET Bundle")
        self.dotnet_bundle_filesystem = {}
        self.extract_bundle_file = types.MethodType(extract_bundle_file, self)

        for efile in manifest["Files"]:
            off = efile["Offset"]
            sz = efile["Size"]
            if "CompressedSize" in efile:
                compressed_size = efile["CompressedSize"]
            else:
                compressed_size = 0
            fname = "#.NETBundle/" + efile["RelativePath"]["String"]
            self.dotnet_bundle_filesystem[fname] = (off, sz, compressed_size)
            self.add_file(fname, sz, "extract_bundle_file")

        deps = manifest["DependenciesOffset"]
        deps_size = manifest["DependenciesSize"]
        if deps and deps_size:
            self.jump(deps)
            yield StringUtf8(deps_size, name="DotNetSingleFileDependencies", category=Type.META)
        runt = manifest["RuntimeConfigOffset"]
        runt_size = manifest["RuntimeConfigSize"]
        if runt and runt_size:
            self.jump(runt)
            yield StringUtf8(runt_size, name="DotNetSingleFileRuntimeConfig", category=Type.META)
