def parse_resources_directory(self, rva, size=0, base_rva=None, level=0, dirs=None):
        """Parse the resources directory.

        Given the RVA of the resources directory, it will process all
        its entries.

        The root will have the corresponding member of its structure,
        IMAGE_RESOURCE_DIRECTORY plus 'entries', a list of all the
        entries in the directory.

        Those entries will have, correspondingly, all the structure's
        members (IMAGE_RESOURCE_DIRECTORY_ENTRY) and an additional one,
        "directory", pointing to the IMAGE_RESOURCE_DIRECTORY structure
        representing upper layers of the tree. This one will also have
        an 'entries' attribute, pointing to the 3rd, and last, level.
        Another directory with more entries. Those last entries will
        have a new attribute (both 'leaf' or 'data_entry' can be used to
        access it). This structure finally points to the resource data.
        All the members of this structure, IMAGE_RESOURCE_DATA_ENTRY,
        are available as its attributes.
        """

        if dirs is None:
            dirs = [rva]

        if base_rva is None:
            base_rva = rva

        if level > MAX_RESOURCE_DEPTH:
            self.__warnings.append(
                "Error parsing the resources directory. "
                "Excessively nested table depth %d (>%s)" % (level, MAX_RESOURCE_DEPTH)
            )
            return None

        try:
            # If the RVA is invalid all would blow up. Some EXEs seem to be
            # specially nasty and have an invalid RVA.
            data = self.get_data(
                rva, Structure(self.__IMAGE_RESOURCE_DIRECTORY_format__).sizeof()
            )
        except PEFormatError:
            self.__warnings.append(
                "Invalid resources directory. Can't read "
                "directory data at RVA: 0x%x" % rva
            )
            return None

        # Get the resource directory structure, that is, the header
        # of the table preceding the actual entries
        #
        resource_dir = self.__unpack_data__(
            self.__IMAGE_RESOURCE_DIRECTORY_format__,
            data,
            file_offset=self.get_offset_from_rva(rva),
        )
        if resource_dir is None:
            # If we can't parse resources directory then silently return.
            # This directory does not necessarily have to be valid to
            # still have a valid PE file
            self.__warnings.append(
                "Invalid resources directory. Can't parse "
                "directory data at RVA: 0x%x" % rva
            )
            return None

        dir_entries = []

        # Advance the RVA to the position immediately following the directory
        # table header and pointing to the first entry in the table
        #
        rva += resource_dir.sizeof()

        number_of_entries = (
            resource_dir.NumberOfNamedEntries + resource_dir.NumberOfIdEntries
        )

        # Set a hard limit on the maximum reasonable number of entries
        MAX_ALLOWED_ENTRIES = 4096
        if number_of_entries > MAX_ALLOWED_ENTRIES:
            self.__warnings.append(
                "Error parsing the resources directory. "
                "The directory contains %d entries (>%s)"
                % (number_of_entries, MAX_ALLOWED_ENTRIES)
            )
            return None

        self.__total_resource_entries_count += number_of_entries
        if self.__total_resource_entries_count > MAX_RESOURCE_ENTRIES:
            self.__warnings.append(
                "Error parsing the resources directory. "
                "The file contains at least %d entries (>%d)"
                % (self.__total_resource_entries_count, MAX_RESOURCE_ENTRIES)
            )
            return None

        strings_to_postprocess = []

        # Keep track of the last name's start and end offsets in order
        # to be able to detect overlapping entries that might suggest
        # and invalid or corrupt directory.
        last_name_begin_end = None
        for idx in range(number_of_entries):
            if (
                not self.__resource_size_limit_reached
                and self.__total_resource_bytes > self.__resource_size_limit_upperbounds
            ):
                self.__resource_size_limit_reached = True
                self.__warnings.append(
                    "Resource size 0x%x exceeds file size 0x%x, overlapping "
                    "resources found."
                    % (
                        self.__total_resource_bytes,
                        self.__resource_size_limit_upperbounds,
                    )
                )

            res = self.parse_resource_entry(rva)
            if res is None:
                self.__warnings.append(
                    "Error parsing the resources directory, "
                    "Entry %d is invalid, RVA = 0x%x. " % (idx, rva)
                )
                break

            entry_name = None
            entry_id = None

            name_is_string = (res.Name & 0x80000000) >> 31
            if not name_is_string:
                entry_id = res.Name
            else:
                ustr_offset = base_rva + res.NameOffset
                try:
                    entry_name = UnicodeStringWrapperPostProcessor(self, ustr_offset)
                    self.__total_resource_bytes += entry_name.get_pascal_16_length()
                    # If the last entry's offset points before the current's but its end
                    # is past the current's beginning, assume the overlap indicates a
                    # corrupt name.
                    if last_name_begin_end and (
                        last_name_begin_end[0] < ustr_offset
                        and last_name_begin_end[1] >= ustr_offset
                    ):
                        # Remove the previous overlapping entry as it's likely to be
                        # already corrupt data.
                        strings_to_postprocess.pop()
                        self.__warnings.append(
                            "Error parsing the resources directory, "
                            "attempting to read entry name. "
                            "Entry names overlap 0x%x" % (ustr_offset)
                        )
                        break

                    last_name_begin_end = (
                        ustr_offset,
                        ustr_offset + entry_name.get_pascal_16_length(),
                    )

                    strings_to_postprocess.append(entry_name)

                except PEFormatError:
                    self.__warnings.append(
                        "Error parsing the resources directory, "
                        "attempting to read entry name. "
                        "Can't read unicode string at offset 0x%x" % (ustr_offset)
                    )

            if res.DataIsDirectory:
                # One trick malware can do is to recursively reference
                # the next directory. This causes hilarity to ensue when
                # trying to parse everything correctly.
                # If the original RVA given to this function is equal to
                # the next one to parse, we assume that it's a trick.
                # Instead of raising a PEFormatError this would skip some
                # reasonable data so we just break.
                #
                # 9ee4d0a0caf095314fd7041a3e4404dc is the offending sample
                if base_rva + res.OffsetToDirectory in dirs:
                    break

                entry_directory = self.parse_resources_directory(
                    base_rva + res.OffsetToDirectory,
                    size - (rva - base_rva),  # size
                    base_rva=base_rva,
                    level=level + 1,
                    dirs=dirs + [base_rva + res.OffsetToDirectory],
                )

                if not entry_directory:
                    break

                # Ange Albertini's code to process resources' strings
                #
                strings = None
                if entry_id == RESOURCE_TYPE["RT_STRING"]:
                    strings = {}
                    for resource_id in entry_directory.entries:
                        if hasattr(resource_id, "directory"):
                            resource_strings = {}

                            for resource_lang in resource_id.directory.entries:
                                if (
                                    resource_lang is None
                                    or not hasattr(resource_lang, "data")
                                    or resource_lang.data.struct.Size is None
                                    or resource_id.id is None
                                ):
                                    continue

                                string_entry_rva = (
                                    resource_lang.data.struct.OffsetToData
                                )
                                string_entry_size = resource_lang.data.struct.Size
                                string_entry_id = resource_id.id

                                # XXX: has been raising exceptions preventing parsing
                                try:
                                    string_entry_data = self.get_data(
                                        string_entry_rva, string_entry_size
                                    )
                                except PEFormatError:
                                    self.__warnings.append(
                                        f"Error parsing resource of type RT_STRING at "
                                        f"RVA 0x{string_entry_rva:x} with "
                                        f"size {string_entry_size}"
                                    )
                                    continue

                                parse_strings(
                                    string_entry_data,
                                    (int(string_entry_id) - 1) * 16,
                                    resource_strings,
                                )
                                strings.update(resource_strings)

                            resource_id.directory.strings = resource_strings

                dir_entries.append(
                    ResourceDirEntryData(
                        struct=res,
                        name=entry_name,
                        id=entry_id,
                        directory=entry_directory,
                    )
                )

            else:
                struct = self.parse_resource_data_entry(
                    base_rva + res.OffsetToDirectory
                )

                if struct:
                    self.__total_resource_bytes += struct.Size
                    entry_data = ResourceDataEntryData(
                        struct=struct, lang=res.Name & 0x3FF, sublang=res.Name >> 10
                    )

                    dir_entries.append(
                        ResourceDirEntryData(
                            struct=res, name=entry_name, id=entry_id, data=entry_data
                        )
                    )

                else:
                    break

            # Check if this entry contains version information
            #
            if level == 0 and res.Id == RESOURCE_TYPE["RT_VERSION"]:
                if dir_entries:
                    last_entry = dir_entries[-1]

                try:
                    version_entries = last_entry.directory.entries[0].directory.entries
                except:
                    # Maybe a malformed directory structure...?
                    # Let's ignore it
                    pass
                else:
                    for version_entry in version_entries:
                        rt_version_struct = None
                        try:
                            rt_version_struct = version_entry.data.struct
                        except:
                            # Maybe a malformed directory structure...?
                            # Let's ignore it
                            pass

                        if rt_version_struct is not None:
                            self.parse_version_information(rt_version_struct)

            rva += res.sizeof()

        string_rvas = [s.get_rva() for s in strings_to_postprocess]
        string_rvas.sort()

        for idx, s in enumerate(strings_to_postprocess):
            s.render_pascal_16()

        resource_directory_data = ResourceDirData(
            struct=resource_dir, entries=dir_entries
        )

        return resource_directory_data
Parse the resources directory.

        Given the RVA of the resources directory, it will process all
        its entries.

        The root will have the corresponding member of its structure,
        IMAGE_RESOURCE_DIRECTORY plus 'entries', a list of all the
        entries in the directory.

        Those entries will have, correspondingly, all the structure's
        members (IMAGE_RESOURCE_DIRECTORY_ENTRY) and an additional one,
        "directory", pointing to the IMAGE_RESOURCE_DIRECTORY structure
        representing upper layers of the tree. This one will also have
        an 'entries' attribute, pointing to the 3rd, and last, level.
        Another directory with more entries. Those last entries will
        have a new attribute (both 'leaf' or 'data_entry' can be used to
        access it). This structure finally points to the resource data.
        All the members of this structure, IMAGE_RESOURCE_DATA_ENTRY,
        are available as its attributes.
        
pefile.py