#include #include #include #include <3ds.h> #include "section.h" #include "task/task.h" #include "../error.h" #include "../info.h" #include "../prompt.h" #include "../ui.h" #include "../../core/screen.h" #include "../../core/util.h" #include "../../json/json.h" #define URL_MAX 1024 typedef struct { char url[URL_MAX]; u32 responseCode; data_op_data installInfo; } update_data; static Result update_is_src_directory(void* data, u32 index, bool* isDirectory) { *isDirectory = false; return 0; } static Result update_make_dst_directory(void* data, u32 index) { return 0; } static Result update_open_src(void* data, u32 index, u32* handle) { update_data* updateData = (update_data*) data; Result res = 0; httpcContext* context = (httpcContext*) calloc(1, sizeof(httpcContext)); if(context != NULL) { if(R_SUCCEEDED(res = httpcOpenContext(context, HTTPC_METHOD_GET, updateData->url, 1))) { httpcSetSSLOpt(context, SSLCOPT_DisableVerify); if(R_SUCCEEDED(res = httpcBeginRequest(context)) && R_SUCCEEDED(res = httpcGetResponseStatusCode(context, &updateData->responseCode, 0))) { if(updateData->responseCode == 200) { *handle = (u32) context; } else if(updateData->responseCode == 301 || updateData->responseCode == 302 || updateData->responseCode == 303) { if(R_SUCCEEDED(res = httpcGetResponseHeader(context, "Location", updateData->url, URL_MAX))) { httpcCloseContext(context); free(context); return update_open_src(data, index, handle); } } else { res = R_FBI_HTTP_RESPONSE_CODE; } } if(R_FAILED(res)) { httpcCloseContext(context); } } if(R_FAILED(res)) { free(context); } } else { res = R_FBI_OUT_OF_MEMORY; } return res; } static Result update_close_src(void* data, u32 index, bool succeeded, u32 handle) { return httpcCloseContext((httpcContext*) handle); } static Result update_get_src_size(void* data, u32 handle, u64* size) { u32 downloadSize = 0; Result res = httpcGetDownloadSizeState((httpcContext*) handle, NULL, &downloadSize); *size = downloadSize; return res; } static Result update_read_src(void* data, u32 handle, u32* bytesRead, void* buffer, u64 offset, u32 size) { Result res = httpcDownloadData((httpcContext*) handle, buffer, size, bytesRead); return res != HTTPC_RESULTCODE_DOWNLOADPENDING ? res : 0; } static Result update_open_dst(void* data, u32 index, void* initialReadBlock, u32* handle) { if(util_get_3dsx_path() != NULL) { FS_Path* path = util_make_path_utf8(util_get_3dsx_path()); if(path != NULL) { Result res = FSUSER_OpenFileDirectly(handle, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), *path, FS_OPEN_WRITE | FS_OPEN_CREATE, 0); util_free_path_utf8(path); return res; } else { return R_FBI_OUT_OF_MEMORY; } } else { return AM_StartCiaInstall(MEDIATYPE_SD, handle); } } static Result update_close_dst(void* data, u32 index, bool succeeded, u32 handle) { if(util_get_3dsx_path() != NULL) { return FSFILE_Close(handle); } else { if(succeeded) { return AM_FinishCiaInstall(handle); } else { return AM_CancelCIAInstall(handle); } } } static Result update_write_dst(void* data, u32 handle, u32* bytesWritten, void* buffer, u64 offset, u32 size) { return FSFILE_Write(handle, bytesWritten, offset, buffer, size, 0); } static Result update_suspend_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) { return 0; } static Result update_restore_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) { return 0; } static Result update_suspend(void* data, u32 index) { return 0; } static Result update_restore(void* data, u32 index) { return 0; } static bool update_error(void* data, u32 index, Result res) { update_data* updateData = (update_data*) data; if(res == R_FBI_CANCELLED) { prompt_display("Failure", "Install cancelled.", COLOR_TEXT, false, NULL, NULL, NULL, NULL); } else if(res == R_FBI_HTTP_RESPONSE_CODE) { error_display(NULL, NULL, NULL, "Failed to update FBI.\nHTTP server returned response code %d", updateData->responseCode); } else { error_display_res(NULL, NULL, NULL, res, "Failed to update FBI."); } return false; } static void update_install_update(ui_view* view, void* data, float* progress, char* text) { update_data* updateData = (update_data*) data; if(updateData->installInfo.finished) { ui_pop(); info_destroy(view); if(R_SUCCEEDED(updateData->installInfo.result)) { prompt_display("Success", "Update complete.", COLOR_TEXT, false, NULL, NULL, NULL, NULL); } free(updateData); return; } if(hidKeysDown() & KEY_B) { svcSignalEvent(updateData->installInfo.cancelEvent); } *progress = updateData->installInfo.currTotal != 0 ? (float) ((double) updateData->installInfo.currProcessed / (double) updateData->installInfo.currTotal) : 0; snprintf(text, PROGRESS_TEXT_MAX, "%.2f MiB / %.2f MiB", updateData->installInfo.currProcessed / 1024.0f / 1024.0f, updateData->installInfo.currTotal / 1024.0f / 1024.0f); } static void update_check_update(ui_view* view, void* data, float* progress, char* text) { update_data* updateData = (update_data*) data; bool hasUpdate = false; Result res = 0; u32 responseCode = 0; httpcContext context; if(R_SUCCEEDED(res = httpcOpenContext(&context, HTTPC_METHOD_GET, "https://api.github.com/repos/Steveice10/FBI/releases/latest", 1))) { httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); httpcAddRequestHeaderField(&context, "User-Agent", "FBI"); if(R_SUCCEEDED(res = httpcBeginRequest(&context)) && R_SUCCEEDED(res = httpcGetResponseStatusCode(&context, &responseCode, 0))) { if(responseCode == 200) { u32 size = 0; if(R_SUCCEEDED(res = httpcGetDownloadSizeState(&context, NULL, &size))) { char* text = (char*) calloc(sizeof(char), size); if(text != NULL) { u32 bytesRead = 0; if(R_SUCCEEDED(res = httpcDownloadData(&context, (u8*) text, size, &bytesRead))) { json_value* json = json_parse(text, size); if(json != NULL) { if(json->type == json_object) { json_value* name = NULL; json_value* assets = NULL; for(u32 i = 0; i < json->u.object.length; i++) { json_value* val = json->u.object.values[i].value; if(strncmp(json->u.object.values[i].name, "name", json->u.object.values[i].name_length) == 0 && val->type == json_string) { name = val; } else if(strncmp(json->u.object.values[i].name, "assets", json->u.object.values[i].name_length) == 0 && val->type == json_array) { assets = val; } } if(name != NULL && assets != NULL) { if(strncmp(name->u.string.ptr, VERSION_STRING, name->u.string.length) != 0) { char* url = NULL; for(u32 i = 0; i < assets->u.array.length; i++) { json_value* val = assets->u.array.values[i]; if(val->type == json_object) { json_value* assetName = NULL; json_value* assetUrl = NULL; for(u32 j = 0; j < val->u.object.length; j++) { json_value* subVal = val->u.object.values[j].value; if(strncmp(val->u.object.values[j].name, "name", val->u.object.values[j].name_length) == 0 && subVal->type == json_string) { assetName = subVal; } else if(strncmp(val->u.object.values[j].name, "browser_download_url", val->u.object.values[j].name_length) == 0 && subVal->type == json_string) { assetUrl = subVal; } } if(assetName != NULL && assetUrl != NULL) { if(strncmp(assetName->u.string.ptr, util_get_3dsx_path() != NULL ? "FBI.3dsx" : "FBI.cia", assetName->u.string.length) == 0) { url = assetUrl->u.string.ptr; break; } } } } if(url != NULL) { strncpy(updateData->url, url, URL_MAX); hasUpdate = true; } else { res = R_FBI_BAD_DATA; } } } else { res = R_FBI_BAD_DATA; } } else { res = R_FBI_BAD_DATA; } } else { res = R_FBI_PARSE_FAILED; } } } else { res = R_FBI_OUT_OF_MEMORY; } } } else { res = R_FBI_HTTP_RESPONSE_CODE; } } httpcCloseContext(&context); } ui_pop(); info_destroy(view); if(hasUpdate) { if(R_SUCCEEDED(res = task_data_op(&updateData->installInfo))) { info_display("Updating FBI", "Press B to cancel.", true, data, update_install_update, NULL); } else { error_display_res(NULL, NULL, NULL, res, "Failed to begin update."); } } else { if(R_FAILED(res)) { if(res == R_FBI_HTTP_RESPONSE_CODE) { error_display(NULL, NULL, NULL, "Failed to check for update.\nHTTP server returned response code %d", responseCode); } else { error_display_res(NULL, NULL, NULL, res, "Failed to check for update."); } } else { prompt_display("Success", "No updates available.", COLOR_TEXT, false, NULL, NULL, NULL, NULL); } free(data); } } static void update_onresponse(ui_view* view, void* data, bool response) { if(response) { info_display("Checking For Updates", "", false, data, update_check_update, NULL); } else { free(data); } } void update_open() { update_data* data = (update_data*) calloc(1, sizeof(update_data)); if(data == NULL) { error_display(NULL, NULL, NULL, "Failed to allocate update check data."); return; } data->responseCode = 0; data->installInfo.data = data; data->installInfo.op = DATAOP_COPY; data->installInfo.copyEmpty = false; data->installInfo.total = 1; data->installInfo.isSrcDirectory = update_is_src_directory; data->installInfo.makeDstDirectory = update_make_dst_directory; data->installInfo.openSrc = update_open_src; data->installInfo.closeSrc = update_close_src; data->installInfo.getSrcSize = update_get_src_size; data->installInfo.readSrc = update_read_src; data->installInfo.openDst = update_open_dst; data->installInfo.closeDst = update_close_dst; data->installInfo.writeDst = update_write_dst; data->installInfo.suspendCopy = update_suspend_copy; data->installInfo.restoreCopy = update_restore_copy; data->installInfo.suspend = update_suspend; data->installInfo.restore = update_restore; data->installInfo.error = update_error; data->installInfo.finished = true; prompt_display("Confirmation", "Check for FBI updates?", COLOR_TEXT, true, data, NULL, NULL, update_onresponse); }