text: // chrlauncher
  // Copyright (c) 2015-2021 Henry++
  
  #include "routine.h"
  
  #include "main.h"
  #include "rapp.h"
  
  #include "CpuArch.h"
  
  #include "7z.h"
  #include "7zAlloc.h"
  #include "7zBuf.h"
  #include "7zCrc.h"
  #include "7zFile.h"
  #include "7zVersion.h"
  
  #include "miniz.h"
  
  #include "resource.h"
  
  PBROWSER_INFORMATION browser_info = NULL;
  
  R_QUEUED_LOCK lock_download = PR_QUEUED_LOCK_INIT;
  R_QUEUED_LOCK lock_thread = PR_QUEUED_LOCK_INIT;
  
  R_WORKQUEUE workqueue;
  
  BOOL CALLBACK activate_browser_window_callback (_In_ HWND hwnd, _In_ LPARAM lparam)
  {
  	PBROWSER_INFORMATION pbi;
  	WCHAR path[1024];
  	ULONG pid;
  	ULONG length;
  	HANDLE hproc;
  
  	GetWindowThreadProcessId (hwnd, &pid);
  
  	if (HandleToUlong (NtCurrentProcessId ()) == pid)
  		return TRUE;
  
  	if (!_r_wnd_isvisible (hwnd))
  		return TRUE;
  
  	hproc = OpenProcess (PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
  
  	if (!_r_fs_isvalidhandle (hproc))
  		return TRUE;
  
  	pbi = (PBROWSER_INFORMATION)lparam;
  
  	length = RTL_NUMBER_OF (path);
  
  	QueryFullProcessImageName (hproc, 0, path, &length);
  
  	CloseHandle (hproc);
  
  	if (_r_str_isequal2 (&pbi->binary_path->sr, path, TRUE))
  	{
  		_r_wnd_toggle (hwnd, TRUE);
  		return FALSE;
  	}
  
  	return TRUE;
  }
  
  FORCEINLINE VOID activate_browser_window (_In_ PBROWSER_INFORMATION pbi)
  {
  	EnumWindows (&activate_browser_window_callback, (LPARAM)pbi);
  }
  
  BOOLEAN path_is_url (_In_ LPCWSTR path)
  {
  	if (PathMatchSpec (path, L"*.ini"))
  		return FALSE;
  
  	if (PathIsURL (path) || PathIsHTMLFile (path))
  		return TRUE;
  
  	static LPCWSTR types[] = {
  		L"application/pdf",
  		L"image/svg+xml",
  		L"image/webp",
  		L"text/html",
  	};
  
  	for (SIZE_T i = 0; i < RTL_NUMBER_OF (types); i++)
  	{
  		if (PathIsContentType (path, types[i]))
  			return TRUE;
  	}
  
  	return FALSE;
  }
  
  VOID update_browser_info (_In_ HWND hwnd, _In_ PBROWSER_INFORMATION pbi)
  {
  	PR_STRING date_dormat;
  	PR_STRING localized_string = NULL;
  	R_STRINGREF empty_string;
  
  	_r_obj_initializestringrefconst (&empty_string, _r_locale_getstring (IDS_STATUS_NOTFOUND));
  
  	date_dormat = _r_format_unixtime_ex (pbi->timestamp, FDTF_SHORTDATE | FDTF_SHORTTIME);
  
  	_r_obj_movereference (&localized_string, _r_format_string (L"%s:", _r_locale_getstring (IDS_BROWSER)));
  	_r_ctrl_settablestring (hwnd, IDC_BROWSER, &localized_string->sr, IDC_BROWSER_DATA, pbi->browser_name ? &pbi->browser_name->sr : &empty_string);
  
  	_r_obj_movereference (&localized_string, _r_format_string (L"%s:", _r_locale_getstring (IDS_CURRENTVERSION)));
  	_r_ctrl_settablestring (hwnd, IDC_CURRENTVERSION, &localized_string->sr, IDC_CURRENTVERSION_DATA, pbi->current_version ? &pbi->current_version->sr : &empty_string);
  
  	_r_obj_movereference (&localized_string, _r_format_string (L"%s:", _r_locale_getstring (IDS_VERSION)));
  	_r_ctrl_settablestring (hwnd, IDC_VERSION, &localized_string->sr, IDC_VERSION_DATA, pbi->new_version ? &pbi->new_version->sr : &empty_string);
  
  	_r_obj_movereference (&localized_string, _r_format_string (L"%s:", _r_locale_getstring (IDS_DATE)));
  	_r_ctrl_settablestring (hwnd, IDC_DATE, &localized_string->sr, IDC_DATE_DATA, date_dormat ? &date_dormat->sr : &empty_string);
  
  	if (date_dormat)
  		_r_obj_dereference (date_dormat);
  
  	if (localized_string)
  		_r_obj_dereference (localized_string);
  }
  
  VOID init_browser_info (_Inout_ PBROWSER_INFORMATION pbi)
  {
  	static R_STRINGREF bin_names[] = {
  		PR_STRINGREF_INIT (L"brave.exe"),
  		PR_STRINGREF_INIT (L"firefox.exe"),
  		PR_STRINGREF_INIT (L"basilisk.exe"),
  		PR_STRINGREF_INIT (L"palemoon.exe"),
  		PR_STRINGREF_INIT (L"waterfox.exe"),
  		PR_STRINGREF_INIT (L"dragon.exe"),
  		PR_STRINGREF_INIT (L"iridium.exe"),
  		PR_STRINGREF_INIT (L"iron.exe"),
  		PR_STRINGREF_INIT (L"opera.exe"),
  		PR_STRINGREF_INIT (L"slimjet.exe"),
  		PR_STRINGREF_INIT (L"vivaldi.exe"),
  		PR_STRINGREF_INIT (L"chromium.exe"),
  		PR_STRINGREF_INIT (L"chrome.exe"), // default
  	};
  
  	static R_STRINGREF separator_sr = PR_STRINGREF_INIT (L"\\");
  
  	// Reset
  	pbi->is_hasurls = FALSE;
  
  	pbi->urls[0] = UNICODE_NULL;
  	pbi->args[0] = UNICODE_NULL;
  
  	// Configure paths
  	PR_STRING binary_dir;
  	PR_STRING binary_name;
  	PR_STRING string;
  
  	binary_dir = _r_config_getstringexpand (L"ChromiumDirectory", L".\\bin");
  	binary_name = _r_config_getstring (L"ChromiumBinary", L"chrome.exe");
  
  	if (!binary_dir || !binary_name)
  	{
  		RtlRaiseStatus (STATUS_INVALID_PARAMETER);
  		return;
  	}
  
  	_r_obj_movereference (&pbi->binary_dir, binary_dir);
  	_r_str_trimstring2 (pbi->binary_dir, L"\\", 0);
  
  	string = _r_path_search (pbi->binary_dir->buffer);
  
  	if (string)
  		_r_obj_movereference (&pbi->binary_dir, string);
  
  	_r_obj_movereference (&pbi->binary_path, _r_obj_concatstringrefs (3, &pbi->binary_dir->sr, &separator_sr, &binary_name->sr));
  
  	if (!pbi->binary_dir || !pbi->binary_path)
  	{
  		RtlRaiseStatus (STATUS_OBJECT_PATH_NOT_FOUND);
  		return;
  	}
  
  	if (!_r_fs_exists (pbi->binary_path->buffer))
  	{
  		for (SIZE_T i = 0; i < RTL_NUMBER_OF (bin_names); i++)
  		{
  			_r_obj_movereference (&pbi->binary_path, _r_obj_concatstringrefs (3, &pbi->binary_dir->sr, &separator_sr, &bin_names[i]));
  
  			if (_r_fs_exists (pbi->binary_path->buffer))
  				break;
  		}
  
  		if (_r_obj_isstringempty (pbi->binary_path) || !_r_fs_exists (pbi->binary_path->buffer))
  		{
  			_r_obj_movereference (&pbi->binary_path, _r_obj_concatstringrefs (3, &pbi->binary_dir->sr, &separator_sr, &binary_name->sr)); // fallback (use defaults)
  		}
  	}
  
  	_r_obj_dereference (binary_name);
  
  	_r_obj_movereference (&pbi->cache_path, _r_format_string (L"%s\\%s_%" TEXT (PR_ULONG) L".bin", _r_sys_gettempdirectory (), _r_app_getnameshort (), _r_obj_getstringhash (pbi->binary_path)));
  
  	// Get browser architecture
  	pbi->architecture = _r_config_getinteger (L"ChromiumArchitecture", 0);
  
  	if (pbi->architecture != 64 && pbi->architecture != 32)
  	{
  		pbi->architecture = 0;
  
  		// ...by executable
  		if (_r_fs_exists (pbi->binary_path->buffer))
  		{
  			ULONG binary_type;
  
  			if (GetBinaryType (pbi->binary_path->buffer, &binary_type))
  			{
  				pbi->architecture = (binary_type == SCS_64BIT_BINARY) ? 64 : 32;
  			}
  		}
  
  		// ...by processor architecture
  		if (!pbi->architecture)
  		{
  			SYSTEM_INFO si = {0};
  			GetNativeSystemInfo (&si);
  
  			pbi->architecture = (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) ? 64 : 32;
  		}
  	}
  
  	if (pbi->architecture != 32 && pbi->architecture != 64)
  	{
  		pbi->architecture = 32; // default architecture
  	}
  
  	// Set common data
  	PR_STRING browser_type = _r_config_getstring (L"ChromiumType", L"dev-codecs-sync");
  	PR_STRING browser_arguments = _r_config_getstringexpand (L"ChromiumCommandLine", L"--user-data-dir=..\\profile --no-default-browser-check");
  
  	if (browser_type)
  	{
  		_r_obj_movereference (&pbi->browser_type, browser_type);
  	}
  
  	if (browser_arguments)
  	{
  		_r_str_copy (pbi->args, RTL_NUMBER_OF (pbi->args), browser_arguments->buffer);
  
  		_r_obj_dereference (browser_arguments);
  	}
  
  	_r_obj_movereference (&pbi->browser_name, _r_format_string (L"%s (%d-bit)", pbi->browser_type->buffer, pbi->architecture));
  	_r_obj_movereference (&pbi->current_version, _r_res_queryversionstring (pbi->binary_path->buffer));
  
  	// Parse command line
  	{
  		LPWSTR *arga;
  		LPWSTR key;
  		SIZE_T first_arg_length = 0;
  		INT numargs;
  
  		arga = CommandLineToArgvW (_r_sys_getimagecommandline (), &numargs);
  
  		if (arga)
  		{
  			first_arg_length = _r_str_getlength (arga[0]);
  
  			if (numargs > 1)
  			{
  				for (INT i = 1; i < numargs; i++)
  				{
  					key = arga[i];
  
  					if (*key == L'/')
  					{
  						if (_r_str_compare_length (key, L"/autodownload", 13) == 0)
  						{
  							pbi->is_autodownload = TRUE;
  						}
  						else if (_r_str_compare_length (key, L"/bringtofront", 13) == 0)
  						{
  							pbi->is_bringtofront = TRUE;
  						}
  						else if (_r_str_compare_length (key, L"/forcecheck", 11) == 0)
  						{
  							pbi->is_forcecheck = TRUE;
  						}
  						else if (_r_str_compare_length (key, L"/wait", 5) == 0)
  						{
  							pbi->is_waitdownloadend = TRUE;
  						}
  						else if (_r_str_compare_length (key, L"/update", 7) == 0)
  						{
  							pbi->is_onlyupdate = TRUE;
  						}
  					}
  					else if (*key == L'-')
  					{
  						if (!pbi->is_opennewwindow)
  						{
  							if (
  								_r_str_compare_length (key, L"-new-tab", 8) == 0 ||
  								_r_str_compare_length (key, L"-new-window", 11) == 0 ||
  								_r_str_compare_length (key, L"--new-window", 12) == 0 ||
  								_r_str_compare_length (key, L"-new-instance", 13) == 0
  								)
  							{
  								pbi->is_opennewwindow = TRUE;
  							}
  						}
  
  						// there is Chromium arguments
  						//_r_str_appendformat (pbi->urls, RTL_NUMBER_OF (pbi->urls), L" %s", key);
  					}
  					else if (path_is_url (key))
  					{
  						// there is Chromium url
  						pbi->is_hasurls = TRUE;
  					}
  				}
  			}
  
  			LocalFree (arga);
  		}
  
  		if (pbi->is_hasurls)
  		{
  			_r_str_copy (pbi->urls, RTL_NUMBER_OF (pbi->urls), _r_sys_getimagecommandline () + first_arg_length + 2);
  			_r_str_trim (pbi->urls, L" ");
  		}
  	}
  
  	pbi->check_period = _r_config_getinteger (L"ChromiumCheckPeriod", 2);
  
  	if (pbi->check_period == -1)
  		pbi->is_forcecheck = TRUE;
  
  	// set default config
  	if (!pbi->is_autodownload)
  		pbi->is_autodownload = _r_config_getboolean (L"ChromiumAutoDownload", FALSE);
  
  	if (!pbi->is_bringtofront)
  		pbi->is_bringtofront = _r_config_getboolean (L"ChromiumBringToFront", TRUE);
  
  	if (!pbi->is_waitdownloadend)
  		pbi->is_waitdownloadend = _r_config_getboolean (L"ChromiumWaitForDownloadEnd", TRUE);
  
  	if (!pbi->is_onlyupdate)
  		pbi->is_onlyupdate = _r_config_getboolean (L"ChromiumUpdateOnly", FALSE);
  
  	// rewrite options when update-only mode is enabled
  	if (pbi->is_onlyupdate)
  	{
  		pbi->is_forcecheck = TRUE;
  		pbi->is_bringtofront = TRUE;
  		pbi->is_waitdownloadend = TRUE;
  	}
  }
  
  VOID _app_setstatus (_In_ HWND hwnd, _In_opt_ LPCWSTR text, _In_opt_ ULONG64 v, _In_opt_ ULONG64 t)
  {
  	LONG64 percent = 0;
  
  	if (!v && t)
  	{
  		_r_status_settextformat (hwnd, IDC_STATUSBAR, 0, L"%s 0%%", text);
  
  		if (!_r_str_isempty (text))
  		{
  			_r_tray_setinfoformat (hwnd, &GUID_TrayIcon, NULL, L"%s\r\n%s: 0%%", _r_app_getname (), text);
  		}
  		else
  		{
  			_r_tray_setinfo (hwnd, &GUID_TrayIcon, NULL, _r_app_getname ());
  		}
  	}
  	else if (v && t)
  	{
  		percent = _r_calc_clamp64 (_r_calc_percentof64 (v, t), 0, 100);
  
  		_r_status_settextformat (hwnd, IDC_STATUSBAR, 0, L"%s %" PR_LONG64 L"%%", text, percent);
  
  		if (!_r_str_isempty (text))
  		{
  			_r_tray_setinfoformat (hwnd, &GUID_TrayIcon, NULL, L"%s\r\n%s: %" PR_LONG64 L"%%", _r_app_getname (), text, percent);
  		}
  		else
  		{
  			_r_tray_setinfo (hwnd, &GUID_TrayIcon, NULL, _r_app_getname ());
  		}
  	}
  	else
  	{
  		_r_status_settext (hwnd, IDC_STATUSBAR, 0, text);
  
  		if (!_r_str_isempty (text))
  		{
  			_r_tray_setinfoformat (hwnd, &GUID_TrayIcon, NULL, L"%s\r\n%s", _r_app_getname (), text);
  		}
  		else
  		{
  			_r_tray_setinfo (hwnd, &GUID_TrayIcon, NULL, _r_app_getname ());
  		}
  	}
  
  	SendDlgItemMessage (hwnd, IDC_PROGRESS, PBM_SETPOS, (WPARAM)(LONG)percent, 0);
  }
  
  VOID _app_cleanupoldbinary (_In_ PBROWSER_INFORMATION pbi)
  {
  	WIN32_FIND_DATA wfd;
  	PR_STRING path;
  	HANDLE hfile;
  
  	if (!pbi->binary_dir)
  		return;
  
  	// remove old binary directory
  	// https://github.com/henrypp/chrlauncher/issues/180
  
  	path = _r_obj_concatstrings (2, pbi->binary_dir->buffer, L"\\*");
  
  	hfile = FindFirstFile (path->buffer, &wfd);
  
  	if (_r_fs_isvalidhandle (hfile))
  	{
  		do
  		{
  			if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
  				continue;
  
  			if (wfd.cFileName[0] == L'.')
  				continue;
  
  			_r_obj_movereference (&path, _r_obj_concatstrings (3, pbi->binary_dir->buffer, L"\\", wfd.cFileName));
  
  			_r_fs_deletedirectory (path->buffer, TRUE);
  		}
  		while (FindNextFile (hfile, &wfd));
  
  		FindClose (hfile);
  	}
  
  	_r_obj_dereference (path);
  }
  
  VOID _app_cleanupoldmanifest (_In_ PBROWSER_INFORMATION pbi)
  {
  	WIN32_FIND_DATA wfd;
  	PR_STRING path;
  	HANDLE hfile;
  
  	if (!pbi->binary_dir || !pbi->current_version)
  		return;
  
  	path = _r_obj_concatstrings (2, pbi->binary_dir->buffer, L"\\*.manifest");
  
  	hfile = FindFirstFile (path->buffer, &wfd);
  
  	if (_r_fs_isvalidhandle (hfile))
  	{
  		do
  		{
  			if (_r_str_isstartswith2 (&pbi->current_version->sr, wfd.cFileName, TRUE))
  			{
  				_r_obj_movereference (&path, _r_obj_concatstrings (3, pbi->binary_dir->buffer, L"\\", wfd.cFileName));
  
  				_r_fs_deletefile (path->buffer, 0);
  			}
  		}
  		while (FindNextFile (hfile, &wfd));
  
  		FindClose (hfile);
  	}
  
  	_r_obj_dereference (path);
  }
  
  BOOLEAN _app_browserisrunning (_In_ PBROWSER_INFORMATION pbi)
  {
  	HANDLE hfile;
  
  	hfile = CreateFile (pbi->binary_path->buffer, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
  
  	if (!_r_fs_isvalidhandle (hfile))
  		return (GetLastError () == ERROR_SHARING_VIOLATION);
  
  	CloseHandle (hfile);
  
  	return FALSE;
  }
  
  VOID _app_openbrowser (_In_ PBROWSER_INFORMATION pbi)
  {
  	BOOLEAN is_running;
  
  	if (_r_obj_isstringempty (pbi->binary_path) || !_r_fs_exists (pbi->binary_path->buffer))
  		return;
  
  	is_running = _app_browserisrunning (pbi);
  
  	if (is_running && !pbi->is_hasurls && !pbi->is_opennewwindow)
  	{
  		activate_browser_window (pbi);
  		return;
  	}
  
  	WCHAR args[512];
  	_r_str_copy (args, RTL_NUMBER_OF (args), pbi->args);
  
  	if (pbi->is_hasurls)
  	{
  		_r_str_appendformat (args, RTL_NUMBER_OF (args), L" %s", pbi->urls);
  
  		// reset
  		pbi->is_hasurls = FALSE;
  		pbi->urls[0] = UNICODE_NULL;
  	}
  
  	pbi->is_opennewwindow = FALSE;
  
  	PR_STRING arg;
  
  	arg = _r_obj_concatstrings (4, L"\"", pbi->binary_path->buffer, L"\" ", args);
  
  	if (!_r_sys_createprocess (pbi->binary_path->buffer, arg->buffer, pbi->binary_dir->buffer))
  		_r_show_errormessage (_r_app_gethwnd (), NULL, GetLastError (), NULL);
  
  	_r_obj_dereference (arg);
  }
  
  BOOLEAN _app_ishaveupdate (_In_ PBROWSER_INFORMATION pbi)
  {
  	return !_r_obj_isstringempty (pbi->download_url) && !_r_obj_isstringempty (pbi->new_version);
  }
  
  BOOLEAN _app_isupdatedownloaded (_In_ PBROWSER_INFORMATION pbi)
  {
  	return !_r_obj_isstringempty (pbi->cache_path) && _r_fs_exists (pbi->cache_path->buffer);
  }
  
  BOOLEAN _app_isupdaterequired (_In_ PBROWSER_INFORMATION pbi)
  {
  	if (pbi->is_forcecheck)
  		return TRUE;
  
  	if (pbi->check_period && ((_r_unixtime_now () - _r_config_getlong64 (L"ChromiumLastCheck", 0)) >= _r_calc_days2seconds (pbi->check_period)))
  		return TRUE;
  
  	return FALSE;
  }
  
  UINT _app_getactionid (_In_ PBROWSER_INFORMATION pbi)
  {
  	if (_app_isupdatedownloaded (pbi))
  		return IDS_ACTION_INSTALL;
  
  	else if (_app_ishaveupdate (pbi))
  		return IDS_ACTION_DOWNLOAD;
  
  	return IDS_ACTION_CHECK;
  }
  
  BOOLEAN _app_checkupdate (_In_ HWND hwnd, _In_ PBROWSER_INFORMATION pbi, _Out_ PBOOLEAN pis_error)
  {
  	*pis_error = FALSE;
  
  	if (_app_ishaveupdate (pbi))
  		return TRUE;
  
  	PR_HASHTABLE result = NULL;
  
  	BOOLEAN is_exists = _r_fs_exists (pbi->binary_path->buffer);
  	BOOLEAN is_checkupdate = _app_isupdaterequired (pbi);
  
  	BOOLEAN result2 = FALSE;
  
  	_app_setstatus (hwnd, _r_locale_getstring (IDS_STATUS_CHECK), 0, 0);
  
  	SAFE_DELETE_REFERENCE (pbi->new_version);
  	pbi->timestamp = 0;
  
  	update_browser_info (hwnd, pbi);
  
  	if (!is_exists || is_checkupdate)
  	{
  		R_DOWNLOAD_INFO download_info;
  		PR_STRING update_url;
  		PR_STRING url;
  
  		update_url = _r_config_getstring (L"ChromiumUpdateUrl", CHROMIUM_UPDATE_URL);
  
  		if (!update_url)
  		{
  			RtlRaiseStatus (STATUS_INVALID_PARAMETER);
  			return FALSE;
  		}
  
  		url = _r_format_string (update_url->buffer, pbi->architecture, pbi->browser_type->buffer);
  
  		_r_obj_dereference (update_url);
  
  		if (url)
  		{
  			HINTERNET hsession = _r_inet_createsession (_r_app_getuseragent ());
  
  			if (hsession)
  			{
  				_r_inet_initializedownload (&download_info, NULL, NULL, NULL);
  
  				ULONG code = _r_inet_begindownload (hsession, url->buffer, &download_info);
  
  				if (code == ERROR_SUCCESS)
  				{
  					if (_r_obj_isstringempty (download_info.string))
  					{
  						_r_show_message (hwnd, MB_OK | MB_ICONSTOP, NULL, NULL, L"Configuration not found.");
  						*pis_error = TRUE;
  					}
  					else
  					{
  						result = _r_str_unserialize (&download_info.string->sr, L';', L'=');
  						*pis_error = FALSE;
  					}
  				}
  				else
  				{
  					_r_log (LOG_LEVEL_ERROR, &GUID_TrayIcon, TEXT (__FUNCTION__), code, url->buffer);
  
  					*pis_error = TRUE;
  				}
  
  				_r_inet_destroydownload (&download_info);
  
  				_r_inet_close (hsession);
  			}
  
  			_r_obj_dereference (url);
  		}
  	}
  
  	if (!_r_obj_ishashtableempty (result))
  	{
  		PR_STRING value_string;
  
  		value_string = _r_obj_findhashtablepointer (result, _r_str_gethash (L"download"));
  		_r_obj_movereference (&pbi->download_url, value_string);
  
  		value_string = _r_obj_findhashtablepointer (result, _r_str_gethash (L"version"));
  		_r_obj_movereference (&pbi->new_version, value_string);
  
  		value_string = _r_obj_findhashtablepointer (result, _r_str_gethash (L"timestamp"));
  
  		if (value_string)
  		{
  			pbi->timestamp = _r_str_tolong64 (&value_string->sr);
  
  			_r_obj_dereference (value_string);
  		}
  
  		update_browser_info (hwnd, browser_info);
  
  		if (!is_exists || (pbi->new_version && _r_str_versioncompare (&pbi->current_version->sr, &pbi->new_version->sr) == -1))
  		{
  			result2 = TRUE;
  		}
  		else
  		{
  			SAFE_DELETE_REFERENCE (pbi->download_url); // clear download url if update not found
  
  			_r_config_setlong64 (L"ChromiumLastCheck", _r_unixtime_now ());
  		}
  	}
  
  	_app_setstatus (hwnd, NULL, 0, 0);
  
  	return result2;
  }
  
  BOOLEAN WINAPI _app_downloadupdate_callback (_In_ ULONG total_written, _In_ ULONG total_length, _In_opt_ PVOID lparam)
  {
  	if (lparam)
  		_app_setstatus ((HWND)lparam, _r_locale_getstring (IDS_STATUS_DOWNLOAD), total_written, total_length);
  
  	return TRUE;
  }
  
  BOOLEAN _app_downloadupdate (_In_ HWND hwnd, _In_ PBROWSER_INFORMATION pbi, _Out_ PBOOLEAN pis_error)
  {
  	*pis_error = FALSE;
  
  	if (_app_isupdatedownloaded (pbi))
  		return TRUE;
  
  	PR_STRING temp_file;
  	BOOLEAN result = FALSE;
  	HINTERNET hsession;
  	HANDLE hfile;
  
  	temp_file = _r_obj_concatstrings (2, pbi->cache_path->buffer, L".tmp");
  
  	_r_fs_deletefile (pbi->cache_path->buffer, TRUE);
  
  	_app_setstatus (hwnd, _r_locale_getstring (IDS_STATUS_DOWNLOAD), 0, 1);
  
  	_r_queuedlock_acquireshared (&lock_download);
  
  	hsession = _r_inet_createsession (_r_app_getuseragent ());
  
  	if (hsession)
  	{
  		hfile = CreateFile (temp_file->buffer, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  
  		if (!_r_fs_isvalidhandle (hfile))
  		{
  			_r_log (LOG_LEVEL_ERROR, &GUID_TrayIcon, L"CreateFile", GetLastError (), temp_file->buffer);
  
  			*pis_error = TRUE;
  		}
  		else
  		{
  			R_DOWNLOAD_INFO download_info;
  			ULONG code;
  
  			_r_inet_initializedownload (&download_info, hfile, &_app_downloadupdate_callback, hwnd);
  
  			code = _r_inet_begindownload (hsession, pbi->download_url->buffer, &download_info);
  
  			_r_inet_destroydownload (&download_info); // required!
  
  			if (code == ERROR_SUCCESS)
  			{
  				SAFE_DELETE_REFERENCE (pbi->download_url); // clear download url
  
  				_r_fs_movefile (temp_file->buffer, pbi->cache_path->buffer, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
  
  				result = TRUE;
  
  				*pis_error = FALSE;
  			}
  			else
  			{
  				_r_log (LOG_LEVEL_ERROR, &GUID_TrayIcon, TEXT (__FUNCTION__), code, pbi->download_url->buffer);
  
  				_r_fs_deletefile (pbi->cache_path->buffer, TRUE);
  
  				*pis_error = TRUE;
  			}
  
  			_r_fs_deletefile (temp_file->buffer, TRUE);
  		}
  
  		_r_inet_close (hsession);
  	}
  
  	_r_queuedlock_releaseshared (&lock_download);
  
  	_r_obj_dereference (temp_file);
  
  	_app_setstatus (hwnd, NULL, 0, 0);
  
  	return result;
  }
  
  BOOLEAN _app_unpack_7zip (_In_ HWND hwnd, _In_ PBROWSER_INFORMATION pbi, _In_ PR_STRINGREF bin_name)
  {
  #define kInputBufSize ((SIZE_T)1 << 18)
  
  	static const ISzAlloc g_Alloc = {SzAlloc, SzFree};
  
  	BOOLEAN result = FALSE;
  
  	ISzAlloc alloc_imp = g_Alloc;
  	ISzAlloc alloc_temp_imp = g_Alloc;
  
  	CFileInStream archive_stream;
  	CLookToRead2 look_stream;
  	CSzArEx db;
  	UInt16 *temp_buff = NULL;
  	SIZE_T temp_size = 0;
  
  	SRes code = InFile_OpenW (&archive_stream.file, pbi->cache_path->buffer);
  
  	if (code != ERROR_SUCCESS)
  	{
  		_r_log (LOG_LEVEL_ERROR, NULL, L"InFile_OpenW", code, pbi->cache_path->buffer);
  		return FALSE;
  	}
  
  	FileInStream_CreateVTable (&archive_stream);
  	LookToRead2_CreateVTable (&look_stream, 0);
  
  	look_stream.buf = (byte *)ISzAlloc_Alloc (&alloc_imp, kInputBufSize);
  
  	if (!look_stream.buf)
  	{
  		_r_log (LOG_LEVEL_ERROR, NULL, L"ISzAlloc_Alloc", SZ_ERROR_MEM, NULL);
  	}
  	else
  	{
  		look_stream.bufSize = kInputBufSize;
  		look_stream.realStream = &archive_stream.vt;
  		LookToRead2_Init (&look_stream);
  
  		CrcGenerateTable ();
  
  		SzArEx_Init (&db);
  
  		code = SzArEx_Open (&db, &look_stream.vt, &alloc_imp, &alloc_temp_imp);
  
  		if (code != SZ_OK)
  		{
  			_r_log (LOG_LEVEL_ERROR, NULL, L"SzArEx_Open", code, pbi->cache_path->buffer);
  		}
  		else
  		{
  			// if you need cache, use these 3 variables.
  			// if you use external function, you can make these variable as static.
  
  			UInt32 block_index = UINT32_MAX; // it can have any value before first call (if out_buffer = 0)
  			Byte *out_buffer = NULL; // it must be 0 before first call for each new archive.
  			size_t out_buffer_size = 0; // it can have any value before first call (if out_buffer = 0)
  
  			R_STRINGREF separator_sr = PR_STRINGREF_INIT (L"\\");
  			R_STRINGREF path;
  			PR_STRING root_dir_name = NULL;
  
  			UInt64 total_size = 0;
  			UInt64 total_read = 0;
  			SIZE_T length;
  
  			// find root directory which contains main executable
  			for (UInt32 i = 0; i < db.NumFiles; i++)
  			{
  				if (SzArEx_IsDir (&db, i))
  					continue;
  
  				length = SzArEx_GetFileNameUtf16 (&db, i, NULL);
  				total_size += SzArEx_GetFileSize (&db, i);
  
  				if (length > temp_size)
  				{
  					temp_size = length;
  
  					if (temp_buff)
  					{
  						temp_buff = _r_mem_reallocatezero (temp_buff, temp_size * sizeof (UInt16));
  					}
  					else
  					{
  						temp_buff = _r_mem_allocatezero (temp_size * sizeof (UInt16));
  					}
  				}
  
  				if (!root_dir_name)
  				{
  					length = SzArEx_GetFileNameUtf16 (&db, i, temp_buff);
  
  					if (!length)
  						continue;
  
  					_r_obj_initializestringref_ex (&path, (LPWSTR)temp_buff, (length - 1) * sizeof (WCHAR));
  
  					_r_str_replacechar (&path, L'/', OBJ_NAME_PATH_SEPARATOR);
  
  					length = path.length - bin_name->length - separator_sr.length;
  
  					if (_r_str_isendsswith (&path, bin_name, TRUE) && path.buffer[length / sizeof (WCHAR)] == OBJ_NAME_PATH_SEPARATOR)
  					{
  						_r_obj_movereference (&root_dir_name, _r_obj_createstring_ex (path.buffer, path.length - bin_name->length));
  
  						_r_str_trimstring (root_dir_name, &separator_sr, 0);
  					}
  				}
  			}
  
  			for (UInt32 i = 0; i < db.NumFiles; i++)
  			{
  				length = SzArEx_GetFileNameUtf16 (&db, i, temp_buff);
  
  				if (!length)
  					continue;
  
  				_r_obj_initializestringref_ex (&path, (LPWSTR)temp_buff, (length - 1) * sizeof (WCHAR));
  
  				_r_str_replacechar (&path, L'/', OBJ_NAME_PATH_SEPARATOR);
  				_r_str_trimstringref (&path, &separator_sr, 0);
  
  				// skip non-root dirs
  				if (!_r_obj_isstringempty (root_dir_name) && (path.length <= root_dir_name->length || !_r_str_isstartswith (&path, &root_dir_name->sr, TRUE)))
  					continue;
  
  				CSzFile out_file;
  				PR_STRING dest_path;
  
  				if (root_dir_name)
  				{
  					_r_str_skiplength (&path, root_dir_name->length + separator_sr.length);
  				}
  
  				dest_path = _r_obj_concatstringrefs (3, &pbi->binary_dir->sr, &separator_sr, &path);
  
  				if (SzArEx_IsDir (&db, i))
  				{
  					_r_fs_mkdir (dest_path->buffer);
  				}
  				else
  				{
  					total_read += SzArEx_GetFileSize (&db, i);
  
  					_app_setstatus (hwnd, _r_locale_getstring (IDS_STATUS_INSTALL), total_read, total_size);
  
  					// create directory if not-exist
  					{
  						PR_STRING sub_dir = _r_path_getbasedirectory (&dest_path->sr);
  
  						if (sub_dir)
  						{
  							if (!_r_fs_exists (sub_dir->buffer))
  								_r_fs_mkdir (sub_dir->buffer);
  
  							_r_obj_dereference (sub_dir);
  						}
  					}
  
  					size_t offset = 0;
  					size_t out_size_processed = 0;
  
  					code = SzArEx_Extract (&db, &look_stream.vt, i, &block_index, &out_buffer, &out_buffer_size, &offset, &out_size_processed, &alloc_imp, &alloc_temp_imp);
  
  					if (code != SZ_OK)
  					{
  						_r_log (LOG_LEVEL_ERROR, NULL, L"SzArEx_Extract", code, dest_path->buffer);
  					}
  					else
  					{
  						code = OutFile_OpenW (&out_file, dest_path->buffer);
  
  						if (code != SZ_OK)
  						{
  							_r_log (LOG_LEVEL_ERROR, NULL, L"OutFile_OpenW", code, dest_path->buffer);
  						}
  						else
  						{
  							SIZE_T processed_size = out_size_processed;
  
  							code = File_Write (&out_file, out_buffer + offset, &processed_size);
  
  							if (code != SZ_OK || processed_size != out_size_processed)
  							{
  								_r_log (LOG_LEVEL_ERROR, NULL, L"File_Write", code, dest_path->buffer);
  							}
  							else
  							{
  								if (SzBitWithVals_Check (&db.Attribs, i))
  								{
  									UInt32 attrib = db.Attribs.Vals[i];
  
  									//	p7zip stores posix attributes in high 16 bits and adds 0x8000 as marker.
  									//	We remove posix bits, if we detect posix mode field
  
  									if ((attrib & 0xF0000000) != 0)
  										attrib &= 0x7FFF;
  
  									SetFileAttributes (dest_path->buffer, attrib);
  								}
  							}
  
  							File_Close (&out_file);
  						}
  					}
  				}
  
  				_r_obj_dereference (dest_path);
  
  				if (!result)
  					result = TRUE;
  			}
  
  			if (root_dir_name)
  				_r_obj_dereference (root_dir_name);
  
  			ISzAlloc_Free (&alloc_imp, out_buffer);
  		}
  	}
  
  	_r_mem_free (temp_buff);
  	SzArEx_Free (&db, &alloc_imp);
  
  	ISzAlloc_Free (&alloc_imp, look_stream.buf);
  
  	File_Close (&archive_stream.file);
  
  	return result;
  }
  
  BOOLEAN _app_unpack_zip (_In_ HWND hwnd, _In_ PBROWSER_INFORMATION pbi, _In_ PR_STRINGREF bin_name)
  {
  	mz_zip_archive zip_archive;
  	mz_bool status;
  
  	PR_BYTE bytes;
  	PR_STRING root_dir_name = NULL;
  	BOOLEAN result = FALSE;
  
  	bytes = _r_str_unicode2multibyte (&pbi->cache_path->sr);
  
  	if (!bytes)
  	{
  		_r_log (LOG_LEVEL_ERROR, NULL, L"_r_str_unicode2multibyte", GetLastError (), NULL);
  
  		goto CleanupExit;
  	}
  
  	RtlZeroMemory (&zip_archive, sizeof (zip_archive));
  
  	status = mz_zip_reader_init_file (&zip_archive, bytes->buffer, 0);
  
  	if (!status)
  	{
  		//_r_log (LOG_LEVEL_ERROR, NULL, L"mz_zip_reader_init_file", GetLastError (), NULL);
  
  		goto CleanupExit;
  	}
  
  	R_STRINGREF separator_sr = PR_STRINGREF_INIT (L"\\");
  	R_BYTEREF path_sr;
  	PR_STRING path;
  
  	mz_zip_archive_file_stat file_stat;
  	ULONG64 total_size = 0;
  	ULONG64 total_read = 0; // this is our progress so far
  	SIZE_T length;
  	INT total_files;
  
  	total_files = mz_zip_reader_get_num_files (&zip_archive);
  
  	// find root directory which contains main executable
  	for (INT i = 0; i < total_files; i++)
  	{
  		if (mz_zip_reader_is_file_a_directory (&zip_archive, i) || !mz_zip_reader_file_stat (&zip_archive, i, &file_stat))
  			continue;
  
  		if (file_stat.m_is_directory)
  			continue;
  
  		// count total size of unpacked files
  		total_size += file_stat.m_uncomp_size;
  
  		if (!root_dir_name)
  		{
  			_r_obj_initializebyteref (&path_sr, file_stat.m_filename);
  
  			path = _r_str_multibyte2unicode (&path_sr);
  
  			if (!path)
  				continue;
  
  			_r_str_replacechar (&path->sr, L'/', OBJ_NAME_PATH_SEPARATOR);
  
  			length = path->length - bin_name->length - separator_sr.length;
  
  			if (_r_str_isendsswith (&path->sr, bin_name, TRUE) && path->buffer[length / sizeof (WCHAR)] == OBJ_NAME_PATH_SEPARATOR)
  			{
  				_r_obj_movereference (&root_dir_name, _r_obj_createstring_ex (path->buffer, path->length - bin_name->length));
  
  				_r_str_trimstring (root_dir_name, &separator_sr, 0);
  			}
  
  			_r_obj_dereference (path);
  		}
  	}
  
  	for (INT i = 0; i < total_files; i++)
  	{
  		if (!mz_zip_reader_file_stat (&zip_archive, i, &file_stat))
  			continue;
  
  		_r_obj_initializebyteref (&path_sr, file_stat.m_filename);
  
  		path = _r_str_multibyte2unicode (&path_sr);
  
  		if (!path)
  			continue;
  
  		_r_str_replacechar (&path->sr, L'/', OBJ_NAME_PATH_SEPARATOR);
  		_r_str_trimstring (path, &separator_sr, 0);
  
  		// skip non-root dirs
  		if (!_r_obj_isstringempty (root_dir_name) && (path->length <= root_dir_name->length || !_r_str_isstartswith (&path->sr, &root_dir_name->sr, TRUE)))
  			continue;
  
  		PR_STRING dest_path;
  
  		if (root_dir_name)
  		{
  			_r_str_skiplength (&path->sr, root_dir_name->length + separator_sr.length);
  		}
  
  		dest_path = _r_obj_concatstringrefs (3, &pbi->binary_dir->sr, &separator_sr, &path->sr);
  
  		_app_setstatus (hwnd, _r_locale_getstring (IDS_STATUS_INSTALL), total_read, total_size);
  
  		if (mz_zip_reader_is_file_a_directory (&zip_archive, i))
  		{
  			_r_fs_mkdir (dest_path->buffer);
  		}
  		else
  		{
  			PR_STRING sub_dir = _r_path_getbasedirectory (&dest_path->sr);
  
  			if (sub_dir)
  			{
  				if (!_r_fs_exists (sub_dir->buffer))
  					_r_fs_mkdir (sub_dir->buffer);
  
  				_r_obj_dereference (sub_dir);
  			}
  
  			bytes = _r_str_unicode2multibyte (&dest_path->sr);
  
  			if (!bytes)
  				continue;
  
  			if (!mz_zip_reader_extract_to_file (&zip_archive, i, bytes->buffer, MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY))
  			{
  				_r_log (LOG_LEVEL_ERROR, NULL, L"mz_zip_reader_extract_to_file", GetLastError (), dest_path->buffer);
  				continue;
  			}
  
  			total_read += file_stat.m_uncomp_size;
  		}
  
  		_r_obj_dereference (dest_path);
  
  		if (!result)
  			result = TRUE;
  	}
  
  CleanupExit:
  
  	if (bytes)
  		_r_obj_dereference (bytes);
  
  	if (root_dir_name)
  		_r_obj_dereference (root_dir_name);
  
  	mz_zip_reader_end (&zip_archive);
  
  	return result;
  }
  
  BOOLEAN _app_installupdate (_In_ HWND hwnd, _In_ PBROWSER_INFORMATION pbi, _Out_ PBOOLEAN pis_error)
  {
  	R_STRINGREF bin_name;
  	BOOLEAN result = FALSE;
  
  	_r_queuedlock_acquireshared (&lock_download);
  
  	if (!_r_fs_exists (pbi->binary_dir->buffer))
  		_r_fs_mkdir (pbi->binary_dir->buffer);
  
  	_app_cleanupoldbinary (pbi);
  
  	_r_path_getpathinfo (&pbi->binary_path->sr, NULL, &bin_name);
  
  	_r_sys_setthreadexecutionstate (ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED);
  
  	result = _app_unpack_zip (hwnd, pbi, &bin_name);
  
  	if (!result)
  	{
  		result = _app_unpack_7zip (hwnd, pbi, &bin_name);
  	}
  
  	if (result)
  	{
  		_r_obj_movereference (&pbi->current_version, _r_res_queryversionstring (pbi->binary_path->buffer));
  
  		_app_cleanupoldmanifest (pbi);
  	}
  	else
  	{
  		_r_log (LOG_LEVEL_ERROR, NULL, TEXT (__FUNCTION__), GetLastError (), pbi->cache_path->buffer);
  
  		_r_fs_deletedirectory (pbi->binary_dir->buffer, FALSE); // no recurse
  	}
  
  	_r_fs_deletefile (pbi->cache_path->buffer, TRUE); // remove cache file when zip cannot be opened
  
  	*pis_error = !result;
  
  	_r_queuedlock_releaseshared (&lock_download);
  
  	_r_sys_setthreadexecutionstate (ES_CONTINUOUS);
  
  	_app_setstatus (hwnd, NULL, 0, 0);
  
  	return result;
  }
  
  VOID _app_thread_check (_In_ PVOID arglist, _In_ ULONG busy_count)
  {
  	PBROWSER_INFORMATION pbi;
  	HWND hwnd;
  
  	BOOLEAN is_haveerror = FALSE;
  	BOOLEAN is_stayopen = FALSE;
  	BOOLEAN is_installed = FALSE;
  
  	pbi = (PBROWSER_INFORMATION)arglist;
  	hwnd = _r_app_gethwnd ();
  
  	_r_queuedlock_acquireshared (&lock_thread);
  
  	_r_ctrl_enable (hwnd, IDC_START_BTN, FALSE);
  
  	_r_progress_setmarquee (hwnd, IDC_PROGRESS, TRUE);
  
  	_r_ctrl_setstring (hwnd, IDC_START_BTN, _r_locale_getstring (_app_getactionid (pbi)));
  
  	// unpack downloaded package
  	if (_app_isupdatedownloaded (pbi))
  	{
  		_r_progress_setmarquee (hwnd, IDC_PROGRESS, FALSE);
  
  		_r_tray_toggle (hwnd, &GUID_TrayIcon, TRUE); // show tray icon
  
  		if (!_app_browserisrunning (pbi))
  		{
  			if (pbi->is_bringtofront)
  				_r_wnd_toggle (hwnd, TRUE); // show window
  
  			if (_app_installupdate (hwnd, pbi, &is_haveerror))
  			{
  				init_browser_info (pbi);
  				update_browser_info (hwnd, pbi);
  
  				_r_config_setlong64 (L"ChromiumLastCheck", _r_unixtime_now ());
  
  				is_installed = TRUE;
  			}
  		}
  		else
  		{
  			_r_ctrl_enable (hwnd, IDC_START_BTN, TRUE);
  			is_stayopen = TRUE;
  		}
  	}
  
  	// check/download/unpack
  	if (!is_installed && !is_stayopen)
  	{
  		_r_progress_setmarquee (hwnd, IDC_PROGRESS, TRUE);
  
  		BOOLEAN is_exists = _r_fs_exists (pbi->binary_path->buffer);
  		BOOLEAN is_checkupdate = _app_isupdaterequired (pbi);
  
  		// show launcher gui
  		if (!is_exists || pbi->is_onlyupdate || pbi->is_bringtofront)
  		{
  			_r_tray_toggle (hwnd, &GUID_TrayIcon, TRUE); // show tray icon
  			_r_wnd_toggle (hwnd, TRUE);
  		}
  
  		if (is_exists && !pbi->is_waitdownloadend && !pbi->is_onlyupdate)
  			_app_openbrowser (pbi);
  
  		if (_app_checkupdate (hwnd, pbi, &is_haveerror))
  		{
  			_r_tray_toggle (hwnd, &GUID_TrayIcon, TRUE); // show tray icon
  
  			if ((!is_exists || pbi->is_autodownload) || _app_ishaveupdate (pbi))
  			{
  				if (pbi->is_bringtofront)
  					_r_wnd_toggle (hwnd, TRUE); // show window
  
  				if (is_exists && !pbi->is_onlyupdate && !pbi->is_waitdownloadend && !_app_isupdatedownloaded (pbi))
  					_app_openbrowser (pbi);
  
  				_r_progress_setmarquee (hwnd, IDC_PROGRESS, FALSE);
  
  				if (_app_downloadupdate (hwnd, pbi, &is_haveerror))
  				{
  					if (!_app_browserisrunning (pbi))
  					{
  						_r_ctrl_enable (hwnd, IDC_START_BTN, FALSE);
  
  						if (_app_installupdate (hwnd, pbi, &is_haveerror))
  							_r_config_setlong64 (L"ChromiumLastCheck", _r_unixtime_now ());
  					}
  					else
  					{
  						_r_tray_popup (hwnd, &GUID_TrayIcon, NIIF_INFO, _r_app_getname (), _r_locale_getstring (IDS_STATUS_DOWNLOADED)); // inform user
  
  						_r_ctrl_setstring (hwnd, IDC_START_BTN, _r_locale_getstring (_app_getactionid (pbi)));
  						_r_ctrl_enable (hwnd, IDC_START_BTN, TRUE);
  
  						is_stayopen = TRUE;
  					}
  				}
  				else
  				{
  					_r_tray_popupformat (hwnd, &GUID_TrayIcon, NIIF_INFO, _r_app_getname (), _r_locale_getstring (IDS_STATUS_FOUND), pbi->new_version->buffer); // just inform user
  
  					_r_ctrl_setstring (hwnd, IDC_START_BTN, _r_locale_getstring (_app_getactionid (pbi)));
  					_r_ctrl_enable (hwnd, IDC_START_BTN, TRUE);
  
  					is_stayopen = TRUE;
  				}
  			}
  
  			if (!pbi->is_autodownload && !_app_isupdatedownloaded (pbi))
  			{
  				_r_tray_popupformat (hwnd, &GUID_TrayIcon, NIIF_INFO, _r_app_getname (), _r_locale_getstring (IDS_STATUS_FOUND), pbi->new_version->buffer); // just inform user
  
  				_r_ctrl_setstring (hwnd, IDC_START_BTN, _r_locale_getstring (_app_getactionid (pbi)));
  				_r_ctrl_enable (hwnd, IDC_START_BTN, TRUE);
  
  				is_stayopen = TRUE;
  			}
  		}
  	}
  
  	_r_progress_setmarquee (hwnd, IDC_PROGRESS, FALSE);
  
  	if (is_haveerror || pbi->is_onlyupdate)
  	{
  		_r_ctrl_setstring (hwnd, IDC_START_BTN, _r_locale_getstring (_app_getactionid (pbi)));
  		_r_ctrl_enable (hwnd, IDC_START_BTN, TRUE);
  
  		if (is_haveerror)
  		{
  			_r_tray_popup (hwnd, &GUID_TrayIcon, NIIF_INFO, _r_app_getname (), _r_locale_getstring (IDS_STATUS_ERROR)); // just inform user
  			_app_setstatus (hwnd, _r_locale_getstring (IDS_STATUS_ERROR), 0, 0);
  		}
  
  		is_stayopen = TRUE;
  	}
  
  	if (!pbi->is_onlyupdate)
  		_app_openbrowser (pbi);
  
  	_r_queuedlock_releaseshared (&lock_thread);
  
  	if (is_stayopen)
  	{
  		update_browser_info (hwnd, pbi);
  	}
  	else
  	{
  		PostMessage (hwnd, WM_DESTROY, 0, 0);
  	}
  }
  
  VOID _app_initialize ()
  {
  	static R_INITONCE init_once = PR_INITONCE_INIT;
  
  	if (_r_initonce_begin (&init_once))
  	{
  		_r_workqueue_initialize (&workqueue, 0, 1, 1000, NULL);
  
  		browser_info = _r_mem_allocatezero (sizeof (BROWSER_INFORMATION));
  
  		_r_initonce_end (&init_once);
  	}
  }
  
  INT_PTR CALLBACK DlgProc (_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wparam, _In_ LPARAM lparam)
  {
  	switch (msg)
  	{
  		case WM_INITDIALOG:
  		{
  			HWND htip;
  
  			htip = _r_ctrl_createtip (hwnd);
  
  			if (htip)
  			{
  				_r_ctrl_settiptext (htip, hwnd, IDC_BROWSER_DATA, LPSTR_TEXTCALLBACK);
  				_r_ctrl_settiptext (htip, hwnd, IDC_CURRENTVERSION_DATA, LPSTR_TEXTCALLBACK);
  				_r_ctrl_settiptext (htip, hwnd, IDC_VERSION_DATA, LPSTR_TEXTCALLBACK);
  				_r_ctrl_settiptext (htip, hwnd, IDC_DATE_DATA, LPSTR_TEXTCALLBACK);
  			}
  
  			break;
  		}
  
  		case RM_INITIALIZE:
  		{
  			LONG dpi_value;
  
  			LONG icon_small_x;
  			LONG icon_small_y;
  
  			HICON hicon;
  
  			dpi_value = _r_dc_getsystemdpi ();
  
  			icon_small_x = _r_dc_getsystemmetrics (SM_CXSMICON, dpi_value);
  			icon_small_y = _r_dc_getsystemmetrics (SM_CYSMICON, dpi_value);
  
  			hicon = _r_sys_loadicon (_r_sys_getimagebase (), MAKEINTRESOURCE (IDI_MAIN), icon_small_y, icon_small_x, TRUE);
  
  			init_browser_info (browser_info);
  
  			_r_tray_create (hwnd, &GUID_TrayIcon, WM_TRAYICON, hicon, _r_app_getname (), (_r_queuedlock_islocked (&lock_download) || _app_isupdatedownloaded (browser_info)) ? FALSE : TRUE);
  
  			_r_workqueue_queueitem (&workqueue, &_app_thread_check, browser_info);
  
  			if (!browser_info->is_waitdownloadend && !browser_info->is_onlyupdate && _r_fs_exists (browser_info->binary_path->buffer) && !_app_isupdatedownloaded (browser_info))
  			{
  				_app_openbrowser (browser_info);
  			}
  
  			break;
  		}
  
  		case RM_UNINITIALIZE:
  		{
  			_r_tray_destroy (hwnd, &GUID_TrayIcon);
  			break;
  		}
  
  		case RM_LOCALIZE:
  		{
  			// localize menu
  			HMENU hmenu;
  
  			hmenu = GetMenu (hwnd);
  
  			if (hmenu)
  			{
  				_r_menu_setitemtext (hmenu, 0, TRUE, _r_locale_getstring (IDS_FILE));
  				_r_menu_setitemtext (hmenu, 1, TRUE, _r_locale_getstring (IDS_SETTINGS));
  				_r_menu_setitemtextformat (GetSubMenu (hmenu, 1), LANG_MENU, TRUE, L"%s (Language)", _r_locale_getstring (IDS_LANGUAGE));
  				_r_menu_setitemtext (hmenu, 2, TRUE, _r_locale_getstring (IDS_HELP));
  
  				_r_menu_setitemtextformat (hmenu, IDM_RUN, FALSE, L"%s...", _r_locale_getstring (IDS_RUN));
  				_r_menu_setitemtextformat (hmenu, IDM_OPEN, FALSE, L"%s...", _r_locale_getstring (IDS_OPEN));
  				_r_menu_setitemtext (hmenu, IDM_EXIT, FALSE, _r_locale_getstring (IDS_EXIT));
  				_r_menu_setitemtext (hmenu, IDM_WEBSITE, FALSE, _r_locale_getstring (IDS_WEBSITE));
  				_r_menu_setitemtextformat (hmenu, IDM_ABOUT, FALSE, L"%s\tF1", _r_locale_getstring (IDS_ABOUT));
  
  				_r_locale_enum ((HWND)GetSubMenu (hmenu, 1), LANG_MENU, IDX_LANGUAGE); // enum localizations
  			}
  
  			update_browser_info (hwnd, browser_info);
  
  			_r_ctrl_setstring (hwnd, IDC_LINKS, L"<a href=\"https://github.com/henrypp\">github.com/henrypp</a>\r\n<a href=\"https://chromium.woolyss.com\">chromium.woolyss.com</a>");
  
  			_r_ctrl_setstring (hwnd, IDC_START_BTN, _r_locale_getstring (_app_getactionid (browser_info)));
  
  			break;
  		}
  
  		case RM_TASKBARCREATED:
  		{
  			LONG dpi_value;
  
  			LONG icon_small_x;
  			LONG icon_small_y;
  
  			HICON hicon;
  
  			dpi_value = _r_dc_getsystemdpi ();
  
  			icon_small_x = _r_dc_getsystemmetrics (SM_CXSMICON, dpi_value);
  			icon_small_y = _r_dc_getsystemmetrics (SM_CYSMICON, dpi_value);
  
  			hicon = _r_sys_loadicon (_r_sys_getimagebase (), MAKEINTRESOURCE (IDI_MAIN), icon_small_x, icon_small_y, TRUE);
  
  			_r_tray_create (hwnd, &GUID_TrayIcon, WM_TRAYICON, hicon, _r_app_getname (), (_r_queuedlock_islocked (&lock_download) || _app_isupdatedownloaded (browser_info)) ? FALSE : TRUE);
  
  			break;
  		}
  
  		case WM_DPICHANGED:
  		{
  			LONG dpi_value;
  
  			LONG icon_small_x;
  			LONG icon_small_y;
  
  			HICON hicon;
  
  			dpi_value = _r_dc_getsystemdpi ();
  
  			icon_small_x = _r_dc_getsystemmetrics (SM_CXSMICON, dpi_value);
  			icon_small_y = _r_dc_getsystemmetrics (SM_CYSMICON, dpi_value);
  
  			hicon = _r_sys_loadicon (_r_sys_getimagebase (), MAKEINTRESOURCE (IDI_MAIN), icon_small_x, icon_small_y, TRUE);
  
  			_r_tray_setinfo (hwnd, &GUID_TrayIcon, hicon, _r_app_getname ());
  
  			SendDlgItemMessage (hwnd, IDC_STATUSBAR, WM_SIZE, 0, 0);
  
  			break;
  		}
  
  		case WM_CLOSE:
  		{
  			if (_r_queuedlock_islocked (&lock_download) && _r_show_message (hwnd, MB_YESNO | MB_ICONQUESTION, NULL, NULL, _r_locale_getstring (IDS_QUESTION_STOP)) != IDYES)
  			{
  				SetWindowLongPtr (hwnd, DWLP_MSGRESULT, TRUE);
  				return TRUE;
  			}
  
  			DestroyWindow (hwnd);
  
  			break;
  		}
  
  		case WM_DESTROY:
  		{
  			_r_tray_destroy (hwnd, &GUID_TrayIcon);
  
  			if (browser_info->is_waitdownloadend && !browser_info->is_onlyupdate)
  				_app_openbrowser (browser_info);
  
  			_r_workqueue_waitforfinish (&workqueue);
  			_r_workqueue_destroy (&workqueue);
  
  			PostQuitMessage (0);
  
  			break;
  		}
  
  		case WM_LBUTTONDOWN:
  		{
  			PostMessage (hwnd, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);
  			break;
  		}
  
  		case WM_ENTERSIZEMOVE:
  		case WM_EXITSIZEMOVE:
  		case WM_CAPTURECHANGED:
  		{
  			LONG_PTR exstyle;
  
  			exstyle = _r_wnd_getstyle_ex (hwnd);
  
  			if ((exstyle & WS_EX_LAYERED) == 0)
  				_r_wnd_setstyle_ex (hwnd, exstyle | WS_EX_LAYERED);
  
  			SetLayeredWindowAttributes (hwnd, 0, (msg == WM_ENTERSIZEMOVE) ? 100 : 255, LWA_ALPHA);
  			SetCursor (LoadCursor (NULL, (msg == WM_ENTERSIZEMOVE) ? IDC_SIZEALL : IDC_ARROW));
  
  			break;
  		}
  
  		case WM_DRAWITEM:
  		{
  			LPDRAWITEMSTRUCT draw_info;
  			HDC buffer_hdc;
  			HBITMAP buffer_bitmap;
  			HGDIOBJ old_buffer_bitmap;
  
  			draw_info = (LPDRAWITEMSTRUCT)lparam;
  
  			if (draw_info->CtlID != IDC_LINE)
  				break;
  
  			buffer_hdc = CreateCompatibleDC (draw_info->hDC);
  
  			if (buffer_hdc)
  			{
  				buffer_bitmap = CreateCompatibleBitmap (draw_info->hDC, _r_calc_rectwidth (&draw_info->rcItem), _r_calc_rectheight (&draw_info->rcItem));
  
  				if (buffer_bitmap)
  				{
  					old_buffer_bitmap = SelectObject (buffer_hdc, buffer_bitmap);
  
  					SetBkMode (buffer_hdc, TRANSPARENT);
  
  					_r_dc_fillrect (buffer_hdc, &draw_info->rcItem, GetSysColor (COLOR_WINDOW));
  
  					for (INT i = draw_info->rcItem.left; i < _r_calc_rectwidth (&draw_info->rcItem); i++)
  						SetPixelV (buffer_hdc, i, draw_info->rcItem.top, GetSysColor (COLOR_APPWORKSPACE));
  
  					BitBlt (draw_info->hDC, draw_info->rcItem.left, draw_info->rcItem.top, draw_info->rcItem.right, draw_info->rcItem.bottom, buffer_hdc, 0, 0, SRCCOPY);
  
  					SelectObject (buffer_hdc, old_buffer_bitmap);
  
  					SAFE_DELETE_OBJECT (buffer_bitmap);
  				}
  
  				SAFE_DELETE_DC (buffer_hdc);
  			}
  
  			SetWindowLongPtr (hwnd, DWLP_MSGRESULT, TRUE);
  			return TRUE;
  		}
  
  		case WM_NOTIFY:
  		{
  			LPNMHDR lpnmhdr;
  
  			lpnmhdr = (LPNMHDR)lparam;
  
  			switch (lpnmhdr->code)
  			{
  				case TTN_GETDISPINFO:
  				{
  					WCHAR buffer[1024];
  					PR_STRING string;
  					INT ctrl_id;
  
  					LPNMTTDISPINFO lpnmdi = (LPNMTTDISPINFO)lparam;
  
  					if ((lpnmdi->uFlags & TTF_IDISHWND) == 0)
  						break;
  
  					ctrl_id = GetDlgCtrlID ((HWND)lpnmdi->hdr.idFrom);
  					string = _r_ctrl_getstring (hwnd, ctrl_id);
  
  					if (string)
  					{
  						_r_str_copy (buffer, RTL_NUMBER_OF (buffer), string->buffer);
  
  						if (!_r_str_isempty (buffer))
  							lpnmdi->lpszText = buffer;
  
  						_r_obj_dereference (string);
  					}
  
  					break;
  				}
  
  				case NM_CLICK:
  				case NM_RETURN:
  				{
  					PNMLINK nmlink;
  
  					nmlink = (PNMLINK)lparam;
  
  					if (!_r_str_isempty (nmlink->item.szUrl))
  						_r_shell_opendefault (nmlink->item.szUrl);
  
  					break;
  				}
  			}
  
  			break;
  		}
  
  		case WM_TRAYICON:
  		{
  			switch (LOWORD (lparam))
  			{
  				case NIN_BALLOONUSERCLICK:
  				{
  					_r_wnd_toggle (hwnd, TRUE);
  					break;
  				}
  
  				case NIN_KEYSELECT:
  				{
  					if (GetForegroundWindow () != hwnd)
  					{
  						_r_wnd_toggle (hwnd, FALSE);
  					}
  
  					break;
  				}
  
  				case WM_MBUTTONUP:
  				{
  					PostMessage (hwnd, WM_COMMAND, MAKEWPARAM (IDM_EXPLORE, 0), 0);
  					break;
  				}
  
  				case WM_LBUTTONUP:
  				{
  					_r_wnd_toggle (hwnd, FALSE);
  					break;
  				}
  
  				case WM_CONTEXTMENU:
  				{
  					HMENU hmenu;
  					HMENU hsubmenu;
  
  					SetForegroundWindow (hwnd); // don't touch
  
  					hmenu = LoadMenu (NULL, MAKEINTRESOURCE (IDM_TRAY));
  
  					if (hmenu)
  					{
  						hsubmenu = GetSubMenu (hmenu, 0);
  
  						if (hsubmenu)
  						{
  							// localize
  							_r_menu_setitemtext (hsubmenu, IDM_TRAY_SHOW, FALSE, _r_locale_getstring (IDS_TRAY_SHOW));
  							_r_menu_setitemtextformat (hsubmenu, IDM_TRAY_RUN, FALSE, L"%s...", _r_locale_getstring (IDS_RUN));
  							_r_menu_setitemtextformat (hsubmenu, IDM_TRAY_OPEN, FALSE, L"%s...", _r_locale_getstring (IDS_OPEN));
  							_r_menu_setitemtext (hsubmenu, IDM_TRAY_WEBSITE, FALSE, _r_locale_getstring (IDS_WEBSITE));
  							_r_menu_setitemtext (hsubmenu, IDM_TRAY_ABOUT, FALSE, _r_locale_getstring (IDS_ABOUT));
  							_r_menu_setitemtext (hsubmenu, IDM_TRAY_EXIT, FALSE, _r_locale_getstring (IDS_EXIT));
  
  							if (_r_obj_isstringempty (browser_info->binary_path) || !_r_fs_exists (browser_info->binary_path->buffer))
  								_r_menu_enableitem (hsubmenu, IDM_TRAY_RUN, MF_BYCOMMAND, FALSE);
  
  							_r_menu_popup (hsubmenu, hwnd, NULL, TRUE);
  						}
  
  						DestroyMenu (hmenu);
  					}
  
  					break;
  				}
  			}
  
  			break;
  		}
  
  		case WM_COMMAND:
  		{
  			if (HIWORD (wparam) == 0 && LOWORD (wparam) >= IDX_LANGUAGE && LOWORD (wparam) <= IDX_LANGUAGE + _r_locale_getcount ())
  			{
  				_r_locale_apply (GetSubMenu (GetSubMenu (GetMenu (hwnd), 1), LANG_MENU), LOWORD (wparam), IDX_LANGUAGE);
  				return FALSE;
  			}
  
  			switch (LOWORD (wparam))
  			{
  				case IDCANCEL: // process Esc key
  				case IDM_TRAY_SHOW:
  				{
  					_r_wnd_toggle (hwnd, FALSE);
  					break;
  				}
  
  				case IDM_EXIT:
  				case IDM_TRAY_EXIT:
  				{
  					PostMessage (hwnd, WM_CLOSE, 0, 0);
  					break;
  				}
  
  				case IDM_RUN:
  				case IDM_TRAY_RUN:
  				{
  					_app_openbrowser (browser_info);
  					break;
  				}
  
  				case IDM_OPEN:
  				case IDM_TRAY_OPEN:
  				{
  					if (_r_fs_exists (_r_app_getconfigpath ()))
  						_r_shell_opendefault (_r_app_getconfigpath ());
  
  					break;
  				}
  
  				case IDM_EXPLORE:
  				{
  					if (browser_info->binary_dir)
  					{
  						if (_r_fs_exists (browser_info->binary_dir->buffer))
  							_r_shell_opendefault (browser_info->binary_dir->buffer);
  					}
  
  					break;
  				}
  
  				case IDM_WEBSITE:
  				case IDM_TRAY_WEBSITE:
  				{
  					_r_shell_opendefault (_r_app_getwebsite_url ());
  					break;
  				}
  
  				case IDM_ABOUT:
  				case IDM_TRAY_ABOUT:
  				{
  					_r_show_aboutmessage (hwnd);
  					break;
  				}
  
  				case IDC_START_BTN:
  				{
  					_r_workqueue_queueitem (&workqueue, &_app_thread_check, browser_info);
  					break;
  				}
  			}
  
  			break;
  		}
  	}
  
  	return FALSE;
  }
  
  INT APIENTRY wWinMain (_In_ HINSTANCE hinst, _In_opt_ HINSTANCE prev_hinst, _In_ LPWSTR cmdline, _In_ INT show_cmd)
  {
  	MSG msg;
  	HACCEL haccel;
  	HWND hwnd;
  
  	if (_r_app_initialize ())
  	{
  		_app_initialize ();
  
  		SetCurrentDirectory (_r_app_getdirectory ());
  
  		if (cmdline)
  		{
  			init_browser_info (browser_info);
  
  			if (browser_info->is_hasurls && _r_fs_exists (browser_info->binary_path->buffer))
  			{
  				_app_openbrowser (browser_info);
  
  				return ERROR_SUCCESS;
  			}
  		}
  
  		hwnd = _r_app_createwindow (IDD_MAIN, IDI_MAIN, &DlgProc);
  
  		if (hwnd)
  		{
  			haccel = LoadAccelerators (_r_sys_getimagebase (), MAKEINTRESOURCE (IDA_MAIN));
  
  			if (haccel)
  			{
  				while (GetMessage (&msg, NULL, 0, 0) > 0)
  				{
  					if (!TranslateAccelerator (hwnd, haccel, &msg) && !IsDialogMessage (hwnd, &msg))
  					{
  						TranslateMessage (&msg);
  						DispatchMessage (&msg);
  					}
  				}
  
  				DestroyAcceleratorTable (haccel);
  			}
  		}
  	}
  
  	return ERROR_SUCCESS;
  }
  