| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077 | 
							- /*
 
-  * Copyright (c) 2017-2018 Hugh Bailey <[email protected]>
 
-  *
 
-  * Permission to use, copy, modify, and distribute this software for any
 
-  * purpose with or without fee is hereby granted, provided that the above
 
-  * copyright notice and this permission notice appear in all copies.
 
-  *
 
-  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 
-  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 
-  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 
-  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 
-  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 
-  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 
-  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-  */
 
- #include "updater.hpp"
 
- #include <psapi.h>
 
- #include <util/windows/CoTaskMemPtr.hpp>
 
- #include <future>
 
- #include <vector>
 
- #include <string>
 
- #include <mutex>
 
- #include <unordered_set>
 
- #include <queue>
 
- using namespace std;
 
- using namespace json11;
 
- /* ----------------------------------------------------------------------- */
 
- HANDLE cancelRequested = nullptr;
 
- HANDLE updateThread = nullptr;
 
- HINSTANCE hinstMain = nullptr;
 
- HWND hwndMain = nullptr;
 
- HCRYPTPROV hProvider = 0;
 
- static bool bExiting = false;
 
- static bool updateFailed = false;
 
- static bool is32bit = false;
 
- static bool downloadThreadFailure = false;
 
- int totalFileSize = 0;
 
- int completedFileSize = 0;
 
- static int completedUpdates = 0;
 
- static wchar_t tempPath[MAX_PATH];
 
- struct LastError {
 
- 	DWORD code;
 
- 	inline LastError() { code = GetLastError(); }
 
- };
 
- void FreeWinHttpHandle(HINTERNET handle)
 
- {
 
- 	WinHttpCloseHandle(handle);
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- static inline bool is_64bit_windows(void);
 
- static inline bool HasVS2019Redist2()
 
- {
 
- 	wchar_t base[MAX_PATH];
 
- 	wchar_t path[MAX_PATH];
 
- 	WIN32_FIND_DATAW wfd;
 
- 	HANDLE handle;
 
- 	int folder = (is32bit && is_64bit_windows()) ? CSIDL_SYSTEMX86
 
- 						     : CSIDL_SYSTEM;
 
- 	SHGetFolderPathW(NULL, folder, NULL, SHGFP_TYPE_CURRENT, base);
 
- #define check_dll_installed(dll)                                    \
 
- 	do {                                                        \
 
- 		StringCbCopyW(path, sizeof(path), base);            \
 
- 		StringCbCatW(path, sizeof(path), L"\\" dll ".dll"); \
 
- 		handle = FindFirstFileW(path, &wfd);                \
 
- 		if (handle == INVALID_HANDLE_VALUE) {               \
 
- 			return false;                               \
 
- 		} else {                                            \
 
- 			FindClose(handle);                          \
 
- 		}                                                   \
 
- 	} while (false)
 
- 	check_dll_installed(L"msvcp140");
 
- 	check_dll_installed(L"vcruntime140");
 
- 	if (!is32bit) {
 
- 		check_dll_installed(L"vcruntime140_1");
 
- 	}
 
- #undef check_dll_installed
 
- 	return true;
 
- }
 
- static bool HasVS2019Redist()
 
- {
 
- 	PVOID old = nullptr;
 
- 	bool redirect = !!Wow64DisableWow64FsRedirection(&old);
 
- 	bool success = HasVS2019Redist2();
 
- 	if (redirect)
 
- 		Wow64RevertWow64FsRedirection(old);
 
- 	return success;
 
- }
 
- static void Status(const wchar_t *fmt, ...)
 
- {
 
- 	wchar_t str[512];
 
- 	va_list argptr;
 
- 	va_start(argptr, fmt);
 
- 	StringCbVPrintf(str, sizeof(str), fmt, argptr);
 
- 	SetDlgItemText(hwndMain, IDC_STATUS, str);
 
- 	va_end(argptr);
 
- }
 
- static void CreateFoldersForPath(const wchar_t *path)
 
- {
 
- 	wchar_t *p = (wchar_t *)path;
 
- 	while (*p) {
 
- 		if (*p == '\\' || *p == '/') {
 
- 			*p = 0;
 
- 			CreateDirectory(path, nullptr);
 
- 			*p = '\\';
 
- 		}
 
- 		p++;
 
- 	}
 
- }
 
- static bool MyCopyFile(const wchar_t *src, const wchar_t *dest)
 
- try {
 
- 	WinHandle hSrc;
 
- 	WinHandle hDest;
 
- 	hSrc = CreateFile(src, GENERIC_READ, 0, nullptr, OPEN_EXISTING,
 
- 			  FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
 
- 	if (!hSrc.Valid())
 
- 		throw LastError();
 
- 	hDest = CreateFile(dest, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0,
 
- 			   nullptr);
 
- 	if (!hDest.Valid())
 
- 		throw LastError();
 
- 	BYTE buf[65536];
 
- 	DWORD read, wrote;
 
- 	for (;;) {
 
- 		if (!ReadFile(hSrc, buf, sizeof(buf), &read, nullptr))
 
- 			throw LastError();
 
- 		if (read == 0)
 
- 			break;
 
- 		if (!WriteFile(hDest, buf, read, &wrote, nullptr))
 
- 			throw LastError();
 
- 		if (wrote != read)
 
- 			return false;
 
- 	}
 
- 	return true;
 
- } catch (LastError error) {
 
- 	SetLastError(error.code);
 
- 	return false;
 
- }
 
- static void MyDeleteFile(const wstring &filename)
 
- {
 
- 	/* Try straightforward delete first */
 
- 	if (DeleteFile(filename.c_str()))
 
- 		return;
 
- 	DWORD err = GetLastError();
 
- 	if (err == ERROR_FILE_NOT_FOUND)
 
- 		return;
 
- 	/* If all else fails, schedule the file to be deleted on reboot */
 
- 	MoveFileEx(filename.c_str(), NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
 
- }
 
- static bool IsSafeFilename(const wchar_t *path)
 
- {
 
- 	const wchar_t *p = path;
 
- 	if (!*p)
 
- 		return false;
 
- 	if (wcsstr(path, L".."))
 
- 		return false;
 
- 	if (*p == '/')
 
- 		return false;
 
- 	while (*p) {
 
- 		if (!isalnum(*p) && *p != '.' && *p != '/' && *p != '_' &&
 
- 		    *p != '-')
 
- 			return false;
 
- 		p++;
 
- 	}
 
- 	return true;
 
- }
 
- static string QuickReadFile(const wchar_t *path)
 
- {
 
- 	string data;
 
- 	WinHandle handle = CreateFileW(path, GENERIC_READ, 0, nullptr,
 
- 				       OPEN_EXISTING, 0, nullptr);
 
- 	if (!handle.Valid()) {
 
- 		return string();
 
- 	}
 
- 	LARGE_INTEGER size;
 
- 	if (!GetFileSizeEx(handle, &size)) {
 
- 		return string();
 
- 	}
 
- 	data.resize((size_t)size.QuadPart);
 
- 	DWORD read;
 
- 	if (!ReadFile(handle, &data[0], (DWORD)data.size(), &read, nullptr)) {
 
- 		return string();
 
- 	}
 
- 	if (read != size.QuadPart) {
 
- 		return string();
 
- 	}
 
- 	return data;
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- enum state_t {
 
- 	STATE_INVALID,
 
- 	STATE_PENDING_DOWNLOAD,
 
- 	STATE_DOWNLOADING,
 
- 	STATE_DOWNLOADED,
 
- 	STATE_ALREADY_DOWNLOADED,
 
- 	STATE_INSTALL_FAILED,
 
- 	STATE_INSTALLED,
 
- };
 
- struct update_t {
 
- 	wstring sourceURL;
 
- 	wstring outputPath;
 
- 	wstring tempPath;
 
- 	wstring previousFile;
 
- 	wstring basename;
 
- 	string packageName;
 
- 	DWORD fileSize = 0;
 
- 	BYTE hash[BLAKE2_HASH_LENGTH];
 
- 	BYTE downloadhash[BLAKE2_HASH_LENGTH];
 
- 	BYTE my_hash[BLAKE2_HASH_LENGTH];
 
- 	state_t state = STATE_INVALID;
 
- 	bool has_hash = false;
 
- 	bool patchable = false;
 
- 	inline update_t() {}
 
- 	inline update_t(const update_t &from)
 
- 		: sourceURL(from.sourceURL),
 
- 		  outputPath(from.outputPath),
 
- 		  tempPath(from.tempPath),
 
- 		  previousFile(from.previousFile),
 
- 		  basename(from.basename),
 
- 		  packageName(from.packageName),
 
- 		  fileSize(from.fileSize),
 
- 		  state(from.state),
 
- 		  has_hash(from.has_hash),
 
- 		  patchable(from.patchable)
 
- 	{
 
- 		memcpy(hash, from.hash, sizeof(hash));
 
- 		memcpy(downloadhash, from.downloadhash, sizeof(downloadhash));
 
- 		memcpy(my_hash, from.my_hash, sizeof(my_hash));
 
- 	}
 
- 	inline update_t(update_t &&from)
 
- 		: sourceURL(std::move(from.sourceURL)),
 
- 		  outputPath(std::move(from.outputPath)),
 
- 		  tempPath(std::move(from.tempPath)),
 
- 		  previousFile(std::move(from.previousFile)),
 
- 		  basename(std::move(from.basename)),
 
- 		  packageName(std::move(from.packageName)),
 
- 		  fileSize(from.fileSize),
 
- 		  state(from.state),
 
- 		  has_hash(from.has_hash),
 
- 		  patchable(from.patchable)
 
- 	{
 
- 		from.state = STATE_INVALID;
 
- 		memcpy(hash, from.hash, sizeof(hash));
 
- 		memcpy(downloadhash, from.downloadhash, sizeof(downloadhash));
 
- 		memcpy(my_hash, from.my_hash, sizeof(my_hash));
 
- 	}
 
- 	void CleanPartialUpdate()
 
- 	{
 
- 		if (state == STATE_INSTALL_FAILED || state == STATE_INSTALLED) {
 
- 			if (!previousFile.empty()) {
 
- 				DeleteFile(outputPath.c_str());
 
- 				MyCopyFile(previousFile.c_str(),
 
- 					   outputPath.c_str());
 
- 				DeleteFile(previousFile.c_str());
 
- 			} else {
 
- 				DeleteFile(outputPath.c_str());
 
- 			}
 
- 			if (state == STATE_INSTALL_FAILED)
 
- 				DeleteFile(tempPath.c_str());
 
- 		} else if (state == STATE_DOWNLOADED) {
 
- 			DeleteFile(tempPath.c_str());
 
- 		}
 
- 	}
 
- 	inline update_t &operator=(const update_t &from)
 
- 	{
 
- 		sourceURL = from.sourceURL;
 
- 		outputPath = from.outputPath;
 
- 		tempPath = from.tempPath;
 
- 		previousFile = from.previousFile;
 
- 		basename = from.basename;
 
- 		packageName = from.packageName;
 
- 		fileSize = from.fileSize;
 
- 		state = from.state;
 
- 		has_hash = from.has_hash;
 
- 		patchable = from.patchable;
 
- 		memcpy(hash, from.hash, sizeof(hash));
 
- 		memcpy(downloadhash, from.downloadhash, sizeof(downloadhash));
 
- 		memcpy(my_hash, from.my_hash, sizeof(my_hash));
 
- 		return *this;
 
- 	}
 
- };
 
- struct deletion_t {
 
- 	wstring originalFilename;
 
- 	wstring deleteMeFilename;
 
- 	void UndoRename()
 
- 	{
 
- 		if (!deleteMeFilename.empty())
 
- 			MoveFile(deleteMeFilename.c_str(),
 
- 				 originalFilename.c_str());
 
- 	}
 
- };
 
- static unordered_map<string, wstring> hashes;
 
- static vector<update_t> updates;
 
- static vector<deletion_t> deletions;
 
- static mutex updateMutex;
 
- static inline void CleanupPartialUpdates()
 
- {
 
- 	for (update_t &update : updates)
 
- 		update.CleanPartialUpdate();
 
- 	for (deletion_t &deletion : deletions)
 
- 		deletion.UndoRename();
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- bool DownloadWorkerThread()
 
- {
 
- 	const DWORD tlsProtocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 |
 
- 				   WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3;
 
- 	const DWORD enableHTTP2Flag = WINHTTP_PROTOCOL_FLAG_HTTP2;
 
- 	HttpHandle hSession = WinHttpOpen(L"OBS Studio Updater/2.2",
 
- 					  WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
 
- 					  WINHTTP_NO_PROXY_NAME,
 
- 					  WINHTTP_NO_PROXY_BYPASS, 0);
 
- 	if (!hSession) {
 
- 		downloadThreadFailure = true;
 
- 		Status(L"Update failed: Couldn't open obsproject.com");
 
- 		return false;
 
- 	}
 
- 	WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS,
 
- 			 (LPVOID)&tlsProtocols, sizeof(tlsProtocols));
 
- 	WinHttpSetOption(hSession, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
 
- 			 (LPVOID)&enableHTTP2Flag, sizeof(enableHTTP2Flag));
 
- 	HttpHandle hConnect = WinHttpConnect(hSession,
 
- 					     L"cdn-fastly.obsproject.com",
 
- 					     INTERNET_DEFAULT_HTTPS_PORT, 0);
 
- 	if (!hConnect) {
 
- 		downloadThreadFailure = true;
 
- 		Status(L"Update failed: Couldn't connect to cdn-fastly.obsproject.com");
 
- 		return false;
 
- 	}
 
- 	for (;;) {
 
- 		bool foundWork = false;
 
- 		unique_lock<mutex> ulock(updateMutex);
 
- 		for (update_t &update : updates) {
 
- 			int responseCode;
 
- 			DWORD waitResult =
 
- 				WaitForSingleObject(cancelRequested, 0);
 
- 			if (waitResult == WAIT_OBJECT_0) {
 
- 				return false;
 
- 			}
 
- 			if (update.state != STATE_PENDING_DOWNLOAD)
 
- 				continue;
 
- 			update.state = STATE_DOWNLOADING;
 
- 			ulock.unlock();
 
- 			foundWork = true;
 
- 			if (downloadThreadFailure) {
 
- 				return false;
 
- 			}
 
- 			Status(L"Downloading %s", update.outputPath.c_str());
 
- 			if (!HTTPGetFile(hConnect, update.sourceURL.c_str(),
 
- 					 update.tempPath.c_str(),
 
- 					 L"Accept-Encoding: gzip",
 
- 					 &responseCode)) {
 
- 				downloadThreadFailure = true;
 
- 				DeleteFile(update.tempPath.c_str());
 
- 				Status(L"Update failed: Could not download "
 
- 				       L"%s (error code %d)",
 
- 				       update.outputPath.c_str(), responseCode);
 
- 				return 1;
 
- 			}
 
- 			if (responseCode != 200) {
 
- 				downloadThreadFailure = true;
 
- 				DeleteFile(update.tempPath.c_str());
 
- 				Status(L"Update failed: Could not download "
 
- 				       L"%s (error code %d)",
 
- 				       update.outputPath.c_str(), responseCode);
 
- 				return 1;
 
- 			}
 
- 			BYTE downloadHash[BLAKE2_HASH_LENGTH];
 
- 			if (!CalculateFileHash(update.tempPath.c_str(),
 
- 					       downloadHash)) {
 
- 				downloadThreadFailure = true;
 
- 				DeleteFile(update.tempPath.c_str());
 
- 				Status(L"Update failed: Couldn't verify "
 
- 				       L"integrity of %s",
 
- 				       update.outputPath.c_str());
 
- 				return 1;
 
- 			}
 
- 			if (memcmp(update.downloadhash, downloadHash, 20)) {
 
- 				downloadThreadFailure = true;
 
- 				DeleteFile(update.tempPath.c_str());
 
- 				Status(L"Update failed: Integrity check "
 
- 				       L"failed on %s",
 
- 				       update.outputPath.c_str());
 
- 				return 1;
 
- 			}
 
- 			ulock.lock();
 
- 			update.state = STATE_DOWNLOADED;
 
- 			completedUpdates++;
 
- 		}
 
- 		if (!foundWork) {
 
- 			break;
 
- 		}
 
- 		if (downloadThreadFailure) {
 
- 			return false;
 
- 		}
 
- 	}
 
- 	return true;
 
- }
 
- static bool RunDownloadWorkers(int num)
 
- try {
 
- 	vector<future<bool>> thread_success_results;
 
- 	thread_success_results.resize(num);
 
- 	for (future<bool> &result : thread_success_results) {
 
- 		result = async(DownloadWorkerThread);
 
- 	}
 
- 	for (future<bool> &result : thread_success_results) {
 
- 		if (!result.get()) {
 
- 			return false;
 
- 		}
 
- 	}
 
- 	return true;
 
- } catch (...) {
 
- 	return false;
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- #define WAITIFOBS_SUCCESS 0
 
- #define WAITIFOBS_WRONG_PROCESS 1
 
- #define WAITIFOBS_CANCELLED 2
 
- static inline DWORD WaitIfOBS(DWORD id, const wchar_t *expected)
 
- {
 
- 	wchar_t path[MAX_PATH];
 
- 	wchar_t *name;
 
- 	*path = 0;
 
- 	WinHandle proc = OpenProcess(PROCESS_QUERY_INFORMATION |
 
- 					     PROCESS_VM_READ | SYNCHRONIZE,
 
- 				     false, id);
 
- 	if (!proc.Valid())
 
- 		return WAITIFOBS_WRONG_PROCESS;
 
- 	if (!GetProcessImageFileName(proc, path, _countof(path)))
 
- 		return WAITIFOBS_WRONG_PROCESS;
 
- 	name = wcsrchr(path, L'\\');
 
- 	if (name)
 
- 		name += 1;
 
- 	else
 
- 		name = path;
 
- 	if (_wcsnicmp(name, expected, 5) == 0) {
 
- 		HANDLE hWait[2];
 
- 		hWait[0] = proc;
 
- 		hWait[1] = cancelRequested;
 
- 		int i = WaitForMultipleObjects(2, hWait, false, INFINITE);
 
- 		if (i == WAIT_OBJECT_0 + 1)
 
- 			return WAITIFOBS_CANCELLED;
 
- 		return WAITIFOBS_SUCCESS;
 
- 	}
 
- 	return WAITIFOBS_WRONG_PROCESS;
 
- }
 
- static bool WaitForOBS()
 
- {
 
- 	DWORD proc_ids[1024], needed, count;
 
- 	const wchar_t *name = is32bit ? L"obs32" : L"obs64";
 
- 	if (!EnumProcesses(proc_ids, sizeof(proc_ids), &needed)) {
 
- 		return true;
 
- 	}
 
- 	count = needed / sizeof(DWORD);
 
- 	for (DWORD i = 0; i < count; i++) {
 
- 		DWORD id = proc_ids[i];
 
- 		if (id != 0) {
 
- 			switch (WaitIfOBS(id, name)) {
 
- 			case WAITIFOBS_SUCCESS:
 
- 				return true;
 
- 			case WAITIFOBS_WRONG_PROCESS:
 
- 				break;
 
- 			case WAITIFOBS_CANCELLED:
 
- 				return false;
 
- 			}
 
- 		}
 
- 	}
 
- 	return true;
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- static inline bool UTF8ToWide(wchar_t *wide, int wideSize, const char *utf8)
 
- {
 
- 	return !!MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, wideSize);
 
- }
 
- static inline bool WideToUTF8(char *utf8, int utf8Size, const wchar_t *wide)
 
- {
 
- 	return !!WideCharToMultiByte(CP_UTF8, 0, wide, -1, utf8, utf8Size,
 
- 				     nullptr, nullptr);
 
- }
 
- #define UTF8ToWideBuf(wide, utf8) UTF8ToWide(wide, _countof(wide), utf8)
 
- #define WideToUTF8Buf(utf8, wide) WideToUTF8(utf8, _countof(utf8), wide)
 
- /* ----------------------------------------------------------------------- */
 
- queue<string> hashQueue;
 
- void HasherThread()
 
- {
 
- 	bool hasherThreadFailure = false;
 
- 	unique_lock<mutex> ulock(updateMutex, defer_lock);
 
- 	while (true) {
 
- 		ulock.lock();
 
- 		if (hashQueue.empty())
 
- 			return;
 
- 		auto fileName = hashQueue.front();
 
- 		hashQueue.pop();
 
- 		ulock.unlock();
 
- 		wchar_t updateFileName[MAX_PATH];
 
- 		if (!UTF8ToWideBuf(updateFileName, fileName.c_str()))
 
- 			continue;
 
- 		if (!IsSafeFilename(updateFileName))
 
- 			continue;
 
- 		BYTE existingHash[BLAKE2_HASH_LENGTH];
 
- 		wchar_t fileHashStr[BLAKE2_HASH_STR_LENGTH];
 
- 		if (CalculateFileHash(updateFileName, existingHash)) {
 
- 			HashToString(existingHash, fileHashStr);
 
- 			ulock.lock();
 
- 			hashes.emplace(fileName, fileHashStr);
 
- 			ulock.unlock();
 
- 		}
 
- 	}
 
- }
 
- static void RunHasherWorkers(int num, const Json &packages)
 
- try {
 
- 	for (const Json &package : packages.array_items()) {
 
- 		for (const Json &file : package["files"].array_items()) {
 
- 			if (!file["name"].is_string())
 
- 				continue;
 
- 			hashQueue.push(file["name"].string_value());
 
- 		}
 
- 	}
 
- 	vector<future<void>> futures;
 
- 	futures.resize(num);
 
- 	for (auto &result : futures) {
 
- 		result = async(launch::async, HasherThread);
 
- 	}
 
- 	for (auto &result : futures) {
 
- 		result.wait();
 
- 	}
 
- } catch (...) {
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- static inline bool FileExists(const wchar_t *path)
 
- {
 
- 	WIN32_FIND_DATAW wfd;
 
- 	HANDLE hFind;
 
- 	hFind = FindFirstFileW(path, &wfd);
 
- 	if (hFind != INVALID_HANDLE_VALUE)
 
- 		FindClose(hFind);
 
- 	return hFind != INVALID_HANDLE_VALUE;
 
- }
 
- static bool NonCorePackageInstalled(const char *name)
 
- {
 
- 	if (is32bit) {
 
- 		if (strcmp(name, "obs-browser") == 0) {
 
- 			return FileExists(
 
- 				L"obs-plugins\\32bit\\obs-browser.dll");
 
- 		} else if (strcmp(name, "realsense") == 0) {
 
- 			return FileExists(L"obs-plugins\\32bit\\win-ivcam.dll");
 
- 		}
 
- 	} else {
 
- 		if (strcmp(name, "obs-browser") == 0) {
 
- 			return FileExists(
 
- 				L"obs-plugins\\64bit\\obs-browser.dll");
 
- 		} else if (strcmp(name, "realsense") == 0) {
 
- 			return FileExists(L"obs-plugins\\64bit\\win-ivcam.dll");
 
- 		}
 
- 	}
 
- 	return false;
 
- }
 
- static inline bool is_64bit_windows(void)
 
- {
 
- #ifdef _WIN64
 
- 	return true;
 
- #else
 
- 	BOOL x86 = false;
 
- 	bool success = !!IsWow64Process(GetCurrentProcess(), &x86);
 
- 	return success && !!x86;
 
- #endif
 
- }
 
- static inline bool is_64bit_file(const char *file)
 
- {
 
- 	if (!file)
 
- 		return false;
 
- 	return strstr(file, "64bit") != nullptr ||
 
- 	       strstr(file, "64.dll") != nullptr ||
 
- 	       strstr(file, "64.exe") != nullptr;
 
- }
 
- #define UPDATE_URL L"https://cdn-fastly.obsproject.com/update_studio"
 
- static bool AddPackageUpdateFiles(const Json &root, size_t idx,
 
- 				  const wchar_t *tempPath,
 
- 				  const wchar_t *branch)
 
- {
 
- 	const Json &package = root[idx];
 
- 	const Json &name = package["name"];
 
- 	const Json &files = package["files"];
 
- 	bool isWin64 = is_64bit_windows();
 
- 	if (!files.is_array())
 
- 		return true;
 
- 	if (!name.is_string())
 
- 		return true;
 
- 	wchar_t wPackageName[512];
 
- 	const string &packageName = name.string_value();
 
- 	size_t fileCount = files.array_items().size();
 
- 	if (!UTF8ToWideBuf(wPackageName, packageName.c_str()))
 
- 		return false;
 
- 	if (packageName != "core" &&
 
- 	    !NonCorePackageInstalled(packageName.c_str()))
 
- 		return true;
 
- 	for (size_t j = 0; j < fileCount; j++) {
 
- 		const Json &file = files[j];
 
- 		const Json &fileName = file["name"];
 
- 		const Json &hash = file["hash"];
 
- 		const Json &size = file["size"];
 
- 		if (!fileName.is_string())
 
- 			continue;
 
- 		if (!hash.is_string())
 
- 			continue;
 
- 		if (!size.is_number())
 
- 			continue;
 
- 		const string &fileUTF8 = fileName.string_value();
 
- 		const string &hashUTF8 = hash.string_value();
 
- 		int fileSize = size.int_value();
 
- 		if (hashUTF8.size() != BLAKE2_HASH_LENGTH * 2)
 
- 			continue;
 
- 		if (!isWin64 && is_64bit_file(fileUTF8.c_str()))
 
- 			continue;
 
- 		/* ignore update files of opposite arch to reduce download */
 
- 		if ((is32bit && fileUTF8.find("/64bit/") != string::npos) ||
 
- 		    (!is32bit && fileUTF8.find("/32bit/") != string::npos))
 
- 			continue;
 
- 		/* convert strings to wide */
 
- 		wchar_t sourceURL[1024];
 
- 		wchar_t updateFileName[MAX_PATH];
 
- 		wchar_t updateHashStr[BLAKE2_HASH_STR_LENGTH];
 
- 		wchar_t tempFilePath[MAX_PATH];
 
- 		if (!UTF8ToWideBuf(updateFileName, fileUTF8.c_str()))
 
- 			continue;
 
- 		if (!UTF8ToWideBuf(updateHashStr, hashUTF8.c_str()))
 
- 			continue;
 
- 		/* make sure paths are safe */
 
- 		if (!IsSafeFilename(updateFileName)) {
 
- 			Status(L"Update failed: Unsafe path '%s' found in "
 
- 			       L"manifest",
 
- 			       updateFileName);
 
- 			return false;
 
- 		}
 
- 		StringCbPrintf(sourceURL, sizeof(sourceURL), L"%s/%s/%s/%s",
 
- 			       UPDATE_URL, branch, wPackageName,
 
- 			       updateFileName);
 
- 		StringCbPrintf(tempFilePath, sizeof(tempFilePath), L"%s\\%s",
 
- 			       tempPath, updateHashStr);
 
- 		/* Check file hash */
 
- 		wstring fileHashStr;
 
- 		bool has_hash;
 
- 		/* We don't really care if this fails, it's just to avoid
 
- 		 * wasting bandwidth by downloading unmodified files */
 
- 		if (hashes.count(fileUTF8)) {
 
- 			fileHashStr = hashes[fileUTF8];
 
- 			if (fileHashStr == updateHashStr)
 
- 				continue;
 
- 			has_hash = true;
 
- 		} else {
 
- 			has_hash = false;
 
- 		}
 
- 		/* Add update file */
 
- 		update_t update;
 
- 		update.fileSize = fileSize;
 
- 		update.basename = updateFileName;
 
- 		update.outputPath = updateFileName;
 
- 		update.tempPath = tempFilePath;
 
- 		update.sourceURL = sourceURL;
 
- 		update.packageName = packageName;
 
- 		update.state = STATE_PENDING_DOWNLOAD;
 
- 		update.patchable = false;
 
- 		StringToHash(updateHashStr, update.downloadhash);
 
- 		memcpy(update.hash, update.downloadhash, sizeof(update.hash));
 
- 		update.has_hash = has_hash;
 
- 		if (has_hash)
 
- 			StringToHash(fileHashStr.data(), update.my_hash);
 
- 		updates.push_back(move(update));
 
- 		totalFileSize += fileSize;
 
- 	}
 
- 	return true;
 
- }
 
- static void AddPackageRemovedFiles(const Json &package)
 
- {
 
- 	const Json &removed_files = package["removed_files"];
 
- 	if (!removed_files.is_array())
 
- 		return;
 
- 	for (auto &item : removed_files.array_items()) {
 
- 		if (!item.is_string())
 
- 			continue;
 
- 		wchar_t removedFileName[MAX_PATH];
 
- 		if (!UTF8ToWideBuf(removedFileName,
 
- 				   item.string_value().c_str()))
 
- 			continue;
 
- 		/* Ensure paths are safe, also check if file exists */
 
- 		if (!IsSafeFilename(removedFileName))
 
- 			continue;
 
- 		/* Technically GetFileAttributes can fail for other reasons,
 
- 		 * so double-check by also checking the last error */
 
- 		if (GetFileAttributesW(removedFileName) ==
 
- 		    INVALID_FILE_ATTRIBUTES) {
 
- 			int err = GetLastError();
 
- 			if (err == ERROR_FILE_NOT_FOUND ||
 
- 			    err == ERROR_PATH_NOT_FOUND)
 
- 				continue;
 
- 		}
 
- 		deletion_t deletion;
 
- 		deletion.originalFilename = removedFileName;
 
- 		deletions.push_back(deletion);
 
- 	}
 
- }
 
- static bool RenameRemovedFile(deletion_t &deletion)
 
- {
 
- 	_TCHAR deleteMeName[MAX_PATH];
 
- 	_TCHAR randomStr[MAX_PATH];
 
- 	BYTE junk[40];
 
- 	BYTE hash[BLAKE2_HASH_LENGTH];
 
- 	CryptGenRandom(hProvider, sizeof(junk), junk);
 
- 	blake2b(hash, sizeof(hash), junk, sizeof(junk), NULL, 0);
 
- 	HashToString(hash, randomStr);
 
- 	randomStr[8] = 0;
 
- 	StringCbCopy(deleteMeName, sizeof(deleteMeName),
 
- 		     deletion.originalFilename.c_str());
 
- 	StringCbCat(deleteMeName, sizeof(deleteMeName), L".");
 
- 	StringCbCat(deleteMeName, sizeof(deleteMeName), randomStr);
 
- 	StringCbCat(deleteMeName, sizeof(deleteMeName), L".deleteme");
 
- 	if (MoveFile(deletion.originalFilename.c_str(), deleteMeName)) {
 
- 		/* Only set this if the file was successfully renamed */
 
- 		deletion.deleteMeFilename = deleteMeName;
 
- 		return true;
 
- 	}
 
- 	return false;
 
- }
 
- static void UpdateWithPatchIfAvailable(const char *name, const char *hash,
 
- 				       const char *source, int size)
 
- {
 
- 	wchar_t widePatchableFilename[MAX_PATH];
 
- 	wchar_t widePatchHash[MAX_PATH];
 
- 	wchar_t sourceURL[1024];
 
- 	wchar_t patchHashStr[BLAKE2_HASH_STR_LENGTH];
 
- 	if (strncmp(source, "https://cdn-fastly.obsproject.com/", 34) != 0)
 
- 		return;
 
- 	string patchPackageName = name;
 
- 	const char *slash = strchr(name, '/');
 
- 	if (!slash)
 
- 		return;
 
- 	patchPackageName.resize(slash - name);
 
- 	name = slash + 1;
 
- 	if (!UTF8ToWideBuf(widePatchableFilename, name))
 
- 		return;
 
- 	if (!UTF8ToWideBuf(widePatchHash, hash))
 
- 		return;
 
- 	if (!UTF8ToWideBuf(sourceURL, source))
 
- 		return;
 
- 	if (!UTF8ToWideBuf(patchHashStr, hash))
 
- 		return;
 
- 	for (update_t &update : updates) {
 
- 		if (update.packageName != patchPackageName)
 
- 			continue;
 
- 		if (update.basename != widePatchableFilename)
 
- 			continue;
 
- 		StringToHash(patchHashStr, update.downloadhash);
 
- 		/* Replace the source URL with the patch file, mark it as
 
- 		 * patchable, and re-calculate download size */
 
- 		totalFileSize -= (update.fileSize - size);
 
- 		update.sourceURL = sourceURL;
 
- 		update.fileSize = size;
 
- 		update.patchable = true;
 
- 		/* Since the patch depends on the previous version, we can
 
- 		 * no longer rely on the temp name being unique to the
 
- 		 * new file's hash */
 
- 		update.tempPath = tempPath;
 
- 		update.tempPath += L"\\";
 
- 		update.tempPath += patchHashStr;
 
- 		break;
 
- 	}
 
- }
 
- static bool MoveInUseFileAway(update_t &file)
 
- {
 
- 	_TCHAR deleteMeName[MAX_PATH];
 
- 	_TCHAR randomStr[MAX_PATH];
 
- 	BYTE junk[40];
 
- 	BYTE hash[BLAKE2_HASH_LENGTH];
 
- 	CryptGenRandom(hProvider, sizeof(junk), junk);
 
- 	blake2b(hash, sizeof(hash), junk, sizeof(junk), NULL, 0);
 
- 	HashToString(hash, randomStr);
 
- 	randomStr[8] = 0;
 
- 	StringCbCopy(deleteMeName, sizeof(deleteMeName),
 
- 		     file.outputPath.c_str());
 
- 	StringCbCat(deleteMeName, sizeof(deleteMeName), L".");
 
- 	StringCbCat(deleteMeName, sizeof(deleteMeName), randomStr);
 
- 	StringCbCat(deleteMeName, sizeof(deleteMeName), L".deleteme");
 
- 	if (MoveFile(file.outputPath.c_str(), deleteMeName)) {
 
- 		if (MyCopyFile(deleteMeName, file.outputPath.c_str())) {
 
- 			MoveFileEx(deleteMeName, NULL,
 
- 				   MOVEFILE_DELAY_UNTIL_REBOOT);
 
- 			return true;
 
- 		} else {
 
- 			MoveFile(deleteMeName, file.outputPath.c_str());
 
- 		}
 
- 	}
 
- 	return false;
 
- }
 
- static bool UpdateFile(update_t &file)
 
- {
 
- 	wchar_t oldFileRenamedPath[MAX_PATH];
 
- 	if (file.patchable)
 
- 		Status(L"Updating %s...", file.outputPath.c_str());
 
- 	else
 
- 		Status(L"Installing %s...", file.outputPath.c_str());
 
- 	/* Check if we're replacing an existing file or just installing a new
 
- 	 * one */
 
- 	DWORD attribs = GetFileAttributes(file.outputPath.c_str());
 
- 	if (attribs != INVALID_FILE_ATTRIBUTES) {
 
- 		wchar_t *curFileName = nullptr;
 
- 		wchar_t baseName[MAX_PATH];
 
- 		StringCbCopy(baseName, sizeof(baseName),
 
- 			     file.outputPath.c_str());
 
- 		curFileName = wcsrchr(baseName, '/');
 
- 		if (curFileName) {
 
- 			curFileName[0] = '\0';
 
- 			curFileName++;
 
- 		} else
 
- 			curFileName = baseName;
 
- 		/* Backup the existing file in case a rollback is needed */
 
- 		StringCbCopy(oldFileRenamedPath, sizeof(oldFileRenamedPath),
 
- 			     file.outputPath.c_str());
 
- 		StringCbCat(oldFileRenamedPath, sizeof(oldFileRenamedPath),
 
- 			    L".old");
 
- 		if (!MyCopyFile(file.outputPath.c_str(), oldFileRenamedPath)) {
 
- 			int is_sharing_violation =
 
- 				(GetLastError() == ERROR_SHARING_VIOLATION);
 
- 			if (is_sharing_violation)
 
- 				Status(L"Update failed: %s is still in use.  "
 
- 				       L"Close all programs and try again.",
 
- 				       curFileName);
 
- 			else
 
- 				Status(L"Update failed: Couldn't backup %s "
 
- 				       L"(error %d)",
 
- 				       curFileName, GetLastError());
 
- 			return false;
 
- 		}
 
- 		file.previousFile = oldFileRenamedPath;
 
- 		int error_code;
 
- 		bool installed_ok;
 
- 		bool already_tried_to_move = false;
 
- 	retryAfterMovingFile:
 
- 		if (file.patchable) {
 
- 			error_code = ApplyPatch(file.tempPath.c_str(),
 
- 						file.outputPath.c_str());
 
- 			installed_ok = (error_code == 0);
 
- 			if (installed_ok) {
 
- 				BYTE patchedFileHash[BLAKE2_HASH_LENGTH];
 
- 				if (!CalculateFileHash(file.outputPath.c_str(),
 
- 						       patchedFileHash)) {
 
- 					Status(L"Update failed: Couldn't "
 
- 					       L"verify integrity of patched %s",
 
- 					       curFileName);
 
- 					file.state = STATE_INSTALL_FAILED;
 
- 					return false;
 
- 				}
 
- 				if (memcmp(file.hash, patchedFileHash,
 
- 					   BLAKE2_HASH_LENGTH) != 0) {
 
- 					Status(L"Update failed: Integrity "
 
- 					       L"check of patched "
 
- 					       L"%s failed",
 
- 					       curFileName);
 
- 					file.state = STATE_INSTALL_FAILED;
 
- 					return false;
 
- 				}
 
- 			}
 
- 		} else {
 
- 			installed_ok = MyCopyFile(file.tempPath.c_str(),
 
- 						  file.outputPath.c_str());
 
- 			error_code = GetLastError();
 
- 		}
 
- 		if (!installed_ok) {
 
- 			int is_sharing_violation =
 
- 				(error_code == ERROR_SHARING_VIOLATION);
 
- 			if (is_sharing_violation) {
 
- 				if (!already_tried_to_move) {
 
- 					already_tried_to_move = true;
 
- 					if (MoveInUseFileAway(file))
 
- 						goto retryAfterMovingFile;
 
- 				}
 
- 				Status(L"Update failed: %s is still in use.  "
 
- 				       L"Close all "
 
- 				       L"programs and try again.",
 
- 				       curFileName);
 
- 			} else {
 
- 				Status(L"Update failed: Couldn't update %s "
 
- 				       L"(error %d)",
 
- 				       curFileName, GetLastError());
 
- 			}
 
- 			file.state = STATE_INSTALL_FAILED;
 
- 			return false;
 
- 		}
 
- 		file.state = STATE_INSTALLED;
 
- 	} else {
 
- 		if (file.patchable) {
 
- 			/* Uh oh, we thought we could patch something but it's
 
- 			 * no longer there! */
 
- 			Status(L"Update failed: Source file %s not found",
 
- 			       file.outputPath.c_str());
 
- 			return false;
 
- 		}
 
- 		/* We may be installing into new folders,
 
- 		 * make sure they exist */
 
- 		CreateFoldersForPath(file.outputPath.c_str());
 
- 		file.previousFile = L"";
 
- 		bool success = !!MyCopyFile(file.tempPath.c_str(),
 
- 					    file.outputPath.c_str());
 
- 		if (!success) {
 
- 			Status(L"Update failed: Couldn't install %s (error %d)",
 
- 			       file.outputPath.c_str(), GetLastError());
 
- 			file.state = STATE_INSTALL_FAILED;
 
- 			return false;
 
- 		}
 
- 		file.state = STATE_INSTALLED;
 
- 	}
 
- 	return true;
 
- }
 
- #define PATCH_MANIFEST_URL \
 
- 	L"https://obsproject.com/update_studio/getpatchmanifest"
 
- #define HASH_NULL L"0000000000000000000000000000000000000000"
 
- static bool UpdateVS2019Redists(const Json &root)
 
- {
 
- 	/* ------------------------------------------ *
 
- 	 * Initialize session                         */
 
- 	const DWORD tlsProtocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
 
- 	HttpHandle hSession = WinHttpOpen(L"OBS Studio Updater/2.2",
 
- 					  WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
 
- 					  WINHTTP_NO_PROXY_NAME,
 
- 					  WINHTTP_NO_PROXY_BYPASS, 0);
 
- 	if (!hSession) {
 
- 		Status(L"Update failed: Couldn't open obsproject.com");
 
- 		return false;
 
- 	}
 
- 	WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS,
 
- 			 (LPVOID)&tlsProtocols, sizeof(tlsProtocols));
 
- 	HttpHandle hConnect = WinHttpConnect(hSession,
 
- 					     L"cdn-fastly.obsproject.com",
 
- 					     INTERNET_DEFAULT_HTTPS_PORT, 0);
 
- 	if (!hConnect) {
 
- 		Status(L"Update failed: Couldn't connect to cdn-fastly.obsproject.com");
 
- 		return false;
 
- 	}
 
- 	int responseCode;
 
- 	DWORD waitResult = WaitForSingleObject(cancelRequested, 0);
 
- 	if (waitResult == WAIT_OBJECT_0) {
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------------ *
 
- 	 * Download redist                            */
 
- 	Status(L"Downloading %s", L"Visual C++ 2019 Redistributable");
 
- 	const wchar_t *file = (is32bit) ? L"VC_redist.x86.exe"
 
- 					: L"VC_redist.x64.exe";
 
- 	wstring sourceURL;
 
- 	sourceURL += L"https://cdn-fastly.obsproject.com/downloads/";
 
- 	sourceURL += file;
 
- 	wstring destPath;
 
- 	destPath += tempPath;
 
- 	destPath += L"\\";
 
- 	destPath += file;
 
- 	if (!HTTPGetFile(hConnect, sourceURL.c_str(), destPath.c_str(),
 
- 			 L"Accept-Encoding: gzip", &responseCode)) {
 
- 		DeleteFile(destPath.c_str());
 
- 		Status(L"Update failed: Could not download "
 
- 		       L"%s (error code %d)",
 
- 		       L"Visual C++ 2019 Redistributable", responseCode);
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------------ *
 
- 	 * Get expected hash                          */
 
- 	const char *which = is32bit ? "vc2019_redist_x86" : "vc2019_redist_x64";
 
- 	const Json &redistJson = root[which];
 
- 	if (!redistJson.is_string()) {
 
- 		Status(L"Update failed: Could not parse VC2019 redist json");
 
- 		return false;
 
- 	}
 
- 	const string &expectedHashUTF8 = redistJson.string_value();
 
- 	wchar_t expectedHashWide[BLAKE2_HASH_STR_LENGTH];
 
- 	BYTE expectedHash[BLAKE2_HASH_LENGTH];
 
- 	if (!UTF8ToWideBuf(expectedHashWide, expectedHashUTF8.c_str())) {
 
- 		DeleteFile(destPath.c_str());
 
- 		Status(L"Update failed: Couldn't convert Json for redist hash");
 
- 		return false;
 
- 	}
 
- 	StringToHash(expectedHashWide, expectedHash);
 
- 	wchar_t downloadHashWide[BLAKE2_HASH_STR_LENGTH];
 
- 	BYTE downloadHash[BLAKE2_HASH_LENGTH];
 
- 	/* ------------------------------------------ *
 
- 	 * Get download hash                          */
 
- 	if (!CalculateFileHash(destPath.c_str(), downloadHash)) {
 
- 		DeleteFile(destPath.c_str());
 
- 		Status(L"Update failed: Couldn't verify integrity of %s",
 
- 		       L"Visual C++ 2019 Redistributable");
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------------ *
 
- 	 * If hashes do not match, integrity failed   */
 
- 	HashToString(downloadHash, downloadHashWide);
 
- 	if (wcscmp(expectedHashWide, downloadHashWide) != 0) {
 
- 		DeleteFile(destPath.c_str());
 
- 		Status(L"Update failed: Couldn't verify integrity of %s",
 
- 		       L"Visual C++ 2019 Redistributable");
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------------ *
 
- 	 * If hashes match, install redist            */
 
- 	wchar_t commandline[MAX_PATH + MAX_PATH];
 
- 	StringCbPrintf(commandline, sizeof(commandline),
 
- 		       L"%s /install /quiet /norestart", destPath.c_str());
 
- 	PROCESS_INFORMATION pi = {};
 
- 	STARTUPINFO si = {};
 
- 	si.cb = sizeof(si);
 
- 	bool success = !!CreateProcessW(destPath.c_str(), commandline, nullptr,
 
- 					nullptr, false, CREATE_NO_WINDOW,
 
- 					nullptr, nullptr, &si, &pi);
 
- 	if (success) {
 
- 		Status(L"Installing %s...", L"Visual C++ 2019 Redistributable");
 
- 		CloseHandle(pi.hThread);
 
- 		WaitForSingleObject(pi.hProcess, INFINITE);
 
- 		CloseHandle(pi.hProcess);
 
- 	} else {
 
- 		Status(L"Update failed: Could not execute "
 
- 		       L"%s (error code %d)",
 
- 		       L"Visual C++ 2019 Redistributable", (int)GetLastError());
 
- 	}
 
- 	DeleteFile(destPath.c_str());
 
- 	waitResult = WaitForSingleObject(cancelRequested, 0);
 
- 	if (waitResult == WAIT_OBJECT_0) {
 
- 		return false;
 
- 	}
 
- 	return success;
 
- }
 
- extern "C" void UpdateHookFiles(void);
 
- static bool Update(wchar_t *cmdLine)
 
- {
 
- 	/* ------------------------------------- *
 
- 	 * Check to make sure OBS isn't running  */
 
- 	HANDLE hObsUpdateMutex =
 
- 		OpenMutexW(SYNCHRONIZE, false, L"OBSStudioUpdateMutex");
 
- 	if (hObsUpdateMutex) {
 
- 		HANDLE hWait[2];
 
- 		hWait[0] = hObsUpdateMutex;
 
- 		hWait[1] = cancelRequested;
 
- 		int i = WaitForMultipleObjects(2, hWait, false, INFINITE);
 
- 		if (i == WAIT_OBJECT_0)
 
- 			ReleaseMutex(hObsUpdateMutex);
 
- 		CloseHandle(hObsUpdateMutex);
 
- 		if (i == WAIT_OBJECT_0 + 1)
 
- 			return false;
 
- 	}
 
- 	if (!WaitForOBS())
 
- 		return false;
 
- 	/* ------------------------------------- *
 
- 	 * Init crypt stuff                      */
 
- 	CryptProvider hProvider;
 
- 	if (!CryptAcquireContext(&hProvider, nullptr, MS_ENH_RSA_AES_PROV,
 
- 				 PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
 
- 		SetDlgItemTextW(hwndMain, IDC_STATUS,
 
- 				L"Update failed: CryptAcquireContext failure");
 
- 		return false;
 
- 	}
 
- 	::hProvider = hProvider;
 
- 	/* ------------------------------------- */
 
- 	SetDlgItemTextW(hwndMain, IDC_STATUS,
 
- 			L"Searching for available updates...");
 
- 	HWND hProgress = GetDlgItem(hwndMain, IDC_PROGRESS);
 
- 	LONG_PTR style = GetWindowLongPtr(hProgress, GWL_STYLE);
 
- 	SetWindowLongPtr(hProgress, GWL_STYLE, style | PBS_MARQUEE);
 
- 	SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETMARQUEE, 1, 0);
 
- 	/* ------------------------------------- *
 
- 	 * Check if updating portable build      */
 
- 	bool bIsPortable = false;
 
- 	wstring branch = L"stable";
 
- 	wstring appdata;
 
- 	if (cmdLine[0]) {
 
- 		int argc;
 
- 		LPWSTR *argv = CommandLineToArgvW(cmdLine, &argc);
 
- 		if (argv) {
 
- 			for (int i = 0; i < argc; i++) {
 
- 				if (wcscmp(argv[i], L"Portable") == 0) {
 
- 					// Legacy OBS
 
- 					bIsPortable = true;
 
- 					break;
 
- 				} else if (wcsncmp(argv[i], L"--branch=", 9) ==
 
- 					   0) {
 
- 					branch = argv[i] + 9;
 
- 				} else if (wcsncmp(argv[i], L"--appdata=",
 
- 						   10) == 0) {
 
- 					appdata = argv[i] + 10;
 
- 				} else if (wcscmp(argv[i], L"--portable") ==
 
- 					   0) {
 
- 					bIsPortable = true;
 
- 				}
 
- 			}
 
- 			LocalFree((HLOCAL)argv);
 
- 		}
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Get config path                       */
 
- 	wchar_t lpAppDataPath[MAX_PATH];
 
- 	lpAppDataPath[0] = 0;
 
- 	if (bIsPortable) {
 
- 		GetCurrentDirectory(_countof(lpAppDataPath), lpAppDataPath);
 
- 		StringCbCat(lpAppDataPath, sizeof(lpAppDataPath), L"\\config");
 
- 	} else {
 
- 		if (!appdata.empty()) {
 
- 			HRESULT hr = StringCbCopy(lpAppDataPath,
 
- 						  sizeof(lpAppDataPath),
 
- 						  appdata.c_str());
 
- 			if (hr != S_OK) {
 
- 				Status(L"Update failed: Could not determine AppData "
 
- 				       L"location");
 
- 				return false;
 
- 			}
 
- 		} else {
 
- 			CoTaskMemPtr<wchar_t> pOut;
 
- 			HRESULT hr = SHGetKnownFolderPath(
 
- 				FOLDERID_RoamingAppData, KF_FLAG_DEFAULT,
 
- 				nullptr, &pOut);
 
- 			if (hr != S_OK) {
 
- 				Status(L"Update failed: Could not determine AppData "
 
- 				       L"location");
 
- 				return false;
 
- 			}
 
- 			StringCbCopy(lpAppDataPath, sizeof(lpAppDataPath),
 
- 				     pOut);
 
- 		}
 
- 	}
 
- 	StringCbCat(lpAppDataPath, sizeof(lpAppDataPath), L"\\obs-studio");
 
- 	/* ------------------------------------- *
 
- 	 * Get download path                     */
 
- 	wchar_t manifestPath[MAX_PATH];
 
- 	wchar_t tempDirName[MAX_PATH];
 
- 	manifestPath[0] = 0;
 
- 	tempDirName[0] = 0;
 
- 	StringCbPrintf(manifestPath, sizeof(manifestPath),
 
- 		       L"%s\\updates\\manifest.json", lpAppDataPath);
 
- 	if (!GetTempPathW(_countof(tempDirName), tempDirName)) {
 
- 		Status(L"Update failed: Failed to get temp path: %ld",
 
- 		       GetLastError());
 
- 		return false;
 
- 	}
 
- 	if (!GetTempFileNameW(tempDirName, L"obs-studio", 0, tempPath)) {
 
- 		Status(L"Update failed: Failed to create temp dir name: %ld",
 
- 		       GetLastError());
 
- 		return false;
 
- 	}
 
- 	DeleteFile(tempPath);
 
- 	CreateDirectory(tempPath, nullptr);
 
- 	/* ------------------------------------- *
 
- 	 * Load manifest file                    */
 
- 	Json root;
 
- 	{
 
- 		string manifestFile = QuickReadFile(manifestPath);
 
- 		if (manifestFile.empty()) {
 
- 			Status(L"Update failed: Couldn't load manifest file");
 
- 			return false;
 
- 		}
 
- 		string error;
 
- 		root = Json::parse(manifestFile, error);
 
- 		if (!error.empty()) {
 
- 			Status(L"Update failed: Couldn't parse update "
 
- 			       L"manifest: %S",
 
- 			       error.c_str());
 
- 			return false;
 
- 		}
 
- 	}
 
- 	if (!root.is_object()) {
 
- 		Status(L"Update failed: Invalid update manifest");
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Hash local files listed in manifest   */
 
- 	RunHasherWorkers(4, root["packages"]);
 
- 	/* ------------------------------------- *
 
- 	 * Parse current manifest update files   */
 
- 	const Json::array &packages = root["packages"].array_items();
 
- 	for (size_t i = 0; i < packages.size(); i++) {
 
- 		if (!AddPackageUpdateFiles(packages, i, tempPath,
 
- 					   branch.c_str())) {
 
- 			Status(L"Update failed: Failed to process update packages");
 
- 			return false;
 
- 		}
 
- 		/* Add removed files to deletion queue (if any) */
 
- 		AddPackageRemovedFiles(packages[i]);
 
- 	}
 
- 	SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETMARQUEE, 0, 0);
 
- 	SetWindowLongPtr(hProgress, GWL_STYLE, style);
 
- 	/* ------------------------------------- *
 
- 	 * Exit if updates already installed     */
 
- 	if (!updates.size()) {
 
- 		Status(L"All available updates are already installed.");
 
- 		SetDlgItemText(hwndMain, IDC_BUTTON, L"Launch OBS");
 
- 		return true;
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Check for VS2019 redistributables     */
 
- 	if (!HasVS2019Redist()) {
 
- 		if (!UpdateVS2019Redists(root)) {
 
- 			return false;
 
- 		}
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Generate file hash json               */
 
- 	Json::array files;
 
- 	for (update_t &update : updates) {
 
- 		wchar_t whash_string[BLAKE2_HASH_STR_LENGTH];
 
- 		char hash_string[BLAKE2_HASH_STR_LENGTH];
 
- 		char outputPath[MAX_PATH];
 
- 		if (!update.has_hash)
 
- 			continue;
 
- 		/* check hash */
 
- 		HashToString(update.my_hash, whash_string);
 
- 		if (wcscmp(whash_string, HASH_NULL) == 0)
 
- 			continue;
 
- 		if (!WideToUTF8Buf(hash_string, whash_string))
 
- 			continue;
 
- 		if (!WideToUTF8Buf(outputPath, update.basename.c_str()))
 
- 			continue;
 
- 		string package_path;
 
- 		package_path = update.packageName;
 
- 		package_path += "/";
 
- 		package_path += outputPath;
 
- 		files.emplace_back(Json::object{
 
- 			{"name", package_path},
 
- 			{"hash", hash_string},
 
- 		});
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Send file hashes                      */
 
- 	string newManifest;
 
- 	if (files.size() > 0) {
 
- 		string post_body;
 
- 		Json(files).dump(post_body);
 
- 		int responseCode;
 
- 		int len = (int)post_body.size();
 
- 		uLong compressSize = compressBound(len);
 
- 		string compressedJson;
 
- 		compressedJson.resize(compressSize);
 
- 		compress2((Bytef *)&compressedJson[0], &compressSize,
 
- 			  (const Bytef *)post_body.c_str(), len,
 
- 			  Z_BEST_COMPRESSION);
 
- 		compressedJson.resize(compressSize);
 
- 		wstring manifestUrl(PATCH_MANIFEST_URL);
 
- 		if (branch != L"stable")
 
- 			manifestUrl += L"?branch=" + branch;
 
- 		bool success = !!HTTPPostData(manifestUrl.c_str(),
 
- 					      (BYTE *)&compressedJson[0],
 
- 					      (int)compressedJson.size(),
 
- 					      L"Accept-Encoding: gzip",
 
- 					      &responseCode, newManifest);
 
- 		if (!success)
 
- 			return false;
 
- 		if (responseCode != 200) {
 
- 			Status(L"Update failed: HTTP/%d while trying to "
 
- 			       L"download patch manifest",
 
- 			       responseCode);
 
- 			return false;
 
- 		}
 
- 	} else {
 
- 		newManifest = "[]";
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Parse new manifest                    */
 
- 	string error;
 
- 	root = Json::parse(newManifest, error);
 
- 	if (!error.empty()) {
 
- 		Status(L"Update failed: Couldn't parse patch manifest: %S",
 
- 		       error.c_str());
 
- 		return false;
 
- 	}
 
- 	if (!root.is_array()) {
 
- 		Status(L"Update failed: Invalid patch manifest");
 
- 		return false;
 
- 	}
 
- 	size_t packageCount = root.array_items().size();
 
- 	for (size_t i = 0; i < packageCount; i++) {
 
- 		const Json &patch = root[i];
 
- 		if (!patch.is_object()) {
 
- 			Status(L"Update failed: Invalid patch manifest");
 
- 			return false;
 
- 		}
 
- 		const Json &name_json = patch["name"];
 
- 		const Json &hash_json = patch["hash"];
 
- 		const Json &source_json = patch["source"];
 
- 		const Json &size_json = patch["size"];
 
- 		if (!name_json.is_string())
 
- 			continue;
 
- 		if (!hash_json.is_string())
 
- 			continue;
 
- 		if (!source_json.is_string())
 
- 			continue;
 
- 		if (!size_json.is_number())
 
- 			continue;
 
- 		const string &name = name_json.string_value();
 
- 		const string &hash = hash_json.string_value();
 
- 		const string &source = source_json.string_value();
 
- 		int size = size_json.int_value();
 
- 		UpdateWithPatchIfAvailable(name.c_str(), hash.c_str(),
 
- 					   source.c_str(), size);
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Deduplicate Downloads                 */
 
- 	unordered_set<wstring> tempFiles;
 
- 	for (update_t &update : updates) {
 
- 		if (update.patchable)
 
- 			continue;
 
- 		if (tempFiles.count(update.tempPath)) {
 
- 			update.state = STATE_ALREADY_DOWNLOADED;
 
- 			totalFileSize -= update.fileSize;
 
- 			completedUpdates++;
 
- 			continue;
 
- 		}
 
- 		tempFiles.insert(update.tempPath);
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Download Updates                      */
 
- 	if (!RunDownloadWorkers(4))
 
- 		return false;
 
- 	if ((size_t)completedUpdates != updates.size()) {
 
- 		Status(L"Update failed to download all files.");
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Install updates                       */
 
- 	int updatesInstalled = 0;
 
- 	int lastPosition = 0;
 
- 	SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETPOS, 0, 0);
 
- 	for (update_t &update : updates) {
 
- 		if (!UpdateFile(update)) {
 
- 			return false;
 
- 		} else {
 
- 			updatesInstalled++;
 
- 			int position = (int)(((float)updatesInstalled /
 
- 					      (float)completedUpdates) *
 
- 					     100.0f);
 
- 			if (position > lastPosition) {
 
- 				lastPosition = position;
 
- 				SendDlgItemMessage(hwndMain, IDC_PROGRESS,
 
- 						   PBM_SETPOS, position, 0);
 
- 			}
 
- 		}
 
- 	}
 
- 	for (deletion_t &deletion : deletions) {
 
- 		if (!RenameRemovedFile(deletion)) {
 
- 			Status(L"Update failed: Couldn't remove "
 
- 			       L"obsolete files");
 
- 			return false;
 
- 		}
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Install virtual camera                */
 
- 	auto runcommand = [](wchar_t *cmd) {
 
- 		STARTUPINFO si = {};
 
- 		si.cb = sizeof(si);
 
- 		si.dwFlags = STARTF_USESHOWWINDOW;
 
- 		si.wShowWindow = SW_HIDE;
 
- 		PROCESS_INFORMATION pi;
 
- 		bool success = !!CreateProcessW(nullptr, cmd, nullptr, nullptr,
 
- 						false, CREATE_NEW_CONSOLE,
 
- 						nullptr, nullptr, &si, &pi);
 
- 		if (success) {
 
- 			WaitForSingleObject(pi.hProcess, INFINITE);
 
- 			CloseHandle(pi.hThread);
 
- 			CloseHandle(pi.hProcess);
 
- 		}
 
- 	};
 
- 	if (!bIsPortable) {
 
- 		Status(L"Installing Virtual Camera...");
 
- 		wchar_t regsvr[MAX_PATH];
 
- 		wchar_t src[MAX_PATH];
 
- 		wchar_t tmp[MAX_PATH];
 
- 		wchar_t tmp2[MAX_PATH];
 
- 		SHGetFolderPathW(nullptr, CSIDL_SYSTEM, nullptr,
 
- 				 SHGFP_TYPE_CURRENT, regsvr);
 
- 		StringCbCat(regsvr, sizeof(regsvr), L"\\regsvr32.exe");
 
- 		GetCurrentDirectoryW(_countof(src), src);
 
- 		StringCbCat(src, sizeof(src),
 
- 			    L"\\data\\obs-plugins\\win-dshow\\");
 
- 		StringCbCopy(tmp, sizeof(tmp), L"\"");
 
- 		StringCbCat(tmp, sizeof(tmp), regsvr);
 
- 		StringCbCat(tmp, sizeof(tmp), L"\" /s \"");
 
- 		StringCbCat(tmp, sizeof(tmp), src);
 
- 		StringCbCat(tmp, sizeof(tmp), L"obs-virtualcam-module");
 
- 		StringCbCopy(tmp2, sizeof(tmp2), tmp);
 
- 		StringCbCat(tmp2, sizeof(tmp2), L"32.dll\"");
 
- 		runcommand(tmp2);
 
- 		if (is_64bit_windows()) {
 
- 			StringCbCopy(tmp2, sizeof(tmp2), tmp);
 
- 			StringCbCat(tmp2, sizeof(tmp2), L"64.dll\"");
 
- 			runcommand(tmp2);
 
- 		}
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Update hook files and vulkan registry */
 
- 	Status(L"Updating Game Capture hooks...");
 
- 	UpdateHookFiles();
 
- 	/* ------------------------------------- *
 
- 	 * Finish                                */
 
- 	Status(L"Cleaning up...");
 
- 	/* If we get here, all updates installed successfully so we can purge
 
- 	 * the old versions */
 
- 	for (update_t &update : updates) {
 
- 		if (!update.previousFile.empty())
 
- 			DeleteFile(update.previousFile.c_str());
 
- 		/* We delete here not above in case of duplicate hashes */
 
- 		if (!update.tempPath.empty())
 
- 			DeleteFile(update.tempPath.c_str());
 
- 	}
 
- 	/* Delete all removed files mentioned in the manifest */
 
- 	for (deletion_t &deletion : deletions)
 
- 		MyDeleteFile(deletion.deleteMeFilename);
 
- 	SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETPOS, 100, 0);
 
- 	Status(L"Update complete.");
 
- 	SetDlgItemText(hwndMain, IDC_BUTTON, L"Launch OBS");
 
- 	return true;
 
- }
 
- static DWORD WINAPI UpdateThread(void *arg)
 
- {
 
- 	wchar_t *cmdLine = (wchar_t *)arg;
 
- 	bool success = Update(cmdLine);
 
- 	if (!success) {
 
- 		/* This handles deleting temp files and rolling back and
 
- 		 * partially installed updates */
 
- 		CleanupPartialUpdates();
 
- 		if (tempPath[0])
 
- 			RemoveDirectory(tempPath);
 
- 		if (WaitForSingleObject(cancelRequested, 0) == WAIT_OBJECT_0)
 
- 			Status(L"Update aborted.");
 
- 		HWND hProgress = GetDlgItem(hwndMain, IDC_PROGRESS);
 
- 		LONG_PTR style = GetWindowLongPtr(hProgress, GWL_STYLE);
 
- 		SetWindowLongPtr(hProgress, GWL_STYLE, style & ~PBS_MARQUEE);
 
- 		SendMessage(hProgress, PBM_SETSTATE, PBST_ERROR, 0);
 
- 		SetDlgItemText(hwndMain, IDC_BUTTON, L"Exit");
 
- 		EnableWindow(GetDlgItem(hwndMain, IDC_BUTTON), true);
 
- 		updateFailed = true;
 
- 	} else {
 
- 		if (tempPath[0])
 
- 			RemoveDirectory(tempPath);
 
- 	}
 
- 	if (bExiting)
 
- 		ExitProcess(success);
 
- 	return 0;
 
- }
 
- static void CancelUpdate(bool quit)
 
- {
 
- 	if (WaitForSingleObject(updateThread, 0) != WAIT_OBJECT_0) {
 
- 		bExiting = quit;
 
- 		SetEvent(cancelRequested);
 
- 	} else {
 
- 		PostQuitMessage(0);
 
- 	}
 
- }
 
- static void LaunchOBS(bool portable)
 
- {
 
- 	wchar_t cwd[MAX_PATH];
 
- 	wchar_t newCwd[MAX_PATH];
 
- 	wchar_t obsPath[MAX_PATH];
 
- 	GetCurrentDirectory(_countof(cwd) - 1, cwd);
 
- 	StringCbCopy(obsPath, sizeof(obsPath), cwd);
 
- 	StringCbCat(obsPath, sizeof(obsPath),
 
- 		    is32bit ? L"\\bin\\32bit" : L"\\bin\\64bit");
 
- 	SetCurrentDirectory(obsPath);
 
- 	StringCbCopy(newCwd, sizeof(newCwd), obsPath);
 
- 	StringCbCat(obsPath, sizeof(obsPath),
 
- 		    is32bit ? L"\\obs32.exe" : L"\\obs64.exe");
 
- 	if (!FileExists(obsPath)) {
 
- 		StringCbCopy(obsPath, sizeof(obsPath), cwd);
 
- 		StringCbCat(obsPath, sizeof(obsPath), L"\\bin\\32bit");
 
- 		SetCurrentDirectory(obsPath);
 
- 		StringCbCopy(newCwd, sizeof(newCwd), obsPath);
 
- 		StringCbCat(obsPath, sizeof(obsPath), L"\\obs32.exe");
 
- 		if (!FileExists(obsPath)) {
 
- 			/* TODO: give user a message maybe? */
 
- 			return;
 
- 		}
 
- 	}
 
- 	SHELLEXECUTEINFO execInfo;
 
- 	ZeroMemory(&execInfo, sizeof(execInfo));
 
- 	execInfo.cbSize = sizeof(execInfo);
 
- 	execInfo.lpFile = obsPath;
 
- 	execInfo.lpDirectory = newCwd;
 
- 	execInfo.nShow = SW_SHOWNORMAL;
 
- 	if (portable)
 
- 		execInfo.lpParameters = L"--portable";
 
- 	ShellExecuteEx(&execInfo);
 
- }
 
- static INT_PTR CALLBACK UpdateDialogProc(HWND hwnd, UINT message, WPARAM wParam,
 
- 					 LPARAM lParam)
 
- {
 
- 	switch (message) {
 
- 	case WM_INITDIALOG: {
 
- 		static HICON hMainIcon =
 
- 			LoadIcon(hinstMain, MAKEINTRESOURCE(IDI_ICON1));
 
- 		SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hMainIcon);
 
- 		SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hMainIcon);
 
- 		return true;
 
- 	}
 
- 	case WM_COMMAND:
 
- 		if (LOWORD(wParam) == IDC_BUTTON) {
 
- 			if (HIWORD(wParam) == BN_CLICKED) {
 
- 				DWORD result =
 
- 					WaitForSingleObject(updateThread, 0);
 
- 				if (result == WAIT_OBJECT_0) {
 
- 					if (updateFailed)
 
- 						PostQuitMessage(0);
 
- 					else
 
- 						PostQuitMessage(1);
 
- 				} else {
 
- 					EnableWindow((HWND)lParam, false);
 
- 					CancelUpdate(false);
 
- 				}
 
- 			}
 
- 		}
 
- 		return true;
 
- 	case WM_CLOSE:
 
- 		CancelUpdate(true);
 
- 		return true;
 
- 	}
 
- 	return false;
 
- }
 
- static int RestartAsAdmin(LPCWSTR lpCmdLine, LPCWSTR cwd)
 
- {
 
- 	wchar_t myPath[MAX_PATH];
 
- 	if (!GetModuleFileNameW(nullptr, myPath, _countof(myPath) - 1)) {
 
- 		return 0;
 
- 	}
 
- 	/* If the admin is a different user, add the path to the user's
 
- 	 * AppData to the command line so we can load the correct manifest. */
 
- 	wstring elevatedCmdLine(lpCmdLine);
 
- 	CoTaskMemPtr<wchar_t> pOut;
 
- 	HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData,
 
- 					  KF_FLAG_DEFAULT, nullptr, &pOut);
 
- 	if (hr == S_OK) {
 
- 		elevatedCmdLine += L" \"--appdata=";
 
- 		elevatedCmdLine += pOut;
 
- 		elevatedCmdLine += L"\"";
 
- 	}
 
- 	SHELLEXECUTEINFO shExInfo = {0};
 
- 	shExInfo.cbSize = sizeof(shExInfo);
 
- 	shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
 
- 	shExInfo.hwnd = 0;
 
- 	shExInfo.lpVerb = L"runas"; /* Operation to perform */
 
- 	shExInfo.lpFile = myPath;   /* Application to start */
 
- 	shExInfo.lpParameters =
 
- 		elevatedCmdLine.c_str(); /* Additional parameters */
 
- 	shExInfo.lpDirectory = cwd;
 
- 	shExInfo.nShow = SW_NORMAL;
 
- 	shExInfo.hInstApp = 0;
 
- 	/* annoyingly the actual elevated updater will disappear behind other
 
- 	 * windows :( */
 
- 	AllowSetForegroundWindow(ASFW_ANY);
 
- 	if (ShellExecuteEx(&shExInfo)) {
 
- 		DWORD exitCode;
 
- 		WaitForSingleObject(shExInfo.hProcess, INFINITE);
 
- 		if (GetExitCodeProcess(shExInfo.hProcess, &exitCode)) {
 
- 			if (exitCode == 1) {
 
- 				return exitCode;
 
- 			}
 
- 		}
 
- 		CloseHandle(shExInfo.hProcess);
 
- 	}
 
- 	return 0;
 
- }
 
- static bool HasElevation()
 
- {
 
- 	SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
 
- 	PSID sid = nullptr;
 
- 	BOOL elevated = false;
 
- 	BOOL success;
 
- 	success = AllocateAndInitializeSid(&sia, 2, SECURITY_BUILTIN_DOMAIN_RID,
 
- 					   DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0,
 
- 					   0, 0, &sid);
 
- 	if (success && sid) {
 
- 		CheckTokenMembership(nullptr, sid, &elevated);
 
- 		FreeSid(sid);
 
- 	}
 
- 	return elevated;
 
- }
 
- int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
 
- {
 
- 	INITCOMMONCONTROLSEX icce;
 
- 	wchar_t cwd[MAX_PATH];
 
- 	wchar_t newPath[MAX_PATH];
 
- 	GetCurrentDirectoryW(_countof(cwd) - 1, cwd);
 
- 	is32bit = wcsstr(cwd, L"bin\\32bit") != nullptr;
 
- 	bool isPortable = wcsstr(lpCmdLine, L"Portable") != nullptr ||
 
- 			  wcsstr(lpCmdLine, L"--portable") != nullptr;
 
- 	if (!IsWindows10OrGreater()) {
 
- 		MessageBox(
 
- 			nullptr,
 
- 			L"OBS Studio 28 and newer no longer support Windows 7,"
 
- 			L" Windows 8, or Windows 8.1. You can disable the"
 
- 			L" following setting to opt out of future updates:"
 
- 			L" Settings → General → General → Automatically check"
 
- 			L" for updates on startup",
 
- 			L"Unsupported Operating System", MB_ICONWARNING);
 
- 		return 0;
 
- 	}
 
- 	if (!HasElevation()) {
 
- 		WinHandle hMutex = OpenMutex(
 
- 			SYNCHRONIZE, false, L"OBSUpdaterRunningAsNonAdminUser");
 
- 		if (hMutex) {
 
- 			MessageBox(
 
- 				nullptr,
 
- 				L"OBS Studio Updater must be run as an administrator.",
 
- 				L"Updater Error", MB_ICONWARNING);
 
- 			return 2;
 
- 		}
 
- 		HANDLE hLowMutex = CreateMutexW(
 
- 			nullptr, true, L"OBSUpdaterRunningAsNonAdminUser");
 
- 		/* return code 1 =  user wanted to launch OBS */
 
- 		if (RestartAsAdmin(lpCmdLine, cwd) == 1) {
 
- 			StringCbCat(cwd, sizeof(cwd), L"\\..\\..");
 
- 			GetFullPathName(cwd, _countof(newPath), newPath,
 
- 					nullptr);
 
- 			SetCurrentDirectory(newPath);
 
- 			LaunchOBS(isPortable);
 
- 		}
 
- 		if (hLowMutex) {
 
- 			ReleaseMutex(hLowMutex);
 
- 			CloseHandle(hLowMutex);
 
- 		}
 
- 		return 0;
 
- 	} else {
 
- 		StringCbCat(cwd, sizeof(cwd), L"\\..\\..");
 
- 		GetFullPathName(cwd, _countof(newPath), newPath, nullptr);
 
- 		SetCurrentDirectory(newPath);
 
- 		hinstMain = hInstance;
 
- 		icce.dwSize = sizeof(icce);
 
- 		icce.dwICC = ICC_PROGRESS_CLASS;
 
- 		InitCommonControlsEx(&icce);
 
- 		hwndMain = CreateDialog(hInstance,
 
- 					MAKEINTRESOURCE(IDD_UPDATEDIALOG),
 
- 					nullptr, UpdateDialogProc);
 
- 		if (!hwndMain) {
 
- 			return -1;
 
- 		}
 
- 		ShowWindow(hwndMain, SW_SHOWNORMAL);
 
- 		SetForegroundWindow(hwndMain);
 
- 		cancelRequested = CreateEvent(nullptr, true, false, nullptr);
 
- 		updateThread = CreateThread(nullptr, 0, UpdateThread, lpCmdLine,
 
- 					    0, nullptr);
 
- 		MSG msg;
 
- 		while (GetMessage(&msg, nullptr, 0, 0)) {
 
- 			if (!IsDialogMessage(hwndMain, &msg)) {
 
- 				TranslateMessage(&msg);
 
- 				DispatchMessage(&msg);
 
- 			}
 
- 		}
 
- 		/* there is no non-elevated process waiting for us if UAC is
 
- 		 * disabled */
 
- 		WinHandle hMutex = OpenMutex(
 
- 			SYNCHRONIZE, false, L"OBSUpdaterRunningAsNonAdminUser");
 
- 		if (msg.wParam == 1 && !hMutex) {
 
- 			LaunchOBS(isPortable);
 
- 		}
 
- 		return (int)msg.wParam;
 
- 	}
 
- }
 
 
  |