/*---------------------------------------------------------------------------*/
//       Author : hiyohiyo
//         Mail : hiyohiyo@crystalmark.info
//          Web : https://crystalmark.info/
//      License : MIT License
/*---------------------------------------------------------------------------*/

#include "stdafx.h"
#include "UtilityFx.h"

#include <io.h>
#pragma comment(lib,"version.lib")

////------------------------------------------------
//   Debug
////------------------------------------------------

static const DWORD DEBUG_MODE_NONE = 0;
static const DWORD DEBUG_MODE_LOG = 1;
static const DWORD DEBUG_MODE_MESSAGE = 2;

static DWORD debugMode = DEBUG_MODE_NONE;

void SetDebugMode(DWORD mode)
{
	if (mode <= DEBUG_MODE_MESSAGE)
	{
		debugMode = mode;
	}
	else
	{
		debugMode = DEBUG_MODE_NONE;
	}
}

void DebugPrint(CString cstr)
{
	static BOOL flag = TRUE;
	static TCHAR file[MAX_PATH] = _T("");
	static DWORD first = (DWORD)GetTickCountFx();
	CString output;

	output.Format(_T("%08d "), (DWORD)GetTickCountFx() - first);
	output += cstr;
	output.Append(_T("\n"));
	output.Replace(_T("\r"), _T(""));

	if (flag)
	{
		TCHAR* ptrEnd;
		::GetModuleFileName(NULL, file, MAX_PATH);
		if ((ptrEnd = _tcsrchr(file, '.')) != NULL)
		{
			*ptrEnd = '\0';
#if _MSC_VER <= 1310
			_tcscat(file, _T(".log"));
#else
			_tcscat_s(file, MAX_PATH, _T(".log"));
#endif		
		}
		DeleteFile(file);
		flag = FALSE;
	}

	if (debugMode == DEBUG_MODE_NONE)
	{
		return;
	}

	FILE* fp = NULL;
#if _MSC_VER <= 1310
	fp = _tfopen(file, _T("ac"));
#else
	_tfopen_s(&fp, file, _T("ac"));
#endif
	
	if (fp != NULL)
	{
		_ftprintf(fp, _T("%s"), (LPCTSTR)output);
		fflush(fp);
		fclose(fp);
	}

	if (debugMode == DEBUG_MODE_MESSAGE)
	{
		AfxMessageBox(output);
	}
}

////------------------------------------------------
//   File Information
////------------------------------------------------

int GetFileVersion(const TCHAR* file, TCHAR* version)
{
	ULONG reserved = 0;
	VS_FIXEDFILEINFO vffi = { 0 };
	TCHAR* buf = NULL;
	int  Locale = 0;
	TCHAR str[256] = { 0 };

	UINT size = GetFileVersionInfoSize((TCHAR*)file, &reserved);
	TCHAR* vbuf = new TCHAR[size];
	if (GetFileVersionInfo((TCHAR*)file, 0, size, vbuf))
	{
		VerQueryValue(vbuf, _T("\\"), (void**)&buf, &size);
		CopyMemory(&vffi, buf, sizeof(VS_FIXEDFILEINFO));

		VerQueryValue(vbuf, _T("\\VarFileInfo\\Translation"), (void**)&buf, &size);
		CopyMemory(&Locale, buf, sizeof(int));
		wsprintf(str, _T("\\StringFileInfo\\%04X%04X\\%s"),
			LOWORD(Locale), HIWORD(Locale), _T("FileVersion"));
		VerQueryValue(vbuf, str, (void**)&buf, &size);

#if _MSC_VER <= 1310
		_tcscpy(str, buf);
#else
		_tcscpy_s(str, 256, buf);
#endif
		
		if (version != NULL)
		{
#if _MSC_VER <= 1310
			_tcscpy(version,buf);
#else
			_tcscpy_s(version, 256, buf);
#endif
		}
	}
	delete[] vbuf;

	if (_tcscmp(str, _T("")) != 0)
	{
		return int(_tstof(str) * 100);
	}
	else
	{
		return 0;
	}
}

void GetFileVersionEx(const TCHAR* file, CString& version)
{
	ULONG reserved = 0;
	VS_FIXEDFILEINFO vffi = { 0 };
	TCHAR* buf = NULL;
	int  Locale = 0;
	TCHAR str[256] = { 0 };

	UINT size = GetFileVersionInfoSize((TCHAR*)file, &reserved);
	TCHAR* vbuf = new TCHAR[size];
	if (GetFileVersionInfo((TCHAR*)file, 0, size, vbuf))
	{
		VerQueryValue(vbuf, _T("\\"), (void**)&buf, &size);
		CopyMemory(&vffi, buf, sizeof(VS_FIXEDFILEINFO));

		if (vffi.dwSignature == 0xfeef04bd)
		{
			version.Format(_T("%d.%d.%d.%d"),
				HIWORD(vffi.dwFileVersionMS),
				LOWORD(vffi.dwFileVersionMS),
				HIWORD(vffi.dwFileVersionLS),
				LOWORD(vffi.dwFileVersionLS));
		}
	}
	delete[] vbuf;
}

BOOL IsFileExist(const TCHAR* path)
{
	FILE* fp = NULL;

#if _MSC_VER <= 1310
	fp = _tfopen(path, _T("rb"));
	if (fp == NULL)
#else
	errno_t err = 0;
	err = _tfopen_s(&fp, path, _T("rb"));
	if (err != 0 || fp == NULL)
#endif
	{
		return FALSE;
	}
	fclose(fp);
	return TRUE;
}

BOOL CanWriteFile(const TCHAR* path)
{
	HANDLE hFile = CreateFile(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);

	if (hFile == INVALID_HANDLE_VALUE) { return FALSE; }
	const char* testData = "1";
	DWORD bytesWritten;
	BOOL writeResult = WriteFile(hFile, testData, (DWORD)strlen(testData), &bytesWritten, NULL);
	CloseHandle(hFile);

	return writeResult;
}


////------------------------------------------------
//   Utility
////------------------------------------------------

typedef ULONGLONG(WINAPI* FuncGetTickCount64)();

ULONGLONG GetTickCountFx()
{
	HMODULE hModule = GetModuleHandle(_T("kernel32.dll"));
	FuncGetTickCount64 pGetTickCount64 = NULL;
	if (hModule)
	{
		pGetTickCount64 = (FuncGetTickCount64)GetProcAddress(hModule, "GetTickCount64");
	}

	if (pGetTickCount64)
	{
		return (ULONGLONG)pGetTickCount64();
	}
	else
	{
#if _MSC_VER <= 1310
		return (ULONGLONG)GetTickCount();
#else
		return _Pragma("warning(suppress: 28159)") (ULONGLONG)GetTickCount();
#endif
	}
}

ULONG64 B8toB64(BYTE b0, BYTE b1, BYTE b2, BYTE b3, BYTE b4, BYTE b5, BYTE b6, BYTE b7)
{
	ULONG64 data =
		  ((ULONG64)b7 << 56)
		+ ((ULONG64)b6 << 48)
		+ ((ULONG64)b5 << 40)
		+ ((ULONG64)b4 << 32)
		+ ((ULONG64)b3 << 24)
		+ ((ULONG64)b2 << 16)
		+ ((ULONG64)b1 <<  8)
		+ ((ULONG64)b0 <<  0);

	return data;
}

DWORD B8toB32(BYTE b0, BYTE b1, BYTE b2, BYTE b3)
{
	DWORD data =
		  ((DWORD)b3 << 24)
		+ ((DWORD)b2 << 16)
		+ ((DWORD)b1 <<  8)
		+ ((DWORD)b0 <<  0);

	return data;
}

void SplitCString(const CString& str, const CString& delimiter, CStringArray& arr)
{
	int startPos = 0;
	while (startPos >= 0)
	{
		int endPos = str.Find(delimiter, startPos);
		if (endPos == -1)
		{
			arr.Add(str.Mid(startPos));
			break;
		}
		arr.Add(str.Mid(startPos, endPos - startPos));
		startPos = endPos + lstrlen(delimiter);
	}
}



#if _MSC_VER > 1310
// ---------------------------------------------------------
// 20260123: Safe for unaligned/page-boundary (Using memcpy for atomic-like MOV) >>>

// Environment Validation (Compile-time) >
static_assert(sizeof(USHORT) == 2, "! (sizeof(USHORT) == 2)");
static_assert(sizeof(INT) == 4, "! (sizeof(INT) == 4)");
static_assert(sizeof(DWORD) == 4, "! (sizeof(DWORD) == 4)");
static_assert(sizeof(ULONG64) == 8, "! (sizeof(ULONG64) == 8)");

// Boundary-Safe Loaders >

NODISCARD ULONG64 B8toB64le_ptr(_In_reads_(8) const BYTE* v) noexcept {
	ULONG64 u64;
	::memcpy(&u64, v, sizeof(u64));
	return u64;
}
NODISCARD DWORD B8toB32le_ptr(_In_reads_(4) const BYTE* v) noexcept {
	return ((static_cast<DWORD>(v[0]))
		| (static_cast<DWORD>(v[1]) << 8)
		| (static_cast<DWORD>(v[2]) << 16)
		| (static_cast<DWORD>(v[3]) << 24));
}
NODISCARD INT B8toINTle_ptr(_In_reads_(4) const BYTE* v) noexcept {
	return static_cast<INT>((static_cast<DWORD>(v[0]))
		| (static_cast<DWORD>(v[1]) << 8)
		| (static_cast<DWORD>(v[2]) << 16)
		| (static_cast<DWORD>(v[3]) << 24));
}
NODISCARD USHORT B8toB16le_ptr(_In_reads_(2) const BYTE* v) noexcept {
	return static_cast<USHORT>((static_cast<USHORT>(v[0]))
		| (static_cast<USHORT>(v[1]) << 8));
}
NODISCARD SHORT B8toSHORTle_ptr(_In_reads_(2) const BYTE* v) noexcept {
	return static_cast<SHORT>((static_cast<USHORT>(v[0]))
		| (static_cast<USHORT>(v[1]) << 8));
}
NODISCARD ULONG64 B8toB64le(const BYTE(&v)[6]) noexcept {
	return ((static_cast<ULONG64>(v[0]))
		| (static_cast<ULONG64>(v[1]) << 8)
		| (static_cast<ULONG64>(v[2]) << 16)
		| (static_cast<ULONG64>(v[3]) << 24)
		| (static_cast<ULONG64>(v[4]) << 32)
		| (static_cast<ULONG64>(v[5]) << 40));
}
NODISCARD DWORD B8toB32le(const BYTE(&v)[6]) noexcept {
	return ((static_cast<DWORD>(v[0]))
		| (static_cast<DWORD>(v[1]) << 8)
		| (static_cast<DWORD>(v[2]) << 16)
		| (static_cast<DWORD>(v[3]) << 24));
}
NODISCARD INT B8toINTle(const BYTE(&v)[6]) noexcept {
	return static_cast<INT>((static_cast<DWORD>(v[0]))
		| (static_cast<DWORD>(v[1]) << 8)
		| (static_cast<DWORD>(v[2]) << 16)
		| (static_cast<DWORD>(v[3]) << 24));
}
NODISCARD USHORT B8toB16le(const BYTE(&v)[6]) noexcept {
	return ((static_cast<USHORT>(v[0]))
		| (static_cast<USHORT>(v[1]) << 8));
}

// 20260123: Safe for unaligned/page-boundary <<<
// ---------------------------------------------------------
#endif


////------------------------------------------------
//   .ini support function
////------------------------------------------------

DWORD GetPrivateProfileStringFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR  lpReturnedString, DWORD nSize, LPCTSTR lpFileName)
{
	DWORD result = 0;
	CString key = lpKeyName;
	key.Replace(_T("="), _T("%#3D"));
	key.Replace(_T("\""), _T("%#22"));
	result = GetPrivateProfileString(lpAppName, key, lpDefault, lpReturnedString, nSize, lpFileName);

	CString value = lpReturnedString;
	value.Replace(_T("%#3D"), _T("="));
	value.Replace(_T("%#22"), _T("\""));

#if _MSC_VER <= 1310
	_tcscpy(lpReturnedString, (LPCTSTR)value);
#else
	_tcscpy_s(lpReturnedString, nSize, (LPCTSTR)value);
#endif

	return result;
}

UINT GetPrivateProfileIntFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, INT nDefault, LPCTSTR lpFileName)
{
	CString key = lpKeyName;
	key.Replace(_T("="), _T("%#3D"));
	key.Replace(_T("\""), _T("%#22"));

	return GetPrivateProfileInt(lpAppName, key, nDefault, lpFileName);
}

BOOL WritePrivateProfileStringFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpString, LPCTSTR lpFileName)
{
	CString key = lpKeyName;
	key.Replace(_T("="), _T("%#3D"));
	key.Replace(_T("\""), _T("%#22"));

	CString value = lpString;
	value.Replace(_T("="), _T("%#3D"));
	value.Replace(_T("\""), _T("%#22"));

	value = _T("\"" + value + _T("\""));

	return WritePrivateProfileString(lpAppName, key, value, lpFileName);
}

////------------------------------------------------
//   Check CodeSign
////------------------------------------------------
#if _MSC_VER > 1310
#include <Softpub.h>
#pragma comment(lib, "Wintrust.lib")
#pragma comment(lib, "Crypt32.lib")

BOOL CheckCodeSign(LPCWSTR certName, LPCWSTR filePath)
{
#ifdef _DEBUG
	return TRUE;
#endif

	// check sign

	WINTRUST_FILE_INFO FileData = { sizeof(WINTRUST_FILE_INFO) };
	FileData.pcwszFilePath = filePath;

	GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
	WINTRUST_DATA WinTrustData = { sizeof(WinTrustData) };
	WinTrustData.dwUIChoice = WTD_UI_NONE;
	WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
	WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;
	WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
	WinTrustData.pFile = &FileData;

	const LONG ret = WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);

	bool cert_chk = false;

	if (ret == ERROR_SUCCESS) {
		// retreive the signer certificate and display its information
		CRYPT_PROVIDER_DATA const* psProvData = NULL;
		CRYPT_PROVIDER_SGNR* psProvSigner = NULL;
		CRYPT_PROVIDER_CERT* psProvCert = NULL;

		psProvData = WTHelperProvDataFromStateData(WinTrustData.hWVTStateData);
		if (psProvData) {
			psProvSigner = WTHelperGetProvSignerFromChain((PCRYPT_PROVIDER_DATA)psProvData, 0, FALSE, 0);
			if (psProvSigner) {
				psProvCert = WTHelperGetProvCertFromChain(psProvSigner, 0);
				if (psProvCert) {
					wchar_t szCertName[200] = {0};
					DWORD dwStrType = CERT_X500_NAME_STR;
					CertGetNameStringW(psProvCert->pCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, &dwStrType, szCertName, 200);
					cert_chk = !(szCertName[0] == '\0' || wcscmp(szCertName, certName) != 0);
				}
			}
		}
	}
	WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
	(void)WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
	if (!cert_chk)  return FALSE;

	return TRUE;
}
#endif

////------------------------------------------------
//   Play Sound
////------------------------------------------------

#include <mmsystem.h>
#include "digitalv.h"

#pragma comment(lib, "winmm.lib")

/*
void ShowMciError(DWORD error)
{
	TCHAR errorBuf[MAXERRORLENGTH];

	if (mciGetErrorString(error, (LPTSTR)errorBuf, MAXERRORLENGTH))
	{
		AfxMessageBox(errorBuf);
	}
	else
	{
		AfxMessageBox(_T("Unknown Error");
	}
}
*/

BOOL AlertSound(const CString& alertSoundPath, int volume)
{
#if _MSC_VER <= 1310
	PlaySound(alertSoundPath, NULL, SND_SYNC);
#else

	static MCI_OPEN_PARMS mop = { 0 };
	static MCI_PLAY_PARMS mpp = { 0 };
	static MCI_GENERIC_PARMS mgp = { 0 };
	MCIERROR error = 0;

	if (mop.wDeviceID != 0)
	{
		// Stop and close
		error = mciSendCommandW(mop.wDeviceID, MCI_STOP, 0, 0);
		error = mciSendCommandW(mop.wDeviceID, MCI_CLOSE, 0, 0);
		mop.wDeviceID = 0;
		ZeroMemory(&mop, sizeof(MCI_OPEN_PARMS));
		if (error)
		{
			// ShowMciError(error);
			return FALSE;
		}
	}

	if (alertSoundPath.Compare(_T("")) == 0) {
		// Close
		error = mciSendCommandW(mop.wDeviceID, MCI_CLOSE, 0, 0);
		if (error)
		{
			// ShowMciError(error);
			return FALSE;
		}
		return TRUE;
	}

	// Open
	mop.lpstrElementName = (LPCTSTR)alertSoundPath;
	mop.lpstrDeviceType = _T("MPEGVideo");
	error = mciSendCommandW(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT, (DWORD_PTR)(&mop));
	
	if (error)
	{
		// ShowMciError(error);
		return FALSE;
	}

	// Set volume
	if (volume < 0 || volume > 100) { volume = 80; }

	MCI_DGV_SETAUDIO_PARMS parms = { 0 };
	parms.dwItem = MCI_DGV_SETAUDIO_VOLUME;
	parms.dwValue = volume * 10; // 0-1000
	error = mciSendCommand(mop.wDeviceID, MCI_SETAUDIO, MCI_DGV_SETAUDIO_ITEM | MCI_DGV_SETAUDIO_VALUE, (DWORD_PTR)&parms);
	if (error)
	{
		// ShowMciError(error);
		return FALSE;
	}
	// Seek
	error = mciSendCommand(mop.wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, reinterpret_cast<DWORD_PTR>(&mgp));
	if (error)
	{
		// ShowMciError(error);
		return FALSE;
	}

	// Play
	error = mciSendCommandW(mop.wDeviceID, MCI_PLAY, 0, reinterpret_cast<DWORD_PTR>(&mpp));
	if (error)
	{
		// ShowMciError(error);
		return FALSE;
	}
#endif

	return TRUE;
}


////------------------------------------------------
//   Hash
////------------------------------------------------

#if _MSC_VER > 1310
#include <iostream>
#include <iomanip>
#include <sstream>
#include <windows.h>
#include <wincrypt.h>
CStringA MD5(const CStringA& str)
{
	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	BYTE hash[16];
	DWORD hashLen = 16;
	CStringA hashStr;

	if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
		return "";
	}

	if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
		CryptReleaseContext(hProv, 0);
		return "";
	}

	std::string utf8Str(str);

	if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(utf8Str.c_str()), (DWORD)utf8Str.size(), 0)) {
		CryptDestroyHash(hHash);
		CryptReleaseContext(hProv, 0);
		return "";
	}

	if (!CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0)) {
		CryptDestroyHash(hHash);
		CryptReleaseContext(hProv, 0);
		return "";
	}

	for (DWORD i = 0; i < hashLen; ++i) {
		CString temp;
		temp.Format(_T("%02x"), hash[i]);
		hashStr += temp;
	}

	CryptDestroyHash(hHash);
	CryptReleaseContext(hProv, 0);

	return hashStr;
}
#else
#include "md5.h"
CStringA MD5(const CStringA& str)
{
	char* input = (char*)(LPCSTR)str;
	uint8_t result[16] = { 0 };
	md5String(input, result);

	CStringA hashStr;
	for (int i = 0; i < 16; ++i)
	{
		CStringA byteStr;
		byteStr.Format("%02x", result[i]);
		hashStr += byteStr;
	}

	return hashStr;
}
#endif

////------------------------------------------------
//   Character Converter
////------------------------------------------------

CStringA URLEncode(const CStringA& str)
{
	CStringA encoded;
	for (int i = 0; i < str.GetLength(); i++)
	{
		if (isalnum((BYTE)str[i]) || str[i] == '-' || str[i] == '_' || str[i] == '.' || str[i] == '~')
		{
			encoded += str[i];
		}
		else
		{
			encoded.AppendFormat("%%%02X", (BYTE)str[i]);
		}
	}
	return encoded;
}

#if _MSC_VER <= 1310

// https://github.com/roytam1/RetroZilla/commit/e44dd98dbf2effeff3305430835e889d76ec73b4#diff-065242b491de972d8519dbbae0c4c8dfb6a1a3c3eb8b9936d56a4c1ad63ec7abR126-R282
/*** UTF16<-->UTF8 functions minicking MultiByteToWideChar/WideCharToMultiByte ***/
int utf8GetMaskIndex(unsigned char n) {
	if ((unsigned char)(n + 2) < 0xc2) return 1; // 00~10111111, fe, ff
	if (n < 0xe0)                      return 2; // 110xxxxx
	if (n < 0xf0)                      return 3; // 1110xxxx
	if (n < 0xf8)                      return 4; // 11110xxx
	if (n < 0xfc)                      return 5; // 111110xx
	return 6; // 1111110x
}

int wc2Utf8Len(wchar_t** n, int* len) {
	wchar_t* ch = *n, ch2;
	int qch;
	if ((0xD800 <= *ch && *ch <= 0xDBFF) && *len) {
		ch2 = *(ch + 1);
		if (0xDC00 <= ch2 && ch2 <= 0xDFFF) {
			qch = 0x10000 + (((*ch - 0xD800) & 0x3ff) << 10) + ((ch2 - 0xDC00) & 0x3ff);
			(*n)++;
			(*len)--;
		}
	}
	else
		qch = (int)*ch;

	if (qch <= 0x7f)           return 1;
	else if (qch <= 0x7ff)     return 2;
	else if (qch <= 0xffff)    return 3;
	else if (qch <= 0x1fffff)  return 4;
	else if (qch <= 0x3ffffff) return 5;
	else                       return 6;
}

int Utf8ToWideChar(unsigned int unused1, unsigned long unused2, char* sb, int ss, wchar_t* wb, int ws) {
	static const unsigned char utf8mask[] = { 0, 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
	char* p = (char*)(sb);
	char* e = (char*)(sb + ss);
	wchar_t* w = wb;
	int cnt = 0, t, qch;

	if (ss < 1) {
		ss = lstrlenA(sb);
		e = (char*)(sb + ss);
	}

	if (wb && ws) {
		for (; p < e; ++w) {
			t = utf8GetMaskIndex(*p);
			qch = (*p++ & utf8mask[t]);
			while (p < e && --t)
				qch <<= 6, qch |= (*p++) & 0x3f;
			if (qch < 0x10000) {
				if (cnt <= ws)
					*w = (wchar_t)qch;
				cnt++;
			}
			else {
				if (cnt + 2 <= ws) {
					*w++ = (wchar_t)(0xD800 + (((qch - 0x10000) >> 10) & 0x3ff)),
						*w = (wchar_t)(0xDC00 + (((qch - 0x10000)) & 0x3ff));
				}
				cnt += 2;
			}
		}
		if (cnt < ws) {
			*(wb + cnt) = 0;
			return cnt;
		}
		else {
			*(wb + ws) = 0;
			return ws;
		}
	}
	else {
		for (t; p < e;) {
			t = utf8GetMaskIndex(*p);
			qch = (*p++ & utf8mask[t]);
			while (p < e && --t)
				qch <<= 6, qch |= (*p++) & 0x3f;
			if (qch < 0x10000)
				cnt++;
			else
				cnt += 2;
		}
		return cnt + 1;
	}
}

int WideCharToUtf8(unsigned int unused1, unsigned long unused2, wchar_t* wb, int ws, char* sb, int ss) {
	wchar_t* p = (wchar_t*)(wb);
	wchar_t* e = (wchar_t*)(wb + ws);
	wchar_t* oldp;
	char* s = sb;
	int cnt = 0, qch, t;

	if (ws < 1) {
		ws = lstrlenW(wb);
		e = (wchar_t*)(wb + ws);
	}

	if (sb && ss) {
		for (t; p < e; ++p) {
			oldp = p;
			t = wc2Utf8Len(&p, &ws);

			if (p != oldp) { /* unicode surrogates encountered */
				qch = 0x10000 + (((*oldp - 0xD800) & 0x3ff) << 10) + ((*p - 0xDC00) & 0x3ff);
			}
			else
				qch = *p;

			if (qch <= 0x7f)
				*s++ = (char)(qch),
				cnt++;
			else if (qch <= 0x7ff)
				*s++ = 0xc0 | (char)(qch >> 6),
				*s++ = 0x80 | (char)(qch & 0x3f),
				cnt += 2;
			else if (qch <= 0xffff)
				*s++ = 0xe0 | (char)(qch >> 12),
				*s++ = 0x80 | (char)((qch >> 6) & 0x3f),
				*s++ = 0x80 | (char)(qch & 0x3f),
				cnt += 3;
			else if (qch <= 0x1fffff)
				*s++ = 0xf0 | (char)(qch >> 18),
				*s++ = 0x80 | (char)((qch >> 12) & 0x3f),
				*s++ = 0x80 | (char)((qch >> 6) & 0x3f),
				*s++ = 0x80 | (char)(qch & 0x3f),
				cnt += 4;
			else if (qch <= 0x3ffffff)
				*s++ = 0xf8 | (char)(qch >> 24),
				*s++ = 0x80 | (char)((qch >> 18) & 0x3f),
				*s++ = 0x80 | (char)((qch >> 12) & 0x3f),
				*s++ = 0x80 | (char)((qch >> 6) & 0x3f),
				*s++ = 0x80 | (char)(qch & 0x3f),
				cnt += 5;
			else
				*s++ = 0xfc | (char)(qch >> 30),
				*s++ = 0x80 | (char)((qch >> 24) & 0x3f),
				*s++ = 0x80 | (char)((qch >> 18) & 0x3f),
				*s++ = 0x80 | (char)((qch >> 12) & 0x3f),
				*s++ = 0x80 | (char)((qch >> 6) & 0x3f),
				*s++ = 0x80 | (char)(qch & 0x3f),
				cnt += 6;
		}
		if (cnt < ss) {
			*(sb + cnt) = 0;
			return cnt;
		}
		else {
			*(sb + ss) = 0;
			return ss;
		}
	}
	else {
		for (t; p < e; ++p) {
			t = wc2Utf8Len(&p, &ws);
			cnt += t;
		}
		return cnt + 1;
	}
}
/*** Ends ***/

#ifdef UNICODE
CStringA UTF16toUTF8(const CStringW& utf16str)
{
	if (IsNT3() || IsNT4())
	{
		CStringA utf8str;
		WCHAR* utf16string = new WCHAR[(utf16str.GetLength() + 1) * 2];
		wsprintf(utf16string, L"%s", utf16str);
		int utf16Length = utf16str.GetLength();
		int utf8Length = WideCharToUtf8(CP_UTF8, 0, utf16string, -1, NULL, 0);
		if (utf8Length <= 0) { return ""; }
		WideCharToUtf8(CP_UTF8, 0, utf16string, -1, utf8str.GetBuffer(utf8Length), utf8Length);
		utf8str.ReleaseBuffer();
		delete utf16string;
		return utf8str;
	}
	else
	{
		CStringA utf8str;
		int utf8Length = WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, NULL, 0, NULL, NULL);
		if (utf8Length <= 0) { return ""; }
		WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, utf8str.GetBuffer(utf8Length), utf8Length, NULL, NULL);
		utf8str.ReleaseBuffer();
		return utf8str;
	}
}

CStringW UTF8toUTF16(const CStringA& utf8str)
{
	CStringW utf16str;
	CHAR* utf8string = new CHAR[(utf8str.GetLength() + 1) * 2];
	sprintf(utf8string, "%s", utf8str);
	int utf8Length = utf8str.GetLength();
	int utf16Length = Utf8ToWideChar(1200, 0, utf8string, -1, NULL, 0);
	if (utf16Length <= 0) { return L""; }
	Utf8ToWideChar(1200, 0, utf8string, -1, utf16str.GetBuffer(utf16Length), utf16Length);
	utf16str.ReleaseBuffer();
	delete utf8string;
	return utf16str;
}

#else
CStringA ANSItoUTF8(const CStringA& ansiStr)
{
	int utf16Length = MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, NULL, 0);
	if (utf16Length <= 0) { return ""; }
	CStringW utf16str;
	MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, utf16str.GetBuffer(utf16Length), utf16Length);
	utf16str.ReleaseBuffer();

	CStringA utf8str;
	int utf8Length = WideCharToUtf8(CP_UTF8, 0, utf16str.GetBuffer(utf16Length), -1, NULL, 0);
	if (utf8Length <= 0) { return ""; }
	WideCharToUtf8(CP_UTF8, 0, utf16str.GetBuffer(utf16Length), -1, utf8str.GetBuffer(utf8Length), utf8Length);
	utf8str.ReleaseBuffer();
	return utf8str;
}
#endif
#else

CStringA UTF16toUTF8(const CStringW& utf16str)
{
	CStringA utf8str(CW2A(utf16str, CP_UTF8));
	return utf8str;
}

CStringW UTF8toUTF16(const CStringA& utf8str)
{
	CStringW utf16str;
	utf16str = CA2W(utf8str);
	return utf16str;
}
#endif

#ifdef UNICODE
CStringA UE(const CStringW& utf16str)
{
	return URLEncode(UTF16toUTF8(utf16str));
}
#else
CStringA UE(const CStringA& ansiStr)
{
	return URLEncode(ANSItoUTF8(ansiStr));
}
#endif
////------------------------------------------------
//   Clipboard
////------------------------------------------------

void SetClipboardText(CString clip)
{
	if (OpenClipboard(NULL))
	{
		HGLOBAL clipbuffer;
		TCHAR* buffer = NULL;
		EmptyClipboard();
		clipbuffer = GlobalAlloc(GMEM_DDESHARE, sizeof(TCHAR) * (clip.GetLength() + 1));
		if (clipbuffer != NULL)
		{
			buffer = (TCHAR*)GlobalLock(clipbuffer);
			if (buffer != NULL)
			{
#if _MSC_VER <= 1310
				_tcscpy(buffer, LPCTSTR(clip));
#else
				_tcscpy_s(buffer, clip.GetLength() + 1, LPCTSTR(clip));
#endif
			}
			GlobalUnlock(clipbuffer);
#ifdef UNICODE
			SetClipboardData(CF_UNICODETEXT, clipbuffer);
#else
			SetClipboardData(CF_TEXT, clipbuffer);
#endif
		}
		CloseClipboard();
	}
}

////------------------------------------------------
//   SHLWAPI.DLL compatible functions
////------------------------------------------------
#if _MSC_VER <= 1310
BOOL PathRemoveFileSpecFxA(char* path)
{
	char* filespec = path;
	BOOL modified = FALSE;

	if (!path || !*path) { return FALSE; }
	if (*path == '\\') { filespec = ++path; }
	if (*path == '\\') { filespec = ++path; }

	while (*path)
	{
		if (*path == '\\')
		{
			filespec = path;
		}
		else if (*path == ':')
		{
			filespec = ++path;
			if (*path == '\\') { filespec++; }
		}
		path = CharNextA(path);
	}

	if (*filespec)
	{
		*filespec = '\0';
		modified = TRUE;
	}

	return modified;
}

BOOL PathRemoveFileSpecFxW(WCHAR* path)
{
	WCHAR* filespec = path;
	BOOL modified = FALSE;

	if (!path || !*path) { return FALSE; }
	if (*path == '\\') { filespec = ++path; }
	if (*path == '\\') { filespec = ++path; }

	while (*path)
	{
		if (*path == '\\')
		{
			filespec = path;
		}
		else if (*path == ':')
		{
			filespec = ++path;
			if (*path == '\\') { filespec++; }
		}
		path++;
	}

	if (*filespec)
	{
		*filespec = '\0';
		modified = TRUE;
	}

	return modified;
}

char* PathFindFileNameFxA(const char* path)
{
	const char* p = path;
	const char* name = NULL;
	while (*p)
	{
		if (*p == '\\' || *p == '/')
		{
			name = p + 1;
		}
		p = CharNextA(p);
	}
	if (name)
	{
		return (char*)name;
	}
	return (char*)path;
}

WCHAR* PathFindFileNameFxW(const WCHAR* path)
{
	const WCHAR* p = path;
	const WCHAR* name = NULL;
	while (*p)
	{
		if (*p == '\\' || *p == '/')
		{
			name = p + 1;
		}
		p++;
	}
	if (name)
	{
		return (WCHAR*)name;
	}
	return (WCHAR*)path;
}
#endif