mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-24 15:01:16 +01:00
Move shared Updater logic into UpdaterCommon
This commit is contained in:
parent
25e9339746
commit
9c84524684
@ -16,5 +16,6 @@ if(ENABLE_QT)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
add_subdirectory(UpdaterCommon)
|
||||||
add_subdirectory(MacUpdater)
|
add_subdirectory(MacUpdater)
|
||||||
endif()
|
endif()
|
||||||
|
@ -4,54 +4,19 @@
|
|||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
#include "UICommon/AutoUpdate.h"
|
#include "Common/FileUtil.h"
|
||||||
|
|
||||||
|
#include "UpdaterCommon/UI.h"
|
||||||
|
#include "UpdaterCommon/UpdaterCommon.h"
|
||||||
|
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
#include <OptionParser.h>
|
#include <OptionParser.h>
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <ed25519/ed25519.h>
|
|
||||||
#include <mbedtls/base64.h>
|
|
||||||
#include <mbedtls/sha256.h>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <signal.h>
|
|
||||||
#include <thread>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "Common/CommonPaths.h"
|
|
||||||
#include "Common/CommonTypes.h"
|
|
||||||
#include "Common/FileUtil.h"
|
|
||||||
#include "Common/HttpRequest.h"
|
|
||||||
#include "Common/StringUtil.h"
|
|
||||||
|
|
||||||
#include "MacUpdater/UI.h"
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// Public key used to verify update manifests.
|
|
||||||
const u8 UPDATE_PUB_KEY[] = {0x2a, 0xb3, 0xd1, 0xdc, 0x6e, 0xf5, 0x07, 0xf6, 0xa0, 0x6c, 0x7c,
|
|
||||||
0x54, 0xdf, 0x54, 0xf4, 0x42, 0x80, 0xa6, 0x28, 0x8b, 0x6d, 0x70,
|
|
||||||
0x14, 0xb5, 0x4c, 0x34, 0x95, 0x20, 0x4d, 0xd4, 0xd3, 0x5d};
|
|
||||||
|
|
||||||
const char UPDATE_TEMP_DIR[] = "TempUpdate";
|
|
||||||
|
|
||||||
// Where to log updater output.
|
|
||||||
FILE* log_fp = stderr;
|
|
||||||
|
|
||||||
void FlushLog()
|
|
||||||
{
|
|
||||||
fflush(log_fp);
|
|
||||||
fclose(log_fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal representation of options passed on the command-line.
|
|
||||||
struct Options
|
struct Options
|
||||||
{
|
{
|
||||||
std::string this_manifest_url;
|
std::string this_manifest_url;
|
||||||
@ -131,545 +96,6 @@ std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
|
|||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> GzipInflate(const std::string& data)
|
|
||||||
{
|
|
||||||
z_stream zstrm;
|
|
||||||
zstrm.zalloc = nullptr;
|
|
||||||
zstrm.zfree = nullptr;
|
|
||||||
zstrm.opaque = nullptr;
|
|
||||||
zstrm.avail_in = static_cast<u32>(data.size());
|
|
||||||
zstrm.next_in = reinterpret_cast<u8*>(const_cast<char*>(data.data()));
|
|
||||||
|
|
||||||
// 16 + MAX_WBITS means gzip. Don't ask me.
|
|
||||||
inflateInit2(&zstrm, 16 + MAX_WBITS);
|
|
||||||
|
|
||||||
std::string out;
|
|
||||||
char buffer[4096];
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
zstrm.avail_out = sizeof(buffer);
|
|
||||||
zstrm.next_out = reinterpret_cast<u8*>(buffer);
|
|
||||||
|
|
||||||
ret = inflate(&zstrm, 0);
|
|
||||||
out.append(buffer, sizeof(buffer) - zstrm.avail_out);
|
|
||||||
} while (ret == Z_OK);
|
|
||||||
|
|
||||||
inflateEnd(&zstrm);
|
|
||||||
|
|
||||||
if (ret != Z_STREAM_END)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not read the data as gzip: error %d.\n", ret);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VerifySignature(const std::string& data, const std::string& b64_signature)
|
|
||||||
{
|
|
||||||
u8 signature[64]; // ed25519 sig size.
|
|
||||||
size_t sig_size;
|
|
||||||
|
|
||||||
if (mbedtls_base64_decode(signature, sizeof(signature), &sig_size,
|
|
||||||
reinterpret_cast<const u8*>(b64_signature.data()),
|
|
||||||
b64_signature.size()) ||
|
|
||||||
sig_size != sizeof(signature))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Invalid base64: %s\n", b64_signature.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ed25519_verify(signature, reinterpret_cast<const u8*>(data.data()), data.size(),
|
|
||||||
UPDATE_PUB_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Manifest
|
|
||||||
{
|
|
||||||
using Filename = std::string;
|
|
||||||
using Hash = std::array<u8, 16>;
|
|
||||||
std::map<Filename, Hash> entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool HexDecode(const std::string& hex, u8* buffer, size_t size)
|
|
||||||
{
|
|
||||||
if (hex.size() != size * 2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto DecodeNibble = [](char c) -> std::optional<u8> {
|
|
||||||
if (c >= '0' && c <= '9')
|
|
||||||
return static_cast<u8>(c - '0');
|
|
||||||
else if (c >= 'a' && c <= 'f')
|
|
||||||
return static_cast<u8>(c - 'a' + 10);
|
|
||||||
else if (c >= 'A' && c <= 'F')
|
|
||||||
return static_cast<u8>(c - 'A' + 10);
|
|
||||||
else
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
for (size_t i = 0; i < size; ++i)
|
|
||||||
{
|
|
||||||
std::optional<u8> high = DecodeNibble(hex[2 * i]);
|
|
||||||
std::optional<u8> low = DecodeNibble(hex[2 * i + 1]);
|
|
||||||
|
|
||||||
if (!high || !low)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
buffer[i] = (*high << 4) | *low;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HexEncode(const u8* buffer, size_t size)
|
|
||||||
{
|
|
||||||
std::string out(size * 2, '\0');
|
|
||||||
|
|
||||||
for (size_t i = 0; i < size; ++i)
|
|
||||||
{
|
|
||||||
out[2 * i] = "0123456789abcdef"[buffer[i] >> 4];
|
|
||||||
out[2 * i + 1] = "0123456789abcdef"[buffer[i] & 0xF];
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Manifest> ParseManifest(const std::string& manifest)
|
|
||||||
{
|
|
||||||
Manifest parsed;
|
|
||||||
size_t pos = 0;
|
|
||||||
|
|
||||||
while (pos < manifest.size())
|
|
||||||
{
|
|
||||||
size_t filename_end_pos = manifest.find('\t', pos);
|
|
||||||
if (filename_end_pos == std::string::npos)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest entry %zu: could not find filename end.\n", parsed.entries.size());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
size_t hash_end_pos = manifest.find('\n', filename_end_pos);
|
|
||||||
if (hash_end_pos == std::string::npos)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest entry %zu: could not find hash end.\n", parsed.entries.size());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filename = manifest.substr(pos, filename_end_pos - pos);
|
|
||||||
std::string hash = manifest.substr(filename_end_pos + 1, hash_end_pos - filename_end_pos - 1);
|
|
||||||
if (hash.size() != 32)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(),
|
|
||||||
hash.c_str());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Manifest::Hash decoded_hash;
|
|
||||||
if (!HexDecode(hash, decoded_hash.data(), decoded_hash.size()))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(),
|
|
||||||
hash.c_str());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed.entries[filename] = decoded_hash;
|
|
||||||
pos = hash_end_pos + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not showing a progress bar here because this part is just too quick
|
|
||||||
std::optional<Manifest> FetchAndParseManifest(const std::string& url)
|
|
||||||
{
|
|
||||||
Common::HttpRequest http;
|
|
||||||
|
|
||||||
Common::HttpRequest::Response resp = http.Get(url);
|
|
||||||
if (!resp)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest download failed.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string contents(reinterpret_cast<char*>(resp->data()), resp->size());
|
|
||||||
std::optional<std::string> maybe_decompressed = GzipInflate(contents);
|
|
||||||
if (!maybe_decompressed)
|
|
||||||
return {};
|
|
||||||
std::string decompressed = std::move(*maybe_decompressed);
|
|
||||||
|
|
||||||
// Split into manifest and signature.
|
|
||||||
size_t boundary = decompressed.rfind("\n\n");
|
|
||||||
if (boundary == std::string::npos)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "No signature was found in manifest.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string signature_block = decompressed.substr(boundary + 2); // 2 for "\n\n".
|
|
||||||
decompressed.resize(boundary + 1); // 1 to keep the final "\n".
|
|
||||||
|
|
||||||
std::vector<std::string> signatures = SplitString(signature_block, '\n');
|
|
||||||
bool found_valid_signature = false;
|
|
||||||
for (const auto& signature : signatures)
|
|
||||||
{
|
|
||||||
if (VerifySignature(decompressed, signature))
|
|
||||||
{
|
|
||||||
found_valid_signature = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found_valid_signature)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not verify signature of the manifest.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseManifest(decompressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Represent the operations to be performed by the updater.
|
|
||||||
struct TodoList
|
|
||||||
{
|
|
||||||
struct DownloadOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
Manifest::Hash hash;
|
|
||||||
};
|
|
||||||
std::vector<DownloadOp> to_download;
|
|
||||||
|
|
||||||
struct UpdateOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
std::optional<Manifest::Hash> old_hash;
|
|
||||||
Manifest::Hash new_hash;
|
|
||||||
};
|
|
||||||
std::vector<UpdateOp> to_update;
|
|
||||||
|
|
||||||
struct DeleteOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
Manifest::Hash old_hash;
|
|
||||||
};
|
|
||||||
std::vector<DeleteOp> to_delete;
|
|
||||||
|
|
||||||
void Log() const
|
|
||||||
{
|
|
||||||
if (to_update.size())
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Updating:\n");
|
|
||||||
for (const auto& op : to_update)
|
|
||||||
{
|
|
||||||
std::string old_desc =
|
|
||||||
op.old_hash ? HexEncode(op.old_hash->data(), op.old_hash->size()) : "(new)";
|
|
||||||
fprintf(log_fp, " - %s: %s -> %s\n", op.filename.c_str(), old_desc.c_str(),
|
|
||||||
HexEncode(op.new_hash.data(), op.new_hash.size()).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (to_delete.size())
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Deleting:\n");
|
|
||||||
for (const auto& op : to_delete)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, " - %s (%s)\n", op.filename.c_str(),
|
|
||||||
HexEncode(op.old_hash.data(), op.old_hash.size()).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest)
|
|
||||||
{
|
|
||||||
TodoList todo;
|
|
||||||
|
|
||||||
// Delete if present in this manifest but not in next manifest.
|
|
||||||
for (const auto& entry : this_manifest.entries)
|
|
||||||
{
|
|
||||||
if (next_manifest.entries.find(entry.first) == next_manifest.entries.end())
|
|
||||||
{
|
|
||||||
TodoList::DeleteOp del;
|
|
||||||
del.filename = entry.first;
|
|
||||||
del.old_hash = entry.second;
|
|
||||||
todo.to_delete.push_back(std::move(del));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download and update if present in next manifest with different hash from this manifest.
|
|
||||||
for (const auto& entry : next_manifest.entries)
|
|
||||||
{
|
|
||||||
std::optional<Manifest::Hash> old_hash;
|
|
||||||
|
|
||||||
const auto& old_entry = this_manifest.entries.find(entry.first);
|
|
||||||
if (old_entry != this_manifest.entries.end())
|
|
||||||
old_hash = old_entry->second;
|
|
||||||
|
|
||||||
if (!old_hash || *old_hash != entry.second)
|
|
||||||
{
|
|
||||||
TodoList::DownloadOp download;
|
|
||||||
download.filename = entry.first;
|
|
||||||
download.hash = entry.second;
|
|
||||||
|
|
||||||
todo.to_download.push_back(std::move(download));
|
|
||||||
|
|
||||||
TodoList::UpdateOp update;
|
|
||||||
update.filename = entry.first;
|
|
||||||
update.old_hash = old_hash;
|
|
||||||
update.new_hash = entry.second;
|
|
||||||
todo.to_update.push_back(std::move(update));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return todo;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::string> FindOrCreateTempDir(const std::string& base_path)
|
|
||||||
{
|
|
||||||
std::string temp_path = base_path + DIR_SEP + UPDATE_TEMP_DIR;
|
|
||||||
int counter = 0;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (!File::Exists(temp_path))
|
|
||||||
{
|
|
||||||
if (File::CreateDir(temp_path))
|
|
||||||
{
|
|
||||||
return temp_path;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Couldn't create temp directory.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (File::IsDirectory(temp_path))
|
|
||||||
{
|
|
||||||
return temp_path;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Try again with a counter appended to the path.
|
|
||||||
std::string suffix = UPDATE_TEMP_DIR + std::to_string(counter);
|
|
||||||
temp_path = base_path + DIR_SEP + suffix;
|
|
||||||
}
|
|
||||||
} while (counter++ < 10);
|
|
||||||
|
|
||||||
fprintf(log_fp, "Could not find an appropriate temp directory name. Giving up.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo)
|
|
||||||
{
|
|
||||||
// This is best-effort cleanup, we ignore most errors.
|
|
||||||
for (const auto& download : todo.to_download)
|
|
||||||
File::Delete(temp_dir + DIR_SEP + HexEncode(download.hash.data(), download.hash.size()));
|
|
||||||
File::DeleteDir(temp_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
Manifest::Hash ComputeHash(const std::string& contents)
|
|
||||||
{
|
|
||||||
std::array<u8, 32> full;
|
|
||||||
mbedtls_sha256(reinterpret_cast<const u8*>(contents.data()), contents.size(), full.data(), false);
|
|
||||||
|
|
||||||
Manifest::Hash out;
|
|
||||||
std::copy(full.begin(), full.begin() + 16, out.begin());
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProgressCallback(double total, double now, double, double)
|
|
||||||
{
|
|
||||||
UI::SetCurrentProgress(static_cast<int>(now), static_cast<int>(total));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
|
|
||||||
const std::string& content_base_url, const std::string& temp_path)
|
|
||||||
{
|
|
||||||
Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback);
|
|
||||||
|
|
||||||
UI::SetTotalMarquee(false);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < to_download.size(); i++)
|
|
||||||
{
|
|
||||||
UI::SetTotalProgress(static_cast<int>(i + 1), static_cast<int>(to_download.size()));
|
|
||||||
|
|
||||||
auto& download = to_download[i];
|
|
||||||
|
|
||||||
std::string hash_filename = HexEncode(download.hash.data(), download.hash.size());
|
|
||||||
UI::SetDescription("Downloading " + download.filename + "... (File " + std::to_string(i + 1) +
|
|
||||||
" of " + std::to_string(to_download.size()) + ")");
|
|
||||||
UI::SetCurrentMarquee(false);
|
|
||||||
|
|
||||||
// Add slashes where needed.
|
|
||||||
std::string content_store_path = hash_filename;
|
|
||||||
content_store_path.insert(4, "/");
|
|
||||||
content_store_path.insert(2, "/");
|
|
||||||
|
|
||||||
std::string url = content_base_url + content_store_path;
|
|
||||||
fprintf(log_fp, "Downloading %s ...\n", url.c_str());
|
|
||||||
|
|
||||||
auto resp = req.Get(url);
|
|
||||||
if (!resp)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
UI::SetCurrentMarquee(true);
|
|
||||||
UI::SetDescription("Verifying " + download.filename + "...");
|
|
||||||
|
|
||||||
std::string contents(reinterpret_cast<char*>(resp->data()), resp->size());
|
|
||||||
std::optional<std::string> maybe_decompressed = GzipInflate(contents);
|
|
||||||
if (!maybe_decompressed)
|
|
||||||
return false;
|
|
||||||
std::string decompressed = std::move(*maybe_decompressed);
|
|
||||||
|
|
||||||
// Check that the downloaded contents have the right hash.
|
|
||||||
Manifest::Hash contents_hash = ComputeHash(decompressed);
|
|
||||||
if (contents_hash != download.hash)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Wrong hash on downloaded content %s.\n", url.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string out = temp_path + DIR_SEP + hash_filename;
|
|
||||||
if (!File::WriteStringToFile(decompressed, out))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not write cache file %s.\n", out.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BackupFile(const std::string& path)
|
|
||||||
{
|
|
||||||
std::string backup_path = path + ".bak";
|
|
||||||
fprintf(log_fp, "Backing up unknown pre-existing %s to .bak.\n", path.c_str());
|
|
||||||
if (!File::Rename(path, backup_path))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Cound not rename %s to %s for backup.\n", path.c_str(), backup_path.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
|
|
||||||
const std::string& install_base_path, const std::string& temp_path)
|
|
||||||
{
|
|
||||||
for (const auto& op : to_update)
|
|
||||||
{
|
|
||||||
std::string path = install_base_path + DIR_SEP + op.filename;
|
|
||||||
if (!File::CreateFullPath(path))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not create directory structure for %s.\n", op.filename.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: A new updater protocol version is required to properly mark executable files. For
|
|
||||||
// now, copy executable bits from existing files. This will break for newly added executables.
|
|
||||||
std::optional<mode_t> permission;
|
|
||||||
|
|
||||||
if (File::Exists(path))
|
|
||||||
{
|
|
||||||
struct stat file_stats;
|
|
||||||
|
|
||||||
if (stat(path.c_str(), &file_stats) != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
permission = file_stats.st_mode;
|
|
||||||
|
|
||||||
std::string contents;
|
|
||||||
if (!File::ReadFileToString(path, contents))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not read existing file %s.\n", op.filename.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Manifest::Hash contents_hash = ComputeHash(contents);
|
|
||||||
if (contents_hash == op.new_hash)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "File %s was already up to date. Partial update?\n", op.filename.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (!op.old_hash || contents_hash != *op.old_hash)
|
|
||||||
{
|
|
||||||
if (!BackupFile(path))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can safely move the new contents to the location.
|
|
||||||
std::string content_filename = HexEncode(op.new_hash.data(), op.new_hash.size());
|
|
||||||
fprintf(log_fp, "Updating file %s from content %s...\n", op.filename.c_str(),
|
|
||||||
content_filename.c_str());
|
|
||||||
if (!File::Copy(temp_path + DIR_SEP + content_filename, path))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not update file %s.\n", op.filename.c_str());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission.has_value() && chmod(path.c_str(), permission.value()) != 0)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
|
|
||||||
const std::string& install_base_path)
|
|
||||||
{
|
|
||||||
for (const auto& op : to_delete)
|
|
||||||
{
|
|
||||||
std::string path = install_base_path + DIR_SEP + op.filename;
|
|
||||||
|
|
||||||
if (!File::Exists(path))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "File %s is already missing.\n", op.filename.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string contents;
|
|
||||||
if (!File::ReadFileToString(path, contents))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not read file planned for deletion: %s.\n", op.filename.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Manifest::Hash contents_hash = ComputeHash(contents);
|
|
||||||
if (contents_hash != op.old_hash)
|
|
||||||
{
|
|
||||||
if (!BackupFile(path))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
File::Delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PerformUpdate(const TodoList& todo, const std::string& install_base_path,
|
|
||||||
const std::string& content_base_url, const std::string& temp_path)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Starting download step...\n");
|
|
||||||
if (!DownloadContent(todo.to_download, content_base_url, temp_path))
|
|
||||||
return false;
|
|
||||||
fprintf(log_fp, "Download step completed.\n");
|
|
||||||
|
|
||||||
fprintf(log_fp, "Starting update step...\n");
|
|
||||||
if (!UpdateFiles(todo.to_update, install_base_path, temp_path))
|
|
||||||
return false;
|
|
||||||
fprintf(log_fp, "Update step completed.\n");
|
|
||||||
|
|
||||||
fprintf(log_fp, "Starting deletion step...\n");
|
|
||||||
if (!DeleteObsoleteFiles(todo.to_delete, install_base_path))
|
|
||||||
return false;
|
|
||||||
fprintf(log_fp, "Deletion step completed.\n");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FatalError(const std::string& message)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "%s\n", message.c_str());
|
|
||||||
|
|
||||||
UI::Error(message);
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@interface AppDelegate ()
|
@interface AppDelegate ()
|
||||||
|
@ -6,8 +6,7 @@ set(SOURCES
|
|||||||
AppDelegate.mm
|
AppDelegate.mm
|
||||||
ViewController.h
|
ViewController.h
|
||||||
ViewController.m
|
ViewController.m
|
||||||
UI.h
|
MacUI.mm
|
||||||
UI.mm
|
|
||||||
${STORYBOARDS}
|
${STORYBOARDS}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,10 +30,8 @@ target_link_libraries(MacUpdater PRIVATE
|
|||||||
"-framework AppKit"
|
"-framework AppKit"
|
||||||
"-framework CoreData"
|
"-framework CoreData"
|
||||||
"-framework Foundation"
|
"-framework Foundation"
|
||||||
uicommon
|
uicommon
|
||||||
mbedtls
|
updatercommon
|
||||||
z
|
|
||||||
ed25519
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compile storyboards (Adapted from https://gitlab.kitware.com/cmake/community/wikis/doc/tutorials/OSX-InterfaceBuilderFiles)
|
# Compile storyboards (Adapted from https://gitlab.kitware.com/cmake/community/wikis/doc/tutorials/OSX-InterfaceBuilderFiles)
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
// Licensed under GPLv2+
|
// Licensed under GPLv2+
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "MacUpdater/UI.h"
|
|
||||||
#include "MacUpdater/ViewController.h"
|
#include "MacUpdater/ViewController.h"
|
||||||
|
|
||||||
|
#include "UpdaterCommon/UI.h"
|
||||||
|
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -105,3 +106,7 @@ void UI::SetTotalProgress(int current, int total)
|
|||||||
{
|
{
|
||||||
run_on_main([&] { [GetView() SetTotalProgress:(double)current total:(double)total]; });
|
run_on_main([&] { [GetView() SetTotalProgress:(double)current total:(double)total]; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UI::Stop()
|
||||||
|
{
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
// Copyright 2019 Dolphin Emulator Project
|
|
||||||
// Licensed under GPLv2+
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace UI
|
|
||||||
{
|
|
||||||
void Error(const std::string& text);
|
|
||||||
|
|
||||||
void SetVisible(bool visible);
|
|
||||||
|
|
||||||
void SetDescription(const std::string& text);
|
|
||||||
|
|
||||||
void SetTotalMarquee(bool marquee);
|
|
||||||
void ResetTotalProgress();
|
|
||||||
void SetTotalProgress(int current, int total);
|
|
||||||
|
|
||||||
void SetCurrentMarquee(bool marquee);
|
|
||||||
void ResetCurrentProgress();
|
|
||||||
void SetCurrentProgress(int current, int total);
|
|
||||||
} // namespace UI
|
|
@ -6,45 +6,22 @@
|
|||||||
#include <ShlObj.h>
|
#include <ShlObj.h>
|
||||||
|
|
||||||
#include <OptionParser.h>
|
#include <OptionParser.h>
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <ed25519/ed25519.h>
|
|
||||||
#include <mbedtls/base64.h>
|
|
||||||
#include <mbedtls/sha256.h>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
#include "Common/CommonPaths.h"
|
#include "Common/CommonPaths.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
#include "Common/HttpRequest.h"
|
|
||||||
#include "Common/StringUtil.h"
|
|
||||||
|
|
||||||
#include "Updater/UI.h"
|
#include "UpdaterCommon/UI.h"
|
||||||
|
#include "UpdaterCommon/UpdaterCommon.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// Public key used to verify update manifests.
|
|
||||||
const u8 UPDATE_PUB_KEY[] = {0x2a, 0xb3, 0xd1, 0xdc, 0x6e, 0xf5, 0x07, 0xf6, 0xa0, 0x6c, 0x7c,
|
|
||||||
0x54, 0xdf, 0x54, 0xf4, 0x42, 0x80, 0xa6, 0x28, 0x8b, 0x6d, 0x70,
|
|
||||||
0x14, 0xb5, 0x4c, 0x34, 0x95, 0x20, 0x4d, 0xd4, 0xd3, 0x5d};
|
|
||||||
|
|
||||||
const char UPDATE_TEMP_DIR[] = "TempUpdate";
|
|
||||||
|
|
||||||
// Where to log updater output.
|
|
||||||
FILE* log_fp = stderr;
|
|
||||||
|
|
||||||
void FlushLog()
|
|
||||||
{
|
|
||||||
fflush(log_fp);
|
|
||||||
fclose(log_fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal representation of options passed on the command-line.
|
// Internal representation of options passed on the command-line.
|
||||||
struct Options
|
struct Options
|
||||||
{
|
{
|
||||||
@ -142,530 +119,7 @@ std::optional<Options> ParseCommandLine(PCWSTR command_line)
|
|||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
}; // namespace
|
||||||
std::optional<std::string> GzipInflate(const std::string& data)
|
|
||||||
{
|
|
||||||
z_stream zstrm;
|
|
||||||
zstrm.zalloc = nullptr;
|
|
||||||
zstrm.zfree = nullptr;
|
|
||||||
zstrm.opaque = nullptr;
|
|
||||||
zstrm.avail_in = static_cast<u32>(data.size());
|
|
||||||
zstrm.next_in = reinterpret_cast<u8*>(const_cast<char*>(data.data()));
|
|
||||||
|
|
||||||
// 16 + MAX_WBITS means gzip. Don't ask me.
|
|
||||||
inflateInit2(&zstrm, 16 + MAX_WBITS);
|
|
||||||
|
|
||||||
std::string out;
|
|
||||||
char buffer[4096];
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
zstrm.avail_out = sizeof(buffer);
|
|
||||||
zstrm.next_out = reinterpret_cast<u8*>(buffer);
|
|
||||||
|
|
||||||
ret = inflate(&zstrm, 0);
|
|
||||||
out.append(buffer, sizeof(buffer) - zstrm.avail_out);
|
|
||||||
} while (ret == Z_OK);
|
|
||||||
|
|
||||||
inflateEnd(&zstrm);
|
|
||||||
|
|
||||||
if (ret != Z_STREAM_END)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not read the data as gzip: error %d.\n", ret);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VerifySignature(const std::string& data, const std::string& b64_signature)
|
|
||||||
{
|
|
||||||
u8 signature[64]; // ed25519 sig size.
|
|
||||||
size_t sig_size;
|
|
||||||
|
|
||||||
if (mbedtls_base64_decode(signature, sizeof(signature), &sig_size,
|
|
||||||
reinterpret_cast<const u8*>(b64_signature.data()),
|
|
||||||
b64_signature.size()) ||
|
|
||||||
sig_size != sizeof(signature))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Invalid base64: %s\n", b64_signature.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ed25519_verify(signature, reinterpret_cast<const u8*>(data.data()), data.size(),
|
|
||||||
UPDATE_PUB_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Manifest
|
|
||||||
{
|
|
||||||
using Filename = std::string;
|
|
||||||
using Hash = std::array<u8, 16>;
|
|
||||||
std::map<Filename, Hash> entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool HexDecode(const std::string& hex, u8* buffer, size_t size)
|
|
||||||
{
|
|
||||||
if (hex.size() != size * 2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto DecodeNibble = [](char c) -> std::optional<u8> {
|
|
||||||
if (c >= '0' && c <= '9')
|
|
||||||
return static_cast<u8>(c - '0');
|
|
||||||
else if (c >= 'a' && c <= 'f')
|
|
||||||
return static_cast<u8>(c - 'a' + 10);
|
|
||||||
else if (c >= 'A' && c <= 'F')
|
|
||||||
return static_cast<u8>(c - 'A' + 10);
|
|
||||||
else
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
for (size_t i = 0; i < size; ++i)
|
|
||||||
{
|
|
||||||
std::optional<u8> high = DecodeNibble(hex[2 * i]);
|
|
||||||
std::optional<u8> low = DecodeNibble(hex[2 * i + 1]);
|
|
||||||
|
|
||||||
if (!high || !low)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
buffer[i] = (*high << 4) | *low;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HexEncode(const u8* buffer, size_t size)
|
|
||||||
{
|
|
||||||
std::string out(size * 2, '\0');
|
|
||||||
|
|
||||||
for (size_t i = 0; i < size; ++i)
|
|
||||||
{
|
|
||||||
out[2 * i] = "0123456789abcdef"[buffer[i] >> 4];
|
|
||||||
out[2 * i + 1] = "0123456789abcdef"[buffer[i] & 0xF];
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Manifest> ParseManifest(const std::string& manifest)
|
|
||||||
{
|
|
||||||
Manifest parsed;
|
|
||||||
size_t pos = 0;
|
|
||||||
|
|
||||||
while (pos < manifest.size())
|
|
||||||
{
|
|
||||||
size_t filename_end_pos = manifest.find('\t', pos);
|
|
||||||
if (filename_end_pos == std::string::npos)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest entry %zu: could not find filename end.\n", parsed.entries.size());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
size_t hash_end_pos = manifest.find('\n', filename_end_pos);
|
|
||||||
if (hash_end_pos == std::string::npos)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest entry %zu: could not find hash end.\n", parsed.entries.size());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filename = manifest.substr(pos, filename_end_pos - pos);
|
|
||||||
std::string hash = manifest.substr(filename_end_pos + 1, hash_end_pos - filename_end_pos - 1);
|
|
||||||
if (hash.size() != 32)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(),
|
|
||||||
hash.c_str());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Manifest::Hash decoded_hash;
|
|
||||||
if (!HexDecode(hash, decoded_hash.data(), decoded_hash.size()))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(),
|
|
||||||
hash.c_str());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed.entries[filename] = decoded_hash;
|
|
||||||
pos = hash_end_pos + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not showing a progress bar here because this part is just too quick
|
|
||||||
std::optional<Manifest> FetchAndParseManifest(const std::string& url)
|
|
||||||
{
|
|
||||||
Common::HttpRequest http;
|
|
||||||
|
|
||||||
Common::HttpRequest::Response resp = http.Get(url);
|
|
||||||
if (!resp)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Manifest download failed.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string contents(reinterpret_cast<char*>(resp->data()), resp->size());
|
|
||||||
std::optional<std::string> maybe_decompressed = GzipInflate(contents);
|
|
||||||
if (!maybe_decompressed)
|
|
||||||
return {};
|
|
||||||
std::string decompressed = std::move(*maybe_decompressed);
|
|
||||||
|
|
||||||
// Split into manifest and signature.
|
|
||||||
size_t boundary = decompressed.rfind("\n\n");
|
|
||||||
if (boundary == std::string::npos)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "No signature was found in manifest.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string signature_block = decompressed.substr(boundary + 2); // 2 for "\n\n".
|
|
||||||
decompressed.resize(boundary + 1); // 1 to keep the final "\n".
|
|
||||||
|
|
||||||
std::vector<std::string> signatures = SplitString(signature_block, '\n');
|
|
||||||
bool found_valid_signature = false;
|
|
||||||
for (const auto& signature : signatures)
|
|
||||||
{
|
|
||||||
if (VerifySignature(decompressed, signature))
|
|
||||||
{
|
|
||||||
found_valid_signature = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found_valid_signature)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not verify signature of the manifest.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseManifest(decompressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Represent the operations to be performed by the updater.
|
|
||||||
struct TodoList
|
|
||||||
{
|
|
||||||
struct DownloadOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
Manifest::Hash hash;
|
|
||||||
};
|
|
||||||
std::vector<DownloadOp> to_download;
|
|
||||||
|
|
||||||
struct UpdateOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
std::optional<Manifest::Hash> old_hash;
|
|
||||||
Manifest::Hash new_hash;
|
|
||||||
};
|
|
||||||
std::vector<UpdateOp> to_update;
|
|
||||||
|
|
||||||
struct DeleteOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
Manifest::Hash old_hash;
|
|
||||||
};
|
|
||||||
std::vector<DeleteOp> to_delete;
|
|
||||||
|
|
||||||
void Log() const
|
|
||||||
{
|
|
||||||
if (to_update.size())
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Updating:\n");
|
|
||||||
for (const auto& op : to_update)
|
|
||||||
{
|
|
||||||
std::string old_desc =
|
|
||||||
op.old_hash ? HexEncode(op.old_hash->data(), op.old_hash->size()) : "(new)";
|
|
||||||
fprintf(log_fp, " - %s: %s -> %s\n", op.filename.c_str(), old_desc.c_str(),
|
|
||||||
HexEncode(op.new_hash.data(), op.new_hash.size()).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (to_delete.size())
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Deleting:\n");
|
|
||||||
for (const auto& op : to_delete)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, " - %s (%s)\n", op.filename.c_str(),
|
|
||||||
HexEncode(op.old_hash.data(), op.old_hash.size()).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest)
|
|
||||||
{
|
|
||||||
TodoList todo;
|
|
||||||
|
|
||||||
// Delete if present in this manifest but not in next manifest.
|
|
||||||
for (const auto& entry : this_manifest.entries)
|
|
||||||
{
|
|
||||||
if (next_manifest.entries.find(entry.first) == next_manifest.entries.end())
|
|
||||||
{
|
|
||||||
TodoList::DeleteOp del;
|
|
||||||
del.filename = entry.first;
|
|
||||||
del.old_hash = entry.second;
|
|
||||||
todo.to_delete.push_back(std::move(del));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download and update if present in next manifest with different hash from this manifest.
|
|
||||||
for (const auto& entry : next_manifest.entries)
|
|
||||||
{
|
|
||||||
std::optional<Manifest::Hash> old_hash;
|
|
||||||
|
|
||||||
const auto& old_entry = this_manifest.entries.find(entry.first);
|
|
||||||
if (old_entry != this_manifest.entries.end())
|
|
||||||
old_hash = old_entry->second;
|
|
||||||
|
|
||||||
if (!old_hash || *old_hash != entry.second)
|
|
||||||
{
|
|
||||||
TodoList::DownloadOp download;
|
|
||||||
download.filename = entry.first;
|
|
||||||
download.hash = entry.second;
|
|
||||||
|
|
||||||
todo.to_download.push_back(std::move(download));
|
|
||||||
|
|
||||||
TodoList::UpdateOp update;
|
|
||||||
update.filename = entry.first;
|
|
||||||
update.old_hash = old_hash;
|
|
||||||
update.new_hash = entry.second;
|
|
||||||
todo.to_update.push_back(std::move(update));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return todo;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::string> FindOrCreateTempDir(const std::string& base_path)
|
|
||||||
{
|
|
||||||
std::string temp_path = base_path + DIR_SEP + UPDATE_TEMP_DIR;
|
|
||||||
int counter = 0;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (!File::Exists(temp_path))
|
|
||||||
{
|
|
||||||
if (File::CreateDir(temp_path))
|
|
||||||
return temp_path;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Couldn't create temp directory.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (File::IsDirectory(temp_path))
|
|
||||||
{
|
|
||||||
return temp_path;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Try again with a counter appended to the path.
|
|
||||||
std::string suffix = UPDATE_TEMP_DIR + std::to_string(counter);
|
|
||||||
temp_path = base_path + DIR_SEP + suffix;
|
|
||||||
}
|
|
||||||
} while (counter++ < 10);
|
|
||||||
|
|
||||||
fprintf(log_fp, "Could not find an appropriate temp directory name. Giving up.\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo)
|
|
||||||
{
|
|
||||||
// This is best-effort cleanup, we ignore most errors.
|
|
||||||
for (const auto& download : todo.to_download)
|
|
||||||
File::Delete(temp_dir + DIR_SEP + HexEncode(download.hash.data(), download.hash.size()));
|
|
||||||
File::DeleteDir(temp_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
Manifest::Hash ComputeHash(const std::string& contents)
|
|
||||||
{
|
|
||||||
std::array<u8, 32> full;
|
|
||||||
mbedtls_sha256(reinterpret_cast<const u8*>(contents.data()), contents.size(), full.data(), false);
|
|
||||||
|
|
||||||
Manifest::Hash out;
|
|
||||||
std::copy(full.begin(), full.begin() + 16, out.begin());
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProgressCallback(double total, double now, double, double)
|
|
||||||
{
|
|
||||||
UI::SetCurrentProgress(static_cast<int>(now), static_cast<int>(total));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
|
|
||||||
const std::string& content_base_url, const std::string& temp_path)
|
|
||||||
{
|
|
||||||
Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback);
|
|
||||||
|
|
||||||
UI::SetTotalMarquee(false);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < to_download.size(); i++)
|
|
||||||
{
|
|
||||||
UI::SetTotalProgress(static_cast<int>(i + 1), static_cast<int>(to_download.size()));
|
|
||||||
|
|
||||||
auto& download = to_download[i];
|
|
||||||
|
|
||||||
std::string hash_filename = HexEncode(download.hash.data(), download.hash.size());
|
|
||||||
UI::SetDescription("Downloading " + download.filename + "... (File " + std::to_string(i + 1) +
|
|
||||||
" of " + std::to_string(to_download.size()) + ")");
|
|
||||||
UI::SetCurrentMarquee(false);
|
|
||||||
|
|
||||||
// Add slashes where needed.
|
|
||||||
std::string content_store_path = hash_filename;
|
|
||||||
content_store_path.insert(4, "/");
|
|
||||||
content_store_path.insert(2, "/");
|
|
||||||
|
|
||||||
std::string url = content_base_url + content_store_path;
|
|
||||||
fprintf(log_fp, "Downloading %s ...\n", url.c_str());
|
|
||||||
|
|
||||||
auto resp = req.Get(url);
|
|
||||||
if (!resp)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
UI::SetCurrentMarquee(true);
|
|
||||||
UI::SetDescription("Verifying " + download.filename + "...");
|
|
||||||
|
|
||||||
std::string contents(reinterpret_cast<char*>(resp->data()), resp->size());
|
|
||||||
std::optional<std::string> maybe_decompressed = GzipInflate(contents);
|
|
||||||
if (!maybe_decompressed)
|
|
||||||
return false;
|
|
||||||
std::string decompressed = std::move(*maybe_decompressed);
|
|
||||||
|
|
||||||
// Check that the downloaded contents have the right hash.
|
|
||||||
Manifest::Hash contents_hash = ComputeHash(decompressed);
|
|
||||||
if (contents_hash != download.hash)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Wrong hash on downloaded content %s.\n", url.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string out = temp_path + DIR_SEP + hash_filename;
|
|
||||||
if (!File::WriteStringToFile(decompressed, out))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not write cache file %s.\n", out.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BackupFile(const std::string& path)
|
|
||||||
{
|
|
||||||
std::string backup_path = path + ".bak";
|
|
||||||
fprintf(log_fp, "Backing up unknown pre-existing %s to .bak.\n", path.c_str());
|
|
||||||
if (!File::Rename(path, backup_path))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Cound not rename %s to %s for backup.\n", path.c_str(), backup_path.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
|
|
||||||
const std::string& install_base_path, const std::string& temp_path)
|
|
||||||
{
|
|
||||||
for (const auto& op : to_update)
|
|
||||||
{
|
|
||||||
std::string path = install_base_path + DIR_SEP + op.filename;
|
|
||||||
if (!File::CreateFullPath(path))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not create directory structure for %s.\n", op.filename.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File::Exists(path))
|
|
||||||
{
|
|
||||||
std::string contents;
|
|
||||||
if (!File::ReadFileToString(path, contents))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not read existing file %s.\n", op.filename.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Manifest::Hash contents_hash = ComputeHash(contents);
|
|
||||||
if (contents_hash == op.new_hash)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "File %s was already up to date. Partial update?\n", op.filename.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (!op.old_hash || contents_hash != *op.old_hash)
|
|
||||||
{
|
|
||||||
if (!BackupFile(path))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can safely move the new contents to the location.
|
|
||||||
std::string content_filename = HexEncode(op.new_hash.data(), op.new_hash.size());
|
|
||||||
fprintf(log_fp, "Updating file %s from content %s...\n", op.filename.c_str(),
|
|
||||||
content_filename.c_str());
|
|
||||||
if (!File::Copy(temp_path + DIR_SEP + content_filename, path))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not update file %s.\n", op.filename.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
|
|
||||||
const std::string& install_base_path)
|
|
||||||
{
|
|
||||||
for (const auto& op : to_delete)
|
|
||||||
{
|
|
||||||
std::string path = install_base_path + DIR_SEP + op.filename;
|
|
||||||
|
|
||||||
if (!File::Exists(path))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "File %s is already missing.\n", op.filename.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string contents;
|
|
||||||
if (!File::ReadFileToString(path, contents))
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Could not read file planned for deletion: %s.\n", op.filename.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Manifest::Hash contents_hash = ComputeHash(contents);
|
|
||||||
if (contents_hash != op.old_hash)
|
|
||||||
{
|
|
||||||
if (!BackupFile(path))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
File::Delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PerformUpdate(const TodoList& todo, const std::string& install_base_path,
|
|
||||||
const std::string& content_base_url, const std::string& temp_path)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Starting download step...\n");
|
|
||||||
if (!DownloadContent(todo.to_download, content_base_url, temp_path))
|
|
||||||
return false;
|
|
||||||
fprintf(log_fp, "Download step completed.\n");
|
|
||||||
|
|
||||||
fprintf(log_fp, "Starting update step...\n");
|
|
||||||
if (!UpdateFiles(todo.to_update, install_base_path, temp_path))
|
|
||||||
return false;
|
|
||||||
fprintf(log_fp, "Update step completed.\n");
|
|
||||||
|
|
||||||
fprintf(log_fp, "Starting deletion step...\n");
|
|
||||||
if (!DeleteObsoleteFiles(todo.to_delete, install_base_path))
|
|
||||||
return false;
|
|
||||||
fprintf(log_fp, "Deletion step completed.\n");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FatalError(const std::string& message)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "%s\n", message.c_str());
|
|
||||||
|
|
||||||
UI::Error(message);
|
|
||||||
UI::Stop();
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
|
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
|
||||||
{
|
{
|
||||||
|
@ -45,25 +45,16 @@
|
|||||||
<ProjectReference Include="..\..\..\Externals\cpp-optparse\cpp-optparse.vcxproj">
|
<ProjectReference Include="..\..\..\Externals\cpp-optparse\cpp-optparse.vcxproj">
|
||||||
<Project>{c636d9d1-82fe-42b5-9987-63b7d4836341}</Project>
|
<Project>{c636d9d1-82fe-42b5-9987-63b7d4836341}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\..\..\Externals\curl\curl.vcxproj">
|
|
||||||
<Project>{bb00605c-125f-4a21-b33b-7bf418322dcb}</Project>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\..\..\externals\ed25519\ed25519.vcxproj">
|
|
||||||
<Project>{5bdf4b91-1491-4fb0-bc27-78e9a8e97dc3}</Project>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\..\..\Externals\mbedtls\mbedTLS.vcxproj">
|
|
||||||
<Project>{bdb6578b-0691-4e80-a46c-df21639fd3b8}</Project>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\..\..\Externals\zlib\zlib.vcxproj">
|
|
||||||
<Project>{ff213b23-2c26-4214-9f88-85271e557e87}</Project>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\Common\Common.vcxproj">
|
<ProjectReference Include="..\Common\Common.vcxproj">
|
||||||
<Project>{2e6c348c-c75c-4d94-8d1e-9c1fcbf3efe4}</Project>
|
<Project>{2e6c348c-c75c-4d94-8d1e-9c1fcbf3efe4}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\UpdaterCommon\UpdaterCommon.vcxproj">
|
||||||
|
<Project>{B001D13E-7EAB-4689-842D-801E5ACFFAC5}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Main.cpp" />
|
<ClCompile Include="Main.cpp" />
|
||||||
<ClCompile Include="UI.cpp" />
|
<ClCompile Include="WinUI.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
@ -72,9 +63,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<SourceFiles Include="$(TargetPath)" />
|
<SourceFiles Include="$(TargetPath)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="UI.h" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Manifest Include="Updater.exe.manifest" />
|
<Manifest Include="Updater.exe.manifest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -82,4 +70,4 @@
|
|||||||
<Message Text="Copy: @(SourceFiles) -> $(BinaryOutputDir)" Importance="High" />
|
<Message Text="Copy: @(SourceFiles) -> $(BinaryOutputDir)" Importance="High" />
|
||||||
<Copy SourceFiles="@(SourceFiles)" DestinationFolder="$(BinaryOutputDir)" />
|
<Copy SourceFiles="@(SourceFiles)" DestinationFolder="$(BinaryOutputDir)" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -2,12 +2,9 @@
|
|||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Main.cpp" />
|
<ClCompile Include="Main.cpp" />
|
||||||
<ClCompile Include="UI.cpp" />
|
<ClCompile Include="WinUI.cpp" />
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="UI.h" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Manifest Include="Updater.exe.manifest" />
|
<Manifest Include="Updater.exe.manifest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
// Licensed under GPLv2+
|
// Licensed under GPLv2+
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "Updater/UI.h"
|
#include "UpdaterCommon/UI.h"
|
||||||
|
|
||||||
#include <CommCtrl.h>
|
|
||||||
#include <ShObjIdl.h>
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <CommCtrl.h>
|
||||||
|
#include <ShObjIdl.h>
|
||||||
|
|
||||||
#include "Common/Flag.h"
|
#include "Common/Flag.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
|
8
Source/Core/UpdaterCommon/CMakeLists.txt
Normal file
8
Source/Core/UpdaterCommon/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
add_library(updatercommon
|
||||||
|
UpdaterCommon.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(updatercommon PRIVATE
|
||||||
|
uicommon
|
||||||
|
mbedtls
|
||||||
|
z
|
||||||
|
ed25519)
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <string>
|
||||||
|
|
||||||
namespace UI
|
namespace UI
|
||||||
{
|
{
|
||||||
@ -21,4 +21,6 @@ void SetTotalProgress(int current, int total);
|
|||||||
void SetCurrentMarquee(bool marquee);
|
void SetCurrentMarquee(bool marquee);
|
||||||
void ResetCurrentProgress();
|
void ResetCurrentProgress();
|
||||||
void SetCurrentProgress(int current, int total);
|
void SetCurrentProgress(int current, int total);
|
||||||
|
|
||||||
|
void SetVisible(bool visible);
|
||||||
} // namespace UI
|
} // namespace UI
|
549
Source/Core/UpdaterCommon/UpdaterCommon.cpp
Normal file
549
Source/Core/UpdaterCommon/UpdaterCommon.cpp
Normal file
@ -0,0 +1,549 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "UpdaterCommon/UpdaterCommon.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <ed25519/ed25519.h>
|
||||||
|
#include <mbedtls/base64.h>
|
||||||
|
#include <mbedtls/sha256.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
#include "Common/CommonPaths.h"
|
||||||
|
#include "Common/FileUtil.h"
|
||||||
|
#include "Common/HttpRequest.h"
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
#include "UpdaterCommon/UI.h"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Where to log updater output.
|
||||||
|
FILE* log_fp = stderr;
|
||||||
|
|
||||||
|
// Public key used to verify update manifests.
|
||||||
|
const std::array<u8, 32> UPDATE_PUB_KEY = {
|
||||||
|
0x2a, 0xb3, 0xd1, 0xdc, 0x6e, 0xf5, 0x07, 0xf6, 0xa0, 0x6c, 0x7c, 0x54, 0xdf, 0x54, 0xf4, 0x42,
|
||||||
|
0x80, 0xa6, 0x28, 0x8b, 0x6d, 0x70, 0x14, 0xb5, 0x4c, 0x34, 0x95, 0x20, 0x4d, 0xd4, 0xd3, 0x5d};
|
||||||
|
|
||||||
|
const char UPDATE_TEMP_DIR[] = "TempUpdate";
|
||||||
|
|
||||||
|
static bool ProgressCallback(double total, double now, double, double)
|
||||||
|
{
|
||||||
|
UI::SetCurrentProgress(static_cast<int>(now), static_cast<int>(total));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string HexEncode(const u8* buffer, size_t size)
|
||||||
|
{
|
||||||
|
std::string out(size * 2, '\0');
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
out[2 * i] = "0123456789abcdef"[buffer[i] >> 4];
|
||||||
|
out[2 * i + 1] = "0123456789abcdef"[buffer[i] & 0xF];
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool HexDecode(const std::string& hex, u8* buffer, size_t size)
|
||||||
|
{
|
||||||
|
if (hex.size() != size * 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto DecodeNibble = [](char c) -> std::optional<u8> {
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return static_cast<u8>(c - '0');
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
return static_cast<u8>(c - 'a' + 10);
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
return static_cast<u8>(c - 'A' + 10);
|
||||||
|
else
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
std::optional<u8> high = DecodeNibble(hex[2 * i]);
|
||||||
|
std::optional<u8> low = DecodeNibble(hex[2 * i + 1]);
|
||||||
|
|
||||||
|
if (!high || !low)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
buffer[i] = (*high << 4) | *low;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<std::string> GzipInflate(const std::string& data)
|
||||||
|
{
|
||||||
|
z_stream zstrm;
|
||||||
|
zstrm.zalloc = nullptr;
|
||||||
|
zstrm.zfree = nullptr;
|
||||||
|
zstrm.opaque = nullptr;
|
||||||
|
zstrm.avail_in = static_cast<u32>(data.size());
|
||||||
|
zstrm.next_in = reinterpret_cast<u8*>(const_cast<char*>(data.data()));
|
||||||
|
|
||||||
|
// 16 + MAX_WBITS means gzip. Don't ask me.
|
||||||
|
inflateInit2(&zstrm, 16 + MAX_WBITS);
|
||||||
|
|
||||||
|
std::string out;
|
||||||
|
char buffer[4096];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
zstrm.avail_out = sizeof(buffer);
|
||||||
|
zstrm.next_out = reinterpret_cast<u8*>(buffer);
|
||||||
|
|
||||||
|
ret = inflate(&zstrm, 0);
|
||||||
|
out.append(buffer, sizeof(buffer) - zstrm.avail_out);
|
||||||
|
} while (ret == Z_OK);
|
||||||
|
|
||||||
|
inflateEnd(&zstrm);
|
||||||
|
|
||||||
|
if (ret != Z_STREAM_END)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Could not read the data as gzip: error %d.\n", ret);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Manifest::Hash ComputeHash(const std::string& contents)
|
||||||
|
{
|
||||||
|
std::array<u8, 32> full;
|
||||||
|
mbedtls_sha256(reinterpret_cast<const u8*>(contents.data()), contents.size(), full.data(), false);
|
||||||
|
|
||||||
|
Manifest::Hash out;
|
||||||
|
std::copy(full.begin(), full.begin() + 16, out.begin());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool VerifySignature(const std::string& data, const std::string& b64_signature)
|
||||||
|
{
|
||||||
|
u8 signature[64]; // ed25519 sig size.
|
||||||
|
size_t sig_size;
|
||||||
|
|
||||||
|
if (mbedtls_base64_decode(signature, sizeof(signature), &sig_size,
|
||||||
|
reinterpret_cast<const u8*>(b64_signature.data()),
|
||||||
|
b64_signature.size()) ||
|
||||||
|
sig_size != sizeof(signature))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Invalid base64: %s\n", b64_signature.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ed25519_verify(signature, reinterpret_cast<const u8*>(data.data()), data.size(),
|
||||||
|
UPDATE_PUB_KEY.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlushLog()
|
||||||
|
{
|
||||||
|
fflush(log_fp);
|
||||||
|
fclose(log_fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TodoList::Log() const
|
||||||
|
{
|
||||||
|
if (to_update.size())
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Updating:\n");
|
||||||
|
for (const auto& op : to_update)
|
||||||
|
{
|
||||||
|
std::string old_desc =
|
||||||
|
op.old_hash ? HexEncode(op.old_hash->data(), op.old_hash->size()) : "(new)";
|
||||||
|
fprintf(log_fp, " - %s: %s -> %s\n", op.filename.c_str(), old_desc.c_str(),
|
||||||
|
HexEncode(op.new_hash.data(), op.new_hash.size()).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (to_delete.size())
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Deleting:\n");
|
||||||
|
for (const auto& op : to_delete)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, " - %s (%s)\n", op.filename.c_str(),
|
||||||
|
HexEncode(op.old_hash.data(), op.old_hash.size()).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
|
||||||
|
const std::string& content_base_url, const std::string& temp_path)
|
||||||
|
{
|
||||||
|
Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback);
|
||||||
|
|
||||||
|
UI::SetTotalMarquee(false);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < to_download.size(); i++)
|
||||||
|
{
|
||||||
|
UI::SetTotalProgress(static_cast<int>(i + 1), static_cast<int>(to_download.size()));
|
||||||
|
|
||||||
|
auto& download = to_download[i];
|
||||||
|
|
||||||
|
std::string hash_filename = HexEncode(download.hash.data(), download.hash.size());
|
||||||
|
UI::SetDescription("Downloading " + download.filename + "... (File " + std::to_string(i + 1) +
|
||||||
|
" of " + std::to_string(to_download.size()) + ")");
|
||||||
|
UI::SetCurrentMarquee(false);
|
||||||
|
|
||||||
|
// Add slashes where needed.
|
||||||
|
std::string content_store_path = hash_filename;
|
||||||
|
content_store_path.insert(4, "/");
|
||||||
|
content_store_path.insert(2, "/");
|
||||||
|
|
||||||
|
std::string url = content_base_url + content_store_path;
|
||||||
|
fprintf(log_fp, "Downloading %s ...\n", url.c_str());
|
||||||
|
|
||||||
|
auto resp = req.Get(url);
|
||||||
|
if (!resp)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
UI::SetCurrentMarquee(true);
|
||||||
|
UI::SetDescription("Verifying " + download.filename + "...");
|
||||||
|
|
||||||
|
std::string contents(reinterpret_cast<char*>(resp->data()), resp->size());
|
||||||
|
std::optional<std::string> maybe_decompressed = GzipInflate(contents);
|
||||||
|
if (!maybe_decompressed)
|
||||||
|
return false;
|
||||||
|
std::string decompressed = std::move(*maybe_decompressed);
|
||||||
|
|
||||||
|
// Check that the downloaded contents have the right hash.
|
||||||
|
Manifest::Hash contents_hash = ComputeHash(decompressed);
|
||||||
|
if (contents_hash != download.hash)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Wrong hash on downloaded content %s.\n", url.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string out = temp_path + DIR_SEP + hash_filename;
|
||||||
|
if (!File::WriteStringToFile(decompressed, out))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Could not write cache file %s.\n", out.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest)
|
||||||
|
{
|
||||||
|
TodoList todo;
|
||||||
|
|
||||||
|
// Delete if present in this manifest but not in next manifest.
|
||||||
|
for (const auto& entry : this_manifest.entries)
|
||||||
|
{
|
||||||
|
if (next_manifest.entries.find(entry.first) == next_manifest.entries.end())
|
||||||
|
{
|
||||||
|
TodoList::DeleteOp del;
|
||||||
|
del.filename = entry.first;
|
||||||
|
del.old_hash = entry.second;
|
||||||
|
todo.to_delete.push_back(std::move(del));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download and update if present in next manifest with different hash from this manifest.
|
||||||
|
for (const auto& entry : next_manifest.entries)
|
||||||
|
{
|
||||||
|
std::optional<Manifest::Hash> old_hash;
|
||||||
|
|
||||||
|
const auto& old_entry = this_manifest.entries.find(entry.first);
|
||||||
|
if (old_entry != this_manifest.entries.end())
|
||||||
|
old_hash = old_entry->second;
|
||||||
|
|
||||||
|
if (!old_hash || *old_hash != entry.second)
|
||||||
|
{
|
||||||
|
TodoList::DownloadOp download;
|
||||||
|
download.filename = entry.first;
|
||||||
|
download.hash = entry.second;
|
||||||
|
|
||||||
|
todo.to_download.push_back(std::move(download));
|
||||||
|
|
||||||
|
TodoList::UpdateOp update;
|
||||||
|
update.filename = entry.first;
|
||||||
|
update.old_hash = old_hash;
|
||||||
|
update.new_hash = entry.second;
|
||||||
|
todo.to_update.push_back(std::move(update));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return todo;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> FindOrCreateTempDir(const std::string& base_path)
|
||||||
|
{
|
||||||
|
std::string temp_path = base_path + DIR_SEP + UPDATE_TEMP_DIR;
|
||||||
|
int counter = 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!File::Exists(temp_path))
|
||||||
|
{
|
||||||
|
if (File::CreateDir(temp_path))
|
||||||
|
{
|
||||||
|
return temp_path;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Couldn't create temp directory.\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (File::IsDirectory(temp_path))
|
||||||
|
{
|
||||||
|
return temp_path;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try again with a counter appended to the path.
|
||||||
|
std::string suffix = UPDATE_TEMP_DIR + std::to_string(counter);
|
||||||
|
temp_path = base_path + DIR_SEP + suffix;
|
||||||
|
}
|
||||||
|
} while (counter++ < 10);
|
||||||
|
|
||||||
|
fprintf(log_fp, "Could not find an appropriate temp directory name. Giving up.\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo)
|
||||||
|
{
|
||||||
|
// This is best-effort cleanup, we ignore most errors.
|
||||||
|
for (const auto& download : todo.to_download)
|
||||||
|
File::Delete(temp_dir + DIR_SEP + HexEncode(download.hash.data(), download.hash.size()));
|
||||||
|
File::DeleteDir(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool BackupFile(const std::string& path)
|
||||||
|
{
|
||||||
|
std::string backup_path = path + ".bak";
|
||||||
|
fprintf(log_fp, "Backing up unknown pre-existing %s to .bak.\n", path.c_str());
|
||||||
|
if (!File::Rename(path, backup_path))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Cound not rename %s to %s for backup.\n", path.c_str(), backup_path.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
|
||||||
|
const std::string& install_base_path)
|
||||||
|
{
|
||||||
|
for (const auto& op : to_delete)
|
||||||
|
{
|
||||||
|
std::string path = install_base_path + DIR_SEP + op.filename;
|
||||||
|
|
||||||
|
if (!File::Exists(path))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "File %s is already missing.\n", op.filename.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string contents;
|
||||||
|
if (!File::ReadFileToString(path, contents))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Could not read file planned for deletion: %s.\n", op.filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Manifest::Hash contents_hash = ComputeHash(contents);
|
||||||
|
if (contents_hash != op.old_hash)
|
||||||
|
{
|
||||||
|
if (!BackupFile(path))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File::Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
|
||||||
|
const std::string& install_base_path, const std::string& temp_path)
|
||||||
|
{
|
||||||
|
for (const auto& op : to_update)
|
||||||
|
{
|
||||||
|
std::string path = install_base_path + DIR_SEP + op.filename;
|
||||||
|
if (!File::CreateFullPath(path))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Could not create directory structure for %s.\n", op.filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
// TODO: A new updater protocol version is required to properly mark executable files. For
|
||||||
|
// now, copy executable bits from existing files. This will break for newly added executables.
|
||||||
|
std::optional<mode_t> permission;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (File::Exists(path))
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
struct stat file_stats;
|
||||||
|
|
||||||
|
if (stat(path.c_str(), &file_stats) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
permission = file_stats.st_mode;
|
||||||
|
#endif
|
||||||
|
std::string contents;
|
||||||
|
if (!File::ReadFileToString(path, contents))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Could not read existing file %s.\n", op.filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Manifest::Hash contents_hash = ComputeHash(contents);
|
||||||
|
if (contents_hash == op.new_hash)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "File %s was already up to date. Partial update?\n", op.filename.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!op.old_hash || contents_hash != *op.old_hash)
|
||||||
|
{
|
||||||
|
if (!BackupFile(path))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can safely move the new contents to the location.
|
||||||
|
std::string content_filename = HexEncode(op.new_hash.data(), op.new_hash.size());
|
||||||
|
fprintf(log_fp, "Updating file %s from content %s...\n", op.filename.c_str(),
|
||||||
|
content_filename.c_str());
|
||||||
|
if (!File::Copy(temp_path + DIR_SEP + content_filename, path))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Could not update file %s.\n", op.filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (permission.has_value() && chmod(path.c_str(), permission.value()) != 0)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerformUpdate(const TodoList& todo, const std::string& install_base_path,
|
||||||
|
const std::string& content_base_url, const std::string& temp_path)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Starting download step...\n");
|
||||||
|
if (!DownloadContent(todo.to_download, content_base_url, temp_path))
|
||||||
|
return false;
|
||||||
|
fprintf(log_fp, "Download step completed.\n");
|
||||||
|
|
||||||
|
fprintf(log_fp, "Starting update step...\n");
|
||||||
|
if (!UpdateFiles(todo.to_update, install_base_path, temp_path))
|
||||||
|
return false;
|
||||||
|
fprintf(log_fp, "Update step completed.\n");
|
||||||
|
|
||||||
|
fprintf(log_fp, "Starting deletion step...\n");
|
||||||
|
if (!DeleteObsoleteFiles(todo.to_delete, install_base_path))
|
||||||
|
return false;
|
||||||
|
fprintf(log_fp, "Deletion step completed.\n");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FatalError(const std::string& message)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "%s\n", message.c_str());
|
||||||
|
|
||||||
|
UI::Error(message);
|
||||||
|
UI::Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<Manifest> ParseManifest(const std::string& manifest)
|
||||||
|
{
|
||||||
|
Manifest parsed;
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
while (pos < manifest.size())
|
||||||
|
{
|
||||||
|
size_t filename_end_pos = manifest.find('\t', pos);
|
||||||
|
if (filename_end_pos == std::string::npos)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Manifest entry %zu: could not find filename end.\n", parsed.entries.size());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
size_t hash_end_pos = manifest.find('\n', filename_end_pos);
|
||||||
|
if (hash_end_pos == std::string::npos)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Manifest entry %zu: could not find hash end.\n", parsed.entries.size());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string filename = manifest.substr(pos, filename_end_pos - pos);
|
||||||
|
std::string hash = manifest.substr(filename_end_pos + 1, hash_end_pos - filename_end_pos - 1);
|
||||||
|
if (hash.size() != 32)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(),
|
||||||
|
hash.c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Manifest::Hash decoded_hash;
|
||||||
|
if (!HexDecode(hash, decoded_hash.data(), decoded_hash.size()))
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(),
|
||||||
|
hash.c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.entries[filename] = decoded_hash;
|
||||||
|
pos = hash_end_pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not showing a progress bar here because this part is just too quick
|
||||||
|
std::optional<Manifest> FetchAndParseManifest(const std::string& url)
|
||||||
|
{
|
||||||
|
Common::HttpRequest http;
|
||||||
|
|
||||||
|
Common::HttpRequest::Response resp = http.Get(url);
|
||||||
|
if (!resp)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Manifest download failed.\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string contents(reinterpret_cast<char*>(resp->data()), resp->size());
|
||||||
|
std::optional<std::string> maybe_decompressed = GzipInflate(contents);
|
||||||
|
if (!maybe_decompressed)
|
||||||
|
return {};
|
||||||
|
std::string decompressed = std::move(*maybe_decompressed);
|
||||||
|
|
||||||
|
// Split into manifest and signature.
|
||||||
|
size_t boundary = decompressed.rfind("\n\n");
|
||||||
|
if (boundary == std::string::npos)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "No signature was found in manifest.\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string signature_block = decompressed.substr(boundary + 2); // 2 for "\n\n".
|
||||||
|
decompressed.resize(boundary + 1); // 1 to keep the final "\n".
|
||||||
|
|
||||||
|
std::vector<std::string> signatures = SplitString(signature_block, '\n');
|
||||||
|
bool found_valid_signature = false;
|
||||||
|
for (const auto& signature : signatures)
|
||||||
|
{
|
||||||
|
if (VerifySignature(decompressed, signature))
|
||||||
|
{
|
||||||
|
found_valid_signature = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found_valid_signature)
|
||||||
|
{
|
||||||
|
fprintf(log_fp, "Could not verify signature of the manifest.\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseManifest(decompressed);
|
||||||
|
}
|
60
Source/Core/UpdaterCommon/UpdaterCommon.h
Normal file
60
Source/Core/UpdaterCommon/UpdaterCommon.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
extern FILE* log_fp;
|
||||||
|
|
||||||
|
struct Manifest
|
||||||
|
{
|
||||||
|
using Filename = std::string;
|
||||||
|
using Hash = std::array<u8, 16>;
|
||||||
|
std::map<Filename, Hash> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Represent the operations to be performed by the updater.
|
||||||
|
struct TodoList
|
||||||
|
{
|
||||||
|
struct DownloadOp
|
||||||
|
{
|
||||||
|
Manifest::Filename filename;
|
||||||
|
Manifest::Hash hash;
|
||||||
|
};
|
||||||
|
std::vector<DownloadOp> to_download;
|
||||||
|
|
||||||
|
struct UpdateOp
|
||||||
|
{
|
||||||
|
Manifest::Filename filename;
|
||||||
|
std::optional<Manifest::Hash> old_hash;
|
||||||
|
Manifest::Hash new_hash;
|
||||||
|
};
|
||||||
|
std::vector<UpdateOp> to_update;
|
||||||
|
|
||||||
|
struct DeleteOp
|
||||||
|
{
|
||||||
|
Manifest::Filename filename;
|
||||||
|
Manifest::Hash old_hash;
|
||||||
|
};
|
||||||
|
std::vector<DeleteOp> to_delete;
|
||||||
|
|
||||||
|
void Log() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
void FatalError(const std::string& message);
|
||||||
|
std::optional<Manifest> FetchAndParseManifest(const std::string& url);
|
||||||
|
TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest);
|
||||||
|
std::optional<std::string> FindOrCreateTempDir(const std::string& base_path);
|
||||||
|
void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo);
|
||||||
|
bool PerformUpdate(const TodoList& todo, const std::string& install_base_path,
|
||||||
|
const std::string& content_base_url, const std::string& temp_path);
|
||||||
|
void FlushLog();
|
65
Source/Core/UpdaterCommon/UpdaterCommon.vcxproj
Normal file
65
Source/Core/UpdaterCommon/UpdaterCommon.vcxproj
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>15.0</VCProjectVersion>
|
||||||
|
<ProjectGuid>{B001D13E-7EAB-4689-842D-801E5ACFFAC5}</ProjectGuid>
|
||||||
|
<RootNamespace>UpdaterCommon</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Label="Configuration">
|
||||||
|
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||||
|
<PlatformToolset>v141</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
<Import Project="..\..\VSProps\Base.props" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup />
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Common\Common.vcxproj">
|
||||||
|
<Project>{2e6c348c-c75c-4d94-8d1e-9c1fcbf3efe4}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\..\Externals\curl\curl.vcxproj">
|
||||||
|
<Project>{bb00605c-125f-4a21-b33b-7bf418322dcb}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\..\externals\ed25519\ed25519.vcxproj">
|
||||||
|
<Project>{5bdf4b91-1491-4fb0-bc27-78e9a8e97dc3}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\..\Externals\mbedtls\mbedTLS.vcxproj">
|
||||||
|
<Project>{bdb6578b-0691-4e80-a46c-df21639fd3b8}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\..\Externals\zlib\zlib.vcxproj">
|
||||||
|
<Project>{ff213b23-2c26-4214-9f88-85271e557e87}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="UpdaterCommon.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
@ -91,180 +91,268 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "..\Externals\min
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imgui", "..\Externals\imgui\imgui.vcxproj", "{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imgui", "..\Externals\imgui\imgui.vcxproj", "{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UpdaterCommon", "Core\UpdaterCommon\UpdaterCommon.vcxproj", "{B001D13E-7EAB-4689-842D-801E5ACFFAC5}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|x64 = Debug|x64
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|x64 = Release|x64
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Debug|x64.ActiveCfg = Debug|x64
|
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Debug|x64.Build.0 = Debug|x64
|
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Release|x64.ActiveCfg = Release|x64
|
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Release|x64.ActiveCfg = Release|x64
|
||||||
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Release|x64.Build.0 = Release|x64
|
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Release|x64.Build.0 = Release|x64
|
||||||
{47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Debug|x64.ActiveCfg = Debug|x64
|
{FA3FA62B-6F58-4B86-9453-4D149940A066}.Release|x86.ActiveCfg = Release|x64
|
||||||
{47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Release|x64.Build.0 = Release|x64
|
|
||||||
{E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|x64.ActiveCfg = Debug|x64
|
{E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|x64.Build.0 = Debug|x64
|
{E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{E54CF649-140E-4255-81A5-30A673C1FB36}.Release|x64.ActiveCfg = Release|x64
|
{E54CF649-140E-4255-81A5-30A673C1FB36}.Release|x64.ActiveCfg = Release|x64
|
||||||
{E54CF649-140E-4255-81A5-30A673C1FB36}.Release|x64.Build.0 = Release|x64
|
{E54CF649-140E-4255-81A5-30A673C1FB36}.Release|x64.Build.0 = Release|x64
|
||||||
|
{E54CF649-140E-4255-81A5-30A673C1FB36}.Release|x86.ActiveCfg = Release|x64
|
||||||
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|x64.ActiveCfg = Debug|x64
|
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|x64.Build.0 = Debug|x64
|
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|x64.ActiveCfg = Release|x64
|
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|x64.ActiveCfg = Release|x64
|
||||||
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|x64.Build.0 = Release|x64
|
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|x64.Build.0 = Release|x64
|
||||||
|
{54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|x86.ActiveCfg = Release|x64
|
||||||
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|x64.ActiveCfg = Debug|x64
|
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|x64.Build.0 = Debug|x64
|
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|x64.ActiveCfg = Release|x64
|
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|x64.ActiveCfg = Release|x64
|
||||||
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|x64.Build.0 = Release|x64
|
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|x64.Build.0 = Release|x64
|
||||||
|
{2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|x86.ActiveCfg = Release|x64
|
||||||
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|x64.ActiveCfg = Debug|x64
|
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|x64.Build.0 = Debug|x64
|
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|x64.ActiveCfg = Release|x64
|
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|x64.ActiveCfg = Release|x64
|
||||||
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|x64.Build.0 = Release|x64
|
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|x64.Build.0 = Release|x64
|
||||||
|
{160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|x86.ActiveCfg = Release|x64
|
||||||
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|x64.ActiveCfg = Debug|x64
|
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|x64.Build.0 = Debug|x64
|
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|x64.ActiveCfg = Release|x64
|
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|x64.ActiveCfg = Release|x64
|
||||||
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|x64.Build.0 = Release|x64
|
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|x64.Build.0 = Release|x64
|
||||||
|
{6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|x86.ActiveCfg = Release|x64
|
||||||
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|x64.ActiveCfg = Debug|x64
|
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|x64.Build.0 = Debug|x64
|
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|x64.ActiveCfg = Release|x64
|
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|x64.ActiveCfg = Release|x64
|
||||||
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|x64.Build.0 = Release|x64
|
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|x64.Build.0 = Release|x64
|
||||||
|
{604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|x86.ActiveCfg = Release|x64
|
||||||
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|x64.ActiveCfg = Debug|x64
|
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|x64.Build.0 = Debug|x64
|
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|x64.ActiveCfg = Release|x64
|
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|x64.ActiveCfg = Release|x64
|
||||||
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|x64.Build.0 = Release|x64
|
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|x64.Build.0 = Release|x64
|
||||||
|
{3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|x86.ActiveCfg = Release|x64
|
||||||
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|x64.ActiveCfg = Debug|x64
|
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|x64.Build.0 = Debug|x64
|
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|x64.ActiveCfg = Release|x64
|
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|x64.ActiveCfg = Release|x64
|
||||||
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|x64.Build.0 = Release|x64
|
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|x64.Build.0 = Release|x64
|
||||||
|
{8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|x86.ActiveCfg = Release|x64
|
||||||
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|x64.ActiveCfg = Debug|x64
|
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|x64.Build.0 = Debug|x64
|
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|x64.ActiveCfg = Release|x64
|
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|x64.ActiveCfg = Release|x64
|
||||||
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|x64.Build.0 = Release|x64
|
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|x64.Build.0 = Release|x64
|
||||||
|
{0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|x86.ActiveCfg = Release|x64
|
||||||
{AB993F38-C31D-4897-B139-A620C42BC565}.Debug|x64.ActiveCfg = Debug|x64
|
{AB993F38-C31D-4897-B139-A620C42BC565}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{AB993F38-C31D-4897-B139-A620C42BC565}.Debug|x64.Build.0 = Debug|x64
|
{AB993F38-C31D-4897-B139-A620C42BC565}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{AB993F38-C31D-4897-B139-A620C42BC565}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{AB993F38-C31D-4897-B139-A620C42BC565}.Release|x64.ActiveCfg = Release|x64
|
{AB993F38-C31D-4897-B139-A620C42BC565}.Release|x64.ActiveCfg = Release|x64
|
||||||
{AB993F38-C31D-4897-B139-A620C42BC565}.Release|x64.Build.0 = Release|x64
|
{AB993F38-C31D-4897-B139-A620C42BC565}.Release|x64.Build.0 = Release|x64
|
||||||
|
{AB993F38-C31D-4897-B139-A620C42BC565}.Release|x86.ActiveCfg = Release|x64
|
||||||
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.ActiveCfg = Debug|x64
|
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.Build.0 = Debug|x64
|
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x64.ActiveCfg = Release|x64
|
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x64.ActiveCfg = Release|x64
|
||||||
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x64.Build.0 = Release|x64
|
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x64.Build.0 = Release|x64
|
||||||
|
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x86.ActiveCfg = Release|x64
|
||||||
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|x64.ActiveCfg = Debug|x64
|
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|x64.Build.0 = Debug|x64
|
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|x64.ActiveCfg = Release|x64
|
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|x64.ActiveCfg = Release|x64
|
||||||
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|x64.Build.0 = Release|x64
|
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|x64.Build.0 = Release|x64
|
||||||
|
{4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|x86.ActiveCfg = Release|x64
|
||||||
{677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|x64.ActiveCfg = Debug|x64
|
{677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|x64.Build.0 = Debug|x64
|
{677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{677EA016-1182-440C-9345-DC88D1E98C0C}.Release|x64.ActiveCfg = Release|x64
|
{677EA016-1182-440C-9345-DC88D1E98C0C}.Release|x64.ActiveCfg = Release|x64
|
||||||
{677EA016-1182-440C-9345-DC88D1E98C0C}.Release|x64.Build.0 = Release|x64
|
{677EA016-1182-440C-9345-DC88D1E98C0C}.Release|x64.Build.0 = Release|x64
|
||||||
|
{677EA016-1182-440C-9345-DC88D1E98C0C}.Release|x86.ActiveCfg = Release|x64
|
||||||
{FF213B23-2C26-4214-9F88-85271E557E87}.Debug|x64.ActiveCfg = Debug|x64
|
{FF213B23-2C26-4214-9F88-85271E557E87}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{FF213B23-2C26-4214-9F88-85271E557E87}.Debug|x64.Build.0 = Debug|x64
|
{FF213B23-2C26-4214-9F88-85271E557E87}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{FF213B23-2C26-4214-9F88-85271E557E87}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{FF213B23-2C26-4214-9F88-85271E557E87}.Release|x64.ActiveCfg = Release|x64
|
{FF213B23-2C26-4214-9F88-85271E557E87}.Release|x64.ActiveCfg = Release|x64
|
||||||
{FF213B23-2C26-4214-9F88-85271E557E87}.Release|x64.Build.0 = Release|x64
|
{FF213B23-2C26-4214-9F88-85271E557E87}.Release|x64.Build.0 = Release|x64
|
||||||
|
{FF213B23-2C26-4214-9F88-85271E557E87}.Release|x86.ActiveCfg = Release|x64
|
||||||
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|x64.ActiveCfg = Debug|x64
|
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|x64.Build.0 = Debug|x64
|
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|x64.ActiveCfg = Release|x64
|
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|x64.ActiveCfg = Release|x64
|
||||||
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|x64.Build.0 = Release|x64
|
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|x64.Build.0 = Release|x64
|
||||||
|
{EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|x86.ActiveCfg = Release|x64
|
||||||
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|x64.ActiveCfg = Debug|x64
|
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|x64.Build.0 = Debug|x64
|
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|x64.ActiveCfg = Release|x64
|
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|x64.ActiveCfg = Release|x64
|
||||||
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|x64.Build.0 = Release|x64
|
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|x64.Build.0 = Release|x64
|
||||||
|
{BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|x86.ActiveCfg = Release|x64
|
||||||
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|x64.ActiveCfg = Debug|x64
|
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|x64.Build.0 = Debug|x64
|
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|x64.ActiveCfg = Release|x64
|
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|x64.ActiveCfg = Release|x64
|
||||||
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|x64.Build.0 = Release|x64
|
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|x64.Build.0 = Release|x64
|
||||||
|
{41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|x86.ActiveCfg = Release|x64
|
||||||
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|x64.ActiveCfg = Debug|x64
|
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|x64.Build.0 = Debug|x64
|
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|x64.ActiveCfg = Release|x64
|
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|x64.ActiveCfg = Release|x64
|
||||||
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|x64.Build.0 = Release|x64
|
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|x64.Build.0 = Release|x64
|
||||||
|
{93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|x86.ActiveCfg = Release|x64
|
||||||
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|x64.ActiveCfg = Debug|x64
|
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|x64.Build.0 = Debug|x64
|
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|x64.ActiveCfg = Release|x64
|
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|x64.ActiveCfg = Release|x64
|
||||||
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|x64.Build.0 = Release|x64
|
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|x64.Build.0 = Release|x64
|
||||||
|
{349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|x86.ActiveCfg = Release|x64
|
||||||
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x64.ActiveCfg = Debug|x64
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x64.Build.0 = Debug|x64
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|x64.ActiveCfg = Release|x64
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|x64.ActiveCfg = Release|x64
|
||||||
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|x64.Build.0 = Release|x64
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|x64.Build.0 = Release|x64
|
||||||
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|x86.ActiveCfg = Release|x64
|
||||||
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|x64.ActiveCfg = Debug|x64
|
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|x64.Build.0 = Debug|x64
|
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|x64.ActiveCfg = Release|x64
|
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|x64.ActiveCfg = Release|x64
|
||||||
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|x64.Build.0 = Release|x64
|
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|x64.Build.0 = Release|x64
|
||||||
|
{96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|x86.ActiveCfg = Release|x64
|
||||||
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|x64.ActiveCfg = Debug|x64
|
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|x64.Build.0 = Debug|x64
|
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|x64.ActiveCfg = Release|x64
|
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|x64.ActiveCfg = Release|x64
|
||||||
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|x64.Build.0 = Release|x64
|
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|x64.Build.0 = Release|x64
|
||||||
|
{EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|x86.ActiveCfg = Release|x64
|
||||||
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|x64.ActiveCfg = Debug|x64
|
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|x64.Build.0 = Debug|x64
|
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|x64.ActiveCfg = Release|x64
|
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|x64.ActiveCfg = Release|x64
|
||||||
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|x64.Build.0 = Release|x64
|
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|x64.Build.0 = Release|x64
|
||||||
|
{A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|x86.ActiveCfg = Release|x64
|
||||||
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Debug|x64.ActiveCfg = Debug|x64
|
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Debug|x64.Build.0 = Debug|x64
|
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Release|x64.ActiveCfg = Release|x64
|
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Release|x64.ActiveCfg = Release|x64
|
||||||
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Release|x64.Build.0 = Release|x64
|
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Release|x64.Build.0 = Release|x64
|
||||||
|
{53A5391B-737E-49A8-BC8F-312ADA00736F}.Release|x86.ActiveCfg = Release|x64
|
||||||
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Debug|x64.ActiveCfg = Debug|x64
|
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Debug|x64.Build.0 = Debug|x64
|
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Release|x64.ActiveCfg = Release|x64
|
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Release|x64.ActiveCfg = Release|x64
|
||||||
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Release|x64.Build.0 = Release|x64
|
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Release|x64.Build.0 = Release|x64
|
||||||
|
{29F29A19-F141-45AD-9679-5A2923B49DA3}.Release|x86.ActiveCfg = Release|x64
|
||||||
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|x64.ActiveCfg = Debug|x64
|
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|x64.Build.0 = Debug|x64
|
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|x64.ActiveCfg = Release|x64
|
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|x64.ActiveCfg = Release|x64
|
||||||
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|x64.Build.0 = Release|x64
|
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|x64.Build.0 = Release|x64
|
||||||
|
{76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|x86.ActiveCfg = Release|x64
|
||||||
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|x64.ActiveCfg = Debug|x64
|
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|x64.Build.0 = Debug|x64
|
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|x64.ActiveCfg = Release|x64
|
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|x64.ActiveCfg = Release|x64
|
||||||
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|x64.Build.0 = Release|x64
|
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|x64.Build.0 = Release|x64
|
||||||
|
{474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|x86.ActiveCfg = Release|x64
|
||||||
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x64.ActiveCfg = Debug|x64
|
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x64.Build.0 = Debug|x64
|
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|x64.ActiveCfg = Release|x64
|
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|x64.ActiveCfg = Release|x64
|
||||||
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|x64.Build.0 = Release|x64
|
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|x64.Build.0 = Release|x64
|
||||||
|
{CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|x86.ActiveCfg = Release|x64
|
||||||
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Debug|x64.ActiveCfg = Debug|x64
|
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Debug|x64.Build.0 = Debug|x64
|
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Release|x64.ActiveCfg = Release|x64
|
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Release|x64.ActiveCfg = Release|x64
|
||||||
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Release|x64.Build.0 = Release|x64
|
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Release|x64.Build.0 = Release|x64
|
||||||
|
{BB00605C-125F-4A21-B33B-7BF418322DCB}.Release|x86.ActiveCfg = Release|x64
|
||||||
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Debug|x64.ActiveCfg = Debug|x64
|
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Debug|x64.Build.0 = Debug|x64
|
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Release|x64.ActiveCfg = Release|x64
|
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Release|x64.ActiveCfg = Release|x64
|
||||||
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Release|x64.Build.0 = Release|x64
|
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Release|x64.Build.0 = Release|x64
|
||||||
|
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Release|x86.ActiveCfg = Release|x64
|
||||||
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Debug|x64.ActiveCfg = Debug|x64
|
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Debug|x64.Build.0 = Debug|x64
|
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Release|x64.ActiveCfg = Release|x64
|
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Release|x64.ActiveCfg = Release|x64
|
||||||
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Release|x64.Build.0 = Release|x64
|
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Release|x64.Build.0 = Release|x64
|
||||||
|
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Release|x86.ActiveCfg = Release|x64
|
||||||
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Debug|x64.ActiveCfg = Debug|x64
|
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Debug|x64.Build.0 = Debug|x64
|
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Release|x64.ActiveCfg = Release|x64
|
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Release|x64.ActiveCfg = Release|x64
|
||||||
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Release|x64.Build.0 = Release|x64
|
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Release|x64.Build.0 = Release|x64
|
||||||
|
{8EA11166-6512-44FC-B7A5-A4D1ECC81170}.Release|x86.ActiveCfg = Release|x64
|
||||||
{38FEE76F-F347-484B-949C-B4649381CFFB}.Debug|x64.ActiveCfg = Debug|x64
|
{38FEE76F-F347-484B-949C-B4649381CFFB}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{38FEE76F-F347-484B-949C-B4649381CFFB}.Debug|x64.Build.0 = Debug|x64
|
{38FEE76F-F347-484B-949C-B4649381CFFB}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{38FEE76F-F347-484B-949C-B4649381CFFB}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{38FEE76F-F347-484B-949C-B4649381CFFB}.Release|x64.ActiveCfg = Release|x64
|
{38FEE76F-F347-484B-949C-B4649381CFFB}.Release|x64.ActiveCfg = Release|x64
|
||||||
{38FEE76F-F347-484B-949C-B4649381CFFB}.Release|x64.Build.0 = Release|x64
|
{38FEE76F-F347-484B-949C-B4649381CFFB}.Release|x64.Build.0 = Release|x64
|
||||||
|
{38FEE76F-F347-484B-949C-B4649381CFFB}.Release|x86.ActiveCfg = Release|x64
|
||||||
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Debug|x64.ActiveCfg = Debug|x64
|
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Debug|x64.Build.0 = Debug|x64
|
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Release|x64.ActiveCfg = Release|x64
|
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Release|x64.ActiveCfg = Release|x64
|
||||||
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Release|x64.Build.0 = Release|x64
|
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Release|x64.Build.0 = Release|x64
|
||||||
|
{2C0D058E-DE35-4471-AD99-E68A2CAF9E18}.Release|x86.ActiveCfg = Release|x64
|
||||||
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Debug|x64.ActiveCfg = Debug|x64
|
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Debug|x64.Build.0 = Debug|x64
|
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Release|x64.ActiveCfg = Release|x64
|
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Release|x64.ActiveCfg = Release|x64
|
||||||
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Release|x64.Build.0 = Release|x64
|
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Release|x64.Build.0 = Release|x64
|
||||||
|
{5BDF4B91-1491-4FB0-BC27-78E9A8E97DC3}.Release|x86.ActiveCfg = Release|x64
|
||||||
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Debug|x64.ActiveCfg = Debug|x64
|
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Debug|x64.Build.0 = Debug|x64
|
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Release|x64.ActiveCfg = Release|x64
|
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Release|x64.ActiveCfg = Release|x64
|
||||||
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Release|x64.Build.0 = Release|x64
|
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Release|x64.Build.0 = Release|x64
|
||||||
|
{E4BECBAB-9C6E-41AB-BB56-F9D70AB6BE03}.Release|x86.ActiveCfg = Release|x64
|
||||||
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Debug|x64.ActiveCfg = Debug|x64
|
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Debug|x64.Build.0 = Debug|x64
|
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Release|x64.ActiveCfg = Release|x64
|
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Release|x64.ActiveCfg = Release|x64
|
||||||
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Release|x64.Build.0 = Release|x64
|
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Release|x64.Build.0 = Release|x64
|
||||||
|
{8498F2FA-5CA6-4169-9971-DE5B1FE6132C}.Release|x86.ActiveCfg = Release|x64
|
||||||
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Debug|x64.ActiveCfg = Debug|x64
|
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Debug|x64.Build.0 = Debug|x64
|
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Release|x64.ActiveCfg = Release|x64
|
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Release|x64.ActiveCfg = Release|x64
|
||||||
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Release|x64.Build.0 = Release|x64
|
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Release|x64.Build.0 = Release|x64
|
||||||
|
{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}.Release|x86.ActiveCfg = Release|x64
|
||||||
{23114507-079A-4418-9707-CFA81A03CA99}.Debug|x64.ActiveCfg = Debug|x64
|
{23114507-079A-4418-9707-CFA81A03CA99}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{23114507-079A-4418-9707-CFA81A03CA99}.Debug|x64.Build.0 = Debug|x64
|
{23114507-079A-4418-9707-CFA81A03CA99}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{23114507-079A-4418-9707-CFA81A03CA99}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{23114507-079A-4418-9707-CFA81A03CA99}.Release|x64.ActiveCfg = Release|x64
|
{23114507-079A-4418-9707-CFA81A03CA99}.Release|x64.ActiveCfg = Release|x64
|
||||||
{23114507-079A-4418-9707-CFA81A03CA99}.Release|x64.Build.0 = Release|x64
|
{23114507-079A-4418-9707-CFA81A03CA99}.Release|x64.Build.0 = Release|x64
|
||||||
|
{23114507-079A-4418-9707-CFA81A03CA99}.Release|x86.ActiveCfg = Release|x64
|
||||||
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Debug|x64.ActiveCfg = Debug|x64
|
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Debug|x64.Build.0 = Debug|x64
|
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Release|x64.ActiveCfg = Release|x64
|
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Release|x64.ActiveCfg = Release|x64
|
||||||
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Release|x64.Build.0 = Release|x64
|
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Release|x64.Build.0 = Release|x64
|
||||||
|
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB}.Release|x86.ActiveCfg = Release|x64
|
||||||
|
{B001D13E-7EAB-4689-842D-801E5ACFFAC5}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{B001D13E-7EAB-4689-842D-801E5ACFFAC5}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{B001D13E-7EAB-4689-842D-801E5ACFFAC5}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
|
{B001D13E-7EAB-4689-842D-801E5ACFFAC5}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{B001D13E-7EAB-4689-842D-801E5ACFFAC5}.Release|x64.Build.0 = Release|x64
|
||||||
|
{B001D13E-7EAB-4689-842D-801E5ACFFAC5}.Release|x86.ActiveCfg = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -307,4 +395,7 @@ Global
|
|||||||
{23114507-079A-4418-9707-CFA81A03CA99} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
|
{23114507-079A-4418-9707-CFA81A03CA99} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
|
||||||
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
|
{4C3B2264-EA73-4A7B-9CFE-65B0FD635EBB} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {710976F2-1BC7-4F2A-B32D-5DD2BBCB44E1}
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
Loading…
x
Reference in New Issue
Block a user