updater.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437
  1. /******************************************************************************
  2. Copyright (C) 2017 Hugh Bailey <[email protected]>
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  14. ******************************************************************************/
  15. #include "updater.hpp"
  16. #include <psapi.h>
  17. #include <util/windows/CoTaskMemPtr.hpp>
  18. #include <future>
  19. #include <vector>
  20. #include <string>
  21. #include <mutex>
  22. using namespace std;
  23. /* ----------------------------------------------------------------------- */
  24. HANDLE cancelRequested = nullptr;
  25. HANDLE updateThread = nullptr;
  26. HINSTANCE hinstMain = nullptr;
  27. HWND hwndMain = nullptr;
  28. HCRYPTPROV hProvider = 0;
  29. static bool bExiting = false;
  30. static bool updateFailed = false;
  31. static bool is32bit = false;
  32. static bool downloadThreadFailure = false;
  33. int totalFileSize = 0;
  34. int completedFileSize = 0;
  35. static int completedUpdates = 0;
  36. struct LastError {
  37. DWORD code;
  38. inline LastError() { code = GetLastError(); }
  39. };
  40. void FreeWinHttpHandle(HINTERNET handle)
  41. {
  42. WinHttpCloseHandle(handle);
  43. }
  44. /* ----------------------------------------------------------------------- */
  45. // http://www.codeproject.com/Articles/320748/Haephrati-Elevating-during-runtime
  46. static bool IsAppRunningAsAdminMode()
  47. {
  48. BOOL fIsRunAsAdmin = FALSE;
  49. DWORD dwError = ERROR_SUCCESS;
  50. PSID pAdministratorsGroup = nullptr;
  51. /* Allocate and initialize a SID of the administrators group. */
  52. SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
  53. if (!AllocateAndInitializeSid(&NtAuthority,
  54. 2,
  55. SECURITY_BUILTIN_DOMAIN_RID,
  56. DOMAIN_ALIAS_RID_ADMINS,
  57. 0,
  58. 0,
  59. 0,
  60. 0,
  61. 0,
  62. 0,
  63. &pAdministratorsGroup)) {
  64. dwError = GetLastError();
  65. goto Cleanup;
  66. }
  67. /* Determine whether the SID of administrators group is enabled in the
  68. * primary access token of the process. */
  69. if (!CheckTokenMembership(nullptr, pAdministratorsGroup,
  70. &fIsRunAsAdmin)) {
  71. dwError = GetLastError();
  72. goto Cleanup;
  73. }
  74. Cleanup:
  75. /* Centralized cleanup for all allocated resources. */
  76. if (pAdministratorsGroup) {
  77. FreeSid(pAdministratorsGroup);
  78. pAdministratorsGroup = nullptr;
  79. }
  80. /* Throw the error if something failed in the function. */
  81. if (ERROR_SUCCESS != dwError)
  82. return false;
  83. return !!fIsRunAsAdmin;
  84. }
  85. static void Status(const wchar_t *fmt, ...)
  86. {
  87. wchar_t str[512];
  88. va_list argptr;
  89. va_start(argptr, fmt);
  90. StringCbVPrintf(str, sizeof(str), fmt, argptr);
  91. SetDlgItemText(hwndMain, IDC_STATUS, str);
  92. va_end(argptr);
  93. }
  94. static void CreateFoldersForPath(const wchar_t *path)
  95. {
  96. wchar_t *p = (wchar_t *)path;
  97. while (*p) {
  98. if (*p == '\\' || *p == '/') {
  99. *p = 0;
  100. CreateDirectory(path, nullptr);
  101. *p = '\\';
  102. }
  103. p++;
  104. }
  105. }
  106. static bool MyCopyFile(const wchar_t *src, const wchar_t *dest)
  107. try {
  108. WinHandle hSrc;
  109. WinHandle hDest;
  110. hSrc = CreateFile(src, GENERIC_READ, 0, nullptr, OPEN_EXISTING,
  111. FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
  112. if (!hSrc.Valid())
  113. throw LastError();
  114. hDest = CreateFile(dest, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
  115. 0, nullptr);
  116. if (!hDest.Valid())
  117. throw LastError();
  118. BYTE buf[65536];
  119. DWORD read, wrote;
  120. for (;;) {
  121. if (!ReadFile(hSrc, buf, sizeof(buf), &read, nullptr))
  122. throw LastError();
  123. if (read == 0)
  124. break;
  125. if (!WriteFile(hDest, buf, read, &wrote, nullptr))
  126. throw LastError();
  127. if (wrote != read)
  128. return false;
  129. }
  130. return true;
  131. } catch (LastError error) {
  132. SetLastError(error.code);
  133. return false;
  134. }
  135. static bool IsSafeFilename(const wchar_t *path)
  136. {
  137. const wchar_t *p = path;
  138. if (!*p)
  139. return false;
  140. if (wcsstr(path, L".."))
  141. return false;
  142. if (*p == '/')
  143. return false;
  144. while (*p) {
  145. if (!isalnum(*p) &&
  146. *p != '.' &&
  147. *p != '/' &&
  148. *p != '_' &&
  149. *p != '-')
  150. return false;
  151. p++;
  152. }
  153. return true;
  154. }
  155. static string QuickReadFile(const wchar_t *path)
  156. {
  157. string data;
  158. WinHandle handle = CreateFileW(path, GENERIC_READ, 0, nullptr,
  159. OPEN_EXISTING, 0, nullptr);
  160. if (!handle.Valid()) {
  161. return string();
  162. }
  163. LARGE_INTEGER size;
  164. if (!GetFileSizeEx(handle, &size)) {
  165. return string();
  166. }
  167. data.resize((size_t)size.QuadPart);
  168. DWORD read;
  169. if (!ReadFile(handle,
  170. &data[0],
  171. (DWORD)data.size(),
  172. &read,
  173. nullptr)) {
  174. return string();
  175. }
  176. if (read != size.QuadPart) {
  177. return string();
  178. }
  179. return data;
  180. }
  181. /* ----------------------------------------------------------------------- */
  182. enum state_t {
  183. STATE_INVALID,
  184. STATE_PENDING_DOWNLOAD,
  185. STATE_DOWNLOADING,
  186. STATE_DOWNLOADED,
  187. STATE_INSTALLED,
  188. };
  189. struct update_t {
  190. wstring sourceURL;
  191. wstring outputPath;
  192. wstring tempPath;
  193. wstring previousFile;
  194. wstring basename;
  195. string packageName;
  196. DWORD fileSize = 0;
  197. BYTE hash[BLAKE2_HASH_LENGTH];
  198. BYTE downloadhash[BLAKE2_HASH_LENGTH];
  199. BYTE my_hash[BLAKE2_HASH_LENGTH];
  200. state_t state = STATE_INVALID;
  201. bool has_hash = false;
  202. bool patchable = false;
  203. inline update_t() {}
  204. inline update_t(const update_t &from)
  205. : sourceURL(from.sourceURL),
  206. outputPath(from.outputPath),
  207. tempPath(from.tempPath),
  208. previousFile(from.previousFile),
  209. basename(from.basename),
  210. packageName(from.packageName),
  211. fileSize(from.fileSize),
  212. state(from.state),
  213. has_hash(from.has_hash),
  214. patchable(from.patchable)
  215. {
  216. memcpy(hash, from.hash, sizeof(hash));
  217. memcpy(downloadhash, from.downloadhash, sizeof(downloadhash));
  218. memcpy(my_hash, from.my_hash, sizeof(my_hash));
  219. }
  220. inline update_t(update_t &&from)
  221. : sourceURL(std::move(from.sourceURL)),
  222. outputPath(std::move(from.outputPath)),
  223. tempPath(std::move(from.tempPath)),
  224. previousFile(std::move(from.previousFile)),
  225. basename(std::move(from.basename)),
  226. packageName(std::move(from.packageName)),
  227. fileSize(from.fileSize),
  228. state(from.state),
  229. has_hash(from.has_hash),
  230. patchable(from.patchable)
  231. {
  232. from.state = STATE_INVALID;
  233. memcpy(hash, from.hash, sizeof(hash));
  234. memcpy(downloadhash, from.downloadhash, sizeof(downloadhash));
  235. memcpy(my_hash, from.my_hash, sizeof(my_hash));
  236. }
  237. void CleanPartialUpdate()
  238. {
  239. if (state == STATE_INSTALLED) {
  240. if (!previousFile.empty()) {
  241. DeleteFile(outputPath.c_str());
  242. MyCopyFile(previousFile.c_str(),
  243. outputPath.c_str());
  244. DeleteFile(previousFile.c_str());
  245. } else {
  246. DeleteFile(outputPath.c_str());
  247. }
  248. } else if (state == STATE_DOWNLOADED) {
  249. DeleteFile(tempPath.c_str());
  250. }
  251. }
  252. inline update_t &operator=(const update_t &from)
  253. {
  254. sourceURL = from.sourceURL;
  255. outputPath = from.outputPath;
  256. tempPath = from.tempPath;
  257. previousFile = from.previousFile;
  258. basename = from.basename;
  259. packageName = from.packageName;
  260. fileSize = from.fileSize;
  261. state = from.state;
  262. has_hash = from.has_hash;
  263. patchable = from.patchable;
  264. memcpy(hash, from.hash, sizeof(hash));
  265. memcpy(downloadhash, from.downloadhash, sizeof(downloadhash));
  266. memcpy(my_hash, from.my_hash, sizeof(my_hash));
  267. }
  268. };
  269. static vector<update_t> updates;
  270. static mutex updateMutex;
  271. static inline void CleanupPartialUpdates()
  272. {
  273. for (update_t &update : updates)
  274. update.CleanPartialUpdate();
  275. }
  276. /* ----------------------------------------------------------------------- */
  277. bool DownloadWorkerThread()
  278. {
  279. HttpHandle hSession = WinHttpOpen(L"OBS Studio Updater/2.1",
  280. WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
  281. WINHTTP_NO_PROXY_NAME,
  282. WINHTTP_NO_PROXY_BYPASS,
  283. 0);
  284. if (!hSession) {
  285. downloadThreadFailure = true;
  286. Status(L"Update failed: Couldn't open obsproject.com");
  287. return false;
  288. }
  289. HttpHandle hConnect = WinHttpConnect(hSession, L"obsproject.com",
  290. INTERNET_DEFAULT_HTTPS_PORT, 0);
  291. if (!hConnect) {
  292. downloadThreadFailure = true;
  293. Status(L"Update failed: Couldn't connect to obsproject.com");
  294. return false;
  295. }
  296. for (;;) {
  297. bool foundWork = false;
  298. unique_lock<mutex> ulock(updateMutex);
  299. for (update_t &update : updates) {
  300. int responseCode;
  301. DWORD waitResult =
  302. WaitForSingleObject(cancelRequested, 0);
  303. if (waitResult == WAIT_OBJECT_0) {
  304. return false;
  305. }
  306. if (update.state != STATE_PENDING_DOWNLOAD)
  307. continue;
  308. update.state = STATE_DOWNLOADING;
  309. ulock.unlock();
  310. foundWork = true;
  311. if (downloadThreadFailure) {
  312. return false;
  313. }
  314. Status(L"Downloading %s", update.outputPath.c_str());
  315. if (!HTTPGetFile(hConnect,
  316. update.sourceURL.c_str(),
  317. update.tempPath.c_str(),
  318. L"Accept-Encoding: gzip",
  319. &responseCode)) {
  320. downloadThreadFailure = true;
  321. DeleteFile(update.tempPath.c_str());
  322. Status(L"Update failed: Could not download "
  323. L"%s (error code %d)",
  324. update.outputPath.c_str(),
  325. responseCode);
  326. return 1;
  327. }
  328. if (responseCode != 200) {
  329. downloadThreadFailure = true;
  330. DeleteFile(update.tempPath.c_str());
  331. Status(L"Update failed: Could not download "
  332. L"%s (error code %d)",
  333. update.outputPath.c_str(),
  334. responseCode);
  335. return 1;
  336. }
  337. BYTE downloadHash[BLAKE2_HASH_LENGTH];
  338. if (!CalculateFileHash(update.tempPath.c_str(),
  339. downloadHash)) {
  340. downloadThreadFailure = true;
  341. DeleteFile(update.tempPath.c_str());
  342. Status(L"Update failed: Couldn't verify "
  343. L"integrity of %s",
  344. update.outputPath.c_str());
  345. return 1;
  346. }
  347. if (memcmp(update.downloadhash, downloadHash, 20)) {
  348. downloadThreadFailure = true;
  349. DeleteFile(update.tempPath.c_str());
  350. Status(L"Update failed: Integrity check "
  351. L"failed on %s",
  352. update.outputPath.c_str());
  353. return 1;
  354. }
  355. ulock.lock();
  356. update.state = STATE_DOWNLOADED;
  357. completedUpdates++;
  358. }
  359. if (!foundWork) {
  360. break;
  361. }
  362. if (downloadThreadFailure) {
  363. return false;
  364. }
  365. }
  366. return true;
  367. }
  368. static bool RunDownloadWorkers(int num)
  369. try {
  370. vector<future<bool>> thread_success_results;
  371. thread_success_results.resize(num);
  372. for (future<bool> &result : thread_success_results) {
  373. result = async(DownloadWorkerThread);
  374. }
  375. for (future<bool> &result : thread_success_results) {
  376. if (!result.get()) {
  377. return false;
  378. }
  379. }
  380. return true;
  381. } catch (...) {
  382. return false;
  383. }
  384. /* ----------------------------------------------------------------------- */
  385. #define WAITIFOBS_SUCCESS 0
  386. #define WAITIFOBS_WRONG_PROCESS 1
  387. #define WAITIFOBS_CANCELLED 2
  388. static inline DWORD WaitIfOBS(DWORD id, const wchar_t *expected)
  389. {
  390. wchar_t name[MAX_PATH];
  391. *name = 0;
  392. WinHandle proc = OpenProcess(
  393. PROCESS_QUERY_INFORMATION |
  394. PROCESS_VM_READ |
  395. SYNCHRONIZE,
  396. false, id);
  397. if (!proc.Valid())
  398. return WAITIFOBS_WRONG_PROCESS;
  399. HMODULE mod;
  400. DWORD temp;
  401. if (!EnumProcessModules(proc, &mod, sizeof(mod), &temp))
  402. return WAITIFOBS_WRONG_PROCESS;
  403. GetModuleBaseName(proc, mod, name, _countof(name));
  404. if (_wcsnicmp(name, expected, 5) == 0) {
  405. HANDLE hWait[2];
  406. hWait[0] = proc;
  407. hWait[1] = cancelRequested;
  408. int i = WaitForMultipleObjects(2, hWait, false, INFINITE);
  409. DWORD err = GetLastError();
  410. if (i == WAIT_OBJECT_0 + 1)
  411. return WAITIFOBS_CANCELLED;
  412. return WAITIFOBS_SUCCESS;
  413. }
  414. return WAITIFOBS_WRONG_PROCESS;
  415. }
  416. static bool WaitForOBS()
  417. {
  418. DWORD proc_ids[1024], needed, count;
  419. const wchar_t *name = is32bit ? L"obs32" : L"obs64";
  420. if (!EnumProcesses(proc_ids, sizeof(proc_ids), &needed)) {
  421. return true;
  422. }
  423. count = needed / sizeof(DWORD);
  424. for (DWORD i = 0; i < count; i++) {
  425. DWORD id = proc_ids[i];
  426. if (id != 0) {
  427. switch (WaitIfOBS(id, name)) {
  428. case WAITIFOBS_SUCCESS:
  429. return true;
  430. case WAITIFOBS_WRONG_PROCESS:
  431. break;
  432. case WAITIFOBS_CANCELLED:
  433. return false;
  434. }
  435. }
  436. }
  437. return true;
  438. }
  439. /* ----------------------------------------------------------------------- */
  440. static inline bool UTF8ToWide(wchar_t *wide, int wideSize, const char *utf8)
  441. {
  442. return !!MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, wideSize);
  443. }
  444. static inline bool WideToUTF8(char *utf8, int utf8Size, const wchar_t *wide)
  445. {
  446. return !!WideCharToMultiByte(CP_UTF8, 0, wide, -1, utf8, utf8Size,
  447. nullptr, nullptr);
  448. }
  449. static inline bool FileExists(const wchar_t *path)
  450. {
  451. WIN32_FIND_DATAW wfd;
  452. HANDLE hFind;
  453. hFind = FindFirstFileW(path, &wfd);
  454. if (hFind != INVALID_HANDLE_VALUE)
  455. FindClose(hFind);
  456. return hFind != INVALID_HANDLE_VALUE;
  457. }
  458. static bool NonCorePackageInstalled(const char *name)
  459. {
  460. if (strcmp(name, "obs-browser") == 0) {
  461. return FileExists(L"obs-plugins\\32bit\\obs-browser.dll");
  462. } else if (strcmp(name, "realsense") == 0) {
  463. return FileExists(L"obs-plugins\\32bit\\win-ivcam.dll");
  464. }
  465. return false;
  466. }
  467. #define UTF8ToWideBuf(wide, utf8) UTF8ToWide(wide, _countof(wide), utf8)
  468. #define WideToUTF8Buf(utf8, wide) WideToUTF8(utf8, _countof(utf8), wide)
  469. #define UPDATE_URL L"https://obsproject.com/update_studio"
  470. static bool AddPackageUpdateFiles(json_t *root, size_t idx,
  471. const wchar_t *tempPath)
  472. {
  473. json_t *package = json_array_get(root, idx);
  474. json_t *name = json_object_get(package, "name");
  475. json_t *files = json_object_get(package, "files");
  476. if (!json_is_array(files))
  477. return true;
  478. if (!json_is_string(name))
  479. return true;
  480. wchar_t wPackageName[512];
  481. const char *packageName = json_string_value(name);
  482. size_t fileCount = json_array_size(files);
  483. if (!UTF8ToWideBuf(wPackageName, packageName))
  484. return false;
  485. if (strcmp(packageName, "core") != 0 &&
  486. !NonCorePackageInstalled(packageName))
  487. return true;
  488. for (size_t j = 0; j < fileCount; j++) {
  489. json_t *file = json_array_get(files, j);
  490. json_t *fileName = json_object_get(file, "name");
  491. json_t *hash = json_object_get(file, "hash");
  492. json_t *size = json_object_get(file, "size");
  493. if (!json_is_string(fileName))
  494. continue;
  495. if (!json_is_string(hash))
  496. continue;
  497. if (!json_is_integer(size))
  498. continue;
  499. const char *fileUTF8 = json_string_value(fileName);
  500. const char *hashUTF8 = json_string_value(hash);
  501. int fileSize = (int)json_integer_value(size);
  502. if (strlen(hashUTF8) != BLAKE2_HASH_LENGTH * 2)
  503. continue;
  504. /* convert strings to wide */
  505. wchar_t sourceURL[1024];
  506. wchar_t updateFileName[MAX_PATH];
  507. wchar_t updateHashStr[BLAKE2_HASH_STR_LENGTH];
  508. wchar_t tempFilePath[MAX_PATH];
  509. if (!UTF8ToWideBuf(updateFileName, fileUTF8))
  510. continue;
  511. if (!UTF8ToWideBuf(updateHashStr, hashUTF8))
  512. continue;
  513. /* make sure paths are safe */
  514. if (!IsSafeFilename(updateFileName)) {
  515. Status(L"Update failed: Unsafe path '%s' found in "
  516. L"manifest", updateFileName);
  517. return false;
  518. }
  519. StringCbPrintf(sourceURL, sizeof(sourceURL), L"%s/%s/%s",
  520. UPDATE_URL, wPackageName, updateFileName);
  521. StringCbPrintf(tempFilePath, sizeof(tempFilePath),
  522. L"%s\\%s", tempPath, updateHashStr);
  523. /* Check file hash */
  524. BYTE existingHash[BLAKE2_HASH_LENGTH];
  525. wchar_t fileHashStr[BLAKE2_HASH_STR_LENGTH];
  526. bool has_hash;
  527. /* We don't really care if this fails, it's just to avoid
  528. * wasting bandwidth by downloading unmodified files */
  529. if (CalculateFileHash(updateFileName, existingHash)) {
  530. HashToString(existingHash, fileHashStr);
  531. if (wcscmp(fileHashStr, updateHashStr) == 0)
  532. continue;
  533. has_hash = true;
  534. } else {
  535. has_hash = false;
  536. }
  537. /* Add update file */
  538. update_t update;
  539. update.fileSize = fileSize;
  540. update.basename = updateFileName;
  541. update.outputPath = updateFileName;
  542. update.tempPath = tempFilePath;
  543. update.sourceURL = sourceURL;
  544. update.packageName = packageName;
  545. update.state = STATE_PENDING_DOWNLOAD;
  546. update.patchable = false;
  547. StringToHash(updateHashStr, update.downloadhash);
  548. memcpy(update.hash, update.downloadhash, sizeof(update.hash));
  549. update.has_hash = has_hash;
  550. if (has_hash)
  551. StringToHash(fileHashStr, update.my_hash);
  552. updates.push_back(move(update));
  553. totalFileSize += fileSize;
  554. }
  555. return true;
  556. }
  557. static void UpdateWithPatchIfAvailable(const char *name, const char *hash,
  558. const char *source,
  559. int size)
  560. {
  561. wchar_t widePatchableFilename[MAX_PATH];
  562. wchar_t widePatchHash[MAX_PATH];
  563. wchar_t sourceURL[1024];
  564. wchar_t patchHashStr[BLAKE2_HASH_STR_LENGTH];
  565. if (strncmp(source, "https://obsproject.com/", 23) != 0)
  566. return;
  567. string patchPackageName = name;
  568. const char *slash = strchr(name, '/');
  569. if (!slash)
  570. return;
  571. patchPackageName.resize(slash - name);
  572. name = slash + 1;
  573. if (!UTF8ToWideBuf(widePatchableFilename, name))
  574. return;
  575. if (!UTF8ToWideBuf(widePatchHash, hash))
  576. return;
  577. if (!UTF8ToWideBuf(sourceURL, source))
  578. return;
  579. if (!UTF8ToWideBuf(patchHashStr, hash))
  580. return;
  581. for (update_t &update : updates) {
  582. if (update.packageName != patchPackageName)
  583. continue;
  584. if (update.basename != widePatchableFilename)
  585. continue;
  586. StringToHash(patchHashStr, update.downloadhash);
  587. /* Replace the source URL with the patch file, mark it as
  588. * patchable, and re-calculate download size */
  589. totalFileSize -= (update.fileSize - size);
  590. update.sourceURL = sourceURL;
  591. update.fileSize = size;
  592. update.patchable = true;
  593. break;
  594. }
  595. }
  596. static bool UpdateFile(update_t &file)
  597. {
  598. wchar_t oldFileRenamedPath[MAX_PATH];
  599. if (file.patchable)
  600. Status(L"Updating %s...", file.outputPath.c_str());
  601. else
  602. Status(L"Installing %s...", file.outputPath.c_str());
  603. /* Check if we're replacing an existing file or just installing a new
  604. * one */
  605. DWORD attribs = GetFileAttributes(file.outputPath.c_str());
  606. if (attribs != INVALID_FILE_ATTRIBUTES) {
  607. wchar_t *curFileName = nullptr;
  608. wchar_t baseName[MAX_PATH];
  609. StringCbCopy(baseName, sizeof(baseName),
  610. file.outputPath.c_str());
  611. curFileName = wcsrchr(baseName, '/');
  612. if (curFileName) {
  613. curFileName[0] = '\0';
  614. curFileName++;
  615. } else
  616. curFileName = baseName;
  617. /* Backup the existing file in case a rollback is needed */
  618. StringCbCopy(oldFileRenamedPath,
  619. sizeof(oldFileRenamedPath),
  620. file.outputPath.c_str());
  621. StringCbCat(oldFileRenamedPath,
  622. sizeof(oldFileRenamedPath),
  623. L".old");
  624. if (!MyCopyFile(file.outputPath.c_str(), oldFileRenamedPath)) {
  625. int is_sharing_violation =
  626. (GetLastError() == ERROR_SHARING_VIOLATION);
  627. if (is_sharing_violation)
  628. Status(L"Update failed: %s is still in use. "
  629. L"Close all programs and try again.",
  630. curFileName);
  631. else
  632. Status(L"Update failed: Couldn't backup %s "
  633. L"(error %d)",
  634. curFileName, GetLastError());
  635. return false;
  636. }
  637. int error_code;
  638. bool installed_ok;
  639. if (file.patchable) {
  640. error_code = ApplyPatch(
  641. file.tempPath.c_str(),
  642. file.outputPath.c_str());
  643. installed_ok = (error_code == 0);
  644. if (installed_ok) {
  645. BYTE patchedFileHash[BLAKE2_HASH_LENGTH];
  646. if (!CalculateFileHash(file.outputPath.c_str(),
  647. patchedFileHash)) {
  648. Status(L"Update failed: Couldn't "
  649. L"verify integrity of patched %s",
  650. curFileName);
  651. return false;
  652. }
  653. if (memcmp(file.hash, patchedFileHash,
  654. BLAKE2_HASH_LENGTH) != 0) {
  655. Status(L"Update failed: Integrity "
  656. L"check of patched "
  657. L"%s failed",
  658. curFileName);
  659. return false;
  660. }
  661. }
  662. } else {
  663. installed_ok = MyCopyFile(
  664. file.tempPath.c_str(),
  665. file.outputPath.c_str());
  666. error_code = GetLastError();
  667. }
  668. if (!installed_ok) {
  669. int is_sharing_violation =
  670. (error_code == ERROR_SHARING_VIOLATION);
  671. if (is_sharing_violation)
  672. Status(L"Update failed: %s is still in use. "
  673. L"Close all "
  674. L"programs and try again.",
  675. curFileName);
  676. else
  677. Status(L"Update failed: Couldn't update %s "
  678. L"(error %d)",
  679. curFileName,
  680. GetLastError());
  681. return false;
  682. }
  683. file.previousFile = oldFileRenamedPath;
  684. file.state = STATE_INSTALLED;
  685. } else {
  686. if (file.patchable) {
  687. /* Uh oh, we thought we could patch something but it's
  688. * no longer there! */
  689. Status(L"Update failed: Source file %s not found",
  690. file.outputPath.c_str());
  691. return false;
  692. }
  693. /* We may be installing into new folders,
  694. * make sure they exist */
  695. CreateFoldersForPath(file.outputPath.c_str());
  696. bool success = !!MyCopyFile(
  697. file.tempPath.c_str(),
  698. file.outputPath.c_str());
  699. if (!success) {
  700. Status(L"Update failed: Couldn't install %s (error %d)",
  701. file.outputPath.c_str(),
  702. GetLastError());
  703. return false;
  704. }
  705. file.previousFile = L"";
  706. file.state = STATE_INSTALLED;
  707. }
  708. return true;
  709. }
  710. static wchar_t tempPath[MAX_PATH] = {};
  711. #define PATCH_MANIFEST_URL \
  712. L"https://obsproject.com/update_studio/getpatchmanifest"
  713. #define HASH_NULL \
  714. L"0000000000000000000000000000000000000000"
  715. static bool Update(wchar_t *cmdLine)
  716. {
  717. /* ------------------------------------- *
  718. * Check to make sure OBS isn't running */
  719. HANDLE hObsUpdateMutex = OpenMutexW(SYNCHRONIZE, false,
  720. L"OBSStudioUpdateMutex");
  721. if (hObsUpdateMutex) {
  722. HANDLE hWait[2];
  723. hWait[0] = hObsUpdateMutex;
  724. hWait[1] = cancelRequested;
  725. int i = WaitForMultipleObjects(2, hWait, false, INFINITE);
  726. if (i == WAIT_OBJECT_0)
  727. ReleaseMutex(hObsUpdateMutex);
  728. CloseHandle(hObsUpdateMutex);
  729. if (i == WAIT_OBJECT_0 + 1)
  730. return false;
  731. }
  732. if (!WaitForOBS())
  733. return false;
  734. /* ------------------------------------- *
  735. * Init crypt stuff */
  736. CryptProvider hProvider;
  737. if (!CryptAcquireContext(&hProvider, nullptr, MS_ENH_RSA_AES_PROV,
  738. PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
  739. SetDlgItemTextW(hwndMain, IDC_STATUS,
  740. L"Update failed: CryptAcquireContext failure");
  741. return false;
  742. }
  743. ::hProvider = hProvider;
  744. /* ------------------------------------- */
  745. SetDlgItemTextW(hwndMain, IDC_STATUS,
  746. L"Searching for available updates...");
  747. /* ------------------------------------- *
  748. * Check if updating portable build */
  749. bool bIsPortable = false;
  750. if (cmdLine[0]) {
  751. int argc;
  752. LPWSTR *argv = CommandLineToArgvW(cmdLine, &argc);
  753. if (argv) {
  754. for (int i = 0; i < argc; i++) {
  755. if (wcscmp(argv[i], L"Portable") == 0) {
  756. bIsPortable = true;
  757. }
  758. }
  759. LocalFree((HLOCAL)argv);
  760. }
  761. }
  762. /* ------------------------------------- *
  763. * Get config path */
  764. wchar_t lpAppDataPath[MAX_PATH];
  765. lpAppDataPath[0] = 0;
  766. if (bIsPortable) {
  767. GetCurrentDirectory(_countof(lpAppDataPath), lpAppDataPath);
  768. StringCbCat(lpAppDataPath, sizeof(lpAppDataPath), L"\\config");
  769. } else {
  770. CoTaskMemPtr<wchar_t> pOut;
  771. HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData,
  772. KF_FLAG_DEFAULT, nullptr, &pOut);
  773. if (hr != S_OK) {
  774. Status(L"Update failed: Could not determine AppData "
  775. L"location");
  776. return false;
  777. }
  778. StringCbCopy(lpAppDataPath, sizeof(lpAppDataPath), pOut);
  779. StringCbCat(lpAppDataPath, sizeof(lpAppDataPath),
  780. L"\\obs-studio");
  781. }
  782. /* ------------------------------------- *
  783. * Get download path */
  784. wchar_t manifestPath[MAX_PATH];
  785. wchar_t tempDirName[MAX_PATH];
  786. manifestPath[0] = 0;
  787. tempDirName[0] = 0;
  788. StringCbPrintf(manifestPath, sizeof(manifestPath),
  789. L"%s\\updates\\manifest.json", lpAppDataPath);
  790. if (!GetTempPathW(_countof(tempPath), tempPath)) {
  791. Status(L"Update failed: Failed to get temp path: %ld",
  792. GetLastError());
  793. return false;
  794. }
  795. if (!GetTempFileNameW(tempDirName, L"obs-studio", 0, tempDirName)) {
  796. Status(L"Update failed: Failed to create temp dir name: %ld",
  797. GetLastError());
  798. return false;
  799. }
  800. StringCbCat(tempPath, sizeof(tempPath), tempDirName);
  801. CreateDirectory(tempPath, nullptr);
  802. /* ------------------------------------- *
  803. * Load manifest file */
  804. Json root;
  805. {
  806. string manifestFile = QuickReadFile(manifestPath);
  807. if (manifestFile.empty()) {
  808. Status(L"Update failed: Couldn't load manifest file");
  809. return false;
  810. }
  811. json_error_t error;
  812. root = json_loads(manifestFile.c_str(), 0, &error);
  813. if (!root) {
  814. Status(L"Update failed: Couldn't parse update "
  815. L"manifest: %S", error.text);
  816. return false;
  817. }
  818. }
  819. if (!json_is_object(root.get())) {
  820. Status(L"Update failed: Invalid update manifest");
  821. return false;
  822. }
  823. /* ------------------------------------- *
  824. * Parse current manifest update files */
  825. json_t *packages = json_object_get(root, "packages");
  826. size_t packageCount = json_array_size(packages);
  827. for (size_t i = 0; i < packageCount; i++) {
  828. if (!AddPackageUpdateFiles(packages, i, tempPath)) {
  829. Status(L"Failed to process update packages");
  830. return false;
  831. }
  832. }
  833. /* ------------------------------------- *
  834. * Exit if updates already installed */
  835. if (!updates.size()) {
  836. Status(L"All available updates are already installed.");
  837. return true;
  838. }
  839. /* ------------------------------------- *
  840. * Generate file hash json */
  841. Json files(json_array());
  842. for (update_t &update : updates) {
  843. wchar_t whash_string[BLAKE2_HASH_STR_LENGTH];
  844. char hash_string[BLAKE2_HASH_STR_LENGTH];
  845. char outputPath[MAX_PATH];
  846. if (!update.has_hash)
  847. continue;
  848. /* check hash */
  849. HashToString(update.my_hash, whash_string);
  850. if (wcscmp(whash_string, HASH_NULL) == 0)
  851. continue;
  852. if (!WideToUTF8Buf(hash_string, whash_string))
  853. continue;
  854. if (!WideToUTF8Buf(outputPath, update.basename.c_str()))
  855. continue;
  856. string package_path;
  857. package_path = update.packageName;
  858. package_path += "/";
  859. package_path += outputPath;
  860. json_t *obj = json_object();
  861. json_object_set(obj, "name", json_string(package_path.c_str()));
  862. json_object_set(obj, "hash", json_string(hash_string));
  863. json_array_append_new(files, obj);
  864. }
  865. /* ------------------------------------- *
  866. * Send file hashes */
  867. string newManifest;
  868. {
  869. char *post_body = json_dumps(files, JSON_COMPACT);
  870. int responseCode;
  871. int len = (int)strlen(post_body);
  872. uLong compressSize = compressBound(len);
  873. string compressedJson;
  874. compressedJson.resize(compressSize);
  875. compress2((Bytef*)&compressedJson[0], &compressSize,
  876. (const Bytef*)post_body, len,
  877. Z_BEST_COMPRESSION);
  878. compressedJson.resize(compressSize);
  879. bool success = !!HTTPPostData(PATCH_MANIFEST_URL,
  880. (BYTE *)&compressedJson[0],
  881. (int)compressedJson.size(),
  882. L"Accept-Encoding: gzip", &responseCode,
  883. newManifest);
  884. free(post_body);
  885. if (!success)
  886. return false;
  887. if (responseCode != 200) {
  888. Status(L"Update failed: HTTP/%d while trying to "
  889. L"download patch manifest",
  890. responseCode);
  891. return false;
  892. }
  893. }
  894. /* ------------------------------------- *
  895. * Parse new manifest */
  896. json_error_t error;
  897. root = json_loads(newManifest.c_str(), 0, &error);
  898. if (!root) {
  899. Status(L"Update failed: Couldn't parse patch manifest: %S",
  900. error.text);
  901. return false;
  902. }
  903. if (!json_is_array(root.get())) {
  904. Status(L"Update failed: Invalid patch manifest");
  905. return false;
  906. }
  907. packageCount = json_array_size(root);
  908. for (size_t i = 0; i < packageCount; i++) {
  909. json_t *patch = json_array_get(root, i);
  910. if (!json_is_object(patch)) {
  911. Status(L"Update failed: Invalid patch manifest");
  912. return false;
  913. }
  914. json_t *name_json = json_object_get(patch, "name");
  915. json_t *hash_json = json_object_get(patch, "hash");
  916. json_t *source_json = json_object_get(patch, "source");
  917. json_t *size_json = json_object_get(patch, "size");
  918. if (!json_is_string(name_json))
  919. continue;
  920. if (!json_is_string(hash_json))
  921. continue;
  922. if (!json_is_string(source_json))
  923. continue;
  924. if (!json_is_integer(size_json))
  925. continue;
  926. const char *name = json_string_value(name_json);
  927. const char *hash = json_string_value(hash_json);
  928. const char *source = json_string_value(source_json);
  929. int size = (int)json_integer_value(size_json);
  930. UpdateWithPatchIfAvailable(name, hash, source, size);
  931. }
  932. /* ------------------------------------- *
  933. * Download Updates */
  934. if (!RunDownloadWorkers(2))
  935. return false;
  936. if (completedUpdates != updates.size()) {
  937. Status(L"Update failed to download all files.");
  938. return false;
  939. }
  940. /* ------------------------------------- *
  941. * Install updates */
  942. for (update_t &update : updates) {
  943. if (!UpdateFile(update))
  944. return false;
  945. }
  946. /* If we get here, all updates installed successfully so we can purge
  947. * the old versions */
  948. for (update_t &update : updates) {
  949. if (!update.previousFile.empty())
  950. DeleteFile(update.previousFile.c_str());
  951. /* We delete here not above in case of duplicate hashes */
  952. if (!update.tempPath.empty())
  953. DeleteFile(update.tempPath.c_str());
  954. }
  955. Status(L"Update complete.");
  956. SetDlgItemText(hwndMain, IDC_BUTTON, L"Launch OBS");
  957. return true;
  958. }
  959. static DWORD WINAPI UpdateThread(void *arg)
  960. {
  961. wchar_t *cmdLine = (wchar_t *)arg;
  962. bool success = Update(cmdLine);
  963. if (!success) {
  964. /* This handles deleting temp files and rolling back and
  965. * partially installed updates */
  966. CleanupPartialUpdates();
  967. if (tempPath[0])
  968. RemoveDirectory(tempPath);
  969. if (WaitForSingleObject(cancelRequested, 0) == WAIT_OBJECT_0)
  970. Status(L"Update aborted.");
  971. SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETSTATE,
  972. PBST_ERROR, 0);
  973. SetDlgItemText(hwndMain, IDC_BUTTON, L"Exit");
  974. EnableWindow(GetDlgItem(hwndMain, IDC_BUTTON), true);
  975. updateFailed = true;
  976. } else {
  977. if (tempPath[0])
  978. RemoveDirectory(tempPath);
  979. }
  980. if (bExiting)
  981. ExitProcess(success);
  982. return 0;
  983. }
  984. static void CancelUpdate(bool quit)
  985. {
  986. if (WaitForSingleObject(updateThread, 0) != WAIT_OBJECT_0) {
  987. bExiting = quit;
  988. SetEvent(cancelRequested);
  989. } else {
  990. PostQuitMessage(0);
  991. }
  992. }
  993. static void LaunchOBS()
  994. {
  995. wchar_t cwd[MAX_PATH];
  996. wchar_t newCwd[MAX_PATH];
  997. wchar_t obsPath[MAX_PATH];
  998. GetCurrentDirectory(_countof(cwd) - 1, cwd);
  999. StringCbCopy(obsPath, sizeof(obsPath), cwd);
  1000. StringCbCat(obsPath, sizeof(obsPath), is32bit
  1001. ? L"\\bin\\32bit"
  1002. : L"\\bin\\64bit");
  1003. SetCurrentDirectory(obsPath);
  1004. StringCbCopy(newCwd, sizeof(newCwd), obsPath);
  1005. StringCbCat(obsPath, sizeof(obsPath), is32bit
  1006. ? L"\\obs32.exe"
  1007. : L"\\obs64.exe");
  1008. if (!FileExists(obsPath)) {
  1009. StringCbCopy(obsPath, sizeof(obsPath), cwd);
  1010. StringCbCat(obsPath, sizeof(obsPath), L"\\bin\\32bit");
  1011. SetCurrentDirectory(obsPath);
  1012. StringCbCopy(newCwd, sizeof(newCwd), obsPath);
  1013. StringCbCat(obsPath, sizeof(obsPath), L"\\obs32.exe");
  1014. if (!FileExists(obsPath)) {
  1015. /* TODO: give user a message maybe? */
  1016. return;
  1017. }
  1018. }
  1019. SHELLEXECUTEINFO execInfo;
  1020. ZeroMemory(&execInfo, sizeof(execInfo));
  1021. execInfo.cbSize = sizeof(execInfo);
  1022. execInfo.lpFile = obsPath;
  1023. execInfo.lpDirectory = newCwd;
  1024. execInfo.nShow = SW_SHOWNORMAL;
  1025. ShellExecuteEx(&execInfo);
  1026. }
  1027. static INT_PTR CALLBACK UpdateDialogProc(HWND hwnd, UINT message,
  1028. WPARAM wParam, LPARAM lParam)
  1029. {
  1030. switch (message) {
  1031. case WM_INITDIALOG: {
  1032. static HICON hMainIcon = LoadIcon(hinstMain,
  1033. MAKEINTRESOURCE(IDI_ICON1));
  1034. SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hMainIcon);
  1035. SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hMainIcon);
  1036. return true;
  1037. }
  1038. case WM_COMMAND:
  1039. if (LOWORD(wParam) == IDC_BUTTON) {
  1040. if (HIWORD(wParam) == BN_CLICKED) {
  1041. DWORD result = WaitForSingleObject(
  1042. updateThread, 0);
  1043. if (result == WAIT_OBJECT_0) {
  1044. if (updateFailed)
  1045. PostQuitMessage(0);
  1046. else
  1047. PostQuitMessage(1);
  1048. } else {
  1049. EnableWindow((HWND)lParam, false);
  1050. CancelUpdate(false);
  1051. }
  1052. }
  1053. }
  1054. return true;
  1055. case WM_CLOSE:
  1056. CancelUpdate(true);
  1057. return true;
  1058. }
  1059. return false;
  1060. }
  1061. static void RestartAsAdmin(LPWSTR lpCmdLine)
  1062. {
  1063. wchar_t myPath[MAX_PATH];
  1064. if (!GetModuleFileNameW(nullptr, myPath, _countof(myPath) - 1)) {
  1065. return;
  1066. }
  1067. wchar_t cwd[MAX_PATH];
  1068. GetCurrentDirectoryW(_countof(cwd) - 1, cwd);
  1069. SHELLEXECUTEINFO shExInfo = {0};
  1070. shExInfo.cbSize = sizeof(shExInfo);
  1071. shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
  1072. shExInfo.hwnd = 0;
  1073. shExInfo.lpVerb = L"runas"; /* Operation to perform */
  1074. shExInfo.lpFile = myPath; /* Application to start */
  1075. shExInfo.lpParameters = lpCmdLine; /* Additional parameters */
  1076. shExInfo.lpDirectory = cwd;
  1077. shExInfo.nShow = SW_NORMAL;
  1078. shExInfo.hInstApp = 0;
  1079. /* annoyingly the actual elevated updater will disappear behind other
  1080. * windows :( */
  1081. AllowSetForegroundWindow(ASFW_ANY);
  1082. if (ShellExecuteEx(&shExInfo)) {
  1083. DWORD exitCode;
  1084. if (GetExitCodeProcess(shExInfo.hProcess, &exitCode)) {
  1085. if (exitCode == 1) {
  1086. LaunchOBS();
  1087. }
  1088. }
  1089. CloseHandle(shExInfo.hProcess);
  1090. }
  1091. }
  1092. int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
  1093. {
  1094. INITCOMMONCONTROLSEX icce;
  1095. if (!IsAppRunningAsAdminMode()) {
  1096. HANDLE hLowMutex = CreateMutexW(nullptr, true,
  1097. L"OBSUpdaterRunningAsNonAdminUser");
  1098. RestartAsAdmin(lpCmdLine);
  1099. if (hLowMutex) {
  1100. ReleaseMutex(hLowMutex);
  1101. CloseHandle(hLowMutex);
  1102. }
  1103. return 0;
  1104. } else {
  1105. {
  1106. wchar_t cwd[MAX_PATH];
  1107. wchar_t newPath[MAX_PATH];
  1108. GetCurrentDirectoryW(_countof(cwd) - 1, cwd);
  1109. is32bit = wcsstr(cwd, L"bin\\32bit") != nullptr;
  1110. StringCbCat(cwd, sizeof(cwd), L"\\..\\..");
  1111. GetFullPathName(cwd, _countof(newPath), newPath,
  1112. nullptr);
  1113. SetCurrentDirectory(newPath);
  1114. }
  1115. hinstMain = hInstance;
  1116. icce.dwSize = sizeof(icce);
  1117. icce.dwICC = ICC_PROGRESS_CLASS;
  1118. InitCommonControlsEx(&icce);
  1119. hwndMain = CreateDialog(hInstance,
  1120. MAKEINTRESOURCE(IDD_UPDATEDIALOG), nullptr,
  1121. UpdateDialogProc);
  1122. if (!hwndMain) {
  1123. return -1;
  1124. }
  1125. ShowWindow(hwndMain, SW_SHOWNORMAL);
  1126. SetForegroundWindow(hwndMain);
  1127. cancelRequested = CreateEvent(nullptr, true, false, nullptr);
  1128. updateThread = CreateThread(nullptr, 0, UpdateThread,
  1129. lpCmdLine, 0, nullptr);
  1130. MSG msg;
  1131. while (GetMessage(&msg, nullptr, 0, 0)) {
  1132. if (!IsDialogMessage(hwndMain, &msg)) {
  1133. TranslateMessage(&msg);
  1134. DispatchMessage(&msg);
  1135. }
  1136. }
  1137. /* there is no non-elevated process waiting for us if UAC is
  1138. * disabled */
  1139. WinHandle hMutex = OpenMutex(SYNCHRONIZE, false,
  1140. L"OBSUpdaterRunningAsNonAdminUser");
  1141. if (msg.wParam == 1 && !hMutex) {
  1142. LaunchOBS();
  1143. }
  1144. return (int)msg.wParam;
  1145. }
  1146. }