OBS 实现强制升级功能

一,要实现的效果

  •  版本更新后发布最新的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;
	//qxl

	BPtr<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
#endif
	if (App()->IsPortableMode())
		execInfo.lpParameters = UPDATE_ARG_SUFFIX L" Portable";
	else
		execInfo.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.exe

		if (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);

	//core
	json_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();
	}
}

 

 


版权声明:本文为weixin_38416696原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。