/* radare - LGPL - Copyright 2008-2025 nibble, pancake, inisider */

#include <limits.h>
#include <r_util.h>
#include <sdb/ht_uu.h>
#include "pe.h"
#include "dotnet.h"
#include "r_util/r_str.h"

#define MAX_METADATA_STRING_LENGTH 256
#define COFF_SYMBOL_SIZE 18
#define PE_READ_STRUCT_FIELD(var, struct_type, field, size) var->field = r_read_le ## size(buf + offsetof(struct_type, field))

struct SCV_NB10_HEADER;
typedef struct {
	ut8 signature[4];
	ut32 offset;
	ut32 timestamp;
	ut32 age;
	ut8 *file_name;
	void (*free) (struct SCV_NB10_HEADER *cv_nb10_header);
} SCV_NB10_HEADER;

typedef struct {
	ut32 data1;
	ut16 data2;
	ut16 data3;
	ut8 data4[8];
} SGUID;

struct SCV_RSDS_HEADER;
typedef struct {
	ut8 signature[4];
	SGUID guid;
	ut32 age;
	ut8 *file_name;
	void (*free) (struct SCV_RSDS_HEADER *rsds_hdr);
} SCV_RSDS_HEADER;

R_API RBinPEObj *PE_(get)(RBinFile *bf) {
	return (bf && bf->bo)? bf->bo->bin_obj: NULL;
}

static inline int is_thumb(RBinPEObj *pe) {
	return pe->nt_headers->optional_header.AddressOfEntryPoint & 1;
}

static inline bool is_arm(RBinPEObj *pe) {
	switch (pe->nt_headers->file_header.Machine) {
	case PE_IMAGE_FILE_MACHINE_ARM:
	case PE_IMAGE_FILE_MACHINE_THUMB:
	case PE_IMAGE_FILE_MACHINE_THUMB2:
		return true;
	}
	return false;
}

static inline bool read_and_follow_jump(struct r_bin_pe_addr_t *entry, RBuffer *buf, ut8 *b, int len, bool big_endian) {
	if (!r_buf_read_at (buf, entry->paddr, b, len)) {
		return false;
	}
	if (b[0] != 0xe9) {
		return true;
	}
	const st32 jmp_dst = r_read_ble32 (b + 1, big_endian) + 5;
	entry->paddr += jmp_dst;
	entry->vaddr += jmp_dst;
	return r_buf_read_at (buf, entry->paddr, b, len) > 0;
}

static inline bool follow_offset(struct r_bin_pe_addr_t *entry, RBuffer *buf, ut8 *b, int len, bool big_endian, size_t instr_off) {
	const st32 dst_offset = r_read_ble32 (b + instr_off + 1, big_endian) + instr_off + 5;
	entry->paddr += dst_offset;
	entry->vaddr += dst_offset;
	return read_and_follow_jump (entry, buf, b, len, big_endian);
}

static st32 sign_extend(ut32 n, ut32 bits) {
	const st32 m = 1U << (bits - 1);
	n = n &((1U << bits) - 1);
	return (n ^ m) - m;
}

static inline bool follow_offset_arm64(struct r_bin_pe_addr_t *entry, RBuffer *buf, ut8 *b, int len, bool big_endian, size_t instr_off) {
	const st32 dst_offset = sign_extend (r_read_ble32 (b + instr_off, big_endian) & 0x3ffffff, 26) * 4 + instr_off;
	entry->paddr += dst_offset;
	entry->vaddr += dst_offset;
	return r_buf_read_at (buf, entry->paddr, b, len) > 0;
}

struct r_bin_pe_addr_t *PE_(check_msvcseh)(RBinPEObj *pe) {
	R_RETURN_VAL_IF_FAIL (pe && pe->b, NULL);
	ut8 b[512];
	size_t n = 0;
	struct r_bin_pe_addr_t *entry = PE_(r_bin_pe_get_entrypoint) (pe);
	ZERO_FILL (b);
	if (r_buf_read_at (pe->b, entry->paddr, b, sizeof (b)) < 0) {
		R_LOG_WARN ("Cannot read entry at 0x%08" PFMT64x, entry->paddr);
		free (entry);
		return NULL;
	}

	read_and_follow_jump (entry, pe->b, b, sizeof (b), pe->big_endian);

	// MSVC SEH
	// E8 13 09 00 00  call    0x44C388
	// E9 05 00 00 00  jmp     0x44BA7F
	if (b[0] == 0xe8 && b[5] == 0xe9) {
		if (follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 5)) {
			// case1:
			// from des address of jmp search for 68 xx xx xx xx e8 and test xx xx xx xx = imagebase
			// 68 00 00 40 00  push    0x400000
			// E8 3E F9 FF FF  call    0x44B4FF
			ut32 imageBase = pe->nt_headers->optional_header.ImageBase;
			for (n = 0; n < sizeof (b) - 6; n++) {
				const ut32 tmp_imgbase = r_read_ble32 (b + n + 1, pe->big_endian);
				if (b[n] == 0x68 && tmp_imgbase == imageBase && b[n + 5] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 5);
					return entry;
				}
			}
			// case2:
			//  from des address of jmp search for 50 FF xx FF xx E8
			// 50			 push    eax
			// FF 37			 push    dword ptr[edi]
			// FF 36          push    dword ptr[esi]
			// E8 6F FC FF FF call    _main
			for (n = 0; n < sizeof (b) - 6; n++) {
				if (b[n] == 0x50 && b[n + 1] == 0xff && b[n + 3] == 0xff && b[n + 5] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 5);
					return entry;
				}
			}
			// case3:
			// 50                                         push    eax
			// FF 35 0C E2 40 00                          push    xxxxxxxx
			// FF 35 08 E2 40 00                          push    xxxxxxxx
			// E8 2B FD FF FF                             call    _main
			for (n = 0; n < sizeof (b) - 20; n++) {
				if (b[n] == 0x50 && b[n + 1] == 0xff && b[n + 7] == 0xff && b[n + 13] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 13);
					return entry;
				}
			}
			// case4:
			// 50                                        push    eax
			// 57                                        push    edi
			// FF 36                                     push    dword ptr[esi]
			// E8 D9 FD FF FF                            call    _main
			for (n = 0; n < sizeof (b) - 5; n++) {
				if (b[n] == 0x50 && b[n + 1] == 0x57 && b[n + 2] == 0xff && b[n + 4] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 4);
					return entry;
				}
			}
			// case5:
			// 57                                        push    edi
			// 56                                        push    esi
			// FF 36                                     push    dword ptr[eax]
			// E8 D9 FD FF FF                            call    _main
			for (n = 0; n < sizeof (b) - 5; n++) {
				if (b[n] == 0x57 && b[n + 1] == 0x56 && b[n + 2] == 0xff && b[n + 4] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 4);
					return entry;
				}
			}
		}
	}

	// MSVC 32bit debug
	if (b[3] == 0xe8) {
		// 55                    push ebp
		// 8B EC                 mov ebp, esp
		// E8 xx xx xx xx        call xxxxxxxx
		// 5D                    pop ebp
		// C3                    ret
		follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 3);
		if (b[8] == 0xe8) {
			// 55                    push ebp
			// 8B EC                 mov ebp, esp
			// E8 xx xx xx xx        call xxxxxxxx
			// E8 xx xx xx xx        call xxxxxxxx <- Follow this
			// 5D                    pop ebp
			// C3                    ret
			follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 8);
			for (n = 0; n + 16 < sizeof (b); n++) {
				// E8 xx xx xx xx    call sub.ucrtbased.dll__register_thread_local_exe_atexit_callback
				// 83 C4 04          add esp, 4
				// E8 xx xx xx xx    call xxxxxxxx <- Follow this
				// 89 xx xx          mov dword [xxxx], eax
				// E8 xx xx xx xx    call xxxxxxxx
				if (b[n] == 0xe8 && n + 8 <= sizeof (b) && !memcmp (b + n + 5, "\x83\xc4\x04", 3) && b[n + 8] == 0xe8 && b[n + 13] == 0x89 && b[n + 16] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 8);
					int j, calls = 0;
					for (j = 0; j < sizeof (b) - 4; j++) {
						if (b[j] == 0xe8) {
							// E8 xx xx xx xx        call xxxxxxxx
							calls++;
							if (calls == 4) {
								follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, j);
								return entry;
							}
						}
					}
				}
			}
		}
	}

	// MSVC AMD64
	if (b[4] == 0xe8) {
		bool found_caller = false;
		if (b[13] == 0xe9) {
			// 48 83 EC 28       sub     rsp, 0x28
			// E8 xx xx xx xx    call    xxxxxxxx
			// 48 83 C4 28       add     rsp, 0x28
			// E9 xx xx xx xx    jmp     xxxxxxxx <- Follow this
			found_caller = follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 13);
		} else {
			// Debug
			// 48 83 EC 28       sub     rsp, 0x28
			// E8 xx xx xx xx    call    xxxxxxxx
			// 48 83 C4 28       add     rsp, 0x28
			// C3                ret
			follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 4);
			if (b[9] == 0xe8) {
				// 48 83 EC 28       sub     rsp, 0x28
				// E8 xx xx xx xx    call    xxxxxxxx
				// E8 xx xx xx xx    call    xxxxxxxx <- Follow this
				// 48 83 C4 28       add     rsp, 0x28
				// C3                ret
				follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 9);
				if (b[0x129] == 0xe8) {
					// E8 xx xx xx xx        call xxxxxxxx
					found_caller = follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 0x129);
				}
			}
		}
		if (found_caller) {
			// from des address of jmp, search for 4C ... 48 ... 8B ... E8
			// 4C 8B C0                    mov     r8, rax
			// 48 8B 17                    mov     rdx, qword [rdi]
			// 8B 0B                       mov     ecx, dword [rbx]
			// E8 xx xx xx xx              call    main
			// or
			// 4C 8B 44 24 28              mov r8, qword [rsp + 0x28]
			// 48 8B 54 24 30              mov rdx, qword [rsp + 0x30]
			// 8B 4C 24 20                 mov ecx, dword [rsp + 0x20]
			// E8 xx xx xx xx              call    main
			for (n = 0; n + 14 < sizeof (b); n++) {
				if (b[n] == 0x4c && b[n + 3] == 0x48 && b[n + 6] == 0x8b && b[n + 8] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 8);
					return entry;
				}
				if (b[n] == 0x4c && b[n + 5] == 0x48 && b[n + 10] == 0x8b && b[n + 14] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 14);
					return entry;
				}
			}
		}
	}

	// MSVC Arm64
	if (b[0] == 0x7f && b[1] == 0x23 && b[2] == 0x03 && b[3] == 0xd5) {
		// 7f 23 03 d5     pacibsp
		// fd 7b bf a9     stp        x29,x30,[sp, #local_10]!
		// fd 03 00 91     mov        x29,sp
		// xx xx xx 94     bl         __security_init_cookie
		// xx xx xx 97     bl         __scrt_common_main_seh <-- Follow this
		// fd 7b c1 a8     ldp        x29=>local_10,x30,[sp], #0x10
		// ff 23 03 d5     autibsp
		// c0 03 5f d6     ret
		if (follow_offset_arm64 (entry, pe->b, b, sizeof (b), pe->big_endian, 16)) {
			// case 1
			// e2 03 14 aa  mov        x2,x20
			// e1 03 13 aa  mov        x1,x19
			// xx xx xx 94 	bl         main
			for (n = 0; n < sizeof (b) - 12; n++) {
				if (b[n] == 0xe2 && b[n + 1] == 0x03 && b[n + 2] == 0x14 && b[n + 3] == 0xaa && b[n + 4] == 0xe1 && b[n + 5] == 0x03 && b[n + 6] == 0x13 && b[n + 7] == 0xaa && (b[n + 11] & 0xfc) == 0x94) {
					follow_offset_arm64 (entry, pe->b, b, sizeof (b), pe->big_endian, 8 + n);
					return entry;
				}
			}

			// case 2
			// 33 xx xx xx     add        x19,x9,xxxxxxxx
			// 60 02 40 b9     ldr        w0,[x19]=>DAT_1400be060
			// xx xx xx 97     bl         main
			for (n = 0; n < sizeof (b) - 12; n++) {
				if (b[n] == 0x33 && b[n + 4] == 0x60 && b[n + 5] == 0x02 && b[n + 6] == 0x40 && b[n + 7] == 0xb9 && (b[n + 11] & 0xfc) == 0x94) {
					follow_offset_arm64 (entry, pe->b, b, sizeof (b), pe->big_endian, 8 + n);
					return entry;
				}
			}

			// case 3
			// 01 00 80 d2     mov        x1,#0x0
			// a8 ff ff d0     adrp       x8,xxxxxxxx
			// 00 01 xx 91     add        x0,x8,xxxxxxxx
			// xx xx xx 94     bl         wWinMain
			for (n = 0; n < sizeof (b) - 16; n++) {
				if (b[n] == 0x01 && b[n + 1] == 0x00 && b[n + 2] == 0x80 && b[n + 3] == 0xd2 && (b[n + 4] & 0x1f) == 0x8 && (b[n + 7] & 0x9f) == 0x90 && b[n + 8] == 0 && (b[n + 9] & 3) == 1 && (b[n + 11] & 0x7f) == 0x11 && (b[n + 15] & 0xfc) == 0x94) {
					follow_offset_arm64 (entry, pe->b, b, sizeof (b), pe->big_endian, 12 + n);
					return entry;
				}
			}
		}
	}

	// Microsoft Visual-C
	//  50                  push eax
	//  FF 75 9C            push dword [ebp - local_64h]
	//  56                  push    esi
	//  56                  push    esi
	//  FF 15 CC C0  44 00  call dword [sym.imp.KERNEL32.dll_GetModuleHandleA]
	//  50                  push    eax
	//  E8 DB DA 00 00      call    main
	//  89 45 A0            mov dword [ebp - local_60h], eax
	//  50                  push    eax
	//  E8 2D 00 00  00     call 0x4015a6
	if (b[188] == 0x50 && b[201] == 0xe8) {
		follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 201);
		return entry;
	}

	if (b[292] == 0x50 && b[303] == 0xe8) {
		follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 303);
		return entry;
	}

	free (entry);
	return NULL;
}

struct r_bin_pe_addr_t *PE_(check_mingw)(RBinPEObj *pe) {
	bool sw = false;
	ut8 b[1024] = { 0 };
	size_t n = 0;
	if (!pe || !pe->b) {
		return 0LL;
	}
	struct r_bin_pe_addr_t *entry = PE_(r_bin_pe_get_entrypoint) (pe);
	if (r_buf_read_at (pe->b, entry->paddr, b, sizeof (b)) < 0) {
		R_LOG_WARN ("Cannot read entry at 0x%08" PFMT64x, entry->paddr);
		free (entry);
		return NULL;
	}
	// mingw
	// 55                                         push    ebp
	// 89 E5                                      mov     ebp, esp
	// 83 EC 08                                   sub     esp, 8
	// C7 04 24 01 00 00 00                       mov     dword ptr[esp], 1
	// FF 15 C8 63 41 00                          call    ds : __imp____set_app_type
	// E8 B8 FE FF FF                             call    ___mingw_CRTStartup
	if (b[0] == 0x55 && b[1] == 0x89 && b[3] == 0x83 && b[6] == 0xc7 && b[13] == 0xff && b[19] == 0xe8) {
		sw = follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 19);
	}
	// 83 EC 1C                                   sub     esp, 1Ch
	// C7 04 24 01 00 00 00                       mov[esp + 1Ch + var_1C], 1
	// FF 15 F8 60 40 00                          call    ds : __imp____set_app_type
	// E8 6B FD FF FF                             call    ___mingw_CRTStartup
	if (b[0] == 0x83 && b[3] == 0xc7 && b[10] == 0xff && b[16] == 0xe8) {
		sw = follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 16);
	}
	// 83 EC 0C                                            sub     esp, 0Ch
	// C7 05 F4 0A 81 00 00 00 00 00                       mov     ds : _mingw_app_type, 0
	// ED E8 3E AD 24 00                                      call    ___security_init_cookie
	// F2 83 C4 0C                                            add     esp, 0Ch
	// F5 E9 86 FC FF FF                                      jmp     ___tmainCRTStartup
	if (b[0] == 0x83 && b[3] == 0xc7 && b[13] == 0xe8 && b[18] == 0x83 && b[21] == 0xe9) {
		sw = follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 21);
	}
	if (sw) {
		// case1:
		// from des address of call search for a1 xx xx xx xx 89 xx xx e8 xx xx xx xx
		// A1 04 50 44 00                             mov     eax, ds:dword_445004
		// 89 04 24                                   mov[esp + 28h + lpTopLevelExceptionFilter], eax
		// E8 A3 01 00 00                             call    sub_4013EE
		for (n = 0; n < sizeof (b) - 12; n++) {
			if (b[n] == 0xa1 && b[n + 5] == 0x89 && b[n + 8] == 0xe8) {
				sw = follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, n + 8);
				return entry;
			}
		}
	}
	free (entry);
	return NULL;
}

struct r_bin_pe_addr_t *PE_(check_unknow)(RBinPEObj *pe) {
	struct r_bin_pe_addr_t *entry;
	if (!pe || !pe->b) {
		return 0LL;
	}
	ut8 b[512] = { 0 };
	ZERO_FILL (b);
	entry = PE_(r_bin_pe_get_entrypoint) (pe);
	// option2: /x 8bff558bec83ec20
	if (r_buf_read_at (pe->b, entry->paddr, b, sizeof (b)) != sizeof (b)) {
		R_LOG_WARN ("Cannot read entry at 0x%08" PFMT64x, entry->paddr);
		free (entry);
		return NULL;
	}
	/* Decode the jmp instruction, this gets the address of the 'main'
	function for PE produced by a compiler whose name someone forgot to
	write down. */
	// this is dirty only a single byte check, can return false positives
	if (b[367] == 0xe8) {
		follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, 367);
		return entry;
	}
	size_t i;
	for (i = 0; i < 512 - 16; i++) {
		// 5. ff 15 .. .. .. .. 50 e8 [main]
		if (!memcmp (b + i, "\xff\x15", 2)) {
			if (b[i + 6] == 0x50) {
				if (b[i + 7] == 0xe8) {
					follow_offset (entry, pe->b, b, sizeof (b), pe->big_endian, i + 7);
					return entry;
				}
			}
		}
	}
	free (entry);
	return NULL;
}

struct r_bin_pe_addr_t *PE_(r_bin_pe_get_main_vaddr)(RBinPEObj *pe) {
	struct r_bin_pe_addr_t *winmain = PE_(check_msvcseh) (pe);
	if (!winmain) {
		winmain = PE_(check_mingw) (pe);
		if (!winmain) {
			winmain = PE_(check_unknow) (pe);
		}
	}
	return winmain;
}

R_API PE_DWord PE_(va2pa)(RBinPEObj *pe, PE_DWord rva) {
	PE_DWord section_base;
	int i, section_size;
	for (i = 0; i < pe->num_sections; i++) {
		section_base = pe->sections[i].vaddr;
		section_size = pe->sections[i].vsize;
		if (rva >= section_base && rva < section_base + section_size) {
			return pe->sections[i].paddr + (rva - section_base);
		}
	}
	return rva;
}

ut64 PE_(r_bin_pe_get_image_base)(RBinPEObj *pe) {
	ut64 imageBase = 0;
	if (!pe || !pe->nt_headers) {
		return 0LL;
	}
	imageBase = pe->nt_headers->optional_header.ImageBase;
	if (!imageBase) {
		// this should only happens with messed up binaries
		// XXX this value should be user defined by bin.baddr
		// but from here we can not access config API
		imageBase = 0x10000;
	}
	return imageBase;
}

static PE_DWord bin_pe_rva_to_va(RBinPEObj *pe, PE_DWord rva) {
	return PE_(r_bin_pe_get_image_base) (pe) + rva;
}

static PE_DWord bin_pe_va_to_rva(RBinPEObj *pe, PE_DWord va) {
	ut64 imageBase = PE_(r_bin_pe_get_image_base) (pe);
	if (va < imageBase) {
		return va;
	}
	return va - imageBase;
}

static char *resolveModuleOrdinal(Sdb *sdb, const char *module, int ordinal) {
	Sdb *db = sdb;
	r_strf_var (key, 32, "%d", ordinal);
	char *foo = sdb_get (db, key, 0);
	if (foo && *foo) {
		return foo;
	} else {
		free (foo); // should never happen
	}
	return NULL;
}

static int bin_pe_parse_imports(RBinPEObj *pe,
	struct r_bin_pe_import_t **importp, int *nimp,
	const char *dll_name,
	PE_DWord OriginalFirstThunk,
	PE_DWord FirstThunk) {
	char import_name[PE_NAME_LENGTH + 1];
	char name[PE_NAME_LENGTH + 1];
	PE_Word import_hint, import_ordinal = 0;
	PE_DWord import_table = 0, off = 0;
	int i = 0;
	size_t len;
	Sdb *db = NULL;
	char *sdb_module = NULL;
	char *symname = NULL;
	char *symdllname = NULL;
	char *filename = NULL;
	char *lower_symdllname = NULL;

	if (R_STR_ISEMPTY (dll_name) || *dll_name == '0') {
		return 0;
	}

#if 0
	// R2_590
	PE_DWord off = PE_(va2pa) (pe, OriginalFirstThunk);
	if (!off) {
		off = PE_(va2pa) (pe, FirstThunk);
		if (!off) {
			return 0;
		}
	}
#else
	if (! (off = PE_(va2pa) (pe, OriginalFirstThunk)) && ! (off = PE_(va2pa) (pe, FirstThunk))) {
		return 0;
	}
#endif
	do {
		if (import_ordinal >= UT16_MAX) {
			break;
		}
		if (off + i * sizeof (PE_DWord) > pe->size) {
			break;
		}
		import_table = R_BUF_READ_PE_DWORD_AT (pe->b, off + i * sizeof (PE_DWord));
		if (import_table == PE_DWORD_MAX) {
			R_LOG_WARN ("read (import table)");
			goto error;
		}
		if (import_table) {
			if (import_table & ILT_MASK1) {
				import_ordinal = import_table & ILT_MASK2;
				import_hint = 0;
				len = snprintf (import_name, sizeof (import_name), "Ordinal_%i", import_ordinal);
				if (len >= sizeof (import_name)) {
					R_LOG_WARN ("Import name truncated: %s", import_name);
				}
				free (symdllname);
				strncpy (name, dll_name, sizeof (name) - 1);
				name[sizeof (name) - 1] = 0;
				symdllname = strdup (name);

				// remove the trailling ".dll"
				size_t len = strlen (symdllname);
				r_str_case (symdllname, 0);
				if (r_str_endswith (symdllname, ".dll")) {
					len -= 4;
					symdllname[len] = 0;
				}

				if (!sdb_module || strcmp (symdllname, sdb_module)) {
					sdb_free (db);
					db = NULL;
					free (sdb_module);
					sdb_module = strdup (symdllname);
					filename = r_str_newf ("%s.sdb", symdllname);
					if (filename && r_file_exists (filename)) {
						db = sdb_new (NULL, filename, 0);
					} else {
						char *dirPrefix = r_sys_prefix (NULL);
						lower_symdllname = strdup (symdllname);
						r_str_case (lower_symdllname, false);
						free (filename);
						filename = r_str_newf (R_JOIN_4_PATHS ("%s", R2_SDB_FORMAT, "dll", "%s.sdb"),
							dirPrefix, lower_symdllname);
						free (dirPrefix);
						free (lower_symdllname);
						lower_symdllname = NULL;
						if (r_file_exists (filename)) {
							db = sdb_new (NULL, filename, 0);
						}
					}
				}
				if (db) {
					symname = resolveModuleOrdinal (db, symdllname, import_ordinal);
					if (symname) {
						len = snprintf (import_name, sizeof (import_name), "%s", symname);
						if (len >= sizeof (import_name)) {
							R_LOG_WARN ("Import name truncated: %s", import_name);
						}
						R_FREE (symname);
					}
				} else {
					R_LOG_ERROR ("Cannot find %s", filename);
				}
				R_FREE (filename);
			} else {
				import_ordinal++;
				const ut64 off = PE_(va2pa) (pe, import_table);
				if (off > pe->size || (off + sizeof (PE_Word)) > pe->size) {
					R_LOG_WARN ("off > pe->size");
					goto error;
				}
				import_hint = r_buf_read_le16_at (pe->b, off);
				if (import_hint == UT16_MAX) {
					R_LOG_WARN ("read import hint at 0x%08" PFMT64x, off);
					goto error;
				}
				name[0] = '\0';
				len = r_buf_read_at (pe->b, off + sizeof (PE_Word), (ut8 *)name, PE_NAME_LENGTH);
				if (len < 1) {
					R_LOG_WARN ("read (import name)");
					goto error;
				}
				if (!*name) {
					break;
				}
				name[PE_NAME_LENGTH] = '\0';
				len = snprintf (import_name, sizeof (import_name), "%s", name);
				if (len >= sizeof (import_name)) {
					R_LOG_WARN ("Import name '%s' has been truncated", import_name);
				}
			}
			struct r_bin_pe_import_t *new_importp = realloc (*importp, (*nimp + 1) * sizeof (struct r_bin_pe_import_t));
			if (!new_importp) {
				r_sys_perror ("realloc (import)");
				goto error;
			}
			// XXX too much indirections here
			*importp = new_importp;
			memcpy ((*importp)[*nimp].name, import_name, PE_NAME_LENGTH);
			(*importp)[*nimp].name[PE_NAME_LENGTH] = '\0';
			memcpy ((*importp)[*nimp].libname, dll_name, PE_NAME_LENGTH);
			(*importp)[*nimp].libname[PE_NAME_LENGTH] = '\0';
			ut64 addr = FirstThunk + (i * sizeof (PE_DWord));
			(*importp)[*nimp].vaddr = bin_pe_rva_to_va (pe, addr);
			(*importp)[*nimp].paddr = PE_(va2pa) (pe, addr);
			(*importp)[*nimp].hint = import_hint;
			(*importp)[*nimp].ntype = IMAGE_REL_BASED_HIGHLOW; // ABSOLUTE; // TODO
			(*importp)[*nimp].ordinal = import_ordinal;
			(*importp)[*nimp].last = 0;
			(*nimp)++;
			i++;
		}
	} while (import_table);

	if (db) {
		sdb_free (db);
		db = NULL;
	}
	free (symdllname);
	free (sdb_module);
	return i;

error:
	if (db) {
		sdb_free (db);
		db = NULL;
	}
	free (symname);
	free (filename);
	free (lower_symdllname);
	free (symdllname);
	free (sdb_module);
	// Free any partially allocated import structures
	if (*importp) {
		free (*importp);
		*importp = NULL;
		*nimp = 0;
	}
	return false;
}

int PE_(read_dos_header)(RBuffer *b, PE_(image_dos_header) * header) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, 0, R_BUF_SET) == -1) {
		return -1;
	}
	header->e_magic = r_buf_read_le16 (b);
	header->e_cblp = r_buf_read_le16 (b);
	header->e_cp = r_buf_read_le16 (b);
	header->e_crlc = r_buf_read_le16 (b);
	header->e_cparhdr = r_buf_read_le16 (b);
	header->e_minalloc = r_buf_read_le16 (b);
	header->e_maxalloc = r_buf_read_le16 (b);
	header->e_ss = r_buf_read_le16 (b);
	header->e_sp = r_buf_read_le16 (b);
	header->e_csum = r_buf_read_le16 (b);
	header->e_ip = r_buf_read_le16 (b);
	header->e_cs = r_buf_read_le16 (b);
	header->e_lfarlc = r_buf_read_le16 (b);
	header->e_ovno = r_buf_read_le16 (b);
	int i;
	for (i = 0; i < 4; i++) {
		header->e_res[i] = r_buf_read_le16 (b);
	}
	header->e_oemid = r_buf_read_le16 (b);
	header->e_oeminfo = r_buf_read_le16 (b);
	for (i = 0; i < 10; i++) {
		header->e_res2[i] = r_buf_read_le16 (b);
	}
	header->e_lfanew = r_buf_read_le32 (b);
	if (r_buf_seek (b, o_addr, R_BUF_SET) == -1) {
		return -1;
	}
	return sizeof (PE_(image_dos_header));
}

int PE_(read_nt_headers)(RBuffer *b, ut64 addr, PE_(image_nt_headers) * headers) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	headers->Signature = r_buf_read_le32 (b);
	headers->file_header.Machine = r_buf_read_le16 (b);
	headers->file_header.NumberOfSections = r_buf_read_le16 (b);
	headers->file_header.TimeDateStamp = r_buf_read_le32 (b);
	headers->file_header.PointerToSymbolTable = r_buf_read_le32 (b);
	headers->file_header.NumberOfSymbols = r_buf_read_le32 (b);
	headers->file_header.SizeOfOptionalHeader = r_buf_read_le16 (b);
	headers->file_header.Characteristics = r_buf_read_le16 (b);
	headers->optional_header.Magic = r_buf_read_le16 (b);
	headers->optional_header.MajorLinkerVersion = r_buf_read8 (b);
	headers->optional_header.MinorLinkerVersion = r_buf_read8 (b);
	headers->optional_header.SizeOfCode = r_buf_read_le32 (b);
	headers->optional_header.SizeOfInitializedData = r_buf_read_le32 (b);
	headers->optional_header.SizeOfUninitializedData = r_buf_read_le32 (b);
	headers->optional_header.AddressOfEntryPoint = r_buf_read_le32 (b);
	headers->optional_header.BaseOfCode = r_buf_read_le32 (b);
#ifdef R_BIN_PE64
	headers->optional_header.ImageBase = r_buf_read_le64 (b);
#else
	headers->optional_header.BaseOfData = r_buf_read_le32 (b);
	headers->optional_header.ImageBase = r_buf_read_le32 (b);
#endif
	headers->optional_header.SectionAlignment = r_buf_read_le32 (b);
	headers->optional_header.FileAlignment = r_buf_read_le32 (b);
	headers->optional_header.MajorOperatingSystemVersion = r_buf_read_le16 (b);
	headers->optional_header.MinorOperatingSystemVersion = r_buf_read_le16 (b);
	headers->optional_header.MajorImageVersion = r_buf_read_le16 (b);
	headers->optional_header.MinorImageVersion = r_buf_read_le16 (b);
	headers->optional_header.MajorSubsystemVersion = r_buf_read_le16 (b);
	headers->optional_header.MinorSubsystemVersion = r_buf_read_le16 (b);
	headers->optional_header.Win32VersionValue = r_buf_read_le32 (b);
	headers->optional_header.SizeOfImage = r_buf_read_le32 (b);
	headers->optional_header.SizeOfHeaders = r_buf_read_le32 (b);
	headers->optional_header.CheckSum = r_buf_read_le32 (b);
	headers->optional_header.Subsystem = r_buf_read_le16 (b);
	headers->optional_header.DllCharacteristics = r_buf_read_le16 (b);
#ifdef R_BIN_PE64
	headers->optional_header.SizeOfStackReserve = r_buf_read_le64 (b);
	headers->optional_header.SizeOfStackCommit = r_buf_read_le64 (b);
	headers->optional_header.SizeOfHeapReserve = r_buf_read_le64 (b);
	headers->optional_header.SizeOfHeapCommit = r_buf_read_le64 (b);
#else
	headers->optional_header.SizeOfStackReserve = r_buf_read_le32 (b);
	headers->optional_header.SizeOfStackCommit = r_buf_read_le32 (b);
	headers->optional_header.SizeOfHeapReserve = r_buf_read_le32 (b);
	headers->optional_header.SizeOfHeapCommit = r_buf_read_le32 (b);
#endif
	headers->optional_header.LoaderFlags = r_buf_read_le32 (b);
	headers->optional_header.NumberOfRvaAndSizes = r_buf_read_le32 (b);
	int i;
	for (i = 0; i < PE_IMAGE_DIRECTORY_ENTRIES; i++) {
		headers->optional_header.DataDirectory[i].VirtualAddress = r_buf_read_le32 (b);
		headers->optional_header.DataDirectory[i].Size = r_buf_read_le32 (b);
	}
	if (r_buf_seek (b, o_addr, R_BUF_SET) == -1) {
		return -1;
	}
	return sizeof (PE_(image_nt_headers));
}

static int bin_pe_init_hdr(RBinPEObj *pe) {
	R_FREE (pe->dos_header);
	if (! (pe->dos_header = malloc (sizeof (PE_(image_dos_header))))) {
		r_sys_perror ("malloc (dos header)");
		return false;
	}
	if (PE_(read_dos_header) (pe->b, pe->dos_header) < 0) {
		R_LOG_WARN ("read (dos header)");
		return false;
	}
	sdb_num_set (pe->kv, "pe_dos_header.offset", 0, 0);
	sdb_set (pe->kv, "pe_dos_header.format", "[2]zwwwwwwwwwwwww[4]www[10]wx"
						" e_magic e_cblp e_cp e_crlc e_cparhdr e_minalloc e_maxalloc"
						" e_ss e_sp e_csum e_ip e_cs e_lfarlc e_ovno e_res e_oemid"
						" e_oeminfo e_res2 e_lfanew",
		0);
	if (pe->dos_header->e_lfanew > (unsigned int)pe->size) {
		R_LOG_WARN ("Invalid e_lfanew field");
		return false;
	}
	R_FREE (pe->nt_headers);
	if (! (pe->nt_headers = malloc (sizeof (PE_(image_nt_headers))))) {
		r_sys_perror ("malloc (nt header)");
		return false;
	}
	pe->nt_header_offset = pe->dos_header->e_lfanew;
	if (PE_(read_nt_headers) (pe->b, pe->dos_header->e_lfanew, pe->nt_headers) < 0) {
		R_LOG_WARN ("read (nt header)");
		return false;
	}
	sdb_set (pe->kv, "pe_magic.cparse", "enum pe_magic { IMAGE_NT_OPTIONAL_HDR32_MAGIC=0x10b, IMAGE_NT_OPTIONAL_HDR64_MAGIC=0x20b, IMAGE_ROM_OPTIONAL_HDR_MAGIC=0x107 };", 0);
	sdb_set (pe->kv, "pe_subsystem.cparse", "enum pe_subsystem { IMAGE_SUBSYSTEM_UNKNOWN=0, IMAGE_SUBSYSTEM_NATIVE=1, IMAGE_SUBSYSTEM_WINDOWS_GUI=2, "
					" IMAGE_SUBSYSTEM_WINDOWS_CUI=3, IMAGE_SUBSYSTEM_OS2_CUI=5, IMAGE_SUBSYSTEM_POSIX_CUI=7, IMAGE_SUBSYSTEM_WINDOWS_CE_GUI=9, "
					" IMAGE_SUBSYSTEM_EFI_APPLICATION=10, IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER=11, IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER=12, "
					" IMAGE_SUBSYSTEM_EFI_ROM=13, IMAGE_SUBSYSTEM_XBOX=14, IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION=16 };",
		0);
	sdb_set (pe->kv, "pe_dllcharacteristics.cparse", "enum pe_dllcharacteristics { IMAGE_LIBRARY_PROCESS_INIT=0x0001, IMAGE_LIBRARY_PROCESS_TERM=0x0002, "
							" IMAGE_LIBRARY_THREAD_INIT=0x0004, IMAGE_LIBRARY_THREAD_TERM=0x0008, IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA=0x0020, "
							" IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE=0x0040, IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY=0x0080, "
							" IMAGE_DLLCHARACTERISTICS_NX_COMPAT=0x0100, IMAGE_DLLCHARACTERISTICS_NO_ISOLATION=0x0200,IMAGE_DLLCHARACTERISTICS_NO_SEH=0x0400, "
							" IMAGE_DLLCHARACTERISTICS_NO_BIND=0x0800, IMAGE_DLLCHARACTERISTICS_APPCONTAINER=0x1000, IMAGE_DLLCHARACTERISTICS_WDM_DRIVER=0x2000, "
							" IMAGE_DLLCHARACTERISTICS_GUARD_CF=0x4000, IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE=0x8000};",
		0);
#if R_BIN_PE64
	sdb_num_set (pe->kv, "pe_nt_image_headers64.offset", pe->dos_header->e_lfanew, 0);
	sdb_set (pe->kv, "pe_nt_image_headers64.format", "[4]z?? signature (pe_image_file_header)fileHeader (pe_image_optional_header64)optionalHeader", 0);
	sdb_set (pe->kv, "pe_image_optional_header64.format", "[2]Ebbxxxxxqxxwwwwwwxxxx[2]E[2]Bqqqqxx[16]?"
							" (pe_magic)magic majorLinkerVersion minorLinkerVersion sizeOfCode sizeOfInitializedData"
							" sizeOfUninitializedData addressOfEntryPoint baseOfCode imageBase"
							" sectionAlignment fileAlignment majorOperatingSystemVersion minorOperatingSystemVersion"
							" majorImageVersion minorImageVersion majorSubsystemVersion minorSubsystemVersion"
							" win32VersionValue sizeOfImage sizeOfHeaders checkSum (pe_subsystem)subsystem (pe_dllcharacteristics)dllCharacteristics"
							" sizeOfStackReserve sizeOfStackCommit sizeOfHeapReserve sizeOfHeapCommit loaderFlags"
							" numberOfRvaAndSizes (pe_image_data_directory)dataDirectory",
		0);
#else
	sdb_num_set (pe->kv, "pe_nt_image_headers32.offset", pe->dos_header->e_lfanew, 0);
	sdb_set (pe->kv, "pe_nt_image_headers32.format", "[4]z?? signature (pe_image_file_header)fileHeader (pe_image_optional_header32)optionalHeader", 0);
	sdb_set (pe->kv, "pe_image_optional_header32.format", "[2]Ebbxxxxxxxxxwwwwwwxxxx[2]E[2]Bxxxxxx[16]?"
							" (pe_magic)magic majorLinkerVersion minorLinkerVersion sizeOfCode sizeOfInitializedData"
							" sizeOfUninitializedData addressOfEntryPoint baseOfCode baseOfData imageBase"
							" sectionAlignment fileAlignment majorOperatingSystemVersion minorOperatingSystemVersion"
							" majorImageVersion minorImageVersion majorSubsystemVersion minorSubsystemVersion"
							" win32VersionValue sizeOfImage sizeOfHeaders checkSum (pe_subsystem)subsystem (pe_dllcharacteristics)dllCharacteristics"
							" sizeOfStackReserve sizeOfStackCommit sizeOfHeapReserve sizeOfHeapCommit loaderFlags numberOfRvaAndSizes"
							" (pe_image_data_directory)dataDirectory",
		0);
#endif
	sdb_set (pe->kv, "pe_machine.cparse", "enum pe_machine { IMAGE_FILE_MACHINE_I386=0x014c, IMAGE_FILE_MACHINE_IA64=0x0200, IMAGE_FILE_MACHINE_AMD64=0x8664 };", 0);
	sdb_set (pe->kv, "pe_characteristics.cparse", "enum pe_characteristics { "
						" IMAGE_FILE_RELOCS_STRIPPED=0x0001, IMAGE_FILE_EXECUTABLE_IMAGE=0x0002, IMAGE_FILE_LINE_NUMS_STRIPPED=0x0004, "
						" IMAGE_FILE_LOCAL_SYMS_STRIPPED=0x0008, IMAGE_FILE_AGGRESIVE_WS_TRIM=0x0010, IMAGE_FILE_LARGE_ADDRESS_AWARE=0x0020, "
						" IMAGE_FILE_BYTES_REVERSED_LO=0x0080, IMAGE_FILE_32BIT_MACHINE=0x0100, IMAGE_FILE_DEBUG_STRIPPED=0x0200, "
						" IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP=0x0400, IMAGE_FILE_NET_RUN_FROM_SWAP=0x0800, IMAGE_FILE_SYSTEM=0x1000, "
						" IMAGE_FILE_DLL=0x2000, IMAGE_FILE_UP_SYSTEM_ONLY=0x4000, IMAGE_FILE_BYTES_REVERSED_HI=0x8000 };",
		0);
	sdb_set (pe->kv, "pe_image_file_header.format", "[2]Ewtxxw[2]B"
						" (pe_machine)machine numberOfSections timeDateStamp pointerToSymbolTable"
						" numberOfSymbols sizeOfOptionalHeader (pe_characteristics)characteristics",
		0);
	sdb_set (pe->kv, "pe_image_data_directory.format", "xx virtualAddress size", 0);

	// adding compile time to the SDB
	{
		sdb_num_set (pe->kv, "image_file_header.TimeDateStamp", pe->nt_headers->file_header.TimeDateStamp, 0);
		char *timestr = r_time_secs_tostring (pe->nt_headers->file_header.TimeDateStamp);
		sdb_set_owned (pe->kv, "image_file_header.TimeDateStamp_string", timestr, 0);
	}
	pe->optional_header = &pe->nt_headers->optional_header;
	pe->data_directory = (PE_(image_data_directory *)) & pe->optional_header->DataDirectory;

	if (pe->dos_header->e_magic != 0x5a4d || // "MZ"
		(pe->nt_headers->Signature != 0x4550 && // "PE"
			/* Check also for Phar Lap TNT DOS extender PL executable */
			pe->nt_headers->Signature != 0x4c50)) { // "PL"
		return false;
	}
	return true;
}

typedef struct {
	ut64 shortname;
	ut32 value;
	ut16 secnum;
	ut16 symtype;
	ut8 symclass;
	ut8 numaux;
} SymbolRecord;

static struct r_bin_pe_export_t *parse_symbol_table(RBinPEObj *pe, struct r_bin_pe_export_t *exports, int sz) {
	ut64 sym_tbl_off, num = 0;
	const int srsz = COFF_SYMBOL_SIZE; // symbol record size
	struct r_bin_pe_section_t *sections;
	struct r_bin_pe_export_t *exp;
	struct r_bin_pe_export_t *new_exports = NULL;
	const size_t export_t_sz = sizeof (struct r_bin_pe_export_t);
	int bufsz, i, shsz;
	SymbolRecord sr;
	ut64 text_off = 0LL;
	ut64 text_rva = 0LL;
	int textn = 0;
	int exports_sz;
	int symctr = 0;
	char *buf;

	if (!pe || !pe->nt_headers) {
		return NULL;
	}

	sym_tbl_off = pe->nt_headers->file_header.PointerToSymbolTable;
	num = pe->nt_headers->file_header.NumberOfSymbols;
	shsz = bufsz = num * srsz;
	if (bufsz < 1 || bufsz > pe->size) {
		return NULL;
	}
	buf = calloc (num, srsz);
	if (!buf) {
		return NULL;
	}
	st64 tmp_exports_sz;
	if (r_mul_overflow ((st64)export_t_sz, (st64)num, &tmp_exports_sz) || tmp_exports_sz > ST32_MAX) {
		free (buf);
		return NULL;
	}
	exports_sz = (int)tmp_exports_sz;
	if (exports) {
		int osz = sz;
		int newsz;
		if (r_add_overflow (sz, exports_sz, &newsz) || newsz < 0 || (size_t)newsz + export_t_sz < (size_t)newsz) {
			free (buf);
			return NULL;
		}
		sz = newsz;
		new_exports = realloc (exports, sz + export_t_sz);
		if (!new_exports) {
			free (buf);
			return NULL;
		}
		exports = new_exports;
		new_exports = NULL;
		exp = (struct r_bin_pe_export_t *) (((const ut8 *)exports) + osz);
	} else {
		sz = exports_sz;
		exports = malloc (sz + export_t_sz);
		exp = exports;
		if (!exports) {
			free (buf);
			return NULL;
		}
	}

	sections = pe->sections;
	for (i = 0; i < pe->num_sections; i++) {
		// XXX search by section with +x permission since the section can be left blank
		if (!strcmp ((char *)sections[i].name, ".text")) {
			text_rva = sections[i].vaddr;
			text_off = sections[i].paddr;
			textn = i + 1;
		}
	}
	symctr = 0;
	if (r_buf_read_at (pe->b, sym_tbl_off, (ut8 *)buf, bufsz) > 0) {
		for (i = 0; i < shsz; i += srsz) {
			// sr = (SymbolRecord*) (buf + i);
			if (i + sizeof (sr) >= bufsz) {
				break;
			}
			memcpy (&sr, buf + i, sizeof (sr));
			if (sr.secnum == textn) {
				if (sr.symtype == 32) {
					char shortname[9];
					memcpy (shortname, &sr.shortname, 8);
					shortname[8] = 0;
					if (*shortname) {
						strncpy ((char *)exp[symctr].name, shortname, PE_NAME_LENGTH - 1);
						exp[symctr].name[PE_NAME_LENGTH - 1] = '\0';
					} else {
						char name[128];
						ut32 idx = r_read_le32 (buf + i + 4);
						int nr = r_buf_read_at (pe->b, sym_tbl_off + idx + shsz, (ut8 *)name, sizeof (name));
						if (nr > 0) {
							name[R_MIN (nr, (int)sizeof (name)) - 1] = 0;
							strncpy ((char *)exp[symctr].name, name, PE_NAME_LENGTH - 1);
							exp[symctr].name[PE_NAME_LENGTH - 1] = '\0';
						} else {
							snprintf ((char *)exp[symctr].name, sizeof (exp[symctr].name), "unk_%d", symctr);
						}
					}
					exp[symctr].libname[0] = '\0';
					exp[symctr].vaddr = bin_pe_rva_to_va (pe, text_rva + sr.value);
					exp[symctr].paddr = text_off + sr.value;
					exp[symctr].ordinal = symctr;
					exp[symctr].forwarder[0] = 0;
					exp[symctr].last = 0;
					symctr++;
				}
			}
		} // for
	} // if read ok
	exp[symctr].last = 1;
	free (buf);
	return exports;
}

int PE_(read_image_section_header)(RBuffer *b, ut64 addr, PE_(image_section_header) * section_header) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		section_header->Name[0] = 0;
		return -1;
	}

	ut8 buf[sizeof (PE_(image_section_header))] = { 0 };
	r_buf_read (b, buf, sizeof (buf));
	memcpy (section_header->Name, buf, PE_IMAGE_SIZEOF_SHORT_NAME);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), Misc.PhysicalAddress, 32);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), VirtualAddress, 32);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), SizeOfRawData, 32);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), PointerToRawData, 32);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), PointerToRelocations, 32);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), PointerToLinenumbers, 32);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), NumberOfRelocations, 16);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), NumberOfLinenumbers, 16);
	PE_READ_STRUCT_FIELD (section_header, PE_(image_section_header), Characteristics, 32);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (PE_(image_section_header));
}

void PE_(write_image_section_header)(RBuffer *b, ut64 addr, PE_(image_section_header) * section_header) {
	ut8 buf[sizeof (PE_(image_section_header))];
	memcpy (buf, section_header->Name, PE_IMAGE_SIZEOF_SHORT_NAME);
	r_write_at_le32 (buf, section_header->Misc.PhysicalAddress, PE_IMAGE_SIZEOF_SHORT_NAME);
	r_write_at_le32 (buf, section_header->VirtualAddress, PE_IMAGE_SIZEOF_SHORT_NAME + 4);
	r_write_at_le32 (buf, section_header->SizeOfRawData, PE_IMAGE_SIZEOF_SHORT_NAME + 8);
	r_write_at_le32 (buf, section_header->PointerToRawData, PE_IMAGE_SIZEOF_SHORT_NAME + 12);
	r_write_at_le32 (buf, section_header->PointerToRelocations, PE_IMAGE_SIZEOF_SHORT_NAME + 16);
	r_write_at_le32 (buf, section_header->PointerToLinenumbers, PE_IMAGE_SIZEOF_SHORT_NAME + 20);
	r_write_at_le16 (buf, section_header->NumberOfRelocations, PE_IMAGE_SIZEOF_SHORT_NAME + 24);
	r_write_at_le16 (buf, section_header->NumberOfLinenumbers, PE_IMAGE_SIZEOF_SHORT_NAME + 26);
	r_write_at_le32 (buf, section_header->Characteristics, PE_IMAGE_SIZEOF_SHORT_NAME + 28);
	r_buf_write_at (b, addr, buf, sizeof (PE_(image_section_header)));
}

static struct r_bin_pe_section_t *PE_(r_bin_pe_get_sections)(RBinPEObj *pe);
static int bin_pe_init_sections(RBinPEObj *pe) {
	pe->num_sections = pe->nt_headers->file_header.NumberOfSections;
	if (pe->num_sections < 1) {
		return true;
	}
	int sections_size = sizeof (PE_(image_section_header)) * pe->num_sections;
	if (sections_size > pe->size) {
		sections_size = pe->size;
		pe->num_sections = pe->size / sizeof (PE_(image_section_header));
		// massage this to make corkami happy
		// R_LOG_WARN ("Invalid NumberOfSections value");
		// goto out_error;
	}
	R_FREE (pe->section_header);
	if (! (pe->section_header = malloc (sections_size))) {
		r_sys_perror ("malloc (section header)");
		goto out_error;
	}
	pe->section_header_offset = pe->dos_header->e_lfanew + 4 + sizeof (PE_(image_file_header)) +
		pe->nt_headers->file_header.SizeOfOptionalHeader;
	int i;
	for (i = 0; i < pe->num_sections; i++) {
		if (PE_(read_image_section_header) (pe->b, pe->section_header_offset + i * sizeof (PE_(image_section_header)),
			pe->section_header + i) < 0) {
			R_LOG_WARN ("read (sections)");
			R_FREE (pe->section_header);
			goto out_error;
		}
	}
#if 0
	Each symbol table entry includes a name, storage class, type, value and section number.Short names (8 characters or fewer) are stored directly in the symbol table;
	longer names are stored as an paddr into the string table at the end of the COFF object.

	================================================================
	COFF SYMBOL TABLE RECORDS (18 BYTES)
	================================================================
	record
	paddr

	struct symrec {
		union {
			char string[8]; // short name
			struct {
				ut32 seros;
				ut32 stridx;
			} stridx;
		} name;
		ut32 value;
		ut16 secnum;
		ut16 symtype;
		ut8 symclass;
		ut8 numaux;
	}
	------------------------------------------------------ -
	0 | 8 - char symbol name |
	| or 32 - bit zeroes followed by 32 - bit |
	| index into string table |
	------------------------------------------------------ -
	8 | symbol value |
	------------------------------------------------------ -
	0Ch | section number | symbol type |
	------------------------------------------------------ -
	10h | sym class | num aux |
	-------------------------- -
	12h

#endif
	return true;
out_error:
	pe->num_sections = 0;
	return false;
}

int PE_(bin_pe_get_claimed_checksum)(RBinPEObj *pe) {
	if (!pe || !pe->optional_header) {
		return 0;
	}
	return pe->optional_header->CheckSum;
}

int PE_(bin_pe_get_actual_checksum)(RBinPEObj *pe) {
	size_t i, j, checksum_offset = 0;
	ut64 computed_cs = 0;
	int remaining_bytes;
	int shift;
	ut32 cur;
	if (!pe || !pe->nt_header_offset) {
		return 0;
	}
	const size_t buf_sz = 0x1000;
	ut32 *buf = malloc (buf_sz);
	if (!buf) {
		return 0;
	}
	if (r_buf_read_at (pe->b, 0, (ut8 *)buf, buf_sz) < 0) {
		free (buf);
		return 0;
	}
	checksum_offset = pe->nt_header_offset + 4 + sizeof (PE_(image_file_header)) + 0x40;
	const size_t quarter = pe->size / 4;
	for (i = 0, j = 0; i < quarter; i++) {
		cur = r_read_at_ble32 (buf, j * 4, pe->endian);
		j++;
		// skip the checksum bytes
		if (i * 4 == checksum_offset) {
			continue;
		}

		computed_cs = (computed_cs & 0xFFFFFFFF) + cur + (computed_cs >> 32);
		if (computed_cs >> 32) {
			computed_cs = (computed_cs & 0xFFFFFFFF) + (computed_cs >> 32);
		}
		if (j == buf_sz / 4) {
			if (r_buf_read_at (pe->b, (i + 1) * 4, (ut8 *)buf, buf_sz) < 0) {
				break;
			}
			j = 0;
		}
	}

	// add resultant bytes to checksum
	remaining_bytes = pe->size % 4;
	i = i * 4;
	if (remaining_bytes != 0) {
		cur = r_buf_read8_at (pe->b, i);
		shift = 8;
		for (j = 1; j < remaining_bytes; j++, shift += 8) {
			cur |= r_buf_read8_at (pe->b, i + j) << shift;
		}
		computed_cs = (computed_cs & 0xFFFFFFFF) + cur + (computed_cs >> 32);
		if (computed_cs >> 32) {
			computed_cs = (computed_cs & 0xFFFFFFFF) + (computed_cs >> 32);
		}
	}

	// 32bits -> 16bits
	computed_cs = (computed_cs & 0xFFFF) + (computed_cs >> 16);
	computed_cs = (computed_cs) + (computed_cs >> 16);
	computed_cs = (computed_cs & 0xFFFF);

	// add filesize
	computed_cs += pe->size;
	free (buf);
	return computed_cs;
}

static const char *PE_(bin_pe_get_claimed_authentihash)(RBinPEObj *pe) {
	if (!pe->spcinfo) {
		return NULL;
	}
	RASN1Binary *digest = pe->spcinfo->messageDigest.digest;
	return digest? r_hex_bin2strdup (digest->binary, digest->length): NULL;
}

const char *PE_(bin_pe_compute_authentihash)(RBinPEObj *pe) {
	R_RETURN_VAL_IF_FAIL (pe, NULL);
	if (!pe->spcinfo || !pe->spcinfo->messageDigest.digestAlgorithm.algorithm) {
		return NULL;
	}
	if (!pe->mb || !pe->mb->hash) {
		return NULL;
	}

	char *hashtype = strdup (pe->spcinfo->messageDigest.digestAlgorithm.algorithm->string);
	r_str_replace_char (hashtype, '-', 0);
	// Validate supported hash types
	if (strcmp (hashtype, "md5") && strcmp (hashtype, "sha1") && strcmp (hashtype, "sha256")) {
		R_LOG_INFO ("Authenticode only supports md5, sha1, sha256. This PE uses %s", hashtype);
		free (hashtype);
		return NULL;
	}
	ut32 checksum_paddr = pe->nt_header_offset + 4 + sizeof (PE_(image_file_header)) + 0x40;
	ut32 security_entry_offset = pe->nt_header_offset + sizeof (PE_(image_nt_headers)) - 96;
	PE_(image_data_directory) *data_dir_security = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_SECURITY];
	PE_DWord security_dir_offset = data_dir_security->VirtualAddress;
	ut32 security_dir_size = data_dir_security->Size;

	RBuffer *buf = r_buf_new ();
	r_buf_append_buf_slice (buf, pe->b, 0, checksum_paddr);
	r_buf_append_buf_slice (buf, pe->b,
		checksum_paddr + 4,
		security_entry_offset - checksum_paddr - 4);
	r_buf_append_buf_slice (buf, pe->b,
		security_entry_offset + 8,
		security_dir_offset - security_entry_offset - 8);
	r_buf_append_buf_slice (buf, pe->b,
		security_dir_offset + security_dir_size,
		r_buf_size (pe->b) - security_dir_offset - security_dir_size);

	ut64 len;
	const ut8 *data = r_buf_data (buf, &len);
	char *hashstr = NULL;
	int outlen = 0;
	ut8 *digest = pe->mb->hash (pe->mb, hashtype, data, len, &outlen);
	if (digest && outlen > 0) {
		hashstr = r_hex_bin2strdup (digest, outlen);
		free (digest);
	}
	r_unref (buf);
	free (hashtype);
	return hashstr;
}

const char *PE_(bin_pe_get_authentihash)(RBinPEObj *pe) {
	return pe->authentihash;
}

int PE_(bin_pe_is_authhash_valid)(RBinPEObj *pe) {
	if (!pe) {
		return false;
	}
	// Lazy computation of authentihash validation
	if (!pe->authhash_computed && pe->cms && pe->spcinfo) {
		const char *actual_authentihash = PE_(bin_pe_compute_authentihash) (pe);
		const char *claimed_authentihash = PE_(bin_pe_get_claimed_authentihash) (pe);
		if (actual_authentihash && claimed_authentihash) {
			pe->is_authhash_valid = !strcmp (actual_authentihash, claimed_authentihash);
		} else {
			pe->is_authhash_valid = false;
		}
		if (actual_authentihash) {
			free ((void *)actual_authentihash);
		}
		free ((void *)claimed_authentihash);
		pe->authhash_computed = true;
	}
	return pe->is_authhash_valid;
}

static void computeOverlayOffset(ut64 offset, ut64 size, ut64 file_size, ut64 *largest_offset, ut64 *largest_size) {
	if (offset + size <= file_size && offset + size > (*largest_offset + *largest_size)) {
		*largest_offset = offset;
		*largest_size = size;
	}
}

/* Inspired from https://github.com/erocarrera/pefile/blob/master/pefile.py#L5425 */
int PE_(bin_pe_get_overlay)(RBinPEObj *pe, ut64 *size) {
	ut64 largest_offset = 0;
	ut64 largest_size = 0;
	*size = 0;
	int i;

	if (!pe) {
		return 0;
	}

	if (pe->optional_header) {
		computeOverlayOffset (
			pe->nt_header_offset + 4 + sizeof (pe->nt_headers->file_header),
			pe->nt_headers->file_header.SizeOfOptionalHeader,
			pe->size,
			&largest_offset,
			&largest_size);
	}

	struct r_bin_pe_section_t *sects = pe->sections;
	for (i = 0; !sects[i].last; i++) {
		computeOverlayOffset (
			sects[i].paddr,
			sects[i].size,
			pe->size,
			&largest_offset,
			&largest_size);
	}

	if (pe->optional_header) {
		for (i = 0; i < PE_IMAGE_DIRECTORY_ENTRIES; i++) {
			if (i == PE_IMAGE_DIRECTORY_ENTRY_SECURITY) {
				continue;
			}

			computeOverlayOffset (
				PE_(va2pa) (pe, pe->data_directory[i].VirtualAddress),
				pe->data_directory[i].Size,
				pe->size,
				&largest_offset,
				&largest_size);
		}
	}

	if ((ut64)pe->size > largest_offset + largest_size) {
		*size = pe->size - largest_offset - largest_size;
		return largest_offset + largest_size;
	}
	return 0;
}

static int bin_pe_read_metadata_string(char *to, RBuffer *frombuf, int fromoff) {
	int covered = 0;
	*to = 0;
	while (covered < MAX_METADATA_STRING_LENGTH) {
		const char covch = r_buf_read8_at (frombuf, fromoff + covered);
		to[covered++] = covch;
		if (!covch) {
			break;
		}
	}
	while (covered % 4 != 0) {
		covered++;
	}
	return covered;
}

static void bin_pe_free_metadata_streams(PE_(image_metadata_stream) **streams, int n_streams) {
	int i;
	if (streams) {
		for (i = 0; i < n_streams; i++) {
			PE_(image_metadata_stream) *stream = streams[i];
			if (stream) {
				free (stream->Name);
			}
			free (stream);
		}
	}
	free (streams);
}

static bool bin_pe_init_metadata_hdr(RBinPEObj *pe) {
	PE_(image_metadata_header) *metadata = R_NEW0 (PE_(image_metadata_header));
	PE_(image_metadata_stream) **streams = NULL;
	int parsed_streams = 0;
	PE_DWord metadata_directory = pe->clr_hdr? PE_(va2pa) (pe, pe->clr_hdr->MetaDataDirectoryAddress): 0;
	if (!metadata_directory) {
		free (metadata);
		return false;
	}
	int rr = r_buf_fread_at (pe->b, metadata_directory,
		(ut8 *)metadata, pe->big_endian? "1I2S": "1i2s", 1);
	if (rr < 1) {
		goto fail;
	}

	rr = r_buf_fread_at (pe->b, metadata_directory + 8,
		(ut8 *) (&metadata->Reserved), pe->big_endian? "1I": "1i", 1);
	if (rr < 1) {
		goto fail;
	}

	rr = r_buf_fread_at (pe->b, metadata_directory + 12,
		(ut8 *) (&metadata->VersionStringLength), pe->big_endian? "1I": "1i", 1);
	if (rr < 1) {
		goto fail;
	}

	R_LOG_DEBUG ("Metadata Signature: 0x%" PFMT64x " 0x%" PFMT64x " %d",
		(ut64)metadata_directory, (ut64)metadata->Signature, (int)metadata->VersionStringLength);

	// read the version string
	int len = metadata->VersionStringLength;
	if (len > 0 && len < 1024) {
		metadata->VersionString = calloc (1, len + 1);
		if (!metadata->VersionString) {
			goto fail;
		}

		rr = r_buf_read_at (pe->b, metadata_directory + 16, (ut8 *) (metadata->VersionString), len);
		if (rr != len) {
			R_LOG_DEBUG ("read (metadata header) - cannot parse version string");
			free (metadata->VersionString);
			free (metadata);
			return 0;
		}
		R_LOG_DEBUG (".NET Version: %s", metadata->VersionString);
	}

	// read the header after the string
	rr = r_buf_fread_at (pe->b, metadata_directory + 16 + metadata->VersionStringLength,
		(ut8 *) (&metadata->Flags), pe->big_endian? "2S": "2s", 1);
	if (rr < 1) {
		goto fail;
	}
	R_LOG_DEBUG ("Number of Metadata Streams: %d", metadata->NumberOfStreams);

	// read metadata streams
	int stream_addr = metadata_directory + 20 + metadata->VersionStringLength;
	PE_(image_metadata_stream) * stream;
	streams = calloc (sizeof (PE_(image_metadata_stream) *), metadata->NumberOfStreams);
	if (!streams && metadata->NumberOfStreams > 0) {
		goto fail;
	}
	int count;
	for (count = 0; count < metadata->NumberOfStreams; count++) {
		stream = R_NEW0 (PE_(image_metadata_stream));
		if (r_buf_size (pe->b) < (stream_addr + 8 + MAX_METADATA_STRING_LENGTH)) {
			R_LOG_DEBUG ("metadata strnig truncated");
			free (stream);
			goto fail;
		}
		if (r_buf_fread_at (pe->b, stream_addr, (ut8 *)stream, pe->big_endian? "2I": "2i", 1) < 1) {
			free (stream);
			goto fail;
		}
		R_LOG_DEBUG ("DirectoryAddress: %x Size: %x", stream->Offset, stream->Size);
		char *stream_name = calloc (1, MAX_METADATA_STRING_LENGTH + 1);

		if (!stream_name) {
			free (stream);
			goto fail;
		}

		int c = bin_pe_read_metadata_string (stream_name, pe->b, stream_addr + 8);
		if (c == 0) {
			free (stream_name);
			free (stream);
			goto fail;
		}
		R_LOG_DEBUG ("Stream name: %s %d", stream_name, c);
		stream->Name = stream_name;
		streams[count] = stream;
		parsed_streams++;
		stream_addr += 8 + c;
	}
	// free previous allocations in case of duplicate metadata header
	if (pe->metadata_header) {
		int old_n_streams = pe->metadata_header->NumberOfStreams;
		bin_pe_free_metadata_streams (pe->streams, old_n_streams);
		pe->streams = NULL;
		free (pe->metadata_header->VersionString);
		R_FREE (pe->metadata_header);
	}
	pe->metadata_header = metadata;
	pe->streams = streams;
	return true;
fail:
	R_LOG_DEBUG ("read (metadata header)");
	bin_pe_free_metadata_streams (streams, parsed_streams);
	if (metadata) {
		free (metadata->VersionString);
		free (metadata);
	}
	return false;
}

static int bin_pe_init_overlay(RBinPEObj *pe) {
	ut64 pe_overlay_size;
	ut64 pe_overlay_offset = PE_(bin_pe_get_overlay) (pe, &pe_overlay_size);
	if (pe_overlay_offset) {
		sdb_num_set (pe->kv, "pe_overlay.offset", pe_overlay_offset, 0);
		sdb_num_set (pe->kv, "pe_overlay.size", pe_overlay_size, 0);
	}
	return 0;
}

static int read_image_clr_header(RBuffer *b, ut64 addr, PE_(image_clr_header) * header) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	ut8 buf[sizeof (PE_(image_clr_header))];
	r_buf_read (b, buf, sizeof (buf));
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), HeaderSize, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), MajorRuntimeVersion, 16);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), MinorRuntimeVersion, 16);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), MetaDataDirectoryAddress, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), MetaDataDirectorySize, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), Flags, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), EntryPointToken, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), ResourcesDirectoryAddress, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), ResourcesDirectorySize, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), StrongNameSignatureAddress, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), StrongNameSignatureSize, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), CodeManagerTableAddress, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), CodeManagerTableSize, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), VTableFixupsAddress, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), VTableFixupsSize, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), ExportAddressTableJumpsAddress, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), ExportAddressTableJumpsSize, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), ManagedNativeHeaderAddress, 32);
	PE_READ_STRUCT_FIELD (header, PE_(image_clr_header), ManagedNativeHeaderSize, 32);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (PE_(image_clr_header));
}

static bool bin_pe_init_dotnet_version(RBinPEObj *pe) {
	if (!pe) {
		return false;
	}

	const ut8 *data = r_buf_data (pe->b, NULL);
	st64 size = r_buf_size (pe->b);
	if (!data || size <= 0 || size > INT_MAX) {
		return false;
	}

	DotNetVersionInfo *version_info = dotnet_parse_version_info (data, (int)size);
	if (!version_info) {
		return false;
	}

	// Store .NET version information in sdb
	sdb_num_set (pe->kv, "dotnet.clr_major", version_info->cli_major, 0);
	sdb_num_set (pe->kv, "dotnet.clr_minor", version_info->cli_minor, 0);
	sdb_num_set (pe->kv, "dotnet.assembly_major", version_info->asm_major, 0);
	sdb_num_set (pe->kv, "dotnet.assembly_minor", version_info->asm_minor, 0);
	sdb_num_set (pe->kv, "dotnet.assembly_build", version_info->asm_build, 0);
	sdb_num_set (pe->kv, "dotnet.assembly_revision", version_info->asm_revision, 0);

	free (version_info->asm_name);
	free (version_info);
	return true;
}

static int bin_pe_init_clr_hdr(RBinPEObj *pe) {
	PE_(image_data_directory) *clr_dir = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
	PE_DWord image_clr_hdr_paddr = PE_(va2pa) (pe, clr_dir->VirtualAddress);
	// int clr_dir_size = clr_dir? clr_dir->Size: 0;
	R_FREE (pe->clr_hdr);
	PE_(image_clr_header) *clr_hdr = R_NEW0 (PE_(image_clr_header));
	int len = sizeof (PE_(image_clr_header));
	int rr = read_image_clr_header (pe->b, image_clr_hdr_paddr, clr_hdr);

	//	printf ("%x\n", clr_hdr->HeaderSize);

	if (clr_hdr->HeaderSize != 0x48) {
		// probably not a .NET binary
		// 64bit?
		free (clr_hdr);
		return 0;
	}
	if (rr != len) {
		free (clr_hdr);
		return 0;
	}
	pe->clr_hdr = clr_hdr;
	// Extract .NET version information from MSIL headers
	bin_pe_init_dotnet_version (pe);
	return 1;
}

static int read_image_import_directory(RBuffer *b, ut64 addr, PE_(image_import_directory) * import_dir) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	ut8 buf[sizeof (PE_(image_import_directory))];
	r_buf_read (b, buf, sizeof (buf));
	PE_READ_STRUCT_FIELD (import_dir, PE_(image_import_directory), Characteristics, 32);
	PE_READ_STRUCT_FIELD (import_dir, PE_(image_import_directory), TimeDateStamp, 32);
	PE_READ_STRUCT_FIELD (import_dir, PE_(image_import_directory), ForwarderChain, 32);
	PE_READ_STRUCT_FIELD (import_dir, PE_(image_import_directory), Name, 32);
	PE_READ_STRUCT_FIELD (import_dir, PE_(image_import_directory), FirstThunk, 32);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (PE_(image_import_directory));
}

static int read_image_delay_import_directory(RBuffer *b, ut64 addr, PE_(image_delay_import_directory) * directory) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	ut8 buf[sizeof (PE_(image_delay_import_directory))];
	r_buf_read (b, buf, sizeof (buf));
	PE_READ_STRUCT_FIELD (directory, PE_(image_delay_import_directory), Attributes, 32);
	PE_READ_STRUCT_FIELD (directory, PE_(image_delay_import_directory), Name, 32);
	PE_READ_STRUCT_FIELD (directory, PE_(image_delay_import_directory), ModulePlugin, 32);
	PE_READ_STRUCT_FIELD (directory, PE_(image_delay_import_directory), DelayImportAddressTable, 32);
	PE_READ_STRUCT_FIELD (directory, PE_(image_delay_import_directory), DelayImportNameTable, 32);
	PE_READ_STRUCT_FIELD (directory, PE_(image_delay_import_directory), BoundDelayImportTable, 32);
	PE_READ_STRUCT_FIELD (directory, PE_(image_delay_import_directory), UnloadDelayImportTable, 32);
	PE_READ_STRUCT_FIELD (directory, PE_(image_delay_import_directory), TimeStamp, 32);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (PE_(image_delay_import_directory));
}

static bool is_null_delay_import_directory(const PE_(image_delay_import_directory) *dir) {
	return !dir->Attributes && !dir->Name && !dir->ModulePlugin &&
		!dir->DelayImportAddressTable && !dir->DelayImportNameTable &&
		!dir->BoundDelayImportTable && !dir->UnloadDelayImportTable &&
		!dir->TimeStamp;
}

static ut64 get_delay_import_directory_last(RBinPEObj *pe) {
	ut64 off = pe->delay_import_directory_offset;
	ut64 size = pe->delay_import_directory_size;
	if (!off || off >= pe->size || size < sizeof (PE_(image_delay_import_directory))) {
		return 0;
	}
	ut64 maxsize = pe->size - off;
	if (size > maxsize) {
		R_LOG_WARN ("read (delay import directory too big)");
		size = maxsize;
		pe->delay_import_directory_size = size;
	}
	return off + size;
}

static int bin_pe_init_imports(RBinPEObj *pe) {
	PE_(image_data_directory) *data_dir_import = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_IMPORT];
	PE_(image_data_directory) *data_dir_delay_import = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT];

	PE_DWord import_dir_paddr = PE_(va2pa) (pe, data_dir_import->VirtualAddress);
	PE_DWord import_dir_offset = PE_(va2pa) (pe, data_dir_import->VirtualAddress);
	PE_DWord delay_import_dir_offset = data_dir_delay_import
		? PE_(va2pa) (pe, data_dir_delay_import->VirtualAddress)
		: 0;

	PE_(image_import_directory) *import_dir = NULL;
	PE_(image_import_directory) *new_import_dir = NULL;
	PE_(image_import_directory) *curr_import_dir = NULL;

	PE_(image_delay_import_directory) *delay_import_dir = NULL;
	PE_(image_delay_import_directory) *new_delay_import_dir = NULL;
	PE_(image_delay_import_directory) *curr_delay_import_dir = NULL;

	int dir_size = sizeof (PE_(image_import_directory));
	int delay_import_size = sizeof (PE_(image_delay_import_directory));
	int indx = 0;
	int rr;
	int import_dir_size = data_dir_import->Size;
	int delay_import_dir_size = data_dir_delay_import->Size;
	/// HACK to modify import size because of begin 0.. this may report wrong info con corkami tests
	if (!import_dir_size) {
		// asume 1 entry for each
		import_dir_size = data_dir_import->Size = 0xffff;
	}
	if (!delay_import_dir_size) {
		// asume 1 entry for each
		delay_import_dir_size = data_dir_delay_import->Size = 0xffff;
	}
	int maxidsz = R_MIN ((PE_DWord)pe->size, import_dir_offset + import_dir_size);
	maxidsz -= import_dir_offset;
	if (maxidsz < 0) {
		maxidsz = 0;
	}
	// int maxcount = maxidsz/ sizeof (struct r_bin_pe_import_t);

	R_FREE (pe->import_directory);
	R_FREE (pe->delay_import_directory);
	pe->delay_import_directory_offset = 0;
	pe->delay_import_directory_size = 0;
	if (import_dir_paddr != 0) {
		if (import_dir_size < 1 || import_dir_size > maxidsz) {
			R_LOG_WARN ("Invalid import directory size: 0x%x is now 0x%x", import_dir_size, maxidsz);
			import_dir_size = maxidsz;
		}
		pe->import_directory_offset = import_dir_offset;
		do {
			new_import_dir = (PE_(image_import_directory) *)realloc (import_dir, ((1 + indx) * dir_size));
			if (!new_import_dir) {
				R_LOG_ERROR ("malloc (import directory)");
				R_FREE (import_dir);
				break;
			}
			import_dir = new_import_dir;
			new_import_dir = NULL;
			curr_import_dir = import_dir + indx;
			if (read_image_import_directory (pe->b, import_dir_offset + indx * dir_size, curr_import_dir) <= 0) {
				R_LOG_WARN ("read (import directory)");
				R_FREE (import_dir);
				break;
			}
			if (((2 + indx) * dir_size) > import_dir_size) {
				break;
			}
			indx++;
		} while (curr_import_dir->FirstThunk != 0 || curr_import_dir->Name != 0 ||
			curr_import_dir->TimeDateStamp != 0 || curr_import_dir->Characteristics != 0 ||
			curr_import_dir->ForwarderChain != 0);

		pe->import_directory = import_dir;
		pe->import_directory_size = import_dir_size;
	}

	indx = 0;
	if (delay_import_dir_offset && delay_import_dir_offset < pe->size) {
		int maxdidsz = pe->size - delay_import_dir_offset;
		if (delay_import_dir_size < 1 || delay_import_dir_size > maxdidsz) {
			R_LOG_WARN ("Invalid delay import directory size: 0x%x is now 0x%x", delay_import_dir_size, maxdidsz);
			delay_import_dir_size = maxdidsz;
		}
		pe->delay_import_directory_offset = delay_import_dir_offset;
		pe->delay_import_directory_size = delay_import_dir_size;
		do {
			new_delay_import_dir = (PE_(image_delay_import_directory) *)realloc (
				delay_import_dir, (indx + 1) * delay_import_size);
			if (!new_delay_import_dir) {
				R_LOG_ERROR ("malloc (delay import directory)");
				free (delay_import_dir);
				return false;
			}
			delay_import_dir = new_delay_import_dir;
			curr_delay_import_dir = delay_import_dir + indx;
			rr = read_image_delay_import_directory (pe->b, delay_import_dir_offset + indx * delay_import_size,
				curr_delay_import_dir);
			if (rr != delay_import_size) {
				R_LOG_WARN ("read (delay import directory)");
				R_FREE (import_dir);
				pe->import_directory = import_dir;
				free (delay_import_dir);
				return false;
			}
			if (((2 + indx) * delay_import_size) > delay_import_dir_size) {
				break;
			}
			indx++;
		} while (!is_null_delay_import_directory (curr_delay_import_dir));
		pe->delay_import_directory = delay_import_dir;
	}

	return true;
}

static int read_image_export_directory(RBuffer *b, ut64 addr, PE_(image_export_directory) * export_dir) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	ut8 buf[sizeof (PE_(image_export_directory))];
	r_buf_read (b, buf, sizeof (buf));
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), Characteristics, 32);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), TimeDateStamp, 32);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), MajorVersion, 16);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), MinorVersion, 16);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), Name, 32);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), Base, 32);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), NumberOfFunctions, 32);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), NumberOfNames, 32);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), AddressOfFunctions, 32);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), AddressOfNames, 32);
	PE_READ_STRUCT_FIELD (export_dir, PE_(image_export_directory), AddressOfOrdinals, 32);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (PE_(image_export_directory));
}

static int bin_pe_init_exports(RBinPEObj *pe) {
	PE_(image_data_directory) *data_dir_export = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_EXPORT];
	PE_DWord export_dir_paddr = PE_(va2pa) (pe, data_dir_export->VirtualAddress);
	if (!export_dir_paddr) {
		// This export-dir-paddr should only appear in DLL files
		// R_LOG_WARN ("Cannot find the paddr of the export directory");
		return false;
	}
	// sdb_setn (DB, "hdr.exports_directory", export_dir_paddr);
	// R_LOG_WARN ("Pexports paddr at 0x%"PFMT64x, export_dir_paddr);
	R_FREE (pe->export_directory);
	if (! (pe->export_directory = malloc (sizeof (PE_(image_export_directory))))) {
		R_LOG_ERROR ("malloc (export directory)");
		return false;
	}
	if (read_image_export_directory (pe->b, export_dir_paddr, pe->export_directory) < 0) {
		R_LOG_WARN ("read (export directory)");
		R_FREE (pe->export_directory);
		return false;
	}
	return true;
}

static void _free_resource(r_pe_resource *rs) {
	if (rs) {
		free (rs->name);
		free (rs->timestr);
		free (rs->data);
		free (rs->type);
		free (rs->language);
		free (rs);
	}
}

static int read_image_resource_directory(RBuffer *b, ut64 addr, Pe_image_resource_directory *dir) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	dir->Characteristics = r_buf_read_le32 (b);
	dir->TimeDateStamp = r_buf_read_le32 (b);
	dir->MajorVersion = r_buf_read_le16 (b);
	dir->MinorVersion = r_buf_read_le16 (b);
	dir->NumberOfNamedEntries = r_buf_read_le16 (b);
	dir->NumberOfIdEntries = r_buf_read_le16 (b);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (Pe_image_resource_directory);
}

static int bin_pe_init_resource(RBinPEObj *pe) {
	PE_(image_data_directory) *resource_dir = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_RESOURCE];
	PE_DWord resource_dir_paddr = PE_(va2pa) (pe, resource_dir->VirtualAddress);
	if (!resource_dir_paddr) {
		return false;
	}

	r_list_free (pe->resources);
	pe->resources = r_list_newf ((RListFree)_free_resource);
	if (!pe->resources) {
		return false;
	}
	R_FREE (pe->resource_directory);
	if (! (pe->resource_directory = malloc (sizeof (*pe->resource_directory)))) {
		r_sys_perror ("malloc (resource directory)");
		r_list_free (pe->resources);
		pe->resources = NULL;
		return false;
	}
	if (read_image_resource_directory (pe->b, resource_dir_paddr, pe->resource_directory) < 0) {
		R_LOG_WARN ("read (resource directory)");
		R_FREE (pe->resource_directory);
		return false;
	}
	pe->resource_directory_offset = resource_dir_paddr;
	return true;
}

static void bin_pe_store_tls_callbacks(RBinPEObj *pe, PE_DWord callbacks) {
	r_strf_buffer (64);
	PE_DWord paddr, haddr;
	int count = 0;
	PE_DWord addressOfTLSCallback = 1;
	char *key;

	while (addressOfTLSCallback != 0) {
		addressOfTLSCallback = R_BUF_READ_PE_DWORD_AT (pe->b, callbacks);
		if (addressOfTLSCallback == PE_DWORD_MAX) {
			return;
		}
		if (!addressOfTLSCallback) {
			break;
		}
		if (pe->optional_header->SizeOfImage) {
			int rva_callback = bin_pe_va_to_rva (pe, (PE_DWord)addressOfTLSCallback);
			if (rva_callback > pe->optional_header->SizeOfImage) {
				break;
			}
		}
		key = r_strf ("pe.tls_callback%d_vaddr", count);
		sdb_num_set (pe->kv, key, addressOfTLSCallback, 0);
		key = r_strf ("pe.tls_callback%d_paddr", count);
		paddr = PE_(va2pa) (pe, bin_pe_va_to_rva (pe, (PE_DWord)addressOfTLSCallback));
		sdb_num_set (pe->kv, key, paddr, 0);
		key = r_strf ("pe.tls_callback%d_haddr", count);
		haddr = callbacks;
		sdb_num_set (pe->kv, key, haddr, 0);
		count++;
		callbacks += sizeof (addressOfTLSCallback);
	}
}

static int read_tls_directory(RBuffer *b, ut64 addr, PE_(image_tls_directory) * tls_directory) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
#if R_BIN_PE64
	tls_directory->StartAddressOfRawData = r_buf_read_le64 (b);
	tls_directory->EndAddressOfRawData = r_buf_read_le64 (b);
	tls_directory->AddressOfIndex = r_buf_read_le64 (b);
	tls_directory->AddressOfCallBacks = r_buf_read_le64 (b);
#else
	tls_directory->StartAddressOfRawData = r_buf_read_le32 (b);
	tls_directory->EndAddressOfRawData = r_buf_read_le32 (b);
	tls_directory->AddressOfIndex = r_buf_read_le32 (b);
	tls_directory->AddressOfCallBacks = r_buf_read_le32 (b);
#endif
	tls_directory->SizeOfZeroFill = r_buf_read_le32 (b);
	tls_directory->Characteristics = r_buf_read_le32 (b);

	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (PE_(image_tls_directory));
}

static int bin_pe_init_tls(RBinPEObj *pe) {
	PE_(image_tls_directory) * image_tls_directory;
	PE_(image_data_directory) *data_dir_tls = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_TLS];
	PE_DWord tls_paddr = PE_(va2pa) (pe, data_dir_tls->VirtualAddress);

	R_FREE (pe->tls_directory);
	image_tls_directory = R_NEW0 (PE_(image_tls_directory));
	if (read_tls_directory (pe->b, tls_paddr, image_tls_directory) < 0) {
		R_LOG_WARN ("read (image_tls_directory)");
		free (image_tls_directory);
		return 0;
	}
	pe->tls_directory = image_tls_directory;
	if (!image_tls_directory->AddressOfCallBacks) {
		return 0;
	}
	if (image_tls_directory->EndAddressOfRawData < image_tls_directory->StartAddressOfRawData) {
		return 0;
	}
	PE_DWord callbacks_paddr = PE_(va2pa) (pe, bin_pe_va_to_rva (pe, (PE_DWord)image_tls_directory->AddressOfCallBacks));
	bin_pe_store_tls_callbacks (pe, callbacks_paddr);
	return 0;
}

static void free_Var(Var *var) {
	if (var) {
		free (var->szKey);
		free (var->Value);
		free (var);
	}
}

static void free_VarFileInfo(VarFileInfo *varFileInfo) {
	if (varFileInfo) {
		free (varFileInfo->szKey);
		if (varFileInfo->Children) {
			ut32 children = 0;
			for (; children < varFileInfo->numOfChildren; children++) {
				free_Var (varFileInfo->Children[children]);
			}
			free (varFileInfo->Children);
		}
		free (varFileInfo);
	}
}

static void free_String(String *string) {
	if (string) {
		free (string->szKey);
		free (string->Value);
		free (string);
	}
}

static void free_StringTable(StringTable *stringTable) {
	if (stringTable) {
		free (stringTable->szKey);
		if (stringTable->Children) {
			ut32 childrenST = 0;
			for (; childrenST < stringTable->numOfChildren; childrenST++) {
				free_String (stringTable->Children[childrenST]);
			}
			free (stringTable->Children);
		}
		free (stringTable);
	}
}

static void free_StringFileInfo(StringFileInfo *stringFileInfo) {
	if (stringFileInfo) {
		free (stringFileInfo->szKey);
		if (stringFileInfo->Children) {
			ut32 childrenSFI = 0;
			for (; childrenSFI < stringFileInfo->numOfChildren; childrenSFI++) {
				free_StringTable (stringFileInfo->Children[childrenSFI]);
			}
			free (stringFileInfo->Children);
		}
		free (stringFileInfo);
	}
}

#define align32(x) x =(((x) & 0x3) == 0)? (x): ((x) & ~0x3) + 0x4;

static void free_VS_VERSIONINFO(PE_VS_VERSIONINFO *vs_VersionInfo) {
	if (vs_VersionInfo) {
		free (vs_VersionInfo->szKey);
		free (vs_VersionInfo->Value);
		free_VarFileInfo (vs_VersionInfo->varFileInfo);
		free_StringFileInfo (vs_VersionInfo->stringFileInfo);
		free (vs_VersionInfo);
	}
}

void PE_(free_VS_VERSIONINFO)(PE_VS_VERSIONINFO *vs_VersionInfo) {
	free_VS_VERSIONINFO (vs_VersionInfo);
}

static Var *Pe_r_bin_pe_parse_var(RBinPEObj *pe, PE_DWord *curAddr) {
	Var *var = calloc (1, sizeof (*var));
	if (!var) {
		R_LOG_WARN ("calloc (Var)");
		return NULL;
	}
	if ((var->wLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (Var wLength)");
		free_Var (var);
		return NULL;
	}
	*curAddr += sizeof (var->wLength);
	if ((var->wValueLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (Var wValueLength)");
		free_Var (var);
		return NULL;
	}
	*curAddr += sizeof (var->wValueLength);
	if ((var->wType = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (Var wType)");
		free_Var (var);
		return NULL;
	}
	*curAddr += sizeof (var->wType);
	if (var->wType != 0 && var->wType != 1) {
		R_LOG_WARN ("check (Var wType)");
		free_Var (var);
		return NULL;
	}

	var->szKey = (ut16 *)malloc (UT16_ALIGN (TRANSLATION_UTF_16_LEN)); // L"Translation"
	if (!var->szKey) {
		R_LOG_WARN ("malloc (Var szKey)");
		free_Var (var);
		return NULL;
	}
	if (r_buf_read_at (pe->b, *curAddr, (ut8 *)var->szKey, TRANSLATION_UTF_16_LEN) != TRANSLATION_UTF_16_LEN) {
		R_LOG_WARN ("read (Var szKey)");
		free_Var (var);
		return NULL;
	}
	*curAddr += TRANSLATION_UTF_16_LEN;
	if (memcmp (var->szKey, TRANSLATION_UTF_16, TRANSLATION_UTF_16_LEN)) {
		R_LOG_WARN ("check (Var szKey)");
		free_Var (var);
		return NULL;
	}
	align32 (*curAddr);
	var->numOfValues = var->wValueLength / 4;
	if (!var->numOfValues) {
		R_LOG_WARN ("check (Var numOfValues)");
		free_Var (var);
		return NULL;
	}
	var->Value = (ut32 *)malloc (var->wValueLength);
	if (!var->Value) {
		R_LOG_WARN ("malloc (Var Value)");
		free_Var (var);
		return NULL;
	}
	if (r_buf_read_at (pe->b, *curAddr, (ut8 *)var->Value, var->wValueLength) != var->wValueLength) {
		R_LOG_WARN ("read (Var Value)");
		free_Var (var);
		return NULL;
	}
	*curAddr += var->wValueLength;
	return var;
}

static VarFileInfo *Pe_r_bin_pe_parse_var_file_info(RBinPEObj *pe, PE_DWord *curAddr) {
	VarFileInfo *varFileInfo = calloc (1, sizeof (*varFileInfo));
	if (!varFileInfo) {
		R_LOG_WARN ("calloc (VarFileInfo)");
		return NULL;
	}
	PE_DWord startAddr = *curAddr;
	if ((varFileInfo->wLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (VarFileInfo wLength)");
		free_VarFileInfo (varFileInfo);
		return NULL;
	}
	*curAddr += sizeof (varFileInfo->wLength);

	if ((varFileInfo->wValueLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (VarFileInfo wValueLength)");
		free_VarFileInfo (varFileInfo);
		return NULL;
	}
	*curAddr += sizeof (varFileInfo->wValueLength);

	if (varFileInfo->wValueLength != 0) {
		R_LOG_WARN ("check (VarFileInfo wValueLength)");
		free_VarFileInfo (varFileInfo);
		return NULL;
	}

	if ((varFileInfo->wType = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (VarFileInfo wType)");
		free_VarFileInfo (varFileInfo);
		return NULL;
	}
	*curAddr += sizeof (varFileInfo->wType);
	if (varFileInfo->wType && varFileInfo->wType != 1) {
		R_LOG_WARN ("check (VarFileInfo wType)");
		free_VarFileInfo (varFileInfo);
		return NULL;
	}

	varFileInfo->szKey = (ut16 *)malloc (UT16_ALIGN (VARFILEINFO_UTF_16_LEN)); // L"VarFileInfo"
	if (!varFileInfo->szKey) {
		R_LOG_WARN ("malloc (VarFileInfo szKey)");
		free_VarFileInfo (varFileInfo);
		return NULL;
	}

	if (r_buf_read_at (pe->b, *curAddr, (ut8 *)varFileInfo->szKey, VARFILEINFO_UTF_16_LEN) != VARFILEINFO_UTF_16_LEN) {
		R_LOG_WARN ("read (VarFileInfo szKey)");
		free_VarFileInfo (varFileInfo);
		return NULL;
	}
	*curAddr += VARFILEINFO_UTF_16_LEN;

	if (memcmp (varFileInfo->szKey, VARFILEINFO_UTF_16, VARFILEINFO_UTF_16_LEN)) {
		R_LOG_WARN ("check (VarFileInfo szKey)");
		free_VarFileInfo (varFileInfo);
		return NULL;
	}
	align32 (*curAddr);
	while (startAddr + varFileInfo->wLength > *curAddr) {
		Var **tmp = (Var **)realloc (varFileInfo->Children, (varFileInfo->numOfChildren + 1) * sizeof (*varFileInfo->Children));
		if (!tmp) {
			R_LOG_WARN ("realloc (VarFileInfo Children)");
			free_VarFileInfo (varFileInfo);
			return NULL;
		}
		varFileInfo->Children = tmp;
		if (! (varFileInfo->Children[varFileInfo->numOfChildren] = Pe_r_bin_pe_parse_var (pe, curAddr))) {
			R_LOG_WARN ("bad parsing Var");
			free_VarFileInfo (varFileInfo);
			return NULL;
		}
		varFileInfo->numOfChildren++;
		align32 (*curAddr);
	}
	return varFileInfo;
}

static String *Pe_r_bin_pe_parse_string(RBinPEObj *pe, PE_DWord *curAddr) {
	String *string = calloc (1, sizeof (*string));
	PE_DWord begAddr = *curAddr;
	int len_value = 0;
	int i = 0;
	if (!string) {
		R_LOG_WARN ("calloc (String)");
		return NULL;
	}
	if (begAddr > pe->size || begAddr + sizeof (string->wLength) > pe->size) {
		free_String (string);
		return NULL;
	}
	if ((string->wLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (String wLength)");
		goto out_error;
	}
	*curAddr += sizeof (string->wLength);
	if (*curAddr > pe->size || *curAddr + sizeof (string->wValueLength) > pe->size) {
		goto out_error;
	}
	if ((string->wValueLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (String wValueLength)");
		goto out_error;
	}
	*curAddr += sizeof (string->wValueLength);

	if (*curAddr > pe->size || *curAddr + sizeof (string->wType) > pe->size) {
		goto out_error;
	}
	if ((string->wType = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (String wType)");
		goto out_error;
	}
	*curAddr += sizeof (string->wType);
	if (string->wType != 0 && string->wType != 1) {
		R_LOG_WARN ("check (String wType)");
		goto out_error;
	}

	for (i = 0; *curAddr < begAddr + string->wLength; i++, *curAddr += sizeof (ut16)) {
		ut16 utf16_char;
		ut16 *tmpKey;
		if (*curAddr > pe->size || *curAddr + sizeof (ut16) > pe->size) {
			goto out_error;
		}
		if (r_buf_read_at (pe->b, *curAddr, (ut8 *)&utf16_char, sizeof (ut16)) != sizeof (ut16)) {
			R_LOG_WARN ("check (String szKey)");
			goto out_error;
		}
		tmpKey = (ut16 *)realloc (string->szKey, (i + 1) * sizeof (ut16));
		if (!tmpKey) {
			R_LOG_WARN ("realloc (String szKey)");
			goto out_error;
		}
		string->szKey = tmpKey;
		string->szKey[i] = utf16_char;
		string->wKeyLen += sizeof (ut16);
		if (!utf16_char) {
			*curAddr += sizeof (ut16);
			break;
		}
	}
	align32 (*curAddr);
	len_value = R_MIN (string->wValueLength * 2, string->wLength - (*curAddr - begAddr));
	string->wValueLength = len_value;
	if (len_value < 0) {
		len_value = 0;
	}
	string->Value = (ut16 *)calloc (len_value + 1, 1);
	if (!string->Value) {
		R_LOG_WARN ("malloc (String Value)");
		goto out_error;
	}
	if (*curAddr > pe->size || *curAddr + len_value > pe->size) {
		goto out_error;
	}
	if (r_buf_read_at (pe->b, *curAddr, (ut8 *)string->Value, len_value) != len_value) {
		R_LOG_WARN ("read (String Value)");
		goto out_error;
	}
	*curAddr += len_value;
	return string;
out_error:
	free_String (string);
	return NULL;
}

static StringTable *Pe_r_bin_pe_parse_string_table(RBinPEObj *pe, PE_DWord *curAddr) {
	StringTable *stringTable = calloc (1, sizeof (*stringTable));
	if (!stringTable) {
		R_LOG_WARN ("calloc (stringTable)");
		return NULL;
	}

	PE_DWord startAddr = *curAddr;
	if ((stringTable->wLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (StringTable wLength)");
		free_StringTable (stringTable);
		return NULL;
	}
	*curAddr += sizeof (stringTable->wLength);

	if ((stringTable->wValueLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (StringTable wValueLength)");
		free_StringTable (stringTable);
		return NULL;
	}
	*curAddr += sizeof (stringTable->wValueLength);

	if (stringTable->wValueLength) {
		R_LOG_WARN ("check (StringTable wValueLength)");
		free_StringTable (stringTable);
		return NULL;
	}

	if ((stringTable->wType = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (StringTable wType)");
		free_StringTable (stringTable);
		return NULL;
	}
	*curAddr += sizeof (stringTable->wType);
	if (stringTable->wType && stringTable->wType != 1) {
		R_LOG_WARN ("check (StringTable wType)");
		free_StringTable (stringTable);
		return NULL;
	}
	stringTable->szKey = (ut16 *)malloc (UT16_ALIGN (EIGHT_HEX_DIG_UTF_16_LEN)); // EIGHT_HEX_DIG_UTF_16_LEN
	if (!stringTable->szKey) {
		R_LOG_WARN ("malloc (stringTable szKey)");
		free_StringTable (stringTable);
		return NULL;
	}

	if (r_buf_read_at (pe->b, *curAddr, (ut8 *)stringTable->szKey, EIGHT_HEX_DIG_UTF_16_LEN) != EIGHT_HEX_DIG_UTF_16_LEN) {
		R_LOG_WARN ("read (StringTable szKey)");
		free_StringTable (stringTable);
		return NULL;
	}
	*curAddr += EIGHT_HEX_DIG_UTF_16_LEN;
	align32 (*curAddr);
	while (startAddr + stringTable->wLength > *curAddr) {
		String **tmp = (String **)realloc (stringTable->Children, (stringTable->numOfChildren + 1) * sizeof (*stringTable->Children));
		if (!tmp) {
			R_LOG_WARN ("realloc (StringTable Children)");
			free_StringTable (stringTable);
			return NULL;
		}
		stringTable->Children = tmp;
		if (! (stringTable->Children[stringTable->numOfChildren] = Pe_r_bin_pe_parse_string (pe, curAddr))) {
			R_LOG_WARN ("bad parsing String");
			free_StringTable (stringTable);
			return NULL;
		}
		stringTable->numOfChildren++;
		align32 (*curAddr);
	}

	if (!stringTable->numOfChildren) {
		R_LOG_WARN ("check (StringTable numOfChildren)");
		free_StringTable (stringTable);
		return NULL;
	}

	return stringTable;
}

static StringFileInfo *Pe_r_bin_pe_parse_string_file_info(RBinPEObj *pe, PE_DWord *curAddr) {
	StringFileInfo *stringFileInfo = calloc (1, sizeof (*stringFileInfo));
	if (!stringFileInfo) {
		R_LOG_WARN ("calloc (StringFileInfo)");
		return NULL;
	}

	PE_DWord startAddr = *curAddr;

	if ((stringFileInfo->wLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (StringFileInfo wLength)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}
	*curAddr += sizeof (stringFileInfo->wLength);

	if ((stringFileInfo->wValueLength = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (StringFileInfo wValueLength)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}
	*curAddr += sizeof (stringFileInfo->wValueLength);

	if (stringFileInfo->wValueLength) {
		R_LOG_WARN ("check (StringFileInfo wValueLength)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}

	if ((stringFileInfo->wType = r_buf_read_le16_at (pe->b, *curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (StringFileInfo wType)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}
	*curAddr += sizeof (stringFileInfo->wType);

	if (stringFileInfo->wType && stringFileInfo->wType != 1) {
		R_LOG_WARN ("check (StringFileInfo wType)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}

	stringFileInfo->szKey = (ut16 *)malloc (UT16_ALIGN (STRINGFILEINFO_UTF_16_LEN)); // L"StringFileInfo"
	if (!stringFileInfo->szKey) {
		R_LOG_WARN ("malloc (StringFileInfo szKey)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}

	if (r_buf_read_at (pe->b, *curAddr, (ut8 *)stringFileInfo->szKey, STRINGFILEINFO_UTF_16_LEN) != STRINGFILEINFO_UTF_16_LEN) {
		R_LOG_WARN ("read (StringFileInfo szKey)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}
	*curAddr += STRINGFILEINFO_UTF_16_LEN;

	if (memcmp (stringFileInfo->szKey, STRINGFILEINFO_UTF_16, STRINGFILEINFO_UTF_16_LEN) != 0) {
		R_LOG_WARN ("check (StringFileInfo szKey)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}

	align32 (*curAddr);

	while (startAddr + stringFileInfo->wLength > *curAddr) {
		StringTable **tmp = (StringTable **)realloc (stringFileInfo->Children, (stringFileInfo->numOfChildren + 1) * sizeof (*stringFileInfo->Children));
		if (!tmp) {
			R_LOG_WARN ("realloc (StringFileInfo Children)");
			free_StringFileInfo (stringFileInfo);
			return NULL;
		}
		stringFileInfo->Children = tmp;
		if (! (stringFileInfo->Children[stringFileInfo->numOfChildren] = Pe_r_bin_pe_parse_string_table (pe, curAddr))) {
			R_LOG_WARN ("bad parsing StringTable");
			free_StringFileInfo (stringFileInfo);
			return NULL;
		}
		stringFileInfo->numOfChildren++;
		align32 (*curAddr);
	}

	if (!stringFileInfo->numOfChildren) {
		R_LOG_WARN ("check (StringFileInfo numOfChildren)");
		free_StringFileInfo (stringFileInfo);
		return NULL;
	}

	return stringFileInfo;
}

#define EXIT_ON_OVERFLOW(S) \
	if (curAddr > pe->size || curAddr + (S) > pe->size) { \
		goto out_error; \
	}
static PE_VS_VERSIONINFO *Pe_r_bin_pe_parse_version_info(RBinPEObj *pe, PE_DWord version_info_paddr) {
	ut32 sz;
	PE_VS_VERSIONINFO *vs_VersionInfo = calloc (1, sizeof (PE_VS_VERSIONINFO));
	if (!vs_VersionInfo) {
		return NULL;
	}
	PE_DWord startAddr = version_info_paddr;
	PE_DWord curAddr = version_info_paddr;
	// align32 (curAddr); // XXX: do we really need this? Because in msdn
	// wLength is The length, in bytes, of the VS_VERSIONINFO structure.
	// This length does not include any padding that aligns any subsequent
	// version resource data on a 32-bit boundary.
	// Mb we are in subsequent version resource data and not aligned.
	sz = sizeof (ut16);
	EXIT_ON_OVERFLOW (sz);
	if ((vs_VersionInfo->wLength = r_buf_read_le16_at (pe->b, curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (VS_VERSIONINFO wLength)");
		goto out_error;
	}
	curAddr += sz;
	EXIT_ON_OVERFLOW (sz);
	if ((vs_VersionInfo->wValueLength = r_buf_read_le16_at (pe->b, curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (VS_VERSIONINFO wValueLength)");
		goto out_error;
	}
	curAddr += sz;
	EXIT_ON_OVERFLOW (sz);
	if ((vs_VersionInfo->wType = r_buf_read_le16_at (pe->b, curAddr)) == UT16_MAX) {
		R_LOG_WARN ("read (VS_VERSIONINFO wType)");
		goto out_error;
	}
	curAddr += sz;
	if (vs_VersionInfo->wType && vs_VersionInfo->wType != 1) {
		R_LOG_WARN ("check (VS_VERSIONINFO wType)");
		goto out_error;
	}

	vs_VersionInfo->szKey = (ut16 *)malloc (UT16_ALIGN (VS_VERSION_INFO_UTF_16_LEN)); // L"VS_VERSION_INFO"
	if (!vs_VersionInfo->szKey) {
		R_LOG_WARN ("malloc (VS_VERSIONINFO szKey)");
		goto out_error;
	}
	sz = VS_VERSION_INFO_UTF_16_LEN;
	EXIT_ON_OVERFLOW (sz);
	if (r_buf_read_at (pe->b, curAddr, (ut8 *)vs_VersionInfo->szKey, sz) != sz) {
		R_LOG_WARN ("read (VS_VERSIONINFO szKey)");
		goto out_error;
	}
	curAddr += sz;
	if (memcmp (vs_VersionInfo->szKey, VS_VERSION_INFO_UTF_16, sz)) {
		goto out_error;
	}
	align32 (curAddr);
	if (vs_VersionInfo->wValueLength) {
		if (vs_VersionInfo->wValueLength != sizeof (*vs_VersionInfo->Value)) {
			R_LOG_WARN ("check (VS_VERSIONINFO wValueLength != sizeof PE_VS_FIXEDFILEINFO)");
			goto out_error;
		}

		vs_VersionInfo->Value = (PE_VS_FIXEDFILEINFO *)malloc (sizeof (*vs_VersionInfo->Value));
		if (!vs_VersionInfo->Value) {
			R_LOG_WARN ("malloc (VS_VERSIONINFO Value)");
			goto out_error;
		}
		sz = sizeof (PE_VS_FIXEDFILEINFO);
		EXIT_ON_OVERFLOW (sz);
		if (r_buf_read_at (pe->b, curAddr, (ut8 *)vs_VersionInfo->Value, sz) != sz) {
			R_LOG_WARN ("read (VS_VERSIONINFO Value)");
			goto out_error;
		}

		if (vs_VersionInfo->Value->dwSignature != 0xFEEF04BD) {
			R_LOG_WARN ("check (PE_VS_FIXEDFILEINFO signature) 0x%08x", vs_VersionInfo->Value->dwSignature);
			goto out_error;
		}
		curAddr += sz;
		align32 (curAddr);
	}

	if (startAddr + vs_VersionInfo->wLength > curAddr) {
		char t = '\0';
		if (curAddr + 3 * sizeof (ut16) > pe->size || curAddr + 3 + sizeof (ut64) + 1 > pe->size) {
			goto out_error;
		}
		if (r_buf_read_at (pe->b, curAddr + 3 * sizeof (ut16), (ut8 *)&t, 1) != 1) {
			R_LOG_WARN ("read (VS_VERSIONINFO Children V or S)");
			goto out_error;
		}
		if (! (t == 'S' || t == 'V')) {
			R_LOG_WARN ("bad type (VS_VERSIONINFO Children)");
			goto out_error;
		}
		if (t == 'S') {
			if (! (vs_VersionInfo->stringFileInfo = Pe_r_bin_pe_parse_string_file_info (pe, &curAddr))) {
				R_LOG_WARN ("bad parsing (VS_VERSIONINFO StringFileInfo)");
				goto out_error;
			}
		}
		if (t == 'V') {
			if (! (vs_VersionInfo->varFileInfo = Pe_r_bin_pe_parse_var_file_info (pe, &curAddr))) {
				R_LOG_WARN ("bad parsing (VS_VERSIONINFO VarFileInfo)");
				goto out_error;
			}
		}

		align32 (curAddr);

		if (startAddr + vs_VersionInfo->wLength > curAddr) {
			if (t == 'V') {
				if (! (vs_VersionInfo->stringFileInfo = Pe_r_bin_pe_parse_string_file_info (pe, &curAddr))) {
					R_LOG_WARN ("bad parsing (VS_VERSIONINFO StringFileInfo)");
					goto out_error;
				}
			} else if (t == 'S') {
				if (! (vs_VersionInfo->varFileInfo = Pe_r_bin_pe_parse_var_file_info (pe, &curAddr))) {
					R_LOG_WARN ("bad parsing (VS_VERSIONINFO VarFileInfo)");
					goto out_error;
				}
			}
			if (startAddr + vs_VersionInfo->wLength > curAddr) {
				R_LOG_WARN ("bad parsing (VS_VERSIONINFO wLength left)");
				goto out_error;
			}
		}
	}
	return vs_VersionInfo;
out_error:
	free_VS_VERSIONINFO (vs_VersionInfo);
	return NULL;
}

static Sdb *Pe_r_bin_store_var(Var *var) {
	unsigned int i = 0;
	char key[20];
	Sdb *sdb = NULL;
	if (var) {
		sdb = sdb_new0 ();
		if (sdb) {
			for (; i < var->numOfValues; i++) {
				snprintf (key, 20, "%d", i);
				sdb_num_set (sdb, key, var->Value[i], 0);
			}
		}
	}
	return sdb;
}

static Sdb *Pe_r_bin_store_var_file_info(VarFileInfo *varFileInfo) {
	char key[20];
	unsigned int i = 0;
	if (!varFileInfo) {
		return NULL;
	}
	Sdb *sdb = sdb_new0 ();
	if (!sdb) {
		return NULL;
	}
	for (; i < varFileInfo->numOfChildren; i++) {
		snprintf (key, 20, "var%d", i);
		sdb_ns_set (sdb, key, Pe_r_bin_store_var (varFileInfo->Children[i]));
	}
	return sdb;
}

static Sdb *Pe_r_bin_store_string(String *string) {
	Sdb *sdb = NULL;
	char *encodedVal = NULL, *encodedKey = NULL;
	if (!string) {
		return NULL;
	}
	sdb = sdb_new0 ();
	if (!sdb) {
		return NULL;
	}
	encodedKey = sdb_encode ((unsigned char *)string->szKey, string->wKeyLen);
	if (!encodedKey) {
		sdb_free (sdb);
		return NULL;
	}
	encodedVal = sdb_encode ((unsigned char *)string->Value, string->wValueLength);
	if (!encodedVal) {
		free (encodedKey);
		sdb_free (sdb);
		return NULL;
	}
	sdb_set (sdb, "key", encodedKey, 0);
	sdb_set (sdb, "value", encodedVal, 0);
	free (encodedKey);
	free (encodedVal);
	return sdb;
}

static Sdb *Pe_r_bin_store_string_table(StringTable *stringTable) {
	char key[20];
	char *encodedKey = NULL;
	int i = 0;
	Sdb *sdb = NULL;
	if (!stringTable) {
		return NULL;
	}
	sdb = sdb_new0 ();
	if (!sdb) {
		return NULL;
	}
	encodedKey = sdb_encode ((unsigned char *)stringTable->szKey, EIGHT_HEX_DIG_UTF_16_LEN);
	if (!encodedKey) {
		sdb_free (sdb);
		return NULL;
	}
	sdb_set (sdb, "key", encodedKey, 0);
	free (encodedKey);
	for (; i < stringTable->numOfChildren; i++) {
		snprintf (key, 20, "string%d", i);
		sdb_ns_set (sdb, key, Pe_r_bin_store_string (stringTable->Children[i]));
	}
	return sdb;
}

static Sdb *Pe_r_bin_store_string_file_info(StringFileInfo *stringFileInfo) {
	char key[30];
	int i = 0;
	Sdb *sdb = NULL;
	if (!stringFileInfo) {
		return NULL;
	}
	sdb = sdb_new0 ();
	if (!sdb) {
		return NULL;
	}
	for (; i < stringFileInfo->numOfChildren; i++) {
		snprintf (key, 30, "stringtable%d", i);
		sdb_ns_set (sdb, key, Pe_r_bin_store_string_table (stringFileInfo->Children[i]));
	}
	return sdb;
}

static Sdb *Pe_r_bin_store_fixed_file_info(PE_VS_FIXEDFILEINFO *vs_fixedFileInfo) {
	Sdb *sdb = NULL;
	if (!vs_fixedFileInfo) {
		return NULL;
	}
	sdb = sdb_new0 ();
	if (!sdb) {
		return NULL;
	}
	sdb_num_set (sdb, "Signature", vs_fixedFileInfo->dwSignature, 0);
	sdb_num_set (sdb, "StrucVersion", vs_fixedFileInfo->dwStrucVersion, 0);
	sdb_num_set (sdb, "FileVersionMS", vs_fixedFileInfo->dwFileVersionMS, 0);
	sdb_num_set (sdb, "FileVersionLS", vs_fixedFileInfo->dwFileVersionLS, 0);
	sdb_num_set (sdb, "ProductVersionMS", vs_fixedFileInfo->dwProductVersionMS, 0);
	sdb_num_set (sdb, "ProductVersionLS", vs_fixedFileInfo->dwProductVersionLS, 0);
	sdb_num_set (sdb, "FileFlagsMask", vs_fixedFileInfo->dwFileFlagsMask, 0);
	sdb_num_set (sdb, "FileFlags", vs_fixedFileInfo->dwFileFlags, 0);
	sdb_num_set (sdb, "FileOS", vs_fixedFileInfo->dwFileOS, 0);
	sdb_num_set (sdb, "FileType", vs_fixedFileInfo->dwFileType, 0);
	sdb_num_set (sdb, "FileSubtype", vs_fixedFileInfo->dwFileSubtype, 0);
	sdb_num_set (sdb, "FileDateMS", vs_fixedFileInfo->dwFileDateMS, 0);
	sdb_num_set (sdb, "FileDateLS", vs_fixedFileInfo->dwFileDateLS, 0);
	return sdb;
}

static Sdb *Pe_r_bin_store_resource_version_info(PE_VS_VERSIONINFO *vs_VersionInfo) {
	if (!vs_VersionInfo) {
		return NULL;
	}
	Sdb *sdb = sdb_new0 ();
	if (!sdb) {
		return NULL;
	}
	if (vs_VersionInfo->Value) {
		sdb_ns_set (sdb, "fixed_file_info", Pe_r_bin_store_fixed_file_info (vs_VersionInfo->Value));
	}
	if (vs_VersionInfo->varFileInfo) {
		sdb_ns_set (sdb, "var_file_info", Pe_r_bin_store_var_file_info (vs_VersionInfo->varFileInfo));
	}
	if (vs_VersionInfo->stringFileInfo) {
		sdb_ns_set (sdb, "string_file_info", Pe_r_bin_store_string_file_info (vs_VersionInfo->stringFileInfo));
	}
	return sdb;
}

static char *_known_product_ids(int id) {
	switch (id) {
	case 0: return "Unknown";
	case 1: return "Import0";
	case 2: return "Linker510";
	case 3: return "Cvtomf510";
	case 4: return "Linker600";
	case 5: return "Cvtomf600";
	case 6: return "Cvtres500";
	case 7: return "Utc11_Basic";
	case 8: return "Utc11_C";
	case 9: return "Utc12_Basic";
	case 10: return "Utc12_C";
	case 11: return "Utc12_CPP";
	case 12: return "AliasObj60";
	case 13: return "VisualBasic60";
	case 14: return "Masm613";
	case 15: return "Masm710";
	case 16: return "Linker511";
	case 17: return "Cvtomf511";
	case 18: return "Masm614";
	case 19: return "Linker512";
	case 20: return "Cvtomf512";
	case 21: return "Utc12_C_Std";
	case 22: return "Utc12_CPP_Std";
	case 23: return "Utc12_C_Book";
	case 24: return "Utc12_CPP_Book";
	case 25: return "Implib700";
	case 26: return "Cvtomf700";
	case 27: return "Utc13_Basic";
	case 28: return "Utc13_C";
	case 29: return "Utc13_CPP";
	case 30: return "Linker610";
	case 31: return "Cvtomf610";
	case 32: return "Linker601";
	case 33: return "Cvtomf601";
	case 34: return "Utc12_1_Basic";
	case 35: return "Utc12_1_C";
	case 36: return "Utc12_1_CPP";
	case 37: return "Linker620";
	case 38: return "Cvtomf620";
	case 39: return "AliasObj70";
	case 40: return "Linker621";
	case 41: return "Cvtomf621";
	case 42: return "Masm615";
	case 43: return "Utc13_LTCG_C";
	case 44: return "Utc13_LTCG_CPP";
	case 45: return "Masm620";
	case 46: return "ILAsm100";
	case 47: return "Utc12_2_Basic";
	case 48: return "Utc12_2_C";
	case 49: return "Utc12_2_CPP";
	case 50: return "Utc12_2_C_Std";
	case 51: return "Utc12_2_CPP_Std";
	case 52: return "Utc12_2_C_Book";
	case 53: return "Utc12_2_CPP_Book";
	case 54: return "Implib622";
	case 55: return "Cvtomf622";
	case 56: return "Cvtres501";
	case 57: return "Utc13_C_Std";
	case 58: return "Utc13_CPP_Std";
	case 59: return "Cvtpgd1300";
	case 60: return "Linker622";
	case 61: return "Linker700";
	case 62: return "Export622";
	case 63: return "Export700";
	case 64: return "Masm700";
	case 65: return "Utc13_POGO_I_C";
	case 66: return "Utc13_POGO_I_CPP";
	case 67: return "Utc13_POGO_O_C";
	case 68: return "Utc13_POGO_O_CPP";
	case 69: return "Cvtres700";
	case 70: return "Cvtres710p";
	case 71: return "Linker710p";
	case 72: return "Cvtomf710p";
	case 73: return "Export710p";
	case 74: return "Implib710p";
	case 75: return "Masm710p";
	case 76: return "Utc1310p_C";
	case 77: return "Utc1310p_CPP";
	case 78: return "Utc1310p_C_Std";
	case 79: return "Utc1310p_CPP_Std";
	case 80: return "Utc1310p_LTCG_C";
	case 81: return "Utc1310p_LTCG_CPP";
	case 82: return "Utc1310p_POGO_I_C";
	case 83: return "Utc1310p_POGO_I_CPP";
	case 84: return "Utc1310p_POGO_O_C";
	case 85: return "Utc1310p_POGO_O_CPP";
	case 86: return "Linker624";
	case 87: return "Cvtomf624";
	case 88: return "Export624";
	case 89: return "Implib624";
	case 90: return "Linker710";
	case 91: return "Cvtomf710";
	case 92: return "Export710";
	case 93: return "Implib710";
	case 94: return "Cvtres710";
	case 95: return "Utc1310_C";
	case 96: return "Utc1310_CPP";
	case 97: return "Utc1310_C_Std";
	case 98: return "Utc1310_CPP_Std";
	case 99: return "Utc1310_LTCG_C";
	case 100: return "Utc1310_LTCG_CPP";
	case 101: return "Utc1310_POGO_I_C";
	case 102: return "Utc1310_POGO_I_CPP";
	case 103: return "Utc1310_POGO_O_C";
	case 104: return "Utc1310_POGO_O_CPP";
	case 105: return "AliasObj710";
	case 106: return "AliasObj710p";
	case 107: return "Cvtpgd1310";
	case 108: return "Cvtpgd1310p";
	case 109: return "Utc1400_C";
	case 110: return "Utc1400_CPP";
	case 111: return "Utc1400_C_Std";
	case 112: return "Utc1400_CPP_Std";
	case 113: return "Utc1400_LTCG_C";
	case 114: return "Utc1400_LTCG_CPP";
	case 115: return "Utc1400_POGO_I_C";
	case 116: return "Utc1400_POGO_I_CPP";
	case 117: return "Utc1400_POGO_O_C";
	case 118: return "Utc1400_POGO_O_CPP";
	case 119: return "Cvtpgd1400";
	case 120: return "Linker800";
	case 121: return "Cvtomf800";
	case 122: return "Export800";
	case 123: return "Implib800";
	case 124: return "Cvtres800";
	case 125: return "Masm800";
	case 126: return "AliasObj800";
	case 127: return "PhoenixPrerelease";
	case 128: return "Utc1400_CVTCIL_C";
	case 129: return "Utc1400_CVTCIL_CPP";
	case 130: return "Utc1400_LTCG_MSIL";
	case 131: return "Utc1500_C";
	case 132: return "Utc1500_CPP";
	case 133: return "Utc1500_C_Std";
	case 134: return "Utc1500_CPP_Std";
	case 135: return "Utc1500_CVTCIL_C";
	case 136: return "Utc1500_CVTCIL_CPP";
	case 137: return "Utc1500_LTCG_C";
	case 138: return "Utc1500_LTCG_CPP";
	case 139: return "Utc1500_LTCG_MSIL";
	case 140: return "Utc1500_POGO_I_C";
	case 141: return "Utc1500_POGO_I_CPP";
	case 142: return "Utc1500_POGO_O_C";
	case 143: return "Utc1500_POGO_O_CPP";

	case 144: return "Cvtpgd1500";
	case 145: return "Linker900";
	case 146: return "Export900";
	case 147: return "Implib900";
	case 148: return "Cvtres900";
	case 149: return "Masm900";
	case 150: return "AliasObj900";
	case 151: return "Resource900";

	case 152: return "AliasObj1000";
	case 154: return "Cvtres1000";
	case 155: return "Export1000";
	case 156: return "Implib1000";
	case 157: return "Linker1000";
	case 158: return "Masm1000";

	case 170: return "Utc1600_C";
	case 171: return "Utc1600_CPP";
	case 172: return "Utc1600_CVTCIL_C";
	case 173: return "Utc1600_CVTCIL_CPP";
	case 174: return "Utc1600_LTCG_C ";
	case 175: return "Utc1600_LTCG_CPP";
	case 176: return "Utc1600_LTCG_MSIL";
	case 177: return "Utc1600_POGO_I_C";
	case 178: return "Utc1600_POGO_I_CPP";
	case 179: return "Utc1600_POGO_O_C";
	case 180: return "Utc1600_POGO_O_CPP";

	case 183: return "Linker1010";
	case 184: return "Export1010";
	case 185: return "Implib1010";
	case 186: return "Cvtres1010";
	case 187: return "Masm1010";
	case 188: return "AliasObj1010";

	case 199: return "AliasObj1100";
	case 201: return "Cvtres1100";
	case 202: return "Export1100";
	case 203: return "Implib1100";
	case 204: return "Linker1100";
	case 205: return "Masm1100";

	case 206: return "Utc1700_C";
	case 207: return "Utc1700_CPP";
	case 208: return "Utc1700_CVTCIL_C";
	case 209: return "Utc1700_CVTCIL_CPP";
	case 210: return "Utc1700_LTCG_C ";
	case 211: return "Utc1700_LTCG_CPP";
	case 212: return "Utc1700_LTCG_MSIL";
	case 213: return "Utc1700_POGO_I_C";
	case 214: return "Utc1700_POGO_I_CPP";
	case 215: return "Utc1700_POGO_O_C";
	case 216: return "Utc1700_POGO_O_CPP";

	case 219: return "Cvtres1200";
	case 220: return "Export1200";
	case 221: return "Implib1200";
	case 222: return "Linker1200";
	case 223:
		return "Masm1200";
		// Speculation
	case 224: return "AliasObj1200";

	case 237: return "Cvtres1210";
	case 238: return "Export1210";
	case 239: return "Implib1210";
	case 240: return "Linker1210";
	case 241:
		return "Masm1210";
		// Speculation
	case 242: return "Utc1810_C";
	case 243: return "Utc1810_CPP";
	case 244: return "Utc1810_CVTCIL_C";
	case 245: return "Utc1810_CVTCIL_CPP";
	case 246: return "Utc1810_LTCG_C ";
	case 247: return "Utc1810_LTCG_CPP";
	case 248: return "Utc1810_LTCG_MSIL";
	case 249: return "Utc1810_POGO_I_C";
	case 250: return "Utc1810_POGO_I_CPP";
	case 251: return "Utc1810_POGO_O_C";
	case 252: return "Utc1810_POGO_O_CPP";

	case 255: return "Cvtres1400";
	case 256: return "Export1400";
	case 257: return "Implib1400";
	case 258: return "Linker1400";
	case 259: return "Masm1400";

	case 260: return "Utc1900_C";
	case 261:
		return "Utc1900_CPP";
		// Speculation
	case 262: return "Utc1900_CVTCIL_C";
	case 263: return "Utc1900_CVTCIL_CPP";
	case 264: return "Utc1900_LTCG_C ";
	case 265: return "Utc1900_LTCG_CPP";
	case 266: return "Utc1900_LTCG_MSIL";
	case 267: return "Utc1900_POGO_I_C";
	case 268: return "Utc1900_POGO_I_CPP";
	case 269: return "Utc1900_POGO_O_C";
	case 270: return "Utc1900_POGO_O_CPP";
	default: return "Unknown";
	}
}

static void bin_pe_init_rich_info(RBinPEObj *pe) {
	if (!pe->rich_entries) {
		pe->rich_entries = r_list_newf (free);
	}
	pe->rich_header_offset = pe->nt_header_offset;
	ut64 off = pe->nt_header_offset - sizeof (ut32);
	ut32 magic = 0x68636952; // Rich
	if (off % sizeof (ut32)) {
		return;
	}
	while ((r_buf_read_le32_at (pe->b, off) != magic) && off) {
		off -= sizeof (ut32);
	}
	if (!off) {
		return;
	}
	ut32 mask = r_buf_read_le32_at (pe->b, off + sizeof (ut32));
	magic = 0x536E6144; // DanS
	int data;
	off -= sizeof (ut32);
	while (((data = r_buf_read_le32_at (pe->b, off)) != magic) && data ^ mask && off > 0x80) {
		Pe_image_rich_entry *entry = R_NEW0 (Pe_image_rich_entry);
		entry->timesUsed = data ^ mask;
		off -= sizeof (ut32);
		data = r_buf_read_le32_at (pe->b, off) ^ mask;
		entry->productId = data >> 16;
		entry->minVersion = data & 0xFFFF;
		entry->productName = _known_product_ids (entry->productId);
		off -= sizeof (ut32);
		r_list_append (pe->rich_entries, entry);
	}
	pe->rich_header_offset = off + sizeof (ut32);
}

static char *_resource_lang_str(int id) {
	switch (id) {
	case 0x00: return "LANG_NEUTRAL";
	case 0x7f: return "LANG_INVARIANT";
	case 0x36: return "LANG_AFRIKAANS";
	case 0x1c: return "LANG_ALBANIAN ";
	case 0x01: return "LANG_ARABIC";
	case 0x2b: return "LANG_ARMENIAN";
	case 0x4d: return "LANG_ASSAMESE";
	case 0x2c: return "LANG_AZERI";
	case 0x2d: return "LANG_BASQUE";
	case 0x23: return "LANG_BELARUSIAN";
	case 0x45: return "LANG_BENGALI";
	case 0x02: return "LANG_BULGARIAN";
	case 0x03: return "LANG_CATALAN";
	case 0x04: return "LANG_CHINESE";
	case 0x1a: return "LANG_CROATIAN";
	case 0x05: return "LANG_CZECH";
	case 0x06: return "LANG_DANISH";
	case 0x65: return "LANG_DIVEHI";
	case 0x13: return "LANG_DUTCH";
	case 0x09: return "LANG_ENGLISH";
	case 0x25: return "LANG_ESTONIAN";
	case 0x38: return "LANG_FAEROESE";
	case 0x29: return "LANG_FARSI";
	case 0x0b: return "LANG_FINNISH";
	case 0x0c: return "LANG_FRENCH";
	case 0x56: return "LANG_GALICIAN";
	case 0x37: return "LANG_GEORGIAN";
	case 0x07: return "LANG_GERMAN";
	case 0x08: return "LANG_GREEK";
	case 0x47: return "LANG_GUJARATI";
	case 0x0d: return "LANG_HEBREW";
	case 0x39: return "LANG_HINDI";
	case 0x0e: return "LANG_HUNGARIAN";
	case 0x0f: return "LANG_ICELANDIC";
	case 0x21: return "LANG_INDONESIAN";
	case 0x10: return "LANG_ITALIAN";
	case 0x11: return "LANG_JAPANESE";
	case 0x4b: return "LANG_KANNADA";
	case 0x60: return "LANG_KASHMIRI";
	case 0x3f: return "LANG_KAZAK";
	case 0x57: return "LANG_KONKANI";
	case 0x12: return "LANG_KOREAN";
	case 0x40: return "LANG_KYRGYZ";
	case 0x26: return "LANG_LATVIAN";
	case 0x27: return "LANG_LITHUANIAN";
	case 0x2f: return "LANG_MACEDONIAN";
	case 0x3e: return "LANG_MALAY";
	case 0x4c: return "LANG_MALAYALAM";
	case 0x58: return "LANG_MANIPURI";
	case 0x4e: return "LANG_MARATHI";
	case 0x50: return "LANG_MONGOLIAN";
	case 0x61: return "LANG_NEPALI";
	case 0x14: return "LANG_NORWEGIAN";
	case 0x48: return "LANG_ORIYA";
	case 0x15: return "LANG_POLISH";
	case 0x16: return "LANG_PORTUGUESE";
	case 0x46: return "LANG_PUNJABI";
	case 0x18: return "LANG_ROMANIAN";
	case 0x19: return "LANG_RUSSIAN";
	case 0x4f: return "LANG_SANSKRIT";
	case 0x59: return "LANG_SINDHI";
	case 0x1b: return "LANG_SLOVAK";
	case 0x24: return "LANG_SLOVENIAN";
	case 0x0a: return "LANG_SPANISH ";
	case 0x41: return "LANG_SWAHILI";
	case 0x1d: return "LANG_SWEDISH";
	case 0x5a: return "LANG_SYRIAC";
	case 0x49: return "LANG_TAMIL";
	case 0x44: return "LANG_TATAR";
	case 0x4a: return "LANG_TELUGU";
	case 0x1e: return "LANG_THAI";
	case 0x1f: return "LANG_TURKISH";
	case 0x22: return "LANG_UKRAINIAN";
	case 0x20: return "LANG_URDU";
	case 0x43: return "LANG_UZBEK";
	case 0x2a: return "LANG_VIETNAMESE";
	case 0x3c: return "LANG_GAELIC";
	case 0x3a: return "LANG_MALTESE";
	case 0x28: return "LANG_MAORI";
	case 0x17: return "LANG_RHAETO_ROMANCE";
	case 0x3b: return "LANG_SAAMI";
	case 0x2e: return "LANG_SORBIAN";
	case 0x30: return "LANG_SUTU";
	case 0x31: return "LANG_TSONGA";
	case 0x32: return "LANG_TSWANA";
	case 0x33: return "LANG_VENDA";
	case 0x34: return "LANG_XHOSA";
	case 0x35: return "LANG_ZULU";
	case 0x8f: return "LANG_ESPERANTO";
	case 0x90: return "LANG_WALON";
	case 0x91: return "LANG_CORNISH";
	case 0x92: return "LANG_WELSH";
	case 0x93: return "LANG_BRETON";
	default: return "UNKNOWN";
	}
}

static char *_resource_type_str(int type) {
	const char *typeName;
	switch (type) {
	case 1:
		typeName = "CURSOR";
		break;
	case 2:
		typeName = "BITMAP";
		break;
	case 3:
		typeName = "ICON";
		break;
	case 4:
		typeName = "MENU";
		break;
	case 5:
		typeName = "DIALOG";
		break;
	case 6:
		typeName = "STRING";
		break;
	case 7:
		typeName = "FONTDIR";
		break;
	case 8:
		typeName = "FONT";
		break;
	case 9:
		typeName = "ACCELERATOR";
		break;
	case 10:
		typeName = "RCDATA";
		break;
	case 11:
		typeName = "MESSAGETABLE";
		break;
	case 12:
		typeName = "GROUP_CURSOR";
		break;
	case 14:
		typeName = "GROUP_ICON";
		break;
	case 16:
		typeName = "VERSION";
		break;
	case 17:
		typeName = "DLGINCLUDE";
		break;
	case 19:
		typeName = "PLUGPLAY";
		break;
	case 20:
		typeName = "VXD";
		break;
	case 21:
		typeName = "ANICURSOR";
		break;
	case 22:
		typeName = "ANIICON";
		break;
	case 23:
		typeName = "HTML";
		break;
	case 24:
		typeName = "MANIFEST";
		break;
	default:
		return r_str_newf ("UNKNOWN (%d)", type);
	}
	return strdup (typeName);
}

static int read_image_resource_directory_entry(RBuffer *b, ut64 addr, Pe_image_resource_directory_entry *entry) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	entry->u1.Name = r_buf_read_le32 (b);
	entry->u2.OffsetToData = r_buf_read_le32 (b);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (Pe_image_resource_directory_entry);
}

static int read_image_resource_data_entry(RBuffer *b, ut64 addr, Pe_image_resource_data_entry *entry) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	ut8 buf[sizeof (Pe_image_resource_data_entry)];
	r_buf_read (b, buf, sizeof (Pe_image_resource_data_entry));
	PE_READ_STRUCT_FIELD (entry, Pe_image_resource_data_entry, OffsetToData, 32);
	PE_READ_STRUCT_FIELD (entry, Pe_image_resource_data_entry, Size, 32);
	PE_READ_STRUCT_FIELD (entry, Pe_image_resource_data_entry, CodePage, 32);
	PE_READ_STRUCT_FIELD (entry, Pe_image_resource_data_entry, Reserved, 32);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (Pe_image_resource_data_entry);
}

static bool is_dos_time(const ut32 certainPosixTimeStamp, const ut32 possiblePosixOrDosTimeStamp) {
	/* We assume they're both POSIX timestamp and thus the higher bits would be equal if they're close to each other */
	if ((certainPosixTimeStamp >> 16) == (possiblePosixOrDosTimeStamp >> 16)) {
		return false;
	}
	return true;
}

static void _parse_resource_directory(RBinPEObj *pe, Pe_image_resource_directory *dir, ut64 offDir, int type, int id, HtUU *dirs, const char *resource_name) {
	char *resourceEntryName = NULL;
	int index = 0;
	ut32 totalRes = dir->NumberOfNamedEntries + dir->NumberOfIdEntries;
	ut64 rsrc_base = pe->resource_directory_offset;
	ut64 off;
	if (totalRes > R_PE_MAX_RESOURCES) {
		return;
	}
	for (index = 0; index < totalRes; index++) {
		Pe_image_resource_directory_entry entry;
		off = rsrc_base + offDir + sizeof (*dir) + index * sizeof (entry);
		if (ht_uu_find (dirs, off, NULL)) {
			break;
		}
		ht_uu_insert (dirs, off, 1);
		if (off > pe->size || off + sizeof (entry) > pe->size) {
			break;
		}
		if (read_image_resource_directory_entry (pe->b, off, &entry) < 0) {
			R_LOG_WARN ("read resource entry");
			break;
		}
		if (entry.u1.Name >> 31) {
			int i;
			ut16 buf;
			ut32 NameOffset = entry.u1.Name & 0x7fffffff;
			if (r_buf_read_at (pe->b, pe->resource_directory_offset + NameOffset, (ut8 *)&buf, sizeof (ut16)) != sizeof (ut16)) {
				break;
			}
			ut16 resourceEntryNameLength = r_read_le16 (&buf);
			resourceEntryName = calloc (resourceEntryNameLength + 1, 1);
			if (resourceEntryName) {
				for (i = 0; i < resourceEntryNameLength; i++) { /* Convert Unicode to ASCII */
					ut8 byte;
					int r = r_buf_read_at (pe->b, pe->resource_directory_offset + NameOffset + 2 + (i * 2), &byte, sizeof (ut8));
					if (r != sizeof (ut8) || !byte) {
						R_FREE (resourceEntryName);
						break;
					}
					resourceEntryName[i] = byte;
				}
			}
		}
		if (entry.u2.OffsetToData >> 31) {
			// detect here malicious file trying to making us infinite loop
			Pe_image_resource_directory identEntry;
			ut32 OffsetToDirectory = entry.u2.OffsetToData & 0x7fffffff;
			off = rsrc_base + OffsetToDirectory;
			int len = read_image_resource_directory (pe->b, off, &identEntry);
			if (len < 1 || len != sizeof (Pe_image_resource_directory)) {
				R_LOG_WARN ("parsing resource directory");
			}
			_parse_resource_directory (pe, &identEntry, OffsetToDirectory, type, entry.u1.Name & 0xffff, dirs, resourceEntryName);
			R_FREE (resourceEntryName);
			continue;
		}
		R_FREE (resourceEntryName);

		Pe_image_resource_data_entry *data = R_NEW0 (Pe_image_resource_data_entry);
		off = rsrc_base + entry.u2.OffsetToData;
		if (off > pe->size || off + sizeof (*data) > pe->size) {
			free (data);
			break;
		}
		if (read_image_resource_data_entry (pe->b, off, data) != sizeof (*data)) {
			R_LOG_WARN ("read (resource data entry)");
			free (data);
			break;
		}
		if (type == PE_RESOURCE_ENTRY_VERSION) {
			char key[64];
			int counter = 0;
			Sdb *sdb = sdb_new0 ();
			if (!sdb) {
				free (data);
				sdb_free (sdb);
				continue;
			}
			PE_DWord data_paddr = PE_(va2pa) (pe, data->OffsetToData);
			if (!data_paddr) {
				R_LOG_WARN ("bad RVA in resource data entry");
				free (data);
				sdb_free (sdb);
				continue;
			}
			PE_DWord cur_paddr = data_paddr;
			if ((cur_paddr & 0x3) != 0) {
				R_LOG_WARN ("not aligned version info address");
				free (data);
				sdb_free (sdb);
				continue;
			}
			while (cur_paddr < (data_paddr + data->Size) && cur_paddr < pe->size) {
				PE_VS_VERSIONINFO *vs_VersionInfo = Pe_r_bin_pe_parse_version_info (pe, cur_paddr);
				if (!vs_VersionInfo) {
					break;
				}
				snprintf (key, 30, "VS_VERSIONINFO%d", counter++);
				sdb_ns_set (sdb, key, Pe_r_bin_store_resource_version_info (vs_VersionInfo));
				if (vs_VersionInfo->wLength < 1) {
					// Invalid version length
					break;
				}
				cur_paddr += vs_VersionInfo->wLength;
				free_VS_VERSIONINFO (vs_VersionInfo);
				align32 (cur_paddr);
			}
			sdb_ns_set (pe->kv, "vs_version_info", sdb);
		}
		r_pe_resource *rs = R_NEW0 (r_pe_resource);
		/* Compare compileTimeStamp to resource timestamp to figure out if DOS date or POSIX date */
		if (is_dos_time ((ut32)sdb_num_get (pe->kv, "image_file_header.TimeDateStamp", 0), dir->TimeDateStamp)) {
			int tz = 0; // TODO: use configurable timezone
			rs->timestr = r_time_secs_tostring (r_time_dos_today (dir->TimeDateStamp, tz));
		} else {
			rs->timestr = r_time_secs_tostring (dir->TimeDateStamp);
		}
		rs->type = _resource_type_str (type);
		rs->language = strdup (_resource_lang_str (entry.u1.Name & 0x3ff));
		rs->data = data;
		if (resource_name) {
			rs->name = strdup (resource_name);
		} else {
			rs->name = r_str_newf ("%d", id);
		}
		r_list_append (pe->resources, rs);
	}
}

static void _store_resource_sdb(RBinPEObj *pe) {
	r_strf_buffer (64);
	RListIter *iter;
	r_pe_resource *rs;
	int index = 0;
	ut64 vaddr = 0;
	char *key;
	Sdb *sdb = sdb_new0 ();
	if (!sdb) {
		return;
	}
	r_list_foreach (pe->resources, iter, rs) {
		key = r_strf ("resource.%d.timestr", index);
		sdb_set (sdb, key, rs->timestr, 0);
		key = r_strf ("resource.%d.vaddr", index);
		vaddr = bin_pe_rva_to_va (pe, rs->data->OffsetToData);
		sdb_num_set (sdb, key, vaddr, 0);
		key = r_strf ("resource.%d.name", index);
		sdb_set (sdb, key, rs->name, 0);
		key = r_strf ("resource.%d.size", index);
		sdb_num_set (sdb, key, rs->data->Size, 0);
		key = r_strf ("resource.%d.type", index);
		sdb_set (sdb, key, rs->type, 0);
		key = r_strf ("resource.%d.language", index);
		sdb_set (sdb, key, rs->language, 0);
		index++;
	}
	sdb_ns_set (pe->kv, "pe_resource", sdb);
}

R_API void PE_(bin_pe_parse_resource)(RBinPEObj *pe) {
	int index = 0;
	ut64 off = 0, rsrc_base = pe->resource_directory_offset;
	Pe_image_resource_directory *rs_directory = pe->resource_directory;
	ut32 curRes = 0;
	int totalRes = 0;
	HtUU *dirs = ht_uu_new0 (); // to avoid infinite loops
	if (!dirs) {
		return;
	}
	if (!rs_directory) {
		ht_uu_free (dirs);
		return;
	}
	curRes = rs_directory->NumberOfNamedEntries;
	totalRes = curRes + rs_directory->NumberOfIdEntries;
	if (totalRes > R_PE_MAX_RESOURCES) {
		if (pe->verbose) {
			R_LOG_ERROR ("parsing resource directory");
		}
		ht_uu_free (dirs);
		return;
	}
	for (index = 0; index < totalRes; index++) {
		Pe_image_resource_directory_entry typeEntry;
		off = rsrc_base + sizeof (*rs_directory) + index * sizeof (typeEntry);
		ht_uu_insert (dirs, off, 1);
		if (off > pe->size || off + sizeof (typeEntry) > pe->size) {
			break;
		}
		if (read_image_resource_directory_entry (pe->b, off, &typeEntry) < 0) {
			R_LOG_WARN ("read resource directory entry");
			break;
		}
		if (typeEntry.u2.OffsetToData >> 31) {
			Pe_image_resource_directory identEntry;
			ut32 OffsetToDirectory = typeEntry.u2.OffsetToData & 0x7fffffff;
			off = rsrc_base + OffsetToDirectory;
			int len = read_image_resource_directory (pe->b, off, &identEntry);
			if (len != sizeof (identEntry)) {
				R_LOG_WARN ("parsing resource directory");
			}
			(void)_parse_resource_directory (pe, &identEntry, OffsetToDirectory, typeEntry.u1.Name & 0xffff, 0, dirs, NULL);
		}
	}
	ht_uu_free (dirs);
	_store_resource_sdb (pe);
}

static void free_security_directory(Pe_image_security_directory *security_directory) {
	if (!security_directory) {
		return;
	}
	size_t numCert = 0;
	for (; numCert < security_directory->length; numCert++) {
		Pe_certificate *cert = security_directory->certificates[numCert];
		if (cert) {
			free (cert->bCertificate);
			free (cert);
		}
	}
	free (security_directory->certificates);
	free (security_directory);
}

static int bin_pe_init_security(RBinPEObj *pe) {
	if (!pe || !pe->nt_headers) {
		return false;
	}
	if (pe->nt_headers->optional_header.NumberOfRvaAndSizes < 5) {
		return false;
	}
	PE_(image_data_directory) *data_dir_security = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_SECURITY];
	PE_DWord paddr = data_dir_security->VirtualAddress;
	ut32 size = data_dir_security->Size;
	if (size == 0) {
		return true;
	}
	if (size < 8 || paddr > pe->size || paddr + size > pe->size) {
		R_LOG_WARN ("Invalid certificate table");
		return false;
	}

	free_security_directory (pe->security_directory);
	Pe_image_security_directory *security_directory = R_NEW0 (Pe_image_security_directory);
	pe->security_directory = security_directory;

	PE_DWord offset = paddr;
	while (offset < paddr + size) {
		Pe_certificate **tmp = (Pe_certificate **)realloc (security_directory->certificates, (security_directory->length + 1) * sizeof (Pe_certificate *));
		if (!tmp) {
			return false;
		}
		security_directory->certificates = tmp;
		Pe_certificate *cert = R_NEW0 (Pe_certificate);
		cert->dwLength = r_buf_read_le32_at (pe->b, offset);
		cert->dwLength += (8 - (cert->dwLength & 7)) & 7; // align32
		if (offset + cert->dwLength > paddr + size) {
			R_LOG_WARN ("Invalid certificate entry");
			R_FREE (cert);
			return false;
		}
		cert->wRevision = r_buf_read_le16_at (pe->b, offset + 4);
		cert->wCertificateType = r_buf_read_le16_at (pe->b, offset + 6);
		if (cert->dwLength < 6) {
			R_LOG_ERROR ("Cert.dwLength must be > 6");
			R_FREE (cert);
			return false;
		}
		if (! (cert->bCertificate = malloc (cert->dwLength - 6))) {
			R_FREE (cert);
			return false;
		}
		r_buf_read_at (pe->b, offset + 8, cert->bCertificate, cert->dwLength - 6);

		if (!pe->cms && cert->wCertificateType == PE_WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
			pe->cms = r_pkcs7_cms_parse (cert->bCertificate, cert->dwLength - 6);
			if (pe->cms) {
				pe->spcinfo = r_pkcs7_spcinfo_parse (pe->cms);
			}
		}

		security_directory->certificates[security_directory->length] = cert;
		security_directory->length++;
		offset += cert->dwLength;
	}

	if (pe->cms && pe->spcinfo && pe->mb) {
		const char *actual_authentihash = PE_(bin_pe_compute_authentihash) (pe);
		const char *claimed_authentihash = PE_(bin_pe_get_claimed_authentihash) (pe);
		if (actual_authentihash && claimed_authentihash) {
			pe->is_authhash_valid = !strcmp (actual_authentihash, claimed_authentihash);
		} else {
			pe->is_authhash_valid = false;
		}
		if (actual_authentihash) {
			free ((void *)actual_authentihash);
		}
		free ((void *)claimed_authentihash);
		pe->authhash_computed = true;
	}
	pe->is_signed = pe->cms;
	return true;
}

static int bin_pe_init(RBinPEObj *pe) {
	pe->dos_header = NULL;
	pe->nt_headers = NULL;
	pe->section_header = NULL;
	pe->export_directory = NULL;
	pe->import_directory = NULL;
	pe->resource_directory = NULL;
	pe->security_directory = NULL;
	pe->delay_import_directory = NULL;
	pe->optional_header = NULL;
	pe->data_directory = NULL;
	pe->delay_import_directory_size = 0;
	pe->big_endian = 0;
	pe->cms = NULL;
	pe->spcinfo = NULL;
	pe->dotnet_symbols = NULL;
	if (!bin_pe_init_hdr (pe)) {
		R_LOG_WARN ("File is not PE");
		return false;
	}
	if (!bin_pe_init_sections (pe)) {
		R_LOG_WARN ("Cannot initialize sections");
		return false;
	}
	pe->sections = PE_(r_bin_pe_get_sections) (pe);
	bin_pe_init_imports (pe);
	bin_pe_init_exports (pe);
	bin_pe_init_resource (pe);
	bin_pe_init_security (pe);

	pe->big_endian = PE_(r_bin_pe_is_big_endian) (pe);

	bin_pe_init_rich_info (pe);
	bin_pe_init_tls (pe);
	bin_pe_init_clr_hdr (pe);
	bin_pe_init_metadata_hdr (pe);
	bin_pe_init_overlay (pe);
	PE_(bin_pe_parse_resource) (pe);
	pe->relocs = NULL;
	return true;
}

const char *PE_(r_bin_pe_get_arch)(RBinPEObj *pe) {
	const char *arch = "x86";
	if (!pe || !pe->nt_headers) {
		return arch;
	}
	switch (pe->nt_headers->file_header.Machine) {
	case PE_IMAGE_FILE_MACHINE_ALPHA:
	case PE_IMAGE_FILE_MACHINE_ALPHA64:
		arch = "alpha";
		break;
	case PE_IMAGE_FILE_MACHINE_ARM:
	case PE_IMAGE_FILE_MACHINE_THUMB:
	case PE_IMAGE_FILE_MACHINE_THUMB2:
		arch = "arm";
		break;
	case PE_IMAGE_FILE_MACHINE_M68K:
		arch = "m68k";
		break;
	case PE_IMAGE_FILE_MACHINE_MIPS16:
	case PE_IMAGE_FILE_MACHINE_MIPSFPU:
	case PE_IMAGE_FILE_MACHINE_MIPSFPU16:
	case PE_IMAGE_FILE_MACHINE_WCEMIPSV2:
		arch = "mips";
		break;
	case PE_IMAGE_FILE_MACHINE_POWERPC:
	case PE_IMAGE_FILE_MACHINE_POWERPCFP:
	case PE_IMAGE_FILE_MACHINE_POWERPCBE:
		arch = "ppc";
		break;
	case PE_IMAGE_FILE_MACHINE_EBC:
		arch = "ebc";
		break;
	case PE_IMAGE_FILE_MACHINE_ARM64:
	case PE_IMAGE_FILE_MACHINE_ARM64X:
	case PE_IMAGE_FILE_MACHINE_ARM64EC:
		arch = "arm";
		break;
	case PE_IMAGE_FILE_MACHINE_RISCV32:
	case PE_IMAGE_FILE_MACHINE_RISCV64:
	case PE_IMAGE_FILE_MACHINE_RISCV128:
		arch = "riscv";
		break;
	case PE_IMAGE_FILE_MACHINE_LOONGARCH32:
	case PE_IMAGE_FILE_MACHINE_LOONGARCH64:
		arch = "loongarch";
		break;
	}
	return arch;
}

struct r_bin_pe_addr_t *PE_(r_bin_pe_get_entrypoint)(RBinPEObj *pe) {
	int i;
	ut64 base_addr = PE_(r_bin_pe_get_image_base) (pe);
	if (!pe || !pe->optional_header) {
		return NULL;
	}
	struct r_bin_pe_addr_t *entry = R_NEW (struct r_bin_pe_addr_t);
	PE_DWord pe_entry = pe->optional_header->AddressOfEntryPoint;
	entry->vaddr = bin_pe_rva_to_va (pe, pe_entry);
	entry->paddr = PE_(va2pa) (pe, pe_entry);
	// haddr is the address of AddressOfEntryPoint in header.
	entry->haddr = pe->dos_header->e_lfanew + 4 + sizeof (PE_(image_file_header)) + 16;

	if (entry->paddr >= pe->size) {
		struct r_bin_pe_section_t *sections = pe->sections;
		ut64 paddr = 0;
		for (i = 0; i < pe->num_sections; i++) {
			if (sections[i].perm & PE_IMAGE_SCN_MEM_EXECUTE) {
				entry->paddr = sections[i].paddr;
				entry->vaddr = sections[i].vaddr + base_addr;
				paddr = 1;
				break;
			}
		}
		if (!paddr) {
			ut64 min_off = -1;
			for (i = 0; i < pe->num_sections; i++) {
				// get the lowest section's paddr
				if (sections[i].paddr < min_off) {
					entry->paddr = sections[i].paddr;
					entry->vaddr = sections[i].vaddr + base_addr;
					min_off = sections[i].paddr;
				}
			}
			if (min_off == -1) {
				// no section just a hack to try to fix entrypoint
				// maybe doesn't work always
				ut32 sa = R_MAX (pe->optional_header->SectionAlignment, 0x1000);
				entry->paddr = pe_entry &((sa << 1) - 1);
				entry->vaddr = entry->paddr + base_addr;
			}
		}
	}
	if (!entry->paddr) {
		struct r_bin_pe_section_t *sections = pe->sections;
		for (i = 0; i < pe->num_sections; i++) {
			// If there is a section with x without w perm is a good candidate to be the entrypoint
			if (sections[i].perm & PE_IMAGE_SCN_MEM_EXECUTE && ! (sections[i].perm & PE_IMAGE_SCN_MEM_WRITE)) {
				entry->paddr = sections[i].paddr;
				entry->vaddr = sections[i].vaddr + base_addr;
				break;
			}
		}
	}
	if (is_arm (pe) && entry->vaddr & 1) {
		entry->vaddr--;
		if (entry->paddr & 1) {
			entry->paddr--;
		}
	}
	return entry;
}

struct r_bin_pe_export_t *PE_(r_bin_pe_get_exports)(RBinPEObj *pe) {
	R_RETURN_VAL_IF_FAIL (pe, NULL);
	struct r_bin_pe_export_t *exp, *exports = NULL;
	PE_Word function_ordinal = 0;
	PE_VWord functions_paddr, names_paddr, ordinals_paddr, function_rva, name_vaddr, name_paddr;
	char function_name[PE_NAME_LENGTH + 1], forwarder_name[PE_NAME_LENGTH + 1];
	char dll_name[PE_NAME_LENGTH + 1];
	PE_(image_data_directory) * data_dir_export;
	PE_VWord export_dir_rva;
	int n, i, export_dir_size;
	st64 exports_sz = 0;

	if (!pe->data_directory) {
		return NULL;
	}
	data_dir_export = &pe->data_directory[PE_IMAGE_DIRECTORY_ENTRY_EXPORT];
	export_dir_rva = data_dir_export->VirtualAddress;
	export_dir_size = data_dir_export->Size;
	PE_VWord *func_rvas = NULL;
	PE_Word *ordinals = NULL;
	if (pe->export_directory) {
		if (pe->export_directory->NumberOfFunctions + 1 <
			pe->export_directory->NumberOfFunctions) {
			// avoid integer overflow
			return NULL;
		}
		exports_sz = (pe->export_directory->NumberOfFunctions + 1) * sizeof (struct r_bin_pe_export_t);
		// we cant exit with export_sz > pe->size, us r_bin_pe_export_t is 256+256+8+8+8+4 bytes is easy get over file size
		// to avoid fuzzing we can abort on export_directory->NumberOfFunctions>0xffff
		if (exports_sz < 0 || pe->export_directory->NumberOfFunctions + 1 > 0xffff) {
			return NULL;
		}
		if (pe->export_directory->NumberOfNames > pe->export_directory->NumberOfFunctions) {
			return NULL;
		}
		if (! (exports = malloc (exports_sz))) {
			return NULL;
		}
		if (r_buf_read_at (pe->b, PE_(va2pa) (pe, pe->export_directory->Name), (ut8 *)dll_name, PE_NAME_LENGTH) < 1) {
			// we dont stop if dll name cant be read, we set dllname to null and continue
			dll_name[0] = '\0';
		}
		dll_name[PE_NAME_LENGTH] = '\0';
		functions_paddr = PE_(va2pa) (pe, pe->export_directory->AddressOfFunctions);
		names_paddr = PE_(va2pa) (pe, pe->export_directory->AddressOfNames);
		ordinals_paddr = PE_(va2pa) (pe, pe->export_directory->AddressOfOrdinals);

		const size_t names_sz = pe->export_directory->NumberOfNames * sizeof (PE_Word);
		const size_t funcs_sz = pe->export_directory->NumberOfFunctions * sizeof (PE_VWord);
		ordinals = malloc (names_sz);
		func_rvas = malloc (funcs_sz);
		if (!ordinals || !func_rvas) {
			goto beach;
		}
		int r = r_buf_read_at (pe->b, ordinals_paddr, (ut8 *)ordinals, names_sz);
		if (r != names_sz) {
			goto beach;
		}
		r = r_buf_read_at (pe->b, functions_paddr, (ut8 *)func_rvas, funcs_sz);
		if (r != funcs_sz) {
			goto beach;
		}
		for (i = 0; i < pe->export_directory->NumberOfFunctions; i++) {
			// get vaddr from AddressOfFunctions array
			function_rva = r_read_at_ble32 ((ut8 *)func_rvas, i * sizeof (PE_VWord), pe->endian);
			// have exports by name?
			if (pe->export_directory->NumberOfNames > 0) {
				// search for value of i into AddressOfOrdinals
				name_vaddr = 0;
				for (n = 0; n < pe->export_directory->NumberOfNames; n++) {
					PE_Word fo = r_read_at_ble16 ((ut8 *)ordinals, n * sizeof (PE_Word), pe->endian);
					// if exist this index into AddressOfOrdinals
					if (i == fo) {
						function_ordinal = fo;
						// get the VA of export name  from AddressOfNames
						name_vaddr = r_buf_read_le32_at (pe->b, names_paddr + n * sizeof (PE_VWord));
						break;
					}
				}
				// have an address into name_vaddr?
				if (name_vaddr) {
					// get the name of the Export
					name_paddr = PE_(va2pa) (pe, name_vaddr);
					if (r_buf_read_at (pe->b, name_paddr, (ut8 *)function_name, PE_NAME_LENGTH) < 1) {
						R_LOG_WARN ("read (function name)");
						exports[i].last = 1;
						return exports;
					}
				} else { // No name export, get the ordinal
					function_ordinal = i;
					snprintf (function_name, PE_NAME_LENGTH, "Ordinal_%i", i + pe->export_directory->Base);
				}
			} else { // if export by name dont exist, get the ordinal taking in mind the Base value.
				snprintf (function_name, PE_NAME_LENGTH, "Ordinal_%i", i + pe->export_directory->Base);
			}
			// check if VA are into export directory, this mean a forwarder export
			if (function_rva >= export_dir_rva && function_rva < (export_dir_rva + export_dir_size)) {
				// if forwarder, the VA point to Forwarded name
				if (r_buf_read_at (pe->b, PE_(va2pa) (pe, function_rva), (ut8 *)forwarder_name, PE_NAME_LENGTH) < 1) {
					exports[i].last = 1;
					return exports;
				}
			} else { // no forwarder export
				snprintf (forwarder_name, PE_NAME_LENGTH, "NONE");
			}
			dll_name[PE_NAME_LENGTH] = '\0';
			function_name[PE_NAME_LENGTH] = '\0';
			exports[i].vaddr = bin_pe_rva_to_va (pe, function_rva);
			exports[i].paddr = PE_(va2pa) (pe, function_rva);
			exports[i].ordinal = function_ordinal + pe->export_directory->Base;
			memcpy (exports[i].forwarder, forwarder_name, PE_NAME_LENGTH);
			exports[i].forwarder[PE_NAME_LENGTH] = '\0';
			memcpy (exports[i].name, function_name, PE_NAME_LENGTH);
			exports[i].name[PE_NAME_LENGTH] = '\0';
			memcpy (exports[i].libname, dll_name, PE_NAME_LENGTH);
			exports[i].libname[PE_NAME_LENGTH] = '\0';
			exports[i].last = 0;
		}
		exports[i].last = 1;
		free (ordinals);
		free (func_rvas);
	}
	exp = parse_symbol_table (pe, exports, exports_sz - sizeof (struct r_bin_pe_export_t));
	if (exp) {
		exports = exp;
	}
	return exports;
beach:
	free (exports);
	free (ordinals);
	free (func_rvas);
	return NULL;
}

static void free_rsdr_hdr(SCV_RSDS_HEADER *rsds_hdr) {
	R_FREE (rsds_hdr->file_name);
}

static void init_rsdr_hdr(SCV_RSDS_HEADER *rsds_hdr) {
	memset (rsds_hdr, 0, sizeof (SCV_RSDS_HEADER));
	rsds_hdr->free = (void (*) (struct SCV_RSDS_HEADER *))free_rsdr_hdr;
}

static void free_cv_nb10_header(SCV_NB10_HEADER *cv_nb10_header) {
	R_FREE (cv_nb10_header->file_name);
}

static void init_cv_nb10_header(SCV_NB10_HEADER *cv_nb10_header) {
	memset (cv_nb10_header, 0, sizeof (SCV_NB10_HEADER));
	cv_nb10_header->free = (void (*) (struct SCV_NB10_HEADER *))free_cv_nb10_header;
}

static bool get_rsds(ut8 *dbg_data, int dbg_data_len, SCV_RSDS_HEADER *res) {
	const int rsds_sz = 4 + sizeof (SGUID) + 4;
	if (dbg_data_len < rsds_sz) {
		return false;
	}
	memcpy (res, dbg_data, rsds_sz);
	res->file_name = (ut8 *)strdup ((const char *)dbg_data + rsds_sz);
	return true;
}

static void get_nb10(ut8 *dbg_data, int dbg_data_len, SCV_NB10_HEADER *res) {
	const int nb10sz = 16;
	if (dbg_data_len < nb10sz) {
		return;
	}
	memcpy (res, dbg_data, nb10sz);
	res->file_name = (ut8 *)strdup ((const char *)dbg_data + nb10sz);
}

static int get_debug_info(RBinPEObj *pe, PE_(image_debug_directory_entry) * dbg_dir_entry, ut8 *dbg_data, int dbg_data_len, SDebugInfo *res) {
#define SIZEOF_FILE_NAME 255
	int i = 0;
	const char *dbgname;
	if (!dbg_data) {
		return 0;
	}
	switch (dbg_dir_entry->Type) {
	case IMAGE_DEBUG_TYPE_CODEVIEW:
		if (!strncmp ((char *)dbg_data, "RSDS", 4)) {
			SCV_RSDS_HEADER rsds_hdr;
			init_rsdr_hdr (&rsds_hdr);
			if (!get_rsds (dbg_data, dbg_data_len, &rsds_hdr)) {
				R_LOG_WARN ("Cannot read PE debug info");
				return 0;
			}
			snprintf (res->guidstr, GUIDSTR_LEN,
				"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x%x",
				rsds_hdr.guid.data1,
				rsds_hdr.guid.data2,
				rsds_hdr.guid.data3,
				rsds_hdr.guid.data4[0],
				rsds_hdr.guid.data4[1],
				rsds_hdr.guid.data4[2],
				rsds_hdr.guid.data4[3],
				rsds_hdr.guid.data4[4],
				rsds_hdr.guid.data4[5],
				rsds_hdr.guid.data4[6],
				rsds_hdr.guid.data4[7],
				rsds_hdr.age);
			dbgname = (char *)rsds_hdr.file_name;
			r_str_ncpy (res->file_name, (const char *)dbgname, sizeof (res->file_name));
			rsds_hdr.free ((struct SCV_RSDS_HEADER *)&rsds_hdr);
		} else if (strncmp ((const char *)dbg_data, "NB10", 4) == 0) {
			if (dbg_data_len < 20) {
				R_LOG_WARN ("Truncated NB10 entry, not enough data to parse");
				return 0;
			}
			SCV_NB10_HEADER nb10_hdr = { { 0 } };
			init_cv_nb10_header (&nb10_hdr);
			get_nb10 (dbg_data, dbg_data_len, &nb10_hdr);
			snprintf (res->guidstr, sizeof (res->guidstr),
				"%x%x", nb10_hdr.timestamp, nb10_hdr.age);
			res->file_name[0] = 0;
			if (nb10_hdr.file_name) {
				strncpy (res->file_name, (const char *)nb10_hdr.file_name, sizeof (res->file_name) - 1);
			}
			res->file_name[sizeof (res->file_name) - 1] = 0;
			nb10_hdr.free ((struct SCV_NB10_HEADER *)&nb10_hdr);
		} else {
			R_LOG_INFO ("CodeView section not NB10 or RSDS");
			return 0;
		}
		break;
	default:
		// R_LOG_WARN ("get_debug_info(): not supported type");
		return 0;
	}

	while (i < 33) {
		res->guidstr[i] = toupper ((ut8)res->guidstr[i]);
		i++;
	}

	return 1;
}

static int read_image_debug_directory_entry(RBuffer *b, ut64 addr, PE_(image_debug_directory_entry) * entry) {
	st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
	if (r_buf_seek (b, addr, R_BUF_SET) == -1) {
		return -1;
	}
	ut8 buf[sizeof (PE_(image_debug_directory_entry))];
	r_buf_read (b, buf, sizeof (buf));
	PE_READ_STRUCT_FIELD (entry, PE_(image_debug_directory_entry), Characteristics, 32);
	PE_READ_STRUCT_FIELD (entry, PE_(image_debug_directory_entry), TimeDateStamp, 32);
	PE_READ_STRUCT_FIELD (entry, PE_(image_debug_directory_entry), MajorVersion, 16);
	PE_READ_STRUCT_FIELD (entry, PE_(image_debug_directory_entry), MinorVersion, 16);
	PE_READ_STRUCT_FIELD (entry, PE_(image_debug_directory_entry), Type, 32);
	PE_READ_STRUCT_FIELD (entry, PE_(image_debug_directory_entry), SizeOfData, 32);
	PE_READ_STRUCT_FIELD (entry, PE_(image_debug_directory_entry), AddressOfRawData, 32);
	PE_READ_STRUCT_FIELD (entry, PE_(image_debug_directory_entry), PointerToRawData, 32);
	r_buf_seek (b, o_addr, R_BUF_SET);
	return sizeof (PE_(image_debug_directory_entry));
}

int PE_(r_bin_pe_get_debug_data)(RBinPEObj *pe, SDebugInfo *res) {
	PE_(image_debug_directory_entry)
	img_dbg_dir_entry = { 0 };
	PE_(image_data_directory) *dbg_dir = NULL;
	PE_DWord dbg_dir_offset;
	ut8 *dbg_data = 0;
	int result = 0;
	if (!pe) {
		return 0;
	}
	dbg_dir = &pe->nt_headers->optional_header.DataDirectory[6 /*IMAGE_DIRECTORY_ENTRY_DEBUG*/];
	dbg_dir_offset = PE_(va2pa) (pe, dbg_dir->VirtualAddress);
	if ((int)dbg_dir_offset < 0 || dbg_dir_offset >= pe->size) {
		return false;
	}
	if (dbg_dir_offset >= r_buf_size (pe->b)) {
		return false;
	}
	read_image_debug_directory_entry (pe->b, dbg_dir_offset, &img_dbg_dir_entry);
	if ((r_buf_size (pe->b) - dbg_dir_offset) < sizeof (PE_(image_debug_directory_entry))) {
		return false;
	}
	ut32 dbg_data_poff = R_MIN (img_dbg_dir_entry.PointerToRawData, r_buf_size (pe->b));
	int dbg_data_len = R_MIN (img_dbg_dir_entry.SizeOfData, r_buf_size (pe->b) - dbg_data_poff);
	if (dbg_data_len < 1) {
		return false;
	}
	dbg_data = (ut8 *)calloc (1, dbg_data_len + 1);
	if (dbg_data) {
		r_buf_read_at (pe->b, dbg_data_poff, dbg_data, dbg_data_len);
		result = get_debug_info (pe, &img_dbg_dir_entry, dbg_data, dbg_data_len, res);
		R_FREE (dbg_data);
	}
	return result;
}

struct r_bin_pe_import_t *PE_(r_bin_pe_get_imports)(RBinPEObj *pe) {
	struct r_bin_pe_import_t *imps, *imports = NULL;
	char dll_name[PE_NAME_LENGTH + 1];
	int nimp = 0;
	ut64 off; // used to cache value
	PE_DWord dll_name_offset = 0;
	PE_DWord paddr = 0;
	PE_DWord import_func_name_offset;
	PE_(image_import_directory)
	curr_import_dir;
	PE_(image_delay_import_directory)
	curr_delay_import_dir;

	if (!pe) {
		return NULL;
	}
	if (pe->import_directory_offset >= pe->size) {
		return NULL;
	}
	if (pe->import_directory_offset + 20 > pe->size) {
		return NULL;
	}

	off = pe->import_directory_offset;
	if (off < pe->size && off > 0) {
		ut64 last;
		int idi = 0;
		if (off + sizeof (PE_(image_import_directory)) > pe->size) {
			return NULL;
		}
		int r = read_image_import_directory (pe->b, pe->import_directory_offset + idi * sizeof (curr_import_dir), &curr_import_dir);
		if (r < 0) {
			return NULL;
		}

		if (pe->import_directory_size < 1) {
			return NULL;
		}
		if (off + pe->import_directory_size > pe->size) {
			// why chopping instead of returning and cleaning?
			R_LOG_WARN ("read (import directory too big)");
			pe->import_directory_size = pe->size - pe->import_directory_offset;
		}
		last = pe->import_directory_offset + pe->import_directory_size;
		while (r == sizeof (curr_import_dir) && pe->import_directory_offset + (idi + 1) * sizeof (curr_import_dir) <= last && (curr_import_dir.FirstThunk != 0 || curr_import_dir.Name != 0 || curr_import_dir.TimeDateStamp != 0 || curr_import_dir.Characteristics != 0 || curr_import_dir.ForwarderChain != 0)) {
			int rr;
			dll_name_offset = curr_import_dir.Name;
			paddr = PE_(va2pa) (pe, dll_name_offset);
			if (paddr > pe->size) {
				goto beach;
			}
			if (paddr + PE_NAME_LENGTH > pe->size) {
				rr = r_buf_read_at (pe->b, paddr, (ut8 *)dll_name, pe->size - paddr);
				if (rr != pe->size - paddr) {
					goto beach;
				}
				dll_name[pe->size - paddr] = '\0';
			} else {
				rr = r_buf_read_at (pe->b, paddr, (ut8 *)dll_name, PE_NAME_LENGTH);
				if (rr != PE_NAME_LENGTH) {
					goto beach;
				}
				dll_name[PE_NAME_LENGTH] = '\0';
			}
			if (!bin_pe_parse_imports (pe, &imports, &nimp, dll_name,
				curr_import_dir.Characteristics,
				curr_import_dir.FirstThunk)) {
				break;
			}
			idi++;
			r = read_image_import_directory (pe->b, pe->import_directory_offset + idi * sizeof (curr_import_dir), &curr_import_dir);
			if (r < 0) {
				free (imports);
				return NULL;
			}
		}
	}
	off = pe->delay_import_directory_offset;
	if (off < pe->size && off > 0) {
		int didi = 0;
		ut64 last = get_delay_import_directory_last (pe);
		if (off + sizeof (PE_(image_delay_import_directory)) > last) {
			goto beach;
		}
		for (didi = 0; off + (didi + 1) * sizeof (curr_delay_import_dir) <= last; didi++) {
			int r = read_image_delay_import_directory (pe->b, off + didi * sizeof (curr_delay_import_dir),
				&curr_delay_import_dir);
			if (r != sizeof (curr_delay_import_dir)) {
				goto beach;
			}
			if (is_null_delay_import_directory (&curr_delay_import_dir)) {
				break;
			}
			if (!curr_delay_import_dir.Name || !curr_delay_import_dir.DelayImportAddressTable ||
				!curr_delay_import_dir.DelayImportNameTable) {
				break;
			}
			if (!curr_delay_import_dir.Attributes) {
				dll_name_offset = PE_(va2pa) (pe, curr_delay_import_dir.Name - PE_(r_bin_pe_get_image_base) (pe));
				import_func_name_offset = curr_delay_import_dir.DelayImportNameTable - PE_(r_bin_pe_get_image_base) (pe);
			} else {
				dll_name_offset = PE_(va2pa) (pe, curr_delay_import_dir.Name);
				import_func_name_offset = curr_delay_import_dir.DelayImportNameTable;
			}
			if (dll_name_offset > pe->size || dll_name_offset + PE_NAME_LENGTH > pe->size) {
				goto beach;
			}
			int rr = r_buf_read_at (pe->b, dll_name_offset, (ut8 *)dll_name, PE_NAME_LENGTH);
			if (rr < 5) {
				goto beach;
			}
			dll_name[PE_NAME_LENGTH] = '\0';
			if (!bin_pe_parse_imports (pe, &imports, &nimp, dll_name, import_func_name_offset,
				curr_delay_import_dir.DelayImportAddressTable)) {
				break;
			}
		}
	}
beach:
	if (nimp) {
		imps = realloc (imports, (nimp + 1) * sizeof (struct r_bin_pe_import_t));
		if (!imps) {
			r_sys_perror ("realloc (import)");
			free (imports);
			return NULL;
		}
		imports = imps;
		imports[nimp].last = 1;
	}
	return imports;
}

struct r_bin_pe_lib_t *PE_(r_bin_pe_get_libs)(RBinPEObj *pe) {
	if (!pe) {
		return NULL;
	}
	struct r_bin_pe_lib_t *libs = NULL;
	struct r_bin_pe_lib_t *new_libs = NULL;
	PE_(image_import_directory)
	curr_import_dir;
	PE_(image_delay_import_directory)
	curr_delay_import_dir;
	PE_DWord name_off = 0;
	HtPP *lib_map = NULL;
	ut64 off; // cache value
	int index = 0;
	int len = 0;
	int max_libs = 20;
	libs = calloc (max_libs + 1, sizeof (struct r_bin_pe_lib_t));
	if (!libs) {
		r_sys_perror ("malloc (libs)");
		return NULL;
	}

	if (pe->import_directory_offset + pe->import_directory_size > pe->size) {
		R_LOG_WARN ("import directory offset bigger than file");
		goto out_error;
	}
	lib_map = sdb_ht_new ();
	off = pe->import_directory_offset;
	if (off < pe->size && off > 0) {
		ut64 last;
		int iidi = 0;
		// normal imports
		if (off + sizeof (PE_(image_import_directory)) > pe->size) {
			goto out_error;
		}
		int r = read_image_import_directory (pe->b, off + iidi * sizeof (curr_import_dir),
			&curr_import_dir);
		last = off + pe->import_directory_size;
		while (r == sizeof (curr_import_dir) && off + (iidi + 1) * sizeof (curr_import_dir) <= last && (curr_import_dir.FirstThunk || curr_import_dir.Name || curr_import_dir.TimeDateStamp || curr_import_dir.Characteristics || curr_import_dir.ForwarderChain)) {
			name_off = PE_(va2pa) (pe, curr_import_dir.Name);
			len = r_buf_read_at (pe->b, name_off, (ut8 *)libs[index].name, PE_STRING_LENGTH);
			if (!libs[index].name[0]) { // minimum string length
				goto next;
			}
			if (len < 2 || libs[index].name[0] == 0) { // minimum string length
				R_LOG_WARN ("read (libs - import dirs) %d", len);
				break;
			}
			libs[index].name[len - 1] = '\0';
			r_str_case (libs[index].name, 0);
			if (!sdb_ht_find (lib_map, libs[index].name, NULL)) {
				sdb_ht_insert (lib_map, libs[index].name, "a");
				libs[index++].last = 0;
				if (index >= max_libs) {
					new_libs = realloc (libs, (max_libs * 2) * sizeof (struct r_bin_pe_lib_t));
					if (!new_libs) {
						r_sys_perror ("realloc (libs)");
						goto out_error;
					}
					libs = new_libs;
					new_libs = NULL;
					max_libs *= 2;
				}
			}
		next:
			iidi++;
			r = read_image_import_directory (pe->b, off + iidi * sizeof (curr_import_dir),
				&curr_import_dir);
		}
	}
	off = pe->delay_import_directory_offset;
	if (off < pe->size && off > 0) {
		ut64 did = 0;
		ut64 last = get_delay_import_directory_last (pe);
		if (off + sizeof (PE_(image_delay_import_directory)) > last) {
			goto out_error;
		}
		int r = read_image_delay_import_directory (pe->b, off, &curr_delay_import_dir);
		if (r != sizeof (curr_delay_import_dir)) {
			goto out_error;
		}
		while (r == sizeof (curr_delay_import_dir) &&
			off + (did + 1) * sizeof (curr_delay_import_dir) <= last &&
			!is_null_delay_import_directory (&curr_delay_import_dir)) {
			if (!curr_delay_import_dir.Name || !curr_delay_import_dir.DelayImportNameTable) {
				break;
			}
			name_off = PE_(va2pa) (pe, curr_delay_import_dir.Name);
			if (name_off > pe->size || name_off + PE_STRING_LENGTH > pe->size) {
				goto out_error;
			}
			len = r_buf_read_at (pe->b, name_off, (ut8 *)libs[index].name, PE_STRING_LENGTH);
			if (len != PE_STRING_LENGTH) {
				R_LOG_WARN ("read (libs - delay import dirs)");
				break;
			}
			libs[index].name[len - 1] = '\0';
			r_str_case (libs[index].name, 0);
			if (!sdb_ht_find (lib_map, libs[index].name, NULL)) {
				sdb_ht_insert (lib_map, libs[index].name, "a");
				libs[index++].last = 0;
				if (index >= max_libs) {
					new_libs = realloc (libs, (max_libs * 2) * sizeof (struct r_bin_pe_lib_t));
					if (!new_libs) {
						r_sys_perror ("realloc (libs)");
						goto out_error;
					}
					libs = new_libs;
					new_libs = NULL;
					max_libs *= 2;
				}
			}
			did++;
			r = read_image_delay_import_directory (pe->b, off + did * sizeof (curr_delay_import_dir),
				&curr_delay_import_dir);
		}
	}
	sdb_ht_free (lib_map);
	libs[index].last = 1;
	return libs;
out_error:
	sdb_ht_free (lib_map);
	free (libs);
	return NULL;
}

int PE_(r_bin_pe_get_image_size)(RBinPEObj *pe) {
	return pe->nt_headers->optional_header.SizeOfImage;
}

// TODO: make it const! like in elf
char *PE_(r_bin_pe_get_machine)(RBinPEObj *pe) {
	char *machine = NULL;

	if (pe && pe->nt_headers) {
		switch (pe->nt_headers->file_header.Machine) {
		case PE_IMAGE_FILE_MACHINE_ALPHA: machine = "Alpha"; break;
		case PE_IMAGE_FILE_MACHINE_ALPHA64: machine = "Alpha 64"; break;
		case PE_IMAGE_FILE_MACHINE_AM33: machine = "AM33"; break;
		case PE_IMAGE_FILE_MACHINE_AMD64: machine = "AMD 64"; break;
		case PE_IMAGE_FILE_MACHINE_ARM: machine = "ARM"; break;
		case PE_IMAGE_FILE_MACHINE_CEE: machine = "CEE"; break;
		case PE_IMAGE_FILE_MACHINE_CEF: machine = "CEF"; break;
		case PE_IMAGE_FILE_MACHINE_EBC: machine = "EBC"; break;
		case PE_IMAGE_FILE_MACHINE_I386: machine = "i386"; break;
		case PE_IMAGE_FILE_MACHINE_IA64: machine = "ia64"; break;
		case PE_IMAGE_FILE_MACHINE_M32R: machine = "M32R"; break;
		case PE_IMAGE_FILE_MACHINE_M68K: machine = "M68K"; break;
		case PE_IMAGE_FILE_MACHINE_MIPS16: machine = "Mips 16"; break;
		case PE_IMAGE_FILE_MACHINE_MIPSFPU: machine = "Mips FPU"; break;
		case PE_IMAGE_FILE_MACHINE_MIPSFPU16: machine = "Mips FPU 16"; break;
		case PE_IMAGE_FILE_MACHINE_POWERPC: machine = "PowerPC"; break;
		case PE_IMAGE_FILE_MACHINE_POWERPCFP: machine = "PowerPC FP"; break;
		case PE_IMAGE_FILE_MACHINE_POWERPCBE: machine = "PowerPC BE"; break;
		case PE_IMAGE_FILE_MACHINE_R10000: machine = "R10000"; break;
		case PE_IMAGE_FILE_MACHINE_R3000: machine = "R3000"; break;
		case PE_IMAGE_FILE_MACHINE_R4000: machine = "R4000"; break;
		case PE_IMAGE_FILE_MACHINE_SH3: machine = "SH3"; break;
		case PE_IMAGE_FILE_MACHINE_SH3DSP: machine = "SH3DSP"; break;
		case PE_IMAGE_FILE_MACHINE_SH3E: machine = "SH3E"; break;
		case PE_IMAGE_FILE_MACHINE_SH4: machine = "SH4"; break;
		case PE_IMAGE_FILE_MACHINE_SH5: machine = "SH5"; break;
		case PE_IMAGE_FILE_MACHINE_THUMB: machine = "Thumb"; break;
		case PE_IMAGE_FILE_MACHINE_TRICORE: machine = "Tricore"; break;
		case PE_IMAGE_FILE_MACHINE_WCEMIPSV2: machine = "WCE Mips V2"; break;
		case PE_IMAGE_FILE_MACHINE_RISCV32: machine = "RISC-V 32-bit"; break;
		case PE_IMAGE_FILE_MACHINE_RISCV64: machine = "RISC-V 64-bit"; break;
		case PE_IMAGE_FILE_MACHINE_RISCV128: machine = "RISC-V 128-bit"; break;
		default: machine = "unknown";
		}
	}
	return machine? strdup (machine): NULL;
}

// TODO: make it const! like in elf
char *PE_(r_bin_pe_get_os)(RBinPEObj *pe) {
	char *os;
	if (!pe || !pe->nt_headers) {
		return NULL;
	}
	switch (pe->nt_headers->optional_header.Subsystem) {
	case PE_IMAGE_SUBSYSTEM_NATIVE:
		os = strdup ("native");
		break;
	case PE_IMAGE_SUBSYSTEM_WINDOWS_GUI:
	case PE_IMAGE_SUBSYSTEM_WINDOWS_CUI:
	case PE_IMAGE_SUBSYSTEM_WINDOWS_CE_GUI:
		os = strdup ("windows");
		break;
	case PE_IMAGE_SUBSYSTEM_POSIX_CUI:
		os = strdup ("posix");
		break;
	case PE_IMAGE_SUBSYSTEM_EFI_APPLICATION:
	case PE_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER:
	case PE_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER:
	case PE_IMAGE_SUBSYSTEM_EFI_ROM:
		os = strdup ("efi");
		break;
	case PE_IMAGE_SUBSYSTEM_XBOX:
		os = strdup ("xbox");
		break;
	default:
		// XXX: this is unknown
		os = strdup ("windows");
	}
	return os;
}

// TODO: make it const
char *PE_(r_bin_pe_get_class)(RBinPEObj *pe) {
	if (pe && pe->nt_headers) {
		switch (pe->nt_headers->optional_header.Magic) {
		case PE_IMAGE_FILE_TYPE_PE32: return strdup ("PE32");
		case PE_IMAGE_FILE_TYPE_PE32PLUS: return strdup ("PE32+");
		default: return strdup ("Unknown");
		}
	}
	return NULL;
}

int PE_(r_bin_pe_get_bits)(RBinPEObj *pe) {
	int bits = 32;
	if (pe && pe->nt_headers) {
		if (is_arm (pe)) {
			if (is_thumb (pe)) {
				bits = 16;
			}
		} else {
			switch (pe->nt_headers->optional_header.Magic) {
			case PE_IMAGE_FILE_TYPE_PE32: bits = 32; break;
			case PE_IMAGE_FILE_TYPE_PE32PLUS: bits = 64; break;
			default: bits = -1;
			}
		}
	}
	return bits;
}

char *PE_(r_bin_pe_get_cc)(RBinPEObj *pe) {
	if (pe && pe->nt_headers) {
		if (is_arm (pe)) {
			if (is_thumb (pe)) {
				return strdup ("arm16");
			}
			switch (pe->nt_headers->optional_header.Magic) {
			case PE_IMAGE_FILE_TYPE_PE32: return strdup ("arm32");
			case PE_IMAGE_FILE_TYPE_PE32PLUS: return strdup ("arm64");
			}
		} else {
			switch (pe->nt_headers->optional_header.Magic) {
			case PE_IMAGE_FILE_TYPE_PE32: return strdup ("cdecl");
			case PE_IMAGE_FILE_TYPE_PE32PLUS: return strdup ("ms");
			}
		}
	}
	return NULL;
}

// This function try to detect anomalies within section
// we check if there is a section mapped at entrypoint, otherwise add it up
void PE_(r_bin_pe_check_sections)(RBinPEObj *pe, struct r_bin_pe_section_t **sects) {
	int i = 0;
	struct r_bin_pe_section_t *sections = *sects;
	ut64 addr_beg, addr_end, new_section_size, new_perm, base_addr;
	struct r_bin_pe_addr_t *entry = PE_(r_bin_pe_get_entrypoint) (pe);

	if (!entry) {
		return;
	}
	new_section_size = pe->size;
	new_section_size -= entry->paddr > pe->size? 0: entry->paddr;
	new_perm = (PE_IMAGE_SCN_MEM_READ | PE_IMAGE_SCN_MEM_WRITE | PE_IMAGE_SCN_MEM_EXECUTE);
	base_addr = PE_(r_bin_pe_get_image_base) (pe);

#if 0
	for (i = 0; !sections[i].last; i++) {
		//strcmp against .text doesn't work in somes cases
		if (strstr ((const char*) sections[i].name, "text")) {
			bool fix = false;
			int j;
			//check paddr boundaries
			addr_beg = sections[i].paddr;
			addr_end = addr_beg + sections[i].size;
			if (entry->paddr < addr_beg || entry->paddr > addr_end) {
				fix = true;
			}
			//check vaddr boundaries
			addr_beg = sections[i].vaddr + base_addr;
			addr_end = addr_beg + sections[i].vsize;
			if (entry->vaddr < addr_beg || entry->vaddr > addr_end) {
				fix = true;
			}
			//look for other segment with x that is already mapped and hold entrypoint
			for (j = 0; !sections[j].last; j++) {
				addr_beg = sections[j].paddr;
				addr_end = addr_beg + sections[j].size;
				if (addr_beg <= entry->paddr && entry->paddr < addr_end) {
					if (!sections[j].vsize) {
						sections[j].vsize = sections[j].size;
					}
					addr_beg = sections[j].vaddr + base_addr;
					addr_end = addr_beg + sections[j].vsize;
					if (addr_beg <= entry->vaddr || entry->vaddr < addr_end) {
						if (! (sections[j].perm & PE_IMAGE_SCN_MEM_EXECUTE)) {
							if (pe->verbose) {
								R_LOG_WARN ("Found entrypoint in non-executable section");
							}
							sections[j].perm |= PE_IMAGE_SCN_MEM_EXECUTE;
						}
						fix = false;
						break;
					}
				}
			}
#if 0
			//if either vaddr or paddr fail we should update this section
			if (fix) {
				strcpy ((char*) sections[i].name, "blob");
				sections[i].paddr = entry->paddr;
				sections[i].vaddr = entry->vaddr - base_addr;
				sections[i].size = sections[i].vsize = new_section_size;
				sections[i].perm = new_perm;
			}
#endif
			goto out_function;
		}
	}
#endif
	// if we arrive til here means there is no text section find one that is holding the code
	for (i = 0; !sections[i].last; i++) {
		if (sections[i].size > pe->size) {
			continue;
		}
		addr_beg = sections[i].paddr;
		addr_end = addr_beg + sections[i].size;
		if (addr_beg <= entry->paddr && entry->paddr < addr_end) {
			if (!sections[i].vsize) {
				sections[i].vsize = sections[i].size;
			}
			addr_beg = sections[i].vaddr + base_addr;
			addr_end = addr_beg + sections[i].vsize;
			if (entry->vaddr < addr_beg || entry->vaddr > addr_end) {
				sections[i].vaddr = entry->vaddr - base_addr;
			}
			goto out_function;
		}
	}
	// we need to create another section in order to load the entrypoint
	void *ss = realloc (sections, (pe->num_sections + 2) * sizeof (struct r_bin_pe_section_t));
	if (!ss) {
		goto out_function;
	}
	pe->sections = sections = ss;
	i = pe->num_sections;
	sections[i].last = 0;
	strcpy ((char *)sections[i].name, "blob");
	sections[i].paddr = entry->paddr;
	sections[i].vaddr = entry->vaddr - base_addr;
	sections[i].size = sections[i].vsize = new_section_size;
	sections[i].perm = new_perm;
	sections[i + 1].last = 1;
	*sects = sections;
out_function:
	free (entry);
}

static struct r_bin_pe_section_t *PE_(r_bin_pe_get_sections)(RBinPEObj *pe) {
	int i, j, section_count = 0;

	if (!pe || !pe->nt_headers) {
		return NULL;
	}
	PE_(image_section_header) *shdr = pe->section_header;
	for (i = 0; i < pe->num_sections; i++) {
		// just allocate the needed
		if (shdr[i].SizeOfRawData || shdr[i].Misc.VirtualSize) {
			section_count++;
		}
	}
	struct r_bin_pe_section_t *sections = calloc (section_count + 1, sizeof (struct r_bin_pe_section_t));
	if (!sections) {
		r_sys_perror ("malloc (sections)");
		return NULL;
	}
	for (i = 0, j = 0; i < pe->num_sections; i++) {
		if (!shdr[i].SizeOfRawData && !shdr[i].Misc.VirtualSize) {
			continue;
		}
		if (shdr[i].Name[0] == '\0') {
			char *new_name = r_str_newf ("sect_%d", j);
			r_str_ncpy ((char *)sections[j].name, new_name, R_ARRAY_SIZE (sections[j].name) - 1);
			free (new_name);
		} else if (shdr[i].Name[0] == '/') {
			// section header is not null terminated, so use ndup
			char *n = r_str_ndup ((const char *)shdr[i].Name + 1, sizeof (shdr[i].Name) - 1);
			int idx = atoi (n);
			free (n);
			ut64 sym_tbl_off = pe->nt_headers->file_header.PointerToSymbolTable;
			const int num_symbols = pe->nt_headers->file_header.NumberOfSymbols;

			st64 off;
			if (r_mul_overflow ((st64)num_symbols, (st64)COFF_SYMBOL_SIZE, &off)) {
				continue;
			}
			if (off > 0 && sym_tbl_off &&
				sym_tbl_off + off + idx < pe->size &&
				sym_tbl_off + off + idx > off) {
				int sz = PE_IMAGE_SIZEOF_SHORT_NAME * 3;
				char buf[64] = { 0 };
				if (r_buf_read_at (pe->b,
					sym_tbl_off + off + idx,
					(ut8 *)buf, 64)) {
					memcpy (sections[j].name, buf, sz);
					sections[j].name[sz - 1] = '\0';
				}
			}
		} else {
			memcpy (sections[j].name, shdr[i].Name, PE_IMAGE_SIZEOF_SHORT_NAME);
			sections[j].name[PE_IMAGE_SIZEOF_SHORT_NAME] = '\0';
		}
		sections[j].vaddr = shdr[i].VirtualAddress;
		sections[j].size = shdr[i].SizeOfRawData;
		if (shdr[i].Misc.VirtualSize) {
			sections[j].vsize = shdr[i].Misc.VirtualSize;
		} else {
			sections[j].vsize = shdr[i].SizeOfRawData;
		}
		sections[j].paddr = shdr[i].PointerToRawData;
		if (pe->optional_header) {
			ut32 sa = pe->optional_header->SectionAlignment;
			if (sa) {
				ut64 diff = sections[j].vsize % sa;
				if (diff) {
					sections[j].vsize += sa - diff;
				}
				if (sections[j].vaddr % sa) {
					R_LOG_WARN ("section %s not aligned to SectionAlignment", sections[j].name);
				}
			}
			const ut32 fa = pe->optional_header->FileAlignment;
			if (fa) {
				const ut64 diff = sections[j].paddr % fa;
				if (diff != 0) {
					R_LOG_WARN ("section %s not aligned to FileAlignment", sections[j].name);
					sections[j].paddr -= diff;
					sections[j].size += diff;
				}
			}
		}
		sections[j].flags = shdr[i].Characteristics;
		sections[j].perm = shdr[i].Characteristics;
		sections[j].last = 0;
		j++;
	}
	sections[j].last = 1;
	pe->num_sections = section_count;
	return sections;
}

char *PE_(r_bin_pe_get_subsystem)(RBinPEObj *pe) {
	char *subsystem = NULL;
	if (pe && pe->nt_headers) {
		switch (pe->nt_headers->optional_header.Subsystem) {
		case PE_IMAGE_SUBSYSTEM_NATIVE:
			subsystem = "Native";
			break;
		case PE_IMAGE_SUBSYSTEM_WINDOWS_GUI:
			subsystem = "Windows GUI";
			break;
		case PE_IMAGE_SUBSYSTEM_WINDOWS_CUI:
			subsystem = "Windows CUI";
			break;
		case PE_IMAGE_SUBSYSTEM_POSIX_CUI:
			subsystem = "POSIX CUI";
			break;
		case PE_IMAGE_SUBSYSTEM_WINDOWS_CE_GUI:
			subsystem = "Windows CE GUI";
			break;
		case PE_IMAGE_SUBSYSTEM_EFI_APPLICATION:
			subsystem = "EFI Application";
			break;
		case PE_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER:
			subsystem = "EFI Boot Service Driver";
			break;
		case PE_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER:
			subsystem = "EFI Runtime Driver";
			break;
		case PE_IMAGE_SUBSYSTEM_EFI_ROM:
			subsystem = "EFI ROM";
			break;
		case PE_IMAGE_SUBSYSTEM_XBOX:
			subsystem = "XBOX";
			break;
		default:
			subsystem = "Unknown";
			break;
		}
	}
	return subsystem? strdup (subsystem): NULL;
}

#define HASCHR(x) (pe->nt_headers->file_header.Characteristics &(x))

int PE_(r_bin_pe_is_dll)(RBinPEObj *pe) {
	if (!pe || !pe->nt_headers) {
		return false;
	}
	return HASCHR (PE_IMAGE_FILE_DLL);
}

int PE_(r_bin_pe_is_pie)(RBinPEObj *pe) {
	if (!pe || !pe->nt_headers) {
		return false;
	}
	return HASCHR (IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE);
#if 0
	BOOL aslr = inh->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;
//TODO: implement dep?
	BOOL dep = inh->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NX_COMPAT;
#endif
}

int PE_(r_bin_pe_is_big_endian)(RBinPEObj *pe) {
	if (!pe || !pe->nt_headers) {
		return false;
	}
	const ut16 arch = pe->nt_headers->file_header.Machine;
	switch (arch) {
	case PE_IMAGE_FILE_MACHINE_I386:
	case PE_IMAGE_FILE_MACHINE_AMD64:
		return false;
	case PE_IMAGE_FILE_MACHINE_POWERPCBE:
		return true;
	}
	return HASCHR (PE_IMAGE_FILE_BYTES_REVERSED_HI);
}

int PE_(r_bin_pe_is_stripped_relocs)(RBinPEObj *pe) {
	if (!pe || !pe->nt_headers) {
		return false;
	}
	return HASCHR (PE_IMAGE_FILE_RELOCS_STRIPPED);
}

int PE_(r_bin_pe_is_stripped_line_nums)(RBinPEObj *pe) {
	if (!pe || !pe->nt_headers) {
		return false;
	}
	return HASCHR (PE_IMAGE_FILE_LINE_NUMS_STRIPPED);
}

int PE_(r_bin_pe_is_stripped_local_syms)(RBinPEObj *pe) {
	if (!pe || !pe->nt_headers) {
		return false;
	}
	return HASCHR (PE_IMAGE_FILE_LOCAL_SYMS_STRIPPED);
}

int PE_(r_bin_pe_is_stripped_debug)(RBinPEObj *pe) {
	if (!pe || !pe->nt_headers) {
		return false;
	}
	return HASCHR (PE_IMAGE_FILE_DEBUG_STRIPPED);
}

void *PE_(r_bin_pe_free)(RBinPEObj *pe) {
	int n_streams = 0;
	if (!pe) {
		return NULL;
	}
	free (pe->dos_header);
	free (pe->nt_headers);
	free (pe->section_header);
	free (pe->export_directory);
	free (pe->import_directory);
	free (pe->resource_directory);
	free_security_directory (pe->security_directory);
	free (pe->delay_import_directory);
	free (pe->tls_directory);
	free (pe->clr_hdr);
	if (pe->metadata_header) {
		n_streams = pe->metadata_header->NumberOfStreams;
		free (pe->metadata_header->VersionString);
	}
	bin_pe_free_metadata_streams (pe->streams, n_streams);
	free (pe->metadata_header);
	free (pe->sections);
	free (pe->authentihash);
	r_list_free (pe->rich_entries);
	r_list_free (pe->relocs);
	r_list_free (pe->resources);
	r_list_free (pe->dotnet_symbols);
	r_pkcs7_cms_free (pe->cms);
	r_pkcs7_spcinfo_free (pe->spcinfo);
	sdb_free (pe->kv);
	r_unref (pe->b);
	pe->b = NULL;
	free (pe);
	return NULL;
}

RBinPEObj *PE_(r_bin_pe_new)(const char *file, bool verbose) {
	RBinPEObj *pe = R_NEW0 (RBinPEObj);
	if (!pe) {
		return NULL;
	}
	pe->file = file;
	size_t binsz;
	ut8 *buf = (ut8 *)r_file_slurp (file, &binsz);
	pe->size = binsz;
	if (!buf) {
		return PE_(r_bin_pe_free) (pe);
	}
	pe->b = r_buf_new ();
	if (!r_buf_set_bytes (pe->b, buf, pe->size)) {
		free (buf);
		return PE_(r_bin_pe_free) (pe);
	}
	pe->verbose = verbose;
	free (buf);
	if (!bin_pe_init (pe)) {
		return PE_(r_bin_pe_free) (pe);
	}
	return pe;
}

R_API RBinPEObj *PE_(r_bin_pe_new_buf)(RBuffer *buf, bool verbose) {
	RBinPEObj *pe = R_NEW0 (RBinPEObj);
	if (!pe) {
		return NULL;
	}
	pe->kv = sdb_new0 ();
	pe->b = r_ref (buf);
	pe->verbose = verbose;
	pe->size = r_buf_size (buf);
	// buffer can be a lot larger so we might overflow
	if (pe->size < 0) {
		// in that case just clamp it
		// it's unlikely that whole buffer is just PE alone
		pe->size = INT_MAX;
	}
	if (!bin_pe_init (pe)) {
		return PE_(r_bin_pe_free) (pe);
	}
	return pe;
}
