/*
 * MSAPI_UTF8: Common API calls using UTF-8 strings
 * Compensating for what Microsoft should have done a long long time ago, that they
 * ONLY started to do in mid-2019 (What the £%^& took them so long?!?), as per:
 * https://docs.microsoft.com/en-us/windows/uwp/design/globalizing/use-utf8-code-page
 *
 * See also: https://utf8everywhere.org
 *
 * Copyright © 2010-2025 Pete Batard <pete@akeo.ie>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
#include <windows.h>
#include <stdio.h>
#include <shlobj.h>
#include <ctype.h>
#include <aclapi.h>
#include <accctrl.h>
#include <commdlg.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <setupapi.h>
#include <direct.h>
#include <share.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <psapi.h>

#pragma once
#if defined(_MSC_VER)
// disable VS2012 Code Analysis warnings that are intentional
#pragma warning(disable: 6387)	// Don't care about bad params
#endif

#ifdef __cplusplus
extern "C" {
#endif

#define _LTEXT(txt) L##txt
#define LTEXT(txt) _LTEXT(txt)

#define wchar_to_utf8_no_alloc(wsrc, dest, dest_size) \
	WideCharToMultiByte(CP_UTF8, 0, wsrc, -1, dest, (int)(dest_size), NULL, NULL)
#define wchar_to_utf8_get_size(wsrc) \
	WideCharToMultiByte(CP_UTF8, 0, wsrc, -1, NULL, 0, NULL, NULL)
#define utf8_to_wchar_no_alloc(src, wdest, wdest_size) \
	MultiByteToWideChar(CP_UTF8, 0, src, -1, wdest, (int)(wdest_size))
#define utf8_to_wchar_get_size(src) \
	MultiByteToWideChar(CP_UTF8, 0, src, -1, NULL, 0)
#define Edit_ReplaceSelU(hCtrl, str) ((void)SendMessageLU(hCtrl, EM_REPLACESEL, (WPARAM)FALSE, str))
#define ComboBox_AddStringU(hCtrl, str) ((int)(DWORD)SendMessageLU(hCtrl, CB_ADDSTRING, (WPARAM)FALSE, str))
#define ComboBox_InsertStringU(hCtrl, index, str) ((int)(DWORD)SendMessageLU(hCtrl, CB_INSERTSTRING, (WPARAM)index, str))
#define ComboBox_GetTextU(hCtrl, str, max_str) GetWindowTextU(hCtrl, str, max_str)
#define GetSaveFileNameU(p) GetOpenSaveFileNameU(p, TRUE)
#define GetOpenFileNameU(p) GetOpenSaveFileNameU(p, FALSE)
#define ListView_SetItemTextU(hwndLV,i,iSubItem_,pszText_) { LVITEMW _ms_wlvi; _ms_wlvi.iSubItem = iSubItem_; \
	_ms_wlvi.pszText = utf8_to_wchar(pszText_); \
	SNDMSG((hwndLV),LVM_SETITEMTEXTW,(WPARAM)(i),(LPARAM)&_ms_wlvi); sfree(_ms_wlvi.pszText);}

// Never ever use isdigit() or isspace(), etc. on UTF-8 strings!
// These calls take an int and char is signed so MS compilers will produce an assert error on anything that's > 0x80
#define isasciiU(c) isascii((unsigned char)(c))
#define iscntrlU(c) iscntrl((unsigned char)(c))
#define isdigitU(c) isdigit((unsigned char)(c))
#define isspaceU(c) isspace((unsigned char)(c))
#define isxdigitU(c) isxdigit((unsigned char)(c))
// NB: other issomething() calls are not implemented as they may require multibyte UTF-8 sequences to be converted

#define sfree(p) do {if (p != NULL) {free((void*)(p)); p = NULL;}} while(0)
#define wconvert(p)     wchar_t* w ## p = utf8_to_wchar(p)
#define walloc(p, size) wchar_t* w ## p = (p == NULL)?NULL:(wchar_t*)calloc(size, sizeof(wchar_t))
#define wfree(p) sfree(w ## p)

/*
 * Converts an UTF-16 string to UTF8 (allocates returned string)
 * Returns NULL on error
 */
static __inline char* wchar_to_utf8(const wchar_t* wstr)
{
	int size = 0;
	char* str = NULL;

	if (wstr == NULL)
		return NULL;

	// Convert the empty string too
	if (wstr[0] == 0)
		return (char*)calloc(1, 1);

	// Find out the size we need to allocate for our converted string
	size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
	if (size <= 1)	// An empty string would be size 1
		return NULL;

	if ((str = (char*)calloc(size, 1)) == NULL)
		return NULL;

	if (wchar_to_utf8_no_alloc(wstr, str, size) != size) {
		sfree(str);
		return NULL;
	}

	return str;
}

/*
 * Converts an UTF8 string to UTF-16 (allocates returned string)
 * Returns NULL on error
 */
static __inline wchar_t* utf8_to_wchar(const char* str)
{
	int size = 0;
	wchar_t* wstr = NULL;

	if (str == NULL)
		return NULL;

	// Convert the empty string too
	if (str[0] == 0)
		return (wchar_t*)calloc(1, sizeof(wchar_t));

	// Find out the size we need to allocate for our converted string
	size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
	if (size <= 1)	// An empty string would be size 1
		return NULL;

	if ((wstr = (wchar_t*)calloc(size, sizeof(wchar_t))) == NULL)
		return NULL;

	if (utf8_to_wchar_no_alloc(str, wstr, size) != size) {
		sfree(wstr);
		return NULL;
	}
	return wstr;
}

/*
* Converts a non NUL-terminated UTF-16 string of length wlen to NUL-terminated UTF8 (allocates returned string)
* Returns NULL on error
*/
static __inline char* wchar_len_to_utf8(const wchar_t* wstr, int wlen)
{
	int size = 0;
	char* str = NULL;

	// Find out the size we need to allocate for our converted string
	size = WideCharToMultiByte(CP_UTF8, 0, wstr, wlen, NULL, 0, NULL, NULL);
	if (size <= 1)	// An empty string would be size 1
		return NULL;

	if ((str = (char*)calloc(size, 1)) == NULL)
		return NULL;

	if (WideCharToMultiByte(CP_UTF8, 0, wstr, wlen, str, size, NULL, NULL) != size) {
		sfree(str);
		return NULL;
	}

	return str;
}

static __inline DWORD FormatMessageU(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId,
									 DWORD dwLanguageId, char* lpBuffer, DWORD nSize, va_list *Arguments)
{
	DWORD ret = 0, err = ERROR_INVALID_DATA;
	// Exclude support for the FORMAT_MESSAGE_ALLOCATE_BUFFER special case.
	if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}
	walloc(lpBuffer, nSize);
	if (wlpBuffer == NULL) {
		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return 0;
	}
	ret = FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, wlpBuffer, nSize, Arguments);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpBuffer, lpBuffer, nSize)) == 0)) {
		err = GetLastError();
		ret = 0;
	}
	// Coverity doesn't realise that we filtered out the FORMAT_MESSAGE_ALLOCATE_BUFFER case
	// coverity[leaked_storage]
	wfree(lpBuffer);
	SetLastError(err);
	return ret;
}

// SendMessage, with LPARAM as UTF-8 string
static __inline LRESULT SendMessageLU(HWND hWnd, UINT Msg, WPARAM wParam, const char* lParam)
{
	LRESULT ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lParam);
	ret = SendMessageW(hWnd, Msg, wParam, (LPARAM)wlParam);
	err = GetLastError();
	wfree(lParam);
	SetLastError(err);
	return ret;
}

static __inline int DrawTextExU(HDC hDC, LPCSTR lpchText, int nCount, LPRECT lpRect, UINT uFormat, LPDRAWTEXTPARAMS lpDTParams)
{
	int ret;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpchText);
	ret = DrawTextExW(hDC, wlpchText, nCount, lpRect, uFormat, lpDTParams);
	err = GetLastError();
	wfree(lpchText);
	SetLastError(err);
	return ret;
}

static __inline BOOL SHGetPathFromIDListU(LPCITEMIDLIST pidl, char* pszPath)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(pszPath, MAX_PATH);
	ret = SHGetPathFromIDListW(pidl, wpszPath);
	err = GetLastError();
	if ((ret) && (wchar_to_utf8_no_alloc(wpszPath, pszPath, MAX_PATH) == 0)) {
		err = GetLastError();
		ret = FALSE;
	}
	wfree(pszPath);
	SetLastError(err);
	return ret;
}

static __inline HWND CreateWindowU(char* lpClassName, char* lpWindowName,
	DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent,
	HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
{
	HWND ret = NULL;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpClassName);
	wconvert(lpWindowName);
	ret = CreateWindowW(wlpClassName, wlpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
	err = GetLastError();
	wfree(lpClassName);
	wfree(lpWindowName);
	SetLastError(err);
	return ret;
}

static __inline HWND CreateWindowExU(DWORD dwExStyle, char* lpClassName, char* lpWindowName,
	DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu,
	HINSTANCE hInstance, LPVOID lpParam)
{
	HWND ret = NULL;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpClassName);
	wconvert(lpWindowName);
	ret = CreateWindowExW(dwExStyle, wlpClassName, wlpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
	err = GetLastError();
	wfree(lpClassName);
	wfree(lpWindowName);
	SetLastError(err);
	return ret;
}

static __inline int MessageBoxU(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
	int ret;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpText);
	wconvert(lpCaption);
	ret = MessageBoxW(hWnd, wlpText, wlpCaption, uType);
	err = GetLastError();
	wfree(lpText);
	wfree(lpCaption);
	SetLastError(err);
	return ret;
}

static __inline int MessageBoxExU(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType, WORD wLanguageId)
{
	int ret;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpText);
	wconvert(lpCaption);
	ret = MessageBoxExW(hWnd, wlpText, wlpCaption, uType, wLanguageId);
	err = GetLastError();
	wfree(lpText);
	wfree(lpCaption);
	SetLastError(err);
	return ret;
}

static __inline int LoadStringU(HINSTANCE hInstance, UINT uID, LPSTR lpBuffer, int nBufferMax)
{
	int ret;
	DWORD err = ERROR_INVALID_DATA;
	if (nBufferMax == 0) {
		// read-only pointer to resource mode is not supported
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}
	// coverity[returned_null]
	walloc(lpBuffer, nBufferMax);
	ret = LoadStringW(hInstance, uID, wlpBuffer, nBufferMax);
	err = GetLastError();
	if ((ret > 0) && ((ret = wchar_to_utf8_no_alloc(wlpBuffer, lpBuffer, nBufferMax)) == 0)) {
		err = GetLastError();
	}
	wfree(lpBuffer);
	SetLastError(err);
	return ret;
}

static __inline HMODULE LoadLibraryU(LPCSTR lpFileName)
{
	HMODULE ret;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpFileName);
	ret = LoadLibraryW(wlpFileName);
	err = GetLastError();
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline HMODULE LoadLibraryExU(LPCSTR lpFileName, HANDLE hFile, DWORD dwFlags)
{
	HMODULE ret;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpFileName);
	ret = LoadLibraryExW(wlpFileName, hFile, dwFlags);
	err = GetLastError();
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline int DrawTextU(HDC hDC, LPCSTR lpText, int nCount, LPRECT lpRect, UINT uFormat)
{
	int ret;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpText);
	ret = DrawTextW(hDC, wlpText, nCount, lpRect, uFormat);
	err = GetLastError();
	wfree(lpText);
	SetLastError(err);
	return ret;
}

static __inline int GetWindowTextU(HWND hWnd, char* lpString, int nMaxCount)
{
	int ret = 0;
	DWORD err = ERROR_INVALID_PARAMETER;
	if (lpString == NULL || nMaxCount < 1)
		goto out;
	// Handle the empty string as GetWindowTextW() returns 0 then
	lpString[0] = 0;
	walloc(lpString, nMaxCount);
	ret = GetWindowTextW(hWnd, wlpString, nMaxCount);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpString, lpString, nMaxCount)) == 0))
		err = GetLastError();
	wfree(lpString);
	lpString[nMaxCount - 1] = 0;
out:
	SetLastError(err);
	return ret;
}

static __inline BOOL SetWindowTextU(HWND hWnd, const char* lpString)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpString);
	ret = SetWindowTextW(hWnd, wlpString);
	err = GetLastError();
	wfree(lpString);
	SetLastError(err);
	return ret;
}

static __inline int GetWindowTextLengthU(HWND hWnd)
{
	int ret = 0;
	DWORD err = ERROR_INVALID_DATA;
	wchar_t* wbuf = NULL;
	char* buf = NULL;

	ret = GetWindowTextLengthW(hWnd);
	err = GetLastError();
	if (ret == 0) goto out;
	wbuf = (wchar_t* )calloc(ret, sizeof(wchar_t));
	err = GetLastError();
	if (wbuf == NULL) {
		err = ERROR_OUTOFMEMORY; ret = 0; goto out;
	}
	ret = GetWindowTextW(hWnd, wbuf, ret);
	err = GetLastError();
	if (ret == 0) goto out;
	buf = wchar_to_utf8(wbuf);
	err = GetLastError();
	if (buf == NULL) {
		err = ERROR_OUTOFMEMORY; ret = 0; goto out;
	}
	ret = (int)strlen(buf) + 2;	// GetDlgItemText seems to add a character
	err = GetLastError();
out:
	sfree(wbuf);
	sfree(buf);
	SetLastError(err);
	return ret;
}

static __inline UINT GetDlgItemTextU(HWND hDlg, int nIDDlgItem, char* lpString, int nMaxCount)
{
	UINT ret = 0;
	DWORD err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(lpString, nMaxCount);
	ret = GetDlgItemTextW(hDlg, nIDDlgItem, wlpString, nMaxCount);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpString, lpString, nMaxCount)) == 0)) {
		err = GetLastError();
	}
	wfree(lpString);
	SetLastError(err);
	return ret;
}

static __inline BOOL SetDlgItemTextU(HWND hDlg, int nIDDlgItem, const char* lpString)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpString);
	ret = SetDlgItemTextW(hDlg, nIDDlgItem, wlpString);
	err = GetLastError();
	wfree(lpString);
	SetLastError(err);
	return ret;
}

static __inline BOOL InsertMenuU(HMENU hMenu, UINT uPosition, UINT uFlags, UINT_PTR uIDNewItem, const char* lpNewItem)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpNewItem);
	ret = InsertMenuW(hMenu, uPosition, uFlags, uIDNewItem, wlpNewItem);
	err = GetLastError();
	wfree(lpNewItem);
	SetLastError(err);
	return ret;
}

static __inline int ComboBox_GetLBTextU(HWND hCtrl, int index, char* lpString)
{
	int size;
	DWORD err = ERROR_INVALID_DATA;
	wchar_t* wlpString;
	if (lpString == NULL)
		return CB_ERR;
	size = (int)SendMessageW(hCtrl, CB_GETLBTEXTLEN, (WPARAM)index, (LPARAM)0);
	if (size < 0)
		return size;
	wlpString = (wchar_t*)calloc((size_t)size + 1, sizeof(wchar_t));
	size = (int)SendMessageW(hCtrl, CB_GETLBTEXT, (WPARAM)index, (LPARAM)wlpString);
	err = GetLastError();
	if (size > 0)
		wchar_to_utf8_no_alloc(wlpString, lpString, size+1);
	wfree(lpString);
	SetLastError(err);
	return size;
}

static __inline DWORD CharUpperBuffU(char* lpString, DWORD len)
{
	DWORD ret;
	wchar_t *wlpString = (wchar_t*)calloc(len, sizeof(wchar_t));
	if (wlpString == NULL)
		return 0;
	utf8_to_wchar_no_alloc(lpString, wlpString, len);
	ret = CharUpperBuffW(wlpString, len);
	wchar_to_utf8_no_alloc(wlpString, lpString, len);
	free(wlpString);
	return ret;
}

static __inline HANDLE CreateFileU(const char* lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
								   LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
								   DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
	HANDLE ret = INVALID_HANDLE_VALUE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpFileName);
	ret = CreateFileW(wlpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
		dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
	err = GetLastError();
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline BOOL CreateDirectoryU(const char* lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpPathName);
	ret = CreateDirectoryW(wlpPathName, lpSecurityAttributes);
	err = GetLastError();
	wfree(lpPathName);
	SetLastError(err);
	return ret;
}

static __inline BOOL CopyFileU(const char* lpExistingFileName, const char* lpNewFileName, BOOL bFailIfExists)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpExistingFileName);
	wconvert(lpNewFileName);
	ret = CopyFileW(wlpExistingFileName, wlpNewFileName, bFailIfExists);
	err = GetLastError();
	wfree(lpExistingFileName);
	wfree(lpNewFileName);
	SetLastError(err);
	return ret;
}

static __inline BOOL DeleteFileU(const char* lpFileName)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpFileName);
	ret = DeleteFileW(wlpFileName);
	err = GetLastError();
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline BOOL PathFileExistsU(char* szPath)
{
	BOOL ret;
	wconvert(szPath);
	ret = PathFileExistsW(wszPath);
	wfree(szPath);
	return ret;
}

static __inline int PathGetDriveNumberU(char* lpPath)
{
	int ret = 0;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpPath);
	ret = PathGetDriveNumberW(wlpPath);
	err = GetLastError();
	wfree(lpPath);
	SetLastError(err);
	return ret;
}

// This one is tricky since we can't blindly convert a
// UTF-16 position to a UTF-8 one. So we do it manually.
static __inline const char* PathFindFileNameU(const char* szPath)
{
	size_t i;
	if (szPath == NULL)
		return NULL;
	for (i = strlen(szPath); i != 0; i--) {
		if ((szPath[i] == '/') || (szPath[i] == '\\')) {
			i++;
			break;
		}
	}
	return &szPath[i];
}

static __inline char* PathCombineU(char* lpDest, char* lpDir, char* lpFile)
{
	wchar_t* wret = NULL;
	DWORD err = ERROR_INVALID_DATA;
	wchar_t wlpDest[MAX_PATH];
	wconvert(lpDir);
	wconvert(lpFile);
	wret = PathCombineW(wlpDest, wlpDir, wlpFile);
	err = GetLastError();
	wfree(lpDir);
	wfree(lpFile);
	if (wret == NULL)
		return NULL;
	wchar_to_utf8_no_alloc(wlpDest, lpDest, MAX_PATH);
	SetLastError(err);
	return lpDest;
}

static __inline HANDLE FindFirstFileU(char* lpFileName, LPWIN32_FIND_DATAA lpFindFileData)
{
	HANDLE ret = INVALID_HANDLE_VALUE;
	WIN32_FIND_DATAW wFindFileData = { 0 };
	wconvert(lpFileName);
	ret = FindFirstFileW(wlpFileName, &wFindFileData);
	if (ret != INVALID_HANDLE_VALUE) {
		memcpy(lpFindFileData, &wFindFileData, offsetof(WIN32_FIND_DATAW, cFileName));
		wchar_to_utf8_no_alloc(wFindFileData.cFileName, lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName));
		wchar_to_utf8_no_alloc(wFindFileData.cAlternateFileName, lpFindFileData->cAlternateFileName, sizeof(lpFindFileData->cAlternateFileName));
	}
	wfree(lpFileName);
	return ret;
}

static __inline BOOL FindNextFileU(HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData)
{
	BOOL ret = FALSE;
	WIN32_FIND_DATAW wFindFileData = { 0 };
	ret = FindNextFileW(hFindFile, &wFindFileData);
	if (ret) {
		memcpy(lpFindFileData, &wFindFileData, offsetof(WIN32_FIND_DATAW, cFileName));
		wchar_to_utf8_no_alloc(wFindFileData.cFileName, lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName));
		wchar_to_utf8_no_alloc(wFindFileData.cAlternateFileName, lpFindFileData->cAlternateFileName, sizeof(lpFindFileData->cAlternateFileName));
	}
	return ret;
}

// This function differs from regular GetTextExtentPoint in that it uses a zero terminated string
static __inline BOOL GetTextExtentPointU(HDC hdc, const char* lpString, LPSIZE lpSize)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpString);
	if (wlpString == NULL)
		return FALSE;
	ret = GetTextExtentPoint32W(hdc, wlpString, (int)wcslen(wlpString), lpSize);
	err = GetLastError();
	wfree(lpString);
	SetLastError(err);
	return ret;
}

// Gets the directory where the executable resises, through GetModuleFileName()
static __inline DWORD GetAppDirectoryU(DWORD nBufferLength, char* lpBuffer)
{
	DWORD i, ret = 0, err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(lpBuffer, nBufferLength);
	if (wlpBuffer == NULL) {
		SetLastError(ERROR_OUTOFMEMORY);
		return 0;
	}
	ret = GetModuleFileNameW(NULL, wlpBuffer, nBufferLength);
	err = GetLastError();
	if (ret > 0) {
		for (i = ret - 1; i > 0; i--) {
			if (wlpBuffer[i] == L'\\') {
				wlpBuffer[i] = 0;
				break;
			}
		}
	}
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpBuffer, lpBuffer, nBufferLength)) == 0)) {
		err = GetLastError();
	}
	wfree(lpBuffer);
	SetLastError(err);
	return ret;
}

static __inline UINT GetSystemDirectoryU(char* lpBuffer, UINT uSize)
{
	UINT ret = 0, err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(lpBuffer, uSize);
	ret = GetSystemDirectoryW(wlpBuffer, uSize);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpBuffer, lpBuffer, uSize)) == 0)) {
		err = GetLastError();
	}
	wfree(lpBuffer);
	SetLastError(err);
	return ret;
}

static __inline UINT GetSystemWindowsDirectoryU(char* lpBuffer, UINT uSize)
{
	UINT ret = 0, err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(lpBuffer, uSize);
	ret = GetSystemWindowsDirectoryW(wlpBuffer, uSize);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpBuffer, lpBuffer, uSize)) == 0)) {
		err = GetLastError();
	}
	wfree(lpBuffer);
	SetLastError(err);
	return ret;
}

static __inline BOOL SHGetSpecialFolderPathU(HWND hwnd, char* pszPath, int csidl, BOOL fCreate)
{
	BOOL ret;
	DWORD err = ERROR_INVALID_DATA;
	// pszPath is at least MAX_PATH characters in size
	WCHAR wpszPath[MAX_PATH] = { 0 };
	ret = SHGetSpecialFolderPathW(hwnd, wpszPath, csidl, fCreate);
	err = GetLastError();
	wchar_to_utf8_no_alloc(wpszPath, pszPath, MAX_PATH);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetTempPathU(DWORD nBufferLength, char* lpBuffer)
{
	DWORD ret = 0, err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(lpBuffer, nBufferLength);
	ret = GetTempPathW(nBufferLength, wlpBuffer);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpBuffer, lpBuffer, nBufferLength)) == 0)) {
		err = GetLastError();
	}
	wfree(lpBuffer);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetTempFileNameU(char* lpPathName, char* lpPrefixString, UINT uUnique, char* lpTempFileName)
{
	DWORD ret = 0, err = ERROR_INVALID_DATA;
	wconvert(lpPathName);
	wconvert(lpPrefixString);
	// coverity[returned_null]
	walloc(lpTempFileName, MAX_PATH);
	ret = GetTempFileNameW(wlpPathName, wlpPrefixString, uUnique, wlpTempFileName);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpTempFileName, lpTempFileName, MAX_PATH)) == 0)) {
		err = GetLastError();
	}
	wfree(lpTempFileName);
	wfree(lpPrefixString);
	wfree(lpPathName);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetTempDirNameU(char* lpPathName, char* lpPrefixString, UINT uUnique, char* lpTempDirName)
{
	DWORD ret = GetTempFileNameU(lpPathName, lpPrefixString, uUnique, lpTempDirName);
	if (ret != 0) {
		DeleteFileU(lpTempDirName);
		if (!CreateDirectoryU(lpTempDirName, NULL))
			ret = 0;
	}
	return ret;
}

static __inline DWORD GetModuleFileNameU(HMODULE hModule, char* lpFilename, DWORD nSize)
{
	DWORD ret = 0, err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(lpFilename, nSize);
	ret = GetModuleFileNameW(hModule, wlpFilename, nSize);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpFilename, lpFilename, nSize)) == 0)) {
		err = GetLastError();
	}
	wfree(lpFilename);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetModuleFileNameExU(HANDLE hProcess, HMODULE hModule, char* lpFilename, DWORD nSize)
{
	DWORD ret = 0, err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(lpFilename, nSize);
	ret = GetModuleFileNameExW(hProcess, hModule, wlpFilename, nSize);
	err = GetLastError();
	if ((ret != 0)
		&& ((ret = wchar_to_utf8_no_alloc(wlpFilename, lpFilename, nSize)) == 0)) {
		err = GetLastError();
	}
	wfree(lpFilename);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetFinalPathNameByHandleU(HANDLE hFile, char* lpszFilePath, DWORD cchFilePath, DWORD dwFlags)
{
	DWORD ret = 0, err = ERROR_INVALID_DATA;
	walloc(lpszFilePath, cchFilePath);
	ret = GetFinalPathNameByHandleW(hFile, wlpszFilePath, cchFilePath, dwFlags);
	err = GetLastError();
	if ((ret != 0)
		&& ((ret = wchar_to_utf8_no_alloc(wlpszFilePath, lpszFilePath, cchFilePath)) == 0)) {
		err = GetLastError();
	}
	wfree(lpszFilePath);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetFileVersionInfoSizeU(const char* lpFileName, LPDWORD lpdwHandle)
{
	DWORD ret = 0, err = ERROR_INVALID_DATA;
	wconvert(lpFileName);
	ret = GetFileVersionInfoSizeW(wlpFileName, lpdwHandle);
	err = GetLastError();
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline BOOL GetFileVersionInfoU(const char* lpFileName, DWORD dwHandle, DWORD dwLen, LPVOID lpData)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpFileName);
	if (dwHandle != 0)
		SetLastError(ERROR_INVALID_PARAMETER);
	else
		ret = GetFileVersionInfoW(wlpFileName, dwHandle, dwLen, lpData);
	err = GetLastError();
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetFullPathNameU(const char* lpFileName, DWORD nBufferLength, char* lpBuffer, char** lpFilePart)
{
	DWORD ret = 0, err = ERROR_INVALID_DATA;
	wchar_t* wlpFilePart;
	wconvert(lpFileName);
	// coverity[returned_null]
	walloc(lpBuffer, nBufferLength);

	// lpFilePart is not supported
	if (lpFilePart != NULL) goto out;

	ret = GetFullPathNameW(wlpFileName, nBufferLength, wlpBuffer, &wlpFilePart);
	err = GetLastError();
	if ((ret != 0) && ((ret = wchar_to_utf8_no_alloc(wlpBuffer, lpBuffer, nBufferLength)) == 0)) {
		err = GetLastError();
	}

out:
	wfree(lpBuffer);
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetFileAttributesU(const char* lpFileName)
{
	DWORD ret = 0xFFFFFFFF, err = ERROR_INVALID_DATA;
	wconvert(lpFileName);
	// Unlike Microsoft's version, ours doesn't fail if the string is quoted
	if ((wlpFileName[0] == L'"') && (wlpFileName[wcslen(wlpFileName) - 1] == L'"')) {
		wlpFileName[wcslen(wlpFileName) - 1] = 0;
		ret = GetFileAttributesW(&wlpFileName[1]);
	} else {
		ret = GetFileAttributesW(wlpFileName);
	}
	err = GetLastError();
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline BOOL SetFileAttributesU(const char* lpFileName, DWORD dwFileAttributes)
{
	BOOL ret = FALSE, err = ERROR_INVALID_DATA;
	wconvert(lpFileName);
	// Unlike Microsoft's version, ours doesn't fail if the string is quoted
	if ((wlpFileName[0] == L'"') && (wlpFileName[wcslen(wlpFileName) - 1] == L'"')) {
		wlpFileName[wcslen(wlpFileName) - 1] = 0;
		ret = SetFileAttributesW(&wlpFileName[1], dwFileAttributes);
	} else {
		ret = SetFileAttributesW(wlpFileName, dwFileAttributes);
	}
	err = GetLastError();
	wfree(lpFileName);
	SetLastError(err);
	return ret;
}

static __inline DWORD GetNamedSecurityInfoU(const char* lpObjectName, SE_OBJECT_TYPE ObjectType,
	SECURITY_INFORMATION SecurityInfo, PSID* ppsidOwner, PSID* ppsidGroup, PACL* ppDacl,
	PACL* ppSacl, PSECURITY_DESCRIPTOR* ppSecurityDescriptor)
{
	DWORD ret;
	wconvert(lpObjectName);
	ret = GetNamedSecurityInfoW(wlpObjectName, ObjectType, SecurityInfo, ppsidOwner, ppsidGroup,
		ppDacl, ppSacl, ppSecurityDescriptor);
	wfree(lpObjectName);
	return ret;
}

static __inline DWORD SetNamedSecurityInfoU(const char* lpObjectName, SE_OBJECT_TYPE ObjectType,
	SECURITY_INFORMATION SecurityInfo, PSID psidOwner, PSID psidGroup, PACL pDacl, PACL pSacl)
{
	DWORD ret;
	wconvert(lpObjectName);
	ret = SetNamedSecurityInfoW(wlpObjectName, ObjectType, SecurityInfo, psidOwner, psidGroup, pDacl, pSacl);
	wfree(lpObjectName);
	return ret;
}

static __inline int SHCreateDirectoryExU(HWND hwnd, const char* pszPath, SECURITY_ATTRIBUTES *psa)
{
	int ret = ERROR_INVALID_DATA;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(pszPath);
	ret = SHCreateDirectoryExW(hwnd, wpszPath, psa);
	err = GetLastError();
	wfree(pszPath);
	SetLastError(err);
	return ret;
}

static __inline int SHDeleteDirectoryExU(HWND hwnd, const char* pszPath, FILEOP_FLAGS fFlags)
{
	int ret;
	// String needs to be double NULL terminated, so we just use the length of the UTF-8 string
	// which is always expected to be larger than our UTF-16 one, and add 2 chars for good measure.
	size_t wpszPath_len = (pszPath == NULL) ? 0 : strlen(pszPath) + 2;
	// coverity[returned_null]
	walloc(pszPath, wpszPath_len);
	SHFILEOPSTRUCTW shfo = { hwnd, FO_DELETE, wpszPath, NULL, fFlags, FALSE, NULL, NULL };
	utf8_to_wchar_no_alloc(pszPath, wpszPath, (int)wpszPath_len);
	// FOF_SILENT | FOF_NOERRORUI | FOF_NOCONFIRMATION,
	ret = SHFileOperationW(&shfo);
	wfree(pszPath);
	return ret;
}

static __inline BOOL ShellExecuteExU(SHELLEXECUTEINFOA* lpExecInfo)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	SHELLEXECUTEINFOW wExecInfo;

	// Because we're lazy, we'll assume that the A and W structs inherently have the same size
	if (lpExecInfo->cbSize != sizeof(SHELLEXECUTEINFOW)) {
		SetLastError(ERROR_BAD_LENGTH); return FALSE;
	}
	memcpy(&wExecInfo, lpExecInfo, lpExecInfo->cbSize);
	wExecInfo.lpVerb = utf8_to_wchar(lpExecInfo->lpVerb);
	wExecInfo.lpFile = utf8_to_wchar(lpExecInfo->lpFile);
	wExecInfo.lpParameters = utf8_to_wchar(lpExecInfo->lpParameters);
	wExecInfo.lpDirectory = utf8_to_wchar(lpExecInfo->lpDirectory);
	if (wExecInfo.fMask & SEE_MASK_CLASSNAME) {
		wExecInfo.lpClass = utf8_to_wchar(lpExecInfo->lpClass);
	} else {
		wExecInfo.lpClass = NULL;
	}
	ret = ShellExecuteExW(&wExecInfo);
	err = GetLastError();
	// Copy the returned values back
	lpExecInfo->hInstApp = wExecInfo.hInstApp;
	lpExecInfo->hProcess = wExecInfo.hProcess;
	sfree(wExecInfo.lpVerb);
	sfree(wExecInfo.lpFile);
	sfree(wExecInfo.lpParameters);
	sfree(wExecInfo.lpDirectory);
	sfree(wExecInfo.lpClass);
	SetLastError(err);
	return ret;
}

// Doesn't support LPSTARTUPINFOEX struct
static __inline BOOL CreateProcessU(const char* lpApplicationName, const char* lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes,
									LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags,
									LPVOID lpEnvironment, const char* lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
									LPPROCESS_INFORMATION lpProcessInformation)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	STARTUPINFOW wStartupInfo;
	wconvert(lpApplicationName);
	wconvert(lpCommandLine);
	wconvert(lpCurrentDirectory);

	// Because we're lazy, we'll assume that the A and W structs inherently have the same size
	// Also prevents the use of STARTUPINFOEX
	if (lpStartupInfo->cb != sizeof(STARTUPINFOW)) {
		err = ERROR_BAD_LENGTH; goto out;
	}
	memcpy(&wStartupInfo, lpStartupInfo, lpStartupInfo->cb);
	wStartupInfo.lpDesktop = utf8_to_wchar(lpStartupInfo->lpDesktop);
	wStartupInfo.lpTitle = utf8_to_wchar(lpStartupInfo->lpTitle);
	ret = CreateProcessW(wlpApplicationName, wlpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles,
		dwCreationFlags, lpEnvironment, wlpCurrentDirectory, &wStartupInfo, lpProcessInformation);
	err = GetLastError();
	sfree(wStartupInfo.lpDesktop);
	sfree(wStartupInfo.lpTitle);
out:
	wfree(lpApplicationName);
	wfree(lpCommandLine);
	wfree(lpCurrentDirectory);
	SetLastError(err);
	return ret;
}

// NOTE: when used, nFileOffset & nFileExtension MUST be provided
// in number of Unicode characters, NOT number of UTF-8 bytes
static __inline BOOL WINAPI GetOpenSaveFileNameU(LPOPENFILENAMEA lpofn, BOOL save)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	size_t i, len;
	OPENFILENAMEW wofn;
	memset(&wofn, 0, sizeof(wofn));
	wofn.lStructSize = sizeof(wofn);
	wofn.hwndOwner = lpofn->hwndOwner;
	wofn.hInstance = lpofn->hInstance;

	// No support for custom filters
	if (lpofn->lpstrCustomFilter != NULL) goto out;

	// Count on Microsoft to use an moronic scheme for filters
	// that relies on NULL separators and double NULL terminators
	if (lpofn->lpstrFilter != NULL) {
		// Replace the NULLs by something that can be converted
		for (i=0; ; i++) {
			if (lpofn->lpstrFilter[i] == 0) {
				((char*)lpofn->lpstrFilter)[i] = '\r';
				if (lpofn->lpstrFilter[i+1] == 0) {
					break;
				}
			}
		}
		wofn.lpstrFilter = utf8_to_wchar(lpofn->lpstrFilter);
		// And revert
		len = wcslen(wofn.lpstrFilter);	// don't use in the loop as it would be reevaluated
		for (i=0; i<len; i++) {
			if (wofn.lpstrFilter[i] == '\r') {
				((wchar_t*)wofn.lpstrFilter)[i] = 0;
			}
		}
		len = strlen(lpofn->lpstrFilter);
		for (i=0; i<len; i++) {
			if (lpofn->lpstrFilter[i] == '\r') {
				((char*)lpofn->lpstrFilter)[i] = 0;
			}
		}
	} else {
		wofn.lpstrFilter = NULL;
	}
	wofn.nMaxCustFilter = lpofn->nMaxCustFilter;
	wofn.nFilterIndex = lpofn->nFilterIndex;
	wofn.lpstrFile = (LPWSTR)calloc(lpofn->nMaxFile, sizeof(wchar_t));
	utf8_to_wchar_no_alloc(lpofn->lpstrFile, wofn.lpstrFile, lpofn->nMaxFile);
	wofn.nMaxFile = lpofn->nMaxFile;
	wofn.lpstrFileTitle = (LPWSTR)calloc(lpofn->nMaxFileTitle, sizeof(wchar_t));
	utf8_to_wchar_no_alloc(lpofn->lpstrFileTitle, wofn.lpstrFileTitle, lpofn->nMaxFileTitle);
	wofn.nMaxFileTitle = lpofn->nMaxFileTitle;
	wofn.lpstrInitialDir = utf8_to_wchar(lpofn->lpstrInitialDir);
	wofn.lpstrTitle = utf8_to_wchar(lpofn->lpstrTitle);
	wofn.Flags = lpofn->Flags;
	wofn.nFileOffset = lpofn->nFileOffset;
	wofn.nFileExtension = lpofn->nFileExtension;
	wofn.lpstrDefExt = utf8_to_wchar(lpofn->lpstrDefExt);
	wofn.lCustData = lpofn->lCustData;
	wofn.lpfnHook = lpofn->lpfnHook;
	wofn.lpTemplateName = utf8_to_wchar(lpofn->lpTemplateName);
	wofn.pvReserved = lpofn->pvReserved;
	wofn.dwReserved = lpofn->dwReserved;
	wofn.FlagsEx = lpofn->FlagsEx;

	if (save) {
		ret = GetSaveFileNameW(&wofn);
	} else {
		ret = GetOpenFileNameW(&wofn);
	}
	err = GetLastError();
	if ( (ret)
	  && ( (wchar_to_utf8_no_alloc(wofn.lpstrFile, lpofn->lpstrFile, lpofn->nMaxFile) == 0)
	    || (wchar_to_utf8_no_alloc(wofn.lpstrFileTitle, lpofn->lpstrFileTitle, lpofn->nMaxFileTitle) == 0) ) ) {
		err = GetLastError();
		ret = FALSE;
	}
out:
	sfree(wofn.lpstrDefExt);
	sfree(wofn.lpstrFile);
	sfree(wofn.lpstrFileTitle);
	sfree(wofn.lpstrFilter);
	sfree(wofn.lpstrInitialDir);
	sfree(wofn.lpstrTitle);
	sfree(wofn.lpTemplateName);
	SetLastError(err);
	return ret;
}

extern BOOL WINAPI UpdateDriverForPlugAndPlayDevicesW(HWND hwndParent, LPCWSTR HardwareId,
	LPCWSTR FullInfPath, DWORD InstallFlags, PBOOL bRebootRequired);

static __inline BOOL UpdateDriverForPlugAndPlayDevicesU(HWND hwndParent, const char* HardwareId, const char* FullInfPath,
														DWORD InstallFlags, PBOOL bRebootRequired)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(HardwareId);
	wconvert(FullInfPath);
	ret = UpdateDriverForPlugAndPlayDevicesW(hwndParent, wHardwareId, wFullInfPath, InstallFlags, bRebootRequired);
	err = GetLastError();
	wfree(HardwareId);
	wfree(FullInfPath);
	SetLastError(err);
	return ret;
}

static __inline BOOL SetupCopyOEMInfU(const char* SourceInfFileName, const char* OEMSourceMediaLocation, DWORD OEMSourceMediaType,
									  DWORD CopyStyle, char* DestinationInfFileName, DWORD DestinationInfFileNameSize,
									  PDWORD RequiredSize, PTSTR DestinationInfFileNameComponent)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(SourceInfFileName);
	wconvert(OEMSourceMediaLocation);
	// coverity[returned_null]
	walloc(DestinationInfFileName, DestinationInfFileNameSize);

	// DestinationInfFileNameComponent is not supported
	if (DestinationInfFileNameComponent != NULL) goto out;

	ret = SetupCopyOEMInfW(wSourceInfFileName, wOEMSourceMediaLocation, OEMSourceMediaType, CopyStyle,
		wDestinationInfFileName, DestinationInfFileNameSize, RequiredSize, NULL);
	err = GetLastError();
	if ((ret != FALSE) && ((ret = wchar_to_utf8_no_alloc(wDestinationInfFileName, DestinationInfFileName, DestinationInfFileNameSize)) == 0)) {
		err = GetLastError();
	}
out:
	wfree(SourceInfFileName);
	wfree(OEMSourceMediaLocation);
	wfree(DestinationInfFileName);
	SetLastError(err);
	return ret;
}

static __inline int _chdirU(const char *dirname)
{
	int ret;
	wconvert(dirname);
	ret = _wchdir(wdirname);
	wfree(dirname);
	return ret;
}

#if defined(_WIN32_WINNT) && (_WIN32_WINNT <= 0x501)
static __inline FILE* fopenU(const char* filename, const char* mode)
{
	FILE* ret = NULL;
	wconvert(filename);
	wconvert(mode);
	ret = _wfopen(wfilename, wmode);
	wfree(filename);
	wfree(mode);
	return ret;
}

static __inline int _openU(const char *filename, int oflag, int pmode)
{
	int ret = -1;
	wconvert(filename);
	ret = _wopen(wfilename, oflag, pmode);
	wfree(filename);
	return ret;
}
#else
static __inline FILE* fopenU(const char* filename, const char* mode)
{
	FILE* ret = NULL;
	wconvert(filename);
	wconvert(mode);
	_wfopen_s(&ret, wfilename, wmode);
	wfree(filename);
	wfree(mode);
	return ret;
}

static __inline int _openU(const char *filename, int oflag , int pmode)
{
	int ret = -1;
	int shflag = _SH_DENYNO;
	wconvert(filename);
	// Try to match the share flag to the oflag
	if ((oflag & 0x03) == _O_RDONLY)
		shflag = _SH_DENYWR;
	else if ((oflag & 0x03) == _O_WRONLY)
		shflag = _SH_DENYRD;
	_wsopen_s(&ret, wfilename, oflag, shflag, pmode);
	wfree(filename);
	return ret;
}
#endif

static __inline int _unlinkU(const char* path)
{
	int ret;
	wconvert(path);
	ret = _wunlink(wpath);
	wfree(path);
	return ret;
}

static __inline int _stat64U(const char *path, struct __stat64 *buffer)
{
	int ret;
	wconvert(path);
	ret = _wstat64(wpath, buffer);
	wfree(path);
	return ret;
}

static __inline int _accessU(const char* path, int mode)
{
	int ret;
	wconvert(path);
	ret = _waccess(wpath, mode);
	wfree(path);
	return ret;
}

static __inline const char* _filenameU(const char* path)
{
	int i;
	if (path == NULL)
		return NULL;
	for (i = (int)strlen(path) - 1; i >= 0; i--)
		if ((path[i] == '/') || (path[i] == '\\'))
			return &path[i + 1];
	return path;
}

static __inline uint64_t _filesizeU(const char* path)
{
	struct __stat64 stat64 = { 0 };
	_stat64U(path, &stat64);
	return stat64.st_size;
}

// returned UTF-8 string must be freed
static __inline char* getenvU(const char* varname)
{
	wconvert(varname);
	char* ret = NULL;
	wchar_t* wbuf = NULL;
	// _wgetenv() is *BROKEN* in MS compilers => use GetEnvironmentVariableW()
	DWORD dwSize = GetEnvironmentVariableW(wvarname, wbuf, 0);
	wbuf = (wchar_t*)calloc(dwSize, sizeof(wchar_t));
	if (wbuf == NULL) {
		wfree(varname);
		return NULL;
	}
	dwSize = GetEnvironmentVariableW(wvarname, wbuf, dwSize);
	if (dwSize != 0)
		ret = wchar_to_utf8(wbuf);
	free(wbuf);
	wfree(varname);
	return ret;
}

static __inline int _mkdirU(const char* dirname)
{
	wconvert(dirname);
	int ret;
	ret = _wmkdir(wdirname);
	wfree(dirname);
	return ret;
}

// This version of _mkdirU creates all needed directories along the way
static __inline int _mkdirExU(const char* dirname)
{
	int ret = -1, trailing_slash = -1;
	size_t i, len;
	wconvert(dirname);
	len = wcslen(wdirname);
	while (trailing_slash && (len > 0)) {
		if ((wdirname[len - 1] == '\\') || (wdirname[len - 1] == '/'))
			wdirname[--len] = 0;
		else
			trailing_slash = 0;
	}
	for (i = 0; i < len; i++)
		if ((wdirname[i] == '\\') || (wdirname[i] == '/'))
			wdirname[i] = 0;
	for (i = 0; i < len; ) {
		if ((_wmkdir(wdirname) < 0) && (errno != EEXIST) && (errno != EACCES))
			goto out;
		i = wcslen(wdirname);
		wdirname[i] = '\\';
	}
	ret = 0;
out:
	wfree(dirname);
	return ret;
}

static __inline int _rmdirU(const char* dirname)
{
	wconvert(dirname);
	int ret;
	ret = _wrmdir(wdirname);
	wfree(dirname);
	return ret;
}

static __inline BOOL MoveFileU(const char* lpExistingFileName, const char* lpNewFileName)
{
	wconvert(lpExistingFileName);
	wconvert(lpNewFileName);
	BOOL ret = MoveFileW(wlpExistingFileName, wlpNewFileName);
	wfree(lpNewFileName);
	wfree(lpExistingFileName);
	return ret;
}

static __inline BOOL MoveFileExU(const char* lpExistingFileName, const char* lpNewFileName, DWORD dwFlags)
{
	wconvert(lpExistingFileName);
	wconvert(lpNewFileName);
	BOOL ret = MoveFileExW(wlpExistingFileName, wlpNewFileName, dwFlags);
	wfree(lpNewFileName);
	wfree(lpExistingFileName);
	return ret;
}

static __inline BOOL CreateSymbolicLinkU(const char* lpSymlinkFileName, const char* lpTargetFileName, DWORD dwFlags)
{
	wconvert(lpSymlinkFileName);
	wconvert(lpTargetFileName);
	BOOL ret = CreateSymbolicLinkW(wlpSymlinkFileName, wlpTargetFileName, dwFlags);
	wfree(lpTargetFileName);
	wfree(lpSymlinkFileName);
	return ret;
}

// The following expects PropertyBuffer to contain a single Unicode string
static __inline BOOL SetupDiGetDeviceRegistryPropertyU(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData,
	DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	// coverity[returned_null]
	walloc(PropertyBuffer, PropertyBufferSize);

	ret = SetupDiGetDeviceRegistryPropertyW(DeviceInfoSet, DeviceInfoData, Property,
		PropertyRegDataType, (PBYTE)wPropertyBuffer, PropertyBufferSize, RequiredSize);
	err = GetLastError();
	if ((ret != 0) && (wchar_to_utf8_no_alloc(wPropertyBuffer,
		(char*)(uintptr_t)PropertyBuffer, PropertyBufferSize) == 0)) {
		err = GetLastError();
		ret = FALSE;
	}
	wfree(PropertyBuffer);
	SetLastError(err);
	return ret;
}

// NB: This does not support the ERROR_INSUFFICIENT_BUFFER dance to retrieve the required buffer size
static __inline BOOL GetUserNameU(LPSTR lpBuffer, LPDWORD pcbBuffer)
{
	BOOL ret;
	DWORD err, size;
	if (lpBuffer == NULL || pcbBuffer == NULL) {
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}
	size = *pcbBuffer;
	// coverity[returned_null]
	walloc(lpBuffer, size);
	ret = GetUserNameW(wlpBuffer, &size);
	err = GetLastError();
	if (ret) {
		*pcbBuffer = (DWORD)wchar_to_utf8_no_alloc(wlpBuffer, lpBuffer, size);
		if (*pcbBuffer == 0)
			err = GetLastError();
		else
			// Reported size includes the NUL terminator
			(*pcbBuffer)++;
	}
	wfree(lpBuffer);
	SetLastError(err);
	return ret;
}

static __inline BOOL GetVolumeInformationU(LPCSTR lpRootPathName, LPSTR lpVolumeNameBuffer,
	DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, LPDWORD lpMaximumComponentLength,
	LPDWORD lpFileSystemFlags, LPSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize)
{
	BOOL ret = FALSE;
	DWORD err = ERROR_INVALID_DATA;
	wconvert(lpRootPathName);
	// coverity[returned_null]
	walloc(lpVolumeNameBuffer, nVolumeNameSize);
	// coverity[returned_null]
	walloc(lpFileSystemNameBuffer, nFileSystemNameSize);

	ret = GetVolumeInformationW(wlpRootPathName, wlpVolumeNameBuffer, nVolumeNameSize,
		lpVolumeSerialNumber, lpMaximumComponentLength, lpFileSystemFlags,
		wlpFileSystemNameBuffer, nFileSystemNameSize);
	err = GetLastError();
	if (ret) {
		if ( ((lpVolumeNameBuffer != NULL) && (wchar_to_utf8_no_alloc(wlpVolumeNameBuffer,
			lpVolumeNameBuffer, nVolumeNameSize) == 0))
		  || ((lpFileSystemNameBuffer != NULL) && (wchar_to_utf8_no_alloc(wlpFileSystemNameBuffer,
			lpFileSystemNameBuffer, nFileSystemNameSize) == 0)) ) {
			err = GetLastError();
			ret = FALSE;
		}
	}
	wfree(lpVolumeNameBuffer);
	wfree(lpFileSystemNameBuffer);
	wfree(lpRootPathName);
	SetLastError(err);
	return ret;
}

#ifdef __cplusplus
}
#endif
