一,要实现的效果
- 版本更新后发布最新的OBS版本 到阿里云
- 用户端打开OBS时,强制让用户从阿里云更新最新版本
二,OBS升级 底层逻辑
三,修改的地方
win-update.cpp
//升级 线程
void AutoUpdateThread::run()
try {long responseCode;vector<string> extraHeaders;string text;string error;string signature;CryptProvider localProvider;BYTE manifestHash[BLAKE2_HASH_LENGTH];bool updatesAvailable = false;bool success;struct FinishedTrigger {inline ~FinishedTrigger(){QMetaObject::invokeMethod(App()->GetMainWindow(),"updateCheckFinished"); }} finishedTrigger;BPtr<char> manifestPath = GetConfigPathPtr("obs-studio\\updates\\manifest.json");auto ActiveOrGameCaptureLocked = [this] (){if (obs_video_active()) {if (manualUpdate)info(QTStr("Updater.Running.Title"),QTStr("Updater.Running.Text"));return true;}if (IsGameCaptureInUse()) {if (manualUpdate)info(QTStr("Updater.GameCaptureActive.Title"),QTStr("Updater.GameCaptureActive.Text"));return true;}return false;};/* ----------------------------------- ** warn if running or gc locked */if (ActiveOrGameCaptureLocked())return;/* ----------------------------------- ** create signature provider */if (!CryptAcquireContext(&localProvider,nullptr,MS_ENH_RSA_AES_PROV,PROV_RSA_AES,CRYPT_VERIFYCONTEXT))throw strprintf("CryptAcquireContext failed: %lu",GetLastError());provider = localProvider;/* ----------------------------------- ** avoid downloading manifest again */if (CalculateFileHash(manifestPath, manifestHash)) {char hashString[BLAKE2_HASH_STR_LENGTH];HashToString(manifestHash, hashString);string header = "If-None-Match: ";header += hashString;extraHeaders.push_back(move(header));}/* ----------------------------------- ** get current install GUID */string guid = GetProgramGUID();if (!guid.empty()) {string header = "X-OBS2-GUID: ";header += guid;extraHeaders.push_back(move(header));}/**************************************************************************/success = GetRemoteFileFromAliyun("updates/config/manifest.json", manifestPath);/* ----------------------------------- ** get manifest from server *///success = GetRemoteFile(WIN_MANIFEST_URL, text, error, &responseCode,/* nullptr, nullptr, extraHeaders, &signature);if (!success || (responseCode != 200 && responseCode != 304)) {if (responseCode == 404)return;throw strprintf("Failed to fetch manifest file: %s", error.c_str());}*//* ----------------------------------- ** verify file signature *//* a new file must be digitally signed 校验数字签名 *//*if (responseCode == 200) {success = CheckDataSignature(text, "manifest",signature.data(), signature.size());if (!success)throw string("Invalid manifest signature");}*//**************************************************************************//* ----------------------------------- ** write or load manifest 读取mainfest文件 */ //if (/*responseCode == 200*/success) {// if (!QuickWriteFile(manifestPath, text.data(), text.size()))// throw strprintf("Could not write file '%s'",// manifestPath.Get());//} else {if (!QuickReadFile(manifestPath, text))throw strprintf("Could not read file '%s'",manifestPath.Get());//}/* ----------------------------------- ** check manifest for update */string notes;int updateVer = 0;success = ParseUpdateManifest(text.c_str(), &updatesAvailable, notes,updateVer);//比对版本 判断是否更新if (!success)throw string("Failed to parse manifest");if (!updatesAvailable) {//if (manualUpdate)// info(QTStr("Updater.NoUpdatesAvailable.Title"),// QTStr("Updater.NoUpdatesAvailable.Text"));return;}/* ----------------------------------- ** skip this version if set to skip */int skipUpdateVer = config_get_int(GetGlobalConfig(), "General","SkipUpdateVersion");if (!manualUpdate && updateVer == skipUpdateVer)return;/* ----------------------------------- ** warn again if running or gc locked */if (ActiveOrGameCaptureLocked())return;/* ----------------------------------- ** fetch updater module 获取updater.exe *///if (!FetchUpdaterModule(WIN_UPDATER_URL))// return;//qxlBPtr<char> updateFilePath = GetConfigPathPtr("obs-studio\\updates\\libcurl.dll");if (!GetRemoteFileFromAliyun("updates/config/libcurl.dll", updateFilePath))return;updateFilePath = GetConfigPathPtr("obs-studio\\updates\\libeay32.dll");if (!GetRemoteFileFromAliyun("updates/config/libeay32.dll", updateFilePath))return;updateFilePath = GetConfigPathPtr("obs-studio\\updates\\ssleay32.dll");if (!GetRemoteFileFromAliyun("updates/config/ssleay32.dll", updateFilePath))return;updateFilePath = GetConfigPathPtr("obs-studio\\updates\\zlibwapi.dll");if (!GetRemoteFileFromAliyun("updates/config/zlibwapi.dll", updateFilePath))return;updateFilePath = GetConfigPathPtr("obs-studio\\updates\\updater.exe");if (!GetRemoteFileFromAliyun("updates/config/updater.exe", updateFilePath))return;/* ----------------------------------- ** query user for update *///不询问用户,直接下载更新/*int queryResult = queryUpdate(manualUpdate, notes.c_str());if (queryResult == OBSUpdate::No) {if (!manualUpdate) {long long t = (long long)time(nullptr);config_set_int(GetGlobalConfig(), "General","LastUpdateCheck", t);}return;} else if (queryResult == OBSUpdate::Skip) {config_set_int(GetGlobalConfig(), "General","SkipUpdateVersion", updateVer);return;}*//* ----------------------------------- ** get working dir */wchar_t cwd[MAX_PATH];GetModuleFileNameW(nullptr, cwd, _countof(cwd) - 1);wchar_t *p = wcsrchr(cwd, '\\');if (p)*p = 0;/* ----------------------------------- ** execute updater */ //确认对话框开始更新//qxl//BPtr<char> updateFilePath = GetConfigPathPtr(//"obs-studio\\updates\\updater.exe");BPtr<wchar_t> wUpdateFilePath;size_t size = os_utf8_to_wcs_ptr(updateFilePath, 0, &wUpdateFilePath);if (!size)throw string("Could not convert updateFilePath to wide");/* note, can't use CreateProcess to launch as admin. */SHELLEXECUTEINFO execInfo = {};execInfo.cbSize = sizeof(execInfo);execInfo.lpFile = wUpdateFilePath;
#ifndef UPDATE_CHANNEL
#define UPDATE_ARG_SUFFIX L""
#else
#define UPDATE_ARG_SUFFIX UPDATE_CHANNEL
#endifif (App()->IsPortableMode())execInfo.lpParameters = UPDATE_ARG_SUFFIX L" Portable";elseexecInfo.lpParameters = UPDATE_ARG_SUFFIX;execInfo.lpDirectory = cwd;execInfo.nShow = SW_SHOWNORMAL;//执行 updater.exe 文件if (!ShellExecuteEx(&execInfo)) {QString msg = QTStr("Updater.FailedToLaunch");info(msg, msg);throw strprintf("Can't launch updater '%s': %d",updateFilePath.Get(), GetLastError());}/* force OBS to perform another update check immediately after updating* in case of issues with the new version */config_set_int(GetGlobalConfig(), "General", "LastUpdateCheck", 0);config_set_int(GetGlobalConfig(), "General", "SkipUpdateVersion", 0);config_set_string(GetGlobalConfig(), "General", "InstallGUID",guid.c_str());QMetaObject::invokeMethod(App()->GetMainWindow(), "close");} catch (string text) {blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
}
update.cpp
//程序入口
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
{//解析参数 生成json文件LPWSTR *szArgList;int argCount;szArgList = CommandLineToArgvW(GetCommandLine(), &argCount);if (szArgList == NULL){MessageBox(NULL, L"Unable to parse command line", L"Error", MB_OK);return 10;}//for (int i = 0; i < argCount; i++)//{// MessageBox(NULL, szArgList[i], L"Arglist contents", MB_OK);//}bool isJson = false;if (argCount == 3 ) {char *parm = ConvertLPWSTRToLPSTR(szArgList[1]);if (string(parm) == "-json") {string path = ConvertLPWSTRToLPSTR(szArgList[2]);GenerateJson(path);isJson = true;}}LocalFree(szArgList);if (isJson)return 10;INITCOMMONCONTROLSEX icce;if (!HasElevation()) { //管理员启动 则不会进入HANDLE hLowMutex = CreateMutexW(nullptr, true,L"OBSUpdaterRunningAsNonAdminUser");RestartAsAdmin(lpCmdLine);//管理员用户启动 updater.exeif (hLowMutex) {ReleaseMutex(hLowMutex);CloseHandle(hLowMutex);}return 0;} else{{wchar_t cwd[MAX_PATH];wchar_t newPath[MAX_PATH];GetCurrentDirectoryW(_countof(cwd) - 1, cwd);is32bit = wcsstr(cwd, L"bin\\32bit") != nullptr;StringCbCat(cwd, sizeof(cwd), L"\\..\\..");GetFullPathName(cwd, _countof(newPath), newPath,nullptr);SetCurrentDirectory(newPath);}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();}return (int)msg.wParam;}
}
manifest.json 文件生成
void GenerateJson(string path) {// 遍历文件runDirPath = path;//"G:\\obs-studio\\obs-23.2.1\\build\\rundir\\Debug\\";char* p = const_cast<char*>(runDirPath.c_str());strcat(p, "*.*");fileSearch(p, 0);//生成hash值std::map<string, string> fileHastMap;if (fileSizeMap.size() > 0) {for (auto it = fileSizeMap.begin(); it != fileSizeMap.end(); ++it) {uint8_t existingHash[BLAKE2_HASH_LENGTH];CalculateFileHash(char2wchar(it->first.c_str()), existingHash);char hashString[BLAKE2_HASH_STR_LENGTH];HashToStringTwo(existingHash, hashString);fileHastMap[it->first] = string(hashString);}}//生成json文件json_t *obj = json_object();json_object_set(obj, "notes", json_string("test"));json_object_set(obj, "version_major", json_integer(0));json_object_set(obj, "version_minor", json_integer(0));json_object_set(obj, "version_patch", json_integer(1));json_t *objPackages = json_array();json_object_set(obj, "packages", objPackages);//corejson_t *objCore = json_object();json_t *objFiles = json_array();if (fileSizeMap.size() > 0) {for (auto it = fileSizeMap.begin(); it != fileSizeMap.end(); ++it) {string name = it->first;int size = it->second;string hash;if (fileHastMap.count(name) == 1) {hash = fileHastMap[name];}//int strSize = runDirPath.size();string subPath = name.substr(strSize);for (size_t i = 0; i<subPath.size(); i++) {if (subPath[i] == '\\') {subPath.replace(i, 1, string("\/"));i++;}}json_t *objFile = json_object();json_object_set_new(objFile, "name", json_string(subPath.c_str()));json_object_set_new(objFile, "hash", json_string(hash.c_str()));json_object_set_new(objFile, "size", json_integer(size));json_array_append_new(objFiles, objFile);}}json_object_set_new(objCore, "files", objFiles);json_object_set_new(objCore, "name", json_string("core"));//json_array_append_new(objPackages, objCore);string newManifest;if (json_object_size(obj) > 0) {char *post_body = json_dumps(obj, JSON_COMPACT);int responseCode;int len = (int)strlen(post_body);uLong compressSize = compressBound(len);string compressedJson;compressedJson.resize(compressSize);compress2((Bytef*)&compressedJson[0], &compressSize,(const Bytef*)post_body, len,Z_BEST_COMPRESSION);compressedJson.resize(compressSize);newManifest = string(post_body);free(post_body);if (obj)json_delete(obj);}else {newManifest = "[]";}//保存json文件if (!newManifest.empty()) {ofstream fout;fout.open("F:\\manifest.json", ios_base::out | ios_base::trunc);fout << newManifest << endl;fout.close();}
}