init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
568
Telegram/SourceFiles/_other/packer.cpp
Normal file
568
Telegram/SourceFiles/_other/packer.cpp
Normal file
@@ -0,0 +1,568 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "packer.h"
|
||||
|
||||
bool BetaChannel = false;
|
||||
quint64 AlphaVersion = 0;
|
||||
bool OnlyAlphaKey = false;
|
||||
|
||||
const char *PublicKey = "\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIGJAoGBAMA4ViQrjkPZ9xj0lrer3r23JvxOnrtE8nI69XLGSr+sRERz9YnUptnU\n\
|
||||
BZpkIfKaRcl6XzNJiN28cVwO1Ui5JSa814UAiDHzWUqCaXUiUEQ6NmNTneiGx2sQ\n\
|
||||
+9PKKlb8mmr3BB9A45ZNwLT6G9AK3+qkZLHojeSA+m84/a6GP4svAgMBAAE=\n\
|
||||
-----END RSA PUBLIC KEY-----\
|
||||
";
|
||||
|
||||
const char *PublicBetaKey = "\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIGJAoGBALWu9GGs0HED7KG7BM73CFZ6o0xufKBRQsdnq3lwA8nFQEvmdu+g/I1j\n\
|
||||
0LQ+0IQO7GW4jAgzF/4+soPDb6uHQeNFrlVx1JS9DZGhhjZ5rf65yg11nTCIHZCG\n\
|
||||
w/CVnbwQOw0g5GBwwFV3r0uTTvy44xx8XXxk+Qknu4eBCsmrAFNnAgMBAAE=\n\
|
||||
-----END RSA PUBLIC KEY-----\
|
||||
";
|
||||
|
||||
extern const char *PrivateKey;
|
||||
extern const char *PrivateBetaKey;
|
||||
#include "../../../../DesktopPrivate/packer_private.h" // RSA PRIVATE KEYS for update signing
|
||||
#include "../../../../DesktopPrivate/alpha_private.h" // private key for alpha version file generation
|
||||
|
||||
QString countAlphaVersionSignature(quint64 version);
|
||||
|
||||
// sha1 hash
|
||||
typedef unsigned char uchar;
|
||||
typedef unsigned int uint32;
|
||||
typedef signed int int32;
|
||||
|
||||
namespace{
|
||||
|
||||
struct BIODeleter {
|
||||
void operator()(BIO *value) {
|
||||
BIO_free(value);
|
||||
}
|
||||
};
|
||||
|
||||
inline auto makeBIO(const void *buf, int len) {
|
||||
return std::unique_ptr<BIO, BIODeleter>{
|
||||
BIO_new_mem_buf(buf, len),
|
||||
};
|
||||
}
|
||||
|
||||
inline uint32 sha1Shift(uint32 v, uint32 shift) {
|
||||
return ((v << shift) | (v >> (32 - shift)));
|
||||
}
|
||||
|
||||
void sha1PartHash(uint32 *sha, uint32 *temp) {
|
||||
uint32 a = sha[0], b = sha[1], c = sha[2], d = sha[3], e = sha[4], round = 0;
|
||||
|
||||
#define _shiftswap(f, v) { \
|
||||
uint32 t = sha1Shift(a, 5) + (f) + e + v + temp[round]; \
|
||||
e = d; \
|
||||
d = c; \
|
||||
c = sha1Shift(b, 30); \
|
||||
b = a; \
|
||||
a = t; \
|
||||
++round; \
|
||||
}
|
||||
|
||||
#define _shiftshiftswap(f, v) { \
|
||||
temp[round] = sha1Shift((temp[round - 3] ^ temp[round - 8] ^ temp[round - 14] ^ temp[round - 16]), 1); \
|
||||
_shiftswap(f, v) \
|
||||
}
|
||||
|
||||
while (round < 16) _shiftswap((b & c) | (~b & d), 0x5a827999)
|
||||
while (round < 20) _shiftshiftswap((b & c) | (~b & d), 0x5a827999)
|
||||
while (round < 40) _shiftshiftswap(b ^ c ^ d, 0x6ed9eba1)
|
||||
while (round < 60) _shiftshiftswap((b & c) | (b & d) | (c & d), 0x8f1bbcdc)
|
||||
while (round < 80) _shiftshiftswap(b ^ c ^ d, 0xca62c1d6)
|
||||
|
||||
#undef _shiftshiftswap
|
||||
#undef _shiftswap
|
||||
|
||||
sha[0] += a;
|
||||
sha[1] += b;
|
||||
sha[2] += c;
|
||||
sha[3] += d;
|
||||
sha[4] += e;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int32 *hashSha1(const void *data, uint32 len, void *dest) {
|
||||
const uchar *buf = (const uchar *)data;
|
||||
|
||||
uint32 temp[80], block = 0, end;
|
||||
uint32 sha[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
|
||||
for (end = block + 64; block + 64 <= len; end = block + 64) {
|
||||
for (uint32 i = 0; block < end; block += 4) {
|
||||
temp[i++] = (uint32) buf[block + 3]
|
||||
| (((uint32) buf[block + 2]) << 8)
|
||||
| (((uint32) buf[block + 1]) << 16)
|
||||
| (((uint32) buf[block]) << 24);
|
||||
}
|
||||
sha1PartHash(sha, temp);
|
||||
}
|
||||
|
||||
end = len - block;
|
||||
memset(temp, 0, sizeof(uint32) * 16);
|
||||
uint32 last = 0;
|
||||
for (; last < end; ++last) {
|
||||
temp[last >> 2] |= (uint32)buf[last + block] << ((3 - (last & 0x03)) << 3);
|
||||
}
|
||||
temp[last >> 2] |= 0x80 << ((3 - (last & 3)) << 3);
|
||||
if (end >= 56) {
|
||||
sha1PartHash(sha, temp);
|
||||
memset(temp, 0, sizeof(uint32) * 16);
|
||||
}
|
||||
temp[15] = len << 3;
|
||||
sha1PartHash(sha, temp);
|
||||
|
||||
uchar *sha1To = (uchar*)dest;
|
||||
|
||||
for (int32 i = 19; i >= 0; --i) {
|
||||
sha1To[i] = (sha[i >> 2] >> (((3 - i) & 0x03) << 3)) & 0xFF;
|
||||
}
|
||||
|
||||
return (int32*)sha1To;
|
||||
}
|
||||
|
||||
QString AlphaSignature;
|
||||
|
||||
int writeAlphaKey() {
|
||||
if (!AlphaVersion) {
|
||||
return 0;
|
||||
}
|
||||
QString keyName(QString("talpha_%1_key").arg(AlphaVersion));
|
||||
QFile key(keyName);
|
||||
if (!key.open(QIODevice::WriteOnly)) {
|
||||
cout << "Can't open '" << keyName.toUtf8().constData() << "' for write..\n";
|
||||
return -1;
|
||||
}
|
||||
key.write(AlphaSignature.toUtf8());
|
||||
key.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QString workDir;
|
||||
|
||||
QString remove;
|
||||
int version = 0;
|
||||
[[maybe_unused]] bool targetwin64 = false;
|
||||
[[maybe_unused]] bool targetwinarm = false;
|
||||
[[maybe_unused]] bool targetarmac = false;
|
||||
QFileInfoList files;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
if (string("-path") == argv[i] && i + 1 < argc) {
|
||||
QString path = workDir + QString(argv[i + 1]);
|
||||
QFileInfo info(path);
|
||||
files.push_back(info);
|
||||
if (remove.isEmpty()) remove = info.canonicalPath() + "/";
|
||||
} else if (string("-target") == argv[i] && i + 1 < argc) {
|
||||
targetwin64 = (string("win64") == argv[i + 1]);
|
||||
targetwinarm = (string("winarm") == argv[i + 1]);
|
||||
} else if (string("-arch") == argv[i] && i + 1 < argc) {
|
||||
targetarmac = (string("arm64") == argv[i + 1]);
|
||||
if (!targetarmac && string("x86_64") != argv[i + 1]) {
|
||||
cout << "Bad -arch param value passed: " << argv[i + 1] << "\n";
|
||||
return -1;
|
||||
}
|
||||
} else if (string("-version") == argv[i] && i + 1 < argc) {
|
||||
version = QString(argv[i + 1]).toInt();
|
||||
} else if (string("-beta") == argv[i]) {
|
||||
BetaChannel = true;
|
||||
} else if (string("-alphakey") == argv[i]) {
|
||||
OnlyAlphaKey = true;
|
||||
} else if (string("-alpha") == argv[i] && i + 1 < argc) {
|
||||
AlphaVersion = QString(argv[i + 1]).toULongLong();
|
||||
if (AlphaVersion > version * 1000ULL && AlphaVersion < (version + 1) * 1000ULL) {
|
||||
BetaChannel = false;
|
||||
AlphaSignature = countAlphaVersionSignature(AlphaVersion);
|
||||
if (AlphaSignature.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
cout << "Bad -alpha param value passed, should be for the same version: " << version << ", alpha: " << AlphaVersion << "\n";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (OnlyAlphaKey) {
|
||||
return writeAlphaKey();
|
||||
}
|
||||
|
||||
if (files.isEmpty() || remove.isEmpty() || version <= 1016 || version > 999999999) {
|
||||
#ifdef Q_OS_WIN
|
||||
cout << "Usage: Packer.exe -path {file} -version {version} OR Packer.exe -path {dir} -version {version}\n";
|
||||
#elif defined Q_OS_MAC
|
||||
cout << "Usage: Packer.app -path {file} -version {version} OR Packer.app -path {dir} -version {version}\n";
|
||||
#else
|
||||
cout << "Usage: Packer -path {file} -version {version} OR Packer -path {dir} -version {version}\n";
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool hasDirs = true;
|
||||
while (hasDirs) {
|
||||
hasDirs = false;
|
||||
for (QFileInfoList::iterator i = files.begin(); i != files.end(); ++i) {
|
||||
QFileInfo info(*i);
|
||||
QString fullPath = info.canonicalFilePath();
|
||||
if (info.isDir()) {
|
||||
hasDirs = true;
|
||||
files.erase(i);
|
||||
QDir d = QDir(info.absoluteFilePath());
|
||||
QString fullDir = d.canonicalPath();
|
||||
QStringList entries = d.entryList(QDir::Files | QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
|
||||
files.append(d.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot));
|
||||
break;
|
||||
} else if (!info.isReadable()) {
|
||||
cout << "Can't read: " << info.absoluteFilePath().toUtf8().constData() << "\n";
|
||||
return -1;
|
||||
} else if (info.isHidden()) {
|
||||
hasDirs = true;
|
||||
files.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (QFileInfoList::iterator i = files.begin(); i != files.end(); ++i) {
|
||||
QFileInfo info(*i);
|
||||
if (!info.canonicalFilePath().startsWith(remove)) {
|
||||
cout << "Can't find '" << remove.toUtf8().constData() << "' in file '" << info.canonicalFilePath().toUtf8().constData() << "' :(\n";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray result;
|
||||
{
|
||||
QBuffer buffer(&result);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QDataStream stream(&buffer);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
if (AlphaVersion) {
|
||||
stream << quint32(0x7FFFFFFF);
|
||||
stream << quint64(AlphaVersion);
|
||||
} else {
|
||||
stream << quint32(version);
|
||||
}
|
||||
|
||||
stream << quint32(files.size());
|
||||
cout << "Found " << files.size() << " file" << (files.size() == 1 ? "" : "s") << "..\n";
|
||||
for (QFileInfoList::iterator i = files.begin(); i != files.end(); ++i) {
|
||||
QFileInfo info(*i);
|
||||
QString fullName = info.canonicalFilePath();
|
||||
QString name = fullName.mid(remove.length());
|
||||
cout << name.toUtf8().constData() << " (" << info.size() << ")\n";
|
||||
|
||||
QFile f(fullName);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
cout << "Can't open '" << fullName.toUtf8().constData() << "' for read..\n";
|
||||
return -1;
|
||||
}
|
||||
QByteArray inner = f.readAll();
|
||||
stream << name << quint32(inner.size()) << inner;
|
||||
#ifndef Q_OS_WIN
|
||||
stream << (QFileInfo(fullName).isExecutable() ? true : false);
|
||||
#endif
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
cout << "Stream status is bad: " << stream.status() << "\n";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int32 resultSize = result.size();
|
||||
cout << "Compression start, size: " << resultSize << "\n";
|
||||
|
||||
QByteArray compressed, resultCheck;
|
||||
#if defined Q_OS_WIN && !defined PACKER_USE_PACKAGED // use Lzma SDK for win
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
|
||||
|
||||
compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size
|
||||
|
||||
size_t compressedLen = compressed.size() - hSize;
|
||||
size_t outPropsSize = LZMA_PROPS_SIZE;
|
||||
uchar *_dest = (uchar*)(compressed.data() + hSize);
|
||||
size_t *_destLen = &compressedLen;
|
||||
const uchar *_src = (const uchar*)(result.constData());
|
||||
size_t _srcLen = result.size();
|
||||
uchar *_outProps = (uchar*)(compressed.data() + hSigLen + hShaLen);
|
||||
int res = LzmaCompress(_dest, _destLen, _src, _srcLen, _outProps, &outPropsSize, 9, 64 * 1024 * 1024, 4, 0, 2, 273, 2);
|
||||
if (res != SZ_OK) {
|
||||
cout << "Error in compression: " << res << "\n";
|
||||
return -1;
|
||||
}
|
||||
compressed.resize(int(hSize + compressedLen));
|
||||
memcpy(compressed.data() + hSigLen + hShaLen + hPropsLen, &resultSize, hOriginalSizeLen);
|
||||
|
||||
cout << "Compressed to size: " << compressedLen << "\n";
|
||||
|
||||
cout << "Checking uncompressed..\n";
|
||||
|
||||
int32 resultCheckLen;
|
||||
memcpy(&resultCheckLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
|
||||
if (resultCheckLen <= 0 || resultCheckLen > 1024 * 1024 * 1024) {
|
||||
cout << "Bad result len: " << resultCheckLen << "\n";
|
||||
return -1;
|
||||
}
|
||||
resultCheck.resize(resultCheckLen);
|
||||
|
||||
size_t resultLen = resultCheck.size();
|
||||
SizeT srcLen = compressedLen;
|
||||
int uncompressRes = LzmaUncompress((uchar*)resultCheck.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
|
||||
if (uncompressRes != SZ_OK) {
|
||||
cout << "Uncompress failed: " << uncompressRes << "\n";
|
||||
return -1;
|
||||
}
|
||||
if (resultLen != size_t(result.size())) {
|
||||
cout << "Uncompress bad size: " << resultLen << ", was: " << result.size() << "\n";
|
||||
return -1;
|
||||
}
|
||||
#else // use liblzma for others
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
|
||||
|
||||
compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size
|
||||
|
||||
size_t compressedLen = compressed.size() - hSize;
|
||||
|
||||
lzma_stream stream = LZMA_STREAM_INIT;
|
||||
|
||||
int preset = 9 | LZMA_PRESET_EXTREME;
|
||||
lzma_ret ret = lzma_easy_encoder(&stream, preset, LZMA_CHECK_CRC64);
|
||||
if (ret != LZMA_OK) {
|
||||
const char *msg;
|
||||
switch (ret) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
|
||||
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
cout << "Error initializing the encoder: " << msg << " (error code " << ret << ")\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
stream.avail_in = resultSize;
|
||||
stream.next_in = (uint8_t*)result.constData();
|
||||
stream.avail_out = compressedLen;
|
||||
stream.next_out = (uint8_t*)(compressed.data() + hSize);
|
||||
|
||||
lzma_ret res = lzma_code(&stream, LZMA_FINISH);
|
||||
compressedLen -= stream.avail_out;
|
||||
lzma_end(&stream);
|
||||
if (res != LZMA_OK && res != LZMA_STREAM_END) {
|
||||
const char *msg;
|
||||
switch (res) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_DATA_ERROR: msg = "File size limits exceeded"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
cout << "Error in compression: " << msg << " (error code " << res << ")\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
compressed.resize(int(hSize + compressedLen));
|
||||
memcpy(compressed.data() + hSigLen + hShaLen, &resultSize, hOriginalSizeLen);
|
||||
|
||||
cout << "Compressed to size: " << compressedLen << "\n";
|
||||
|
||||
cout << "Checking uncompressed..\n";
|
||||
|
||||
int32 resultCheckLen;
|
||||
memcpy(&resultCheckLen, compressed.constData() + hSigLen + hShaLen, hOriginalSizeLen);
|
||||
if (resultCheckLen <= 0 || resultCheckLen > 1024 * 1024 * 1024) {
|
||||
cout << "Bad result len: " << resultCheckLen << "\n";
|
||||
return -1;
|
||||
}
|
||||
resultCheck.resize(resultCheckLen);
|
||||
|
||||
size_t resultLen = resultCheck.size();
|
||||
|
||||
stream = LZMA_STREAM_INIT;
|
||||
|
||||
ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
|
||||
if (ret != LZMA_OK) {
|
||||
const char *msg;
|
||||
switch (ret) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
|
||||
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
cout << "Error initializing the decoder: " << msg << " (error code " << ret << ")\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
stream.avail_in = compressedLen;
|
||||
stream.next_in = (uint8_t*)(compressed.constData() + hSize);
|
||||
stream.avail_out = resultLen;
|
||||
stream.next_out = (uint8_t*)resultCheck.data();
|
||||
|
||||
res = lzma_code(&stream, LZMA_FINISH);
|
||||
if (stream.avail_in) {
|
||||
cout << "Error in decompression, " << stream.avail_in << " bytes left in _in of " << compressedLen << " whole.\n";
|
||||
return -1;
|
||||
} else if (stream.avail_out) {
|
||||
cout << "Error in decompression, " << stream.avail_out << " bytes free left in _out of " << resultLen << " whole.\n";
|
||||
return -1;
|
||||
}
|
||||
lzma_end(&stream);
|
||||
if (res != LZMA_OK && res != LZMA_STREAM_END) {
|
||||
const char *msg;
|
||||
switch (res) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
|
||||
case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
|
||||
case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
cout << "Error in decompression: " << msg << " (error code " << res << ")\n";
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
if (memcmp(result.constData(), resultCheck.constData(), resultLen)) {
|
||||
cout << "Data differ :(\n";
|
||||
return -1;
|
||||
}
|
||||
/**/
|
||||
result = resultCheck = QByteArray();
|
||||
|
||||
cout << "Counting SHA1 hash..\n";
|
||||
|
||||
uchar sha1Buffer[20];
|
||||
memcpy(compressed.data() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, uint32(compressedLen + hPropsLen + hOriginalSizeLen), sha1Buffer), hShaLen); // count sha1
|
||||
|
||||
uint32 siglen = 0;
|
||||
|
||||
cout << "Signing..\n";
|
||||
RSA *prKey = [] {
|
||||
const auto bio = makeBIO(
|
||||
const_cast<char*>(
|
||||
(BetaChannel || AlphaVersion)
|
||||
? PrivateBetaKey
|
||||
: PrivateKey),
|
||||
-1);
|
||||
return PEM_read_bio_RSAPrivateKey(bio.get(), 0, 0, 0);
|
||||
}();
|
||||
if (!prKey) {
|
||||
cout << "Could not read RSA private key!\n";
|
||||
return -1;
|
||||
}
|
||||
if (RSA_size(prKey) != hSigLen) {
|
||||
cout << "Bad private key, size: " << RSA_size(prKey) << "\n";
|
||||
RSA_free(prKey);
|
||||
return -1;
|
||||
}
|
||||
if (RSA_sign(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (uchar*)(compressed.data()), &siglen, prKey) != 1) { // count signature
|
||||
cout << "Signing failed!\n";
|
||||
RSA_free(prKey);
|
||||
return -1;
|
||||
}
|
||||
RSA_free(prKey);
|
||||
|
||||
if (siglen != hSigLen) {
|
||||
cout << "Bad signature length: " << siglen << "\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
cout << "Checking signature..\n";
|
||||
RSA *pbKey = [] {
|
||||
const auto bio = makeBIO(
|
||||
const_cast<char*>(
|
||||
(BetaChannel || AlphaVersion)
|
||||
? PublicBetaKey
|
||||
: PublicKey),
|
||||
-1);
|
||||
return PEM_read_bio_RSAPublicKey(bio.get(), 0, 0, 0);
|
||||
}();
|
||||
if (!pbKey) {
|
||||
cout << "Could not read RSA public key!\n";
|
||||
return -1;
|
||||
}
|
||||
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), siglen, pbKey) != 1) { // verify signature
|
||||
RSA_free(pbKey);
|
||||
cout << "Signature verification failed!\n";
|
||||
return -1;
|
||||
}
|
||||
cout << "Signature verified!\n";
|
||||
RSA_free(pbKey);
|
||||
#ifdef Q_OS_WIN
|
||||
QString outName((targetwinarm ? QString("tarm64upd%1") : targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
#elif defined Q_OS_MAC
|
||||
QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
#else
|
||||
QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version));
|
||||
#endif
|
||||
if (AlphaVersion) {
|
||||
outName += "_" + AlphaSignature;
|
||||
}
|
||||
QFile out(outName);
|
||||
if (!out.open(QIODevice::WriteOnly)) {
|
||||
cout << "Can't open '" << outName.toUtf8().constData() << "' for write..\n";
|
||||
return -1;
|
||||
}
|
||||
out.write(compressed);
|
||||
out.close();
|
||||
|
||||
cout << "Update file '" << outName.toUtf8().constData() << "' written successfully!\n";
|
||||
|
||||
return writeAlphaKey();
|
||||
}
|
||||
|
||||
QString countAlphaVersionSignature(quint64 version) { // duplicated in autoupdater.cpp
|
||||
QByteArray cAlphaPrivateKey(AlphaPrivateKey);
|
||||
if (cAlphaPrivateKey.isEmpty()) {
|
||||
cout << "Error: Trying to count alpha version signature without alpha private key!\n";
|
||||
return QString();
|
||||
}
|
||||
|
||||
QByteArray signedData = (QLatin1String("TelegramBeta_") + QString::number(version, 16).toLower()).toUtf8();
|
||||
|
||||
static const int32 shaSize = 20, keySize = 128;
|
||||
|
||||
uchar sha1Buffer[shaSize];
|
||||
hashSha1(signedData.constData(), signedData.size(), sha1Buffer); // count sha1
|
||||
|
||||
uint32 siglen = 0;
|
||||
|
||||
RSA *prKey = [&] {
|
||||
const auto bio = makeBIO(
|
||||
const_cast<char*>(cAlphaPrivateKey.constData()),
|
||||
-1);
|
||||
return PEM_read_bio_RSAPrivateKey(bio.get(), 0, 0, 0);
|
||||
}();
|
||||
if (!prKey) {
|
||||
cout << "Error: Could not read alpha private key!\n";
|
||||
return QString();
|
||||
}
|
||||
if (RSA_size(prKey) != keySize) {
|
||||
cout << "Error: Bad alpha private key size: " << RSA_size(prKey) << "\n";
|
||||
RSA_free(prKey);
|
||||
return QString();
|
||||
}
|
||||
QByteArray signature;
|
||||
signature.resize(keySize);
|
||||
if (RSA_sign(NID_sha1, (const uchar*)(sha1Buffer), shaSize, (uchar*)(signature.data()), &siglen, prKey) != 1) { // count signature
|
||||
cout << "Error: Counting alpha version signature failed!\n";
|
||||
RSA_free(prKey);
|
||||
return QString();
|
||||
}
|
||||
RSA_free(prKey);
|
||||
|
||||
if (siglen != keySize) {
|
||||
cout << "Error: Bad alpha version signature length: " << siglen << "\n";
|
||||
return QString();
|
||||
}
|
||||
|
||||
signature = signature.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
signature = signature.replace('-', '8').replace('_', 'B');
|
||||
return QString::fromUtf8(signature.mid(19, 32));
|
||||
}
|
||||
42
Telegram/SourceFiles/_other/packer.h
Normal file
42
Telegram/SourceFiles/_other/packer.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QDataStream>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
extern "C" {
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
} // extern "C"
|
||||
|
||||
#if defined Q_OS_WIN && !defined PACKER_USE_PACKAGED // use Lzma SDK for win
|
||||
#include <LzmaLib.h>
|
||||
#else
|
||||
#include <lzma.h>
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
|
||||
using std::string;
|
||||
using std::wstring;
|
||||
using std::cout;
|
||||
55
Telegram/SourceFiles/_other/startup_task_win.cpp
Normal file
55
Telegram/SourceFiles/_other/startup_task_win.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
constexpr auto kMaxPathLong = 32767;
|
||||
|
||||
[[nodiscard]] std::wstring ExecutableDirectory() {
|
||||
auto exePath = std::array<WCHAR, kMaxPathLong + 1>{ 0 };
|
||||
const auto exeLength = GetModuleFileName(
|
||||
nullptr,
|
||||
exePath.data(),
|
||||
kMaxPathLong + 1);
|
||||
if (!exeLength || exeLength >= kMaxPathLong + 1) {
|
||||
return {};
|
||||
}
|
||||
const auto exe = std::wstring(exePath.data());
|
||||
const auto last1 = exe.find_last_of('\\');
|
||||
const auto last2 = exe.find_last_of('/');
|
||||
const auto last = std::max(
|
||||
(last1 == std::wstring::npos) ? -1 : int(last1),
|
||||
(last2 == std::wstring::npos) ? -1 : int(last2));
|
||||
if (last < 0) {
|
||||
return {};
|
||||
}
|
||||
return exe.substr(0, last);
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(
|
||||
HINSTANCE instance,
|
||||
HINSTANCE prevInstance,
|
||||
LPWSTR cmdParamarg,
|
||||
int cmdShow) {
|
||||
const auto directory = ExecutableDirectory();
|
||||
if (!directory.empty()) {
|
||||
ShellExecute(
|
||||
nullptr,
|
||||
nullptr,
|
||||
(directory + L"\\Telegram.exe").c_str(),
|
||||
L"-autostart",
|
||||
directory.data(),
|
||||
SW_SHOWNORMAL);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
37
Telegram/SourceFiles/_other/updater.h
Normal file
37
Telegram/SourceFiles/_other/updater.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
#ifdef small
|
||||
#undef small
|
||||
#endif // small
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4091)
|
||||
#include <DbgHelp.h>
|
||||
#include <ShlObj.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
#include <Shellapi.h>
|
||||
#include <Shlwapi.h>
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
|
||||
using std::deque;
|
||||
using std::wstring;
|
||||
|
||||
extern LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter;
|
||||
LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers);
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
|
||||
|
||||
static int updaterVersion = 1000;
|
||||
static const WCHAR *updaterVersionStr = L"0.1.0";
|
||||
520
Telegram/SourceFiles/_other/updater_linux.cpp
Normal file
520
Telegram/SourceFiles/_other/updater_linux.cpp
Normal file
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#define _GLIBCXX_USE_CXX11_ABI 0
|
||||
#include <cstdio>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <pwd.h>
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <algorithm>
|
||||
#include <cstdarg>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
|
||||
using std::string;
|
||||
using std::deque;
|
||||
using std::vector;
|
||||
using std::cout;
|
||||
|
||||
bool do_mkdir(const char *path) { // from http://stackoverflow.com/questions/675039/how-can-i-create-directory-tree-in-c-linux
|
||||
struct stat statbuf;
|
||||
if (stat(path, &statbuf) != 0) {
|
||||
/* Directory does not exist. EEXIST for race condition */
|
||||
if (mkdir(path, S_IRWXU) != 0 && errno != EEXIST) return false;
|
||||
} else if (!S_ISDIR(statbuf.st_mode)) {
|
||||
errno = ENOTDIR;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _debug = false;
|
||||
bool writeprotected = false;
|
||||
string updaterDir;
|
||||
string updaterName;
|
||||
string workDir;
|
||||
string exeName;
|
||||
string exePath;
|
||||
string argv0;
|
||||
|
||||
FILE *_logFile = 0;
|
||||
void openLog() {
|
||||
if (!_debug || _logFile) return;
|
||||
|
||||
if (!do_mkdir((workDir + "DebugLogs").c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
time_t timer;
|
||||
|
||||
time(&timer);
|
||||
struct tm *t = localtime(&timer);
|
||||
|
||||
static const int maxFileLen = 65536;
|
||||
char logName[maxFileLen];
|
||||
sprintf(logName, "%sDebugLogs/%04d%02d%02d_%02d%02d%02d_upd.txt", workDir.c_str(),
|
||||
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
|
||||
_logFile = fopen(logName, "w");
|
||||
}
|
||||
|
||||
void closeLog() {
|
||||
if (!_logFile) return;
|
||||
|
||||
fclose(_logFile);
|
||||
_logFile = 0;
|
||||
}
|
||||
|
||||
void writeLog(const char *format, ...) {
|
||||
if (!_logFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(_logFile, format, args);
|
||||
fprintf(_logFile, "\n");
|
||||
fflush(_logFile);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
bool copyFile(const char *from, const char *to) {
|
||||
FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
|
||||
if (!ffrom) {
|
||||
if (fto) fclose(fto);
|
||||
return false;
|
||||
}
|
||||
if (!fto) {
|
||||
fclose(ffrom);
|
||||
return false;
|
||||
}
|
||||
static const int BufSize = 65536;
|
||||
char buf[BufSize];
|
||||
while (size_t size = fread(buf, 1, BufSize, ffrom)) {
|
||||
fwrite(buf, 1, size, fto);
|
||||
}
|
||||
|
||||
struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c
|
||||
//let's say this wont fail since you already worked OK on that fp
|
||||
if (fstat(fileno(ffrom), &fst) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
//update to the same uid/gid
|
||||
if (!writeprotected && fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
//update the permissions
|
||||
if (fchmod(fileno(fto), fst.st_mode) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool remove_directory(const string &path) { // from http://stackoverflow.com/questions/2256945/removing-a-non-empty-directory-programmatically-in-c-or-c
|
||||
DIR *d = opendir(path.c_str());
|
||||
writeLog("Removing dir '%s'", path.c_str());
|
||||
|
||||
if (!d) {
|
||||
writeLog("Could not open dir '%s'", path.c_str());
|
||||
return (errno == ENOENT);
|
||||
}
|
||||
|
||||
while (struct dirent *p = readdir(d)) {
|
||||
/* Skip the names "." and ".." as we don't want to recurse on them. */
|
||||
if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) continue;
|
||||
|
||||
string fname = path + '/' + p->d_name;
|
||||
struct stat statbuf;
|
||||
writeLog("Trying to get stat() for '%s'", fname.c_str());
|
||||
if (!stat(fname.c_str(), &statbuf)) {
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
if (!remove_directory(fname.c_str())) {
|
||||
closedir(d);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
writeLog("Unlinking file '%s'", fname.c_str());
|
||||
if (unlink(fname.c_str())) {
|
||||
writeLog("Failed to unlink '%s'", fname.c_str());
|
||||
closedir(d);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeLog("Failed to call stat() on '%s'", fname.c_str());
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
writeLog("Finally removing dir '%s'", path.c_str());
|
||||
return !rmdir(path.c_str());
|
||||
}
|
||||
|
||||
bool mkpath(const char *path) {
|
||||
int status = 0, pathsize = strlen(path) + 1;
|
||||
char *copypath = new char[pathsize];
|
||||
memcpy(copypath, path, pathsize);
|
||||
|
||||
char *pp = copypath, *sp;
|
||||
while (status == 0 && (sp = strchr(pp, '/')) != 0) {
|
||||
if (sp != pp) {
|
||||
/* Neither root nor double slash in path */
|
||||
*sp = '\0';
|
||||
if (!do_mkdir(copypath)) {
|
||||
delete[] copypath;
|
||||
return false;
|
||||
}
|
||||
*sp = '/';
|
||||
}
|
||||
pp = sp + 1;
|
||||
}
|
||||
delete[] copypath;
|
||||
return do_mkdir(path);
|
||||
}
|
||||
|
||||
bool equal(string a, string b) {
|
||||
std::transform(a.begin(), a.end(), a.begin(), ::tolower);
|
||||
std::transform(b.begin(), b.end(), b.begin(), ::tolower);
|
||||
return a == b;
|
||||
}
|
||||
|
||||
void delFolder() {
|
||||
string delPathOld = workDir + "tupdates/ready", delPath = workDir + "tupdates/temp", delFolder = workDir + "tupdates";
|
||||
writeLog("Fully clearing old path '%s'..", delPathOld.c_str());
|
||||
if (!remove_directory(delPathOld)) {
|
||||
writeLog("Failed to clear old path! :( New path was used?..");
|
||||
}
|
||||
writeLog("Fully clearing path '%s'..", delPath.c_str());
|
||||
if (!remove_directory(delPath)) {
|
||||
writeLog("Error: failed to clear path! :(");
|
||||
}
|
||||
rmdir(delFolder.c_str());
|
||||
}
|
||||
|
||||
bool update() {
|
||||
writeLog("Update started..");
|
||||
|
||||
string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata";
|
||||
{
|
||||
FILE *readyFile = fopen(readyFilePath.c_str(), "rb");
|
||||
if (readyFile) {
|
||||
fclose(readyFile);
|
||||
writeLog("Ready file found! Using new path '%s'..", updDir.c_str());
|
||||
} else {
|
||||
updDir = workDir + "tupdates/ready"; // old
|
||||
tdataDir = workDir + "tupdates/ready/tdata";
|
||||
writeLog("Ready file not found! Using old path '%s'..", updDir.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
deque<string> dirs;
|
||||
dirs.push_back(updDir);
|
||||
|
||||
deque<string> from, to, forcedirs;
|
||||
|
||||
do {
|
||||
string dir = dirs.front();
|
||||
dirs.pop_front();
|
||||
|
||||
string toDir = exePath;
|
||||
if (dir.size() > updDir.size() + 1) {
|
||||
toDir += (dir.substr(updDir.size() + 1) + '/');
|
||||
forcedirs.push_back(toDir);
|
||||
writeLog("Parsing dir '%s' in update tree..", toDir.c_str());
|
||||
}
|
||||
|
||||
DIR *d = opendir(dir.c_str());
|
||||
if (!d) {
|
||||
writeLog("Failed to open dir %s", dir.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
while (struct dirent *p = readdir(d)) {
|
||||
/* Skip the names "." and ".." as we don't want to recurse on them. */
|
||||
if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) continue;
|
||||
|
||||
string fname = dir + '/' + p->d_name;
|
||||
struct stat statbuf;
|
||||
if (fname.substr(0, tdataDir.size()) == tdataDir && (fname.size() <= tdataDir.size() || fname.at(tdataDir.size()) == '/')) {
|
||||
writeLog("Skipping 'tdata' path '%s'", fname.c_str());
|
||||
} else if (!stat(fname.c_str(), &statbuf)) {
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
dirs.push_back(fname);
|
||||
writeLog("Added dir '%s' in update tree..", fname.c_str());
|
||||
} else {
|
||||
string tofname = exePath + fname.substr(updDir.size() + 1);
|
||||
if (equal(tofname, updaterName)) { // bad update - has Updater - delete all dir
|
||||
writeLog("Error: bad update, has Updater! '%s' equal '%s'", tofname.c_str(), updaterName.c_str());
|
||||
delFolder();
|
||||
return false;
|
||||
} else if (equal(tofname, exePath + "Telegram") && exeName != "Telegram") {
|
||||
string fullBinaryPath = exePath + exeName;
|
||||
writeLog("Target binary found: '%s', changing to '%s'", tofname.c_str(), fullBinaryPath.c_str());
|
||||
tofname = fullBinaryPath;
|
||||
}
|
||||
if (fname == readyFilePath) {
|
||||
writeLog("Skipped ready file '%s'", fname.c_str());
|
||||
} else {
|
||||
from.push_back(fname);
|
||||
to.push_back(tofname);
|
||||
writeLog("Added file '%s' to be copied to '%s'", fname.c_str(), tofname.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeLog("Could not get stat() for file %s", fname.c_str());
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
} while (!dirs.empty());
|
||||
|
||||
for (size_t i = 0; i < forcedirs.size(); ++i) {
|
||||
string forcedir = forcedirs[i];
|
||||
writeLog("Forcing dir '%s'..", forcedir.c_str());
|
||||
if (!forcedir.empty() && !mkpath(forcedir.c_str())) {
|
||||
writeLog("Error: failed to create dir '%s'..", forcedir.c_str());
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < from.size(); ++i) {
|
||||
string fname = from[i], tofname = to[i];
|
||||
|
||||
// it is necessary to remove the old file to not to get an error if appimage file is used by fuse
|
||||
struct stat statbuf;
|
||||
writeLog("Trying to get stat() for '%s'", tofname.c_str());
|
||||
if (!stat(tofname.c_str(), &statbuf)) {
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
writeLog("Fully clearing path '%s'..", tofname.c_str());
|
||||
if (!remove_directory(tofname.c_str())) {
|
||||
writeLog("Error: failed to clear path '%s'", tofname.c_str());
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
writeLog("Unlinking file '%s'", tofname.c_str());
|
||||
if (unlink(tofname.c_str())) {
|
||||
writeLog("Error: failed to unlink '%s'", tofname.c_str());
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeLog("Copying file '%s' to '%s'..", fname.c_str(), tofname.c_str());
|
||||
int copyTries = 0, triesLimit = 30;
|
||||
do {
|
||||
if (!copyFile(fname.c_str(), tofname.c_str())) {
|
||||
++copyTries;
|
||||
usleep(100000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (copyTries < triesLimit);
|
||||
if (copyTries == triesLimit) {
|
||||
writeLog("Error: failed to copy, asking to retry..");
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
writeLog("Update succeed! Clearing folder..");
|
||||
delFolder();
|
||||
return true;
|
||||
}
|
||||
|
||||
string CurrentExecutablePath(int argc, char *argv[]) {
|
||||
constexpr auto kMaxPath = 1024;
|
||||
char result[kMaxPath] = { 0 };
|
||||
auto count = readlink("/proc/self/exe", result, kMaxPath);
|
||||
if (count > 0) {
|
||||
return string(result);
|
||||
}
|
||||
|
||||
// Fallback to the first command line argument.
|
||||
return argc ? string(argv[0]) : string();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool needupdate = true;
|
||||
bool autostart = false;
|
||||
bool debug = false;
|
||||
bool tosettings = false;
|
||||
bool startintray = false;
|
||||
bool customWorkingDir = false;
|
||||
bool justUpdate = false;
|
||||
|
||||
char *key = 0;
|
||||
char *workdir = 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (equal(argv[i], "-noupdate")) {
|
||||
needupdate = false;
|
||||
} else if (equal(argv[i], "-autostart")) {
|
||||
autostart = true;
|
||||
} else if (equal(argv[i], "-debug")) {
|
||||
debug = _debug = true;
|
||||
} else if (equal(argv[i], "-startintray")) {
|
||||
startintray = true;
|
||||
} else if (equal(argv[i], "-tosettings")) {
|
||||
tosettings = true;
|
||||
} else if (equal(argv[i], "-workdir_custom")) {
|
||||
customWorkingDir = true;
|
||||
} else if (equal(argv[i], "-writeprotected")) {
|
||||
writeprotected = true;
|
||||
justUpdate = true;
|
||||
} else if (equal(argv[i], "-justupdate")) {
|
||||
justUpdate = true;
|
||||
} else if (equal(argv[i], "-key") && ++i < argc) {
|
||||
key = argv[i];
|
||||
} else if (equal(argv[i], "-workpath") && ++i < argc) {
|
||||
workDir = workdir = argv[i];
|
||||
} else if (equal(argv[i], "-exename") && ++i < argc) {
|
||||
exeName = argv[i];
|
||||
} else if (equal(argv[i], "-exepath") && ++i < argc) {
|
||||
exePath = argv[i];
|
||||
} else if (equal(argv[i], "-argv0") && ++i < argc) {
|
||||
argv0 = argv[i];
|
||||
}
|
||||
}
|
||||
if (exeName.empty() || exeName.find('/') != string::npos) {
|
||||
exeName = "Telegram";
|
||||
}
|
||||
openLog();
|
||||
|
||||
writeLog("Updater started, new argments formatting..");
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
writeLog("Argument: '%s'", argv[i]);
|
||||
}
|
||||
if (needupdate) writeLog("Need to update!");
|
||||
if (autostart) writeLog("From autostart!");
|
||||
if (writeprotected) writeLog("Write Protected folder!");
|
||||
|
||||
updaterName = CurrentExecutablePath(argc, argv);
|
||||
writeLog("Updater binary full path is: %s", updaterName.c_str());
|
||||
if (exePath.empty()) {
|
||||
writeLog("Executable path is not specified :(");
|
||||
} else {
|
||||
writeLog("Executable path: %s", exePath.c_str());
|
||||
}
|
||||
if (updaterName.size() >= 7) {
|
||||
if (equal(updaterName.substr(updaterName.size() - 7), "Updater")) {
|
||||
updaterDir = updaterName.substr(0, updaterName.size() - 7);
|
||||
writeLog("Updater binary dir is: %s", updaterDir.c_str());
|
||||
if (exePath.empty()) {
|
||||
exePath = updaterDir;
|
||||
writeLog("Using updater binary dir.", exePath.c_str());
|
||||
}
|
||||
if (needupdate) {
|
||||
if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp)
|
||||
customWorkingDir = false;
|
||||
|
||||
writeLog("No workdir, trying to figure it out");
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw && pw->pw_dir && strlen(pw->pw_dir)) {
|
||||
string tryDir = pw->pw_dir + string("/.TelegramDesktop/");
|
||||
struct stat statbuf;
|
||||
writeLog("Trying to use '%s' as workDir, getting stat() for tupdates/ready", tryDir.c_str());
|
||||
if (!stat((tryDir + "tupdates/ready").c_str(), &statbuf)) {
|
||||
writeLog("Stat got");
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
writeLog("It is directory, using home work dir");
|
||||
workDir = tryDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (workDir.empty()) {
|
||||
workDir = exePath;
|
||||
|
||||
struct stat statbuf;
|
||||
writeLog("Trying to use current as workDir, getting stat() for tupdates/ready");
|
||||
if (!stat("tupdates/ready", &statbuf)) {
|
||||
writeLog("Stat got");
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
writeLog("It is directory, using current dir");
|
||||
workDir = string();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeLog("Passed workpath is '%s'", workDir.c_str());
|
||||
}
|
||||
update();
|
||||
}
|
||||
} else {
|
||||
writeLog("Error: bad exe name!");
|
||||
}
|
||||
} else {
|
||||
writeLog("Error: short exe name!");
|
||||
}
|
||||
|
||||
// let the parent launch instead
|
||||
if (justUpdate) {
|
||||
writeLog("Closing log and quitting..");
|
||||
} else {
|
||||
const auto fullBinaryPath = exePath + exeName;
|
||||
|
||||
auto values = vector<string>();
|
||||
const auto push = [&](string arg) {
|
||||
// Force null-terminated .data() call result.
|
||||
values.push_back(arg + char(0));
|
||||
};
|
||||
push(!argv0.empty() ? argv0 : fullBinaryPath);
|
||||
push("-noupdate");
|
||||
if (autostart) push("-autostart");
|
||||
if (debug) push("-debug");
|
||||
if (startintray) push("-startintray");
|
||||
if (tosettings) push("-tosettings");
|
||||
if (key) {
|
||||
push("-key");
|
||||
push(key);
|
||||
}
|
||||
if (customWorkingDir && workdir) {
|
||||
push("-workdir");
|
||||
push(workdir);
|
||||
}
|
||||
|
||||
auto args = vector<char*>();
|
||||
for (auto &arg : values) {
|
||||
args.push_back(arg.data());
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
|
||||
pid_t pid = fork();
|
||||
switch (pid) {
|
||||
case -1:
|
||||
writeLog("fork() failed!");
|
||||
return 1;
|
||||
case 0:
|
||||
execv(fullBinaryPath.c_str(), args.data());
|
||||
return 1;
|
||||
}
|
||||
|
||||
writeLog("Executed Telegram, closing log and quitting..");
|
||||
}
|
||||
|
||||
closeLog();
|
||||
|
||||
return 0;
|
||||
}
|
||||
285
Telegram/SourceFiles/_other/updater_osx.m
Normal file
285
Telegram/SourceFiles/_other/updater_osx.m
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
NSString *appName = @"Telegram.app";
|
||||
NSString *appDir = nil;
|
||||
NSString *workDir = nil;
|
||||
|
||||
#ifdef _DEBUG
|
||||
BOOL _debug = YES;
|
||||
#else
|
||||
BOOL _debug = NO;
|
||||
#endif
|
||||
|
||||
NSFileHandle *_logFile = nil;
|
||||
void openLog() {
|
||||
if (!_debug || _logFile) return;
|
||||
NSString *logDir = [workDir stringByAppendingString:@"DebugLogs"];
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:logDir withIntermediateDirectories:YES attributes:nil error:nil]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
|
||||
[fmt setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
||||
[fmt setDateFormat:@"'DebugLogs/'yyyyMMdd'_'HHmmss'_update.txt'"];
|
||||
NSString *logPath = [workDir stringByAppendingString:[fmt stringFromDate:[NSDate date]]];
|
||||
[[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil];
|
||||
_logFile = [NSFileHandle fileHandleForWritingAtPath:logPath];
|
||||
}
|
||||
|
||||
void closeLog() {
|
||||
if (!_logFile) return;
|
||||
|
||||
[_logFile closeFile];
|
||||
}
|
||||
|
||||
void writeLog(NSString *msg) {
|
||||
if (!_logFile) return;
|
||||
|
||||
[_logFile writeData:[[msg stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[_logFile synchronizeFile];
|
||||
}
|
||||
|
||||
void RemoveQuarantineAttribute(NSString *path) {
|
||||
const char *kQuarantineAttribute = "com.apple.quarantine";
|
||||
|
||||
writeLog([@"Removing quarantine: " stringByAppendingString:path]);
|
||||
removexattr([path fileSystemRepresentation], kQuarantineAttribute, 0);
|
||||
}
|
||||
|
||||
void RemoveQuarantineFromBundle(NSString *path) {
|
||||
RemoveQuarantineAttribute(path);
|
||||
RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/MacOS/Telegram"]);
|
||||
RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/Helpers/crashpad_handler"]);
|
||||
RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/Frameworks/Updater"]);
|
||||
}
|
||||
|
||||
void delFolder() {
|
||||
writeLog([@"Fully clearing old path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/ready"]]);
|
||||
if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil]) {
|
||||
writeLog(@"Failed to clear old path! :( New path was used?..");
|
||||
}
|
||||
writeLog([@"Fully clearing new path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/temp"]]);
|
||||
if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/temp"] error:nil]) {
|
||||
writeLog(@"Error: failed to clear new path! :(");
|
||||
}
|
||||
rmdir([[workDir stringByAppendingString:@"tupdates"] fileSystemRepresentation]);
|
||||
}
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
NSString *path = [[NSBundle mainBundle] bundlePath];
|
||||
if (!path) {
|
||||
return -1;
|
||||
}
|
||||
NSRange range = [path rangeOfString:@".app/" options:NSBackwardsSearch];
|
||||
if (range.location == NSNotFound) {
|
||||
return -1;
|
||||
}
|
||||
path = [path substringToIndex:range.location > 0 ? range.location : 0];
|
||||
|
||||
range = [path rangeOfString:@"/" options:NSBackwardsSearch];
|
||||
NSString *appRealName = (range.location == NSNotFound) ? path : [path substringFromIndex:range.location + 1];
|
||||
appRealName = [[NSArray arrayWithObjects:appRealName, @".app", nil] componentsJoinedByString:@""];
|
||||
appDir = (range.location == NSNotFound) ? @"" : [path substringToIndex:range.location + 1];
|
||||
NSString *appDirFull = [appDir stringByAppendingString:appRealName];
|
||||
|
||||
openLog();
|
||||
pid_t procId = 0;
|
||||
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO;
|
||||
BOOL customWorkingDir = NO;
|
||||
NSString *key = nil;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
if ([@"-workpath" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
if (++i < argc) {
|
||||
workDir = [NSString stringWithUTF8String:argv[i]];
|
||||
}
|
||||
} else if ([@"-procid" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
if (++i < argc) {
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
|
||||
procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue];
|
||||
}
|
||||
} else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
update = NO;
|
||||
} else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
toSettings = YES;
|
||||
} else if ([@"-autostart" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
autoStart = YES;
|
||||
} else if ([@"-debug" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
_debug = YES;
|
||||
} else if ([@"-startintray" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
startInTray = YES;
|
||||
} else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
customWorkingDir = YES;
|
||||
} else if ([@"-key" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
if (++i < argc) key = [NSString stringWithUTF8String:argv[i]];
|
||||
}
|
||||
}
|
||||
if (!workDir) {
|
||||
workDir = appDir;
|
||||
customWorkingDir = NO;
|
||||
}
|
||||
openLog();
|
||||
NSMutableArray *argsArr = [[NSMutableArray alloc] initWithCapacity:argc];
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
[argsArr addObject:[NSString stringWithUTF8String:argv[i]]];
|
||||
}
|
||||
writeLog([[NSArray arrayWithObjects:@"Arguments: '", [argsArr componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
|
||||
if (key) writeLog([@"Key: " stringByAppendingString:key]);
|
||||
if (toSettings) writeLog(@"To Settings!");
|
||||
|
||||
if (procId) {
|
||||
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId];
|
||||
for (int i = 0; i < 5 && app != nil && ![app isTerminated]; ++i) {
|
||||
usleep(200000);
|
||||
app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId];
|
||||
}
|
||||
if (app) [app forceTerminate];
|
||||
app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId];
|
||||
for (int i = 0; i < 5 && app != nil && ![app isTerminated]; ++i) {
|
||||
usleep(200000);
|
||||
app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId];
|
||||
}
|
||||
}
|
||||
|
||||
if (update) {
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSString *readyFilePath = [workDir stringByAppendingString:@"tupdates/temp/ready"];
|
||||
NSString *srcDir = [workDir stringByAppendingString:@"tupdates/temp/"], *srcEnum = [workDir stringByAppendingString:@"tupdates/temp"];
|
||||
if ([fileManager fileExistsAtPath:readyFilePath]) {
|
||||
writeLog([@"Ready file found! Using new path: " stringByAppendingString: srcEnum]);
|
||||
} else {
|
||||
srcDir = [workDir stringByAppendingString:@"tupdates/ready/"]; // old
|
||||
srcEnum = [workDir stringByAppendingString:@"tupdates/ready"];
|
||||
writeLog([@"Ready file not found! Using old path: " stringByAppendingString: srcEnum]);
|
||||
}
|
||||
|
||||
writeLog([@"Starting update files iteration, path: " stringByAppendingString: srcEnum]);
|
||||
|
||||
// Take the Updater (this currently running binary) from the place where it was placed by Telegram
|
||||
// and copy it to the folder with the new version of the app (ready),
|
||||
// so it won't be deleted when we will clear the "Telegram.app/Contents" folder.
|
||||
NSString *oldVersionUpdaterPath = [appDirFull stringByAppendingString: @"/Contents/Frameworks/Updater" ];
|
||||
NSString *newVersionUpdaterPath = [srcEnum stringByAppendingString:[[NSArray arrayWithObjects:@"/", appName, @"/Contents/Frameworks/Updater", nil] componentsJoinedByString:@""]];
|
||||
writeLog([[NSArray arrayWithObjects: @"Copying Updater from old path ", oldVersionUpdaterPath, @" to new path ", newVersionUpdaterPath, nil] componentsJoinedByString:@""]);
|
||||
if (![fileManager fileExistsAtPath:newVersionUpdaterPath]) {
|
||||
if (![fileManager copyItemAtPath:oldVersionUpdaterPath toPath:newVersionUpdaterPath error:nil]) {
|
||||
writeLog([[NSArray arrayWithObjects: @"Failed to copy file from ", oldVersionUpdaterPath, @" to ", newVersionUpdaterPath, nil] componentsJoinedByString:@""]);
|
||||
delFolder();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NSString *contentsPath = [appDirFull stringByAppendingString: @"/Contents"];
|
||||
writeLog([[NSArray arrayWithObjects: @"Clearing dir ", contentsPath, nil] componentsJoinedByString:@""]);
|
||||
if (![fileManager removeItemAtPath:contentsPath error:nil]) {
|
||||
writeLog([@"Failed to clear path for directory " stringByAppendingString:contentsPath]);
|
||||
delFolder();
|
||||
return -1;
|
||||
}
|
||||
|
||||
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
|
||||
NSDirectoryEnumerator *enumerator = [fileManager
|
||||
enumeratorAtURL:[NSURL fileURLWithPath:srcEnum]
|
||||
includingPropertiesForKeys:keys
|
||||
options:0
|
||||
errorHandler:^(NSURL *url, NSError *error) {
|
||||
writeLog([[[@"Error in enumerating " stringByAppendingString:[url absoluteString]] stringByAppendingString: @" error is: "] stringByAppendingString: [error description]]);
|
||||
return NO;
|
||||
}];
|
||||
for (NSURL *url in enumerator) {
|
||||
NSString *srcPath = [url path];
|
||||
writeLog([@"Handling file " stringByAppendingString:srcPath]);
|
||||
NSRange r = [srcPath rangeOfString:srcDir];
|
||||
if (r.location != 0) {
|
||||
writeLog([@"Bad file found, no base path " stringByAppendingString:srcPath]);
|
||||
delFolder();
|
||||
break;
|
||||
}
|
||||
NSString *pathPart = [srcPath substringFromIndex:r.length];
|
||||
r = [pathPart rangeOfString:appName];
|
||||
if (r.location != 0) {
|
||||
writeLog([@"Skipping not app file " stringByAppendingString:srcPath]);
|
||||
continue;
|
||||
}
|
||||
NSString *dstPath = [appDirFull stringByAppendingString:[pathPart substringFromIndex:r.length]];
|
||||
NSError *error;
|
||||
NSNumber *isDirectory = nil;
|
||||
if (![url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
|
||||
writeLog([@"Failed to get IsDirectory for file " stringByAppendingString:[url path]]);
|
||||
delFolder();
|
||||
break;
|
||||
}
|
||||
if ([isDirectory boolValue]) {
|
||||
writeLog([[NSArray arrayWithObjects: @"Copying dir ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]);
|
||||
if (![fileManager createDirectoryAtPath:dstPath withIntermediateDirectories:YES attributes:nil error:nil]) {
|
||||
writeLog([@"Failed to force path for directory " stringByAppendingString:dstPath]);
|
||||
delFolder();
|
||||
break;
|
||||
}
|
||||
} else if ([srcPath isEqualToString:readyFilePath]) {
|
||||
writeLog([[NSArray arrayWithObjects: @"Skipping ready file ", srcPath, nil] componentsJoinedByString:@""]);
|
||||
} else if ([fileManager fileExistsAtPath:dstPath]) {
|
||||
if (![[NSData dataWithContentsOfFile:srcPath] writeToFile:dstPath atomically:YES]) {
|
||||
writeLog([@"Failed to edit file " stringByAppendingString:dstPath]);
|
||||
delFolder();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (![fileManager copyItemAtPath:srcPath toPath:dstPath error:nil]) {
|
||||
writeLog([@"Failed to copy file to " stringByAppendingString:dstPath]);
|
||||
delFolder();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
delFolder();
|
||||
}
|
||||
|
||||
NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""];
|
||||
|
||||
RemoveQuarantineFromBundle(appPath);
|
||||
|
||||
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: @"-noupdate", nil];
|
||||
if (toSettings) [args addObject:@"-tosettings"];
|
||||
if (_debug) [args addObject:@"-debug"];
|
||||
if (startInTray) [args addObject:@"-startintray"];
|
||||
if (autoStart) [args addObject:@"-autostart"];
|
||||
if (key) {
|
||||
[args addObject:@"-key"];
|
||||
[args addObject:key];
|
||||
}
|
||||
if (customWorkingDir) {
|
||||
[args addObject:@"-workdir"];
|
||||
[args addObject:workDir];
|
||||
}
|
||||
writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
NSError *error = nil;
|
||||
NSRunningApplication *result = [[NSWorkspace sharedWorkspace]
|
||||
launchApplicationAtURL:[NSURL fileURLWithPath:appPath]
|
||||
options:NSWorkspaceLaunchDefault
|
||||
configuration:[NSDictionary
|
||||
dictionaryWithObject:args
|
||||
forKey:NSWorkspaceLaunchConfigurationArguments]
|
||||
error:&error];
|
||||
if (result) {
|
||||
closeLog();
|
||||
return 0;
|
||||
}
|
||||
writeLog([[NSString stringWithFormat:@"Could not run application, error %ld: ", (long)[error code]] stringByAppendingString: error ? [error localizedDescription] : @"(nil)"]);
|
||||
usleep(200000);
|
||||
}
|
||||
closeLog();
|
||||
return -1;
|
||||
}
|
||||
|
||||
604
Telegram/SourceFiles/_other/updater_win.cpp
Normal file
604
Telegram/SourceFiles/_other/updater_win.cpp
Normal file
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "updater.h"
|
||||
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
|
||||
bool _debug = false;
|
||||
|
||||
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
|
||||
|
||||
bool equal(const wstring &a, const wstring &b) {
|
||||
return !_wcsicmp(a.c_str(), b.c_str());
|
||||
}
|
||||
|
||||
void updateError(const WCHAR *msg, DWORD errorCode) {
|
||||
WCHAR errMsg[2048];
|
||||
LPWSTR errorTextFormatted = nullptr;
|
||||
auto formatFlags = FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| FORMAT_MESSAGE_ALLOCATE_BUFFER
|
||||
| FORMAT_MESSAGE_IGNORE_INSERTS;
|
||||
FormatMessage(
|
||||
formatFlags,
|
||||
NULL,
|
||||
errorCode,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPWSTR)&errorTextFormatted,
|
||||
0,
|
||||
0);
|
||||
auto errorText = errorTextFormatted
|
||||
? errorTextFormatted
|
||||
: L"(Unknown error)";
|
||||
wsprintf(errMsg, L"%s, error code: %d\nError message: %s", msg, errorCode, errorText);
|
||||
|
||||
MessageBox(0, errMsg, L"Update error!", MB_ICONERROR);
|
||||
|
||||
LocalFree(errorTextFormatted);
|
||||
}
|
||||
|
||||
HANDLE _logFile = 0;
|
||||
void openLog() {
|
||||
if (!_debug || _logFile) return;
|
||||
wstring logPath = L"DebugLogs";
|
||||
if (!CreateDirectory(logPath.c_str(), NULL)) {
|
||||
DWORD errorCode = GetLastError();
|
||||
if (errorCode && errorCode != ERROR_ALREADY_EXISTS) {
|
||||
updateError(L"Failed to create log directory", errorCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SYSTEMTIME stLocalTime;
|
||||
|
||||
GetLocalTime(&stLocalTime);
|
||||
|
||||
static const int maxFileLen = MAX_PATH * 10;
|
||||
WCHAR logName[maxFileLen];
|
||||
wsprintf(logName, L"DebugLogs\\%04d%02d%02d_%02d%02d%02d_upd.txt",
|
||||
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
|
||||
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond);
|
||||
_logFile = CreateFile(logName, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (_logFile == INVALID_HANDLE_VALUE) { // :(
|
||||
updateError(L"Failed to create log file", GetLastError());
|
||||
_logFile = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void closeLog() {
|
||||
if (!_logFile) return;
|
||||
|
||||
CloseHandle(_logFile);
|
||||
_logFile = 0;
|
||||
}
|
||||
|
||||
void writeLog(const wstring &msg) {
|
||||
if (!_logFile) return;
|
||||
|
||||
wstring full = msg + L'\n';
|
||||
DWORD written = 0;
|
||||
BOOL result = WriteFile(_logFile, full.c_str(), full.size() * sizeof(wchar_t), &written, 0);
|
||||
if (!result) {
|
||||
updateError((L"Failed to write log entry '" + msg + L"'").c_str(), GetLastError());
|
||||
closeLog();
|
||||
return;
|
||||
}
|
||||
BOOL flushr = FlushFileBuffers(_logFile);
|
||||
if (!flushr) {
|
||||
updateError((L"Failed to flush log on entry '" + msg + L"'").c_str(), GetLastError());
|
||||
closeLog();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void fullClearPath(const wstring &dir) {
|
||||
WCHAR path[4096];
|
||||
memcpy(path, dir.c_str(), (dir.size() + 1) * sizeof(WCHAR));
|
||||
path[dir.size() + 1] = 0;
|
||||
writeLog(L"Fully clearing path '" + dir + L"'..");
|
||||
SHFILEOPSTRUCT file_op = {
|
||||
NULL,
|
||||
FO_DELETE,
|
||||
path,
|
||||
L"",
|
||||
FOF_NOCONFIRMATION |
|
||||
FOF_NOERRORUI |
|
||||
FOF_SILENT,
|
||||
false,
|
||||
0,
|
||||
L""
|
||||
};
|
||||
int res = SHFileOperation(&file_op);
|
||||
if (res) writeLog(L"Error: failed to clear path! :(");
|
||||
}
|
||||
|
||||
void delFolder() {
|
||||
wstring delPathOld = L"tupdates\\ready", delPath = L"tupdates\\temp", delFolder = L"tupdates";
|
||||
fullClearPath(delPathOld);
|
||||
fullClearPath(delPath);
|
||||
RemoveDirectory(delFolder.c_str());
|
||||
}
|
||||
|
||||
DWORD versionNum = 0, versionLen = 0, readLen = 0;
|
||||
WCHAR versionStr[32] = { 0 };
|
||||
|
||||
bool update() {
|
||||
writeLog(L"Update started..");
|
||||
|
||||
wstring updDir = L"tupdates\\temp", readyFilePath = L"tupdates\\temp\\ready", tdataDir = L"tupdates\\temp\\tdata";
|
||||
{
|
||||
HANDLE readyFile = CreateFile(readyFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (readyFile != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(readyFile);
|
||||
} else {
|
||||
updDir = L"tupdates\\ready"; // old
|
||||
tdataDir = L"tupdates\\ready\\tdata";
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE versionFile = CreateFile((tdataDir + L"\\version").c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (versionFile != INVALID_HANDLE_VALUE) {
|
||||
if (!ReadFile(versionFile, &versionNum, sizeof(DWORD), &readLen, NULL) || readLen != sizeof(DWORD)) {
|
||||
versionNum = 0;
|
||||
} else {
|
||||
if (versionNum == 0x7FFFFFFF) { // alpha version
|
||||
|
||||
} else if (!ReadFile(versionFile, &versionLen, sizeof(DWORD), &readLen, NULL) || readLen != sizeof(DWORD) || versionLen > 63) {
|
||||
versionNum = 0;
|
||||
} else if (!ReadFile(versionFile, versionStr, versionLen, &readLen, NULL) || readLen != versionLen) {
|
||||
versionNum = 0;
|
||||
}
|
||||
}
|
||||
CloseHandle(versionFile);
|
||||
writeLog(L"Version file read.");
|
||||
} else {
|
||||
writeLog(L"Could not open version file to update registry :(");
|
||||
}
|
||||
|
||||
deque<wstring> dirs;
|
||||
dirs.push_back(updDir);
|
||||
|
||||
deque<wstring> from, to, forcedirs;
|
||||
|
||||
do {
|
||||
wstring dir = dirs.front();
|
||||
dirs.pop_front();
|
||||
|
||||
wstring toDir = updateTo;
|
||||
if (dir.size() > updDir.size() + 1) {
|
||||
toDir += (dir.substr(updDir.size() + 1) + L"\\");
|
||||
forcedirs.push_back(toDir);
|
||||
writeLog(L"Parsing dir '" + toDir + L"' in update tree..");
|
||||
}
|
||||
|
||||
WIN32_FIND_DATA findData;
|
||||
HANDLE findHandle = FindFirstFileEx((dir + L"\\*").c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, 0, 0);
|
||||
if (findHandle == INVALID_HANDLE_VALUE) {
|
||||
DWORD errorCode = GetLastError();
|
||||
if (errorCode == ERROR_PATH_NOT_FOUND) { // no update is ready
|
||||
return true;
|
||||
}
|
||||
writeLog(L"Error: failed to find update files :(");
|
||||
updateError(L"Failed to find update files", errorCode);
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
wstring fname = dir + L"\\" + findData.cFileName;
|
||||
if (fname.substr(0, tdataDir.size()) == tdataDir && (fname.size() <= tdataDir.size() || fname.at(tdataDir.size()) == '/')) {
|
||||
writeLog(L"Skipped 'tdata' path '" + fname + L"'");
|
||||
} else if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if (findData.cFileName != wstring(L".") && findData.cFileName != wstring(L"..")) {
|
||||
dirs.push_back(fname);
|
||||
writeLog(L"Added dir '" + fname + L"' in update tree..");
|
||||
}
|
||||
} else {
|
||||
wstring tofname = updateTo + fname.substr(updDir.size() + 1);
|
||||
if (equal(tofname, updaterName)) { // bad update - has Updater.exe - delete all dir
|
||||
writeLog(L"Error: bad update, has Updater.exe! '" + tofname + L"' equal '" + updaterName + L"'");
|
||||
delFolder();
|
||||
return false;
|
||||
} else if (equal(tofname, updateTo + L"Telegram.exe") && exeName != L"Telegram.exe") {
|
||||
wstring fullBinaryPath = updateTo + exeName;
|
||||
writeLog(L"Target binary found: '" + tofname + L"', changing to '" + fullBinaryPath + L"'");
|
||||
tofname = fullBinaryPath;
|
||||
}
|
||||
if (equal(fname, readyFilePath)) {
|
||||
writeLog(L"Skipped ready file '" + fname + L"'");
|
||||
} else {
|
||||
from.push_back(fname);
|
||||
to.push_back(tofname);
|
||||
writeLog(L"Added file '" + fname + L"' to be copied to '" + tofname + L"'");
|
||||
}
|
||||
}
|
||||
} while (FindNextFile(findHandle, &findData));
|
||||
DWORD errorCode = GetLastError();
|
||||
if (errorCode && errorCode != ERROR_NO_MORE_FILES) { // everything is found
|
||||
writeLog(L"Error: failed to find next update file :(");
|
||||
updateError(L"Failed to find next update file", errorCode);
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
FindClose(findHandle);
|
||||
} while (!dirs.empty());
|
||||
|
||||
for (size_t i = 0; i < forcedirs.size(); ++i) {
|
||||
wstring forcedir = forcedirs[i];
|
||||
writeLog(L"Forcing dir '" + forcedir + L"'..");
|
||||
if (!forcedir.empty() && !CreateDirectory(forcedir.c_str(), NULL)) {
|
||||
DWORD errorCode = GetLastError();
|
||||
if (errorCode && errorCode != ERROR_ALREADY_EXISTS) {
|
||||
writeLog(L"Error: failed to create dir '" + forcedir + L"'..");
|
||||
updateError(L"Failed to create directory", errorCode);
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
writeLog(L"Already exists!");
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < from.size(); ++i) {
|
||||
wstring fname = from[i], tofname = to[i];
|
||||
BOOL copyResult;
|
||||
do {
|
||||
writeLog(L"Copying file '" + fname + L"' to '" + tofname + L"'..");
|
||||
int copyTries = 0;
|
||||
do {
|
||||
copyResult = CopyFile(fname.c_str(), tofname.c_str(), FALSE);
|
||||
if (!copyResult) {
|
||||
++copyTries;
|
||||
Sleep(100);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (copyTries < 100);
|
||||
if (!copyResult) {
|
||||
writeLog(L"Error: failed to copy, asking to retry..");
|
||||
WCHAR errMsg[2048];
|
||||
wsprintf(errMsg, L"Failed to update Telegram :(\n%s is not accessible.", tofname.c_str());
|
||||
if (MessageBox(0, errMsg, L"Update error!", MB_ICONERROR | MB_RETRYCANCEL) != IDRETRY) {
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (!copyResult);
|
||||
}
|
||||
|
||||
writeLog(L"Update succeed! Clearing folder..");
|
||||
delFolder();
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateRegistry() {
|
||||
if (versionNum && versionNum != 0x7FFFFFFF) {
|
||||
writeLog(L"Updating registry..");
|
||||
versionStr[versionLen / 2] = 0;
|
||||
HKEY rkey;
|
||||
LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{53F49750-6209-4FBF-9CA8-7A333C87D1ED}_is1", 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &rkey);
|
||||
if (status == ERROR_SUCCESS) {
|
||||
writeLog(L"Checking registry install location..");
|
||||
static const int bufSize = 4096;
|
||||
DWORD locationType, locationSize = bufSize * 2;
|
||||
WCHAR locationStr[bufSize], exp[bufSize];
|
||||
if (RegQueryValueEx(rkey, L"InstallLocation", 0, &locationType, (BYTE*)locationStr, &locationSize) == ERROR_SUCCESS) {
|
||||
locationSize /= 2;
|
||||
if (locationStr[locationSize - 1]) {
|
||||
locationStr[locationSize++] = 0;
|
||||
}
|
||||
if (locationType == REG_EXPAND_SZ) {
|
||||
DWORD copy = ExpandEnvironmentStrings(locationStr, exp, bufSize);
|
||||
if (copy <= bufSize) {
|
||||
memcpy(locationStr, exp, copy * sizeof(WCHAR));
|
||||
}
|
||||
}
|
||||
if (locationType == REG_EXPAND_SZ || locationType == REG_SZ) {
|
||||
if (PathCanonicalize(exp, locationStr)) {
|
||||
memcpy(locationStr, exp, bufSize * sizeof(WCHAR));
|
||||
if (GetFullPathName(L".", bufSize, exp, 0) < bufSize) {
|
||||
wstring installpath = locationStr, mypath = exp;
|
||||
if (installpath == mypath + L"\\" || true) { // always update reg info, if we found it
|
||||
WCHAR nameStr[bufSize], dateStr[bufSize], publisherStr[bufSize], icongroupStr[bufSize];
|
||||
SYSTEMTIME stLocalTime;
|
||||
GetLocalTime(&stLocalTime);
|
||||
RegSetValueEx(rkey, L"DisplayVersion", 0, REG_SZ, (const BYTE*)versionStr, ((versionLen / 2) + 1) * sizeof(WCHAR));
|
||||
wsprintf(nameStr, L"Telegram Desktop");
|
||||
RegSetValueEx(rkey, L"DisplayName", 0, REG_SZ, (const BYTE*)nameStr, (wcslen(nameStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(publisherStr, L"Telegram FZ-LLC");
|
||||
RegSetValueEx(rkey, L"Publisher", 0, REG_SZ, (const BYTE*)publisherStr, (wcslen(publisherStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(icongroupStr, L"Telegram Desktop");
|
||||
RegSetValueEx(rkey, L"Inno Setup: Icon Group", 0, REG_SZ, (const BYTE*)icongroupStr, (wcslen(icongroupStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(dateStr, L"%04d%02d%02d", stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay);
|
||||
RegSetValueEx(rkey, L"InstallDate", 0, REG_SZ, (const BYTE*)dateStr, (wcslen(dateStr) + 1) * sizeof(WCHAR));
|
||||
|
||||
const WCHAR *appURL = L"https://desktop.telegram.org";
|
||||
RegSetValueEx(rkey, L"HelpLink", 0, REG_SZ, (const BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
RegSetValueEx(rkey, L"URLInfoAbout", 0, REG_SZ, (const BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
RegSetValueEx(rkey, L"URLUpdateInfo", 0, REG_SZ, (const BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RegCloseKey(rkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) {
|
||||
base::Platform::InitDynamicLibraries();
|
||||
|
||||
openLog();
|
||||
|
||||
_oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter);
|
||||
// CAPIHook apiHook("kernel32.dll", "SetUnhandledExceptionFilter", (PROC)RedirectedSetUnhandledExceptionFilter);
|
||||
|
||||
writeLog(L"Updaters started..");
|
||||
|
||||
LPWSTR *args;
|
||||
int argsCount;
|
||||
|
||||
bool needupdate = false, autostart = false, debug = false, writeprotected = false, startintray = false;
|
||||
args = CommandLineToArgvW(GetCommandLine(), &argsCount);
|
||||
if (args) {
|
||||
for (int i = 1; i < argsCount; ++i) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
if (equal(args[i], L"-update")) {
|
||||
needupdate = true;
|
||||
} else if (equal(args[i], L"-autostart")) {
|
||||
autostart = true;
|
||||
} else if (equal(args[i], L"-debug")) {
|
||||
debug = _debug = true;
|
||||
openLog();
|
||||
} else if (equal(args[i], L"-startintray")) {
|
||||
startintray = true;
|
||||
} else if (equal(args[i], L"-writeprotected") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
writeprotected = true;
|
||||
updateTo = args[i];
|
||||
for (int j = 0, l = updateTo.size(); j < l; ++j) {
|
||||
if (updateTo[j] == L'/') {
|
||||
updateTo[j] = L'\\';
|
||||
}
|
||||
}
|
||||
} else if (equal(args[i], L"-workdir") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
customWorkingDir = args[i];
|
||||
} else if (equal(args[i], L"-key") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
customKeyFile = args[i];
|
||||
} else if (equal(args[i], L"-exename") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
exeName = args[i];
|
||||
for (int j = 0, l = exeName.size(); j < l; ++j) {
|
||||
if (exeName[j] == L'/' || exeName[j] == L'\\') {
|
||||
exeName = L"Telegram.exe";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exeName.empty()) {
|
||||
exeName = L"Telegram.exe";
|
||||
}
|
||||
if (needupdate) writeLog(L"Need to update!");
|
||||
if (autostart) writeLog(L"From autostart!");
|
||||
if (writeprotected) writeLog(L"Write Protected folder!");
|
||||
if (!customWorkingDir.empty()) writeLog(L"Will pass custom working dir: " + customWorkingDir);
|
||||
|
||||
updaterName = args[0];
|
||||
writeLog(L"Updater name is: " + updaterName);
|
||||
if (updaterName.size() > 11) {
|
||||
if (equal(updaterName.substr(updaterName.size() - 11), L"Updater.exe")) {
|
||||
updaterDir = updaterName.substr(0, updaterName.size() - 11);
|
||||
writeLog(L"Updater dir is: " + updaterDir);
|
||||
if (!writeprotected) {
|
||||
updateTo = updaterDir;
|
||||
}
|
||||
writeLog(L"Update to: " + updateTo);
|
||||
if (needupdate && update()) {
|
||||
updateRegistry();
|
||||
}
|
||||
if (writeprotected) { // if we can't clear all tupdates\ready (Updater.exe is there) - clear only version
|
||||
if (DeleteFile(L"tupdates\\temp\\tdata\\version") || DeleteFile(L"tupdates\\ready\\tdata\\version")) {
|
||||
writeLog(L"Version file deleted!");
|
||||
} else {
|
||||
writeLog(L"Error: could not delete version file");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeLog(L"Error: bad exe name!");
|
||||
}
|
||||
} else {
|
||||
writeLog(L"Error: short exe name!");
|
||||
}
|
||||
LocalFree(args);
|
||||
} else {
|
||||
writeLog(L"Error: No command line arguments!");
|
||||
}
|
||||
|
||||
wstring targs;
|
||||
if (autostart) targs += L" -autostart";
|
||||
if (debug) targs += L" -debug";
|
||||
if (startintray) targs += L" -startintray";
|
||||
if (!customWorkingDir.empty()) {
|
||||
targs += L" -workdir \"" + customWorkingDir + L"\"";
|
||||
}
|
||||
if (!customKeyFile.empty()) {
|
||||
targs += L" -key \"" + customKeyFile + L"\"";
|
||||
}
|
||||
writeLog(L"Result arguments: " + targs);
|
||||
|
||||
bool executed = false;
|
||||
if (writeprotected) { // run un-elevated
|
||||
writeLog(L"Trying to run un-elevated by temp.lnk");
|
||||
|
||||
HRESULT hres = CoInitialize(0);
|
||||
if (SUCCEEDED(hres)) {
|
||||
IShellLink* psl;
|
||||
HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
|
||||
if (SUCCEEDED(hres)) {
|
||||
IPersistFile* ppf;
|
||||
|
||||
wstring exe = updateTo + exeName, dir = updateTo;
|
||||
psl->SetArguments((targs.size() ? targs.substr(1) : targs).c_str());
|
||||
psl->SetPath(exe.c_str());
|
||||
psl->SetWorkingDirectory(dir.c_str());
|
||||
psl->SetDescription(L"");
|
||||
|
||||
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
||||
|
||||
if (SUCCEEDED(hres)) {
|
||||
wstring lnk = L"tupdates\\temp\\temp.lnk";
|
||||
hres = ppf->Save(lnk.c_str(), TRUE);
|
||||
if (!SUCCEEDED(hres)) {
|
||||
lnk = L"tupdates\\ready\\temp.lnk"; // old
|
||||
hres = ppf->Save(lnk.c_str(), TRUE);
|
||||
}
|
||||
ppf->Release();
|
||||
|
||||
if (SUCCEEDED(hres)) {
|
||||
writeLog(L"Executing un-elevated through link..");
|
||||
ShellExecute(0, 0, L"explorer.exe", lnk.c_str(), 0, SW_SHOWNORMAL);
|
||||
executed = true;
|
||||
} else {
|
||||
writeLog(L"Error: ppf->Save failed");
|
||||
}
|
||||
} else {
|
||||
writeLog(L"Error: Could not create interface IID_IPersistFile");
|
||||
}
|
||||
psl->Release();
|
||||
} else {
|
||||
writeLog(L"Error: could not create instance of IID_IShellLink");
|
||||
}
|
||||
CoUninitialize();
|
||||
} else {
|
||||
writeLog(L"Error: Could not initialize COM");
|
||||
}
|
||||
}
|
||||
if (!executed) {
|
||||
ShellExecute(0, 0, (updateTo + exeName).c_str(), (L"-noupdate" + targs).c_str(), 0, SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
writeLog(L"Executed '" + exeName + L"', closing log and quitting..");
|
||||
closeLog();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const WCHAR *_programName = L"Telegram Desktop"; // folder in APPDATA, if current path is unavailable for writing
|
||||
static const WCHAR *_exeName = L"Updater.exe";
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter = 0;
|
||||
|
||||
typedef BOOL (FAR STDAPICALLTYPE *t_miniDumpWriteDump)(
|
||||
_In_ HANDLE hProcess,
|
||||
_In_ DWORD ProcessId,
|
||||
_In_ HANDLE hFile,
|
||||
_In_ MINIDUMP_TYPE DumpType,
|
||||
_In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
|
||||
_In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
|
||||
_In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam
|
||||
);
|
||||
t_miniDumpWriteDump miniDumpWriteDump = 0;
|
||||
|
||||
HANDLE _generateDumpFileAtPath(const WCHAR *path) {
|
||||
static const int maxFileLen = MAX_PATH * 10;
|
||||
|
||||
WCHAR szPath[maxFileLen];
|
||||
wsprintf(szPath, L"%stdata\\", path);
|
||||
if (!CreateDirectory(szPath, NULL)) {
|
||||
if (GetLastError() != ERROR_ALREADY_EXISTS) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
wsprintf(szPath, L"%sdumps\\", path);
|
||||
if (!CreateDirectory(szPath, NULL)) {
|
||||
if (GetLastError() != ERROR_ALREADY_EXISTS) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
WCHAR szFileName[maxFileLen];
|
||||
WCHAR szExeName[maxFileLen];
|
||||
|
||||
wcscpy_s(szExeName, _exeName);
|
||||
WCHAR *dotFrom = wcschr(szExeName, WCHAR(L'.'));
|
||||
if (dotFrom) {
|
||||
wsprintf(dotFrom, L"");
|
||||
}
|
||||
|
||||
SYSTEMTIME stLocalTime;
|
||||
|
||||
GetLocalTime(&stLocalTime);
|
||||
|
||||
wsprintf(
|
||||
szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
|
||||
szPath, szExeName, updaterVersionStr,
|
||||
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
|
||||
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
|
||||
GetCurrentProcessId(), GetCurrentThreadId());
|
||||
return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
|
||||
}
|
||||
|
||||
void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) {
|
||||
static const int maxFileLen = MAX_PATH * 10;
|
||||
|
||||
closeLog();
|
||||
|
||||
HMODULE hDll = LoadLibrary(L"DBGHELP.DLL");
|
||||
if (!hDll) return;
|
||||
|
||||
miniDumpWriteDump = (t_miniDumpWriteDump)GetProcAddress(hDll, "MiniDumpWriteDump");
|
||||
if (!miniDumpWriteDump) return;
|
||||
|
||||
HANDLE hDumpFile = 0;
|
||||
|
||||
WCHAR szPath[maxFileLen];
|
||||
DWORD len = GetModuleFileName(GetModuleHandle(0), szPath, maxFileLen);
|
||||
if (!len) return;
|
||||
|
||||
WCHAR *pathEnd = szPath + len;
|
||||
|
||||
if (!_wcsicmp(pathEnd - wcslen(_exeName), _exeName)) {
|
||||
wsprintf(pathEnd - wcslen(_exeName), L"");
|
||||
hDumpFile = _generateDumpFileAtPath(szPath);
|
||||
}
|
||||
if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) {
|
||||
WCHAR wstrPath[maxFileLen];
|
||||
DWORD wstrPathLen = GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen);
|
||||
if (wstrPathLen) {
|
||||
wsprintf(wstrPath + wstrPathLen, L"\\%s\\", _programName);
|
||||
hDumpFile = _generateDumpFileAtPath(wstrPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
MINIDUMP_EXCEPTION_INFORMATION ExpParam = {0};
|
||||
ExpParam.ThreadId = GetCurrentThreadId();
|
||||
ExpParam.ExceptionPointers = pExceptionPointers;
|
||||
ExpParam.ClientPointers = TRUE;
|
||||
|
||||
miniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);
|
||||
}
|
||||
|
||||
LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) {
|
||||
_generateDump(pExceptionPointers);
|
||||
return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// see http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
|
||||
// When the CRT calls SetUnhandledExceptionFilter with NULL parameter
|
||||
// our handler will not get removed.
|
||||
_oldWndExceptionFilter = lpTopLevelExceptionFilter;
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user