diff --git a/source/ui/section/action/action.h b/source/ui/section/action/action.h index e72abc3..a5144a6 100644 --- a/source/ui/section/action/action.h +++ b/source/ui/section/action/action.h @@ -23,6 +23,7 @@ void action_delete_pending_title(pending_title_info* info, bool* populated); void action_delete_all_pending_titles(pending_title_info* info, bool* populated); void action_delete_ticket(ticket_info* info, bool* populated); +void action_install_cdn(ticket_info* info, bool* populated); void action_delete_title(title_info* info, bool* populated); void action_launch_title(title_info* info, bool* populated); diff --git a/source/ui/section/action/installcdn.c b/source/ui/section/action/installcdn.c new file mode 100644 index 0000000..5a435fb --- /dev/null +++ b/source/ui/section/action/installcdn.c @@ -0,0 +1,275 @@ +#include +#include + +#include <3ds.h> + +#include "action.h" +#include "../../error.h" +#include "../../info.h" +#include "../../prompt.h" +#include "../../../screen.h" + +#define CONTENTS_MAX 64 + +typedef struct { + ticket_info* ticket; + + u32 contentCount; + u16 contentIndices[CONTENTS_MAX]; + u32 contentIds[CONTENTS_MAX]; + + u32 responseCode; + + data_op_info installInfo; + Handle cancelEvent; +} install_cdn_data; + +static Result action_install_cdn_is_src_directory(void* data, u32 index, bool* isDirectory) { + *isDirectory = false; + return 0; +} + +static Result action_install_cdn_make_dst_directory(void* data, u32 index) { + return 0; +} + +static Result action_install_cdn_open_src(void* data, u32 index, u32* handle) { + install_cdn_data* installData = (install_cdn_data*) data; + + Result res = 0; + + httpcContext* context = (httpcContext*) calloc(1, sizeof(httpcContext)); + if(context != NULL) { + char url[256]; + if(index == 0) { + snprintf(url, 256, "http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%016llX/tmd", installData->ticket->ticketId); + } else { + snprintf(url, 256, "http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%016llX/%08lX", installData->ticket->ticketId, installData->contentIds[index - 1]); + } + + if(R_SUCCEEDED(res = httpcOpenContext(context, HTTPC_METHOD_GET, url, 1))) { + httpcSetSSLOpt(context, SSLCOPT_DisableVerify); + if(R_SUCCEEDED(res = httpcBeginRequest(context)) && R_SUCCEEDED(res = httpcGetResponseStatusCode(context, &installData->responseCode, 0))) { + if(installData->responseCode == 200) { + *handle = (u32) context; + } 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 action_install_cdn_close_src(void* data, u32 index, bool succeeded, u32 handle) { + return httpcCloseContext((httpcContext*) handle); +} + +static Result action_install_cdn_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 action_install_cdn_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 action_install_cdn_open_dst(void* data, u32 index, void* initialReadBlock, u32* handle) { + install_cdn_data* installData = (install_cdn_data*) data; + + if(index == 0) { + static u32 dataOffsets[6] = {0x240, 0x140, 0x80, 0x240, 0x140, 0x80}; + + u8* tmd = (u8*) initialReadBlock; + u8 sigType = tmd[0x03]; + + installData->contentCount = __builtin_bswap16(*(u16*) &tmd[dataOffsets[sigType] + 0x9E]); + if(installData->contentCount > CONTENTS_MAX) { + return MAKERESULT(RL_PERMANENT, RS_INVALIDARG, RM_APPLICATION, RD_OUT_OF_RANGE); + } + + for(u32 i = 0; i < installData->contentCount; i++) { + u8* contentChunk = &tmd[dataOffsets[sigType] + 0x9C4 + (i * 0x30)]; + + installData->contentIds[i] = __builtin_bswap32(*(u32*) &contentChunk[0x00]); + installData->contentIndices[i] = __builtin_bswap16(*(u16*) &contentChunk[0x04]); + } + + installData->installInfo.total += installData->contentCount; + + return AM_InstallTmdBegin(handle); + } else { + return AM_InstallContentBegin(handle, installData->contentIndices[index - 1]); + } +} + +static Result action_install_cdn_close_dst(void* data, u32 index, bool succeeded, u32 handle) { + if(succeeded) { + if(index == 0) { + return AM_InstallTmdFinish(handle, true); + } else { + return AM_InstallContentFinish(handle); + } + } else { + if(index == 0) { + return AM_InstallTmdAbort(handle); + } else { + return AM_InstallContentCancel(handle); + } + } +} + +static Result action_install_cdn_write_dst(void* data, u32 handle, u32* bytesWritten, void* buffer, u64 offset, u32 size) { + return FSFILE_Write(handle, bytesWritten, offset, buffer, size, 0); +} + +bool action_install_cdn_error(void* data, u32 index, Result res) { + install_cdn_data* installData = (install_cdn_data*) data; + + if(res == R_FBI_CANCELLED) { + prompt_display("Failure", "Install cancelled.", COLOR_TEXT, false, installData->ticket, NULL, ui_draw_ticket_info, NULL); + } else if(res == R_FBI_HTTP_RESPONSE_CODE) { + error_display(NULL, installData->ticket, ui_draw_ticket_info, "Failed to install CDN title.\nHTTP server returned response code %d", installData->responseCode); + } else { + error_display_res(NULL, installData->ticket, ui_draw_ticket_info, res, "Failed to install CDN title."); + } + + return false; +} + +static void action_install_cdn_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + ui_draw_ticket_info(view, ((install_cdn_data*) data)->ticket, x1, y1, x2, y2); +} + +static void action_install_cdn_free_data(install_cdn_data* data) { + free(data); +} + +static void action_install_cdn_update(ui_view* view, void* data, float* progress, char* text) { + install_cdn_data* installData = (install_cdn_data*) data; + + if(installData->installInfo.finished) { + ui_pop(); + info_destroy(view); + + if(!installData->installInfo.premature) { + Result res = 0; + if(R_SUCCEEDED(res = AM_InstallTitleFinish()) + && R_SUCCEEDED(res = AM_CommitImportTitlesAndUpdateFirmwareAuto(((installData->ticket->ticketId >> 32) & 0x8010) != 0 ? MEDIATYPE_NAND : MEDIATYPE_SD, 1, false, &installData->ticket->ticketId))) { + prompt_display("Success", "Install finished.", COLOR_TEXT, false, installData->ticket, NULL, ui_draw_ticket_info, NULL); + } else { + AM_InstallTitleAbort(); + + error_display_res(NULL, installData->ticket, ui_draw_ticket_info, res, "Failed to install CDN title."); + } + } else { + AM_InstallTitleAbort(); + } + + action_install_cdn_free_data(installData); + + return; + } + + if(hidKeysDown() & KEY_B) { + svcSignalEvent(installData->cancelEvent); + } + + *progress = installData->installInfo.currTotal != 0 ? (float) ((double) installData->installInfo.currProcessed / (double) installData->installInfo.currTotal) : 0; + snprintf(text, PROGRESS_TEXT_MAX, "%lu / %lu\n%.2f MB / %.2f MB", installData->installInfo.processed, installData->installInfo.total, installData->installInfo.currProcessed / 1024.0 / 1024.0, installData->installInfo.currTotal / 1024.0 / 1024.0); +} + +static void action_install_cdn_onresponse(ui_view* view, void* data, bool response) { + install_cdn_data* installData = (install_cdn_data*) data; + + if(response) { + u8 n3ds = false; + if(R_SUCCEEDED(APT_CheckNew3DS(&n3ds)) && !n3ds && ((installData->ticket->ticketId >> 28) & 0xF) == 2) { + error_display(NULL, installData->ticket, ui_draw_ticket_info, "Failed to install CDN title.\nAttempted to install N3DS title to O3DS."); + + action_install_cdn_free_data(installData); + + return; + } + + FS_MediaType dest = ((installData->ticket->ticketId >> 32) & 0x8010) != 0 ? MEDIATYPE_NAND : MEDIATYPE_SD; + + AM_DeleteTitle(dest, installData->ticket->ticketId); + if(dest == MEDIATYPE_SD) { + AM_QueryAvailableExternalTitleDatabase(NULL); + } + + Result res = 0; + + if(R_SUCCEEDED(res = AM_InstallTitleBegin(dest, installData->ticket->ticketId, false))) { + installData->cancelEvent = task_data_op(&installData->installInfo); + if(installData->cancelEvent != 0) { + info_display("Installing CDN Title", "Press B to cancel.", true, data, action_install_cdn_update, action_install_cdn_draw_top); + } else { + AM_InstallTitleAbort(); + } + } + + if(R_FAILED(res) || installData->cancelEvent == 0) { + error_display(NULL, installData->ticket, ui_draw_ticket_info, "Failed to initiate CDN title installation."); + + action_install_cdn_free_data(installData); + } + } else { + action_install_cdn_free_data(installData); + } +} + +void action_install_cdn(ticket_info* info, bool* populated) { + install_cdn_data* data = (install_cdn_data*) calloc(1, sizeof(install_cdn_data)); + if(data == NULL) { + error_display(NULL, NULL, NULL, "Failed to allocate install CDN data."); + + return; + } + + data->ticket = info; + + data->responseCode = 0; + + data->installInfo.data = data; + + data->installInfo.op = DATAOP_COPY; + + data->installInfo.copyEmpty = false; + + data->installInfo.total = 1; + + data->installInfo.isSrcDirectory = action_install_cdn_is_src_directory; + data->installInfo.makeDstDirectory = action_install_cdn_make_dst_directory; + + data->installInfo.openSrc = action_install_cdn_open_src; + data->installInfo.closeSrc = action_install_cdn_close_src; + data->installInfo.getSrcSize = action_install_cdn_get_src_size; + data->installInfo.readSrc = action_install_cdn_read_src; + + data->installInfo.openDst = action_install_cdn_open_dst; + data->installInfo.closeDst = action_install_cdn_close_dst; + data->installInfo.writeDst = action_install_cdn_write_dst; + + data->installInfo.error = action_install_cdn_error; + + data->cancelEvent = 0; + + prompt_display("Confirmation", "Install the selected title from the CDN?", COLOR_TEXT, true, data, NULL, action_install_cdn_draw_top, action_install_cdn_onresponse); +} \ No newline at end of file diff --git a/source/ui/section/action/installcias.c b/source/ui/section/action/installcias.c index 3807d67..119b098 100644 --- a/source/ui/section/action/installcias.c +++ b/source/ui/section/action/installcias.c @@ -96,7 +96,7 @@ static Result action_install_cias_open_dst(void* data, u32 index, void* initialR AM_DeleteTitle(dest, titleId); AM_DeleteTicket(titleId); - if(dest == 1) { + if(dest == MEDIATYPE_SD) { AM_QueryAvailableExternalTitleDatabase(NULL); } } diff --git a/source/ui/section/action/installtickets.c b/source/ui/section/action/installtickets.c index c4d0191..34ea8fe 100644 --- a/source/ui/section/action/installtickets.c +++ b/source/ui/section/action/installtickets.c @@ -63,7 +63,7 @@ static Result action_install_tickets_open_dst(void* data, u32 index, void* initi static Result action_install_tickets_close_dst(void* data, u32 index, bool succeeded, u32 handle) { if(succeeded) { - return AM_InstallTicketFinalize(handle); + return AM_InstallTicketFinish(handle); } else { return AM_InstallTicketAbort(handle); } diff --git a/source/ui/section/networkinstall.c b/source/ui/section/networkinstall.c index c5d31cd..9d0caee 100644 --- a/source/ui/section/networkinstall.c +++ b/source/ui/section/networkinstall.c @@ -117,7 +117,7 @@ static Result networkinstall_open_dst(void* data, u32 index, void* initialReadBl AM_DeleteTitle(dest, titleId); AM_DeleteTicket(titleId); - if(dest == 1) { + if(dest == MEDIATYPE_SD) { AM_QueryAvailableExternalTitleDatabase(NULL); } } diff --git a/source/ui/section/qrinstall.c b/source/ui/section/qrinstall.c index 98d5118..5db9065 100644 --- a/source/ui/section/qrinstall.c +++ b/source/ui/section/qrinstall.c @@ -113,7 +113,7 @@ static Result qrinstall_open_dst(void* data, u32 index, void* initialReadBlock, AM_DeleteTitle(dest, titleId); AM_DeleteTicket(titleId); - if(dest == 1) { + if(dest == MEDIATYPE_SD) { AM_QueryAvailableExternalTitleDatabase(NULL); } } @@ -164,7 +164,11 @@ static bool qrinstall_error(void* data, u32 index, Result res) { error_display(&dismissed, NULL, NULL, "Failed to install CIA file.\n%.48s\nAttempted to install N3DS title to O3DS.", url); } } else if(res == R_FBI_HTTP_RESPONSE_CODE) { - error_display(&dismissed, NULL, NULL, "Failed to install CIA file.\nHTTP server returned response code %d", ((qr_install_data*) data)->responseCode); + if(strlen(url) > 48) { + error_display(&dismissed, NULL, NULL, "Failed to install CIA file.\n%.45s...\nHTTP server returned response code %d", url, qrInstallData->responseCode); + } else { + error_display(&dismissed, NULL, NULL, "Failed to install CIA file.\n%.48s\nHTTP server returned response code %d", url, qrInstallData->responseCode); + } } else { if(strlen(url) > 48) { error_display_res(&dismissed, NULL, NULL, res, "Failed to install CIA file.\n%.45s...", url); @@ -364,6 +368,7 @@ void qrinstall_open() { data->tex = 0; + data->responseCode = 0; data->currTitleId = 0; data->installInfo.data = data; diff --git a/source/ui/section/tickets.c b/source/ui/section/tickets.c index 2c74cf6..754af4f 100644 --- a/source/ui/section/tickets.c +++ b/source/ui/section/tickets.c @@ -17,10 +17,11 @@ typedef struct { bool populated; } tickets_data; -#define TICKETS_ACTION_COUNT 1 +#define TICKETS_ACTION_COUNT 2 static u32 tickets_action_count = TICKETS_ACTION_COUNT; static list_item tickets_action_items[TICKETS_ACTION_COUNT] = { + {"Install from CDN", COLOR_TEXT, action_install_cdn}, {"Delete Ticket", COLOR_TEXT, action_delete_ticket}, };