一、实现自己运行程序的manifest.json
static void GenerateJson(string path)
{
//path = "D:\\OBSUpdate\\obs-studio\\build\\rundir\\Release";
string runDirPath = path;
char *p = const_cast<char *>(path.c_str());
fileSearch(p);
//生成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];
CalculateFileHash2(AnsiToUnicode(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.substr(1).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("D:\\manifest.json", ios_base::out | ios_base::trunc);
fout << newManifest << endl;
fout.close();
}
}
static std::map<string, int> fileSizeMap;
static void fileSearch(char *path)
{
char *AddMapPath = path;
char dirNew[200];
strcpy(dirNew, path);
strcat(dirNew, "\\*.*"); // 在目录后面加上"\\*.*"进行第一次搜索
strcat(AddMapPath, "\\");
intptr_t handle;
_finddata_t findData;
handle = _findfirst(dirNew, &findData);
if (handle == -1) // 检查是否成功
{
return;
}
do {
if (findData.attrib & _A_SUBDIR) {
if (strcmp(findData.name, ".") == 0 ||
strcmp(findData.name, "..") == 0)
continue;
// 在目录后面加上"\\"和搜索到的目录名进行下一次搜索
strcpy(dirNew, path);
//strcat(dirNew, "\\");
strcat(dirNew, findData.name);
fileSearch(dirNew);
} else {
fileSizeMap.insert(std::make_pair(AddMapPath + string(findData.name),int(findData.size)));
//cout << "++++++++++++++++++++++++++++++\n"<< AddMapPath + string(findData.name);
//cout << findData.name << "\t" << findData.size << " bytes.\n"<< fileSizeMap.size();
}
} while (_findnext(handle, &findData) == 0);
_findclose(handle); // 关闭搜索句柄
}
以上代码是实现运行程序的json。
二、需要修改的地方
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;
//GenerateJson("D:\\OBSUpdate\\obs-studio\\build\\rundir\\Release");
struct FinishedTrigger {
inline ~FinishedTrigger()
{
QMetaObject::invokeMethod(App()->GetMainWindow(),
"updateCheckFinished");
}
} finishedTrigger;
BPtr<char> manifestPath = GetConfigPathPtr("obs-studio\\updates\\manifest.json");
/* ----------------------------------- *
* 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);
qDebug() << "--------------------";
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));
qDebug() << "+++++++++++++++++++";
}
/* ----------------------------------- *
* 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) {
// qDebug() << "LLLLLLLLLLLLLLLLLLLLLLLLLL"<< signature.data()
// << signature.size();
// success = CheckDataSignature(text, "manifest", signature.data(),
// signature.size());
//
// if (!success)
// throw string("Invalid manifest signature");
//}
/* ----------------------------------- *
* write or load manifest */
if (responseCode == 200) {
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;
/* ----------------------------------- *
* fetch updater module */
// 获取更新程序模块
/*if (!FetchUpdaterModule(WIN_UPDATER_URL))
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 */
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());
}
updater.cpp
bool DownloadWorkerThread()
{
const DWORD tlsProtocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
HttpHandle hSession = WinHttpOpen(L"OBS Studio Updater/2.1",
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));
HttpHandle hConnect = WinHttpConnect(hSession,
L"seefm.oss-cn-beijing.aliyuncs.com",
INTERNET_DEFAULT_HTTPS_PORT, 0);
if (!hConnect) {
downloadThreadFailure = true;
Status(L"Update failed: Couldn't connect to cdn-fastly.obsproject.com");
return false;
}
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.sourceURL.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);
Sleep(10000);
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);
Sleep(10000);
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());
Sleep(10000);
return 1;
}
//比较下载文件的hash值与manifest中文件的hash值是否一致 这个作用就是这个
//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());
// Sleep(10000);
// return 1;
//}
ulock.lock();
update.state = STATE_DOWNLOADED;
completedUpdates++;
}
if (!foundWork) {
break;
}
if (downloadThreadFailure) {
return false;
}
}
return true;
}
需要注意的是要将WIN_MANIFEST_URL和UPDATE_URL的地址换成自己公司的下载地址,一般都在云上存储,您必须维护自己的json和updater.exe,
每次更新完都要将新的json和新的程序上传到存储云上。