// SPDX-FileCopyrightText: 2019 MapleLeaf-X
// SPDX-License-Identifier: LGPL-3.0-only

#include <ntstatus.h>
#define WIN32_NO_STATUS
#include "windows_debug.h"
#include "../../common_windows.h"
#include <w32dbg_wrap.h>
#undef WIN32_NO_STATUS

const DWORD wait_time = 1000;
static RzList *lib_list = NULL;

#define SystemHandleInformation 16

bool setup_debug_privileges(bool b) {
	HANDLE tok;
	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &tok)) {
		return false;
	}
	bool ret = false;
	LUID luid;
	if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
		TOKEN_PRIVILEGES tp;
		tp.PrivilegeCount = 1;
		tp.Privileges[0].Luid = luid;
		tp.Privileges[0].Attributes = b ? SE_PRIVILEGE_ENABLED : 0;
		if (AdjustTokenPrivileges(tok, FALSE, &tp, 0, NULL, NULL)) {
			// TODO: handle ERROR_NOT_ALL_ASSIGNED
			ret = GetLastError() == ERROR_SUCCESS;
		}
	}
	CloseHandle(tok);
	return ret;
}

int w32_init(RzDebug *dbg) {
	if (!dbg->plugin_data) {
		dbg->plugin_data = dbg->iob.get_w32dbg_wrap(dbg->iob.io);
		if (!dbg->plugin_data) {
			return 0;
		}
	}
	// escalate privs (required for win7/vista)
	setup_debug_privileges(true);

	HMODULE lib = GetModuleHandleW(L"kernel32"); // Always loaded
	if (!lib) {
		return false;
	}
	// lookup function pointers for portability
	// only windows vista :(
	w32_ProcessIdToSessionId = (BOOL(WINAPI *)(DWORD, DWORD *))
		GetProcAddress(lib, "ProcessIdToSessionId");

	w32_QueryFullProcessImageNameW = (BOOL(WINAPI *)(HANDLE, DWORD, LPWSTR, PDWORD))
		GetProcAddress(lib, "QueryFullProcessImageNameW");

	// api to retrieve YMM from w7 sp1
	w32_GetEnabledXStateFeatures = (ut64(WINAPI *)())
		GetProcAddress(lib, "GetEnabledXStateFeatures");

	w32_InitializeContext = (BOOL(WINAPI *)(PVOID, DWORD, PCONTEXT *, PDWORD))
		GetProcAddress(lib, "InitializeContext");

	w32_GetXStateFeaturesMask = (BOOL(WINAPI *)(PCONTEXT Context, PDWORD64))
		GetProcAddress(lib, "GetXStateFeaturesMask");

	w32_LocateXStateFeature = (PVOID(WINAPI *)(PCONTEXT Context, DWORD, PDWORD))
		GetProcAddress(lib, "LocateXStateFeature");

	w32_SetXStateFeaturesMask = (BOOL(WINAPI *)(PCONTEXT Context, DWORD64))
		GetProcAddress(lib, "SetXStateFeaturesMask");

	lib = GetModuleHandleW(L"ntdll.dll");
	if (!lib) {
		eprintf("Cannot load ntdll.dll. Aborting\n");
		return false;
	}
	w32_NtQuerySystemInformation = (NTSTATUS(WINAPI *)(ULONG, PVOID, ULONG, PULONG))
		GetProcAddress(lib, "NtQuerySystemInformation");

	w32_NtDuplicateObject = (NTSTATUS(WINAPI *)(HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG))
		GetProcAddress(lib, "NtDuplicateObject");

	w32_NtQueryObject = (NTSTATUS(WINAPI *)(HANDLE, ULONG, PVOID, ULONG, PULONG))
		GetProcAddress(lib, "NtQueryObject");

	w32_NtQueryInformationThread = (NTSTATUS(WINAPI *)(HANDLE, ULONG, PVOID, ULONG, PULONG))
		GetProcAddress(lib, "NtQueryInformationThread");

	return true;
}

static int w32_findthread_cmp(int *tid, PTHREAD_ITEM th, void *user) {
	return (int)!(*tid == th->tid);
}

static inline PTHREAD_ITEM find_thread(RzDebug *dbg, int tid) {
	if (!dbg->threads) {
		return NULL;
	}
	RzListIter *it = rz_list_find(dbg->threads, &tid, (RzListComparator)w32_findthread_cmp, NULL);
	return it ? rz_list_iter_get_data(it) : NULL;
}

static PTHREAD_ITEM add_thread(RzDebug *dbg, DWORD pid, DWORD tid, HANDLE hThread, LPVOID lpThreadLocalBase, LPVOID lpStartAddress, BOOL bFinished) {
	rz_return_val_if_fail(dbg, NULL);
	if (!dbg->threads) {
		dbg->threads = rz_list_newf(free);
	}
	if (!lpStartAddress) {
		w32_NtQueryInformationThread(hThread, 9, &lpStartAddress, sizeof(LPVOID), NULL);
	}
	THREAD_ITEM th = {
		pid,
		tid,
		bFinished,
		false,
		hThread,
		lpThreadLocalBase,
		lpStartAddress
	};
	PTHREAD_ITEM pthread = find_thread(dbg, tid);
	if (pthread) {
		*pthread = th;
		return NULL;
	}
	pthread = RZ_NEW0(THREAD_ITEM);
	if (!pthread) {
		RZ_LOG_ERROR("Memory allocation failed.\n");
		return NULL;
	}
	*pthread = th;
	rz_list_append(dbg->threads, pthread);
	return pthread;
}

static inline int suspend_thread(HANDLE th) {
	int ret;
	if ((ret = SuspendThread(th)) == -1) {
		rz_sys_perror("SuspendThread");
	}
	return ret;
}

static int resume_thread(HANDLE th) {
	int ret;
	if ((ret = ResumeThread(th)) == -1) {
		rz_sys_perror("ResumeThread");
	}
	return ret;
}

static inline void continue_thread(HANDLE th) {
	int ret;
	do {
		ret = resume_thread(th);
	} while (ret > 0);
}

static bool is_thread_alive(RzDebug *dbg, int tid) {
	PTHREAD_ITEM th = find_thread(dbg, tid);
	if (!th) {
		return false;
	}
	if (!th->bFinished) {
		if (SuspendThread(th->hThread) != -1) {
			ResumeThread(th->hThread);
			return true;
		}
	}
	th->bFinished = true;
	return false;
}

static bool is_process_alive(HANDLE ph) {
	if (ph) {
		DWORD code;
		if (!GetExitCodeProcess(ph, &code)) {
			GetExitCodeThread(ph, &code);
		}
		return code == STILL_ACTIVE;
	}
	return false;
}

static int set_thread_context(HANDLE th, const ut8 *buf, int size) {
	bool ret;
	CONTEXT ctx = { 0 };
	size = RZ_MIN(size, sizeof(ctx));
	memcpy(&ctx, buf, size);
	if (!(ret = SetThreadContext(th, &ctx))) {
		rz_sys_perror("SetThreadContext");
	}
	return ret;
}

static int get_thread_context(HANDLE th, ut8 *buf, int size, DWORD context_flags) {
	int ret = 0;
	CONTEXT ctx = { 0 };
	// TODO: support various types?
	ctx.ContextFlags = context_flags;
	if (GetThreadContext(th, &ctx)) {
		if (size > sizeof(ctx)) {
			size = sizeof(ctx);
		}
		memcpy(buf, &ctx, size);
		ret = size;
	} else {
		if (is_process_alive(th)) {
			rz_sys_perror("GetThreadContext");
		}
	}
	return ret;
}

#if __i386__ || __x86_64__

int w32_step(RzDebug *dbg) {
	/* set TRAP flag */
	CONTEXT ctx;
	if (!w32_reg_read(dbg, RZ_REG_TYPE_GPR, (ut8 *)&ctx, sizeof(ctx))) {
		return false;
	}
	ctx.EFlags |= 0x100;
	if (!w32_reg_write(dbg, RZ_REG_TYPE_GPR, (ut8 *)&ctx, sizeof(ctx))) {
		return false;
	}
	return w32_continue(dbg, dbg->pid, dbg->tid, 0);
}

static int get_avx(HANDLE th, ut128 xmm[16], ut128 ymm[16]) {
	int nregs = 0, index = 0;
	DWORD ctxsize = 0;
	DWORD featurelen = 0;
	ut64 featuremask = 0;
	ut128 *newxmm = NULL;
	ut128 *newymm = NULL;
	void *buffer = NULL;
	PCONTEXT ctx;
	if (!w32_GetEnabledXStateFeatures) {
		return 0;
	}
	// Check for AVX extension
	featuremask = w32_GetEnabledXStateFeatures();
	if ((featuremask & XSTATE_MASK_AVX) == 0) {
		return 0;
	}
	if ((w32_InitializeContext(NULL, CONTEXT_ALL | CONTEXT_XSTATE, NULL, &ctxsize)) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
		return 0;
	}
	buffer = malloc(ctxsize);
	if (!buffer) {
		return 0;
	}
	if (!w32_InitializeContext(buffer, CONTEXT_ALL | CONTEXT_XSTATE, &ctx, &ctxsize)) {
		goto err_get_avx;
	}
	if (!w32_SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX)) {
		goto err_get_avx;
	}
	// TODO: Use get_thread_context
	if (!GetThreadContext(th, ctx)) {
		goto err_get_avx;
	}
	if (w32_GetXStateFeaturesMask(ctx, &featuremask)) {
		goto err_get_avx;
	}
	newxmm = (ut128 *)w32_LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &featurelen);
	nregs = featurelen / sizeof(*newxmm);
	for (index = 0; index < nregs; index++) {
		ymm[index].High = 0;
		xmm[index].High = 0;
		ymm[index].Low = 0;
		xmm[index].Low = 0;
	}
	if (newxmm != NULL) {
		for (index = 0; index < nregs; index++) {
			xmm[index].High = newxmm[index].High;
			xmm[index].Low = newxmm[index].Low;
		}
	}
	if ((featuremask & XSTATE_MASK_AVX) != 0) {
		// check for AVX initialization and get the pointer.
		newymm = (ut128 *)w32_LocateXStateFeature(ctx, XSTATE_AVX, NULL);
		if (!newymm) {
			goto err_get_avx;
		}
		for (index = 0; index < nregs; index++) {
			ymm[index].High = newymm[index].High;
			ymm[index].Low = newymm[index].Low;
		}
	}
err_get_avx:
	free(buffer);
	return nregs;
}

static void print_fpu_context(HANDLE th, CONTEXT *ctx) {
	ut128 xmm[16];
	ut128 ymm[16];
	ut80 st[8];
	ut64 mm[8];
	ut16 top = 0;
	int x, nxmm = 0, nymm = 0;
#if _WIN64
	eprintf("ControlWord   = %08x StatusWord   = %08x\n", ctx->FltSave.ControlWord, ctx->FltSave.StatusWord);
	eprintf("MxCsr         = %08lx TagWord      = %08x\n", ctx->MxCsr, ctx->FltSave.TagWord);
	eprintf("ErrorOffset   = %08lx DataOffset   = %08lx\n", ctx->FltSave.ErrorOffset, ctx->FltSave.DataOffset);
	eprintf("ErrorSelector = %08x DataSelector = %08x\n", ctx->FltSave.ErrorSelector, ctx->FltSave.DataSelector);
	for (x = 0; x < 8; x++) {
		st[x].Low = ctx->FltSave.FloatRegisters[x].Low;
		st[x].High = (ut16)ctx->FltSave.FloatRegisters[x].High;
	}
	top = (ctx->FltSave.StatusWord & 0x3fff) >> 11;
	for (x = 0; x < 8; x++) {
		mm[top] = ctx->FltSave.FloatRegisters[x].Low;
		top++;
		if (top > 7) {
			top = 0;
		}
	}
	for (x = 0; x < 16; x++) {
		xmm[x].High = ctx->FltSave.XmmRegisters[x].High;
		xmm[x].Low = ctx->FltSave.XmmRegisters[x].Low;
	}
	nxmm = 16;
#else
	eprintf("ControlWord   = %08x StatusWord   = %08x\n", (ut32)ctx->FloatSave.ControlWord, (ut32)ctx->FloatSave.StatusWord);
	eprintf("MxCsr         = %08x TagWord      = %08x\n", *(ut32 *)&ctx->ExtendedRegisters[24], (ut32)ctx->FloatSave.TagWord);
	eprintf("ErrorOffset   = %08x DataOffset   = %08x\n", (ut32)ctx->FloatSave.ErrorOffset, (ut32)ctx->FloatSave.DataOffset);
	eprintf("ErrorSelector = %08x DataSelector = %08x\n", (ut32)ctx->FloatSave.ErrorSelector, (ut32)ctx->FloatSave.DataSelector);
	for (x = 0; x < 8; x++) {
		st[x].High = (ut16) * ((ut16 *)(&ctx->FloatSave.RegisterArea[x * 10] + 8));
		st[x].Low = (ut64) * ((ut64 *)&ctx->FloatSave.RegisterArea[x * 10]);
	}
	top = (ctx->FloatSave.StatusWord & 0x3fff) >> 11;
	for (x = 0; x < 8; x++) {
		mm[top] = *((ut64 *)&ctx->FloatSave.RegisterArea[x * 10]);
		top++;
		if (top > 7) {
			top = 0;
		}
	}
	for (x = 0; x < 8; x++) {
		xmm[x] = *((ut128 *)&ctx->ExtendedRegisters[(10 + x) * 16]);
	}
	nxmm = 8;
#endif
	// show fpu,mm,xmm regs
	for (x = 0; x < 8; x++) {
		// the conversin from long double to double only work for compilers
		// with long double size >=10 bytes (also we lost 2 bytes of precision)
		//   in mingw long double is 12 bytes size
		//   in msvc long double is alias for double = 8 bytes size
		//   in gcc long double is 10 bytes (correct representation)
		eprintf("ST%i %04x %016" PFMT64x " (%f)\n", x, st[x].High, st[x].Low, (double)(*((long double *)&st[x])));
	}
	for (x = 0; x < 8; x++) {
		eprintf("MM%i %016" PFMT64x "\n", x, mm[x]);
	}
	for (x = 0; x < nxmm; x++) {
		eprintf("XMM%i %016" PFMT64x " %016" PFMT64x "\n", x, xmm[x].High, xmm[x].Low);
	}
	// show Ymm regs
	nymm = get_avx(th, xmm, ymm);
	if (nymm) {
		for (x = 0; x < nymm; x++) {
			eprintf("Ymm%d: %016" PFMT64x " %016" PFMT64x " %016" PFMT64x " %016" PFMT64x "\n", x, ymm[x].High, ymm[x].Low, xmm[x].High, xmm[x].Low);
		}
	}
}

static void transfer_drx(RzDebug *dbg, const ut8 *buf) {
	CONTEXT cur_ctx;
	if (w32_reg_read(dbg, RZ_REG_TYPE_ANY, (ut8 *)&cur_ctx, sizeof(CONTEXT))) {
		CONTEXT *new_ctx = (CONTEXT *)buf;
		size_t drx_size = offsetof(CONTEXT, Dr7) - offsetof(CONTEXT, Dr0) + sizeof(new_ctx->Dr7);
		memcpy(&cur_ctx.Dr0, &new_ctx->Dr0, drx_size);
		*new_ctx = cur_ctx;
	}
}

#else

static void transfer_drx(RzDebug *dbg, const ut8 *buf) {
	// Do nothing (not supported)
}

static void print_fpu_context(HANDLE th, CONTEXT *buf) {
	// TODO
}

int w32_step(RzDebug *dbg) {
	// Do nothing (not supported)
	return 0;
}

static inline void get_arm_hwbp_values(ut64 address, ut32 *control, ut64 *value) {
	const ut32 type = 0b0100 << 20; // match
	const ut32 bas = 0xF << 5; // match a64 and a32
	const ut32 priv = 1 << 2;
	const ut32 enable = 1;
	*control = type | bas | priv | enable;
	*value = address;
}

static inline void get_arm64_hwwp_values(ut64 address, int size, int rw, ut32 *control, ut64 *value) {
	const unsigned int offset = address % 8;
	const ut32 byte_mask = ((1 << size) - 1) << offset;
	const ut32 priv = 1 << 2;
	const ut32 enable = 1;
	ut32 load_store = 0;
	switch (rw) {
	case RZ_PERM_R:
		load_store = 1;
		break;
	case RZ_PERM_W:
		load_store = 2;
		break;
	case RZ_PERM_RW:
		load_store = 3;
		break;
	}
	*control = byte_mask << 5 | load_store << 3 | priv | enable;
	*value = address - offset;
}

static inline bool is_watchpoint(RzBreakpointItem *b) {
	return b->perm & (RZ_PERM_RW | RZ_PERM_R | RZ_PERM_W);
}

static inline bool is_breakpoint(RzBreakpointItem *b) {
	return b->perm & RZ_PERM_X;
}

int w32_hwbp_arm_add(RzDebug *dbg, RzBreakpoint *bp, RzBreakpointItem *b) {
	rz_return_val_if_fail(dbg && bp && b, 0);
	W32DbgWInst *wrap = dbg->plugin_data;
	CONTEXT ctx;
	const bool alive = is_thread_alive(dbg, wrap->pi.dwThreadId);
	if (alive && suspend_thread(wrap->pi.hThread) == -1) {
		return 0;
	}
	get_thread_context(wrap->pi.hThread, (ut8 *)&ctx, sizeof(CONTEXT), CONTEXT_DEBUG_REGISTERS);
	ut32 control;
	ut64 value;
	int i;
	if (is_watchpoint(b)) {
		get_arm64_hwwp_values(b->addr, b->size, b->perm, &control, &value);
		for (i = 0; i < ARM64_MAX_WATCHPOINTS; i++) {
			if (!ctx.Wvr[i] || ctx.Wvr[i] == value) {
				break;
			}
		}
		if (i < ARM64_MAX_WATCHPOINTS) {
			ctx.Wcr[i] = control;
			ctx.Wvr[i] = value;
		} else {
			eprintf("Too many hardware watchpoints\n");
		}
	}
	if (is_breakpoint(b)) {
		get_arm_hwbp_values(b->addr, &control, &value);
		for (i = 0; i < ARM64_MAX_BREAKPOINTS; i++) {
			if (!ctx.Bvr[i] || ctx.Bvr[i] == value) {
				break;
			}
		}
		if (i < ARM64_MAX_BREAKPOINTS) {
			ctx.Bcr[i] = control;
			ctx.Bvr[i] = value;
		} else {
			eprintf("Too many hardware breakpoints\n");
		}
	}
	set_thread_context(wrap->pi.hThread, (ut8 *)&ctx, sizeof(CONTEXT));
	if (alive && resume_thread(wrap->pi.hThread) == -1) {
		return 0;
	}
	return 1;
}

int w32_hwbp_arm_del(RzDebug *dbg, RzBreakpoint *bp, RzBreakpointItem *b) {
	W32DbgWInst *wrap = dbg->plugin_data;
	CONTEXT ctx;
	const bool alive = is_thread_alive(dbg, wrap->pi.dwThreadId);
	if (alive && suspend_thread(wrap->pi.hThread) == -1) {
		return 0;
	}
	get_thread_context(wrap->pi.hThread, (ut8 *)&ctx, sizeof(CONTEXT), CONTEXT_DEBUG_REGISTERS);
	ut32 control;
	ut64 value;
	int i;
	if (is_watchpoint(b)) {
		get_arm64_hwwp_values(b->addr, b->size, b->perm, &control, &value);
		for (i = 0; i < ARM64_MAX_WATCHPOINTS; i++) {
			if (ctx.Wcr[i] == control && ctx.Wvr[i] == value) {
				break;
			}
		}
		if (i < ARM64_MAX_WATCHPOINTS) {
			ctx.Wcr[i] = 0;
			ctx.Wvr[i] = 0;
		}
	}
	if (is_breakpoint(b)) {
		get_arm_hwbp_values(b->addr, &control, &value);
		for (i = 0; i < ARM64_MAX_BREAKPOINTS; i++) {
			if (ctx.Bvr[i] == value) {
				break;
			}
		}
		if (i < ARM64_MAX_BREAKPOINTS) {
			ctx.Bcr[i] = 0;
			ctx.Bvr[i] = 0;
		}
	}
	set_thread_context(wrap->pi.hThread, (ut8 *)&ctx, sizeof(CONTEXT));
	if (alive && resume_thread(wrap->pi.hThread, dbg->bits) == -1) {
		return 0;
	}
	return 1;
}

#endif

static HANDLE get_thread_handle_from_tid(RzDebug *dbg, int tid) {
	rz_return_val_if_fail(dbg, NULL);
	W32DbgWInst *wrap = dbg->plugin_data;
	HANDLE th = NULL;
	if (wrap->pi.dwThreadId == tid) {
		th = wrap->pi.hThread;
	} else {
		PTHREAD_ITEM thread = find_thread(dbg, tid);
		if (thread) {
			th = thread->hThread;
		}
	}
	return th;
}

int w32_reg_read(RzDebug *dbg, int type, ut8 *buf, int size) {
	bool showfpu = false;
	if (type < -1) {
		showfpu = true; // hack for debugging
		type = -type;
	}
	bool alive = is_thread_alive(dbg, dbg->tid);
	HANDLE th = get_thread_handle_from_tid(dbg, dbg->tid);
	if (!th || th == INVALID_HANDLE_VALUE) {
		return 0;
	}
	// Always suspend
	if (alive && suspend_thread(th) == -1) {
		return 0;
	}
	size = get_thread_context(th, buf, size, CONTEXT_ALL);
	if (showfpu) {
		print_fpu_context(th, (CONTEXT *)buf);
	}
	// Always resume
	if (alive && resume_thread(th) == -1) {
		size = 0;
	}
	return size;
}

int w32_reg_write(RzDebug *dbg, int type, const ut8 *buf, int size) {
	bool alive = is_thread_alive(dbg, dbg->tid);
	if (!alive) {
		return false;
	}
	HANDLE th = get_thread_handle_from_tid(dbg, dbg->tid);
	if (!th || th == INVALID_HANDLE_VALUE) {
		return 0;
	}
	// Always suspend
	if (suspend_thread(th) == -1) {
		return false;
	}
	if (type == RZ_REG_TYPE_DRX) {
		transfer_drx(dbg, buf);
	}
	bool ret = set_thread_context(th, buf, size);
	// Always resume
	if (resume_thread(th) == -1) {
		ret = false;
	}
	return ret;
}

static inline bool already_attached(W32DbgWInst *wrap, int pid) {
	return wrap->pi.hProcess && wrap->pi.dwProcessId == pid && wrap->pi.dwThreadId;
}

int w32_attach(RzDebug *dbg, int pid) {
	W32DbgWInst *wrap = dbg->plugin_data;
	if (already_attached(wrap, pid)) {
		return wrap->pi.dwThreadId;
	}
	if (wrap->pi.hProcess && wrap->pi.hProcess != INVALID_HANDLE_VALUE) {
		CloseHandle(wrap->pi.hProcess);
	}
	dbg->main_pid = pid;
	wrap->pi.dwProcessId = pid;
	wrap->params.type = W32_ATTACH;
	w32dbg_wrap_wait_ret(wrap);
	if (!wrap->params.ret) {
		w32dbgw_err(wrap);
		rz_sys_perror("DebugActiveProcess");
		wrap->pi.hProcess = NULL;
		wrap->pi.dwProcessId = 0;
		return -1;
	}
	dbg->cur->wait(dbg, pid);
	rz_debug_continue(dbg);
	return wrap->pi.dwThreadId;
}

int w32_detach(RzDebug *dbg, int pid) {
	if (pid == -1 || dbg->pid != pid) {
		return false;
	}

	// Resume suspended threads
	RzListIter *it;
	PTHREAD_ITEM th;
	rz_list_foreach (dbg->threads, it, th) {
		if (th->bSuspended && !th->bFinished) {
			resume_thread(th->hThread);
		}
	}
	rz_list_purge(dbg->threads);
	if (lib_list) {
		rz_list_purge(lib_list);
	}
	W32DbgWInst *wrap = dbg->plugin_data;
	bool ret = false;
	wrap->pi.dwProcessId = pid;
	wrap->params.type = W32_DETACH;
	w32dbg_wrap_wait_ret(wrap);
	ret = wrap->params.ret;
	memset(&wrap->pi, 0, sizeof(wrap->pi));
	return ret;
}

static char *get_file_name_from_handle(HANDLE handle_file) {
	HANDLE handle_file_map = NULL;
	LPWSTR filename = NULL;
	DWORD file_size_high = 0;
	LPVOID map = NULL;
	DWORD file_size_low = GetFileSize(handle_file, &file_size_high);

	if (file_size_low == 0 && file_size_high == 0) {
		return NULL;
	}
	handle_file_map = CreateFileMappingW(handle_file, NULL, PAGE_READONLY, 0, 1, NULL);
	if (!handle_file_map) {
		goto err_get_file_name_from_handle;
	}
	filename = malloc((MAX_PATH + 1) * sizeof(WCHAR));
	if (!filename) {
		goto err_get_file_name_from_handle;
	}
	/* Create a file mapping to get the file name. */
	map = MapViewOfFile(handle_file_map, FILE_MAP_READ, 0, 0, 1);
	if (!map || !GetMappedFileNameW(GetCurrentProcess(), map, filename, MAX_PATH)) {
		RZ_FREE(filename);
		goto err_get_file_name_from_handle;
	}
	WCHAR temp_buffer[512];
	/* Translate path with device name to drive letters. */
	if (!GetLogicalDriveStringsW(_countof(temp_buffer) - 1, temp_buffer)) {
		goto err_get_file_name_from_handle;
	}
	WCHAR name[MAX_PATH];
	WCHAR drive[3] = L" :";
	LPWSTR cur_drive = temp_buffer;
	while (*cur_drive) {
		/* Look up each device name */
		*drive = *cur_drive;
		if (QueryDosDeviceW(drive, name, MAX_PATH)) {
			size_t name_length = wcslen(name);

			if (name_length < MAX_PATH) {
				if (wcsnicmp(filename, name, name_length) == 0 && *(filename + name_length) == L'\\') {
					WCHAR temp_filename[MAX_PATH];
					_snwprintf_s(temp_filename, MAX_PATH, _TRUNCATE, L"%s%s",
						drive, filename + name_length);
					wcsncpy(filename, temp_filename,
						wcslen(temp_filename) + 1);
					filename[MAX_PATH] = L'\0';
					break;
				}
			}
		}
		cur_drive++;
	}
err_get_file_name_from_handle:
	if (map) {
		UnmapViewOfFile(map);
	}
	if (handle_file_map) {
		CloseHandle(handle_file_map);
	}
	if (filename) {
		char *ret = rz_utf16_to_utf8(filename);
		free(filename);
		return ret;
	}
	return NULL;
}

static char *resolve_path(HANDLE ph, HANDLE mh) {
	// TODO: add maximum path length support
	const DWORD maxlength = MAX_PATH;
	WCHAR filename[MAX_PATH];
	DWORD length = GetModuleFileNameExW(ph, mh, filename, maxlength);
	if (length > 0) {
		return rz_utf16_to_utf8(filename);
	}
	char *name = get_file_name_from_handle(mh);
	if (name) {
		return name;
	}
	// Upon failure fallback to GetProcessImageFileName
	length = GetProcessImageFileNameW(mh, filename, maxlength);
	if (length == 0) {
		return NULL;
	}
	// Convert NT path to win32 path
	WCHAR *tmp = wcschr(filename + 1, L'\\');
	if (!tmp) {
		return NULL;
	}
	tmp = wcschr(tmp + 1, L'\\');
	if (!tmp) {
		return NULL;
	}
	length = tmp - filename;
	WCHAR device[MAX_PATH];
	char *ret = NULL;
	for (WCHAR drv[] = L"A:"; drv[0] <= L'Z'; drv[0]++) {
		if (QueryDosDeviceW(drv, device, maxlength) > 0) {
			if (!wcsncmp(filename, device, length)) {
				WCHAR path[MAX_PATH];
				_snwprintf(path, maxlength, L"%s%s", drv, tmp);
				ret = rz_utf16_to_utf8(path);
				break;
			}
		}
	}
	return ret;
}

static void libfree(void *lib) {
	PLIB_ITEM lib_item = (PLIB_ITEM)lib;
	free(lib_item->Name);
	free(lib_item->Path);
	if (lib_item->hFile && lib_item->hFile != INVALID_HANDLE_VALUE) {
		CloseHandle(lib_item->hFile);
	}
	free(lib_item);
}

static int findlibcmp(void *BaseOfDll, void *lib, void *user) {
	PLIB_ITEM lib_item = (PLIB_ITEM)lib;
	return !lib_item->hFile || lib_item->hFile == INVALID_HANDLE_VALUE || lib_item->BaseOfDll != BaseOfDll;
}

static void *find_library(void *BaseOfDll) {
	RzListIter *it = rz_list_find(lib_list, BaseOfDll, (RzListComparator)findlibcmp, NULL);
	return it ? rz_list_iter_get_data(it) : NULL;
}

static void remove_library(PLIB_ITEM library) {
	rz_list_delete_data(lib_list, library);
}

static void add_library(DWORD pid, LPVOID lpBaseOfDll, HANDLE hFile, char *dllname) {
	if (lib_list == NULL) {
		lib_list = rz_list_newf((RzListFree)libfree);
		if (!lib_list) {
			RZ_LOG_ERROR("Failed to allocate memory\n");
			return;
		}
	}
	RzListIter *it;
	PLIB_ITEM lib;
	rz_list_foreach (lib_list, it, lib) {
		if (lib->hFile == hFile && lib->BaseOfDll == lpBaseOfDll) {
			return;
		}
	}
	lib = RZ_NEW0(LIB_ITEM);
	if (!lib) {
		RZ_LOG_ERROR("Failed to allocate memory\n");
		return;
	}
	lib->pid = pid;
	lib->hFile = hFile;
	lib->BaseOfDll = lpBaseOfDll;
	lib->Path = rz_str_dup(dllname);
	lib->Name = rz_str_dup(rz_file_basename(dllname));

	(void)rz_list_append(lib_list, lib);
}

static void *last_library(void) {
	return lib_list ? rz_list_last(lib_list) : NULL;
}

static bool breaked = false;

int w32_attach_new_process(RzDebug *dbg, int pid) {
	int tid = -1;

	if (!w32_detach(dbg, dbg->pid)) {
		eprintf("Failed to detach from (%d)\n", dbg->pid);
		return -1;
	}

	if ((tid = w32_attach(dbg, pid)) < 0) {
		eprintf("Failed to attach to (%d)\n", pid);
		return -1;
	}

	dbg->tid = tid;
	dbg->pid = pid;
	// Call select to sync the new pid's data
	rz_debug_select(dbg, pid, tid);
	return dbg->tid;
}

int w32_select(RzDebug *dbg, int pid, int tid) {
	RzListIter *it;
	W32DbgWInst *wrap = dbg->plugin_data;

	// Re-attach to a different pid
	if (dbg->pid > -1 && dbg->pid != pid) {
		return w32_attach_new_process(dbg, pid);
	}

	if (dbg->tid == -1) {
		return tid;
	}

	if (!dbg->threads) {
		dbg->threads = rz_list_newf(free);
		return tid;
	}

	if (rz_list_empty(dbg->threads)) {
		return tid;
	}

	PTHREAD_ITEM th = find_thread(dbg, tid);

	int selected = 0;
	if (th && is_thread_alive(dbg, th->tid)) {
		wrap->pi.hThread = th->hThread;
		selected = tid;
	} else if (tid) {
		// If thread is dead, search for another one
		rz_list_foreach (dbg->threads, it, th) {
			if (!is_thread_alive(dbg, th->tid)) {
				continue;
			}
			wrap->pi.hThread = th->hThread;
			selected = th->tid;
			break;
		}
	}

	if (dbg->corebind.cfggeti(dbg->corebind.core, "dbg.threads")) {
		// Suspend all other threads
		rz_list_foreach (dbg->threads, it, th) {
			if (!th->bFinished && !th->bSuspended && th->tid != selected) {
				suspend_thread(th->hThread);
				th->bSuspended = true;
			}
		}
	}

	return selected;
}

int w32_kill(RzDebug *dbg, int pid, int tid, int sig) {
	W32DbgWInst *wrap = dbg->plugin_data;

	if (sig == 0) {
		if (rz_list_empty(dbg->threads)) {
			if (lib_list) {
				rz_list_purge(lib_list);
			}
			return false;
		}
		return true;
	}

	bool ret = false;
	if (TerminateProcess(wrap->pi.hProcess, 1)) {
		ret = true;
	}
	wrap->pi.hProcess = NULL;
	wrap->pi.hThread = NULL;
	return ret;
}

void w32_break_process(void *user) {
	RzDebug *dbg = (RzDebug *)user;
	W32DbgWInst *wrap = dbg->plugin_data;
	if (dbg->corebind.cfggeti(dbg->corebind.core, "dbg.threads")) {
		w32_select(dbg, wrap->pi.dwProcessId, -1); // Suspend all threads
	} else {
		if (!DebugBreakProcess(wrap->pi.hProcess)) {
			rz_sys_perror("DebugBreakProcess");
			eprintf("Could not interrupt program, attempt to press Ctrl-C in the program's console.\n");
		}
	}

	breaked = true;
}

int w32_dbg_wait(RzDebug *dbg, int pid) {
	W32DbgWInst *wrap = dbg->plugin_data;
	DEBUG_EVENT de;
	int tid, next_event = 0;
	char *dllname = NULL;
	int ret = RZ_DEBUG_REASON_UNKNOWN;
	static int exited_already = 0;

	rz_cons_break_push(w32_break_process, dbg);

	/* handle debug events */
	do {
		/* do not continue when already exited but still open for examination */
		if (exited_already == pid) {
			rz_cons_break_pop();
			return RZ_DEBUG_REASON_DEAD;
		}
		memset(&de, 0, sizeof(DEBUG_EVENT));
		do {
			wrap->params.type = W32_WAIT;
			wrap->params.wait.de = &de;
			wrap->params.wait.wait_time = wait_time;
			void *bed = rz_cons_sleep_begin();
			w32dbg_wrap_wait_ret(wrap);
			rz_cons_sleep_end(bed);
			if (!w32dbgw_ret(wrap)) {
				if (w32dbgw_err(wrap) != ERROR_SEM_TIMEOUT) {
					rz_sys_perror("WaitForDebugEvent");
					ret = -1;
					goto end;
				}
				if (!is_thread_alive(dbg, dbg->tid)) {
					ret = w32_select(dbg, dbg->pid, dbg->tid);
					if (ret == -1) {
						ret = RZ_DEBUG_REASON_DEAD;
						goto end;
					}
				}
			} else {
				break;
			}
		} while (!breaked);

		if (breaked) {
			ret = RZ_DEBUG_REASON_USERSUSP;
			breaked = false;
		}

		dbg->tid = tid = de.dwThreadId;
		dbg->pid = pid = de.dwProcessId;

		/* TODO: DEBUG_CONTROL_C */
		switch (de.dwDebugEventCode) {
		case CREATE_PROCESS_DEBUG_EVENT:
			CloseHandle(de.u.CreateProcessInfo.hFile);
			add_thread(dbg, pid, tid, de.u.CreateProcessInfo.hThread, de.u.CreateProcessInfo.lpThreadLocalBase, de.u.CreateProcessInfo.lpStartAddress, FALSE);
			wrap->pi.hProcess = de.u.CreateProcessInfo.hProcess;
			wrap->pi.hThread = de.u.CreateProcessInfo.hThread;
			wrap->winbase = (ULONG_PTR)de.u.CreateProcessInfo.lpBaseOfImage;
			ret = RZ_DEBUG_REASON_NEW_PID;
			next_event = 0;
			break;
		case CREATE_THREAD_DEBUG_EVENT:
			add_thread(dbg, pid, tid, de.u.CreateThread.hThread, de.u.CreateThread.lpThreadLocalBase, de.u.CreateThread.lpStartAddress, FALSE);
			if (ret != RZ_DEBUG_REASON_USERSUSP) {
				ret = RZ_DEBUG_REASON_NEW_TID;
			}
			dbg->corebind.cmdf(dbg->corebind.core, "f teb.%d @ 0x%p", tid, de.u.CreateThread.lpThreadLocalBase);
			next_event = 0;
			break;
		case EXIT_PROCESS_DEBUG_EVENT:
		case EXIT_THREAD_DEBUG_EVENT: {
			PTHREAD_ITEM th = find_thread(dbg, tid);
			if (th) {
				th->bFinished = TRUE;
				th->dwExitCode = de.u.ExitThread.dwExitCode;
			} else {
				rz_warn_if_reached();
			}
			dbg->corebind.cmdf(dbg->corebind.core, "f- teb.%d", tid);
			if (de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) {
				exited_already = pid;
				w32_continue(dbg, pid, tid, DBG_CONTINUE);
				ret = pid == dbg->main_pid ? RZ_DEBUG_REASON_DEAD : RZ_DEBUG_REASON_EXIT_PID;
			} else {
				ret = RZ_DEBUG_REASON_EXIT_TID;
			}
			next_event = 0;
			break;
		}
		case LOAD_DLL_DEBUG_EVENT:
			dllname = resolve_path(wrap->pi.hProcess, de.u.LoadDll.hFile);
			if (dllname) {
				add_library(pid, de.u.LoadDll.lpBaseOfDll, de.u.LoadDll.hFile, dllname);
				free(dllname);
			}
			ret = RZ_DEBUG_REASON_NEW_LIB;
			next_event = 0;
			break;
		case UNLOAD_DLL_DEBUG_EVENT: {
			PLIB_ITEM lib = (PLIB_ITEM)find_library(de.u.UnloadDll.lpBaseOfDll);
			if (lib) {
				remove_library(lib);
			}
			ret = RZ_DEBUG_REASON_EXIT_LIB;
			next_event = 0;
			break;
		}
		case OUTPUT_DEBUG_STRING_EVENT: {
			char *str = calloc(de.u.DebugString.nDebugStringLength, sizeof(WCHAR));
			ReadProcessMemory(wrap->pi.hProcess, de.u.DebugString.lpDebugStringData, str, de.u.DebugString.nDebugStringLength, NULL);
			char *tmp = de.u.DebugString.fUnicode
				? rz_utf16_to_utf8((wchar_t *)str)
				: rz_acp_to_utf8(str);
			if (tmp) {
				free(str);
				str = tmp;
			}
			eprintf("(%d) Debug string: %s\n", pid, str);
			free(str);
			w32_continue(dbg, pid, tid, DBG_EXCEPTION_NOT_HANDLED);
			next_event = 1;
			break;
		}
		case RIP_EVENT:
			eprintf("(%d) RIP event\n", pid);
			w32_continue(dbg, pid, tid, -1);
			next_event = 1;
			// XXX unknown ret = RZ_DEBUG_REASON_TRAP;
			break;
		case EXCEPTION_DEBUG_EVENT:
			dbg->reason.signum = DBG_EXCEPTION_NOT_HANDLED;
			switch (de.u.Exception.ExceptionRecord.ExceptionCode) {
			case DBG_CONTROL_C:
				eprintf("Received CTRL+C, suspending execution\n");
				ret = RZ_DEBUG_REASON_SIGNAL;
				next_event = 0;
				break;
#if _WIN64
			case 0x4000001f: /* STATUS_WX86_BREAKPOINT */
#endif
			case EXCEPTION_BREAKPOINT:
				ret = RZ_DEBUG_REASON_BREAKPOINT;
				next_event = 0;
				break;
#if _WIN64
			case 0x4000001e: /* STATUS_WX86_SINGLE_STEP */
#endif
			case EXCEPTION_SINGLE_STEP:
				ret = RZ_DEBUG_REASON_STEP;
				next_event = 0;
				break;
			default:
				if (rz_bp_get_at(dbg->bp, (size_t)de.u.Exception.ExceptionRecord.ExceptionAddress)) {
					ret = RZ_DEBUG_REASON_BREAKPOINT;
					next_event = 0;
					break;
				}
				EXCEPTION_DEBUG_INFO *exp = &de.u.Exception;
				windows_print_exception_event(de.dwProcessId, de.dwThreadId, exp->ExceptionRecord.ExceptionCode, exp->dwFirstChance);
				if (windows_is_exception_fatal(de.u.Exception.ExceptionRecord.ExceptionCode)) {
					next_event = 0;
					dbg->reason.type = windows_exception_to_reason(de.u.Exception.ExceptionRecord.ExceptionCode);
					dbg->reason.tid = de.dwThreadId;
					dbg->reason.addr = (size_t)de.u.Exception.ExceptionRecord.ExceptionAddress;
					dbg->reason.timestamp = rz_time_now();
					ret = dbg->reason.type;
				} else {
					w32_continue(dbg, pid, tid, DBG_EXCEPTION_NOT_HANDLED);
					next_event = 1;
				}
			}
			break;
		default:
			// This case might be reached if break doesn't trigger an event
			if (ret != RZ_DEBUG_REASON_USERSUSP) {
				eprintf("(%d) unknown event: %lu\n", pid, de.dwDebugEventCode);
				ret = -1;
			}
			next_event = 0;
		}
	} while (next_event);

	if (ret != RZ_DEBUG_REASON_DEAD) {
		PTHREAD_ITEM th = find_thread(dbg, tid);
		if (th) {
			wrap->pi.hThread = th->hThread;
		} else {
			rz_warn_if_reached();
		}
	}
#if __arm__ || __arm64__
	if (ret != RZ_DEBUG_REASON_EXIT_TID) {
		CONTEXT ctx;
		suspend_thread(wrap->pi.hThread);
		get_thread_context(wrap->pi.hThread, (ut8 *)&ctx, sizeof(ctx), CONTEXT_CONTROL);
		resume_thread(wrap->pi.hThread);
		if (ctx.Cpsr & 0x20) {
			dbg->bits = RZ_SYS_BITS_16;
		} else {
#if __arm__
			dbg->bits = RZ_SYS_BITS_32;
#else
			dbg->bits = RZ_SYS_BITS_64;
#endif
		}
	}
#endif
end:
	if (ret == RZ_DEBUG_REASON_DEAD) {
		w32_detach(dbg, dbg->pid);
		rz_list_purge(dbg->threads);
		rz_list_purge(lib_list);
	}
	rz_cons_break_pop();
	return ret;
}

int w32_continue(RzDebug *dbg, int pid, int tid, int sig) {
	if (tid != dbg->tid) {
		dbg->tid = w32_select(dbg, pid, tid);
	}
	// Don't continue with a thread that wasn't requested
	if (dbg->tid != tid) {
		return -1;
	}

	if (breaked) {
		breaked = false;
		return -1;
	}

	PTHREAD_ITEM th = find_thread(dbg, tid);
	if (th && th->hThread != INVALID_HANDLE_VALUE && th->bSuspended) {
		continue_thread(th->hThread);
		th->bSuspended = false;
	}

	W32DbgWInst *wrap = dbg->plugin_data;
	wrap->params.type = W32_CONTINUE;

	/* Honor the Windows-specific signal that instructs threads to process exceptions */
	wrap->params.continue_status = (sig == DBG_EXCEPTION_NOT_HANDLED)
		? DBG_EXCEPTION_NOT_HANDLED
		: DBG_EXCEPTION_HANDLED;

	w32dbg_wrap_wait_ret(wrap);
	if (!w32dbgw_ret(wrap)) {
		w32dbgw_err(wrap);
		rz_sys_perror("ContinueDebugEvent");
		return -1;
	}

	if (th && th->bFinished) {
		rz_list_delete_data(dbg->threads, th);
	}

	return tid;
}

RzDebugMap *w32_map_alloc(RzDebug *dbg, ut64 addr, int size) {
	W32DbgWInst *wrap = dbg->plugin_data;
	LPVOID base = VirtualAllocEx(wrap->pi.hProcess, (LPVOID)addr, (SIZE_T)size, MEM_COMMIT, PAGE_READWRITE);
	if (!base) {
		rz_sys_perror("VirtualAllocEx");
		return NULL;
	}
	rz_debug_map_sync(dbg);
	return rz_debug_map_get(dbg, (ut64)base);
}

int w32_map_dealloc(RzDebug *dbg, ut64 addr, int size) {
	W32DbgWInst *wrap = dbg->plugin_data;
	if (!VirtualFreeEx(wrap->pi.hProcess, (LPVOID)addr, 0, MEM_RELEASE)) {
		rz_sys_perror("VirtualFreeEx");
		return false;
	}
	return true;
}

static int io_perms_to_prot(int io_perms) {
	int prot_perms;

	if ((io_perms & RZ_PERM_RWX) == RZ_PERM_RWX) {
		prot_perms = PAGE_EXECUTE_READWRITE;
	} else if ((io_perms & (RZ_PERM_W | RZ_PERM_X)) == (RZ_PERM_W | RZ_PERM_X)) {
		prot_perms = PAGE_EXECUTE_READWRITE;
	} else if ((io_perms & (RZ_PERM_R | RZ_PERM_X)) == (RZ_PERM_R | RZ_PERM_X)) {
		prot_perms = PAGE_EXECUTE_READ;
	} else if ((io_perms & RZ_PERM_RW) == RZ_PERM_RW) {
		prot_perms = PAGE_READWRITE;
	} else if (io_perms & RZ_PERM_W) {
		prot_perms = PAGE_READWRITE;
	} else if (io_perms & RZ_PERM_X) {
		prot_perms = PAGE_EXECUTE;
	} else if (io_perms & RZ_PERM_R) {
		prot_perms = PAGE_READONLY;
	} else {
		prot_perms = PAGE_NOACCESS;
	}
	return prot_perms;
}

int w32_map_protect(RzDebug *dbg, ut64 addr, int size, int perms) {
	DWORD old;
	W32DbgWInst *wrap = dbg->plugin_data;
	return VirtualProtectEx(wrap->pi.hProcess, (LPVOID)(size_t)addr,
		size, io_perms_to_prot(perms), &old);
}

static inline ut64 pc_from_context(CONTEXT *ctx) {
#if __x86_64__
	return ctx->Rip;
#elif __arm__ || __arm64__
	return ctx->Pc;
#elif __i386__
	return ctx->Eip;
#else
#pragma warning("platform not supported")
	return 0;
#endif
}

static char *get_process_path(HANDLE ph, int pid) {
	bool close_handle = false;
	char *path = NULL;
	if (!ph) {
		ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
		if (!ph) {
			ph = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
			if (!ph) {
				ph = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
				if (!ph) {
					return NULL;
				}
			}
		}
		close_handle = true;
	}
	if (w32_QueryFullProcessImageNameW) {
		WCHAR tmp[MAX_PATH + 1];
		DWORD sz = MAX_PATH;
		if (w32_QueryFullProcessImageNameW(ph, 0, tmp, &sz)) {
			path = rz_utf16_to_utf8(tmp);
		}
	} else {
		path = resolve_path(ph, NULL);
	}
	if (close_handle) {
		CloseHandle(ph);
	}
	return path;
}

RzList *w32_thread_list(RzDebug *dbg, int pid, RzList *list) {
	// pid is not respected for TH32CS_SNAPTHREAD flag
	HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	if (th == INVALID_HANDLE_VALUE) {
		rz_sys_perror("CreateToolhelp32Snapshot");
		return list;
	}
	THREADENTRY32 te;
	te.dwSize = sizeof(te);
	HANDLE ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
	if (Thread32First(th, &te)) {
		// TODO: export this code to its own function?
		char *path = NULL;
		int uid = -1;
		if (!te.th32ThreadID) {
			path = get_process_path(ph, pid);
			DWORD sid;
			if (w32_ProcessIdToSessionId && w32_ProcessIdToSessionId(pid, &sid)) {
				uid = sid;
			}
		}
		if (!path) {
			// TODO: enum processes to get binary's name
			path = rz_str_dup("???");
		}
		int saved_tid = dbg->tid;
		do {
			char status = RZ_DBG_PROC_SLEEP;
			if (te.th32OwnerProcessID == pid) {
				ut64 pc = 0;
				if (dbg->pid == pid) {
					CONTEXT ctx = { 0 };
					dbg->tid = te.th32ThreadID;
					w32_reg_read(dbg, RZ_REG_TYPE_GPR, (ut8 *)&ctx, sizeof(ctx));
					// TODO: is needed check context for x32 and x64??
					pc = pc_from_context(&ctx);
					PTHREAD_ITEM pthread = find_thread(dbg, te.th32ThreadID);
					if (pthread) {
						if (pthread->bFinished) {
							status = RZ_DBG_PROC_DEAD;
						} else if (pthread->bSuspended) {
							status = RZ_DBG_PROC_SLEEP;
						} else {
							status = RZ_DBG_PROC_RUN; // TODO: Get more precise thread status
						}
					}
				}
				rz_list_append(list, rz_debug_pid_new(path, te.th32ThreadID, uid, status, pc));
			}
		} while (Thread32Next(th, &te));
		dbg->tid = saved_tid;
		free(path);
	} else {
		rz_sys_perror("Thread32First");
	}
	CloseHandle(th);
	return list;
}

static void w32_info_user(RzDebug *dbg, RzDebugInfo *rdi) {
	HANDLE h_tok = NULL;
	DWORD tok_len = 0;
	PTOKEN_USER tok_usr = NULL;
	LPWSTR usr = NULL, usr_dom = NULL;
	DWORD usr_len = 512;
	DWORD usr_dom_len = 512;
	SID_NAME_USE snu = { 0 };
	W32DbgWInst *wrap = dbg->plugin_data;

	if (!wrap || !wrap->pi.hProcess) {
		return;
	}
	if (!OpenProcessToken(wrap->pi.hProcess, TOKEN_QUERY, &h_tok)) {
		rz_sys_perror("OpenProcessToken");
		goto err_w32_info_user;
	}
	if (!GetTokenInformation(h_tok, TokenUser, (LPVOID)&tok_usr, 0, &tok_len) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
		rz_sys_perror("GetTokenInformation");
		goto err_w32_info_user;
	}
	tok_usr = (PTOKEN_USER)malloc(tok_len);
	if (!tok_usr) {
		goto err_w32_info_user;
	}
	if (!GetTokenInformation(h_tok, TokenUser, (LPVOID)tok_usr, tok_len, &tok_len)) {
		rz_sys_perror("GetTokenInformation");
		goto err_w32_info_user;
	}
	usr = (LPWSTR)malloc(usr_len * sizeof(WCHAR));
	if (!usr) {
		goto err_w32_info_user;
	}
	*usr = '\0';
	usr_dom = (LPWSTR)malloc(usr_dom_len * sizeof(WCHAR));
	if (!usr_dom) {
		goto err_w32_info_user;
	}
	*usr_dom = '\0';
	if (!LookupAccountSidW(NULL, tok_usr->User.Sid, usr, &usr_len, usr_dom, &usr_dom_len, &snu)) {
		rz_sys_perror("LookupAccountSid");
		goto err_w32_info_user;
	}
	if (*usr_dom) {
		rdi->usr = rz_str_newf("%S\\%S", usr_dom, usr);
	} else {
		rdi->usr = rz_utf16_to_utf8(usr);
	}
err_w32_info_user:
	if (h_tok) {
		CloseHandle(h_tok);
	}
	free(usr);
	free(usr_dom);
	free(tok_usr);
}

static void w32_info_exe(RzDebug *dbg, RzDebugInfo *rdi) {
	W32DbgWInst *wrap = dbg->plugin_data;
	if (!wrap) {
		return;
	}
	rdi->exe = resolve_path(wrap->pi.hProcess, NULL);
}

RzDebugInfo *w32_info(RzDebug *dbg, const char *arg) {
	RzDebugInfo *rdi = RZ_NEW0(RzDebugInfo);
	if (!rdi) {
		return NULL;
	}
	rdi->status = RZ_DBG_PROC_SLEEP; // TODO: Fix this
	rdi->pid = dbg->pid;
	rdi->tid = dbg->tid;
	rdi->lib = last_library();
	rdi->thread = find_thread(dbg, dbg->tid);
	rdi->uid = -1;
	rdi->gid = -1;
	rdi->cwd = NULL;
	rdi->exe = NULL;
	rdi->cmdline = NULL;
	rdi->libname = NULL;
	w32_info_user(dbg, rdi);
	w32_info_exe(dbg, rdi);
	return rdi;
}

static RzDebugPid *build_debug_pid(int pid, int ppid, HANDLE ph, const WCHAR *name) {
	char *path = get_process_path(ph, pid);
	int uid = -1;

	DWORD sid;
	if (w32_ProcessIdToSessionId && w32_ProcessIdToSessionId(pid, &sid)) {
		uid = sid;
	}
	if (!path) {
		path = rz_utf16_to_utf8(name);
	}
	// it is possible to get pc for a non debugged process but the operation is expensive and might be risky
	RzDebugPid *ret = rz_debug_pid_new(path, pid, uid, 's', 0);
	ret->ppid = ppid;
	free(path);
	return ret;
}

RzList *w32_pid_list(RzDebug *dbg, int pid, RzList *list) {
	W32DbgWInst *wrap = dbg->plugin_data;
	HANDLE sh = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, pid);
	if (sh == INVALID_HANDLE_VALUE) {
		rz_sys_perror("CreateToolhelp32Snapshot");
		return list;
	}
	PROCESSENTRY32W pe;
	pe.dwSize = sizeof(pe);
	if (Process32FirstW(sh, &pe)) {
		bool all = pid == 0;
		do {
			if (all || pe.th32ProcessID == pid || pe.th32ParentProcessID == pid) {
				// Returns NULL if process is inaccessible unless if its a child process of debugged process
				RzDebugPid *dbg_pid = build_debug_pid(pe.th32ProcessID, pe.th32ParentProcessID,
					dbg->pid == pe.th32ProcessID ? wrap->pi.hProcess : NULL, pe.szExeFile);
				if (dbg_pid) {
					rz_list_append(list, dbg_pid);
				}
			}
		} while (Process32NextW(sh, &pe));
	} else {
		rz_sys_perror("Process32First");
	}
	CloseHandle(sh);
	return list;
}

RzList *w32_desc_list(int pid) {
	HANDLE ph;
	if (!(ph = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid))) {
		return NULL;
	}
	ULONG handleInfoSize = 0x10000;
	POBJECT_TYPE_INFORMATION objectTypeInfo = malloc(0x1000);
	if (!objectTypeInfo) {
		CloseHandle(ph);
		return NULL;
	}
	RzDebugDesc *desc;
	NTSTATUS status;
	PSYSTEM_HANDLE_INFORMATION handleInfo = NULL;
	PVOID objectNameInfo = NULL;
	RzList *ret = rz_list_newf((RzListFree)rz_debug_desc_free);
	if (!ret) {
		goto beach;
	}
	handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
	while ((status = w32_NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH) {
		handleInfoSize *= 2;
		void *tmp = realloc(handleInfo, (size_t)handleInfoSize);
		if (tmp) {
			handleInfo = (PSYSTEM_HANDLE_INFORMATION)tmp;
		} else {
			goto beach;
		}
	}
	if (status) {
		rz_sys_perror("NtQuerySystemInformation");
		goto beach;
	}
	size_t objectNameInfo_sz = 0x1000;
	objectNameInfo = malloc(objectNameInfo_sz);
	if (!objectNameInfo) {
		goto beach;
	}
	int i;
	for (i = 0; i < handleInfo->HandleCount; i++) {
		SYSTEM_HANDLE handle = handleInfo->Handles[i];
		HANDLE dupHandle = NULL;
		ULONG returnLength;
		int perms = 0;
		if (handle.ProcessId != pid) {
			continue;
		}
		if (w32_NtDuplicateObject(ph, (HANDLE)(size_t)handle.Handle, GetCurrentProcess(), &dupHandle, 0, 0, 0)) {
			continue;
		}
		if (w32_NtQueryObject(dupHandle, 2, objectTypeInfo, 0x1000, NULL)) {
			CloseHandle(dupHandle);
			continue;
		}
		if (wcscmp(objectTypeInfo->Name.Buffer, L"File")) {
			CloseHandle(dupHandle);
			continue;
		}
		GENERIC_MAPPING *gm = &objectTypeInfo->GenericMapping;
		if ((handle.GrantedAccess & gm->GenericRead) == gm->GenericRead) {
			perms |= RZ_PERM_R;
		}
		if ((handle.GrantedAccess & gm->GenericWrite) == gm->GenericWrite) {
			perms |= RZ_PERM_W;
		}
		if ((handle.GrantedAccess & gm->GenericExecute) == gm->GenericExecute) {
			perms |= RZ_PERM_X;
		}
		if (w32_NtQueryObject(dupHandle, 1, objectNameInfo, objectNameInfo_sz, &returnLength)) {
			void *tmp = realloc(objectNameInfo, returnLength);
			if (tmp) {
				objectNameInfo = tmp;
				objectNameInfo_sz = returnLength;
			}
			if (w32_NtQueryObject(dupHandle, 1, objectNameInfo, objectNameInfo_sz, NULL)) {
				CloseHandle(dupHandle);
				continue;
			}
		}
		PUNICODE_STRING objectName = objectNameInfo;
		if (objectName->Length) {
			char *name = rz_utf16_to_utf8_l(objectName->Buffer, objectName->Length / 2);
			desc = rz_debug_desc_new(handle.Handle, name, perms, '?', 0);
			if (!desc) {
				free(name);
				break;
			}
			rz_list_append(ret, desc);
			free(name);
		} else {
			char *name = rz_utf16_to_utf8_l(objectTypeInfo->Name.Buffer, objectTypeInfo->Name.Length / 2);
			desc = rz_debug_desc_new(handle.Handle, name, perms, '?', 0);
			if (!desc) {
				free(name);
				break;
			}
			rz_list_append(ret, desc);
			free(name);
		}
		CloseHandle(dupHandle);
	}
beach:
	free(objectNameInfo);
	free(objectTypeInfo);
	free(handleInfo);
	CloseHandle(ph);
	return ret;
}
