| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901 | 
							- /*
 
-  * Copyright (c) 2023 Lain 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 "manifest.hpp"
 
- #include <psapi.h>
 
- #include <WinTrust.h>
 
- #include <SoftPub.h>
 
- #include <util/windows/CoTaskMemPtr.hpp>
 
- #include <future>
 
- #include <string>
 
- #include <string_view>
 
- #include <mutex>
 
- #include <unordered_set>
 
- #include <queue>
 
- using namespace std;
 
- using namespace updater;
 
- /* ----------------------------------------------------------------------- */
 
- constexpr const string_view kCDNUrl = "https://cdn-fastly.obsproject.com/";
 
- constexpr const wchar_t *kCDNHostname = L"cdn-fastly.obsproject.com";
 
- constexpr const wchar_t *kCDNUpdateBaseUrl = L"https://cdn-fastly.obsproject.com/update_studio";
 
- constexpr const wchar_t *kPatchManifestURL = L"https://obsproject.com/update_studio/getpatchmanifest";
 
- constexpr const wchar_t *kVSRedistURL = L"https://aka.ms/vs/17/release/vc_redist.x64.exe";
 
- constexpr const wchar_t *kMSHostname = L"aka.ms";
 
- /* ----------------------------------------------------------------------- */
 
- 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 downloadThreadFailure = false;
 
- size_t totalFileSize = 0;
 
- size_t completedFileSize = 0;
 
- static int completedUpdates = 0;
 
- static wchar_t tempPath[MAX_PATH];
 
- static wchar_t obs_base_directory[MAX_PATH];
 
- struct LastError {
 
- 	DWORD code;
 
- 	inline LastError() { code = GetLastError(); }
 
- };
 
- void FreeWinHttpHandle(HINTERNET handle)
 
- {
 
- 	WinHttpCloseHandle(handle);
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- static bool IsVSRedistOutdated()
 
- {
 
- 	VS_FIXEDFILEINFO *info = nullptr;
 
- 	UINT len = 0;
 
- 	vector<std::byte> buf;
 
- 	const wchar_t vc_dll[] = L"msvcp140";
 
- 	auto size = GetFileVersionInfoSize(vc_dll, nullptr);
 
- 	if (!size)
 
- 		return true;
 
- 	buf.resize(size);
 
- 	if (!GetFileVersionInfo(vc_dll, 0, size, buf.data()))
 
- 		return true;
 
- 	bool success = VerQueryValue(buf.data(), L"\\", reinterpret_cast<LPVOID *>(&info), &len);
 
- 	if (!success || !info || !len)
 
- 		return true;
 
- 	return LOWORD(info->dwFileVersionMS) < 40;
 
- }
 
- 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 bool MyCopyFile(const wchar_t *src, const wchar_t *dest)
 
- try {
 
- 	WinHandle hSrc;
 
- 	WinHandle hDest;
 
- 	hSrc = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, 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(), nullptr, 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 {};
 
- 	}
 
- 	LARGE_INTEGER size;
 
- 	if (!GetFileSizeEx(handle, &size)) {
 
- 		return {};
 
- 	}
 
- 	data.resize((size_t)size.QuadPart);
 
- 	DWORD read;
 
- 	if (!ReadFile(handle, data.data(), (DWORD)data.size(), &read, nullptr)) {
 
- 		return {};
 
- 	}
 
- 	if (read != size.QuadPart) {
 
- 		return {};
 
- 	}
 
- 	return data;
 
- }
 
- static bool QuickWriteFile(const wchar_t *file, const void *data, size_t size)
 
- try {
 
- 	WinHandle handle = CreateFile(file, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, nullptr);
 
- 	if (handle == INVALID_HANDLE_VALUE)
 
- 		throw GetLastError();
 
- 	DWORD written;
 
- 	if (!WriteFile(handle, data, (DWORD)size, &written, nullptr))
 
- 		throw GetLastError();
 
- 	return true;
 
- } catch (LastError &error) {
 
- 	SetLastError(error.code);
 
- 	return false;
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- /* Extend std::hash for B2Hash */
 
- template<> struct std::hash<B2Hash> {
 
- 	size_t operator()(const B2Hash &value) const noexcept
 
- 	{
 
- 		return hash<string_view>{}(string_view(reinterpret_cast<const char *>(value.data()), value.size()));
 
- 	}
 
- };
 
- 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 previousFile;
 
- 	string packageName;
 
- 	B2Hash hash;
 
- 	B2Hash my_hash;
 
- 	B2Hash downloadHash;
 
- 	size_t fileSize = 0;
 
- 	state_t state = STATE_INVALID;
 
- 	bool has_hash = false;
 
- 	bool patchable = false;
 
- 	bool compressed = false;
 
- 	update_t() = default;
 
- 	update_t(const update_t &from) = default;
 
- 	update_t(update_t &&from) noexcept
 
- 		: sourceURL(std::move(from.sourceURL)),
 
- 		  outputPath(std::move(from.outputPath)),
 
- 		  previousFile(std::move(from.previousFile)),
 
- 		  packageName(std::move(from.packageName)),
 
- 		  hash(from.hash),
 
- 		  my_hash(from.my_hash),
 
- 		  downloadHash(from.downloadHash),
 
- 		  fileSize(from.fileSize),
 
- 		  state(from.state),
 
- 		  has_hash(from.has_hash),
 
- 		  patchable(from.patchable),
 
- 		  compressed(from.compressed)
 
- 	{
 
- 		from.state = STATE_INVALID;
 
- 	}
 
- 	void CleanPartialUpdate() const
 
- 	{
 
- 		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());
 
- 			}
 
- 		}
 
- 	}
 
- 	update_t &operator=(const update_t &from) = default;
 
- };
 
- struct deletion_t {
 
- 	wstring originalFilename;
 
- 	wstring deleteMeFilename;
 
- 	void UndoRename() const
 
- 	{
 
- 		if (!deleteMeFilename.empty())
 
- 			MoveFile(deleteMeFilename.c_str(), originalFilename.c_str());
 
- 	}
 
- };
 
- static unordered_map<B2Hash, vector<std::byte>> download_data;
 
- static unordered_map<string, B2Hash> 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();
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- static int Decompress(ZSTD_DCtx *ctx, std::vector<std::byte> &buf, size_t size)
 
- {
 
- 	// Copy compressed data
 
- 	vector<std::byte> comp(buf.begin(), buf.end());
 
- 	try {
 
- 		buf.resize(size);
 
- 	} catch (...) {
 
- 		return -1;
 
- 	}
 
- 	// Overwrite buffer with decompressed data
 
- 	size_t result = ZSTD_decompressDCtx(ctx, buf.data(), buf.size(), comp.data(), comp.size());
 
- 	if (result != size)
 
- 		return -9;
 
- 	if (ZSTD_isError(result))
 
- 		return -10;
 
- 	return 0;
 
- }
 
- bool DownloadWorkerThread()
 
- {
 
- 	const DWORD tlsProtocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3;
 
- 	const DWORD enableHTTP2Flag = WINHTTP_PROTOCOL_FLAG_HTTP2;
 
- 	const DWORD compressionFlags = WINHTTP_DECOMPRESSION_FLAG_ALL;
 
- 	HttpHandle hSession = WinHttpOpen(L"OBS Studio Updater/3.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_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));
 
- 	WinHttpSetOption(hSession, WINHTTP_OPTION_DECOMPRESSION, (LPVOID)&compressionFlags, sizeof(compressionFlags));
 
- 	HttpHandle hConnect = WinHttpConnect(hSession, kCDNHostname, INTERNET_DEFAULT_HTTPS_PORT, 0);
 
- 	if (!hConnect) {
 
- 		downloadThreadFailure = true;
 
- 		Status(L"Update failed: Couldn't connect to %S", kCDNHostname);
 
- 		return false;
 
- 	}
 
- 	ZSTDDCtx zCtx;
 
- 	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;
 
- 			}
 
- 			auto &buf = download_data[update.downloadHash];
 
- 			/* Reserve required memory */
 
- 			buf.reserve(update.fileSize);
 
- 			if (!HTTPGetBuffer(hConnect, update.sourceURL.c_str(), L"Accept-Encoding: gzip", buf,
 
- 					   &responseCode)) {
 
- 				downloadThreadFailure = true;
 
- 				Status(L"Update failed: Could not download "
 
- 				       L"%s (error code %d)",
 
- 				       update.outputPath.c_str(), responseCode);
 
- 				return true;
 
- 			}
 
- 			if (responseCode != 200) {
 
- 				downloadThreadFailure = true;
 
- 				Status(L"Update failed: Could not download "
 
- 				       L"%s (error code %d)",
 
- 				       update.outputPath.c_str(), responseCode);
 
- 				return true;
 
- 			}
 
- 			/* Validate hash of downloaded data. */
 
- 			B2Hash dataHash;
 
- 			blake2b(dataHash.data(), dataHash.size(), buf.data(), buf.size(), nullptr, 0);
 
- 			if (dataHash != update.downloadHash) {
 
- 				downloadThreadFailure = true;
 
- 				Status(L"Update failed: Integrity check "
 
- 				       L"failed on %s",
 
- 				       update.outputPath.c_str());
 
- 				return true;
 
- 			}
 
- 			/* Decompress data in compressed buffer. */
 
- 			if (update.compressed && !update.patchable) {
 
- 				int res = Decompress(zCtx, buf, update.fileSize);
 
- 				if (res) {
 
- 					downloadThreadFailure = true;
 
- 					Status(L"Update failed: Decompression "
 
- 					       L"failed on %s (error code %d)",
 
- 					       update.outputPath.c_str(), res);
 
- 					return true;
 
- 				}
 
- 			}
 
- 			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;
 
- }
 
- /* ----------------------------------------------------------------------- */
 
- enum { WAITIFOBS_SUCCESS, WAITIFOBS_WRONG_PROCESS, WAITIFOBS_CANCELLED };
 
- static inline DWORD WaitIfOBS(DWORD id, const wchar_t *expected)
 
- {
 
- 	wchar_t path[MAX_PATH];
 
- 	wchar_t *name;
 
- 	DWORD path_len = _countof(path);
 
- 	*path = 0;
 
- 	WinHandle proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE, false, id);
 
- 	if (!proc.Valid())
 
- 		return WAITIFOBS_WRONG_PROCESS;
 
- 	if (!QueryFullProcessImageNameW(proc, 0, path, &path_len))
 
- 		return WAITIFOBS_WRONG_PROCESS;
 
- 	// check it's actually our exe that's running
 
- 	size_t len = wcslen(obs_base_directory);
 
- 	if (wcsncmp(path, obs_base_directory, len) != 0)
 
- 		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;
 
- 	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, L"obs64")) {
 
- 			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()
 
- {
 
- 	unique_lock 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;
 
- 		B2Hash existingHash;
 
- 		if (CalculateFileHash(updateFileName, existingHash)) {
 
- 			ulock.lock();
 
- 			hashes.emplace(fileName, existingHash);
 
- 			ulock.unlock();
 
- 		}
 
- 	}
 
- }
 
- static void RunHasherWorkers(int num, const vector<Package> &packages)
 
- try {
 
- 	for (const Package &package : packages) {
 
- 		for (const File &file : package.files) {
 
- 			hashQueue.push(file.name);
 
- 		}
 
- 	}
 
- 	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 (strcmp(name, "obs-browser") == 0)
 
- 		return FileExists(L"obs-plugins\\64bit\\obs-browser.dll");
 
- 	return false;
 
- }
 
- static bool AddPackageUpdateFiles(const Package &package, const wchar_t *branch)
 
- {
 
- 	wchar_t wPackageName[512];
 
- 	if (!UTF8ToWideBuf(wPackageName, package.name.c_str()))
 
- 		return false;
 
- 	if (package.name != "core" && !NonCorePackageInstalled(package.name.c_str()))
 
- 		return true;
 
- 	for (const File &file : package.files) {
 
- 		if (file.hash.size() != kBlake2StrLength)
 
- 			continue;
 
- 		/* The download hash may not exist if a file is uncompressed */
 
- 		bool compressed = false;
 
- 		if (file.compressed_hash.size() == kBlake2StrLength)
 
- 			compressed = true;
 
- 		/* convert strings to wide */
 
- 		wchar_t sourceURL[1024];
 
- 		wchar_t updateFileName[MAX_PATH];
 
- 		if (!UTF8ToWideBuf(updateFileName, file.name.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", kCDNUpdateBaseUrl, branch, wPackageName,
 
- 			       updateFileName);
 
- 		/* Convert hashes */
 
- 		B2Hash updateHash;
 
- 		StringToHash(file.hash, updateHash);
 
- 		/* We don't really care if this fails, it's just to avoid
 
- 		 * wasting bandwidth by downloading unmodified files */
 
- 		B2Hash localFileHash;
 
- 		bool has_hash = false;
 
- 		if (hashes.count(file.name)) {
 
- 			localFileHash = hashes[file.name];
 
- 			if (localFileHash == updateHash)
 
- 				continue;
 
- 			has_hash = true;
 
- 		}
 
- 		/* Add update file */
 
- 		update_t update;
 
- 		update.fileSize = file.size;
 
- 		update.outputPath = updateFileName;
 
- 		update.sourceURL = sourceURL;
 
- 		update.packageName = package.name;
 
- 		update.state = STATE_PENDING_DOWNLOAD;
 
- 		update.patchable = false;
 
- 		update.compressed = compressed;
 
- 		update.hash = updateHash;
 
- 		if (compressed) {
 
- 			update.sourceURL += L".zst";
 
- 			StringToHash(file.compressed_hash, update.downloadHash);
 
- 		} else {
 
- 			update.downloadHash = updateHash;
 
- 		}
 
- 		update.has_hash = has_hash;
 
- 		if (has_hash)
 
- 			update.my_hash = localFileHash;
 
- 		updates.push_back(std::move(update));
 
- 		totalFileSize += file.size;
 
- 	}
 
- 	return true;
 
- }
 
- static void AddPackageRemovedFiles(const Package &package)
 
- {
 
- 	for (const string &filename : package.removed_files) {
 
- 		wchar_t removedFileName[MAX_PATH];
 
- 		if (!UTF8ToWideBuf(removedFileName, filename.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];
 
- 	B2Hash hash;
 
- 	string temp;
 
- 	CryptGenRandom(hProvider, sizeof(junk), junk);
 
- 	blake2b(hash.data(), hash.size(), junk, sizeof(junk), nullptr, 0);
 
- 	HashToString(hash, temp);
 
- 	if (!UTF8ToWideBuf(randomStr, temp.c_str()))
 
- 		return false;
 
- 	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 PatchResponse &patch)
 
- {
 
- 	wchar_t widePatchableFilename[MAX_PATH];
 
- 	wchar_t sourceURL[1024];
 
- 	if (patch.source.compare(0, kCDNUrl.size(), kCDNUrl) != 0)
 
- 		return;
 
- 	if (patch.name.find('/') == string::npos)
 
- 		return;
 
- 	string patchPackageName(patch.name, 0, patch.name.find('/'));
 
- 	string fileName(patch.name, patch.name.find('/') + 1);
 
- 	if (!UTF8ToWideBuf(widePatchableFilename, fileName.c_str()))
 
- 		return;
 
- 	if (!UTF8ToWideBuf(sourceURL, patch.source.c_str()))
 
- 		return;
 
- 	for (update_t &update : updates) {
 
- 		if (update.packageName != patchPackageName)
 
- 			continue;
 
- 		if (update.outputPath != widePatchableFilename)
 
- 			continue;
 
- 		update.patchable = true;
 
- 		/* Replace the source URL with the patch file, update
 
- 	         * the download hash, and re-calculate download size */
 
- 		StringToHash(patch.hash, update.downloadHash);
 
- 		update.sourceURL = sourceURL;
 
- 		totalFileSize -= (update.fileSize - patch.size);
 
- 		update.fileSize = patch.size;
 
- 		break;
 
- 	}
 
- }
 
- static bool MoveInUseFileAway(const update_t &file)
 
- {
 
- 	_TCHAR deleteMeName[MAX_PATH];
 
- 	_TCHAR randomStr[MAX_PATH];
 
- 	BYTE junk[40];
 
- 	B2Hash hash;
 
- 	string temp;
 
- 	CryptGenRandom(hProvider, sizeof(junk), junk);
 
- 	blake2b(hash.data(), hash.size(), junk, sizeof(junk), nullptr, 0);
 
- 	HashToString(hash, temp);
 
- 	if (!UTF8ToWideBuf(randomStr, temp.c_str()))
 
- 		return false;
 
- 	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(ZSTD_DCtx *ctx, update_t &file)
 
- {
 
- 	wchar_t oldFileRenamedPath[MAX_PATH];
 
- 	/* Grab the patch/file data from the global cache. */
 
- 	vector<std::byte> &patch_data = download_data[file.downloadHash];
 
- 	/* 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 baseName[MAX_PATH];
 
- 		StringCbCopy(baseName, sizeof(baseName), file.outputPath.c_str());
 
- 		wchar_t *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)) {
 
- 			DWORD err = GetLastError();
 
- 			int is_sharing_violation = (err == ERROR_SHARING_VIOLATION || err == ERROR_USER_MAPPED_FILE);
 
- 			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(ctx, patch_data.data(), file.fileSize, file.outputPath.c_str());
 
- 			installed_ok = (error_code == 0);
 
- 			if (installed_ok) {
 
- 				B2Hash patchedFileHash;
 
- 				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 (file.hash != patchedFileHash) {
 
- 					Status(L"Update failed: Integrity "
 
- 					       L"check of patched "
 
- 					       L"%s failed",
 
- 					       curFileName);
 
- 					file.state = STATE_INSTALL_FAILED;
 
- 					return false;
 
- 				}
 
- 			}
 
- 		} else {
 
- 			installed_ok = QuickWriteFile(file.outputPath.c_str(), patch_data.data(), patch_data.size());
 
- 			error_code = GetLastError();
 
- 		}
 
- 		if (!installed_ok) {
 
- 			int is_sharing_violation =
 
- 				(error_code == ERROR_SHARING_VIOLATION || error_code == ERROR_USER_MAPPED_FILE);
 
- 			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 {
 
- 				DWORD err = GetLastError();
 
- 				Status(L"Update failed: Couldn't update %s "
 
- 				       L"(error %d)",
 
- 				       curFileName, err ? err : error_code);
 
- 			}
 
- 			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 */
 
- 		filesystem::path filePath(file.outputPath.c_str());
 
- 		create_directories(filePath.parent_path());
 
- 		file.previousFile = L"";
 
- 		bool success = !!QuickWriteFile(file.outputPath.c_str(), patch_data.data(), patch_data.size());
 
- 		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;
 
- }
 
- queue<reference_wrapper<update_t>> updateQueue;
 
- static int lastPosition = 0;
 
- static int installed = 0;
 
- static bool updateThreadFailed = false;
 
- static bool UpdateWorker()
 
- {
 
- 	unique_lock<mutex> ulock(updateMutex, defer_lock);
 
- 	ZSTDDCtx zCtx;
 
- 	while (true) {
 
- 		ulock.lock();
 
- 		if (updateThreadFailed)
 
- 			return false;
 
- 		if (updateQueue.empty())
 
- 			break;
 
- 		auto update = updateQueue.front();
 
- 		updateQueue.pop();
 
- 		ulock.unlock();
 
- 		if (!UpdateFile(zCtx, update)) {
 
- 			updateThreadFailed = true;
 
- 			return false;
 
- 		} else {
 
- 			int position = (int)(((float)++installed / (float)completedUpdates) * 100.0f);
 
- 			if (position > lastPosition) {
 
- 				lastPosition = position;
 
- 				SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETPOS, position, 0);
 
- 			}
 
- 		}
 
- 	}
 
- 	return true;
 
- }
 
- static bool RunUpdateWorkers(int num)
 
- try {
 
- 	for (update_t &update : updates)
 
- 		updateQueue.emplace(update);
 
- 	vector<future<bool>> thread_success_results;
 
- 	thread_success_results.resize(num);
 
- 	for (future<bool> &result : thread_success_results) {
 
- 		result = async(launch::async, UpdateWorker);
 
- 	}
 
- 	for (future<bool> &result : thread_success_results) {
 
- 		if (!result.get()) {
 
- 			return false;
 
- 		}
 
- 	}
 
- 	return true;
 
- } catch (...) {
 
- 	return false;
 
- }
 
- static bool UpdateVSRedists()
 
- {
 
- 	/* ------------------------------------------ *
 
- 	 * Initialize session                         */
 
- 	const DWORD tlsProtocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
 
- 	const DWORD compressionFlags = WINHTTP_DECOMPRESSION_FLAG_ALL;
 
- 	HttpHandle hSession = WinHttpOpen(L"OBS Studio Updater/3.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
 
- 					  WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
 
- 	if (!hSession) {
 
- 		Status(L"VC Redist Update failed: Couldn't create session");
 
- 		return false;
 
- 	}
 
- 	WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, (LPVOID)&tlsProtocols, sizeof(tlsProtocols));
 
- 	WinHttpSetOption(hSession, WINHTTP_OPTION_DECOMPRESSION, (LPVOID)&compressionFlags, sizeof(compressionFlags));
 
- 	HttpHandle hConnect = WinHttpConnect(hSession, kMSHostname, INTERNET_DEFAULT_HTTPS_PORT, 0);
 
- 	if (!hConnect) {
 
- 		Status(L"Update failed: Couldn't connect to %S", kMSHostname);
 
- 		return false;
 
- 	}
 
- 	int responseCode;
 
- 	DWORD waitResult = WaitForSingleObject(cancelRequested, 0);
 
- 	if (waitResult == WAIT_OBJECT_0) {
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------------ *
 
- 	 * Download redist                            */
 
- 	Status(L"Downloading Visual C++ Redistributable");
 
- 	wstring destPath;
 
- 	destPath += tempPath;
 
- 	destPath += L"\\VC_redist.x64.exe";
 
- 	if (!HTTPGetFile(hConnect, kVSRedistURL, 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++ Redistributable", responseCode);
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------------ *
 
- 	 * Verify file signature                      */
 
- 	GUID action = WINTRUST_ACTION_GENERIC_VERIFY_V2;
 
- 	WINTRUST_FILE_INFO fileInfo = {};
 
- 	fileInfo.cbStruct = sizeof(fileInfo);
 
- 	fileInfo.pcwszFilePath = destPath.c_str();
 
- 	WINTRUST_DATA data = {};
 
- 	data.cbStruct = sizeof(data);
 
- 	data.dwUIChoice = WTD_UI_NONE;
 
- 	data.dwUnionChoice = WTD_CHOICE_FILE;
 
- 	data.dwStateAction = WTD_STATEACTION_VERIFY;
 
- 	data.pFile = &fileInfo;
 
- 	LONG result = WinVerifyTrust(nullptr, &action, &data);
 
- 	if (result != ERROR_SUCCESS) {
 
- 		Status(L"Update failed: Signature verification failed for "
 
- 		       L"%s (error code %d / %d)",
 
- 		       L"Visual C++ Redistributable", result, GetLastError());
 
- 		DeleteFile(destPath.c_str());
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------------ *
 
- 	 * If verification succeeded, 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++ 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++ Redistributable", (int)GetLastError());
 
- 	}
 
- 	DeleteFile(destPath.c_str());
 
- 	waitResult = WaitForSingleObject(cancelRequested, 0);
 
- 	if (waitResult == WAIT_OBJECT_0) {
 
- 		return false;
 
- 	}
 
- 	return success;
 
- }
 
- static void UpdateRegistryVersion(const Manifest &manifest)
 
- {
 
- 	const char *regKey = R"(Software\Microsoft\Windows\CurrentVersion\Uninstall\OBS Studio)";
 
- 	LSTATUS res;
 
- 	HKEY key;
 
- 	char version[32];
 
- 	int formattedLen;
 
- 	/* The manifest does not store a version string, so we gotta make one ourselves. */
 
- 	if (manifest.beta || manifest.rc) {
 
- 		formattedLen = sprintf_s(version, sizeof(version), "%d.%d.%d-%s%d", manifest.version_major,
 
- 					 manifest.version_minor, manifest.version_patch, manifest.beta ? "beta" : "rc",
 
- 					 manifest.beta ? manifest.beta : manifest.rc);
 
- 	} else {
 
- 		formattedLen = sprintf_s(version, sizeof(version), "%d.%d.%d", manifest.version_major,
 
- 					 manifest.version_minor, manifest.version_patch);
 
- 	}
 
- 	if (formattedLen <= 0)
 
- 		return;
 
- 	res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, regKey, 0, KEY_WRITE | KEY_WOW64_32KEY, &key);
 
- 	if (res != ERROR_SUCCESS)
 
- 		return;
 
- 	RegSetValueExA(key, "DisplayVersion", 0, REG_SZ, (const BYTE *)version, formattedLen + 1);
 
- 	RegCloseKey(key);
 
- }
 
- static void ClearShaderCache()
 
- {
 
- 	wchar_t shader_path[MAX_PATH];
 
- 	SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, shader_path);
 
- 	StringCbCatW(shader_path, sizeof(shader_path), L"\\obs-studio\\shader-cache");
 
- 	filesystem::remove_all(shader_path);
 
- }
 
- 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;
 
- 				} else if (wcsncmp(argv[i], L"--portable--branch=", 19) == 0) {
 
- 					/* Versions pre-29.1 beta 2 produce broken parameters :( */
 
- 					bIsPortable = true;
 
- 					branch = argv[i] + 19;
 
- 				}
 
- 			}
 
- 			LocalFree((HLOCAL)argv);
 
- 		}
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Get config path                       */
 
- 	wchar_t lpAppDataPath[MAX_PATH];
 
- 	lpAppDataPath[0] = 0;
 
- 	if (bIsPortable) {
 
- 		StringCbCopy(lpAppDataPath, sizeof(lpAppDataPath), obs_base_directory);
 
- 		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                    */
 
- 	Manifest manifest;
 
- 	{
 
- 		string manifestFile = QuickReadFile(manifestPath);
 
- 		if (manifestFile.empty()) {
 
- 			Status(L"Update failed: Couldn't load manifest file");
 
- 			return false;
 
- 		}
 
- 		try {
 
- 			json manifestContents = json::parse(manifestFile);
 
- 			manifest = manifestContents.get<Manifest>();
 
- 		} catch (json::exception &e) {
 
- 			Status(L"Update failed: Couldn't parse update "
 
- 			       L"manifest: %S",
 
- 			       e.what());
 
- 			return false;
 
- 		}
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Hash local files listed in manifest   */
 
- 	RunHasherWorkers(4, manifest.packages);
 
- 	/* ------------------------------------- *
 
- 	 * Parse current manifest update files   */
 
- 	for (const Package &package : manifest.packages) {
 
- 		if (!AddPackageUpdateFiles(package, branch.c_str())) {
 
- 			Status(L"Update failed: Failed to process update packages");
 
- 			return false;
 
- 		}
 
- 		/* Add removed files to deletion queue (if any) */
 
- 		AddPackageRemovedFiles(package);
 
- 	}
 
- 	SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETMARQUEE, 0, 0);
 
- 	SetWindowLongPtr(hProgress, GWL_STYLE, style);
 
- 	/* ------------------------------------- *
 
- 	 * Exit if updates already installed     */
 
- 	if (updates.empty()) {
 
- 		Status(L"All available updates are already installed.");
 
- 		SetDlgItemText(hwndMain, IDC_BUTTON, L"Launch OBS");
 
- 		return true;
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Check VS redistributables version     */
 
- 	if (IsVSRedistOutdated()) {
 
- 		if (!UpdateVSRedists()) {
 
- 			return false;
 
- 		}
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Generate file hash json               */
 
- 	PatchesRequest files;
 
- 	for (update_t &update : updates) {
 
- 		if (!update.has_hash)
 
- 			continue;
 
- 		char outputPath[MAX_PATH];
 
- 		if (!WideToUTF8Buf(outputPath, update.outputPath.c_str()))
 
- 			continue;
 
- 		string hash_string;
 
- 		HashToString(update.my_hash, hash_string);
 
- 		string package_path;
 
- 		package_path = update.packageName;
 
- 		package_path += "/";
 
- 		package_path += outputPath;
 
- 		files.push_back({package_path, hash_string});
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Send file hashes                      */
 
- 	string newManifest;
 
- 	if (!files.empty()) {
 
- 		json request = files;
 
- 		string post_body = request.dump();
 
- 		int len = (int)post_body.size();
 
- 		size_t compressSize = ZSTD_compressBound(len);
 
- 		string compressedJson;
 
- 		compressedJson.resize(compressSize);
 
- 		size_t result = ZSTD_compress(compressedJson.data(), compressedJson.size(), post_body.data(),
 
- 					      post_body.size(), ZSTD_CLEVEL_DEFAULT);
 
- 		if (ZSTD_isError(result))
 
- 			return false;
 
- 		compressedJson.resize(result);
 
- 		wstring manifestUrl(kPatchManifestURL);
 
- 		if (branch != L"stable")
 
- 			manifestUrl += L"?branch=" + branch;
 
- 		int responseCode;
 
- 		bool success = !!HTTPPostData(manifestUrl.c_str(), (BYTE *)compressedJson.data(),
 
- 					      (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                    */
 
- 	PatchesResponse patches;
 
- 	try {
 
- 		json patchManifest = json::parse(newManifest);
 
- 		patches = patchManifest.get<PatchesResponse>();
 
- 	} catch (json::exception &e) {
 
- 		Status(L"Update failed: Couldn't parse patch manifest: %S", e.what());
 
- 		return false;
 
- 	}
 
- 	/* Update updates with patch information. */
 
- 	for (const PatchResponse &patch : patches) {
 
- 		UpdateWithPatchIfAvailable(patch);
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Deduplicate Downloads                 */
 
- 	unordered_set<B2Hash> downloadHashes;
 
- 	for (update_t &update : updates) {
 
- 		if (downloadHashes.count(update.downloadHash)) {
 
- 			update.state = STATE_ALREADY_DOWNLOADED;
 
- 			totalFileSize -= update.fileSize;
 
- 			completedUpdates++;
 
- 		} else {
 
- 			downloadHashes.insert(update.downloadHash);
 
- 		}
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Download Updates                      */
 
- 	Status(L"Downloading updates...");
 
- 	if (!RunDownloadWorkers(4))
 
- 		return false;
 
- 	if ((size_t)completedUpdates != updates.size()) {
 
- 		Status(L"Update failed to download all files.");
 
- 		return false;
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * Install updates                       */
 
- 	SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETPOS, 0, 0);
 
- 	Status(L"Installing updates...");
 
- 	if (!RunUpdateWorkers(4))
 
- 		return false;
 
- 	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");
 
- 		StringCbCopy(src, sizeof(src), obs_base_directory);
 
- 		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);
 
- 		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();
 
- 	/* ------------------------------------- *
 
- 	 * Clear shader cache                    */
 
- 	Status(L"Clearing shader cache...");
 
- 	ClearShaderCache();
 
- 	/* ------------------------------------- *
 
- 	 * Update installed version in registry  */
 
- 	if (!bIsPortable) {
 
- 		Status(L"Updating version information...");
 
- 		UpdateRegistryVersion(manifest);
 
- 	}
 
- 	/* ------------------------------------- *
 
- 	 * 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());
 
- 	}
 
- 	/* 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(LPWSTR lpCmdLine)
 
- {
 
- 	wchar_t newCwd[MAX_PATH];
 
- 	wchar_t obsPath[MAX_PATH];
 
- 	StringCbCopy(obsPath, sizeof(obsPath), obs_base_directory);
 
- 	StringCbCat(obsPath, sizeof(obsPath), L"\\bin\\64bit");
 
- 	SetCurrentDirectory(obsPath);
 
- 	StringCbCopy(newCwd, sizeof(newCwd), obsPath);
 
- 	StringCbCat(obsPath, sizeof(obsPath), L"\\obs64.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 (lpCmdLine[0])
 
- 		execInfo.lpParameters = lpCmdLine;
 
- 	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 = nullptr;
 
- 	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 = nullptr;
 
- 	/* 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];
 
- 	GetCurrentDirectoryW(_countof(cwd) - 1, cwd);
 
- 	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(obs_base_directory), obs_base_directory, nullptr);
 
- 			SetCurrentDirectory(obs_base_directory);
 
- 			LaunchOBS(lpCmdLine);
 
- 		}
 
- 		if (hLowMutex) {
 
- 			ReleaseMutex(hLowMutex);
 
- 			CloseHandle(hLowMutex);
 
- 		}
 
- 		return 0;
 
- 	} else {
 
- 		StringCbCat(cwd, sizeof(cwd), L"\\..\\..");
 
- 		GetFullPathName(cwd, _countof(obs_base_directory), obs_base_directory, nullptr);
 
- 		SetCurrentDirectory(obs_base_directory);
 
- 		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(lpCmdLine);
 
- 		}
 
- 		return (int)msg.wParam;
 
- 	}
 
- }
 
 
  |