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

# https://github.com/Kinimiwar/VB-Exe-Parser/blob/master/VB-Parser.py


class VBHeader(Struct):

    def parse(self):
        yield String(4, name="Signature", comment="VB signature")
        yield UInt16(name="RuntimeBuild", comment="VB runtime build")
        yield String(14, name="LanguageDLL", comment="language dll or *")
        yield String(14, name="BackupLanguageDLL", comment="secondary language dll or ~")
        yield UInt16(name="RuntimeDLLVersion", comment="version of the runtime dll")
        yield UInt32(name="LanguageID", comment="application language")
        yield UInt32(name="BackupLanguageID", comment="application backup language")
        yield Va32(name="SubMain", hint=UInt32(), comment="startup procedure")
        yield Va32(name="ProjectInfo", comment="pointer to project infos")
        yield UInt32(name="fMdlIntCtls", comment="VB Control Flags for IDs < 32")
        yield UInt32(name="fMdlIntCtls2", comment="VB Control Flags for IDs > 32")
        yield UInt32(name="ThreadFlags", comment="thread flags")
        yield UInt32(name="ThreadCount", comment="number of threads")
        yield UInt16(name="FormCount", comment="number of forms")
        yield UInt16(name="ExternalCount", comment="number of external OCX components")
        yield UInt32(name="ThunkCount", comment="number of thunks to create")
        yield Va32(name="GUITable", comment="pointer to the GUI table")
        yield Va32(name="ExternalTable", comment="pointer to the external table")
        yield Va32(name="COMRegisterData", comment="pointer to COM information")
        yield UInt32(name="ProjectExeName", comment="offset to the project executable name")
        yield UInt32(name="ProjectTitle", comment="offset to the project title")
        yield UInt32(name="HelpFile", comment="offset to the project help file name")
        yield UInt32(name="ProjectName", comment="offset to the project name")

class VBProjectInfo(Struct):

    def parse(self):
        yield UInt32(name="TemplateVersion", comment="VB compatible version")
        yield Va32(name="ObjectTable", comment="pointer to the object table")
        yield Unused(4, name="Null")
        yield Va32(name="StartOfCode", comment="pointer to the start of code")
        yield Va32(name="EndOfCode", comment="pointer to the end of code")
        yield UInt32(name="DataSize", comment="size of data")
        yield Va32(name="ThreadObject", comment="pointer to the thread object")
        yield Va32(name="VBAExceptionHandler", comment="pointer to the exception handler")
        yield Va32(name="NativeCode", comment="pointer to the native code (0 for p-code)")
        yield StringUtf16le(264, name="PathInformation", comment="VB Include Internal Project Identifier")
        yield Va32(name="ExternalTable", comment="pointer to imported APIs")
        yield UInt32(name="ExternalCount", comment="number of API imported")


class VBObjectTable(Struct):
    def parse(self):
        yield UInt32(name="HeapLink", comment="should be null")
        yield Va32(name="ExecProj", comment="pointer to VB Project Exec COM Object")
        yield Va32(name="ProjectInfo2", comment="pointer to secondary project information")
        yield UInt32(name="Reserved")
        yield UInt32(name="Null")
        yield Va32(name="ProjectObject", comment="pointer to in-memory Project Data")
        yield GUID(name="GUIDObjectTable", comment="GUID of the Object Table")
        yield UInt16(name="CompileState", comment="internal flag used during compilation")
        yield UInt16(name="TotalObjects", comment="number of objects in project")
        yield UInt16(name="CompiledObjects", comment="equal to above after compiling")
        yield UInt16(name="ObjectsInUse", comment="usually equal to above after compiling")
        yield Va32(name="ObjectArray", comment="pointer to the array of objects")
        yield UInt32(name="IdeFlag", comment="used by IDE")
        yield UInt32(name="IdeData", comment="used by IDE")
        yield UInt32(name="IdeData2", comment="used by IDE")
        yield Va32(name="ProjectName", hint=String(0, True), comment="pointer to project name")
        yield UInt32(name="LCID", comment="project LCID")
        yield UInt32(name="LCID2", comment="project secondary LCID")
        yield UInt32(name="IdeData3", comment="used by IDE")
        yield UInt32(name="TemplateVersion", comment="template version of structure")


class VBObject(Struct):
    def parse(self):
        yield Va32(name="ObjectInfo", comment="pointer to ObjectInfo structure")
        yield UInt32(name="Reserved")
        yield Va32(name="PublicBytes", comment="pointer to public varints")
        yield Va32(name="StaticBytes", comment="pointer to static varints")
        yield Va32(name="ModulePublic", comment="pointer to public variables in data section")
        yield Va32(name="ModuleStatic", comment="pointer to static variables in data section")
        yield Va32(name="ObjectName", hint=String(0, True), comment="pointer to object name")
        yield UInt32(name="MethodCount", comment="number of methods in object")
        yield Va32(name="MethodNames", comment="pointer to method names")
        yield UInt32(name="StaticVars", comment="offset from modulestatic to static vars")
        yield UInt32(name="ObjectType", comment="flags for the object type")
        yield UInt32(name="Null")


class VBObjectInfo(Struct):
    def parse(self):
        yield UInt16(name="RefCount", comment="should be 1")
        yield UInt16(name="ObjectIndex", comment="index in the object array")
        yield Va32(name="ObjectTable", comment="pointer to the object table")
        yield UInt32(name="IdeData", comment="used by IDE")
        yield Va32(name="PrivateObject", comment="pointer to the Private Object Descriptor")
        yield UInt32(name="Reserved")
        yield UInt32(name="Null")
        yield Va32(name="Object", comment="back-pointer to the VBObject")
        yield Va32(name="ProjectData", comment="pointer to project object in data section")
        yield UInt16(name="MethodCount", comment="number of methods in object")
        yield UInt16(name="MethodCount2", comment="should be zero")
        yield Va32(name="MethodTable", comment="pointer to methods array")
        yield UInt16(name="ConstantsCount", comment="number of constants in constant pool")
        yield UInt16(name="MaxConstants", comment="number of constants to allocate in constant pool")
        yield UInt32(name="IdeData2", comment="used by IDE")
        yield UInt32(name="IdeData3", comment="used by IDE")
        yield Va32(name="ConstantTable", comment="pointer to constant pool")


class VBOptionalObjectInfo(Struct):
    def parse(self):
        yield UInt32(name="ObjectGuids", comment="how many GUID to register")
        yield Va32(name="ObjectGuid", hint=GUID(), comment="pointer to object GUID")
        yield UInt32(name="Null")
        yield Va32(name="ObjectTypesGuids", comment="pointer to array of object interface GUIDs")
        yield UInt32(name="ObjectTypesCount", comment="how many entries in array above")
        yield Va32(name="Controls2", comment="usually the same as Controls")
        yield UInt32(name="Null2")
        yield Va32(name="ObjectGuid2", comment="pointer to array of object GUIDs")
        yield UInt32(name="ControlCount", comment="number of controls in Controls array")
        yield Va32(name="Controls", comment="pointer to array of Controls")
        yield UInt16(name="EventCount", comment="number of events in Events array")
        yield UInt16(name="PCodeCount", comment="number of P-Code used by this object")
        yield UInt16(name="InitializeEvent", comment="offset to Initialize event from EventTable")
        yield UInt16(name="TerminateEvent", comment="offset to Terminate event from EventTable")
        yield Va32(name="Events", comment="pointer to array of Controls")
        yield Va32(name="ClassObject", comment="pointer to class object in data section")
        yield UInt32(name="Null3")
        yield UInt32(name="IdeData", comment="used by IDE")
       

class VBControlInfo(Struct):
    def parse(self):
        yield UInt16(name="Unused", comment="unused")
        yield UInt16(name="ControlType", comment="type of control")
        yield UInt16(name="EventCount", comment="number of events handlers supported")
        yield UInt16(name="EventsOffset", comment="offset in memory to events")
        yield Va32(name="Guid", hint=GUID(), comment="pointer to control GUID")
        yield UInt32(name="Index", comment="index of this control")
        yield UInt32(name="Null")
        yield UInt32(name="Null2")
        yield Va32(name="EventTable", comment="pointer to Event Handler array")
        yield UInt32(name="IdeData", comment="used by IDE")
        yield Va32(name="Name", hint=String(0, True), comment="pointer to control name")
        yield UInt32(name="IndexCopy", comment="secondary index of this control")


class VBForm(Struct):
    def parse(self):
        yield UInt32(name="StructureSize", comment="size of structure")
        yield Unused(60, name="Unknown")
        yield UInt32(name="FormSize", comment="size of form description block")
        yield UInt32(name="Null")
        yield Va32(name="FormBlock", comment="pointer to form description block")
        yield UInt32(name="Null2")


CtrlButtonEvents = {
    0x0: "Click",
    0x1: "DragDrop",
    0x2: "DragOver",
    0x3: "GotFocus",
    0x4: "KeyDown",
    0x5: "KeyPress",
    0x6: "KeyUp",
    0x7: "LostFocus",
    0x8: "MouseDown",
    0x9: "MouseMove",
    0xA: "MouseUp",
    0xB: "OLEDragOver",
    0xC: "OLEDragDrop",
    0xD: "OLEGiveFeedback",
    0xE: "OLEStartDrag",
    0xF: "OLESetData",
    0x10: "OLECompleteDrag"
}

CtrlTextboxEvents = {
    0x0: "Change",
    0x1: "DragDrop",
    0x2: "DragOver",
    0x3: "GotFocus",
    0x4: "KeyDown",
    0x5: "KeyPress",
    0x6: "KeyUp",
    0x7: "LinkClose",
    0x8: "LinkError",
    0x9: "LinkOpen",
    0xA: "LostFocus",
    0xB: "LinkNotify",
    0xC: "MouseDown",
    0xD: "MouseMove",
    0xE: "MouseUp",
    0xF: "Click",
    0x10: "DblClick",
    0x11: "OLEDragOver",
    0x12: "OLEDragDrop",
    0x13: "OLEGiveFeedback",
    0x14: "OLEStartDrag",
    0x15: "OLESetData",
    0x16: "OLECompleteDrag",
    0x17: "Validate"
}

CtrlFormEvents = {
    0x0: "DragDrop",
    0x1: "DragOver",
    0x2: "LinkClose",
    0x3: "LinkError",
    0x4: "LinkExecute",
    0x5: "LinkOpen",
    0x6: "Load",
    0x7: "Resize",
    0x8: "Unload",
    0x9: "QueryUnload",
    0xA: "Activate",
    0xB: "Deactivate",
    0xC: "Click",
    0xD: "DblClick",
    0xE: "GotFocus",
    0xF: "KeyDown",
    0x10: "KeyPress",
    0x11: "KeyUp",
    0x12: "LostFocus",
    0x13: "MouseDown",
    0x14: "MouseMove",
    0x15: "MouseUp",
    0x16: "Paint",
    0x17: "Initialize",
    0x18: "Terminate",
    0x19: "OLEDragOver",
    0x1A: "OLEDragDrop",
    0x1B: "OLEGiveFeedback",
    0x1C: "OLEStartDrag",
    0x1D: "OLESetData",
    0x1E: "OLECompleteDrag"
}

CtrlFileEvents = {
    0x0: "Click",
    0x1: "DblClick",
    0x2: "DragDrop",
    0x3: "DragOver",
    0x4: "GotFocus",
    0x5: "KeyDown",
    0x6: "KeyPress",
    0x7: "KeyUp",
    0x8: "LostFocus",
    0x9: "MouseDown",
    0xA: "MouseMove",
    0xB: "MouseUp",
    0xC: "PathChange",
    0xD: "PatternChange",
    0xE: "OLEDragOver",
    0xF: "OLEDragDrop",
    0x10: "OLEGiveFeedback",
    0x11: "OLEStartDrag",
    0x12: "OLESetData",
    0x13: "OLECompleteDrag",
    0x14: "Scroll",
    0x15: "Validate"
}

CtrlOptionEvents = {
    0x0: "Click",
    0x1: "DblClick",
    0x2: "DragDrop",
    0x3: "DragOver",
    0x4: "GotFocus",
    0x5: "KeyDown",
    0x6: "KeyPress",
    0x7: "KeyUp",
    0x8: "LostFocus",
    0x9: "MouseDown",
    0xA: "MouseMove",
    0xB: "MouseUp",
    0xC: "OLEDragOver",
    0xD: "OLEDragDrop",
    0xE: "OLEGiveFeedback",
    0xF: "OLEStartDrag",
    0x10: "OLESetData",
    0x11: "OLECompleteDrag",
    0x12: "Validate"
}

CtrlComboEvents = {
    0x0: "Change",
    0x1: "Click",
    0x2: "DblClick",
    0x3: "DragDrop",
    0x4: "DragOver",
    0x5: "DropDown",
    0x6: "GotFocus",
    0x7: "KeyDown",
    0x8: "KeyPress",
    0x9: "KeyUp",
    0xA: "LostFocus",
    0xB: "OLEDragOver",
    0xC: "OLEDragDrop",
    0xD: "OLEGiveFeedback",
    0xE: "OLEStartDrag",
    0xF: "OLESetData",
    0x10: "OLECompleteDrag",
    0x11: "Scroll",
    0x12: "Validate"
}

CtrlLabelEvents = {
    0x0: "Change",
    0x1: "Click",
    0x2: "DblClick",
    0x3: "DragDrop",
    0x4: "DragOver",
    0x5: "LinkClose",
    0x6: "LinkError",
    0x7: "LinkOpen",
    0x8: "MouseDown",
    0x9: "MouseMove",
    0xA: "MouseUp",
    0xB: "LinkNotify",
    0xC: "OLEDragOver",
    0xD: "OLEDragDrop",
    0xE: "OLEGiveFeedback",
    0xF: "OLEStartDrag",
    0x10: "OLESetData",
    0x11: "OLECompleteDrag"
}

CtrlMenuEvents = {
    0x0: "Click"
}

CtrlTimerEvents = {
    0x0: "Timer"
}


CtrlEvents = {
    0x33AD4EF2: CtrlButtonEvents,
    0x33AD4EE2: CtrlTextboxEvents,
    0x33AD4F2A: CtrlTimerEvents,
    0x33AD4F3A: CtrlFormEvents,
    0x33AD4F62: CtrlFileEvents,
    0x33AD4F02: CtrlOptionEvents,
    0x33AD4F03: CtrlComboEvents,
    0x33AD4F0A: CtrlComboEvents,
    0x33AD4F6A: CtrlMenuEvents,
    0x33AD4EDA: CtrlLabelEvents
}

class VBExternal(Struct):

    def parse(self):   
        yield UInt32(name="ReferenceType", values=[
            ("REF_DLL", 7),
            ])
        yield Va32(name="ReferencePointer", comment="reference to the external")

class VBExternalApi(Struct):

    def parse(self):   
        yield Va32(name="ApiModule", hint=String(0, True), comment="api module")
        yield Va32(name="ApiFunction", hint=String(0, True), comment="api name")


class VBEventHandler(Struct):

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

    def parse(self):   
        yield UInt32(name="Null", comment="always zero")
        yield Va32(name="Control", comment="back-reference to the control")
        yield Va32(name="Object", comment="back-reference to the object")
        yield Va32(name="EVENT_SINK_QueryInterface", comment="???")
        yield Va32(name="EVENT_SINK_AddRef", comment="???")
        yield Va32(name="EVENT_SINK_Release", comment="???")
        yield Array(self.entries, Va32(), name="Methods")


class VBProcDscInfo(Struct):

    def parse(self):   
        yield Va32(name="Object", comment="back-reference to the object")
        yield UInt16(name="Unk", comment="???")
        yield UInt16(name="FrameSize", comment="???")
        yield UInt16(name="ProcSize", comment="size of preceding method")


def re_escape_all(data):
    res = b""
    for d in data:
        res += "\\x{:02x}".format(d).encode("ascii")
    return res

HEADER_REs = [
    re.compile(re.escape(b"\x68")+b"(....)"+re.escape(b"\xE8"))
]


def parse_vb(pe):
    if  not "msvbvm60.FT" in pe:
        return
    imagebase = pe["OptionalHeader"]["ImageBase"]
    ep = imagebase + pe["OptionalHeader"]["AddressOfEntryPoint"]
    ep_off = pe.va2off(ep)
    if not ep_off:
        return
    code_at_ep = pe.read(ep_off, 16)
    header_va = None
    for rexp in HEADER_REs:
        m = rexp.match(code_at_ep)
        if m:
            header_va, = struct.unpack("<I", m.group(1))
            break
    if header_va is not None:
        header_off = pe.va2off(header_va)
    else:
        header_off, _ = pe.search("VB5!")
        
    if header_off is None:
        return
    sig = pe.read(header_off, 4)
    if sig != b"VB5!":
        return

    # ok looks like a vb header
    pe.jump(header_off)
    vbheader = yield VBHeader(category=Type.HEADER)
    for met in ("ProjectExeName", "ProjectTitle", "HelpFile", "ProjectName"):
        meta_off = header_off + vbheader[met]
        try:
            string = pe.read_cstring_ascii(meta_off)
            if string:
                pe.add_metadata(met, string, category="VisualBasicInfos")
        except: pass
    pe.resources_vb = {}
    if vbheader["FormCount"]:
        guitable_off = pe.va2off(vbheader["GUITable"])
        if guitable_off:
            pe.jump(guitable_off)
            forms = yield Array(vbheader["FormCount"], VBForm(), name="VBForms", category=Type.HEADER)
            for i, form in enumerate(forms):
                form_off = pe.va2off(form["FormBlock"])
                form_name = "VBForm.{}".format(i)
                pe.resources_vb[form_name] = Resource("", form_name, "", form["FormBlock"] - pe.imagebase, form["FormSize"])
                if form_off:
                    pe.jump(form_off)
                    yield Bytes(form["FormSize"], name=form_name, parent=forms, category=Type.RESOURCE)

    # project info
    pinfo_off = pe.va2off(vbheader["ProjectInfo"])
    if not pinfo_off:
        return
    pe.jump(pinfo_off)
    vbprojectinfo = yield VBProjectInfo(category=Type.HEADER)
    if vbprojectinfo["PathInformation"]:
        pe.add_metadata("PathInformation", vbprojectinfo["PathInformation"], category="VisualBasicInfos")
    if vbprojectinfo["NativeCode"] == 0:
        pe.set_architecture(malcat.Architecture.PCODE)

    otable_off = pe.va2off(vbprojectinfo["ObjectTable"])
    if otable_off is None:
        return
    pe.jump(otable_off)
    vbobjecttable = yield VBObjectTable(category=Type.HEADER)

    # parse objects
    off = pe.va2off(vbobjecttable["ObjectArray"])
    if off is not None:
        pe.jump(off)
        objects = yield Array(vbobjecttable["TotalObjects"], VBObject(), name="VBObjectArray", category=Type.HEADER)
        for obj in objects:
            name_off = pe.va2off(obj["ObjectName"])
            if name_off is None:
                break
            name = pe.read_cstring_ascii(name_off)
            if not name:
                name = "{:x}".format(pe.tell())
            infos_off = pe.va2off(obj["ObjectInfo"])
            if infos_off is None:
                break
            pe.jump(infos_off)
            # parse public object infos
            vbobjinfo = yield VBObjectInfo(name="VBObj.{}".format(name), parent=objects, category=Type.HEADER)
            # parse private object infos
            if pe.va2off(vbobjinfo["ConstantTable"]) == pe.tell():
                vboptinfos = None
            else:
                vboptinfos = yield VBOptionalObjectInfo(name="VBObj.{}.OptInfos".format(name), parent=vbobjinfo, category=Type.HEADER)
            if vboptinfos is not None:
                # parse object controls
                control_off = pe.va2off(vboptinfos["Controls"])
                if control_off:
                    pe.jump(control_off)
                    cis = yield Array(vboptinfos["ControlCount"], VBControlInfo(), name="VBObj.{}.Controls".format(name), parent=vboptinfos, category=Type.HEADER)
                    for ci in cis:
                        # parse controls events
                        event_table = pe.va2off(ci["EventTable"])
                        if event_table is not None:
                            pe.jump(event_table)
                            name_off = pe.va2off(ci["Name"])
                            if name_off is not None:
                                cname = pe.read_cstring_ascii(name_off)
                            else:
                                cname = "???"
                            eh = yield VBEventHandler(ci["ControlType"], name="VBObj.{}.Controls.{}.Events".format(name, cname), parent=cis)
                            for i, m in enumerate(eh["Methods"]):
                                m = m.value
                                if not m:
                                    continue
                                off = pe.va2off(m)
                                if not off:
                                    continue
                                ename = str(i)
                                guid_off = pe.va2off(ci["Guid"])
                                if guid_off:
                                    guid_1, = struct.unpack("<I", pe.read(guid_off, 4))
                                    ctrl_events = CtrlEvents.get(guid_1, {})
                                    ename = ctrl_events.get(i, i)
                                fname = "VBObj.{}.Controls.{}.Events.{}".format(name, cname, ename)
                                pe.add_symbol(m, fname, malcat.FileSymbol.ENTRY)
                # parse object events
                events_off = pe.va2off(vboptinfos["Events"])
                if events_off:
                    pe.jump(events_off)
                    events = yield Array(vboptinfos["EventCount"], Va32(), name="VBObj.{}.Events".format(name), parent=vboptinfos)
                    for i, event in enumerate(events):
                        event = event.value
                        fname = "VBObj.{}.Events.{}".format(name, i)
                        pe.add_symbol(event, fname, malcat.FileSymbol.ENTRY)
            # parse object constant pool
            constantpool_off = pe.va2off(vbobjinfo["ConstantTable"])
            if constantpool_off and vbobjinfo["ConstantsCount"]:
                pe.jump(constantpool_off)
                yield Array(vbobjinfo["ConstantsCount"], Va32(), name="VBObj.{}.CPool".format(name), parent=vbobjinfo, category=Type.HEADER)
            # parse object methods
            method_table = pe.va2off(vbobjinfo["MethodTable"])
            methods_names = None
            if obj["MethodNames"]:
                method_names_table = pe.va2off(obj["MethodNames"])
                if method_names_table:
                    pe.jump(method_names_table)
                    methods_names = yield Array(obj["MethodCount"], Va32(), name="VBObj.{}.MethodNames".format(name), parent=vbobjinfo)
            method_cache = {}
            if method_table:
                pe.jump(method_table)
                methods = yield Array(vbobjinfo["MethodCount"], Va32(), name="VBObj.{}.Methods".format(name), parent=vbobjinfo)
                for i, m_va in enumerate(methods):
                    m_va = m_va.value
                    m_name = "VBObj.{}.Methods.{}".format(name, i)
                    if methods_names is not None and i < methods_names.count and methods_names[i]:
                        mnameoff = pe.va2off(methods_names[i])
                        if mnameoff:
                            string = pe.read_cstring_ascii(mnameoff)
                            m_name = "VBObj.{}.Methods.{}".format(name, string)

                    m_off = pe.va2off(m_va)
                    if not m_off:
                        continue
                    first_dword, = struct.unpack("<I", pe.read(m_off, 4))
                    if first_dword != obj["ObjectInfo"]:
                        continue
                    pe.jump(m_off)
                    try:
                        pdi = yield VBProcDscInfo(name="{}.Header".format(m_name), parent=methods, category=Type.HEADER)
                        method_cache[m_va] = (m_name, m_va - pdi["ProcSize"])
                        #pe.jump(pdi.offset - pdi["ProcSize"])
                        #yield Bytes(pdi["ProcSize"], name=m_name, parent=methods, category=Type.FUNCTION)
                    except ParsingError:
                        continue

    # try to locate and resolve MethCallEngine calls
    apis_ff25 = set()
    ff25_va = None
    for sym in pe.symbols:
        split = sym.name.split(":")
        if len(split) <= 1:
            continue
        api_name = split[1]
        if api_name == "MethCallEngine":
            #find ff25
            rexp = rb"\xFF\x25" + re_escape_all(struct.pack("<I", sym.address))
            ff25_off, ff25_sz = pe.search(rexp)
            if ff25_sz:
                ff25_va = pe.off2va(ff25_off)
                if ff25_va:
                    apis_ff25.add(sym.address)
    if ff25_va:
        for sym in pe.symbols:
            if sym.type != malcat.FileSymbol.ENTRY: 
                continue
            sym_off = pe.va2off(sym.address)
            if not sym_off:
                continue
            data = pe.read(sym_off, 24)
            # find mov edx, <fn header>; push <ff25>; ret
            rexp = rb"(?:\xB8......)\x33\xc0\xBA(....)\x68" + re_escape_all(struct.pack("<I", ff25_va)) + rb"\xC3"
            m = re.match(rexp, data)
            if m:
                method_header_va, = struct.unpack("<I", m.group(1))
                if method_header_va not in method_cache:
                    continue
                method_name, method_va = method_cache[method_header_va]
                pe.add_symbol(method_va, ".".join(sym.name.split(".")[2:]+["Stub"]), malcat.FileSymbol.ENTRY)

    # parse external table
    otable_off = pe.va2off(vbprojectinfo["ObjectTable"])
    off = pe.va2off(vbprojectinfo["ExternalTable"])
    if off is not None and vbprojectinfo["ExternalCount"]:
        pe.jump(off)
        external_refs = yield Array(vbprojectinfo["ExternalCount"], VBExternal(), name="VBExternalTable")
        for ref in external_refs:
            if ref["ReferenceType"] == 7:
                off = pe.va2off(ref["ReferencePointer"])
                if not off:
                    continue
                pe.jump(off)
                api = yield VBExternalApi(parent=external_refs)
                dll_off = pe.va2off(api["ApiModule"])
                fn_off = pe.va2off(api["ApiFunction"])
                if not dll_off or not fn_off:
                    continue
                dll = pe.read_cstring_ascii(dll_off)
                fn = pe.read_cstring_ascii(fn_off)
                if fn and dll:
                    dll = dll.split(".")[0]
                    pe.add_symbol(ref["ReferencePointer"], "{}.{}".format(dll, fn), malcat.FileSymbol.IMPORT)
