Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved updater for Windows #571

Merged
merged 4 commits into from
Apr 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion updater/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
add_executable(updater updater.cpp)
# USC updater for Windows

set(SRCROOT ${CMAKE_CURRENT_SOURCE_DIR}/src/)

file(GLOB SRC "${SRCROOT}/*.cpp" "${SRCROOT}/*.hpp")
source_group("Source" FILES ${SRC})

set(UPDATER_SRC ${SRC})

add_executable(updater ${UPDATER_SRC})

target_compile_features(updater PUBLIC cxx_std_17)

target_link_libraries(updater cpr)
target_link_libraries(updater ${LibArchive_LIBRARIES})
target_include_directories(updater SYSTEM PRIVATE ${LibArchive_INCLUDE_DIRS})
115 changes: 115 additions & 0 deletions updater/src/Downloader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "Downloader.hpp"

#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <type_traits>

#include "cpr/cpr.h"

void Downloader::Download(const std::string& url)
{
std::cout << "Downloading the game from \"" << url << "\"..." << std::endl;
m_content.clear();

cpr::Session session;
session.SetUrl(url);
session.SetProgressCallback(cpr::ProgressCallback([&](cpr::cpr_off_t total, cpr::cpr_off_t current, cpr::cpr_off_t, cpr::cpr_off_t, intptr_t) {
m_progressBar.Update(m_content.size() + total, m_content.size() + current);
return true;
}));

m_progressBar.Start();

cpr::Response response = {};

while(true)
{
response = session.Get();
if (response.text.empty()) break;

m_content += std::move(response.text);

if (!response.error) break;
if (response.error.code != cpr::ErrorCode::NETWORK_RECEIVE_ERROR) break;

// It might be a fluke so continue trying...
std::stringstream sb;
sb << "bytes=" << m_content.size() << "-";
session.SetHeader({{"Range", sb.str()}});
}

m_progressBar.Finish();

if (response.error)
{
std::stringstream sb;

sb << "Download failed with code " << static_cast<std::underlying_type_t<cpr::ErrorCode>>(response.error.code);

if (!response.error.message.empty())
{
sb << ": " << response.error.message;
}

throw std::runtime_error(sb.str());
}

if (response.status_code/100 != 2)
{
std::stringstream sb;
sb << "The server has returned HTTP status code " << response.status_code << ".";

throw std::runtime_error(sb.str());
}

if (response.status_code != 200 && response.status_code != 206)
{
std::cerr << "Warning: downloaded succeeded with HTTP status code " << response.status_code << ".";
}
}

void Downloader::ProgressBar::Start()
{
Render(0, 0);
}

void Downloader::ProgressBar::Update(size_t total, size_t current)
{
Render(total, current);
}

void Downloader::ProgressBar::Finish()
{
std::cout << std::endl;
}

void Downloader::ProgressBar::Render(size_t total, size_t current)
{
const size_t percentage = total == 0 ? 0 : (current * 100) / total;
const size_t num_completes = total == 0 ? 0 : (current * m_width) / total;

std::cout << '\r';
std::cout << std::setfill(' ') << std::setw(3) << percentage << "%";

std::cout << " [";

for (size_t i = 0; i < m_width; ++i)
{
std::cout << (i < num_completes ? '#' : ' ');
}

std::cout << ']';

size_t current_kb = current / 1000;
size_t total_kb = total / 1000;

// Dunno whether doing this instead of using floating points and std::setprecision is better...
std::cout << ' ' << std::setfill(' ') << std::setw(2) << current_kb / 1000 << '.' << std::setfill('0') << std::setw(2) << (current_kb / 10) % 10;
std::cout << " / " << std::setfill(' ') << std::setw(2) << total_kb / 1000 << '.' << std::setfill('0') << std::setw(2) << (total_kb / 10) % 10;
std::cout << " MB";

std::cout.flush();
}

28 changes: 28 additions & 0 deletions updater/src/Downloader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <string>

/// Simple class for downloading a file using libcpr
class Downloader
{
public:
Downloader() = default;
void Download(const std::string& url);

inline const std::string& GetContent() const { return m_content; }

private:
class ProgressBar
{
public:
void Start();
void Update(size_t total, size_t current);
void Finish();

private:
void Render(size_t total, size_t current);
const size_t m_width = 50;
};

ProgressBar m_progressBar;

std::string m_content;
};
123 changes: 123 additions & 0 deletions updater/src/Extractor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include "Extractor.hpp"

#include <iostream>
#include <stdexcept>

#include "archive.h"
#include "archive_entry.h"

void Extractor::Extract(const std::string_view data)
{
struct archive* src = CreateRead(data);
struct archive* dst = CreateDiskWrite();

try
{
CopyArchive(src, dst);
}
catch (...)
{
archive_read_free(src);
archive_write_free(dst);

throw;
}

archive_read_free(src);
archive_write_free(dst);
}

archive* Extractor::CreateRead(const std::string_view data)
{
struct archive* a = archive_read_new();
archive_read_support_format_all(a);
archive_read_support_compression_all(a);

if (int r = archive_read_open_memory(a, data.data(), data.size()))
{
archive_read_free(a);
throw std::runtime_error("Failed to open the archive.");
}

return a;
}

archive* Extractor::CreateDiskWrite()
{
int flags = 0;

flags |= ARCHIVE_EXTRACT_TIME;
flags |= ARCHIVE_EXTRACT_ACL;
flags |= ARCHIVE_EXTRACT_FFLAGS;

flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS;

struct archive* a = archive_write_disk_new();
archive_write_disk_set_options(a, flags);
archive_write_disk_set_standard_lookup(a);

return a;
}

// https://github.com/libarchive/libarchive/wiki/Examples#a-complete-extractor

static void WarnOrThrow(int code, archive* a, int throw_level)
{
if (code < throw_level) throw std::runtime_error(archive_error_string(a));
if (code < ARCHIVE_OK)
{
std::cerr << "- Warning: " << archive_error_string(a) << std::endl;
}
}

void Extractor::CopyArchive(archive* src, archive* dst)
{
struct archive_entry* entry = nullptr;

for (;;)
{
int r = archive_read_next_header(src, &entry);

if (r == ARCHIVE_EOF) break;
if (r < ARCHIVE_WARN) throw std::runtime_error(archive_error_string(src));

std::cout << "Extracting \"" << archive_entry_pathname(entry) << "\"..." << std::endl;

if (r < ARCHIVE_OK)
{
std::cerr << "- Warning: " << archive_error_string(src) << std::endl;
}

r = archive_write_header(dst, entry);
if (r < ARCHIVE_OK)
{
std::cerr << "- Warning: " << archive_error_string(dst) << std::endl;
}
else if (archive_entry_size(entry) > 0)
{
CopyArchiveData(src, dst);
}
}
}

void Extractor::CopyArchiveData(archive* src, archive* dst)
{
int r;
const void* buff;
size_t size;
la_int64_t offset;

for (;;) {
r = archive_read_data_block(src, &buff, &size, &offset);

if (r == ARCHIVE_EOF) break;
WarnOrThrow(r, src, ARCHIVE_OK);

r = archive_write_data_block(dst, buff, size, offset);
WarnOrThrow(r, dst, ARCHIVE_OK);
}

r = archive_write_finish_entry(dst);
WarnOrThrow(r, dst, ARCHIVE_WARN);
}
17 changes: 17 additions & 0 deletions updater/src/Extractor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <iterator>
#include <string_view>

/// Simple class for extracting an archive file to a specific path
class Extractor
{
public:
Extractor() = default;
void Extract(const std::string_view data);

private:
struct archive* CreateRead(const std::string_view data);
struct archive* CreateDiskWrite();

static void CopyArchive(struct archive* src, struct archive* dst);
static void CopyArchiveData(struct archive* src, struct archive* dst);
};
79 changes: 79 additions & 0 deletions updater/src/updater.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <iostream>
#include <stdexcept>
#include <string>

#include <Windows.h>

#include "archive.h"
#include "archive_entry.h"

#include "Downloader.hpp"
#include "Extractor.hpp"

void StartUSC();

int main(int argc, const char* argv[])
{
std::ios_base::sync_with_stdio(false);
std::string archiveUrl = "https://www.drewol.me/Downloads/Game.zip";

if (argc > 1)
{
std::cerr << "Waiting for the game to close..." << std::endl;
DWORD uscPid = std::stol(argv[1]);
HANDLE uscHandle = OpenProcess(SYNCHRONIZE, false, uscPid);
WaitForSingleObject(uscHandle, INFINITE);
}

if (argc > 2)
{
archiveUrl = argv[2];
}

try
{
Downloader downloader;
downloader.Download(archiveUrl);

Extractor extractor;
extractor.Extract(downloader.GetContent());
}
catch (std::runtime_error err)
{
std::cerr << "Updated failed due to an error.\n";
std::cerr << err.what() << std::endl;

std::cout << "Press ENTER to exit." << std::endl;
std::cin.get();
return 1;
}

std::cout << "Update completed. USC will restart after a moment..." << std::endl;

StartUSC();
Sleep(500);

return 0;
}

void StartUSC()
{
char currDir[MAX_PATH];
GetCurrentDirectoryA(sizeof(currDir), currDir);
std::string cd(currDir);
std::string usc_path = cd + "\\usc-game.exe";

STARTUPINFOA info = {sizeof(info)};
PROCESS_INFORMATION processInfo;
CreateProcessA(NULL, &usc_path.front(),
NULL,
NULL,
FALSE,
DETACHED_PROCESS,
NULL,
NULL,
&info,
&processInfo);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
Loading