From b77a18b6f0b81bf8a27f47f20f3bcfcf08279b91 Mon Sep 17 00:00:00 2001 From: dankmeme01 <42031238+dankmeme01@users.noreply.github.com> Date: Wed, 12 Aug 2026 22:40:42 +0200 Subject: [PATCH] download loader from given urls by the server --- loader/src/loader/updater.cpp | 108 ++++++++++++++++------------------ loader/src/loader/updater.hpp | 8 ++- loader/src/server/Server.cpp | 15 +++++ loader/src/server/Server.hpp | 9 +++ 4 files changed, 80 insertions(+), 60 deletions(-) diff --git a/loader/src/loader/updater.cpp b/loader/src/loader/updater.cpp index ac31d871e..65f629038 100644 --- a/loader/src/loader/updater.cpp +++ b/loader/src/loader/updater.cpp @@ -17,16 +17,6 @@ static StringMap> 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"); @@ -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) @@ -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(); @@ -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) { @@ -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()); @@ -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; @@ -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(); @@ -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( @@ -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); diff --git a/loader/src/loader/updater.hpp b/loader/src/loader/updater.hpp index 64d94afd6..bf66fccdb 100644 --- a/loader/src/loader/updater.hpp +++ b/loader/src/loader/updater.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace geode::updater { using UpdateFinished = std::monostate; @@ -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(); diff --git a/loader/src/server/Server.cpp b/loader/src/server/Server.cpp index e0efa6baf..4fa61c595 100644 --- a/loader/src/server/Server.cpp +++ b/loader/src/server/Server.cpp @@ -620,6 +620,14 @@ Result ServerModsList::parse(matjson::Value raw) { return payload.ok(std::move(list)); } +Result 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::parse(matjson::Value raw) { auto root = checkJson(std::move(raw), "ServerLoaderVersion"); @@ -631,6 +639,13 @@ Result 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)); } diff --git a/loader/src/server/Server.hpp b/loader/src/server/Server.hpp index 7ed770047..d356c811c 100644 --- a/loader/src/server/Server.hpp +++ b/loader/src/server/Server.hpp @@ -112,11 +112,20 @@ namespace server { static Result parse(matjson::Value json); }; + struct ServerLoaderDownload final { + std::string url; + std::string hash; + + static Result 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 parse(matjson::Value json); };