@staticmethod
def search_file_pattern(path, pattern, start_address=0, chunk_size=0,
    chunk_limit=0, find_all=True):
    """
        Yields addresses of matching byte patterns inside a file.

        Supports wildcard bytes, a starting address, reading in chunks, and
        read limits.

        Args:
            path (str): A file path.
            pattern (str): A sequence of bytes, e.g. 'FF ?? E8 26 23 D7'.
                '??' represents a single byte wildcard. Spaces between bytes
                are optional.
            start_address (int, optional): Search start address.
            chunk_size (int, optional): The read length per chunk. By default,
                chunks are not used and the entire file is read into memory.
            chunk_limit (int, optional): The maximum number of chunks to read.
                By default, all chunks are read.
            find_all (bool, optional): If True, all found instances are
                yielded. If False, only the first found instance is yielded
                and searching stops.

        Yields:
            int: Found address.

        Example:
            import invade
            found_addresses = []
            for found_address in tool.search_file_pattern(
                    r'C:	arget.exe',
                    '?? 01 55 ?? ?? 4B 20 1E 1D ?? 15',
                    start_address=0x1000,
                    chunk_size=1024,
                    chunk_limit=10,
                    find_all=False):
                found_addresses.append(found_address)
            for address in found_addresses:
                print(hex(address))
        """
    pattern = ''.join(pattern.split())
    if not path or len(pattern) < 2 or len(pattern) % 2 != 0 or pattern.count(
        '?') == len(pattern):
        return
    if chunk_size == 0:
        chunk_size = -1
    if chunk_limit == 0:
        chunk_limit = -1
    pattern_largest_segment = list(filter(None, pattern.split('??')))
    pattern_largest_segment.sort(key=len, reverse=True)
    pattern_largest_segment = pattern_largest_segment[0]
    pattern_largest_segment_position = pattern.index(pattern_largest_segment
        ) // 2
    pattern_largest_segment = bytes.fromhex(pattern_largest_segment)
    if pattern.count('?') == 0:
        pattern_bytes = bytes.fromhex(pattern)
        chunk_position = 0
        with open(path, 'rb') as f:
            if start_address > 0:
                f.seek(start_address)
            while True:
                if chunk_limit > 0:
                    if chunk_position / chunk_size >= chunk_limit:
                        return
                try:
                    data = f.read(chunk_size)
                except MemoryError:
                    return
                if not data:
                    return
                i = 0
                found_position = 0
                while True:
                    try:
                        found_position = data.index(pattern_bytes, 
                            found_position + i)
                        if chunk_size > 0:
                            yield chunk_position + found_position + start_address
                        else:
                            yield found_position + start_address
                        if find_all is False:
                            return
                    except ValueError:
                        break
                    i += 1
                chunk_position += chunk_size
                continue
    pattern_wildcard_positions = []
    for i in range(0, len(pattern), 2):
        pattern_byte = pattern[i:i + 2]
        if pattern_byte == '??':
            pattern_wildcard_positions.append(i // 2)
    pattern_len = len(pattern) // 2
    pattern_bytes = pattern.replace('?', '')
    pattern_bytes = bytes.fromhex(pattern_bytes)
    possible_positions = []
    end_of_file = False
    first_result = True
    chunk_position = 0
    with open(path, 'rb') as f:
        if start_address > 0:
            f.seek(start_address)
        while not end_of_file:
            if chunk_limit > 0:
                if chunk_position / chunk_size >= chunk_limit:
                    return
            try:
                data = f.read(chunk_size)
            except MemoryError:
                return
            if not data:
                end_of_file = True
            chunk_search = True
            while chunk_search:
                try:
                    if first_result is True:
                        possible_positions.append(data.index(
                            pattern_largest_segment))
                        first_result = False
                    else:
                        possible_positions.append(data.index(
                            pattern_largest_segment, possible_positions[-1] +
                            1))
                except ValueError:
                    if chunk_size > 0:
                        chunk_position += chunk_size
                    chunk_search = False
            for possible_position in possible_positions:
                possible_position -= pattern_largest_segment_position
                match_count = 0
                pattern_bytes_pos = 0
                data_offset_pos = 0
                i = 0
                while i < pattern_len:
                    if i in pattern_wildcard_positions:
                        match_count += 1
                        data_offset_pos += 1
                        i += 1
                        continue
                    if possible_position < 0:
                        possible_position = 0
                    if pattern_bytes[pattern_bytes_pos] == data[
                        possible_position + data_offset_pos]:
                        match_count += 1
                        data_offset_pos += 1
                        pattern_bytes_pos += 1
                        i += 1
                        continue
                    i += 1
                if match_count == pattern_len:
                    if find_all is True:
                        if chunk_size > 0:
                            yield chunk_position + possible_position + start_address - chunk_size
                        else:
                            yield possible_position + start_address
                    else:
                        yield possible_position + chunk_position + start_address - chunk_size
                        return
            possible_positions = []
            first_result = True
        return
