diff --git a/source/ui/section/action/action.h b/source/ui/section/action/action.h index 20b8380..e72abc3 100644 --- a/source/ui/section/action/action.h +++ b/source/ui/section/action/action.h @@ -11,6 +11,7 @@ void action_delete_system_save_data(system_save_data_info* info, bool* populated void action_install_cias(file_info* info, bool* populated); void action_install_cias_delete(file_info* info, bool* populated); +void action_install_tickets(file_info* info, bool* populated); void action_copy_contents(file_info* info, bool* populated); void action_delete_contents(file_info* info, bool* populated); void action_delete_dir(file_info* info, bool* populated); diff --git a/source/ui/section/action/installtickets.c b/source/ui/section/action/installtickets.c new file mode 100644 index 0000000..6089a21 --- /dev/null +++ b/source/ui/section/action/installtickets.c @@ -0,0 +1,135 @@ +#include +#include +#include + +#include <3ds.h> + +#include "action.h" +#include "../task/task.h" +#include "../../error.h" +#include "../../progressbar.h" +#include "../../prompt.h" +#include "../../../screen.h" +#include "../../../util.h" + +typedef struct { + file_info* base; + u32 processed; + u32 total; + char** contents; +} install_tickets_data; + +static void action_install_tickets_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + ui_draw_file_info(view, ((install_tickets_data*) data)->base, x1, y1, x2, y2); +} + +static void action_install_tickets_free_data(install_tickets_data* data) { + util_free_contents(data->contents, data->total); + free(data); +} + +static void action_install_tickets_done_onresponse(ui_view* view, void* data, bool response) { + action_install_tickets_free_data((install_tickets_data*) data); + + prompt_destroy(view); +} + +static void action_install_tickets_update(ui_view* view, void* data, float* progress, char* progressText) { + install_tickets_data* installData = (install_tickets_data*) data; + + if(hidKeysDown() & KEY_B) { + ui_pop(); + progressbar_destroy(view); + + ui_push(prompt_create("Failed", "Install cancelled.", COLOR_TEXT, false, data, NULL, action_install_tickets_draw_top, action_install_tickets_done_onresponse)); + return; + } + + if(installData->processed >= installData->total) { + ui_pop(); + progressbar_destroy(view); + + ui_push(prompt_create("Success", "Install finished.", COLOR_TEXT, false, data, NULL, action_install_tickets_draw_top, action_install_tickets_done_onresponse)); + return; + } else { + char* path = installData->contents[installData->processed]; + + Result res = 0; + + Handle handle = 0; + if(R_SUCCEEDED(res = FSUSER_OpenFile(&handle, *installData->base->archive, fsMakePath(PATH_ASCII, path), FS_OPEN_READ, 0))) { + u64 size = 0; + if(R_SUCCEEDED(res = FSFILE_GetSize(handle, &size))) { + u8* buffer = (u8*) calloc((size_t) size, 1); + if(buffer != NULL) { + u32 bytesRead = 0; + if(R_SUCCEEDED(res = FSFILE_Read(handle, &bytesRead, 0, buffer, (u32) size)) && bytesRead == size) { + Handle ticketHandle; + if(R_SUCCEEDED(res = AM_InstallTicketBegin(&ticketHandle))) { + u32 bytesWritten = 0; + if(R_FAILED(res = FSFILE_Write(ticketHandle, &bytesWritten, 0, buffer, (u32) size, 0)) || bytesWritten != size || R_FAILED(res = AM_InstallTicketFinalize(ticketHandle))) { + AM_InstallTicketAbort(ticketHandle); + } + } + } + + free(buffer); + } + } + + FSFILE_Close(handle); + } + + installData->processed++; + + if(R_FAILED(res)) { + if(installData->processed >= installData->total - 1) { + ui_pop(); + } + + if(strlen(path) > 48) { + error_display_res(installData->base, ui_draw_file_info, res, "Failed to install ticket.\n%.45s...", path); + } else { + error_display_res(installData->base, ui_draw_file_info, res, "Failed to install ticket.\n%.48s", path); + } + + if(installData->processed >= installData->total - 1) { + action_install_tickets_free_data(installData); + progressbar_destroy(view); + + return; + } + } + } + + *progress = (float) ((double) installData->processed / (double) installData->total); + snprintf(progressText, PROGRESS_TEXT_MAX, "%lu / %lu", installData->processed, installData->total); +} + +static void action_install_tickets_onresponse(ui_view* view, void* data, bool response) { + prompt_destroy(view); + + if(response) { + ui_view* progressView = progressbar_create("Installing tickets(s)", "Press B to cancel.", data, action_install_tickets_update, action_install_tickets_draw_top); + snprintf(progressbar_get_progress_text(progressView), PROGRESS_TEXT_MAX, "0 / %lu", ((install_tickets_data*) data)->total); + ui_push(progressView); + } else { + free(data); + } +} + +void action_install_tickets(file_info* info, bool* populated) { + install_tickets_data* data = (install_tickets_data*) calloc(1, sizeof(install_tickets_data)); + data->base = info; + data->processed = 0; + + Result res = 0; + if(R_FAILED(res = util_populate_contents(&data->contents, &data->total, info->archive, info->path, false, false, ".tik", util_filter_file_extension))) { + error_display_res(info, ui_draw_file_info, res, "Failed to retrieve content list."); + + free(data); + return; + } + + ui_push(prompt_create("Confirmation", "Install the selected ticket(s)?", COLOR_TEXT, true, data, NULL, action_install_tickets_draw_top, action_install_tickets_onresponse)); +} \ No newline at end of file diff --git a/source/ui/section/files.c b/source/ui/section/files.c index 0921e59..ff5c97c 100644 --- a/source/ui/section/files.c +++ b/source/ui/section/files.c @@ -47,6 +47,16 @@ static list_item cia_files_action_items[CIA_FILES_ACTION_COUNT] = { {"Paste", COLOR_TEXT, action_paste_contents}, }; +#define TICKET_FILES_ACTION_COUNT 4 + +static u32 ticket_files_action_count = TICKET_FILES_ACTION_COUNT; +static list_item ticket_files_action_items[TICKET_FILES_ACTION_COUNT] = { + {"Install ticket", COLOR_TEXT, action_install_tickets}, + {"Delete", COLOR_TEXT, action_delete_contents}, + {"Copy", COLOR_TEXT, action_copy_contents}, + {"Paste", COLOR_TEXT, action_paste_contents}, +}; + #define DIRECTORIES_ACTION_COUNT 4 static u32 directories_action_count = DIRECTORIES_ACTION_COUNT; @@ -70,6 +80,31 @@ static list_item cia_directories_action_items[CIA_DIRECTORIES_ACTION_COUNT] = { {"Paste", COLOR_TEXT, action_paste_contents}, }; +#define TICKET_DIRECTORIES_ACTION_COUNT 5 + +static u32 ticket_directories_action_count = TICKET_DIRECTORIES_ACTION_COUNT; +static list_item ticket_directories_action_items[TICKET_DIRECTORIES_ACTION_COUNT] = { + {"Install all tickets", COLOR_TEXT, action_install_tickets}, + {"Delete all contents", COLOR_TEXT, action_delete_dir_contents}, + {"Delete", COLOR_TEXT, action_delete_dir}, + {"Copy", COLOR_TEXT, action_copy_contents}, + {"Paste", COLOR_TEXT, action_paste_contents}, +}; + +#define CIA_TICKET_DIRECTORIES_ACTION_COUNT 8 + +static u32 cia_ticket_directories_action_count = CIA_TICKET_DIRECTORIES_ACTION_COUNT; +static list_item cia_ticket_directories_action_items[CIA_TICKET_DIRECTORIES_ACTION_COUNT] = { + {"Install all CIAs", COLOR_TEXT, action_install_cias}, + {"Install and delete all CIAs", COLOR_TEXT, action_install_cias_delete}, + {"Install all tickets", COLOR_TEXT, action_install_tickets}, + {"Delete all CIAs", COLOR_TEXT, action_delete_dir_cias}, + {"Delete all contents", COLOR_TEXT, action_delete_dir_contents}, + {"Delete", COLOR_TEXT, action_delete_dir}, + {"Copy", COLOR_TEXT, action_copy_contents}, + {"Paste", COLOR_TEXT, action_paste_contents}, +}; + typedef struct { file_info* info; bool* populated; @@ -105,11 +140,21 @@ static void files_action_update(ui_view* view, void* data, list_item** items, u3 } if(actionData->info->isDirectory) { - if(actionData->info->containsCias) { + if(actionData->info->containsCias && actionData->info->containsTickets) { + if(*itemCount != &cia_ticket_directories_action_count || *items != cia_ticket_directories_action_items) { + *itemCount = &cia_ticket_directories_action_count; + *items = cia_ticket_directories_action_items; + } + } else if(actionData->info->containsCias) { if(*itemCount != &cia_directories_action_count || *items != cia_directories_action_items) { *itemCount = &cia_directories_action_count; *items = cia_directories_action_items; } + } else if(actionData->info->containsTickets) { + if(*itemCount != &ticket_directories_action_count || *items != ticket_directories_action_items) { + *itemCount = &ticket_directories_action_count; + *items = ticket_directories_action_items; + } } else { if(*itemCount != &directories_action_count || *items != directories_action_items) { *itemCount = &directories_action_count; @@ -122,6 +167,11 @@ static void files_action_update(ui_view* view, void* data, list_item** items, u3 *itemCount = &cia_files_action_count; *items = cia_files_action_items; } + } else if(actionData->info->isTicket) { + if(*itemCount != &ticket_files_action_count || *items != ticket_files_action_items) { + *itemCount = &ticket_files_action_count; + *items = ticket_files_action_items; + } } else { if(*itemCount != &files_action_count || *items != files_action_items) { *itemCount = &files_action_count; diff --git a/source/ui/section/task/listfiles.c b/source/ui/section/task/listfiles.c index 6fda29d..b13d077 100644 --- a/source/ui/section/task/listfiles.c +++ b/source/ui/section/task/listfiles.c @@ -80,37 +80,48 @@ static void task_populate_files_thread(void* arg) { FSFILE_GetSize(fileHandle, &fileInfo->size); size_t len = strlen(fileInfo->path); - if(len > 4 && strcasecmp(&fileInfo->path[len - 4], ".cia") == 0) { - AM_TitleEntry titleEntry; - if(R_SUCCEEDED(AM_GetCiaFileInfo(MEDIATYPE_SD, &titleEntry, fileHandle))) { - data->dir->containsCias = true; + if(len > 4) { + if(strcasecmp(&fileInfo->path[len - 4], ".cia") == 0) { + AM_TitleEntry titleEntry; + if(R_SUCCEEDED(AM_GetCiaFileInfo(MEDIATYPE_SD, &titleEntry, fileHandle))) { + data->dir->containsCias = true; - fileInfo->isCia = true; - fileInfo->ciaInfo.titleId = titleEntry.titleID; - fileInfo->ciaInfo.version = titleEntry.version; - fileInfo->ciaInfo.installedSizeSD = titleEntry.size; - fileInfo->ciaInfo.hasSmdh = false; + fileInfo->isCia = true; + fileInfo->ciaInfo.titleId = titleEntry.titleID; + fileInfo->ciaInfo.version = titleEntry.version; + fileInfo->ciaInfo.installedSize = titleEntry.size; + fileInfo->ciaInfo.hasSmdh = false; - if(R_SUCCEEDED(AM_GetCiaFileInfo(MEDIATYPE_NAND, &titleEntry, fileHandle))) { - fileInfo->ciaInfo.installedSizeNAND = titleEntry.size; - } else { - fileInfo->ciaInfo.installedSizeNAND = 0; - } + if(((titleEntry.titleID >> 32) & 0x8010) != 0 && R_SUCCEEDED(AM_GetCiaFileInfo(MEDIATYPE_NAND, &titleEntry, fileHandle))) { + fileInfo->ciaInfo.installedSize = titleEntry.size; + } - u32 bytesRead; - if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, fileInfo->size - sizeof(SMDH), &smdh, sizeof(SMDH))) && bytesRead == sizeof(SMDH)) { - if(smdh.magic[0] == 'S' && smdh.magic[1] == 'M' && smdh.magic[2] == 'D' && - smdh.magic[3] == 'H') { + if(R_SUCCEEDED(AM_GetCiaIcon(&smdh, fileHandle))) { u8 systemLanguage = CFG_LANGUAGE_EN; CFGU_GetSystemLanguage(&systemLanguage); fileInfo->ciaInfo.hasSmdh = true; - utf16_to_utf8((uint8_t *) fileInfo->ciaInfo.smdhInfo.shortDescription, smdh.titles[systemLanguage].shortDescription, sizeof(fileInfo->ciaInfo.smdhInfo.shortDescription)); - utf16_to_utf8((uint8_t *) fileInfo->ciaInfo.smdhInfo.longDescription, smdh.titles[systemLanguage].longDescription, sizeof(fileInfo->ciaInfo.smdhInfo.longDescription)); - utf16_to_utf8((uint8_t *) fileInfo->ciaInfo.smdhInfo.publisher, smdh.titles[systemLanguage].publisher, sizeof(fileInfo->ciaInfo.smdhInfo.publisher)); + utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.smdhInfo.shortDescription, smdh.titles[systemLanguage].shortDescription, sizeof(fileInfo->ciaInfo.smdhInfo.shortDescription)); + utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.smdhInfo.longDescription, smdh.titles[systemLanguage].longDescription, sizeof(fileInfo->ciaInfo.smdhInfo.longDescription)); + utf16_to_utf8((uint8_t*) fileInfo->ciaInfo.smdhInfo.publisher, smdh.titles[systemLanguage].publisher, sizeof(fileInfo->ciaInfo.smdhInfo.publisher)); fileInfo->ciaInfo.smdhInfo.texture = screen_load_texture_tiled_auto(smdh.largeIcon, sizeof(smdh.largeIcon), 48, 48, GPU_RGB565, false); } } + } else if(strcasecmp(&fileInfo->path[len - 4], ".tik") == 0) { + u32 bytesRead = 0; + + u8 sigType = 0; + if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, 0, &sigType, sizeof(sigType))) && bytesRead == sizeof(sigType) && sigType <= 5) { + static u32 sigSizes[6] = {0x23C, 0x13C, 0x7C, 0x23C, 0x13C, 0x7C}; + + u64 titleId = 0; + if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, sigSizes[sigType] + 0x9C, &titleId, sizeof(titleId))) && bytesRead == sizeof(titleId)) { + data->dir->containsTickets = true; + + fileInfo->isTicket = true; + fileInfo->ticketInfo.ticketId = titleId; + } + } } } diff --git a/source/ui/section/task/task.h b/source/ui/section/task/task.h index 4d8522a..822a167 100644 --- a/source/ui/section/task/task.h +++ b/source/ui/section/task/task.h @@ -11,26 +11,6 @@ typedef struct { u32 texture; } smdh_info; -typedef struct { - u64 titleId; - u16 version; - u64 installedSizeSD; - u64 installedSizeNAND; - bool hasSmdh; - smdh_info smdhInfo; -} cia_info; - -typedef struct { - FS_Archive* archive; - char name[NAME_MAX]; - char path[PATH_MAX]; - bool isDirectory; - bool containsCias; - u64 size; - bool isCia; - cia_info ciaInfo; -} file_info; - typedef struct { FS_MediaType mediaType; u64 titleId; @@ -64,6 +44,30 @@ typedef struct { u32 systemSaveDataId; } system_save_data_info; +typedef struct { + u64 titleId; + u16 version; + u64 installedSize; + bool hasSmdh; + smdh_info smdhInfo; +} cia_info; + +typedef struct { + FS_Archive* archive; + char name[NAME_MAX]; + char path[PATH_MAX]; + bool isDirectory; + u64 size; + + bool containsCias; + bool isCia; + cia_info ciaInfo; + + bool containsTickets; + bool isTicket; + ticket_info ticketInfo; +} file_info; + typedef struct { bool finished; bool failed; diff --git a/source/ui/ui.c b/source/ui/ui.c index be30ab7..c36b90d 100644 --- a/source/ui/ui.c +++ b/source/ui/ui.c @@ -387,25 +387,25 @@ void ui_draw_file_info(ui_view* view, void* data, float x1, float y1, float x2, float versionY = titleIdY + titleIdHeight + 2; screen_draw_string(buf, versionX, versionY, 0.5f, 0.5f, COLOR_TEXT, false); - snprintf(buf, 64, "Installed Size (SD): %.2f MB", info->ciaInfo.installedSizeSD / 1024.0 / 1024.0); + snprintf(buf, 64, "Installed Size: %.2f MB", info->ciaInfo.installedSize / 1024.0 / 1024.0); - float installedSizeSDWidth; - float installedSizeSDHeight; - screen_get_string_size(&installedSizeSDWidth, &installedSizeSDHeight, buf, 0.5f, 0.5f); + float installedSizeWidth; + float installedSizeHeight; + screen_get_string_size(&installedSizeWidth, &installedSizeHeight, buf, 0.5f, 0.5f); - float installedSizeSDX = x1 + (x2 - x1 - installedSizeSDWidth) / 2; - float installedSizeSDY = versionY + versionHeight + 2; - screen_draw_string(buf, installedSizeSDX, installedSizeSDY, 0.5f, 0.5f, COLOR_TEXT, false); + float installedSizeX = x1 + (x2 - x1 - installedSizeWidth) / 2; + float installedSizeY = versionY + versionHeight + 2; + screen_draw_string(buf, installedSizeX, installedSizeY, 0.5f, 0.5f, COLOR_TEXT, false); + } else if(info->isTicket) { + snprintf(buf, 64, "Ticket ID: %016llX", info->ticketInfo.ticketId); - snprintf(buf, 64, "Installed Size (NAND): %.2f MB", info->ciaInfo.installedSizeNAND / 1024.0 / 1024.0); + float ticketIdWidth; + float ticketIdHeight; + screen_get_string_size(&ticketIdWidth, &ticketIdHeight, buf, 0.5f, 0.5f); - float installedSizeNANDWidth; - float installedSizeNANDHeight; - screen_get_string_size(&installedSizeNANDWidth, &installedSizeNANDHeight, buf, 0.5f, 0.5f); - - float installedSizeNANDX = x1 + (x2 - x1 - installedSizeNANDWidth) / 2; - float installedSizeNANDY = installedSizeSDY + installedSizeSDHeight + 2; - screen_draw_string(buf, installedSizeNANDX, installedSizeNANDY, 0.5f, 0.5f, COLOR_TEXT, false); + float ticketIdX = x1 + (x2 - x1 - ticketIdWidth) / 2; + float ticketIdY = sizeY + sizeHeight + 2; + screen_draw_string(buf, ticketIdX, ticketIdY, 0.5f, 0.5f, COLOR_TEXT, false); } } else { snprintf(buf, 64, "Directory");