diff options
| author | 2024-01-04 05:41:51 -0600 | |
|---|---|---|
| committer | 2024-01-04 05:41:51 -0600 | |
| commit | 302af9fb398d96387fcb6d29041f5ac9dc7d6f1e (patch) | |
| tree | 60a08623ed986a10446b81f7915f8b65c8b002fe /src/assets.c | |
| parent | refactor and download asset index (diff) | |
downloads assets correctly
TODO: pre-1.6 versions don't work yet
Diffstat (limited to 'src/assets.c')
| -rw-r--r-- | src/assets.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/src/assets.c b/src/assets.c index 57624ae..7699d33 100644 --- a/src/assets.c +++ b/src/assets.c @@ -1,8 +1,281 @@ #include "assets.h" +#include "digest/digest.h" #include "version.h" #include "l2su.h" +#include "downloadpool.h" +#include "macros.h" +#include "command.h" +#include <stdio.h> +#include <string.h> +#include <stdio.h> #include <jansson.h> +#include <curl/curl.h> +#include <stdbool.h> +#include <errno.h> +#include <unistd.h> + +struct l2_assets__download_info { + char *url; + const char *name; + char *fullpath; + + size_t expect_size; + l2_sha1_digest_t expect_digest; + + /* initialized in init function */ + FILE *fp; + l2_sha1_state_t digest_state; + size_t recv_size; + + char errbuf[CURL_ERROR_SIZE]; + + struct l2_assets__download_info *next; +}; + +size_t l2_assets__download_asset_dlcb(char *buf, size_t size, size_t nmemb, void *user) +{ + struct l2_assets__download_info *info = user; + size_t realsz = size * nmemb; + + if (fwrite(buf, size, nmemb, info->fp) < nmemb) { + return CURL_WRITEFUNC_ERROR; + } + + l2_sha1_update(&info->digest_state, buf, realsz); + info->recv_size += realsz; + return realsz; +} + +int l2_assets__download_asset_init(CURL *curl, void *user) +{ + struct l2_assets__download_info *info = user; + + l2_sha1_init(&info->digest_state); + + if (l2_launcher_mkdir_parents_ex(info->fullpath, 1) < 0) { + CMD_WARN("Error creating parent directories for %s (%s): %s", info->fullpath, info->name, strerror(errno)); + return -1; + } + + info->fp = fopen(info->fullpath, "wb"); + if (!info->fp) { + CMD_WARN("Error opening %s (%s) for writing: %s", info->fullpath, info->name, strerror(errno)); + return -1; + } + + CMD_DEBUG("asset url %s", info->url); + + curl_easy_setopt(curl, CURLOPT_URL, info->url); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, info); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &l2_assets__download_asset_dlcb); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, info->errbuf); + + return 0; +} + +int l2_assets__download_asset_complete(CURL *curl, CURLcode code, void *user) +{ + struct l2_assets__download_info *info = user; + l2_sha1_digest_t dg; + + fclose(info->fp); + info->fp = NULL; + + if (code != CURLE_OK) { + CMD_WARN("Failed to download asset %s: %s", info->name, curl_easy_strerror(code)); + CMD_DEBUG("Info for %s: %s", info->name, info->errbuf); + return -1; + } + + long rescode; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rescode); + if (rescode / 100 != 2) { + CMD_WARN("Failed to download asset %s: server responded with %ld", info->name, rescode); + return -1; + } + + l2_sha1_finalize(&info->digest_state, &dg); + if (l2_sha1_digest_compare(&dg, &info->expect_digest)) { + char expstr[L2_SHA1_HEX_STRLEN + 1], gotstr[L2_SHA1_HEX_STRLEN + 1]; + l2_sha1_digest_to_hex(&info->expect_digest, expstr); + l2_sha1_digest_to_hex(&dg, gotstr); + + CMD_WARN("Resource %s SHA1 mismatch! (expected %s, got %s)", info->name, expstr, gotstr); + if (unlink(info->fullpath) < 0) { + CMD_WARN("Failed to delete resource %s: %s", info->name, strerror(errno)); + } + return -1; + } + + if (info->recv_size != info->expect_size) { + CMD_WARN("Resource %s has wrong size! (expected %zu bytes, got %zu bytes)", info->name, info->expect_size, info->recv_size); + if (unlink(info->fullpath) < 0) { + CMD_WARN("Failed to delete resource %s: %s", info->name, strerror(errno)); + } + return -1; + } + + return 0; +} + +void l2_assets__free_download_info(struct l2_assets__download_info *info) +{ + if (info->fp) { + fclose(info->fp); + if (unlink(info->fullpath) < 0) { + CMD_WARN("Failed to delete partially downloaded asset %s (%s): %s", info->name, info->fullpath, strerror(errno)); + } + } + + free(info->url); + free(info->fullpath); + free(info); +} + +int l2_assets__download_assets(json_t *asset_index, char **path) +{ + int res = -1; + json_t *objects; + int virtual = 0; + int map_to_resources = 0; + l2_dlpool_t *pool = NULL; + + char *resbasepath = NULL; + + const char *key; + json_t *asset; + struct l2_assets__download_info *dl_info_head, *dl_info_tail; + + if (json_unpack(asset_index, "{s:o, s?:b, s?:b}", "objects", &objects, "virtual", &virtual, "map_to_resources", &map_to_resources) < 0) { + CMD_WARN0("Malformed asset index!"); + return -1; + } + + virtual |= map_to_resources; /* they're the same for our purposes */ + + dl_info_head = dl_info_tail = NULL; + + pool = l2_dlpool_init(&l2_assets__download_asset_init, &l2_assets__download_asset_complete); + if (!pool) { + res = -1; + goto cleanup; + } + + json_object_foreach(objects, key, asset) { + /* key is path */ + + const char *digeststr; + char urldgpart[L2_SHA1_HEX_STRLEN + 1]; + json_int_t size; + l2_sha1_digest_t digest; + + if (json_unpack(asset, "{s:s, s:I}", "hash", &digeststr, "size", &size) < 0) { + CMD_WARN("Malformed asset object %s!", key); + res = -1; + goto cleanup; + } + + if (size <= 0) { + CMD_WARN("Asset object %s has nonpositive size (%" JSON_INTEGER_FORMAT ")!", key, size); + res = -1; + goto cleanup; + } + + if ((res = l2_sha1_digest_from_hex(&digest, digeststr)) < 0) { + CMD_WARN("Asset object %s has invalid SHA1!", key); + res = -1; + goto cleanup; + } + + l2_sha1_digest_to_hex(&digest, urldgpart); + char *path = l2_launcher_sprintf_alloc("%s/assets/objects/%2.2s/%s", l2_state.paths.data, urldgpart, urldgpart); + int shoulddl = l2_launcher_should_download(path, &digest, size); + if (shoulddl < 0) { + free(path); + res = -1; + goto cleanup; + } else if (!shoulddl) { + CMD_DEBUG("Not downloading asset %s", key); + free(path); + continue; + } + + struct l2_assets__download_info *curinfo = calloc(1, sizeof(struct l2_assets__download_info)); + if (!curinfo) { + free(path); + res = -1; + goto cleanup; + } + + if (dl_info_tail) { + dl_info_tail->next = curinfo; + dl_info_tail = curinfo; + } else { + dl_info_head = dl_info_tail = curinfo; + } + + l2_sha1_digest_copy(&curinfo->expect_digest, &digest); + curinfo->expect_size = (size_t)size; + + curinfo->url = l2_launcher_sprintf_alloc("%s/%2.2s/%s", L2_URL_RESOURCES_BASE, urldgpart, urldgpart); + curinfo->fullpath = path; + if (!curinfo->url) { + res = -1; + goto cleanup; + } + + curinfo->name = key; + + if ((res = l2_dlpool_add(pool, curinfo)) <= 0) { + res = -1; + goto cleanup; + } + } + + if (dl_info_head) { + if (l2_dlpool_perform(pool) <= 0) { + res = -1; + goto cleanup; + } + } else { + CMD_INFO0("No assets to download."); + res = 0; + } + + if (virtual) { + resbasepath = l2_launcher_sprintf_alloc("%s/assets/virtual", l2_state.paths.data); + + CMD_ERROR0("virtual resources are not yet implemented. please nag figboot"); + res = -1; + goto cleanup; + } else { + resbasepath = l2_launcher_sprintf_alloc("%s/assets/objects", l2_state.paths.data); + } + + if (!resbasepath) { + res = -1; + goto cleanup; + } + + *path = resbasepath; + resbasepath = NULL; /* prevent out variable from being freed */ + + CMD_INFO0("Successfully downloaded assets."); + res = 0; + +cleanup: + if (resbasepath) free(resbasepath); + if (pool) l2_dlpool_cleanup(pool); + if (dl_info_head) { + for (struct l2_assets__download_info *cur = dl_info_head, *temp; cur; cur = temp) { + temp = cur->next; + l2_assets__free_download_info(cur); + } + } + + return res; +} int l2_assets__download_index(json_t *version, char **path) { |
