New upstream version 18.0.1+dfsg1

This commit is contained in:
Sebastian Ramacher 2017-04-19 21:54:15 +02:00
parent 6efda2859e
commit f2cf6cce50
1337 changed files with 41178 additions and 84670 deletions

View file

@ -0,0 +1,44 @@
#include "update-window.hpp"
#include "obs-app.hpp"
OBSUpdate::OBSUpdate(QWidget *parent, bool manualUpdate, const QString &text)
: QDialog (parent, Qt::WindowSystemMenuHint |
Qt::WindowTitleHint |
Qt::WindowCloseButtonHint),
ui (new Ui_OBSUpdate)
{
ui->setupUi(this);
ui->text->setHtml(text);
if (manualUpdate) {
delete ui->skip;
ui->skip = nullptr;
ui->no->setText(QTStr("Cancel"));
}
}
void OBSUpdate::on_yes_clicked()
{
done(OBSUpdate::Yes);
}
void OBSUpdate::on_no_clicked()
{
done(OBSUpdate::No);
}
void OBSUpdate::on_skip_clicked()
{
done(OBSUpdate::Skip);
}
void OBSUpdate::accept()
{
done(OBSUpdate::Yes);
}
void OBSUpdate::reject()
{
done(OBSUpdate::No);
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <QDialog>
#include <memory>
#include "ui_OBSUpdate.h"
class OBSUpdate : public QDialog {
Q_OBJECT
public:
enum ReturnVal {
No,
Yes,
Skip
};
OBSUpdate(QWidget *parent, bool manualUpdate, const QString &text);
public slots:
void on_yes_clicked();
void on_no_clicked();
void on_skip_clicked();
virtual void accept() override;
virtual void reject() override;
private:
std::unique_ptr<Ui_OBSUpdate> ui;
};

View file

@ -0,0 +1,51 @@
if(NOT ENABLE_WIN_UPDATER)
return()
endif()
if(NOT DEFINED STATIC_ZLIB_PATH OR "${STATIC_ZLIB_PATH}" STREQUAL "")
message(STATUS "STATIC_ZLIB_PATH not set, windows updater disabled")
return()
endif()
project(updater)
include_directories(${OBS_JANSSON_INCLUDE_DIRS})
include_directories(${LIBLZMA_INCLUDE_DIRS})
include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs")
include_directories(${BLAKE2_INCLUDE_DIR})
find_package(ZLIB REQUIRED)
set(updater_HEADERS
../win-update-helpers.hpp
resource.h
updater.hpp
)
set(updater_SOURCES
../win-update-helpers.cpp
updater.cpp
patch.cpp
http.cpp
hash.cpp
updater.rc
)
add_definitions(-DNOMINMAX -DUNICODE -D_UNICODE)
if(MSVC)
add_compile_options("$<$<CONFIG:RelWithDebInfo>:/MT>")
endif()
add_executable(updater WIN32
${updater_HEADERS}
${updater_SOURCES}
)
target_link_libraries(updater
${OBS_JANSSON_IMPORT}
${STATIC_ZLIB_PATH}
lzma
blake2
psapi
comctl32
shell32
winhttp
)

View file

@ -0,0 +1,61 @@
#include "updater.hpp"
#include <util/windows/WinHandle.hpp>
#include <vector>
using namespace std;
void HashToString(const uint8_t *in, wchar_t *out)
{
const wchar_t alphabet[] = L"0123456789abcdef";
for (int i = 0; i != BLAKE2_HASH_LENGTH; ++i) {
out[2 * i] = alphabet[in[i] / 16];
out[2 * i + 1] = alphabet[in[i] % 16];
}
out[BLAKE2_HASH_LENGTH * 2] = 0;
}
void StringToHash(const wchar_t *in, BYTE *out)
{
int temp;
for (int i = 0; i < BLAKE2_HASH_LENGTH; i++) {
swscanf_s(in + i * 2, L"%02x", &temp);
out[i] = (BYTE)temp;
}
}
bool CalculateFileHash(const wchar_t *path, BYTE *hash)
{
blake2b_state blake2;
if (blake2b_init(&blake2, BLAKE2_HASH_LENGTH) != 0)
return false;
WinHandle handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE)
return false;
vector<BYTE> buf;
buf.resize(65536);
for (;;) {
DWORD read = 0;
if (!ReadFile(handle, buf.data(), (DWORD)buf.size(), &read,
nullptr))
return false;
if (!read)
break;
if (blake2b_update(&blake2, buf.data(), read) != 0)
return false;
}
if (blake2b_final(&blake2, hash, BLAKE2_HASH_LENGTH) != 0)
return false;
return true;
}

View file

@ -0,0 +1,537 @@
#include "Updater.hpp"
#include <algorithm>
using namespace std;
#define MAX_BUF_SIZE 262144
#define READ_BUF_SIZE 32768
/* ------------------------------------------------------------------------ */
class ZipStream {
z_stream strm = {};
bool initialized = false;
public:
inline ~ZipStream()
{
if (initialized)
inflateEnd(&strm);
}
inline operator z_stream*() {return &strm;}
inline z_stream *operator->() {return &strm;}
inline bool inflate()
{
int ret = inflateInit2(&strm, 16 + MAX_WBITS);
initialized = (ret == Z_OK);
return initialized;
}
};
/* ------------------------------------------------------------------------ */
static bool ReadZippedHTTPData(string &responseBuf, z_stream *strm,
string &zipBuf, const uint8_t *buffer, DWORD outSize)
{
do {
strm->avail_in = outSize;
strm->next_in = buffer;
strm->avail_out = (uInt)zipBuf.size();
strm->next_out = (Bytef *)zipBuf.data();
int zret = inflate(strm, Z_NO_FLUSH);
if (zret != Z_STREAM_END && zret != Z_OK)
return false;
try {
responseBuf.append(zipBuf.data(),
zipBuf.size() - strm->avail_out);
} catch (...) {
return false;
}
} while (strm->avail_out == 0);
return true;
}
static bool ReadHTTPData(string &responseBuf, const uint8_t *buffer,
DWORD outSize)
{
try {
responseBuf.append((const char *)buffer, outSize);
} catch (...) {
return false;
}
return true;
}
bool HTTPPostData(const wchar_t *url,
const BYTE * data,
int dataLen,
const wchar_t *extraHeaders,
int * responseCode,
string & responseBuf)
{
HttpHandle hSession;
HttpHandle hConnect;
HttpHandle hRequest;
string zipBuf;
URL_COMPONENTS urlComponents = {};
bool secure = false;
wchar_t hostName[256];
wchar_t path[1024];
const wchar_t *acceptTypes[] = {L"*/*", nullptr};
responseBuf.clear();
/* -------------------------------------- *
* get URL components */
urlComponents.dwStructSize = sizeof(urlComponents);
urlComponents.lpszHostName = hostName;
urlComponents.dwHostNameLength = _countof(hostName);
urlComponents.lpszUrlPath = path;
urlComponents.dwUrlPathLength = _countof(path);
WinHttpCrackUrl(url, 0, 0, &urlComponents);
if (urlComponents.nPort == 443)
secure = true;
/* -------------------------------------- *
* connect to server */
hSession = WinHttpOpen(L"OBS Updater/2.1",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0);
if (!hSession) {
*responseCode = -1;
return false;
}
hConnect = WinHttpConnect(hSession, hostName,
secure
? INTERNET_DEFAULT_HTTPS_PORT
: INTERNET_DEFAULT_HTTP_PORT, 0);
if (!hConnect) {
*responseCode = -2;
return false;
}
/* -------------------------------------- *
* request data */
hRequest = WinHttpOpenRequest(hConnect,
L"POST",
path,
nullptr,
WINHTTP_NO_REFERER,
acceptTypes,
secure
? WINHTTP_FLAG_SECURE |
WINHTTP_FLAG_REFRESH
: WINHTTP_FLAG_REFRESH);
if (!hRequest) {
*responseCode = -3;
return false;
}
bool bResults = !!WinHttpSendRequest(hRequest, extraHeaders,
extraHeaders ? -1 : 0,
(void *)data, dataLen, dataLen, 0);
/* -------------------------------------- *
* end request */
if (bResults) {
bResults = !!WinHttpReceiveResponse(hRequest, nullptr);
} else {
*responseCode = GetLastError();
return false;
}
/* -------------------------------------- *
* get headers */
wchar_t encoding[64];
DWORD encodingLen;
wchar_t statusCode[8];
DWORD statusCodeLen;
statusCodeLen = sizeof(statusCode);
if (!WinHttpQueryHeaders(hRequest,
WINHTTP_QUERY_STATUS_CODE,
WINHTTP_HEADER_NAME_BY_INDEX,
&statusCode,
&statusCodeLen,
WINHTTP_NO_HEADER_INDEX)) {
*responseCode = -4;
return false;
} else {
statusCode[_countof(statusCode) - 1] = 0;
}
encodingLen = sizeof(encoding);
if (!WinHttpQueryHeaders(hRequest,
WINHTTP_QUERY_CONTENT_ENCODING,
WINHTTP_HEADER_NAME_BY_INDEX,
encoding,
&encodingLen,
WINHTTP_NO_HEADER_INDEX)) {
encoding[0] = 0;
if (GetLastError() != ERROR_WINHTTP_HEADER_NOT_FOUND) {
*responseCode = -5;
return false;
}
} else {
encoding[_countof(encoding) - 1] = 0;
}
/* -------------------------------------- *
* allocate response data */
DWORD responseBufSize = MAX_BUF_SIZE;
try {
responseBuf.reserve(responseBufSize);
} catch (...) {
*responseCode = -6;
return false;
}
/* -------------------------------------- *
* if zipped, initialize zip data */
ZipStream strm;
bool gzip = wcscmp(encoding, L"gzip") == 0;
if (gzip) {
strm->zalloc = Z_NULL;
strm->zfree = Z_NULL;
strm->opaque = Z_NULL;
strm->avail_in = 0;
strm->next_in = Z_NULL;
if (!strm.inflate())
return false;
try {
zipBuf.resize(MAX_BUF_SIZE);
} catch (...) {
*responseCode = -6;
return false;
}
}
/* -------------------------------------- *
* read data */
*responseCode = wcstoul(statusCode, nullptr, 10);
/* are we supposed to return true here? */
if (!bResults || *responseCode != 200)
return true;
BYTE buffer[READ_BUF_SIZE];
DWORD dwSize, outSize;
do {
/* Check for available data. */
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
*responseCode = -8;
return false;
}
dwSize = std::min(dwSize, (DWORD)sizeof(buffer));
if (!WinHttpReadData(hRequest, (void *)buffer, dwSize,
&outSize)) {
*responseCode = -9;
return false;
}
if (!outSize)
break;
if (gzip) {
if (!ReadZippedHTTPData(responseBuf, strm, zipBuf,
buffer, outSize)) {
*responseCode = -6;
return false;
}
} else {
if (!ReadHTTPData(responseBuf, buffer, outSize)) {
*responseCode = -6;
return false;
}
}
if (WaitForSingleObject(cancelRequested, 0) == WAIT_OBJECT_0) {
*responseCode = -14;
return false;
}
} while (dwSize > 0);
return true;
}
/* ------------------------------------------------------------------------ */
static bool ReadHTTPZippedFile(z_stream *strm, HANDLE updateFile,
string &zipBuf, const uint8_t *buffer, DWORD outSize,
int *responseCode)
{
do {
strm->avail_in = outSize;
strm->next_in = buffer;
strm->avail_out = (uInt)zipBuf.size();
strm->next_out = (Bytef *)zipBuf.data();
int zret = inflate(strm, Z_NO_FLUSH);
if (zret != Z_STREAM_END && zret != Z_OK)
return false;
DWORD written;
if (!WriteFile(updateFile,
zipBuf.data(),
MAX_BUF_SIZE - strm->avail_out,
&written,
nullptr)) {
*responseCode = -10;
return false;
}
if (written != MAX_BUF_SIZE - strm->avail_out) {
*responseCode = -11;
return false;
}
completedFileSize += written;
} while (strm->avail_out == 0);
return true;
}
static bool ReadHTTPFile(HANDLE updateFile, const uint8_t *buffer,
DWORD outSize, int *responseCode)
{
DWORD written;
if (!WriteFile(updateFile, buffer, outSize, &written, nullptr)) {
*responseCode = -12;
return false;
}
if (written != outSize) {
*responseCode = -13;
return false;
}
completedFileSize += outSize;
return true;
}
bool HTTPGetFile(HINTERNET hConnect,
const wchar_t *url,
const wchar_t *outputPath,
const wchar_t *extraHeaders,
int * responseCode)
{
HttpHandle hRequest;
const wchar_t *acceptTypes[] = {L"*/*", nullptr};
URL_COMPONENTS urlComponents = {};
bool secure = false;
string zipBuf;
wchar_t hostName[256];
wchar_t path[1024];
/* -------------------------------------- *
* get URL components */
urlComponents.dwStructSize = sizeof(urlComponents);
urlComponents.lpszHostName = hostName;
urlComponents.dwHostNameLength = _countof(hostName);
urlComponents.lpszUrlPath = path;
urlComponents.dwUrlPathLength = _countof(path);
WinHttpCrackUrl(url, 0, 0, &urlComponents);
if (urlComponents.nPort == 443)
secure = true;
/* -------------------------------------- *
* request data */
hRequest = WinHttpOpenRequest(hConnect,
L"GET",
path,
nullptr,
WINHTTP_NO_REFERER,
acceptTypes,
secure
? WINHTTP_FLAG_SECURE |
WINHTTP_FLAG_REFRESH
: WINHTTP_FLAG_REFRESH);
if (!hRequest) {
*responseCode = -3;
return false;
}
bool bResults = !!WinHttpSendRequest(hRequest, extraHeaders,
extraHeaders ? -1 : 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
/* -------------------------------------- *
* end request */
if (bResults) {
bResults = !!WinHttpReceiveResponse(hRequest, nullptr);
} else {
*responseCode = GetLastError();
return false;
}
/* -------------------------------------- *
* get headers */
wchar_t encoding[64];
DWORD encodingLen;
wchar_t statusCode[8];
DWORD statusCodeLen;
statusCodeLen = sizeof(statusCode);
if (!WinHttpQueryHeaders(hRequest,
WINHTTP_QUERY_STATUS_CODE,
WINHTTP_HEADER_NAME_BY_INDEX,
&statusCode,
&statusCodeLen,
WINHTTP_NO_HEADER_INDEX)) {
*responseCode = -4;
return false;
} else {
statusCode[_countof(statusCode) - 1] = 0;
}
encodingLen = sizeof(encoding);
if (!WinHttpQueryHeaders(hRequest,
WINHTTP_QUERY_CONTENT_ENCODING,
WINHTTP_HEADER_NAME_BY_INDEX,
encoding,
&encodingLen,
WINHTTP_NO_HEADER_INDEX)) {
encoding[0] = 0;
if (GetLastError() != ERROR_WINHTTP_HEADER_NOT_FOUND) {
*responseCode = -5;
return false;
}
} else {
encoding[_countof(encoding) - 1] = 0;
}
/* -------------------------------------- *
* allocate response data */
ZipStream strm;
bool gzip = wcscmp(encoding, L"gzip") == 0;
if (gzip) {
strm->zalloc = Z_NULL;
strm->zfree = Z_NULL;
strm->opaque = Z_NULL;
strm->avail_in = 0;
strm->next_in = Z_NULL;
if (!strm.inflate())
return false;
try {
zipBuf.resize(MAX_BUF_SIZE);
} catch (...) {
*responseCode = -6;
return false;
}
}
/* -------------------------------------- *
* read data */
*responseCode = wcstoul(statusCode, nullptr, 10);
/* are we supposed to return true here? */
if (!bResults || *responseCode != 200)
return true;
BYTE buffer[READ_BUF_SIZE];
DWORD dwSize, outSize;
int lastPosition = 0;
WinHandle updateFile = CreateFile(outputPath, GENERIC_WRITE, 0,
nullptr, CREATE_ALWAYS, 0, nullptr);
if (!updateFile.Valid()) {
*responseCode = -7;
return false;
}
do {
/* Check for available data. */
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
*responseCode = -8;
return false;
}
dwSize = std::min(dwSize, (DWORD)sizeof(buffer));
if (!WinHttpReadData(hRequest, (void *)buffer, dwSize,
&outSize)) {
*responseCode = -9;
return false;
} else {
if (!outSize)
break;
if (gzip) {
if (!ReadHTTPZippedFile(strm, updateFile,
zipBuf, buffer,
outSize, responseCode))
return false;
} else {
if (!ReadHTTPFile(updateFile, buffer,
outSize, responseCode))
return false;
}
int position = (int)(((float)completedFileSize /
(float)totalFileSize) * 100.0f);
if (position > lastPosition) {
lastPosition = position;
SendDlgItemMessage(hwndMain, IDC_PROGRESS,
PBM_SETPOS, position, 0);
}
}
if (WaitForSingleObject(cancelRequested, 0) == WAIT_OBJECT_0) {
*responseCode = -14;
return false;
}
} while (dwSize > 0);
return true;
}

View file

@ -0,0 +1,301 @@
#include "updater.hpp"
#include <stdint.h>
#include <vector>
#include <lzma.h>
using namespace std;
#define MAX_BUF_SIZE 262144
#define READ_BUF_SIZE 32768
/* ------------------------------------------------------------------------ */
class LZMAStream {
lzma_stream strm = {};
bool initialized = false;
public:
inline ~LZMAStream()
{
if (initialized) {
lzma_end(&strm);
}
}
inline bool init_decoder()
{
lzma_ret ret = lzma_stream_decoder(
&strm,
200 * 1024 * 1024,
0);
initialized = (ret == LZMA_OK);
return initialized;
}
inline operator lzma_stream *() { return &strm; }
inline bool operator!() const { return !initialized; }
inline lzma_stream *get() { return &strm; }
};
class File {
FILE *f = nullptr;
public:
inline ~File()
{
if (f)
fclose(f);
}
inline FILE **operator&() { return &f; }
inline operator FILE *() const { return f; }
inline bool operator!() const { return !f; }
};
/* ------------------------------------------------------------------------ */
struct bspatch_stream {
void *opaque;
int (*read)(const struct bspatch_stream *stream, void *buffer,
int length);
};
/* ------------------------------------------------------------------------ */
static int64_t offtin(const uint8_t *buf)
{
int64_t y;
y = buf[7] & 0x7F;
y = y * 256;
y += buf[6];
y = y * 256;
y += buf[5];
y = y * 256;
y += buf[4];
y = y * 256;
y += buf[3];
y = y * 256;
y += buf[2];
y = y * 256;
y += buf[1];
y = y * 256;
y += buf[0];
if (buf[7] & 0x80)
y = -y;
return y;
}
/* ------------------------------------------------------------------------ */
static int bspatch(const uint8_t *old, int64_t oldsize, uint8_t *newp,
int64_t newsize, struct bspatch_stream *stream)
{
uint8_t buf[8];
int64_t oldpos, newpos;
int64_t ctrl[3];
int64_t i;
oldpos = 0;
newpos = 0;
while (newpos < newsize) {
/* Read control data */
for (i = 0; i <= 2; i++) {
if (stream->read(stream, buf, 8))
return -1;
ctrl[i] = offtin(buf);
};
/* Sanity-check */
if (newpos + ctrl[0] > newsize)
return -1;
/* Read diff string */
if (stream->read(stream, newp + newpos, (int)ctrl[0]))
return -1;
/* Add old data to diff string */
for (i = 0; i < ctrl[0]; i++)
if ((oldpos + i >= 0) && (oldpos + i < oldsize))
newp[newpos + i] += old[oldpos + i];
/* Adjust pointers */
newpos += ctrl[0];
oldpos += ctrl[0];
/* Sanity-check */
if (newpos + ctrl[1] > newsize)
return -1;
/* Read extra string */
if (stream->read(stream, newp + newpos, (int)ctrl[1]))
return -1;
/* Adjust pointers */
newpos += ctrl[1];
oldpos += ctrl[2];
};
return 0;
}
/* ------------------------------------------------------------------------ */
struct patch_data {
HANDLE h;
lzma_stream *strm;
uint8_t buf[READ_BUF_SIZE];
};
static int read_lzma(const struct bspatch_stream *stream, void *buffer, int len)
{
if (!len)
return 0;
patch_data *data = (patch_data*)stream->opaque;
HANDLE h = data->h;
lzma_stream *strm = data->strm;
strm->avail_out = (size_t)len;
strm->next_out = (uint8_t *)buffer;
for (;;) {
if (strm->avail_in == 0) {
DWORD read_size;
if (!ReadFile(h, data->buf, READ_BUF_SIZE, &read_size,
nullptr))
return -1;
if (read_size == 0)
return -1;
strm->avail_in = (size_t)read_size;
strm->next_in = data->buf;
}
lzma_ret ret = lzma_code(strm, LZMA_RUN);
if (ret == LZMA_STREAM_END)
return 0;
if (ret != LZMA_OK)
return -1;
if (strm->avail_out == 0)
break;
}
return 0;
}
int ApplyPatch(const wchar_t *patchFile, const wchar_t *targetFile)
try {
uint8_t header[24];
int64_t newsize;
struct bspatch_stream stream;
bool success;
WinHandle hPatch;
WinHandle hTarget;
LZMAStream strm;
/* --------------------------------- *
* open patch and file to patch */
hPatch = CreateFile(patchFile, GENERIC_READ, 0, nullptr,
OPEN_EXISTING, 0, nullptr);
if (!hPatch.Valid())
throw int(GetLastError());
hTarget = CreateFile(targetFile, GENERIC_READ, 0, nullptr,
OPEN_EXISTING, 0, nullptr);
if (!hTarget.Valid())
throw int(GetLastError());
/* --------------------------------- *
* read patch header */
DWORD read;
success = !!ReadFile(hPatch, header, sizeof(header), &read, nullptr);
if (success && read == sizeof(header)) {
if (memcmp(header, "JIMSLEY/BSDIFF43", 16))
throw int(-4);
} else {
throw int(GetLastError());
}
/* --------------------------------- *
* allocate new file size data */
newsize = offtin(header + 16);
if (newsize < 0 || newsize >= 0x7ffffffff)
throw int(-5);
vector<uint8_t> newData;
try {
newData.resize(newsize);
} catch (...) {
throw int(-1);
}
/* --------------------------------- *
* read old file */
DWORD targetFileSize;
targetFileSize = GetFileSize(hTarget, nullptr);
if (targetFileSize == INVALID_FILE_SIZE)
throw int(GetLastError());
vector<uint8_t> oldData;
try {
oldData.resize(targetFileSize);
} catch (...) {
throw int(-1);
}
if (!ReadFile(hTarget, &oldData[0], targetFileSize, &read, nullptr))
throw int(GetLastError());
if (read != targetFileSize)
throw int(-1);
/* --------------------------------- *
* patch to new file data */
if (!strm.init_decoder())
throw int(-10);
patch_data data;
data.h = hPatch;
data.strm = strm.get();
stream.read = read_lzma;
stream.opaque = &data;
int ret = bspatch(oldData.data(), oldData.size(), newData.data(),
newData.size(), &stream);
if (ret != 0)
throw int(-9);
/* --------------------------------- *
* write new file */
hTarget = nullptr;
hTarget = CreateFile(targetFile, GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, 0, nullptr);
if (!hTarget.Valid())
throw int(GetLastError());
DWORD written;
success = !!WriteFile(hTarget, newData.data(), (DWORD)newsize,
&written, nullptr);
if (!success || written != newsize)
throw int(GetLastError());
return 0;
} catch (int code) {
return code;
}

View file

@ -0,0 +1,21 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by updater.rc
//
#define IDD_UPDATEDIALOG 101
#define IDI_ICON1 103
#define IDC_PROGRESS 1001
#define IDC_STATUS 1002
#define IDCBUTTON 1004
#define IDC_BUTTON 1004
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 104
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1005
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
#pragma once
#define WINVER 0x0600
#define _WIN32_WINDOWS 0x0600
#define _WIN32_WINNT 0x0600
#define WIN32_LEAN_AND_MEAN
#define ZLIB_CONST
#include <windows.h>
#include <winhttp.h>
#include <commctrl.h>
#include <Wincrypt.h>
#include <shlobj.h>
#include <shellapi.h>
#include <malloc.h>
#include <stdlib.h>
#include <tchar.h>
#include <strsafe.h>
#include <zlib.h>
#include <ctype.h>
#include <blake2.h>
#include <string>
#include "../win-update-helpers.hpp"
#define BLAKE2_HASH_LENGTH 20
#define BLAKE2_HASH_STR_LENGTH ((BLAKE2_HASH_LENGTH * 2) + 1)
#if defined _M_IX86
#pragma comment(linker, \
"/manifestdependency:\"type='win32' " \
"name='Microsoft.Windows.Common-Controls' " \
"version='6.0.0.0' " \
"processorArchitecture='x86' " \
"publicKeyToken='6595b64144ccf1df' " \
"language='*'\"")
#elif defined _M_IA64
#pragma comment(linker, \
"/manifestdependency:\"type='win32' " \
"name='Microsoft.Windows.Common-Controls' " \
"version='6.0.0.0' " \
"processorArchitecture='ia64' " \
"publicKeyToken='6595b64144ccf1df' " \
"language='*'\"")
#elif defined _M_X64
#pragma comment(linker, \
"/manifestdependency:\"type='win32' " \
"name='Microsoft.Windows.Common-Controls' " \
"version='6.0.0.0' " \
"processorArchitecture='amd64' " \
"publicKeyToken='6595b64144ccf1df' " \
"language='*'\"")
#else
#pragma comment(linker, \
"/manifestdependency:\"type='win32' " \
"name='Microsoft.Windows.Common-Controls' " \
"version='6.0.0.0' processorArchitecture='*' " \
"publicKeyToken='6595b64144ccf1df' " \
"language='*'\"")
#endif
#include <util/windows/WinHandle.hpp>
#include <jansson.h>
#include "resource.h"
bool HTTPGetFile(HINTERNET hConnect,
const wchar_t *url,
const wchar_t *outputPath,
const wchar_t *extraHeaders,
int * responseCode);
bool HTTPPostData(const wchar_t *url,
const BYTE * data,
int dataLen,
const wchar_t *extraHeaders,
int * responseCode,
std::string & response);
void HashToString(const BYTE *in, wchar_t *out);
void StringToHash(const wchar_t *in, BYTE *out);
bool CalculateFileHash(const wchar_t *path, BYTE *hash);
int ApplyPatch(LPCTSTR patchFile, LPCTSTR targetFile);
extern HWND hwndMain;
extern HCRYPTPROV hProvider;
extern int totalFileSize;
extern int completedFileSize;
extern HANDLE cancelRequested;
#pragma pack(push, r1, 1)
typedef struct {
BLOBHEADER blobheader;
RSAPUBKEY rsapubkey;
} PUBLICKEYHEADER;
#pragma pack(pop, r1)
void FreeWinHttpHandle(HINTERNET handle);
using HttpHandle = CustomHandle<HINTERNET, FreeWinHttpHandle>;

View file

@ -0,0 +1,145 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""afxres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_UPDATEDIALOG DIALOGEX 0, 0, 316, 56
STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "OBS Studio Update"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
PUSHBUTTON "Cancel",IDC_BUTTON,259,34,50,14
CONTROL "",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH,7,17,302,14
LTEXT "Waiting for OBS to exit...",IDC_STATUS,7,7,302,8,SS_WORDELLIPSIS
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_UPDATEDIALOG, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 309
TOPMARGIN, 7
BOTTOMMARGIN, 48
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "obsproject.com"
VALUE "FileDescription", "OBS Updater"
VALUE "FileVersion", "1.0.0.1"
VALUE "InternalName", "updater.exe"
VALUE "LegalCopyright", "Copyright (C) 2013 Richard Stanway"
VALUE "OriginalFilename", "updater.exe"
VALUE "ProductName", "OBS Updater"
VALUE "ProductVersion", "1.0.0.1"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "../../../cmake/winrc/obs-studio.ico"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View file

@ -0,0 +1,40 @@
#include "win-update-helpers.hpp"
void FreeProvider(HCRYPTPROV prov)
{
CryptReleaseContext(prov, 0);
}
void FreeHash(HCRYPTHASH hash)
{
CryptDestroyHash(hash);
}
void FreeKey(HCRYPTKEY key)
{
CryptDestroyKey(key);
}
std::string vstrprintf(const char *format, va_list args)
{
if (!format)
return std::string();
std::string str;
int size = (int)vsnprintf(nullptr, 0, format, args);
str.resize(size);
vsnprintf(&str[0], size, format, args);
return str;
}
std::string strprintf(const char *format, ...)
{
std::string str;
va_list args;
va_start(args, format);
str = vstrprintf(format, args);
va_end(args);
return str;
}

View file

@ -0,0 +1,139 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Wincrypt.h>
#include <jansson.h>
#include <cstdint>
#include <string>
/* ------------------------------------------------------------------------ */
template<typename T, void freefunc(T)> class CustomHandle {
T handle;
public:
inline CustomHandle() : handle(0) {}
inline CustomHandle(T in) : handle(in) {}
inline ~CustomHandle()
{
if (handle)
freefunc(handle);
}
inline T *operator&() {return &handle;}
inline operator T() const {return handle;}
inline T get() const {return handle;}
inline CustomHandle<T, freefunc> &operator=(T in)
{
if (handle)
freefunc(handle);
handle = in;
return *this;
}
inline bool operator!() const {return !handle;}
};
void FreeProvider(HCRYPTPROV prov);
void FreeHash(HCRYPTHASH hash);
void FreeKey(HCRYPTKEY key);
using CryptProvider = CustomHandle<HCRYPTPROV, FreeProvider>;
using CryptHash = CustomHandle<HCRYPTHASH, FreeHash>;
using CryptKey = CustomHandle<HCRYPTKEY, FreeKey>;
/* ------------------------------------------------------------------------ */
template<typename T> class LocalPtr {
T *ptr = nullptr;
public:
inline ~LocalPtr()
{
if (ptr)
LocalFree(ptr);
}
inline T **operator&() {return &ptr;}
inline operator T() const {return ptr;}
inline T *get() const {return ptr;}
inline bool operator!() const {return !ptr;}
inline T *operator->() {return ptr;}
};
/* ------------------------------------------------------------------------ */
class Json {
json_t *json;
public:
inline Json() : json(nullptr) {}
explicit inline Json(json_t *json_) : json(json_) {}
inline Json(const Json &from) : json(json_incref(from.json)) {}
inline Json(Json &&from) : json(from.json) {from.json = nullptr;}
inline ~Json() {
if (json)
json_decref(json);
}
inline Json &operator=(json_t *json_)
{
if (json)
json_decref(json);
json = json_;
return *this;
}
inline Json &operator=(const Json &from)
{
if (json)
json_decref(json);
json = json_incref(from.json);
return *this;
}
inline Json &operator=(Json &&from)
{
if (json)
json_decref(json);
json = from.json;
from.json = nullptr;
return *this;
}
inline operator json_t *() const {return json;}
inline bool operator!() const {return !json;}
inline const char *GetString(const char *name,
const char *def = nullptr) const
{
json_t *obj(json_object_get(json, name));
if (!obj)
return def;
return json_string_value(obj);
}
inline int64_t GetInt(const char *name, int def = 0) const
{
json_t *obj(json_object_get(json, name));
if (!obj)
return def;
return json_integer_value(obj);
}
inline json_t *GetObject(const char *name) const
{
return json_object_get(json, name);
}
inline json_t *get() const {return json;}
};
/* ------------------------------------------------------------------------ */
std::string vstrprintf(const char *format, va_list args);
std::string strprintf(const char *format, ...);

View file

@ -0,0 +1,781 @@
#include "win-update-helpers.hpp"
#include "update-window.hpp"
#include "remote-text.hpp"
#include "win-update.hpp"
#include "obs-app.hpp"
#include <QMessageBox>
#include <string>
#include <util/windows/WinHandle.hpp>
#include <util/util.hpp>
#include <jansson.h>
#include <blake2.h>
#include <time.h>
#include <strsafe.h>
#include <winhttp.h>
#include <shellapi.h>
using namespace std;
/* ------------------------------------------------------------------------ */
#ifndef WIN_MANIFEST_URL
#define WIN_MANIFEST_URL "https://obsproject.com/update_studio/manifest.json"
#endif
#ifndef WIN_UPDATER_URL
#define WIN_UPDATER_URL "https://obsproject.com/update_studio/updater.exe"
#endif
static HCRYPTPROV provider = 0;
#pragma pack(push, r1, 1)
typedef struct {
BLOBHEADER blobheader;
RSAPUBKEY rsapubkey;
} PUBLICKEYHEADER;
#pragma pack(pop, r1)
#define BLAKE2_HASH_LENGTH 20
#define BLAKE2_HASH_STR_LENGTH ((BLAKE2_HASH_LENGTH * 2) + 1)
#define TEST_BUILD
// Hard coded 4096 bit RSA public key for obsproject.com in PEM format
static 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, 0x6c,
0x33, 0x73, 0x76, 0x65, 0x72, 0x77, 0x39, 0x48, 0x51, 0x2b, 0x72, 0x59,
0x51, 0x4e, 0x6e, 0x39, 0x43, 0x61, 0x37, 0x0a, 0x39, 0x4c, 0x55, 0x36,
0x32, 0x6e, 0x47, 0x36, 0x4e, 0x6f, 0x7a, 0x45, 0x2f, 0x46, 0x73, 0x49,
0x56, 0x4e, 0x65, 0x72, 0x2b, 0x57, 0x2f, 0x68, 0x75, 0x65, 0x45, 0x38,
0x57, 0x51, 0x31, 0x6d, 0x72, 0x46, 0x50, 0x2b, 0x32, 0x79, 0x41, 0x2b,
0x69, 0x59, 0x52, 0x75, 0x74, 0x59, 0x50, 0x65, 0x45, 0x67, 0x70, 0x78,
0x74, 0x6f, 0x64, 0x48, 0x68, 0x67, 0x6b, 0x52, 0x34, 0x70, 0x45, 0x4b,
0x0a, 0x56, 0x6e, 0x72, 0x72, 0x31, 0x38, 0x71, 0x34, 0x73, 0x7a, 0x6c,
0x76, 0x38, 0x39, 0x51, 0x49, 0x37, 0x74, 0x38, 0x6c, 0x4d, 0x6f, 0x4c,
0x54, 0x6c, 0x46, 0x2b, 0x74, 0x31, 0x49, 0x52, 0x30, 0x56, 0x34, 0x77,
0x4a, 0x56, 0x33, 0x34, 0x49, 0x33, 0x43, 0x2b, 0x33, 0x35, 0x39, 0x4b,
0x69, 0x78, 0x6e, 0x7a, 0x4c, 0x30, 0x42, 0x6c, 0x39, 0x61, 0x6a, 0x2f,
0x7a, 0x44, 0x63, 0x72, 0x58, 0x0a, 0x57, 0x6c, 0x35, 0x70, 0x48, 0x54,
0x69, 0x6f, 0x4a, 0x77, 0x59, 0x4f, 0x67, 0x4d, 0x69, 0x42, 0x47, 0x4c,
0x79, 0x50, 0x65, 0x69, 0x74, 0x4d, 0x46, 0x64, 0x6a, 0x6a, 0x54, 0x49,
0x70, 0x43, 0x4d, 0x2b, 0x6d, 0x78, 0x54, 0x57, 0x58, 0x43, 0x72, 0x5a,
0x39, 0x64, 0x50, 0x55, 0x4b, 0x76, 0x5a, 0x74, 0x67, 0x7a, 0x6a, 0x64,
0x2b, 0x49, 0x7a, 0x6c, 0x48, 0x69, 0x64, 0x48, 0x74, 0x4f, 0x0a, 0x4f,
0x52, 0x42, 0x4e, 0x35, 0x6d, 0x52, 0x73, 0x38, 0x4c, 0x4e, 0x4f, 0x35,
0x38, 0x6b, 0x37, 0x39, 0x72, 0x37, 0x37, 0x44, 0x63, 0x67, 0x51, 0x59,
0x50, 0x4e, 0x69, 0x69, 0x43, 0x74, 0x57, 0x67, 0x43, 0x2b, 0x59, 0x34,
0x4b, 0x37, 0x75, 0x53, 0x5a, 0x58, 0x33, 0x48, 0x76, 0x65, 0x6f, 0x6d,
0x32, 0x74, 0x48, 0x62, 0x56, 0x58, 0x79, 0x30, 0x4c, 0x2f, 0x43, 0x6c,
0x37, 0x66, 0x4d, 0x0a, 0x48, 0x4b, 0x71, 0x66, 0x63, 0x51, 0x47, 0x75,
0x79, 0x72, 0x76, 0x75, 0x64, 0x34, 0x32, 0x4f, 0x72, 0x57, 0x61, 0x72,
0x41, 0x73, 0x6e, 0x32, 0x70, 0x32, 0x45, 0x69, 0x36, 0x4b, 0x7a, 0x78,
0x62, 0x33, 0x47, 0x36, 0x45, 0x53, 0x43, 0x77, 0x31, 0x35, 0x6e, 0x48,
0x41, 0x67, 0x4c, 0x61, 0x6c, 0x38, 0x7a, 0x53, 0x71, 0x37, 0x2b, 0x72,
0x61, 0x45, 0x2f, 0x78, 0x6b, 0x4c, 0x70, 0x43, 0x0a, 0x62, 0x59, 0x67,
0x35, 0x67, 0x6d, 0x59, 0x36, 0x76, 0x62, 0x6d, 0x57, 0x6e, 0x71, 0x39,
0x64, 0x71, 0x57, 0x72, 0x55, 0x7a, 0x61, 0x71, 0x4f, 0x66, 0x72, 0x5a,
0x50, 0x67, 0x76, 0x67, 0x47, 0x30, 0x57, 0x76, 0x6b, 0x42, 0x53, 0x68,
0x66, 0x61, 0x45, 0x4f, 0x42, 0x61, 0x49, 0x55, 0x78, 0x41, 0x33, 0x51,
0x42, 0x67, 0x7a, 0x41, 0x5a, 0x68, 0x71, 0x65, 0x65, 0x64, 0x46, 0x39,
0x68, 0x0a, 0x61, 0x66, 0x4d, 0x47, 0x4d, 0x4d, 0x39, 0x71, 0x56, 0x62,
0x66, 0x77, 0x75, 0x75, 0x7a, 0x4a, 0x32, 0x75, 0x68, 0x2b, 0x49, 0x6e,
0x61, 0x47, 0x61, 0x65, 0x48, 0x32, 0x63, 0x30, 0x34, 0x6f, 0x56, 0x63,
0x44, 0x46, 0x66, 0x65, 0x4f, 0x61, 0x44, 0x75, 0x78, 0x52, 0x6a, 0x43,
0x43, 0x62, 0x71, 0x72, 0x35, 0x73, 0x4c, 0x53, 0x6f, 0x31, 0x43, 0x57,
0x6f, 0x6b, 0x79, 0x6e, 0x6a, 0x4e, 0x0a, 0x43, 0x42, 0x2b, 0x62, 0x32,
0x72, 0x51, 0x46, 0x37, 0x44, 0x50, 0x50, 0x62, 0x44, 0x34, 0x73, 0x2f,
0x6e, 0x54, 0x39, 0x4e, 0x73, 0x63, 0x6b, 0x2f, 0x4e, 0x46, 0x7a, 0x72,
0x42, 0x58, 0x52, 0x4f, 0x2b, 0x64, 0x71, 0x6b, 0x65, 0x42, 0x77, 0x44,
0x55, 0x43, 0x76, 0x37, 0x62, 0x5a, 0x67, 0x57, 0x37, 0x4f, 0x78, 0x75,
0x4f, 0x58, 0x30, 0x37, 0x4c, 0x54, 0x71, 0x66, 0x70, 0x35, 0x73, 0x0a,
0x4f, 0x65, 0x47, 0x67, 0x75, 0x62, 0x75, 0x62, 0x69, 0x77, 0x59, 0x33,
0x55, 0x64, 0x48, 0x59, 0x71, 0x2b, 0x4c, 0x39, 0x4a, 0x71, 0x49, 0x53,
0x47, 0x31, 0x74, 0x4d, 0x34, 0x48, 0x65, 0x4b, 0x6a, 0x61, 0x48, 0x6a,
0x75, 0x31, 0x4d, 0x44, 0x6a, 0x76, 0x48, 0x5a, 0x32, 0x44, 0x62, 0x6d,
0x4c, 0x77, 0x55, 0x78, 0x75, 0x59, 0x61, 0x36, 0x4a, 0x5a, 0x44, 0x4b,
0x57, 0x73, 0x37, 0x72, 0x0a, 0x49, 0x72, 0x64, 0x44, 0x77, 0x78, 0x33,
0x4a, 0x77, 0x61, 0x63, 0x46, 0x36, 0x36, 0x68, 0x33, 0x59, 0x55, 0x57,
0x36, 0x74, 0x7a, 0x55, 0x5a, 0x68, 0x7a, 0x74, 0x63, 0x6d, 0x51, 0x65,
0x70, 0x50, 0x2f, 0x75, 0x37, 0x42, 0x67, 0x47, 0x72, 0x6b, 0x4f, 0x50,
0x50, 0x70, 0x59, 0x41, 0x30, 0x4e, 0x45, 0x4a, 0x38, 0x30, 0x53, 0x65,
0x41, 0x78, 0x37, 0x68, 0x69, 0x4e, 0x34, 0x76, 0x61, 0x0a, 0x65, 0x45,
0x51, 0x4b, 0x6e, 0x52, 0x6e, 0x2b, 0x45, 0x70, 0x42, 0x4e, 0x36, 0x55,
0x42, 0x61, 0x35, 0x66, 0x37, 0x4c, 0x6f, 0x4b, 0x38, 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
};
static const unsigned int obs_pub_len = 800;
/* ------------------------------------------------------------------------ */
static bool QuickWriteFile(const char *file, const void *data, size_t size)
try {
BPtr<wchar_t> w_file;
if (os_utf8_to_wcs_ptr(file, 0, &w_file) == 0)
return false;
WinHandle handle = CreateFileW(
w_file,
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_FLAG_WRITE_THROUGH,
nullptr);
if (handle == INVALID_HANDLE_VALUE)
throw strprintf("Failed to open file '%s': %lu",
file, GetLastError());
DWORD written;
if (!WriteFile(handle, data, (DWORD)size, &written, nullptr))
throw strprintf("Failed to write file '%s': %lu",
file, GetLastError());
return true;
} catch (string text) {
blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
return false;
}
static bool QuickReadFile(const char *file, string &data)
try {
BPtr<wchar_t> w_file;
if (os_utf8_to_wcs_ptr(file, 0, &w_file) == 0)
return false;
WinHandle handle = CreateFileW(
w_file,
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (handle == INVALID_HANDLE_VALUE)
throw strprintf("Failed to open file '%s': %lu",
file, GetLastError());
DWORD size = GetFileSize(handle, nullptr);
data.resize(size);
DWORD read;
if (!ReadFile(handle, &data[0], size, &read, nullptr))
throw strprintf("Failed to write file '%s': %lu",
file, GetLastError());
return true;
} catch (string text) {
blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
return false;
}
static void HashToString(const uint8_t *in, char *out)
{
const char alphabet[] = "0123456789abcdef";
for (int i = 0; i != BLAKE2_HASH_LENGTH; ++i) {
out[2 * i] = alphabet[in[i] / 16];
out[2 * i + 1] = alphabet[in[i] % 16];
}
out[BLAKE2_HASH_LENGTH * 2] = 0;
}
static bool CalculateFileHash(const char *path, uint8_t *hash)
try {
blake2b_state blake2;
if (blake2b_init(&blake2, BLAKE2_HASH_LENGTH) != 0)
return false;
BPtr<wchar_t> w_path;
if (os_utf8_to_wcs_ptr(path, 0, &w_path) == 0)
return false;
WinHandle handle = CreateFileW(w_path, GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE)
throw strprintf("Failed to open file '%s': %lu",
path, GetLastError());
vector<BYTE> buf;
buf.resize(65536);
for (;;) {
DWORD read = 0;
if (!ReadFile(handle, buf.data(), (DWORD)buf.size(), &read,
nullptr))
throw strprintf("Failed to read file '%s': %lu",
path, GetLastError());
if (!read)
break;
if (blake2b_update(&blake2, buf.data(), read) != 0)
return false;
}
if (blake2b_final(&blake2, hash, BLAKE2_HASH_LENGTH) != 0)
return false;
return true;
} catch (string text) {
blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
return false;
}
/* ------------------------------------------------------------------------ */
static bool VerifyDigitalSignature(uint8_t *buf, size_t len, uint8_t *sig,
size_t sigLen)
{
/* ASN of PEM public key */
BYTE binaryKey[1024];
DWORD binaryKeyLen = sizeof(binaryKey);
/* Windows X509 public key info from ASN */
LocalPtr<CERT_PUBLIC_KEY_INFO> publicPBLOB;
DWORD iPBLOBSize;
/* RSA BLOB info from X509 public key */
LocalPtr<PUBLICKEYHEADER> rsaPublicBLOB;
DWORD rsaPublicBLOBSize;
/* Handle to public key */
CryptKey keyOut;
/* Handle to hash context */
CryptHash hash;
/* Signature in little-endian format */
vector<BYTE> reversedSig;
if (!CryptStringToBinaryA((LPCSTR)obs_pub,
obs_pub_len,
CRYPT_STRING_BASE64HEADER,
binaryKey,
&binaryKeyLen,
nullptr,
nullptr))
return false;
if (!CryptDecodeObjectEx(X509_ASN_ENCODING,
X509_PUBLIC_KEY_INFO,
binaryKey,
binaryKeyLen,
CRYPT_ENCODE_ALLOC_FLAG,
nullptr,
&publicPBLOB,
&iPBLOBSize))
return false;
if (!CryptDecodeObjectEx(X509_ASN_ENCODING,
RSA_CSP_PUBLICKEYBLOB,
publicPBLOB->PublicKey.pbData,
publicPBLOB->PublicKey.cbData,
CRYPT_ENCODE_ALLOC_FLAG,
nullptr,
&rsaPublicBLOB,
&rsaPublicBLOBSize))
return false;
if (!CryptImportKey(provider,
(const BYTE *)rsaPublicBLOB.get(),
rsaPublicBLOBSize,
0,
0,
&keyOut))
return false;
if (!CryptCreateHash(provider, CALG_SHA_512, 0, 0, &hash))
return false;
if (!CryptHashData(hash, buf, (DWORD)len, 0))
return false;
/* Windows requires signature in little-endian. Every other crypto
* provider is big-endian of course. */
reversedSig.resize(sigLen);
for (size_t i = 0; i < sigLen; i++)
reversedSig[i] = sig[sigLen - i - 1];
if (!CryptVerifySignature(hash,
reversedSig.data(),
(DWORD)sigLen,
keyOut,
nullptr,
0))
return false;
return true;
}
static inline void HexToByteArray(const char *hexStr, size_t hexLen,
vector<uint8_t> &out)
{
char ptr[3];
ptr[2] = 0;
for (size_t i = 0; i < hexLen; i += 2) {
ptr[0] = hexStr[i];
ptr[1] = hexStr[i + 1];
out.push_back((uint8_t)strtoul(ptr, nullptr, 16));
}
}
static bool CheckDataSignature(const string &data, const char *name,
const char *hexSig, size_t sigLen)
try {
if (sigLen == 0 || sigLen > 0xFFFF || (sigLen & 1) != 0)
throw strprintf("Missing or invalid signature for %s", name);
/* Convert TCHAR signature to byte array */
vector<uint8_t> signature;
signature.reserve(sigLen);
HexToByteArray(hexSig, sigLen, signature);
if (!VerifyDigitalSignature((uint8_t*)data.data(),
data.size(),
signature.data(),
signature.size()))
throw strprintf("Signature check failed for %s", name);
return true;
} catch (string text) {
blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
return false;
}
/* ------------------------------------------------------------------------ */
static bool FetchUpdaterModule(const char *url)
try {
long responseCode;
uint8_t updateFileHash[BLAKE2_HASH_LENGTH];
vector<string> extraHeaders;
BPtr<char> updateFilePath = GetConfigPathPtr(
"obs-studio\\updates\\updater.exe");
if (CalculateFileHash(updateFilePath, updateFileHash)) {
char hashString[BLAKE2_HASH_STR_LENGTH];
HashToString(updateFileHash, hashString);
string header = "If-None-Match: ";
header += hashString;
extraHeaders.push_back(move(header));
}
string signature;
string error;
string data;
bool success = GetRemoteFile(url, data, error, &responseCode,
nullptr, nullptr, extraHeaders, &signature);
if (!success || (responseCode != 200 && responseCode != 304)) {
if (responseCode == 404)
return false;
throw strprintf("Could not fetch '%s': %s", url, error.c_str());
}
/* A new file must be digitally signed */
if (responseCode == 200) {
bool valid = CheckDataSignature(data, url, signature.data(),
signature.size());
if (!valid)
throw string("Invalid updater module signature");
if (!QuickWriteFile(updateFilePath, data.data(), data.size()))
return false;
}
return true;
} catch (string text) {
blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
return false;
}
/* ------------------------------------------------------------------------ */
static bool ParseUpdateManifest(const char *manifest, bool *updatesAvailable,
string &notes_str, int &updateVer)
try {
json_error_t error;
Json root(json_loads(manifest, 0, &error));
if (!root)
throw strprintf("Failed reading json string (%d): %s",
error.line, error.text);
if (!json_is_object(root.get()))
throw string("Root of manifest is not an object");
int major = root.GetInt("version_major");
int minor = root.GetInt("version_minor");
int patch = root.GetInt("version_patch");
if (major == 0)
throw strprintf("Invalid version number: %d.%d.%d",
major,
minor,
patch);
json_t *notes = json_object_get(root, "notes");
if (!json_is_string(notes))
throw string("'notes' value invalid");
notes_str = json_string_value(notes);
json_t *packages = json_object_get(root, "packages");
if (!json_is_array(packages))
throw string("'packages' value invalid");
int cur_ver = LIBOBS_API_VER;
int new_ver = MAKE_SEMANTIC_VERSION(major, minor, patch);
updateVer = new_ver;
*updatesAvailable = new_ver > cur_ver;
return true;
} catch (string text) {
blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
return false;
}
/* ------------------------------------------------------------------------ */
void GenerateGUID(string &guid)
{
BYTE junk[20];
if (!CryptGenRandom(provider, sizeof(junk), junk))
return;
guid.resize(41);
HashToString(junk, &guid[0]);
}
void AutoUpdateThread::infoMsg(const QString &title, const QString &text)
{
QMessageBox::information(App()->GetMainWindow(), title, text);
}
void AutoUpdateThread::info(const QString &title, const QString &text)
{
QMetaObject::invokeMethod(this, "infoMsg",
Qt::BlockingQueuedConnection,
Q_ARG(QString, title),
Q_ARG(QString, text));
}
int AutoUpdateThread::queryUpdateSlot(bool manualUpdate, const QString &text)
{
OBSUpdate updateDlg(App()->GetMainWindow(), manualUpdate, text);
return updateDlg.exec();
}
int AutoUpdateThread::queryUpdate(bool manualUpdate, const char *text_utf8)
{
int ret = OBSUpdate::No;
QString text = text_utf8;
QMetaObject::invokeMethod(this, "queryUpdateSlot",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(int, ret),
Q_ARG(bool, manualUpdate),
Q_ARG(QString, text));
return ret;
}
static bool IsFileInUse(const wstring &file)
{
WinHandle f = CreateFile(file.c_str(), GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, 0, nullptr);
if (!f.Valid()) {
int err = GetLastError();
if (err == ERROR_SHARING_VIOLATION ||
err == ERROR_LOCK_VIOLATION)
return true;
}
return false;
}
static bool IsGameCaptureInUse()
{
wstring path = L"..\\..\\data\\obs-plugins\\win-capture\\graphics-hook";
return IsFileInUse(path + L"32.dll") ||
IsFileInUse(path + L"64.dll");
}
void AutoUpdateThread::run()
try {
long responseCode;
vector<string> extraHeaders;
string text;
string error;
string signature;
CryptProvider provider;
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 (video_output_active(obs_get_video())) {
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(&provider,
nullptr,
MS_ENH_RSA_AES_PROV,
PROV_RSA_AES,
CRYPT_VERIFYCONTEXT))
throw strprintf("CryptAcquireContext failed: %lu",
GetLastError());
::provider = provider;
/* ----------------------------------- *
* 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 */
/* NOTE: 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 */
const char *pguid = config_get_string(GetGlobalConfig(),
"General", "InstallGUID");
string guid;
if (pguid)
guid = pguid;
if (guid.empty()) {
GenerateGUID(guid);
if (!guid.empty())
config_set_string(GetGlobalConfig(),
"General", "InstallGUID",
guid.c_str());
}
if (!guid.empty()) {
string header = "X-OBS2-GUID: ";
header += guid;
extraHeaders.push_back(move(header));
}
/* ----------------------------------- *
* 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);
}
/* ----------------------------------- *
* 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 */
if (responseCode == 200) {
if (!QuickWriteFile(manifestPath, text.data(), text.size()))
throw strprintf("Could not write file '%s'",
manifestPath);
} else {
if (!QuickReadFile(manifestPath, text))
throw strprintf("Could not read file '%s'",
manifestPath);
}
/* ----------------------------------- *
* 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 */
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;
if (!ShellExecuteEx(&execInfo)) {
QString msg = QTStr("Updater.FailedToLaunch");
info(msg, msg);
throw strprintf("Can't launch updater '%s': %d",
updateFilePath, 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());
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <QThread>
#include <QString>
class AutoUpdateThread : public QThread {
Q_OBJECT
bool manualUpdate;
bool user_confirmed = false;
virtual void run() override;
void info(const QString &title, const QString &text);
int queryUpdate(bool manualUpdate, const char *text_utf8);
private slots:
void infoMsg(const QString &title, const QString &text);
int queryUpdateSlot(bool manualUpdate, const QString &text);
public:
AutoUpdateThread(bool manualUpdate_) : manualUpdate(manualUpdate_) {}
};