
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680383(v=vs.85).aspx	
class MINIDUMP_LOCATION_DESCRIPTOR:
	def __init__(self):
		self.DataSize = None
		self.Rva = None

	def get_size(self):
		return 8

	def to_bytes(self):
		t = self.DataSize.to_bytes(4, byteorder = 'little', signed = False)
		t += self.Rva.to_bytes(4, byteorder = 'little', signed = False)
		return t
	
	@staticmethod
	def parse(buff):
		mld = MINIDUMP_LOCATION_DESCRIPTOR()
		mld.DataSize = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
		mld.Rva = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
		return mld

	@staticmethod
	async def aparse(buff):
		mld = MINIDUMP_LOCATION_DESCRIPTOR()
		t = await buff.read(4)
		mld.DataSize = int.from_bytes(t, byteorder = 'little', signed = False)
		t = await buff.read(4)
		mld.Rva = int.from_bytes(t, byteorder = 'little', signed = False)
		return mld
	
	def __str__(self):
		t = 'Size: %s File offset: %s' % (self.DataSize, self.Rva)
		return t
		
class MINIDUMP_LOCATION_DESCRIPTOR64:
	def __init__(self):
		self.DataSize = None
		self.Rva = None

	def get_size(self):
		return 16

	def to_bytes(self):
		t = self.DataSize.to_bytes(8, byteorder = 'little', signed = False)
		t += self.Rva.to_bytes(8, byteorder = 'little', signed = False)
		return t
	
	@staticmethod
	def parse(buff):
		mld = MINIDUMP_LOCATION_DESCRIPTOR64()
		mld.DataSize = int.from_bytes(buff.read(8), byteorder = 'little', signed = False)
		mld.Rva = int.from_bytes(buff.read(8), byteorder = 'little', signed = False)
		return mld
	
	def __str__(self):
		t = 'Size: %s File offset: %s' % (self.DataSize, self.Rva)
		return t
		
class MINIDUMP_STRING:
	def __init__(self):
		self.Length = None
		self.Buffer = None
	
	@staticmethod
	def parse(buff):
		ms = MINIDUMP_STRING()
		ms.Length = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
		ms.Buffer = buff.read(ms.Length)
		return ms

	@staticmethod
	async def aparse(buff):
		ms = MINIDUMP_STRING()
		t = await buff.read(4)
		ms.Length = int.from_bytes(t, byteorder = 'little', signed = False)
		ms.Buffer = await buff.read(ms.Length)
		return ms
		
	@staticmethod
	def get_from_rva(rva, buff):
		pos = buff.tell()
		buff.seek(rva, 0)
		ms = MINIDUMP_STRING.parse(buff)
		buff.seek(pos, 0)
		return ms.Buffer.decode('utf-16-le')
	
	@staticmethod
	async def aget_from_rva(rva, buff):
		pos = buff.tell()
		await buff.seek(rva, 0)
		ms = await MINIDUMP_STRING.aparse(buff)
		await buff.seek(pos, 0)
		return ms.Buffer.decode('utf-16-le')
		
class MinidumpMemorySegment:
	def __init__(self):
		self.start_virtual_address = None
		self.size = None
		self.end_virtual_address = None
		self.start_file_address = None
	
	@staticmethod
	def parse_mini(memory_decriptor, buff):
		"""
		memory_descriptor: MINIDUMP_MEMORY_DESCRIPTOR
		buff: file_handle
		"""
		mms = MinidumpMemorySegment()
		mms.start_virtual_address = memory_decriptor.StartOfMemoryRange
		mms.size = memory_decriptor.DataSize
		mms.start_file_address = memory_decriptor.Rva
		mms.end_virtual_address = mms.start_virtual_address + mms.size
		return mms
	
	@staticmethod
	def parse_full(memory_decriptor, rva):
		mms = MinidumpMemorySegment()
		mms.start_virtual_address = memory_decriptor.StartOfMemoryRange
		mms.size = memory_decriptor.DataSize
		mms.start_file_address = rva
		mms.end_virtual_address = mms.start_virtual_address + mms.size
		return mms		
		
	def inrange(self, virt_addr):
		if virt_addr >= self.start_virtual_address and virt_addr < self.end_virtual_address:
			return True
		return False
	
	def read(self, virtual_address, size, file_handler):
		if virtual_address > self.end_virtual_address or virtual_address < self.start_virtual_address:
			raise Exception('Reading from wrong segment!')
		
		if virtual_address+size > self.end_virtual_address:
			raise Exception('Read would cross boundaries!')
		
		pos = file_handler.tell()
		offset = virtual_address - self.start_virtual_address
		file_handler.seek(self.start_file_address + offset, 0)
		data = file_handler.read(size)
		file_handler.seek(pos, 0)
		return data

	async def aread(self, virtual_address, size, file_handler):
		if virtual_address > self.end_virtual_address or virtual_address < self.start_virtual_address:
			raise Exception('Reading from wrong segment!')
		
		if virtual_address+size > self.end_virtual_address:
			raise Exception('Read would cross boundaries!')
		
		pos = file_handler.tell()
		offset = virtual_address - self.start_virtual_address
		await file_handler.seek(self.start_file_address + offset, 0)
		data = await file_handler.read(size)
		await file_handler.seek(pos, 0)
		return data
		
	def search(self, pattern, file_handler, find_first = False, chunksize = 50*1024):
		if len(pattern) > self.size:
			return []
		pos = file_handler.tell()
		file_handler.seek(self.start_file_address, 0)
		fl = []
		if find_first is True:
			chunksize = min(chunksize, self.size)
			data = b''
			i = 0
			while len(data) < self.size:
				i += 1
				if chunksize > (self.size - len(data)):
					chunksize = (self.size - len(data))
				data += file_handler.read(chunksize)
				marker = data.find(pattern)
				if marker != -1:
					#print('FOUND! size: %s i: %s read: %s perc: %s' % (self.size, i, i*chunksize, 100*((i*chunksize)/self.size)))
					file_handler.seek(pos, 0)
					return [self.start_virtual_address + marker]
			
			
			#print('NOTFOUND! size: %s i: %s read: %s perc %s' % (self.size, i, len(data), 100*(len(data)/self.size) ))
			
		else:
			data = file_handler.read(self.size)
			file_handler.seek(pos, 0)
			
			offset = 0
			while len(data) > len(pattern):
				marker = data.find(pattern)
				if marker == -1:
					return fl
				fl.append(marker + offset + self.start_virtual_address)
				data = data[marker+1:]
				offset += marker + 1
				if find_first is True:
					return fl
		
		file_handler.seek(pos, 0)
		return fl

	async def asearch(self, pattern, file_handler, find_first = False, chunksize = 50*1024):
		if len(pattern) > self.size:
			return []
		pos = file_handler.tell()
		await file_handler.seek(self.start_file_address, 0)
		fl = []
		
		if find_first is True:
			chunksize = min(chunksize, self.size)
			data = b''
			i = 0
			while len(data) < self.size:
				i += 1
				if chunksize > (self.size - len(data)):
					chunksize = (self.size - len(data))
				data += await file_handler.read(chunksize)
				marker = data.find(pattern)
				if marker != -1:
					#print('FOUND! size: %s i: %s read: %s perc: %s' % (self.size, i, i*chunksize, 100*((i*chunksize)/self.size)))
					await file_handler.seek(pos, 0)
					return [self.start_virtual_address + marker]
			
			
			#print('NOTFOUND! size: %s i: %s read: %s perc %s' % (self.size, i, len(data), 100*(len(data)/self.size) ))
		
		else:
			offset = 0
			data = await file_handler.read(self.size)
			await file_handler.seek(pos, 0)
			while len(data) > len(pattern):
				marker = data.find(pattern)
				if marker == -1:
					return fl
				fl.append(marker + offset + self.start_virtual_address)
				data = data[marker+1:]
				offset += marker + 1
				if find_first is True:
					return fl
		
		await file_handler.seek(pos, 0)
		return fl
	
	
	@staticmethod
	def get_header():
		t = [
			'VA Start',
			'RVA',
			'Size',
		]
		return t
	
	def to_row(self):
		t = [
			hex(self.start_virtual_address),
			hex(self.start_file_address),
			hex(self.size)
		]
		return t
		
	def __str__(self):
		t = 'VA Start: %s, RVA: %s, Size: %s' % (hex(self.start_virtual_address), hex(self.start_file_address), hex(self.size))
		return t
		
		

def hexdump( src, length=16, sep='.', start = 0):
	'''
	@brief Return {src} in hex dump.
	@param[in] length	{Int} Nb Bytes by row.
	@param[in] sep		{Char} For the text part, {sep} will be used for non ASCII char.
	@return {Str} The hexdump

	@note Full support for python2 and python3 !
	'''
	result = [];

	# Python3 support
	try:
		xrange(0,1);
	except NameError:
		xrange = range;

	for i in xrange(0, len(src), length):
		subSrc = src[i:i+length];
		hexa = '';
		isMiddle = False;
		for h in xrange(0,len(subSrc)):
			if h == length/2:
				hexa += ' ';
			h = subSrc[h];
			if not isinstance(h, int):
				h = ord(h);
			h = hex(h).replace('0x','');
			if len(h) == 1:
				h = '0'+h;
			hexa += h+' ';
		hexa = hexa.strip(' ');
		text = '';
		for c in subSrc:
			if not isinstance(c, int):
				c = ord(c);
			if 0x20 <= c < 0x7F:
				text += chr(c);
			else:
				text += sep;
		if start == 0:
			result.append(('%08x:  %-'+str(length*(2+1)+1)+'s  |%s|') % (i, hexa, text));
		else:
			result.append(('%08x(+%04x):  %-'+str(length*(2+1)+1)+'s  |%s|') % (start+i, i, hexa, text));
	return '\n'.join(result);
	
def construct_table(lines, separate_head=True):
	"""Prints a formatted table given a 2 dimensional array"""
	#Count the column width
	widths = []
	for line in lines:
			for i,size in enumerate([len(x) for x in line]):
					while i >= len(widths):
							widths.append(0)
					if size > widths[i]:
							widths[i] = size
       
	#Generate the format string to pad the columns
	print_string = ""
	for i,width in enumerate(widths):
			print_string += "{" + str(i) + ":" + str(width) + "} | "
	if (len(print_string) == 0):
			return
	print_string = print_string[:-3]
       
	#Print the actual data
	t = ''
	for i,line in enumerate(lines):
			t += print_string.format(*line) + '\n'
			if (i == 0 and separate_head):
					t += "-"*(sum(widths)+3*(len(widths)-1)) + '\n'
					
	return t
