// SoftEther VPN Source Code - Developer Edition Master Branch
// Cedar Communication Module


// vpninstall.c
// VPN Client Web Installer Bootstrap

#include <GlobalConst.h>

#include <winsock2.h>
#include <windows.h>
#include <wincrypt.h>
#include <wininet.h>
#include <shlobj.h>
#include <commctrl.h>
#include <Dbghelp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <stdarg.h>
#include <time.h>
#include <Msiquery.h>
#include <Mayaqua/Mayaqua.h>
#include <Cedar/Cedar.h>
#include "vpninstall.h"
#include "resource.h"

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

static bool is_debug = true;
static LIST *string_table = NULL;
static VI_SETTING setting;
static bool sleep_before_exit = false;
static int skip = 0;

// Convert the URL to the file name
char *ViUrlToFileName(char *url)
{
	UINT i, len;
	char *ret = url;
	bool b = true;
	len = lstrlen(url);

	for (i = 0;i < len;i++)
	{
		char c = url[i];

		if (c == '?' || c == '#')
		{
			b = false;
		}

		if (b)
		{
			if (c == '/' || c == '\\')
			{
				if (lstrlen(url + i + 1) > 1)
				{
					ret = url + i + 1;
				}
			}
		}
	}

	return ret;
}

// Check the signature of the EXE file, and displays a warning if dangerous
bool ViCheckExeSign(HWND hWnd, wchar_t *exew)
{
	wchar_t tmp[2048];
	bool danger = true;
	wchar_t *warningMessage = _U(IDS_SIGN_WARNING+skip);
	wchar_t *warningMessageTitle = _U(IDS_SIGN_WARNING_TITLE+skip);
	// Validate arguments
	if (hWnd == NULL || exew == NULL)
	{
		return false;
	}

	if (MsCheckFileDigitalSignatureW(hWnd, exew, &danger))
	{
		if (danger == false)
		{
			// Safe
			return true;
		}
		else
		{
			wchar_t filename[MAX_PATH];

			GetFileNameFromFilePathW(filename, sizeof(filename), exew);

			// Show the message because there is potentially dangerous
			swprintf(tmp, sizeof(tmp) / 2, warningMessage,
				filename, filename, filename);

			if (MessageBoxW(hWnd, tmp, warningMessageTitle,
				MB_OKCANCEL | MB_DEFBUTTON2 | MB_ICONEXCLAMATION) == IDOK)
			{
				return true;
			}

			return false;
		}
	}
	else
	{
		// Danger
		return false;
	}
}

// Start the installation process
void ViInstallProcessStart(HWND hWnd, VI_INSTALL_DLG *d)
{
	wchar_t *exew;
	bool ok;
	char instdir[MAX_PATH];
	char hamcore[MAX_PATH];
	// Validate arguments
	if (hWnd == NULL || d == NULL)
	{
		return;
	}

	ViGenerateVpnSMgrTempDirName(instdir, sizeof(instdir), ViGetSuitableArchForCpu()->Build);
	ConbinePath(hamcore, sizeof(hamcore), instdir, "hamcore.se2");

	exew = setting.DownloadedInstallerPathW;
	d->NoClose = true;

	Hide(hWnd, IDCANCEL);
	SetPos(hWnd, P_PROGRESS, 100);
	Hide(hWnd, P_PROGRESS);
	Hide(hWnd, S_SIZEINFO);
	SetText(hWnd, S_STATUS, _U(IDS_INSTALLSTART+skip));

	ok = true;

	if (setting.DownloadNotRequired == false)
	{
		if (setting.WebMode && ViCheckExeSign(hWnd, exew) == false)
		{
			// The digital signature is not reliable
			ok = false;
		}
		else
		{
			// Installation
			HANDLE hProcess;
			SHELLEXECUTEINFOW info;

			// Run
			Zero(&info, sizeof(info));
			info.cbSize = sizeof(info);
			info.lpVerb = L"open";
			info.lpFile = exew;
			info.fMask = SEE_MASK_NOCLOSEPROCESS;
			info.lpParameters = L"/HIDESTARTCOMMAND:1 /DISABLEAUTOIMPORT:1 /ISWEBINSTALLER:1";
			info.nShow = SW_SHOWNORMAL;
			if (ShellExecuteExW(&info) == false)
			{
				MsgBox(hWnd, MB_ICONSTOP, _U(IDS_INSTALLSTART_ERROR+skip));
				ok = false;
			}
			else
			{
				hProcess = info.hProcess;

				// Wait for the install process to complete
				while (true)
				{
					if (WaitForSingleObject(hProcess, 50) != WAIT_TIMEOUT)
					{
						break;
					}

					DoEvents(hWnd);
				}
				CloseHandle(hProcess);
			}
		}
	}

	if (ok && d->WindowsShutdowning == false)
	{
		VI_SETTING_ARCH *a = ViGetSuitableArchForCpu();
		wchar_t arg[MAX_PATH];
		wchar_t exe[MAX_PATH];
		char *arg1 = "/easy";
		// Hide the screen
		Hide(hWnd, 0);

		if (setting.NormalMode)
		{
			arg1 = "/normal";
		}

		// (Just in case) start the VPN Client service
		if (MsIsServiceRunning("vpnclient") == false)
		{
			MsStartService("vpnclient");
		}

		// Wait for that the service becomes available
		SwWaitForVpnClientPortReady(0);

		if (UniIsEmptyStr(setting.DownloadedSettingPathW) == false)
		{
			// Start a connection by importing the configuration file into the VPN Client
			UniFormat(arg, sizeof(arg), L"%S \"%s\"", arg1, setting.DownloadedSettingPathW);
		}
		else
		{
			// Just start the Connection Manager
			UniFormat(arg, sizeof(arg), L"%S", arg1);
		}

		// Get the installation state
		ViLoadCurrentInstalledStatusForArch(a);

		if (a->CurrentInstalled)
		{
			HANDLE h;
			wchar_t filename[MAX_PATH];

			StrToUni(filename, sizeof(filename), a->VpnCMgrExeFileName);

			ConbinePathW(exe, sizeof(exe), a->CurrentInstalledPathW, filename);

			// Start the Connection Manager
			h = MsRunAsUserExW(exe, arg, false);
			if (h != NULL)
			{
				if (UniIsEmptyStr(setting.DownloadedSettingPathW) == false)
				{
					sleep_before_exit = true;
				}

				CloseHandle(h);
			}
		}
	}

	d->NoClose = false;
	Close(hWnd);
}

// End User License Agreement dialog
UINT ViEulaDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, void *param)
{
	wchar_t *text = (wchar_t *)param;
	// Validate arguments
	if (hWnd == NULL)
	{
		return 0;
	}

	switch (msg)
	{
	case WM_INITDIALOG:
		SetText(hWnd, 0, _U(IDS_DLG_TITLE+skip));
		SetText(hWnd, S_EULA_NOTICE1, _U(IDS_EULA_NOTICE1+skip));
		SetText(hWnd, S_BOLD, _U(IDS_EULA_NOTICE2+skip));
		SetText(hWnd, S_EULA_NOTICE3, _U(IDS_EULA_NOTICE3+skip));
		SetText(hWnd, IDOK, _U(IDS_EULA_AGREE+skip));
		SetText(hWnd, IDCANCEL, _U(IDS_EULA_DISAGREE+skip));

		DlgFont(hWnd, S_BOLD, 0, true);
		SetText(hWnd, E_EULA, text);
		Focus(hWnd, E_EULA);
		SendMsg(hWnd, E_EULA, EM_SETSEL, 0, 0);
		Center(hWnd);
		break;

	case WM_COMMAND:
		switch (wParam)
		{
		case IDOK:
			EndDialog(hWnd, 1);
			break;

		case IDCANCEL:
			Close(hWnd);
			break;
		}
		break;

	case WM_CLOSE:
		EndDialog(hWnd, 0);
		break;
	}

	return 0;
}

// Display the End User License Agreement
bool ViEulaDlg(HWND hWnd, wchar_t *text)
{
	// Validate arguments
	if (text == NULL)
	{
		return false;
	}

	if (Dialog(hWnd, D_EULA, ViEulaDlgProc, text) == 0)
	{
		return false;
	}

	return true;
}

// Extract the license agreement from the EXE file
wchar_t *ViExtractEula(char *exe)
{
	BUF *b;
	UINT tmp_size;
	char *tmp;
	wchar_t *ret;
	// Validate arguments
	if (exe == NULL)
	{
		return false;
	}

	b = ViExtractResource(exe, RT_RCDATA, "LICENSE");
	if (b == NULL)
	{
		return NULL;
	}

	tmp_size = b->Size + 1;
	tmp = ZeroMalloc(tmp_size);

	Copy(tmp, b->Buf, b->Size);
	FreeBuf(b);

	ret = CopyStrToUni(tmp);
	Free(tmp);

	return ret;
}

// Extract the Cabinet file from the EXE file
bool ViExtractCabinetFile(char *exe, char *cab)
{
	BUF *b;
	// Validate arguments
	if (exe == NULL || cab == NULL)
	{
		return false;
	}

	b = ViExtractResource(exe, RT_RCDATA, "CABINET");
	if (b == NULL)
	{
		return false;
	}

	if (DumpBuf(b, cab) == false)
	{
		FreeBuf(b);

		return false;
	}

	FreeBuf(b);

	return true;
}

// Extract the resource from the EXE file
BUF *ViExtractResource(char *exe, char *type, char *name)
{
	HINSTANCE h;
	HRSRC hr;
	HGLOBAL hg;
	UINT size;
	void *data;
	BUF *buf;
	// Validate arguments
	if (exe == NULL || type == NULL || name == NULL)
	{
		return NULL;
	}

	h = LoadLibraryExA(exe, NULL, LOAD_LIBRARY_AS_DATAFILE);
	if (h == NULL)
	{
		return NULL;
	}

	hr = FindResourceA(h, name, type);
	if (hr == NULL)
	{
		FreeLibrary(h);
		return NULL;
	}

	hg = LoadResource(h, hr);
	if (hg == NULL)
	{
		FreeLibrary(h);
		return NULL;
	}

	size = SizeofResource(h, hr);
	data = (void *)LockResource(hg);

	buf = NewBuf();
	WriteBuf(buf, data, size);

	FreeResource(hg);
	FreeLibrary(h);

	SeekBuf(buf, 0, 0);

	return buf;
}

// Open the file
VI_FILE *ViOpenFile(char *path)
{
	VI_FILE *f;
	// Validate arguments
	if (path == NULL)
	{
		return NULL;
	}

	if (ViIsInternetFile(path))
	{
		HINTERNET hHttpFile;
		HINTERNET hInternet = InternetOpenA(DEFAULT_USER_AGENT,
			INTERNET_OPEN_TYPE_PRECONFIG,
			NULL, NULL, 0);
		UINT size;
		UINT sizesize;

		if (hInternet == NULL)
		{
			return NULL;
		}

		hHttpFile = InternetOpenUrlA(hInternet, path, NULL, 0,
			INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_RELOAD, 0);

		if (hHttpFile == NULL)
		{
			InternetCloseHandle(hInternet);
			return NULL;
		}

		size = 0;
		sizesize = sizeof(size);

		if (StartWith(path, "ftp://"))
		{
			// ftp
			DWORD high = 0;

			size = FtpGetFileSize(hHttpFile, &high);
		}
		else
		{
			UINT errorcode = 0;
			UINT errorcode_size = sizeof(errorcode);

			// http
			if (HttpQueryInfo(hHttpFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
				&size, &sizesize, NULL) == false)
			{
				size = 0;
			}

			if (HttpQueryInfo(hHttpFile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
				&errorcode, &errorcode_size, NULL) == false ||
				(errorcode / 100) != 2)
			{
				// HTTP getting error
				InternetCloseHandle(hInternet);
				InternetCloseHandle(hHttpFile);
				return NULL;
			}
		}

		f = ZeroMalloc(sizeof(VI_FILE));
		f->InternetFile = true;
		f->hInternet = hInternet;
		f->hHttpFile = hHttpFile;
		f->FileSize = size;

		return f;
	}
	else
	{
		IO *io;
		char fullpath[MAX_PATH];
		char exedir[MAX_PATH];

		GetExeDir(exedir, sizeof(exedir));

		ConbinePath(fullpath, sizeof(fullpath), exedir, path);

		io = FileOpen(fullpath, false);
		if (io == NULL)
		{
			return NULL;
		}

		f = ZeroMalloc(sizeof(VI_FILE));
		f->InternetFile = false;
		f->FileSize = FileSize(io);
		f->io = io;

		return f;
	}
}

// Get the file size
UINT ViGetFileSize(VI_FILE *f)
{
	// Validate arguments
	if (f == NULL)
	{
		return 0;
	}

	return f->FileSize;
}

// Read from the file
UINT ViReadFile(VI_FILE *f, void *buf, UINT size)
{
	// Validate arguments
	if (f == NULL || buf == NULL)
	{
		return INFINITE;
	}

	if (f->InternetFile == false)
	{
		UINT readsize = MIN(size, f->FileSize - f->IoReadFileSize);
		bool ret;

		if (readsize == 0)
		{
			return 0;
		}

		ret = FileRead(f->io, buf, readsize);

		if (ret == false)
		{
			return INFINITE;
		}

		f->IoReadFileSize += readsize;

		return readsize;
	}
	else
	{
		UINT readsize = 0;

		if (InternetReadFile(f->hHttpFile, buf, size, &readsize) == false)
		{
			return INFINITE;
		}

		return readsize;
	}
}

// Close the file
void ViCloseFile(VI_FILE *f)
{
	// Validate arguments
	if (f == NULL)
	{
		return;
	}

	if (f->InternetFile == false)
	{
		FileClose(f->io);
	}
	else
	{
		InternetCloseHandle(f->hHttpFile);
		InternetCloseHandle(f->hInternet);
	}

	Free(f);
}

// Determine whether the specified file name is the file on the Internet
bool ViIsInternetFile(char *path)
{
	// Validate arguments
	if (path == NULL)
	{
		return false;
	}

	if (StartWith(path, "http://") || StartWith(path, "https://") || StartWith(path, "ftp://"))
	{
		return true;
	}

	return false;
}

// Installer dialog initialization
void ViInstallDlgOnInit(HWND hWnd, VI_INSTALL_DLG *d)
{
	// Validate arguments
	if (hWnd == NULL || d == NULL)
	{
		return;
	}

	d->hWnd = hWnd;

	SetIcon(hWnd, 0, IDI_MAIN);

	SetText(hWnd, 0, _U(IDS_DLG_TITLE+skip));
	SetText(hWnd, S_TITLE, _U(IDS_DLG_TITLE+skip));

	SetText(hWnd, S_STATUS, _U(IDS_INSTALL_DLG__STATUS_INIT+skip));
	SetText(hWnd, IDCANCEL, _U(IDS_INSTALL_CANCEL+skip));

	DlgFont(hWnd, S_TITLE+skip, 12, true);
	SetRange(hWnd, P_PROGRESS, 0, 100);
	SetPos(hWnd, P_PROGRESS, 0);

	SetTimer(hWnd, 1, 22, NULL);
}

// Start the download thread
void ViDownloadThreadStart(VI_INSTALL_DLG *d)
{
	// Validate arguments
	if (d == NULL)
	{
		return;
	}

	d->DownloadStarted = true;
	d->DownloadThread = NewThread(ViDownloadThread, d);
}


// Stop the download thread
void ViDownloadThreadStop(VI_INSTALL_DLG *d)
{
	// Validate arguments
	if (d == NULL)
	{
		return;
	}

	if (d->DownloadStarted == false)
	{
		return;
	}

	d->DownloadStarted = false;
	d->Halt = true;

	while (true)
	{
		if (WaitThread(d->DownloadThread, 50))
		{
			break;
		}

		DoEvents(NULL);
	}

	ReleaseThread(d->DownloadThread);
}

// Download thread
void ViDownloadThread(THREAD *thread, void *param)
{
	VI_INSTALL_DLG *d;
	VI_SETTING_ARCH *a;
	HWND hWnd;
	UINT num_files = 2;
	VI_DOWNLOAD_FILE files[2];
	VI_DOWNLOAD_FILE *f;
	UINT i;
	// Validate arguments
	if (thread == NULL || param == NULL)
	{
		return;
	}

	d = (VI_INSTALL_DLG *)param;
	hWnd = d->hWnd;

	Zero(files, sizeof(files));

	a = ViGetSuitableArchForCpu();

	// File body
	f = &files[0];
	StrCpy(f->SrcPath, sizeof(f->SrcPath), a->Path);

	// Configuration file
	if (IsEmptyStr(setting.SettingPath) == false)
	{
		f = &files[1];
		StrCpy(f->SrcPath, sizeof(f->SrcPath), setting.SettingPath);
	}
	else
	{
		// No configuration file
		num_files = 1;
	}

	for (i = 0;i < num_files;i++)
	{
		bool b = true;

		if (i == 0 && setting.DownloadNotRequired)
		{
			b = false;
		}

		if (b)
		{
			wchar_t tmp[MAX_SIZE];
			IO *dest = NULL;
			VI_FILE *down;
			UINT ret;
			UINT totalsize;
			UINT currentsize;
			wchar_t filename_w[MAX_PATH];

			f = &files[i];
			GetFileNameFromFilePath(f->FileName, sizeof(f->FileName), f->SrcPath);
			MakeSafeFileName(f->FileName, sizeof(f->FileName), f->FileName);

			StrToUni(filename_w, sizeof(filename_w), f->FileName);
			ConbinePathW(f->DestPathW, sizeof(f->DestPathW), MsGetMyTempDirW(), filename_w);

			ViInstallDlgSetPos(hWnd, 0);
			UniFormat(tmp, sizeof(tmp), _U(IDS_DOWNLOADSTART+skip), f->FileName);
			ViInstallDlgSetText(d, hWnd, S_STATUS, tmp);

			down = ViOpenFile(f->SrcPath);
			if (down == NULL)
			{
				MsgBoxEx(hWnd, MB_ICONSTOP, _U(IDS_DOWNLOAD_ERROR+skip), f->FileName);

				ViInstallDlgCancel(hWnd);
				return;
			}

			dest = FileCreateW(f->DestPathW);
			if (dest == NULL)
			{
				MsgBoxEx(hWnd, MB_ICONSTOP, _U(IDS_TEMP_ERROR+skip), f->DestPathW);

				ViCloseFile(down);
				ViInstallDlgCancel(hWnd);
				return;
			}

			totalsize = ViGetFileSize(down);
			currentsize = 0;

			UniFormat(tmp, sizeof(tmp), _U(IDS_DOWNLOADING3+skip), f->FileName);
			ViInstallDlgSetText(d, hWnd, S_STATUS, tmp);

			while (true)
			{
				UINT pos = 0;

				if (d->Halt)
				{
					// User cancel
					FileClose(dest);
					ViCloseFile(down);
					return;
				}

				UniFormat(tmp, sizeof(tmp), _U(IDS_DOWNLOADING3+skip), f->FileName);

				ViInstallDlgSetText(d, hWnd, IDS_DOWNLOADING3+skip, tmp);
				ret = ViReadFile(down, d->Buf, d->BufSize);

				if (ret == INFINITE)
				{
					// Communication error
					MsgBoxEx(hWnd, MB_ICONSTOP, _U(IDS_DOWNLOAD_ERROR+skip), f->FileName);

					FileClose(dest);
					ViCloseFile(down);
					ViInstallDlgCancel(hWnd);

					return;
				}

				// Draw progress
				currentsize += ret;

				if (totalsize != 0)
				{
					UniFormat(tmp, sizeof(tmp), _U(IDS_DOWNLOADING+skip),
						((float)totalsize) / 1024.0f / 1024.0f,
						((float)currentsize) / 1024.0f / 1024.0f);

					pos = (UINT)(((float)currentsize) * 100.0f / ((float)totalsize));
				}
				else
				{
					UniFormat(tmp, sizeof(tmp), _U(IDS_DOWNLOADING2+skip),
						((float)currentsize) / 1024.0f / 1024.0f);
					pos = (UINT)(((float)currentsize) * 100.0f / (1024.0f * 1024.0f * 10.0f));
				}

				ViInstallDlgSetText(d, hWnd, S_SIZEINFO, tmp);
				ViInstallDlgSetPos(hWnd, pos);

				if (ret == 0)
				{
					// Download Complete
					break;
				}
				else
				{
					FileWrite(dest, d->Buf, ret);
				}
			}

			ViCloseFile(down);
			FileClose(dest);
		}
	}

	UniStrCpy(setting.DownloadedInstallerPathW, sizeof(setting.DownloadedInstallerPathW),
		files[0].DestPathW);

	if (num_files >= 2)
	{
		UniStrCpy(setting.DownloadedSettingPathW, sizeof(setting.DownloadedSettingPathW),
			files[1].DestPathW);
	}

	PostMessageA(hWnd, WM_VI_DOWNLOAD_FINISHED, 0, 0);
}

// Operation of the progress bar
void ViInstallDlgSetPos(HWND hWnd, UINT pos)
{
	PostMessage(hWnd, WM_VI_SETPOS, 0, pos);
}

// Set the text
void ViInstallDlgSetText(VI_INSTALL_DLG *d, HWND hWnd, UINT id, wchar_t *text)
{
	DWORD value = 0;
	// Validate arguments
	if (d == NULL)
	{
		return;
	}

	if (d->Halt)
	{
		return;
	}

	SendMessageTimeout(hWnd, WM_VI_SETTEXT, id, (LPARAM)text, SMTO_BLOCK, 200, &value);
}

// Cancel
void ViInstallDlgCancel(HWND hWnd)
{
	PostMessageA(hWnd, WM_VI_CANCEL, 0, 0);
}

// Installer operation start
void ViInstallDlgOnStart(HWND hWnd, VI_INSTALL_DLG *d)
{
	// Validate arguments
	if (hWnd == NULL || d == NULL)
	{
		return;
	}

	// Start the download thread
	ViDownloadThreadStart(d);
}

// Cancel the installation
void ViInstallDlgOnClose(HWND hWnd, VI_INSTALL_DLG *d)
{
	// Validate arguments
	if (hWnd == NULL || d == NULL)
	{
		return;
	}

	if (d->DialogCanceling)
	{
		return;
	}
	if (d->NoClose)
	{
		return;
	}

	d->DialogCanceling = true;

	// Disable the cancel button
	Disable(hWnd, IDCANCEL);

	// Stop the download thread if it runs
	ViDownloadThreadStop(d);

	// Exit the dialog
	EndDialog(hWnd, 0);
}

// Installer procedure
UINT ViInstallDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, void *param)
{
	VI_INSTALL_DLG *d = (VI_INSTALL_DLG *)param;
	UINT pos;
	wchar_t *text;
	// Validate arguments
	if (hWnd == NULL)
	{
		return 0;
	}

	switch (msg)
	{
	case WM_INITDIALOG:
		ViInstallDlgOnInit(hWnd, param);
		break;

	case WM_TIMER:
		switch (wParam)
		{
		case 1:
			KillTimer(hWnd, 1);
			ViInstallDlgOnStart(hWnd, d);
			break;
		}
		break;

	case WM_VI_SETPOS:
		// Setting the progress bar
		pos = (UINT)lParam;
		SetPos(hWnd, P_PROGRESS, MAKESURE(pos, 0, 100));
		break;

	case WM_VI_SETTEXT:
		// Set the string
		text = (wchar_t *)lParam;
		SetText(hWnd, (UINT)wParam, text);
		break;

	case WM_VI_CANCEL:
		// There was a cancellation from the thread side
		ViInstallDlgOnClose(hWnd, d);
		break;

	case WM_VI_DOWNLOAD_FINISHED:
		// Download Complete
		ViInstallProcessStart(hWnd, d);
		break;

	case WM_COMMAND:
		switch (wParam)
		{
		case IDCANCEL:
			ViInstallDlgOnClose(hWnd, d);
			break;
		}
		break;

	case WM_QUERYENDSESSION:
		d->WindowsShutdowning = true;
		break;

	case WM_CLOSE:
		break;
	}

	return 0;
}

// Show the dialog
void ViInstallDlg()
{
	VI_INSTALL_DLG d;

	Zero(&d, sizeof(d));

	d.BufSize = 65535;
	d.Buf = Malloc(d.BufSize);

	Dialog(NULL, D_INSTALL, ViInstallDlgProc, &d);

	Free(d.Buf);
}

// Read the inf file from the buffer
bool ViLoadInfFromBuf(VI_SETTING *set, BUF *buf)
{
	bool ret;
	if (set == NULL || buf == NULL)
	{
		return false;
	}

	Zero(set, sizeof(VI_SETTING));

	SeekBuf(buf, 0, 0);
	while (true)
	{
		char *tmp = CfgReadNextLine(buf);
		TOKEN_LIST *tokens;

		if (tmp == NULL)
		{
			break;
		}

		tokens = ParseToken(tmp, " \t");

		if (tokens != NULL)
		{
			if (tokens->NumTokens >= 2)
			{
				if (StartWith(tokens->Token[0], "#") == false
					|| StartWith(tokens->Token[0], "//") == false)
				{
					char *name, *value;
					name = tokens->Token[0];
					value = tokens->Token[1];

					if (StrCmpi(name, "VpnInstallBuild") == 0)
					{
						set->VpnInstallBuild = ToInt(value);
					}
					else if (StrCmpi(name, "NormalMode") == 0)
					{
						set->NormalMode = ToBool(value);
					}
					else if (StrCmpi(name, "VpnSettingPath") == 0)
					{
						StrCpy(set->SettingPath, sizeof(set->SettingPath), value);
					}
					else if (StrCmpi(name, "VpnClientBuild") == 0)
					{
						set->x86.Build = ToInt(value);
					}
					else if (StrCmpi(name, "VpnClientPath") == 0)
					{
						StrCpy(set->x86.Path, sizeof(set->x86.Path), value);
					}
				}
			}
			FreeToken(tokens);
		}

		Free(tmp);
	}

	ret = false;

	StrCpy(set->x86.VpnCMgrExeFileName, sizeof(set->x86.VpnCMgrExeFileName), (MsIsX64() ? "vpncmgr_x64.exe" : "vpncmgr.exe"));

	if (set->VpnInstallBuild != 0)
	{
		if (set->x86.Build != 0 && IsEmptyStr(set->x86.Path) == false)
		{
			set->x86.Supported = true;
			ret = true;
		}
	}

	return ret;
}

// Read the inf file
bool ViLoadInf(VI_SETTING *set, char *filename)
{
	BUF *b;
	bool ret = false;
	// Validate arguments
	if (set == NULL || filename == NULL)
	{
		return false;
	}

	b = ReadDump(filename);
	if (b == NULL)
	{
		return false;
	}

	ret = ViLoadInfFromBuf(set, b);

	FreeBuf(b);

	return ret;
}

// Get the product information from the Msi
bool ViMsiGetProductInfo(char *product_code, char *name, char *buf, UINT size)
{
	UINT ret;
	char tmp[MAX_SIZE];
	DWORD sz;
	// Validate arguments
	if (product_code == NULL || name == NULL || buf == NULL)
	{
		return false;
	}

	sz = sizeof(tmp);

	ret = MsiGetProductInfoA(product_code, name, tmp, &sz);
	if (ret != ERROR_SUCCESS)
	{
		return false;
	}

	StrCpy(buf, size, tmp);

	return true;
}

// Extract the build number from the version string
UINT ViVersionStrToBuild(char *str)
{
	TOKEN_LIST *t;
	UINT ret;
	// Validate arguments
	if (str == NULL)
	{
		return 0;
	}

	t = ParseToken(str, ".");
	if (t == NULL)
	{
		return 0;
	}

	ret = 0;

	if (t->NumTokens == 3)
	{
		ret = ToInt(t->Token[2]);
	}

	FreeToken(t);

	return ret;
}

// Get the current installation state for the given architecture
void ViLoadCurrentInstalledStatusForArch(VI_SETTING_ARCH *a)
{
	char tmp[MAX_SIZE];
	UINT build;
	wchar_t *dir;
	// Validate arguments
	if (a == NULL)
	{
		return;
	}
	if (a->Supported == false)
	{
		// Unsupported
		return;
	}

	// Read from the registry
	Format(tmp, sizeof(tmp), "%s\\%s", SW_REG_KEY, "vpnclient");

	build = MsRegReadIntEx2(REG_LOCAL_MACHINE, tmp, "InstalledBuild", false, true);

	dir = MsRegReadStrEx2W(REG_LOCAL_MACHINE, tmp, "InstalledDir", false, true);

	if (build == 0 || UniIsEmptyStr(dir))
	{
		// Not installed
		a->CurrentInstalled = false;
	}
	else
	{
		// Installed
		a->CurrentInstalled = true;
		a->CurrentInstalledBuild = build;

		UniStrCpy(a->CurrentInstalledPathW, sizeof(a->CurrentInstalledPathW), dir);
	}

	Free(dir);
}

// Get the best architecture for the current CPU
VI_SETTING_ARCH *ViGetSuitableArchForCpu()
{
	return &setting.x86;
}

// Get the current installation state
void ViLoadCurrentInstalledStates()
{
	ViLoadCurrentInstalledStatusForArch(&setting.x86);
}

// Main process
void ViMain()
{
	char tmp[MAX_PATH];
	UINT ostype = GetOsInfo()->OsType;
	VI_SETTING_ARCH *suitable;
	TOKEN_LIST *t;
	UINT i;

	if (OS_IS_WINDOWS_NT(ostype) == false ||
		GET_KETA(ostype, 100) <= 1)
	{
		// The OS is too old
		MsgBox(NULL, MB_ICONEXCLAMATION, _U(IDS_BAD_OS+skip));
		return;
	}

	Zero(&setting, sizeof(setting));

	// Read the inf file
	Format(tmp, sizeof(tmp), "%s\\%s", MsGetExeDirName(), VI_INF_FILENAME);
	if (ViLoadInf(&setting, tmp) == false)
	{
		// Failure
		MsgBoxEx(NULL, MB_ICONSTOP, _U(IDS_INF_LOAD_FAILED+skip), VI_INF_FILENAME);
		return;
	}

	ViSetSkip();

	// Parse the command line options
	t = GetCommandLineToken();

	for (i = 0;i < t->NumTokens;i++)
	{
		char *s = t->Token[i];

		if (IsEmptyStr(s) == false)
		{
			if (StartWith(s, "/") || StartWith(s, "-"))
			{
				if (StrCmpi(&s[1], "web") == 0)
				{
					setting.WebMode = true;
				}
			}
			else
			{
				StrCpy(setting.SettingPath, sizeof(setting.SettingPath), s);
			}
		}
	}

	FreeToken(t);

	suitable = ViGetSuitableArchForCpu();

	// Security check
	if (setting.WebMode)
	{
		bool ok = true;

		if (ViIsInternetFile(suitable->Path) == false)
		{
			ok = false;
		}

		if (IsEmptyStr(setting.SettingPath) == false)
		{
			if (ViIsInternetFile(setting.SettingPath) == false)
			{
				ok = false;
			}
		}

		if (ok == false)
		{
			// Security breach
			MsgBox(NULL, MB_ICONEXCLAMATION, _U(IDS_SECURITY_ERROR+skip));
			return;
		}
	}

	// Get the current installation state
	ViLoadCurrentInstalledStates();

	if (suitable->Supported == false)
	{
		// This CPU isn't supported
		MsgBox(NULL, MB_ICONEXCLAMATION, _U(IDS_CPU_NOT_SUPPORTED+skip));
		return;
	}

	if (suitable->CurrentInstalled && suitable->Build <= suitable->CurrentInstalledBuild)
	{
		// Do not download client software since it has already been installed
		setting.DownloadNotRequired = true;
	}

	// Show the dialog
	ViInstallDlg();
}

// Generate the temporary directory name for vpnsmgr
void ViGenerateVpnSMgrTempDirName(char *name, UINT size, UINT build)
{
	// Validate arguments
	if (name == NULL)
	{
		return;
	}

	Format(name, size, "%s\\px_" GC_SW_SOFTETHER_PREFIX "vpnsmgr_%u", MsGetTempDir(), build);
}

// Compare the string resources
int ViCompareString(void *p1, void *p2)
{
	VI_STRING *s1, *s2;
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}

	s1 = *(VI_STRING **)p1;
	s2 = *(VI_STRING **)p2;

	if (s1 == NULL || s2 == NULL)
	{
		return 0;
	}

	if (s1->Id > s2->Id)
	{
		return 1;
	}
	else if (s1->Id < s2->Id)
	{
		return -1;
	}
	return 0;
}

// Reading a string resource
wchar_t *ViLoadString(HINSTANCE hInst, UINT id)
{
	wchar_t *ret = NULL;

	if (OS_IS_WINDOWS_9X(GetOsInfo()->OsType))
	{
		char *a = ViLoadStringA(hInst, id);
		if (a != NULL)
		{
			ret = CopyStrToUni(a);
			Free(a);
		}
	}
	else
	{
		UINT tmp_size = 60000;
		wchar_t *tmp = Malloc(tmp_size);

		if (LoadStringW(hInst, id, tmp, tmp_size) != 0)
		{
			ret = CopyUniStr(tmp);
		}

		Free(tmp);
	}

	return ret;
}
char *ViLoadStringA(HINSTANCE hInst, UINT id)
{
	UINT tmp_size = 60000;
	char *tmp = Malloc(tmp_size);
	char *ret = NULL;

	if (LoadStringA(hInst, id, tmp, tmp_size) != 0)
	{
		ret = CopyStr(tmp);
	}

	Free(tmp);

	return ret;
}

// Acquisition of string
wchar_t *ViGetString(UINT id)
{
	VI_STRING t, *s;
	wchar_t *ret = NULL;

	Zero(&t, sizeof(t));
	t.Id = id;

	LockList(string_table);
	{
		s = Search(string_table, &t);

		if (s != NULL)
		{
			ret = s->String;
		}
	}
	UnlockList(string_table);

	return ret;
}
char *ViGetStringA(UINT id)
{
	VI_STRING t, *s;
	char *ret = NULL;

	Zero(&t, sizeof(t));
	t.Id = id;

	LockList(string_table);
	{
		s = Search(string_table, &t);

		if (s != NULL)
		{
			ret = s->StringA;
		}
	}
	UnlockList(string_table);

	return ret;
}

// Calculate the difference between the the current language configuration and the base of the string table
void ViSetSkip()
{
	skip = 0;

	if (MsIsCurrentUserLocaleIdJapanese() == false)
	{
		skip = MESSAGE_OFFSET_EN - MESSAGE_OFFSET_JP;
	}
}

// Read the string table
void ViLoadStringTables()
{
	UINT i, n;
	HINSTANCE hInst = GetModuleHandle(NULL);

	string_table = NewList(ViCompareString);

	n = 0;
	for (i = 1;;i++)
	{
		wchar_t *str = ViLoadString(hInst, i);
		if (str != NULL)
		{
			VI_STRING *s;
			n = 0;

			s = ZeroMalloc(sizeof(VI_STRING));
			s->Id = i;
			s->String = str;
			s->StringA = CopyUniToStr(str);

			Insert(string_table, s);
		}
		else
		{
			n++;
			if (n >= 1500)
			{
				break;
			}
		}
	}
}

// Release the string table
void ViFreeStringTables()
{
	UINT i;
	if (string_table == NULL)
	{
		return;
	}

	for (i = 0;i < LIST_NUM(string_table);i++)
	{
		VI_STRING *s = LIST_DATA(string_table, i);

		Free(s->String);
		Free(s->StringA);
		Free(s);
	}

	ReleaseList(string_table);
	string_table = NULL;
}

// WinMain function
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, char *CmdLine, int CmdShow)
{
	INSTANCE *instance;
	InitProcessCallOnce();
#if defined(_DEBUG) || defined(DEBUG)	// In VC++ compilers, the macro is "_DEBUG", not "DEBUG".
	is_debug = true;
#else
	is_debug = false;
#endif
	MayaquaMinimalMode();
	// If set memcheck = true, the program will be vitally slow since it will log all malloc() / realloc() / free() calls to find the cause of memory leak.
	// For normal debug we set memcheck = false.
	// Please set memcheck = true if you want to test the cause of memory leaks.
	InitMayaqua(false, is_debug, 0, NULL);
	InitCedar();
	ViSetSkip();
	ViLoadStringTables();
	InitWinUi(_U(IDS_TITLE+skip), _A(IDS_FONT+skip), ToInt(_A(IDS_FONT_SIZE+skip)));
	instance = NewSingleInstance(VI_INSTANCE_NAME);
	if (instance == NULL)
	{
		MsgBox(NULL, MB_ICONINFORMATION, _U(IDS_INSTANCE_EXISTS+skip));
	}
	else
	{
		ViMain();
		FreeSingleInstance(instance);
		if (sleep_before_exit)
		{
			SleepThread(60 * 1000);
		}
	}
	FreeWinUi();
	ViFreeStringTables();
	FreeCedar();
	FreeMayaqua();
	return 0;
}


