class ModDownloader { private: const char* VERIFICATION_FLAG = "-disablemodverification"; const char* CUSTOM_MODS_URL_FLAG = "-customverifiedurl="; const char* STORE_URL = "https://gcdn.thunderstore.io/live/repository/packages/"; const char* DEFAULT_MODS_LIST_URL = "https://raw.githubusercontent.com/R2Northstar/VerifiedMods/main/verified-mods.json"; char* modsListUrl; struct VerifiedModVersion { std::string checksum; }; struct VerifiedModDetails { std::string dependencyPrefix; std::unordered_map<std::string, VerifiedModVersion> versions = {}; }; std::unordered_map<std::string, VerifiedModDetails> verifiedMods = {}; /** * Mod archive download callback. * * This function is called by curl as it's downloading the mod archive; this * will retrieve the current `ModDownloader` instance and update its `modState` * member accordingly. */ static int ModFetchingProgressCallback( void* ptr, curl_off_t totalDownloadSize, curl_off_t finishedDownloadSize, curl_off_t totalToUpload, curl_off_t nowUploaded); /** * Downloads a mod archive from distant store. * * This rebuilds the URI of the mod archive using both a predefined store URI * and the mod dependency string from the `verifiedMods` variable, or using * input mod name as mod dependency string if bypass flag is set up; fetched * archive is then stored in a temporary location. * * If something went wrong during archive download, this will return an empty * optional object. * * @param modName name of the mod to be downloaded * @param modVersion version of the mod to be downloaded * @returns location of the downloaded archive */ std::optional<fs::path> FetchModFromDistantStore(std::string_view modName, std::string_view modVersion); /** * Tells if a mod archive has not been corrupted. * * The mod validation procedure includes computing the SHA256 hash of the final * archive, which is stored in the verified mods list. This hash is used by this * very method to ensure the archive downloaded from the Internet is the exact * same that has been manually verified. * * @param modPath path of the archive to check * @param expectedChecksum checksum the archive should have * @returns whether archive is legit */ bool IsModLegit(fs::path modPath, std::string_view expectedChecksum); /** * Extracts a mod archive to the game folder. * * This extracts a downloaded mod archive from its original location to the * current game profile, in the remote mods folder. * * @param modPath location of the downloaded archive * @returns nothing */ void ExtractMod(fs::path modPath); public: ModDownloader(); /** * Retrieves the verified mods list from the central authority. * * The Northstar auto-downloading feature does NOT allow automatically installing * all mods for various (notably security) reasons; mods that are candidate to * auto-downloading are rather listed on a GitHub repository * (https://raw.githubusercontent.com/R2Northstar/VerifiedMods/main/verified-mods.json), * which this method gets via a HTTP call to load into local state. * * If list fetching fails, local mods list will be initialized as empty, thus * preventing any mod from being auto-downloaded. * * @returns nothing */ void FetchModsListFromAPI(); /** * Checks whether a mod is verified. * * A mod is deemed verified/authorized through a manual validation process that is * described here: https://github.com/R2Northstar/VerifiedMods; in practice, a mod * is considered authorized if their name AND exact version appear in the * `verifiedMods` variable. * * @param modName name of the mod to be checked * @param modVersion version of the mod to be checked, must follow semantic versioning * @returns whether the mod is authorized and can be auto-downloaded */ bool IsModAuthorized(std::string_view modName, std::string_view modVersion); /** * Downloads a given mod from Thunderstore API to local game profile. * * @param modName name of the mod to be downloaded * @param modVersion version of the mod to be downloaded * @returns nothing **/ void DownloadMod(std::string modName, std::string modVersion); enum ModInstallState { // Normal installation process DOWNLOADING, CHECKSUMING, EXTRACTING, DONE, // Everything went great, mod can be used in-game // Errors FAILED, // Generic error message, should be avoided as much as possible FAILED_READING_ARCHIVE, FAILED_WRITING_TO_DISK, MOD_FETCHING_FAILED, MOD_CORRUPTED, // Downloaded archive checksum does not match verified hash NO_DISK_SPACE_AVAILABLE, NOT_FOUND // Mod is not currently being auto-downloaded }; struct MOD_STATE { ModInstallState state; int progress; int total; float ratio; } modState = {}; /** * Cancels installation of the mod. * * Prevents installation of the mod currently being installed, no matter the install * progress (downloading, checksuming, extracting), and frees all resources currently * being used in this purpose. * * @returns nothing */ void CancelDownload(); };