Skip to content
Open
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
108 changes: 51 additions & 57 deletions loader/src/loader/updater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,6 @@ static StringMap<async::TaskHolder<web::WebResponse>> RUNNING_REQUESTS {};

bool s_isNewUpdateDownloaded = false;

namespace {
inline std::string formatDownloadUrl(std::string_view tag) {
return fmt::format("https://github.com/geode-sdk/geode/releases/download/{0}/geode-{0}-{1}.zip", tag, GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH);
}

inline std::string formatResourcesUrl(std::string_view tag) {
return fmt::format("https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", tag);
}
}

void updater::downloadLatestLoaderResources() {
log::debug("Downloading latest resources");

Expand All @@ -36,10 +26,7 @@ void updater::downloadLatestLoaderResources() {
if (res.ok()) {
auto& release = res.unwrap();

updater::tryDownloadLoaderResources(
formatResourcesUrl(release.tag),
false
);
updater::tryDownloadLoaderResources(release.resources.url, release.resources.hash, false);
} else {
ResourceDownloadEvent().send(
UpdateFailed("Unable to download resources: " + res.unwrapErr().details)
Expand All @@ -49,7 +36,13 @@ void updater::downloadLatestLoaderResources() {
);
}

Result<> updater::extractLoaderResources(ByteSpan data) {
Result<> updater::extractLoaderResources(ByteSpan data, std::string_view expectedHash) {
auto actualHash = geode::sha256(data).toString();
if (actualHash != expectedHash) {
log::error("Hash mismatch in downloaded resources: expected {} but got {}", expectedHash, actualHash);
return Err("Hash mismatch in downloaded resources");
}

auto tempDir = dirs::getGeodeResourcesDir() / fmt::format("{}_tmp", Mod::get()->getID());
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();

Expand Down Expand Up @@ -90,7 +83,7 @@ Result<> updater::extractLoaderResources(ByteSpan data) {
return Ok();
}

void updater::tryDownloadLoaderResources(std::string url, bool tryLatestOnError) {
void updater::tryDownloadLoaderResources(std::string url, std::string hash, bool tryLatestOnError) {
if (RUNNING_REQUESTS.contains(url)) return;

auto progress = [](const web::WebProgress& prog) {
Expand All @@ -106,10 +99,10 @@ void updater::tryDownloadLoaderResources(std::string url, bool tryLatestOnError)
holder.spawn(
"Geode resources download",
web::WebRequest{}.onProgress(std::move(progress)).get(url),
[url](auto response) {
[url, hash = std::move(hash)](auto response) {
if (response.ok()) {
auto data = std::move(response).data();
if (GEODE_UNWRAP_IF_ERR(e, updater::extractLoaderResources(data))) {
if (GEODE_UNWRAP_IF_ERR(e, updater::extractLoaderResources(data, hash))) {
ResourceDownloadEvent().send(UpdateFailed(e));
} else {
ResourceDownloadEvent().send(UpdateFinished());
Expand Down Expand Up @@ -151,9 +144,7 @@ void updater::downloadLoaderResources(bool useLatestRelease) {
if (res.ok()) {
auto& release = res.unwrap();

updater::tryDownloadLoaderResources(
formatResourcesUrl(release.tag), false
);
updater::tryDownloadLoaderResources(release.resources.url, release.resources.hash, false);

DOWNLOADING_LOADER_RESOURCES = false;
return;
Expand Down Expand Up @@ -233,7 +224,7 @@ bool updater::verifyLoaderResources() {
return true;
}

void updater::downloadLoaderUpdate(std::string url) {
void updater::downloadLoaderUpdate(std::string url, std::string hash) {
if (RUNNING_REQUESTS.contains("@downloadLoaderUpdate")) return;

auto req = web::WebRequest();
Expand All @@ -249,49 +240,53 @@ void updater::downloadLoaderUpdate(std::string url) {
auto& holder = RUNNING_REQUESTS["@downloadLoaderUpdate"];
holder.spawn(
req.get(std::move(url)),
[](web::WebResponse response) {
[hash = std::move(hash)](web::WebResponse response) {
RUNNING_REQUESTS.erase("@downloadLoaderUpdate");

auto updateZip = dirs::getTempDir() / "loader-update.zip";
auto targetDir = dirs::getGeodeDir() / "update";

if (response.ok()) {
// unzip resources zip
auto data = std::move(response).data();
auto unzip = file::Unzip::create(data);
if (unzip) {
auto ok = unzip.unwrap().extractAllTo(targetDir);
if (ok) {
s_isNewUpdateDownloaded = true;
LoaderUpdateEvent().send(UpdateFinished());
}
else {
LoaderUpdateEvent().send(
UpdateFailed("Unable to unzip update: " + ok.unwrapErr())
);
Mod::get()->setSavedValue("last-modified-auto-update-check", std::string());
}
}
else {
LoaderUpdateEvent().send(
UpdateFailed("Unable to unzip update: " + unzip.unwrapErr())
);
Mod::get()->setSavedValue("last-modified-auto-update-check", std::string());
}
}
else {
auto info = response.string().unwrapOr("Unknown error");
log::error("Failed to download latest update {}", info);
auto result = installLoaderUpdate(std::move(response), hash);
if (!result) {
log::error("Failed to install latest update: {}", result.unwrapErr());
LoaderUpdateEvent().send(
UpdateFailed("Unable to download update: " + info)
UpdateFailed(fmt::format("Unable to install loader update: {}", result.unwrapErr()))
);

Mod::get()->setSavedValue("last-modified-auto-update-check", std::string());
}
}
);
}

Result<> updater::installLoaderUpdate(utils::web::WebResponse response, std::string_view expectedHash) {
auto targetDir = dirs::getGeodeDir() / "update";

if (!response.ok()) {
auto info = response.string().unwrapOr("Unknown error");
return Err("Download failed: {}", info);
}

// validate hash
auto data = std::move(response).data();
auto actualHash = geode::sha256(data).toString();
if (actualHash != expectedHash) {
log::error("Hash mismatch in downloaded loader update, we expected {}, but got {}", expectedHash, actualHash);
return Err("Hash mismatch in downloaded loader update");
}

// unzip resources zip
auto unzip = file::Unzip::create(data);
if (!unzip) {
return Err("Unable to unzip update: {}", unzip.unwrapErr());
}

auto ok = unzip.unwrap().extractAllTo(targetDir);
if (!ok) {
return Err("Unable to extract update: {}", ok.unwrapErr());
}

s_isNewUpdateDownloaded = true;
LoaderUpdateEvent().send(UpdateFinished());
return Ok();
}

void updater::checkForLoaderUpdates() {
// Check for updates in the background
async::spawn(
Expand All @@ -314,8 +309,7 @@ void updater::checkForLoaderUpdates() {
return;
}

// find release asset
updater::downloadLoaderUpdate(formatDownloadUrl(release.tag));
updater::downloadLoaderUpdate(release.download.url, release.download.hash);
} else {
auto info = res.unwrapErr().details;
log::error("Failed to fetch updates {}", info);
Expand Down
8 changes: 5 additions & 3 deletions loader/src/loader/updater.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <Geode/loader/Event.hpp>
#include <Geode/utils/function.hpp>
#include <Geode/utils/general.hpp>
#include <Geode/utils/web.hpp>

namespace geode::updater {
using UpdateFinished = std::monostate;
Expand All @@ -25,11 +26,12 @@ namespace geode::updater {
};

void updateSpecialFiles();
Result<> extractLoaderResources(ByteSpan data);
void tryDownloadLoaderResources(std::string url, bool tryLatestOnError = true);
Result<> extractLoaderResources(ByteSpan data, std::string_view hash);
void tryDownloadLoaderResources(std::string url, std::string hash, bool tryLatestOnError = true);
void downloadLoaderResources(bool useLatestRelease = false);
void downloadLatestLoaderResources();
void downloadLoaderUpdate(std::string url);
void downloadLoaderUpdate(std::string url, std::string hash);
Result<> installLoaderUpdate(utils::web::WebResponse response, std::string_view expectedHash);

bool verifyLoaderResources();
void checkForLoaderUpdates();
Expand Down
15 changes: 15 additions & 0 deletions loader/src/server/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,14 @@ Result<ServerModsList> ServerModsList::parse(matjson::Value raw) {
return payload.ok(std::move(list));
}

Result<ServerLoaderDownload> ServerLoaderDownload::parse(matjson::Value json) {
auto root = checkJson(std::move(json), "ServerLoaderDownload");
auto res = ServerLoaderDownload();
root.needs("url").into(res.url);
root.needs("hash").into(res.hash);
return root.ok(std::move(res));
}

Result<ServerLoaderVersion> ServerLoaderVersion::parse(matjson::Value raw) {
auto root = checkJson(std::move(raw), "ServerLoaderVersion");

Expand All @@ -631,6 +639,13 @@ Result<ServerLoaderVersion> ServerLoaderVersion::parse(matjson::Value raw) {
auto gd_obj = root.needs("gd");
gd_obj.needs(GEODE_PLATFORM_SHORT_IDENTIFIER).into(res.gameVersion);

auto downloads = root.needs("downloads");

auto dlobj = downloads.needs(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH);
auto rsobj = downloads.needs("resources");
res.download = GEODE_UNWRAP(ServerLoaderDownload::parse(dlobj.takeJson()));
res.resources = GEODE_UNWRAP(ServerLoaderDownload::parse(rsobj.takeJson()));

return root.ok(std::move(res));
}

Expand Down
9 changes: 9 additions & 0 deletions loader/src/server/Server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,20 @@ namespace server {
static Result<ServerModsList> parse(matjson::Value json);
};

struct ServerLoaderDownload final {
std::string url;
std::string hash;

static Result<ServerLoaderDownload> parse(matjson::Value json);
};

struct ServerLoaderVersion final {
std::string version;
std::string tag;
std::string commitHash;
std::string gameVersion;
ServerLoaderDownload download;
ServerLoaderDownload resources;

static Result<ServerLoaderVersion> parse(matjson::Value json);
};
Expand Down
Loading