/******************************************************************************** Copyright (C) 2012 Hugh Bailey Richard Stanway This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. ********************************************************************************/ #include "Main.h" #include #include #include HCRYPTPROV hProvider; #pragma pack(push, r1, 1) typedef struct { BLOBHEADER blobheader; RSAPUBKEY rsapubkey; } PUBLICKEYHEADER; #pragma pack(pop, r1) // Hard coded 4096 bit RSA public key for obsproject.com in PEM format const unsigned char obs_pub[] = { 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x43, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x67, 0x45, 0x41, 0x70, 0x63, 0x51, 0x4a, 0x33, 0x57, 0x44, 0x62, 0x43, 0x62, 0x66, 0x6e, 0x4d, 0x75, 0x48, 0x4b, 0x50, 0x77, 0x53, 0x5a, 0x0a, 0x56, 0x36, 0x77, 0x49, 0x2f, 0x67, 0x36, 0x58, 0x50, 0x65, 0x46, 0x36, 0x62, 0x4c, 0x62, 0x72, 0x53, 0x66, 0x71, 0x39, 0x66, 0x53, 0x4e, 0x68, 0x61, 0x47, 0x50, 0x37, 0x55, 0x6e, 0x67, 0x4e, 0x2b, 0x44, 0x46, 0x66, 0x53, 0x36, 0x30, 0x62, 0x35, 0x31, 0x42, 0x2f, 0x58, 0x6e, 0x6a, 0x67, 0x37, 0x51, 0x38, 0x70, 0x35, 0x77, 0x4f, 0x57, 0x34, 0x39, 0x44, 0x46, 0x65, 0x62, 0x33, 0x73, 0x0a, 0x75, 0x65, 0x41, 0x2b, 0x49, 0x42, 0x50, 0x77, 0x6d, 0x71, 0x62, 0x32, 0x46, 0x39, 0x4a, 0x7a, 0x63, 0x71, 0x67, 0x73, 0x6a, 0x74, 0x46, 0x76, 0x63, 0x37, 0x74, 0x4b, 0x4a, 0x6c, 0x70, 0x66, 0x59, 0x6b, 0x42, 0x39, 0x71, 0x67, 0x47, 0x2b, 0x48, 0x4a, 0x54, 0x30, 0x53, 0x50, 0x69, 0x64, 0x66, 0x53, 0x69, 0x63, 0x51, 0x32, 0x6e, 0x79, 0x35, 0x67, 0x6d, 0x37, 0x69, 0x4d, 0x6f, 0x71, 0x0a, 0x35, 0x34, 0x36, 0x6a, 0x69, 0x53, 0x75, 0x41, 0x75, 0x41, 0x63, 0x49, 0x71, 0x74, 0x33, 0x39, 0x37, 0x67, 0x41, 0x47, 0x56, 0x6f, 0x45, 0x46, 0x58, 0x30, 0x58, 0x47, 0x46, 0x41, 0x4c, 0x64, 0x63, 0x38, 0x35, 0x35, 0x2f, 0x34, 0x46, 0x74, 0x61, 0x63, 0x68, 0x79, 0x39, 0x70, 0x59, 0x34, 0x77, 0x58, 0x39, 0x41, 0x59, 0x57, 0x69, 0x50, 0x42, 0x4e, 0x48, 0x52, 0x65, 0x78, 0x45, 0x51, 0x0a, 0x54, 0x46, 0x75, 0x55, 0x73, 0x69, 0x76, 0x44, 0x64, 0x51, 0x6c, 0x50, 0x64, 0x76, 0x6a, 0x79, 0x59, 0x6e, 0x72, 0x63, 0x30, 0x4c, 0x50, 0x2f, 0x4f, 0x71, 0x4a, 0x2f, 0x71, 0x2f, 0x42, 0x58, 0x7a, 0x4f, 0x4b, 0x66, 0x33, 0x69, 0x4e, 0x6b, 0x58, 0x70, 0x72, 0x63, 0x68, 0x50, 0x61, 0x67, 0x52, 0x72, 0x61, 0x43, 0x34, 0x64, 0x41, 0x74, 0x34, 0x62, 0x42, 0x4a, 0x54, 0x34, 0x43, 0x7a, 0x0a, 0x56, 0x6a, 0x66, 0x55, 0x33, 0x69, 0x53, 0x2f, 0x57, 0x74, 0x68, 0x7a, 0x54, 0x50, 0x49, 0x51, 0x34, 0x4b, 0x34, 0x4c, 0x47, 0x57, 0x35, 0x4f, 0x70, 0x4e, 0x2b, 0x46, 0x35, 0x70, 0x67, 0x6d, 0x6f, 0x69, 0x44, 0x4e, 0x30, 0x64, 0x36, 0x4a, 0x35, 0x43, 0x49, 0x47, 0x6d, 0x37, 0x62, 0x5a, 0x78, 0x50, 0x58, 0x61, 0x32, 0x6a, 0x73, 0x64, 0x52, 0x30, 0x6c, 0x31, 0x6a, 0x4d, 0x6a, 0x48, 0x0a, 0x6a, 0x53, 0x52, 0x74, 0x68, 0x73, 0x59, 0x33, 0x62, 0x33, 0x31, 0x75, 0x73, 0x5a, 0x34, 0x43, 0x64, 0x6b, 0x6a, 0x48, 0x4f, 0x68, 0x64, 0x76, 0x32, 0x67, 0x4a, 0x43, 0x4d, 0x52, 0x65, 0x71, 0x41, 0x67, 0x78, 0x4a, 0x77, 0x54, 0x39, 0x2f, 0x48, 0x6a, 0x69, 0x6c, 0x57, 0x67, 0x4b, 0x72, 0x51, 0x72, 0x42, 0x34, 0x31, 0x50, 0x46, 0x4d, 0x4e, 0x71, 0x47, 0x42, 0x66, 0x4c, 0x4b, 0x61, 0x0a, 0x32, 0x6b, 0x69, 0x50, 0x74, 0x58, 0x48, 0x30, 0x35, 0x46, 0x50, 0x52, 0x35, 0x71, 0x6f, 0x56, 0x79, 0x42, 0x6b, 0x65, 0x77, 0x63, 0x45, 0x65, 0x75, 0x41, 0x35, 0x73, 0x6b, 0x78, 0x5a, 0x31, 0x67, 0x33, 0x7a, 0x4a, 0x30, 0x67, 0x48, 0x35, 0x6f, 0x76, 0x51, 0x64, 0x42, 0x6a, 0x54, 0x34, 0x59, 0x68, 0x32, 0x4a, 0x37, 0x50, 0x39, 0x68, 0x7a, 0x76, 0x73, 0x62, 0x6a, 0x39, 0x76, 0x56, 0x0a, 0x4a, 0x75, 0x68, 0x44, 0x6e, 0x45, 0x34, 0x52, 0x38, 0x2f, 0x65, 0x38, 0x45, 0x74, 0x61, 0x66, 0x53, 0x41, 0x68, 0x44, 0x53, 0x32, 0x47, 0x74, 0x38, 0x6c, 0x77, 0x62, 0x4f, 0x52, 0x64, 0x43, 0x55, 0x48, 0x4d, 0x48, 0x59, 0x49, 0x57, 0x4e, 0x47, 0x59, 0x30, 0x46, 0x6e, 0x61, 0x4d, 0x79, 0x41, 0x74, 0x72, 0x37, 0x54, 0x75, 0x48, 0x2f, 0x53, 0x43, 0x44, 0x77, 0x4c, 0x6a, 0x6a, 0x52, 0x0a, 0x6e, 0x41, 0x54, 0x64, 0x30, 0x44, 0x43, 0x30, 0x48, 0x49, 0x74, 0x59, 0x31, 0x59, 0x32, 0x53, 0x49, 0x43, 0x7a, 0x42, 0x61, 0x41, 0x66, 0x4c, 0x41, 0x6a, 0x78, 0x79, 0x38, 0x67, 0x58, 0x61, 0x49, 0x53, 0x6c, 0x52, 0x69, 0x79, 0x6b, 0x37, 0x70, 0x4b, 0x5a, 0x62, 0x4b, 0x6e, 0x61, 0x67, 0x6b, 0x35, 0x4c, 0x49, 0x6f, 0x63, 0x52, 0x69, 0x73, 0x79, 0x36, 0x4e, 0x6e, 0x76, 0x41, 0x31, 0x0a, 0x59, 0x66, 0x56, 0x7a, 0x4f, 0x69, 0x6a, 0x46, 0x4b, 0x55, 0x72, 0x48, 0x6c, 0x76, 0x76, 0x38, 0x38, 0x59, 0x39, 0x41, 0x32, 0x43, 0x77, 0x78, 0x6d, 0x77, 0x4c, 0x78, 0x34, 0x7a, 0x78, 0x4a, 0x73, 0x4f, 0x77, 0x74, 0x68, 0x75, 0x42, 0x57, 0x79, 0x76, 0x31, 0x59, 0x64, 0x34, 0x30, 0x4d, 0x68, 0x67, 0x71, 0x54, 0x75, 0x72, 0x7a, 0x39, 0x51, 0x51, 0x6e, 0x39, 0x32, 0x6e, 0x6f, 0x72, 0x0a, 0x46, 0x46, 0x58, 0x2f, 0x58, 0x54, 0x30, 0x6e, 0x65, 0x67, 0x74, 0x4a, 0x4f, 0x56, 0x44, 0x67, 0x49, 0x41, 0x30, 0x76, 0x49, 0x61, 0x6b, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a }; const unsigned int obs_pub_len = 800; VOID HashToString (BYTE *in, TCHAR *out) { const char alphabet[] = "0123456789abcdef"; for (int i = 0; i != 20; ++i) { out[2*i] = alphabet[in[i] / 16]; out[2*i + 1] = alphabet[in[i] % 16]; } out[40] = 0; } VOID HexToByteArray(TCHAR *hexStr, int hexLen, BYTE *out) { TCHAR ptr[3]; ptr[2] = 0; for (int i = 0; i < hexLen; i += 2) { ptr[0] = hexStr[i]; ptr[1] = hexStr[i + 1]; out[i / 2] = (BYTE)wcstoul(ptr, NULL, 16); } } VOID GenerateGUID(String &strGUID) { BYTE junk[20]; if (!CryptGenRandom(hProvider, sizeof(junk), junk)) return; strGUID.SetLength(41); HashToString(junk, strGUID.Array()); } BOOL CalculateFileHash (TCHAR *path, BYTE *hash) { HCRYPTHASH hHash; if (!CryptCreateHash(hProvider, CALG_SHA1, 0, 0, &hHash)) return FALSE; XFile file; if (file.Open(path, XFILE_READ, OPEN_EXISTING)) { BYTE buff[65536]; for (;;) { DWORD read = file.Read(buff, sizeof(buff)); if (!read) break; if (!CryptHashData(hHash, buff, read, 0)) { CryptDestroyHash(hHash); file.Close(); return FALSE; } } } else { CryptDestroyHash(hHash); return FALSE; } file.Close(); DWORD hashLength = 20; if (!CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLength, 0)) return FALSE; CryptDestroyHash(hHash); return TRUE; } BOOL VerifyDigitalSignature(BYTE *buff, DWORD len, BYTE *signature, DWORD signatureLen) { // ASN of PEM public key BYTE binaryKey[1024]; DWORD binaryKeyLen = sizeof(binaryKey); // Windows X509 public key info from ASN CERT_PUBLIC_KEY_INFO *pbPublicPBLOB = NULL; DWORD iPBLOBSize; // RSA BLOB info from X509 public key PUBLICKEYHEADER *rsaPublicBLOB = NULL; DWORD rsaPublicBLOBSize; // Handle to public key HCRYPTKEY keyOut = NULL; // Handle to hash context HCRYPTHASH hHash = NULL; // Signature in little-endian format BYTE *reversedSignature = NULL; BOOL ret = FALSE; if (!CryptStringToBinaryA((LPCSTR)obs_pub, obs_pub_len, CRYPT_STRING_BASE64HEADER, binaryKey, &binaryKeyLen, NULL, NULL)) goto cleanup; if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, binaryKey, binaryKeyLen, CRYPT_ENCODE_ALLOC_FLAG, NULL, &pbPublicPBLOB, &iPBLOBSize)) goto cleanup; if (!CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB, pbPublicPBLOB->PublicKey.pbData, pbPublicPBLOB->PublicKey.cbData, CRYPT_ENCODE_ALLOC_FLAG, NULL, &rsaPublicBLOB, &rsaPublicBLOBSize)) goto cleanup; if (!CryptImportKey(hProvider, (const BYTE *)rsaPublicBLOB, rsaPublicBLOBSize, NULL, 0, &keyOut)) goto cleanup; if (!CryptCreateHash(hProvider, CALG_SHA_512, 0, 0, &hHash)) goto cleanup; if (!CryptHashData(hHash, buff, len, 0)) goto cleanup; // Windows requires signature in little-endian. Every other crypto provider is big-endian of course. reversedSignature = (BYTE *)HeapAlloc(GetProcessHeap(), 0, signatureLen); for (DWORD i = 0; i < signatureLen; i++) reversedSignature[i] = signature[signatureLen - i - 1]; if (!CryptVerifySignature(hHash, reversedSignature, signatureLen, keyOut, NULL, 0)) { DWORD i = GetLastError(); goto cleanup; } ret = TRUE; cleanup: if (keyOut != NULL) CryptDestroyKey(keyOut); if (hHash != NULL) CryptDestroyHash(hHash); if (pbPublicPBLOB) LocalFree(pbPublicPBLOB); if (rsaPublicBLOB) LocalFree(rsaPublicBLOB); if (reversedSignature) HeapFree(GetProcessHeap(), 0, reversedSignature); return ret; } BOOL CheckSignature(TCHAR *path, TCHAR *hexSignature, int signatureLength) { BYTE *fileContents = NULL; BYTE *signature = NULL; BOOL ret = FALSE; XFile updateFile; if (signatureLength == 0 || signatureLength > 0xFFFF || (signatureLength & 1) != 0) { Log(TEXT("WARNING: Missing or invalid signature for %s."), path); goto cleanup; } signature = (BYTE *)Allocate(signatureLength); // Convert TCHAR signature to byte array HexToByteArray(hexSignature, signatureLength, signature); signatureLength /= 2; if (updateFile.Open(path, XFILE_READ, XFILE_OPENEXISTING)) { DWORD fileLength = (DWORD)updateFile.GetFileSize(); fileContents = (BYTE *)Allocate(fileLength); if (updateFile.Read(fileContents, fileLength) != fileLength) goto cleanup; if (!VerifyDigitalSignature(fileContents, fileLength, signature, signatureLength)) { Log(TEXT("WARNING: Signature check failed for %s."), path); goto cleanup; } updateFile.Close(); } else { goto cleanup; } ret = TRUE; cleanup: if (fileContents) Free(fileContents); if (signature) Free(signature); return ret; } /* required defines for archive based updating: #define MANIFEST_WITH_ARCHIVES 1 #define MANIFEST_PATH "/updates/org.catchexception.builds.xconfig" #define MANIFEST_URL "https://builds.catchexception.org/update.json" #define UPDATER_PATH "/updates/org.catchexception.builds.updater.exe" #define UPDATE_CHANNEL "master" */ #ifndef MANIFEST_WITH_ARCHIVES #define MANIFEST_WITH_ARCHIVES 0 #endif #ifndef MANIFEST_PATH #define MANIFEST_PATH "\\updates\\packages.xconfig" #endif #ifndef MANIFEST_URL #define MANIFEST_URL "https://obsproject.com/update/packages.xconfig" #endif #ifndef UPDATER_PATH #define UPDATER_PATH "\\updates\\updater.exe" #endif BOOL FetchUpdaterModule(String const &url, String const &hash=String()) { int responseCode; TCHAR updateFilePath[MAX_PATH]; BYTE updateFileHash[20]; TCHAR extraHeaders[256]; tsprintf_s (updateFilePath, _countof(updateFilePath)-1, TEXT("%s") TEXT(UPDATER_PATH), lpAppDataPath); if (CalculateFileHash(updateFilePath, updateFileHash)) { TCHAR hashString[41]; HashToString(updateFileHash, hashString); if (hash.Compare(hashString)) return true; tsprintf_s (extraHeaders, _countof(extraHeaders)-1, TEXT("If-None-Match: %s"), hashString); } else extraHeaders[0] = 0; TCHAR hexSignature[4096]; DWORD signatureLength = sizeof(hexSignature); if (HTTPGetFile(url, updateFilePath, extraHeaders, &responseCode, hexSignature, &signatureLength)) { if (responseCode != 200 && responseCode != 304) return FALSE; // A new file must be digitally signed. if (responseCode == 200) { if (!CheckSignature(updateFilePath, hexSignature, signatureLength)) { DeleteFile(updateFilePath); return FALSE; } } #if MANIFEST_WITH_ARCHIVES if (!CalculateFileHash(updateFilePath, updateFileHash)) return false; TCHAR hashString[41]; HashToString(updateFileHash, hashString); if (!hash.Compare(hashString)) return false; #endif } return TRUE; } BOOL IsSafePath (CTSTR path) { const TCHAR *p; p = path; if (!*p) return TRUE; if (!isalnum(*p)) return FALSE; while (*p) { if (*p == '.' || *p == '\\') return FALSE; p++; } return TRUE; } #if MANIFEST_WITH_ARCHIVES bool ParseUpdateArchiveManifest(TCHAR *path, BOOL *updatesAvailable, String &description) { XConfig manifest; *updatesAvailable = FALSE; if (!manifest.Open(path)) return false; XElement *root = manifest.GetRootElement(); XElement *updater = root->GetElement(L"updater"); if (!updater) return false; String updaterURL = updater->GetString(L"url"); if (updaterURL.IsEmpty()) return false; String updaterHash = updater->GetString(L"sha1"); XElement *channel = root->GetElement(TEXT(UPDATE_CHANNEL)); if (!channel) return false; XElement *platform = channel->GetElement( #ifdef _WIN64 L"win64" #else L"win" #endif ); if (!platform) return false; String version = platform->GetString(TEXT("version")); if (version.Compare(TEXT(OBS_VERSION_SUFFIX))) return true; String url = platform->GetString(L"url"); if (url.IsEmpty()) return false; description << "Open Broadcaster Software" << version << L"\n"; if (!FetchUpdaterModule(updaterURL, updaterHash)) return false; *updatesAvailable = TRUE; return true; } #endif bool ParseUpdateManifest (TCHAR *path, BOOL *updatesAvailable, String &description) { #if MANIFEST_WITH_ARCHIVES return ParseUpdateArchiveManifest(path, updatesAvailable, description); #else XConfig manifest; XElement *root; if (!manifest.Open(path)) return FALSE; root = manifest.GetRootElement(); DWORD numPackages = root->NumElements(); DWORD totalUpdatableFiles = 0; int priority, bestPriority = 999; for (DWORD i = 0; i < numPackages; i++) { XElement *package; package = root->GetElementByID(i); CTSTR packageName = package->GetName(); //find out if this package is relevant to us String platform = package->GetString(TEXT("platform")); if (!platform) continue; if (scmp(platform, TEXT("all"))) { #ifndef _WIN64 if (scmp(platform, TEXT("Win32"))) continue; #else if (scmp(platform, TEXT("Win64"))) continue; #endif } //what is it? String name = package->GetString(TEXT("name")); String version = package->GetString(TEXT("version")); //figure out where the files belong XDataItem *pathElement = package->GetDataItem(TEXT("path")); if (!pathElement) continue; CTSTR path = pathElement->GetData(); if (path == NULL) path = TEXT(""); if (!IsSafePath(path)) continue; priority = package->GetInt(TEXT("priority"), 999); //get the file list for this package XElement *files = package->GetElement(TEXT("files")); if (!files) continue; DWORD numFiles = files->NumElements(); DWORD numUpdatableFiles = 0; for (DWORD j = 0; j < numFiles; j++) { XElement *file = files->GetElementByID(j); String hash = file->GetString(TEXT("hash")); if (!hash || hash.Length() != 40) continue; String fileName = file->GetName(); if (!fileName) continue; if (!IsSafeFilename(fileName)) continue; String filePath; filePath << path; filePath << fileName; if (OSFileExists(filePath)) { BYTE fileHash[20]; TCHAR fileHashString[41]; if (!CalculateFileHash(filePath, fileHash)) continue; HashToString(fileHash, fileHashString); if (!scmp(fileHashString, hash)) continue; } numUpdatableFiles++; } if (numUpdatableFiles) { if (version.Length()) description << name << TEXT(" (") << version << TEXT(")\r\n"); else description << name << TEXT("\r\n"); if (priority < bestPriority) bestPriority = priority; } totalUpdatableFiles += numUpdatableFiles; numUpdatableFiles = 0; } manifest.Close(); if (totalUpdatableFiles) { if (!FetchUpdaterModule(L"https://obsproject.com/update/updater.exe")) return FALSE; } if (bestPriority <= 5) *updatesAvailable = TRUE; else *updatesAvailable = FALSE; return TRUE; #endif } DWORD WINAPI CheckUpdateThread (VOID *arg) { int responseCode; TCHAR extraHeaders[256]; BYTE manifestHash[20]; TCHAR manifestPath[MAX_PATH]; BOOL updatesAvailable = false; BOOL notify = (BOOL)arg; tsprintf_s (manifestPath, _countof(manifestPath)-1, TEXT("%s") TEXT(MANIFEST_PATH), lpAppDataPath); if (!CryptAcquireContext(&hProvider, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { Log (TEXT("Updater: CryptAcquireContext failed: %08x"), GetLastError()); return 1; } extraHeaders[0] = 0; if (CalculateFileHash(manifestPath, manifestHash)) { TCHAR hashString[41]; HashToString(manifestHash, hashString); tsprintf_s (extraHeaders, _countof(extraHeaders)-1, TEXT("If-None-Match: %s"), hashString); } //this is an arbitrary random number that we use to count the number of unique OBS installations //and is not associated with any kind of identifiable information String strGUID = GlobalConfig->GetString(TEXT("General"), TEXT("InstallGUID")); if (strGUID.IsEmpty()) { GenerateGUID(strGUID); if (strGUID.IsValid()) GlobalConfig->SetString(TEXT("General"), TEXT("InstallGUID"), strGUID); } if (strGUID.IsValid()) { if (extraHeaders[0]) scat(extraHeaders, TEXT("\n")); scat(extraHeaders, TEXT("X-OBS-GUID: ")); scat(extraHeaders, strGUID); } TCHAR hexSignature[4096]; DWORD signatureLength = sizeof(hexSignature); if (HTTPGetFile(TEXT(MANIFEST_URL), manifestPath, extraHeaders, &responseCode, hexSignature, &signatureLength)) { if (responseCode == 200 || responseCode == 304) { String updateInfo; // A new file must be digitally signed. if (responseCode == 200) { if (!CheckSignature(manifestPath, hexSignature, signatureLength)) { DeleteFile(manifestPath); return FALSE; } } updateInfo = Str("Updater.NewUpdates"); if (ParseUpdateManifest(manifestPath, &updatesAvailable, updateInfo)) { if (updatesAvailable) { updateInfo << TEXT("\r\n") << Str("Updater.DownloadNow"); if (OBSMessageBox (NULL, updateInfo.Array(), Str("Updater.UpdatesAvailable"), MB_ICONQUESTION|MB_YESNO) == IDYES) { if (App->IsRunning()) { if (OBSMessageBox (NULL, Str("Updater.RunningWarning"), NULL, MB_ICONEXCLAMATION|MB_YESNO) == IDNO) goto abortUpdate; } TCHAR updateFilePath[MAX_PATH]; TCHAR cwd[MAX_PATH]; GetModuleFileName(NULL, cwd, _countof(cwd)-1); TCHAR *p = srchr(cwd, '\\'); if (p) *p = 0; tsprintf_s (updateFilePath, _countof(updateFilePath)-1, TEXT("%s") TEXT(UPDATER_PATH), lpAppDataPath); //note, can't use CreateProcess to launch as admin. SHELLEXECUTEINFO execInfo; zero(&execInfo, sizeof(execInfo)); execInfo.cbSize = sizeof(execInfo); execInfo.lpFile = updateFilePath; #ifndef UPDATE_CHANNEL #define UPDATE_ARG_SUFFIX L"" #else #define UPDATE_ARG_SUFFIX L" " TEXT(UPDATE_CHANNEL) #endif #ifndef _WIN64 if (bIsPortable) execInfo.lpParameters = L"Win32" UPDATE_ARG_SUFFIX L" Portable"; else execInfo.lpParameters = L"Win32" UPDATE_ARG_SUFFIX; #else if (bIsPortable) execInfo.lpParameters = L"Win64" UPDATE_ARG_SUFFIX L" Portable"; else execInfo.lpParameters = L"Win64" UPDATE_ARG_SUFFIX; #endif execInfo.lpDirectory = cwd; execInfo.nShow = SW_SHOWNORMAL; if (!ShellExecuteEx (&execInfo)) { AppWarning(TEXT("Can't launch updater '%s': %d"), updateFilePath, GetLastError()); goto abortUpdate; } //force OBS to perform another update check immediately after updating in case of issues //with the new version GlobalConfig->SetInt(TEXT("General"), OBS_CONFIG_UPDATE_KEY, 0); //since we're in a separate thread we can't just PostQuitMessage ourselves SendMessage(hwndMain, WM_CLOSE, 0, 0); } } } } } if (notify && !updatesAvailable) OBSMessageBox (hwndMain, Str("Updater.NoUpdatesAvailable"), NULL, MB_ICONINFORMATION); abortUpdate: CryptReleaseContext(hProvider, 0); return 0; }