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