updater.cpp 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441
  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 path[MAX_PATH];
  391. wchar_t *name;
  392. *path = 0;
  393. WinHandle proc = OpenProcess(
  394. PROCESS_QUERY_INFORMATION |
  395. PROCESS_VM_READ |
  396. SYNCHRONIZE,
  397. false, id);
  398. if (!proc.Valid())
  399. return WAITIFOBS_WRONG_PROCESS;
  400. if (!GetProcessImageFileName(proc, path, _countof(path)))
  401. return WAITIFOBS_WRONG_PROCESS;
  402. name = wcsrchr(path, L'\\');
  403. if (name)
  404. name += 1;
  405. else
  406. name = path;
  407. if (_wcsnicmp(name, expected, 5) == 0) {
  408. HANDLE hWait[2];
  409. hWait[0] = proc;
  410. hWait[1] = cancelRequested;
  411. int i = WaitForMultipleObjects(2, hWait, false, INFINITE);
  412. if (i == WAIT_OBJECT_0 + 1)
  413. return WAITIFOBS_CANCELLED;
  414. return WAITIFOBS_SUCCESS;
  415. }
  416. return WAITIFOBS_WRONG_PROCESS;
  417. }
  418. static bool WaitForOBS()
  419. {
  420. DWORD proc_ids[1024], needed, count;
  421. const wchar_t *name = is32bit ? L"obs32" : L"obs64";
  422. if (!EnumProcesses(proc_ids, sizeof(proc_ids), &needed)) {
  423. return true;
  424. }
  425. count = needed / sizeof(DWORD);
  426. for (DWORD i = 0; i < count; i++) {
  427. DWORD id = proc_ids[i];
  428. if (id != 0) {
  429. switch (WaitIfOBS(id, name)) {
  430. case WAITIFOBS_SUCCESS:
  431. return true;
  432. case WAITIFOBS_WRONG_PROCESS:
  433. break;
  434. case WAITIFOBS_CANCELLED:
  435. return false;
  436. }
  437. }
  438. }
  439. return true;
  440. }
  441. /* ----------------------------------------------------------------------- */
  442. static inline bool UTF8ToWide(wchar_t *wide, int wideSize, const char *utf8)
  443. {
  444. return !!MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, wideSize);
  445. }
  446. static inline bool WideToUTF8(char *utf8, int utf8Size, const wchar_t *wide)
  447. {
  448. return !!WideCharToMultiByte(CP_UTF8, 0, wide, -1, utf8, utf8Size,
  449. nullptr, nullptr);
  450. }
  451. static inline bool FileExists(const wchar_t *path)
  452. {
  453. WIN32_FIND_DATAW wfd;
  454. HANDLE hFind;
  455. hFind = FindFirstFileW(path, &wfd);
  456. if (hFind != INVALID_HANDLE_VALUE)
  457. FindClose(hFind);
  458. return hFind != INVALID_HANDLE_VALUE;
  459. }
  460. static bool NonCorePackageInstalled(const char *name)
  461. {
  462. if (strcmp(name, "obs-browser") == 0) {
  463. return FileExists(L"obs-plugins\\32bit\\obs-browser.dll");
  464. } else if (strcmp(name, "realsense") == 0) {
  465. return FileExists(L"obs-plugins\\32bit\\win-ivcam.dll");
  466. }
  467. return false;
  468. }
  469. #define UTF8ToWideBuf(wide, utf8) UTF8ToWide(wide, _countof(wide), utf8)
  470. #define WideToUTF8Buf(utf8, wide) WideToUTF8(utf8, _countof(utf8), wide)
  471. #define UPDATE_URL L"https://obsproject.com/update_studio"
  472. static bool AddPackageUpdateFiles(json_t *root, size_t idx,
  473. const wchar_t *tempPath)
  474. {
  475. json_t *package = json_array_get(root, idx);
  476. json_t *name = json_object_get(package, "name");
  477. json_t *files = json_object_get(package, "files");
  478. if (!json_is_array(files))
  479. return true;
  480. if (!json_is_string(name))
  481. return true;
  482. wchar_t wPackageName[512];
  483. const char *packageName = json_string_value(name);
  484. size_t fileCount = json_array_size(files);
  485. if (!UTF8ToWideBuf(wPackageName, packageName))
  486. return false;
  487. if (strcmp(packageName, "core") != 0 &&
  488. !NonCorePackageInstalled(packageName))
  489. return true;
  490. for (size_t j = 0; j < fileCount; j++) {
  491. json_t *file = json_array_get(files, j);
  492. json_t *fileName = json_object_get(file, "name");
  493. json_t *hash = json_object_get(file, "hash");
  494. json_t *size = json_object_get(file, "size");
  495. if (!json_is_string(fileName))
  496. continue;
  497. if (!json_is_string(hash))
  498. continue;
  499. if (!json_is_integer(size))
  500. continue;
  501. const char *fileUTF8 = json_string_value(fileName);
  502. const char *hashUTF8 = json_string_value(hash);
  503. int fileSize = (int)json_integer_value(size);
  504. if (strlen(hashUTF8) != BLAKE2_HASH_LENGTH * 2)
  505. continue;
  506. /* convert strings to wide */
  507. wchar_t sourceURL[1024];
  508. wchar_t updateFileName[MAX_PATH];
  509. wchar_t updateHashStr[BLAKE2_HASH_STR_LENGTH];
  510. wchar_t tempFilePath[MAX_PATH];
  511. if (!UTF8ToWideBuf(updateFileName, fileUTF8))
  512. continue;
  513. if (!UTF8ToWideBuf(updateHashStr, hashUTF8))
  514. continue;
  515. /* make sure paths are safe */
  516. if (!IsSafeFilename(updateFileName)) {
  517. Status(L"Update failed: Unsafe path '%s' found in "
  518. L"manifest", updateFileName);
  519. return false;
  520. }
  521. StringCbPrintf(sourceURL, sizeof(sourceURL), L"%s/%s/%s",
  522. UPDATE_URL, wPackageName, updateFileName);
  523. StringCbPrintf(tempFilePath, sizeof(tempFilePath),
  524. L"%s\\%s", tempPath, updateHashStr);
  525. /* Check file hash */
  526. BYTE existingHash[BLAKE2_HASH_LENGTH];
  527. wchar_t fileHashStr[BLAKE2_HASH_STR_LENGTH];
  528. bool has_hash;
  529. /* We don't really care if this fails, it's just to avoid
  530. * wasting bandwidth by downloading unmodified files */
  531. if (CalculateFileHash(updateFileName, existingHash)) {
  532. HashToString(existingHash, fileHashStr);
  533. if (wcscmp(fileHashStr, updateHashStr) == 0)
  534. continue;
  535. has_hash = true;
  536. } else {
  537. has_hash = false;
  538. }
  539. /* Add update file */
  540. update_t update;
  541. update.fileSize = fileSize;
  542. update.basename = updateFileName;
  543. update.outputPath = updateFileName;
  544. update.tempPath = tempFilePath;
  545. update.sourceURL = sourceURL;
  546. update.packageName = packageName;
  547. update.state = STATE_PENDING_DOWNLOAD;
  548. update.patchable = false;
  549. StringToHash(updateHashStr, update.downloadhash);
  550. memcpy(update.hash, update.downloadhash, sizeof(update.hash));
  551. update.has_hash = has_hash;
  552. if (has_hash)
  553. StringToHash(fileHashStr, update.my_hash);
  554. updates.push_back(move(update));
  555. totalFileSize += fileSize;
  556. }
  557. return true;
  558. }
  559. static void UpdateWithPatchIfAvailable(const char *name, const char *hash,
  560. const char *source,
  561. int size)
  562. {
  563. wchar_t widePatchableFilename[MAX_PATH];
  564. wchar_t widePatchHash[MAX_PATH];
  565. wchar_t sourceURL[1024];
  566. wchar_t patchHashStr[BLAKE2_HASH_STR_LENGTH];
  567. if (strncmp(source, "https://obsproject.com/", 23) != 0)
  568. return;
  569. string patchPackageName = name;
  570. const char *slash = strchr(name, '/');
  571. if (!slash)
  572. return;
  573. patchPackageName.resize(slash - name);
  574. name = slash + 1;
  575. if (!UTF8ToWideBuf(widePatchableFilename, name))
  576. return;
  577. if (!UTF8ToWideBuf(widePatchHash, hash))
  578. return;
  579. if (!UTF8ToWideBuf(sourceURL, source))
  580. return;
  581. if (!UTF8ToWideBuf(patchHashStr, hash))
  582. return;
  583. for (update_t &update : updates) {
  584. if (update.packageName != patchPackageName)
  585. continue;
  586. if (update.basename != widePatchableFilename)
  587. continue;
  588. StringToHash(patchHashStr, update.downloadhash);
  589. /* Replace the source URL with the patch file, mark it as
  590. * patchable, and re-calculate download size */
  591. totalFileSize -= (update.fileSize - size);
  592. update.sourceURL = sourceURL;
  593. update.fileSize = size;
  594. update.patchable = true;
  595. break;
  596. }
  597. }
  598. static bool UpdateFile(update_t &file)
  599. {
  600. wchar_t oldFileRenamedPath[MAX_PATH];
  601. if (file.patchable)
  602. Status(L"Updating %s...", file.outputPath.c_str());
  603. else
  604. Status(L"Installing %s...", file.outputPath.c_str());
  605. /* Check if we're replacing an existing file or just installing a new
  606. * one */
  607. DWORD attribs = GetFileAttributes(file.outputPath.c_str());
  608. if (attribs != INVALID_FILE_ATTRIBUTES) {
  609. wchar_t *curFileName = nullptr;
  610. wchar_t baseName[MAX_PATH];
  611. StringCbCopy(baseName, sizeof(baseName),
  612. file.outputPath.c_str());
  613. curFileName = wcsrchr(baseName, '/');
  614. if (curFileName) {
  615. curFileName[0] = '\0';
  616. curFileName++;
  617. } else
  618. curFileName = baseName;
  619. /* Backup the existing file in case a rollback is needed */
  620. StringCbCopy(oldFileRenamedPath,
  621. sizeof(oldFileRenamedPath),
  622. file.outputPath.c_str());
  623. StringCbCat(oldFileRenamedPath,
  624. sizeof(oldFileRenamedPath),
  625. L".old");
  626. if (!MyCopyFile(file.outputPath.c_str(), oldFileRenamedPath)) {
  627. int is_sharing_violation =
  628. (GetLastError() == ERROR_SHARING_VIOLATION);
  629. if (is_sharing_violation)
  630. Status(L"Update failed: %s is still in use. "
  631. L"Close all programs and try again.",
  632. curFileName);
  633. else
  634. Status(L"Update failed: Couldn't backup %s "
  635. L"(error %d)",
  636. curFileName, GetLastError());
  637. return false;
  638. }
  639. int error_code;
  640. bool installed_ok;
  641. if (file.patchable) {
  642. error_code = ApplyPatch(
  643. file.tempPath.c_str(),
  644. file.outputPath.c_str());
  645. installed_ok = (error_code == 0);
  646. if (installed_ok) {
  647. BYTE patchedFileHash[BLAKE2_HASH_LENGTH];
  648. if (!CalculateFileHash(file.outputPath.c_str(),
  649. patchedFileHash)) {
  650. Status(L"Update failed: Couldn't "
  651. L"verify integrity of patched %s",
  652. curFileName);
  653. return false;
  654. }
  655. if (memcmp(file.hash, patchedFileHash,
  656. BLAKE2_HASH_LENGTH) != 0) {
  657. Status(L"Update failed: Integrity "
  658. L"check of patched "
  659. L"%s failed",
  660. curFileName);
  661. return false;
  662. }
  663. }
  664. } else {
  665. installed_ok = MyCopyFile(
  666. file.tempPath.c_str(),
  667. file.outputPath.c_str());
  668. error_code = GetLastError();
  669. }
  670. if (!installed_ok) {
  671. int is_sharing_violation =
  672. (error_code == ERROR_SHARING_VIOLATION);
  673. if (is_sharing_violation)
  674. Status(L"Update failed: %s is still in use. "
  675. L"Close all "
  676. L"programs and try again.",
  677. curFileName);
  678. else
  679. Status(L"Update failed: Couldn't update %s "
  680. L"(error %d)",
  681. curFileName,
  682. GetLastError());
  683. return false;
  684. }
  685. file.previousFile = oldFileRenamedPath;
  686. file.state = STATE_INSTALLED;
  687. } else {
  688. if (file.patchable) {
  689. /* Uh oh, we thought we could patch something but it's
  690. * no longer there! */
  691. Status(L"Update failed: Source file %s not found",
  692. file.outputPath.c_str());
  693. return false;
  694. }
  695. /* We may be installing into new folders,
  696. * make sure they exist */
  697. CreateFoldersForPath(file.outputPath.c_str());
  698. bool success = !!MyCopyFile(
  699. file.tempPath.c_str(),
  700. file.outputPath.c_str());
  701. if (!success) {
  702. Status(L"Update failed: Couldn't install %s (error %d)",
  703. file.outputPath.c_str(),
  704. GetLastError());
  705. return false;
  706. }
  707. file.previousFile = L"";
  708. file.state = STATE_INSTALLED;
  709. }
  710. return true;
  711. }
  712. static wchar_t tempPath[MAX_PATH] = {};
  713. #define PATCH_MANIFEST_URL \
  714. L"https://obsproject.com/update_studio/getpatchmanifest"
  715. #define HASH_NULL \
  716. L"0000000000000000000000000000000000000000"
  717. static bool Update(wchar_t *cmdLine)
  718. {
  719. /* ------------------------------------- *
  720. * Check to make sure OBS isn't running */
  721. HANDLE hObsUpdateMutex = OpenMutexW(SYNCHRONIZE, false,
  722. L"OBSStudioUpdateMutex");
  723. if (hObsUpdateMutex) {
  724. HANDLE hWait[2];
  725. hWait[0] = hObsUpdateMutex;
  726. hWait[1] = cancelRequested;
  727. int i = WaitForMultipleObjects(2, hWait, false, INFINITE);
  728. if (i == WAIT_OBJECT_0)
  729. ReleaseMutex(hObsUpdateMutex);
  730. CloseHandle(hObsUpdateMutex);
  731. if (i == WAIT_OBJECT_0 + 1)
  732. return false;
  733. }
  734. if (!WaitForOBS())
  735. return false;
  736. /* ------------------------------------- *
  737. * Init crypt stuff */
  738. CryptProvider hProvider;
  739. if (!CryptAcquireContext(&hProvider, nullptr, MS_ENH_RSA_AES_PROV,
  740. PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
  741. SetDlgItemTextW(hwndMain, IDC_STATUS,
  742. L"Update failed: CryptAcquireContext failure");
  743. return false;
  744. }
  745. ::hProvider = hProvider;
  746. /* ------------------------------------- */
  747. SetDlgItemTextW(hwndMain, IDC_STATUS,
  748. L"Searching for available updates...");
  749. /* ------------------------------------- *
  750. * Check if updating portable build */
  751. bool bIsPortable = false;
  752. if (cmdLine[0]) {
  753. int argc;
  754. LPWSTR *argv = CommandLineToArgvW(cmdLine, &argc);
  755. if (argv) {
  756. for (int i = 0; i < argc; i++) {
  757. if (wcscmp(argv[i], L"Portable") == 0) {
  758. bIsPortable = true;
  759. }
  760. }
  761. LocalFree((HLOCAL)argv);
  762. }
  763. }
  764. /* ------------------------------------- *
  765. * Get config path */
  766. wchar_t lpAppDataPath[MAX_PATH];
  767. lpAppDataPath[0] = 0;
  768. if (bIsPortable) {
  769. GetCurrentDirectory(_countof(lpAppDataPath), lpAppDataPath);
  770. StringCbCat(lpAppDataPath, sizeof(lpAppDataPath), L"\\config");
  771. } else {
  772. CoTaskMemPtr<wchar_t> pOut;
  773. HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData,
  774. KF_FLAG_DEFAULT, nullptr, &pOut);
  775. if (hr != S_OK) {
  776. Status(L"Update failed: Could not determine AppData "
  777. L"location");
  778. return false;
  779. }
  780. StringCbCopy(lpAppDataPath, sizeof(lpAppDataPath), pOut);
  781. StringCbCat(lpAppDataPath, sizeof(lpAppDataPath),
  782. L"\\obs-studio");
  783. }
  784. /* ------------------------------------- *
  785. * Get download path */
  786. wchar_t manifestPath[MAX_PATH];
  787. wchar_t tempDirName[MAX_PATH];
  788. manifestPath[0] = 0;
  789. tempDirName[0] = 0;
  790. StringCbPrintf(manifestPath, sizeof(manifestPath),
  791. L"%s\\updates\\manifest.json", lpAppDataPath);
  792. if (!GetTempPathW(_countof(tempDirName), tempDirName)) {
  793. Status(L"Update failed: Failed to get temp path: %ld",
  794. GetLastError());
  795. return false;
  796. }
  797. if (!GetTempFileNameW(tempDirName, L"obs-studio", 0, tempPath)) {
  798. Status(L"Update failed: Failed to create temp dir name: %ld",
  799. GetLastError());
  800. return false;
  801. }
  802. DeleteFile(tempPath);
  803. CreateDirectory(tempPath, nullptr);
  804. /* ------------------------------------- *
  805. * Load manifest file */
  806. Json root;
  807. {
  808. string manifestFile = QuickReadFile(manifestPath);
  809. if (manifestFile.empty()) {
  810. Status(L"Update failed: Couldn't load manifest file");
  811. return false;
  812. }
  813. json_error_t error;
  814. root = json_loads(manifestFile.c_str(), 0, &error);
  815. if (!root) {
  816. Status(L"Update failed: Couldn't parse update "
  817. L"manifest: %S", error.text);
  818. return false;
  819. }
  820. }
  821. if (!json_is_object(root.get())) {
  822. Status(L"Update failed: Invalid update manifest");
  823. return false;
  824. }
  825. /* ------------------------------------- *
  826. * Parse current manifest update files */
  827. json_t *packages = json_object_get(root, "packages");
  828. size_t packageCount = json_array_size(packages);
  829. for (size_t i = 0; i < packageCount; i++) {
  830. if (!AddPackageUpdateFiles(packages, i, tempPath)) {
  831. Status(L"Failed to process update packages");
  832. return false;
  833. }
  834. }
  835. /* ------------------------------------- *
  836. * Exit if updates already installed */
  837. if (!updates.size()) {
  838. Status(L"All available updates are already installed.");
  839. return true;
  840. }
  841. /* ------------------------------------- *
  842. * Generate file hash json */
  843. Json files(json_array());
  844. for (update_t &update : updates) {
  845. wchar_t whash_string[BLAKE2_HASH_STR_LENGTH];
  846. char hash_string[BLAKE2_HASH_STR_LENGTH];
  847. char outputPath[MAX_PATH];
  848. if (!update.has_hash)
  849. continue;
  850. /* check hash */
  851. HashToString(update.my_hash, whash_string);
  852. if (wcscmp(whash_string, HASH_NULL) == 0)
  853. continue;
  854. if (!WideToUTF8Buf(hash_string, whash_string))
  855. continue;
  856. if (!WideToUTF8Buf(outputPath, update.basename.c_str()))
  857. continue;
  858. string package_path;
  859. package_path = update.packageName;
  860. package_path += "/";
  861. package_path += outputPath;
  862. json_t *obj = json_object();
  863. json_object_set(obj, "name", json_string(package_path.c_str()));
  864. json_object_set(obj, "hash", json_string(hash_string));
  865. json_array_append_new(files, obj);
  866. }
  867. /* ------------------------------------- *
  868. * Send file hashes */
  869. string newManifest;
  870. if (json_array_size(files) > 0) {
  871. char *post_body = json_dumps(files, JSON_COMPACT);
  872. int responseCode;
  873. int len = (int)strlen(post_body);
  874. uLong compressSize = compressBound(len);
  875. string compressedJson;
  876. compressedJson.resize(compressSize);
  877. compress2((Bytef*)&compressedJson[0], &compressSize,
  878. (const Bytef*)post_body, len,
  879. Z_BEST_COMPRESSION);
  880. compressedJson.resize(compressSize);
  881. bool success = !!HTTPPostData(PATCH_MANIFEST_URL,
  882. (BYTE *)&compressedJson[0],
  883. (int)compressedJson.size(),
  884. L"Accept-Encoding: gzip", &responseCode,
  885. newManifest);
  886. free(post_body);
  887. if (!success)
  888. return false;
  889. if (responseCode != 200) {
  890. Status(L"Update failed: HTTP/%d while trying to "
  891. L"download patch manifest",
  892. responseCode);
  893. return false;
  894. }
  895. } else {
  896. newManifest = "[]";
  897. }
  898. /* ------------------------------------- *
  899. * Parse new manifest */
  900. json_error_t error;
  901. root = json_loads(newManifest.c_str(), 0, &error);
  902. if (!root) {
  903. Status(L"Update failed: Couldn't parse patch manifest: %S",
  904. error.text);
  905. return false;
  906. }
  907. if (!json_is_array(root.get())) {
  908. Status(L"Update failed: Invalid patch manifest");
  909. return false;
  910. }
  911. packageCount = json_array_size(root);
  912. for (size_t i = 0; i < packageCount; i++) {
  913. json_t *patch = json_array_get(root, i);
  914. if (!json_is_object(patch)) {
  915. Status(L"Update failed: Invalid patch manifest");
  916. return false;
  917. }
  918. json_t *name_json = json_object_get(patch, "name");
  919. json_t *hash_json = json_object_get(patch, "hash");
  920. json_t *source_json = json_object_get(patch, "source");
  921. json_t *size_json = json_object_get(patch, "size");
  922. if (!json_is_string(name_json))
  923. continue;
  924. if (!json_is_string(hash_json))
  925. continue;
  926. if (!json_is_string(source_json))
  927. continue;
  928. if (!json_is_integer(size_json))
  929. continue;
  930. const char *name = json_string_value(name_json);
  931. const char *hash = json_string_value(hash_json);
  932. const char *source = json_string_value(source_json);
  933. int size = (int)json_integer_value(size_json);
  934. UpdateWithPatchIfAvailable(name, hash, source, size);
  935. }
  936. /* ------------------------------------- *
  937. * Download Updates */
  938. if (!RunDownloadWorkers(2))
  939. return false;
  940. if (completedUpdates != updates.size()) {
  941. Status(L"Update failed to download all files.");
  942. return false;
  943. }
  944. /* ------------------------------------- *
  945. * Install updates */
  946. for (update_t &update : updates) {
  947. if (!UpdateFile(update))
  948. return false;
  949. }
  950. /* If we get here, all updates installed successfully so we can purge
  951. * the old versions */
  952. for (update_t &update : updates) {
  953. if (!update.previousFile.empty())
  954. DeleteFile(update.previousFile.c_str());
  955. /* We delete here not above in case of duplicate hashes */
  956. if (!update.tempPath.empty())
  957. DeleteFile(update.tempPath.c_str());
  958. }
  959. Status(L"Update complete.");
  960. SetDlgItemText(hwndMain, IDC_BUTTON, L"Launch OBS");
  961. return true;
  962. }
  963. static DWORD WINAPI UpdateThread(void *arg)
  964. {
  965. wchar_t *cmdLine = (wchar_t *)arg;
  966. bool success = Update(cmdLine);
  967. if (!success) {
  968. /* This handles deleting temp files and rolling back and
  969. * partially installed updates */
  970. CleanupPartialUpdates();
  971. if (tempPath[0])
  972. RemoveDirectory(tempPath);
  973. if (WaitForSingleObject(cancelRequested, 0) == WAIT_OBJECT_0)
  974. Status(L"Update aborted.");
  975. SendDlgItemMessage(hwndMain, IDC_PROGRESS, PBM_SETSTATE,
  976. PBST_ERROR, 0);
  977. SetDlgItemText(hwndMain, IDC_BUTTON, L"Exit");
  978. EnableWindow(GetDlgItem(hwndMain, IDC_BUTTON), true);
  979. updateFailed = true;
  980. } else {
  981. if (tempPath[0])
  982. RemoveDirectory(tempPath);
  983. }
  984. if (bExiting)
  985. ExitProcess(success);
  986. return 0;
  987. }
  988. static void CancelUpdate(bool quit)
  989. {
  990. if (WaitForSingleObject(updateThread, 0) != WAIT_OBJECT_0) {
  991. bExiting = quit;
  992. SetEvent(cancelRequested);
  993. } else {
  994. PostQuitMessage(0);
  995. }
  996. }
  997. static void LaunchOBS()
  998. {
  999. wchar_t cwd[MAX_PATH];
  1000. wchar_t newCwd[MAX_PATH];
  1001. wchar_t obsPath[MAX_PATH];
  1002. GetCurrentDirectory(_countof(cwd) - 1, cwd);
  1003. StringCbCopy(obsPath, sizeof(obsPath), cwd);
  1004. StringCbCat(obsPath, sizeof(obsPath), is32bit
  1005. ? L"\\bin\\32bit"
  1006. : L"\\bin\\64bit");
  1007. SetCurrentDirectory(obsPath);
  1008. StringCbCopy(newCwd, sizeof(newCwd), obsPath);
  1009. StringCbCat(obsPath, sizeof(obsPath), is32bit
  1010. ? L"\\obs32.exe"
  1011. : L"\\obs64.exe");
  1012. if (!FileExists(obsPath)) {
  1013. StringCbCopy(obsPath, sizeof(obsPath), cwd);
  1014. StringCbCat(obsPath, sizeof(obsPath), L"\\bin\\32bit");
  1015. SetCurrentDirectory(obsPath);
  1016. StringCbCopy(newCwd, sizeof(newCwd), obsPath);
  1017. StringCbCat(obsPath, sizeof(obsPath), L"\\obs32.exe");
  1018. if (!FileExists(obsPath)) {
  1019. /* TODO: give user a message maybe? */
  1020. return;
  1021. }
  1022. }
  1023. SHELLEXECUTEINFO execInfo;
  1024. ZeroMemory(&execInfo, sizeof(execInfo));
  1025. execInfo.cbSize = sizeof(execInfo);
  1026. execInfo.lpFile = obsPath;
  1027. execInfo.lpDirectory = newCwd;
  1028. execInfo.nShow = SW_SHOWNORMAL;
  1029. ShellExecuteEx(&execInfo);
  1030. }
  1031. static INT_PTR CALLBACK UpdateDialogProc(HWND hwnd, UINT message,
  1032. WPARAM wParam, LPARAM lParam)
  1033. {
  1034. switch (message) {
  1035. case WM_INITDIALOG: {
  1036. static HICON hMainIcon = LoadIcon(hinstMain,
  1037. MAKEINTRESOURCE(IDI_ICON1));
  1038. SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hMainIcon);
  1039. SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hMainIcon);
  1040. return true;
  1041. }
  1042. case WM_COMMAND:
  1043. if (LOWORD(wParam) == IDC_BUTTON) {
  1044. if (HIWORD(wParam) == BN_CLICKED) {
  1045. DWORD result = WaitForSingleObject(
  1046. updateThread, 0);
  1047. if (result == WAIT_OBJECT_0) {
  1048. if (updateFailed)
  1049. PostQuitMessage(0);
  1050. else
  1051. PostQuitMessage(1);
  1052. } else {
  1053. EnableWindow((HWND)lParam, false);
  1054. CancelUpdate(false);
  1055. }
  1056. }
  1057. }
  1058. return true;
  1059. case WM_CLOSE:
  1060. CancelUpdate(true);
  1061. return true;
  1062. }
  1063. return false;
  1064. }
  1065. static void RestartAsAdmin(LPWSTR lpCmdLine)
  1066. {
  1067. wchar_t myPath[MAX_PATH];
  1068. if (!GetModuleFileNameW(nullptr, myPath, _countof(myPath) - 1)) {
  1069. return;
  1070. }
  1071. wchar_t cwd[MAX_PATH];
  1072. GetCurrentDirectoryW(_countof(cwd) - 1, cwd);
  1073. SHELLEXECUTEINFO shExInfo = {0};
  1074. shExInfo.cbSize = sizeof(shExInfo);
  1075. shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
  1076. shExInfo.hwnd = 0;
  1077. shExInfo.lpVerb = L"runas"; /* Operation to perform */
  1078. shExInfo.lpFile = myPath; /* Application to start */
  1079. shExInfo.lpParameters = lpCmdLine; /* Additional parameters */
  1080. shExInfo.lpDirectory = cwd;
  1081. shExInfo.nShow = SW_NORMAL;
  1082. shExInfo.hInstApp = 0;
  1083. /* annoyingly the actual elevated updater will disappear behind other
  1084. * windows :( */
  1085. AllowSetForegroundWindow(ASFW_ANY);
  1086. if (ShellExecuteEx(&shExInfo)) {
  1087. DWORD exitCode;
  1088. if (GetExitCodeProcess(shExInfo.hProcess, &exitCode)) {
  1089. if (exitCode == 1) {
  1090. LaunchOBS();
  1091. }
  1092. }
  1093. CloseHandle(shExInfo.hProcess);
  1094. }
  1095. }
  1096. int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
  1097. {
  1098. INITCOMMONCONTROLSEX icce;
  1099. if (!IsAppRunningAsAdminMode()) {
  1100. HANDLE hLowMutex = CreateMutexW(nullptr, true,
  1101. L"OBSUpdaterRunningAsNonAdminUser");
  1102. RestartAsAdmin(lpCmdLine);
  1103. if (hLowMutex) {
  1104. ReleaseMutex(hLowMutex);
  1105. CloseHandle(hLowMutex);
  1106. }
  1107. return 0;
  1108. } else {
  1109. {
  1110. wchar_t cwd[MAX_PATH];
  1111. wchar_t newPath[MAX_PATH];
  1112. GetCurrentDirectoryW(_countof(cwd) - 1, cwd);
  1113. is32bit = wcsstr(cwd, L"bin\\32bit") != nullptr;
  1114. StringCbCat(cwd, sizeof(cwd), L"\\..\\..");
  1115. GetFullPathName(cwd, _countof(newPath), newPath,
  1116. nullptr);
  1117. SetCurrentDirectory(newPath);
  1118. }
  1119. hinstMain = hInstance;
  1120. icce.dwSize = sizeof(icce);
  1121. icce.dwICC = ICC_PROGRESS_CLASS;
  1122. InitCommonControlsEx(&icce);
  1123. hwndMain = CreateDialog(hInstance,
  1124. MAKEINTRESOURCE(IDD_UPDATEDIALOG), nullptr,
  1125. UpdateDialogProc);
  1126. if (!hwndMain) {
  1127. return -1;
  1128. }
  1129. ShowWindow(hwndMain, SW_SHOWNORMAL);
  1130. SetForegroundWindow(hwndMain);
  1131. cancelRequested = CreateEvent(nullptr, true, false, nullptr);
  1132. updateThread = CreateThread(nullptr, 0, UpdateThread,
  1133. lpCmdLine, 0, nullptr);
  1134. MSG msg;
  1135. while (GetMessage(&msg, nullptr, 0, 0)) {
  1136. if (!IsDialogMessage(hwndMain, &msg)) {
  1137. TranslateMessage(&msg);
  1138. DispatchMessage(&msg);
  1139. }
  1140. }
  1141. /* there is no non-elevated process waiting for us if UAC is
  1142. * disabled */
  1143. WinHandle hMutex = OpenMutex(SYNCHRONIZE, false,
  1144. L"OBSUpdaterRunningAsNonAdminUser");
  1145. if (msg.wParam == 1 && !hMutex) {
  1146. LaunchOBS();
  1147. }
  1148. return (int)msg.wParam;
  1149. }
  1150. }