#include "assets.h" #include "digest/digest.h" #include "version.h" #include "l2su.h" #include "downloadpool.h" #include "macros.h" #include "command.h" #include #include #include #include #include #include #include #include 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) { const char *url = NULL; const char *id = NULL; const char *digeststr = NULL; json_int_t jsize = 0; l2_sha1_digest_t expect_digest; if (json_unpack(version, "{s:{s?:s, s:s, s:I, s:s}}", "assetIndex", "url", &url, "id", &id, "size", &jsize, "sha1", &digeststr) < 0) { return -1; } if (jsize < 0) return -1; if (l2_sha1_digest_from_hex(&expect_digest, digeststr) < 0) return -1; char *pathstr = l2_launcher_sprintf_alloc("%s/assets/indexes/%s.json", l2_state.paths.data, id); if (!pathstr) return -1; int res = l2_launcher_download_checksummed(url, pathstr, &expect_digest, (size_t)jsize); if (res < 0) { free(pathstr); return res; } *path = pathstr; return res; } int l2_assets__load_index(json_t *version, json_t **asset_index) { char *path = NULL; int res; res = l2_assets__download_index(version, &path); if (res < 0) { goto cleanup; } json_error_t jerr; json_t *js; if (!(js = json_load_file(path, JSON_REJECT_DUPLICATES, &jerr))) { CMD_ERROR("JSON error parsing asset index: %s", jerr.text); res = -1; goto cleanup; } free(path); *asset_index = js; return res; cleanup: free(path); return res; }