Initial TitleDB support.

This commit is contained in:
Steven Smith 2016-07-12 14:01:06 -07:00
parent 8c25d82c80
commit 6bca2bd74f
23 changed files with 979 additions and 577 deletions

View File

@ -8,39 +8,39 @@
#define MAX_TEXTURES 1024
#define TEXTURE_BOTTOM_SCREEN_BG 0
#define TEXTURE_BOTTOM_SCREEN_TOP_BAR 1
#define TEXTURE_BOTTOM_SCREEN_TOP_BAR_SHADOW 2
#define TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR 3
#define TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR_SHADOW 4
#define TEXTURE_TOP_SCREEN_BG 5
#define TEXTURE_TOP_SCREEN_TOP_BAR 6
#define TEXTURE_TOP_SCREEN_TOP_BAR_SHADOW 7
#define TEXTURE_TOP_SCREEN_BOTTOM_BAR 8
#define TEXTURE_TOP_SCREEN_BOTTOM_BAR_SHADOW 9
#define TEXTURE_LOGO 10
#define TEXTURE_SELECTION_OVERLAY 11
#define TEXTURE_SCROLL_BAR 12
#define TEXTURE_BUTTON_SMALL 13
#define TEXTURE_BUTTON_LARGE 14
#define TEXTURE_PROGRESS_BAR_BG 15
#define TEXTURE_PROGRESS_BAR_CONTENT 16
#define TEXTURE_META_INFO_BOX 17
#define TEXTURE_META_INFO_BOX_SHADOW 18
#define TEXTURE_BATTERY_CHARGING 19
#define TEXTURE_BATTERY_0 20
#define TEXTURE_BATTERY_1 21
#define TEXTURE_BATTERY_2 22
#define TEXTURE_BATTERY_3 23
#define TEXTURE_BATTERY_4 24
#define TEXTURE_BATTERY_5 25
#define TEXTURE_WIFI_DISCONNECTED 26
#define TEXTURE_WIFI_0 27
#define TEXTURE_WIFI_1 28
#define TEXTURE_WIFI_2 29
#define TEXTURE_WIFI_3 30
#define TEXTURE_BOTTOM_SCREEN_BG 1
#define TEXTURE_BOTTOM_SCREEN_TOP_BAR 2
#define TEXTURE_BOTTOM_SCREEN_TOP_BAR_SHADOW 3
#define TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR 4
#define TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR_SHADOW 5
#define TEXTURE_TOP_SCREEN_BG 6
#define TEXTURE_TOP_SCREEN_TOP_BAR 7
#define TEXTURE_TOP_SCREEN_TOP_BAR_SHADOW 8
#define TEXTURE_TOP_SCREEN_BOTTOM_BAR 9
#define TEXTURE_TOP_SCREEN_BOTTOM_BAR_SHADOW 10
#define TEXTURE_LOGO 11
#define TEXTURE_SELECTION_OVERLAY 12
#define TEXTURE_SCROLL_BAR 13
#define TEXTURE_BUTTON_SMALL 14
#define TEXTURE_BUTTON_LARGE 15
#define TEXTURE_PROGRESS_BAR_BG 16
#define TEXTURE_PROGRESS_BAR_CONTENT 17
#define TEXTURE_META_INFO_BOX 18
#define TEXTURE_META_INFO_BOX_SHADOW 19
#define TEXTURE_BATTERY_CHARGING 20
#define TEXTURE_BATTERY_0 21
#define TEXTURE_BATTERY_1 22
#define TEXTURE_BATTERY_2 23
#define TEXTURE_BATTERY_3 24
#define TEXTURE_BATTERY_4 25
#define TEXTURE_BATTERY_5 26
#define TEXTURE_WIFI_DISCONNECTED 27
#define TEXTURE_WIFI_0 28
#define TEXTURE_WIFI_1 29
#define TEXTURE_WIFI_2 30
#define TEXTURE_WIFI_3 31
#define TEXTURE_AUTO_START 31
#define TEXTURE_AUTO_START 32
#define NUM_COLORS 9

View File

@ -227,7 +227,7 @@ static void do_memchunkhax2(void)
if (!mch2.threads[i].keep)
svcCloseHandle(mch2.threads[i].handle);
svcCreateEvent(&mch2.dummy_threads_lock, 1);
svcCreateEvent(&mch2.dummy_threads_lock, RESET_STICKY);
svcClearEvent(mch2.dummy_threads_lock);
for (i = 0; i < mch2.threads_limit; i++)
@ -311,8 +311,8 @@ static void do_memchunkhax2(void)
volatile u32* thread_ACL = &mapped_page[THREAD_PAGE_ACL_OFFSET >> 2];
svcCreateEvent(&mch2.main_thread_lock, 0);
svcCreateEvent(&mch2.target_threads_lock, 1);
svcCreateEvent(&mch2.main_thread_lock, RESET_ONESHOT);
svcCreateEvent(&mch2.target_threads_lock, RESET_STICKY);
svcClearEvent(mch2.target_threads_lock);
for (i = 0; i < mch2.threads_limit; i++)

View File

@ -21,6 +21,7 @@ static list_item pending_titles = {"Pending Titles", COLOR_TEXT, pendingtitles_o
static list_item tickets = {"Tickets", COLOR_TEXT, tickets_open};
static list_item ext_save_data = {"Ext Save Data", COLOR_TEXT, extsavedata_open};
static list_item system_save_data = {"System Save Data", COLOR_TEXT, systemsavedata_open};
static list_item titledb = {"TitleDB", COLOR_TEXT, titledb_open};
static list_item network_install = {"Network Install", COLOR_TEXT, networkinstall_open};
static list_item qr_code_install = {"QR Code Install", COLOR_TEXT, qrinstall_open};
static list_item update = {"Update", COLOR_TEXT, update_open};
@ -60,6 +61,7 @@ static void mainmenu_update(ui_view* view, void* data, linked_list* items, list_
linked_list_add(items, &tickets);
linked_list_add(items, &ext_save_data);
linked_list_add(items, &system_save_data);
linked_list_add(items, &titledb);
linked_list_add(items, &network_install);
linked_list_add(items, &qr_code_install);
linked_list_add(items, &update);

View File

@ -45,4 +45,7 @@ void action_import_twl_save(linked_list* items, list_item* selected);
void action_browse_title_save_data(linked_list* items, list_item* selected);
void action_import_secure_value(linked_list* items, list_item* selected);
void action_export_secure_value(linked_list* items, list_item* selected);
void action_delete_secure_value(linked_list* items, list_item* selected);
void action_delete_secure_value(linked_list* items, list_item* selected);
void action_url_install(const char* confirmMessage, const char* urls);
void action_install_titledb(linked_list* items, list_item* selected);

View File

@ -0,0 +1,14 @@
#include <stdio.h>
#include <3ds.h>
#include "action.h"
#include "../task/task.h"
#include "../../list.h"
void action_install_titledb(linked_list* items, list_item* selected) {
char url[128];
snprintf(url, sizeof(url), "https://api.titledb.com/v0/proxy/%016llX", ((titledb_info*) selected->data)->titleId);
action_url_install("Install the selected title from TitleDB?", url);
}

View File

@ -0,0 +1,379 @@
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <3ds.h>
#include "../action/action.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"
#define URL_MAX 1024
#define URLS_MAX 128
typedef struct {
char urls[URLS_MAX][URL_MAX];
bool cdn;
bool cdnDecided;
u32 responseCode;
bool ticket;
u64 currTitleId;
volatile bool n3dsContinue;
ticket_info ticketInfo;
data_op_data installInfo;
} url_install_data;
static void action_url_install_cdn_check_onresponse(ui_view* view, void* data, bool response) {
url_install_data* installData = (url_install_data*) data;
installData->cdn = response;
installData->cdnDecided = true;
}
static void action_url_install_n3ds_onresponse(ui_view* view, void* data, bool response) {
((url_install_data*) data)->n3dsContinue = response;
}
static Result action_url_install_is_src_directory(void* data, u32 index, bool* isDirectory) {
*isDirectory = false;
return 0;
}
static Result action_url_install_make_dst_directory(void* data, u32 index) {
return 0;
}
static Result action_url_install_open_src(void* data, u32 index, u32* handle) {
url_install_data* installData = (url_install_data*) data;
Result res = 0;
httpcContext* context = (httpcContext*) calloc(1, sizeof(httpcContext));
if(context != NULL) {
if(R_SUCCEEDED(res = httpcOpenContext(context, HTTPC_METHOD_GET, installData->urls[index], 1))) {
char userAgent[128];
snprintf(userAgent, sizeof(userAgent), "Mozilla/5.0 (Nintendo 3DS; Mobile; rv:10.0) Gecko/20100101 FBI/%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
if(R_SUCCEEDED(res = httpcSetSSLOpt(context, SSLCOPT_DisableVerify)) && R_SUCCEEDED(res = httpcAddRequestHeaderField(context, "User-Agent", userAgent)) && R_SUCCEEDED(res = httpcBeginRequest(context)) && R_SUCCEEDED(res = httpcGetResponseStatusCode(context, &installData->responseCode, 0))) {
if(installData->responseCode == 200) {
*handle = (u32) context;
} else if(installData->responseCode == 301 || installData->responseCode == 302 || installData->responseCode == 303) {
memset(installData->urls[index], '\0', URL_MAX);
if(R_SUCCEEDED(res = httpcGetResponseHeader(context, "Location", installData->urls[index], URL_MAX))) {
httpcCloseContext(context);
free(context);
return action_url_install_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 action_url_install_close_src(void* data, u32 index, bool succeeded, u32 handle) {
return httpcCloseContext((httpcContext*) handle);
}
static Result action_url_install_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_url_install_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_url_install_open_dst(void* data, u32 index, void* initialReadBlock, u64 size, u32* handle) {
url_install_data* installData = (url_install_data*) data;
Result res = 0;
installData->responseCode = 0;
installData->ticket = false;
installData->currTitleId = 0;
installData->n3dsContinue = false;
memset(&installData->ticketInfo, 0, sizeof(installData->ticketInfo));
if(*(u16*) initialReadBlock == 0x0100) {
if(!installData->cdnDecided) {
ui_view* view = prompt_display("Optional", "Install ticket titles from CDN?", COLOR_TEXT, true, data, NULL, action_url_install_cdn_check_onresponse);
if(view != NULL) {
svcWaitSynchronization(view->active, U64_MAX);
}
}
installData->ticket = true;
installData->ticketInfo.titleId = util_get_ticket_title_id((u8*) initialReadBlock);
AM_DeleteTicket(installData->ticketInfo.titleId);
res = AM_InstallTicketBegin(handle);
} else if(*(u16*) initialReadBlock == 0x2020) {
u64 titleId = util_get_cia_title_id((u8*) initialReadBlock);
FS_MediaType dest = ((titleId >> 32) & 0x8010) != 0 ? MEDIATYPE_NAND : MEDIATYPE_SD;
bool n3ds = false;
if(R_SUCCEEDED(APT_CheckNew3DS(&n3ds)) && !n3ds && ((titleId >> 28) & 0xF) == 2) {
ui_view* view = prompt_display("Confirmation", "Title is intended for New 3DS systems.\nContinue?", COLOR_TEXT, true, data, NULL, action_url_install_n3ds_onresponse);
if(view != NULL) {
svcWaitSynchronization(view->active, U64_MAX);
}
if(!installData->n3dsContinue) {
return R_FBI_WRONG_SYSTEM;
}
}
// Deleting FBI before it reinstalls itself causes issues.
if(((titleId >> 8) & 0xFFFFF) != 0xF8001) {
AM_DeleteTitle(dest, titleId);
AM_DeleteTicket(titleId);
if(dest == MEDIATYPE_SD) {
AM_QueryAvailableExternalTitleDatabase(NULL);
}
}
if(R_SUCCEEDED(res = AM_StartCiaInstall(dest, handle))) {
installData->currTitleId = titleId;
}
} else {
res = R_FBI_BAD_DATA;
}
return res;
}
static Result action_url_install_close_dst(void* data, u32 index, bool succeeded, u32 handle) {
url_install_data* installData = (url_install_data*) data;
if(succeeded) {
Result res = 0;
if(installData->ticket) {
res = AM_InstallTicketFinish(handle);
if(R_SUCCEEDED(res) && installData->cdn) {
volatile bool done = false;
action_install_cdn_noprompt(&done, &installData->ticketInfo, false);
while(!done) {
svcSleepThread(100000000);
}
}
} else {
if(R_SUCCEEDED(res = AM_FinishCiaInstall(handle))) {
util_import_seed(installData->currTitleId);
if(installData->currTitleId == 0x0004013800000002 || installData->currTitleId == 0x0004013820000002) {
res = AM_InstallFirm(installData->currTitleId);
}
}
}
return res;
} else {
if(installData->ticket) {
return AM_InstallTicketAbort(handle);
} else {
return AM_CancelCIAInstall(handle);
}
}
}
static Result action_url_install_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 action_url_install_suspend_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) {
return 0;
}
static Result action_url_install_restore_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) {
return 0;
}
static Result action_url_install_suspend(void* data, u32 index) {
return 0;
}
static Result action_url_install_restore(void* data, u32 index) {
return 0;
}
static bool action_url_install_error(void* data, u32 index, Result res) {
url_install_data* installData = (url_install_data*) data;
if(res == R_FBI_CANCELLED) {
prompt_display("Failure", "Install cancelled.", COLOR_TEXT, false, NULL, NULL, NULL);
return false;
} else if(res != R_FBI_WRONG_SYSTEM) {
char* url = installData->urls[index];
ui_view* view = NULL;
if(res == R_FBI_HTTP_RESPONSE_CODE) {
if(strlen(url) > 38) {
view = error_display(NULL, NULL, "Failed to install from URL.\n%.35s...\nHTTP server returned response code %d", url, installData->responseCode);
} else {
view = error_display(NULL, NULL, "Failed to install from URL.\n%.38s\nHTTP server returned response code %d", url, installData->responseCode);
}
} else {
if(strlen(url) > 38) {
view = error_display_res(NULL, NULL, res, "Failed to install from URL.\n%.35s...", url);
} else {
view = error_display_res(NULL, NULL, res, "Failed to install from URL.\n%.38s", url);
}
}
if(view != NULL) {
svcWaitSynchronization(view->active, U64_MAX);
}
}
return index < installData->installInfo.total - 1;
}
static void action_url_install_install_update(ui_view* view, void* data, float* progress, char* text) {
url_install_data* installData = (url_install_data*) data;
if(installData->installInfo.finished) {
ui_pop();
info_destroy(view);
if(R_SUCCEEDED(installData->installInfo.result)) {
prompt_display("Success", "Install finished.", COLOR_TEXT, false, NULL, NULL, NULL);
}
return;
}
if(hidKeysDown() & KEY_B) {
svcSignalEvent(installData->installInfo.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 %s / %.2f %s", installData->installInfo.processed, installData->installInfo.total, util_get_display_size(installData->installInfo.currProcessed), util_get_display_size_units(installData->installInfo.currProcessed), util_get_display_size(installData->installInfo.currTotal), util_get_display_size_units(installData->installInfo.currTotal));
}
static void action_url_install_confirm_onresponse(ui_view* view, void* data, bool response) {
url_install_data* installData = (url_install_data*) data;
if(response) {
Result res = task_data_op(&installData->installInfo);
if(R_SUCCEEDED(res)) {
info_display("Installing From URL(s)", "Press B to cancel.", true, data, action_url_install_install_update, NULL);
} else {
error_display_res(NULL, NULL, res, "Failed to initiate installation.");
}
}
}
void action_url_install(const char* confirmMessage, const char* urls) {
url_install_data* data = (url_install_data*) calloc(1, sizeof(url_install_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate QR install data.");
return;
}
data->installInfo.total = 0;
size_t payloadLen = strlen(urls);
if(payloadLen > 0) {
const char* currStart = urls;
while(data->installInfo.total < URLS_MAX && currStart - urls < payloadLen) {
const char* currEnd = strchr(currStart, '\n');
if(currEnd == NULL) {
currEnd = urls + payloadLen;
}
u32 len = currEnd - currStart;
if((len < 7 || strncmp(currStart, "http://", 7) != 0) && (len < 8 || strncmp(currStart, "https://", 8) != 0)) {
if(len > URL_MAX - 7) {
len = URL_MAX - 7;
}
strncpy(data->urls[data->installInfo.total], "http://", 7);
strncpy(&data->urls[data->installInfo.total][7], currStart, len);
} else {
if(len > URL_MAX) {
len = URL_MAX;
}
strncpy(data->urls[data->installInfo.total], currStart, len);
}
data->installInfo.total++;
currStart = currEnd + 1;
}
}
data->cdn = false;
data->cdnDecided = false;
data->responseCode = 0;
data->ticket = false;
data->currTitleId = 0;
data->n3dsContinue = false;
memset(&data->ticketInfo, 0, sizeof(data->ticketInfo));
data->installInfo.data = data;
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 256 * 1024;
data->installInfo.copyEmpty = false;
data->installInfo.isSrcDirectory = action_url_install_is_src_directory;
data->installInfo.makeDstDirectory = action_url_install_make_dst_directory;
data->installInfo.openSrc = action_url_install_open_src;
data->installInfo.closeSrc = action_url_install_close_src;
data->installInfo.getSrcSize = action_url_install_get_src_size;
data->installInfo.readSrc = action_url_install_read_src;
data->installInfo.openDst = action_url_install_open_dst;
data->installInfo.closeDst = action_url_install_close_dst;
data->installInfo.writeDst = action_url_install_write_dst;
data->installInfo.suspendCopy = action_url_install_suspend_copy;
data->installInfo.restoreCopy = action_url_install_restore_copy;
data->installInfo.suspend = action_url_install_suspend;
data->installInfo.restore = action_url_install_restore;
data->installInfo.error = action_url_install_error;
data->installInfo.finished = true;
prompt_display("Confirmation", confirmMessage, COLOR_TEXT, true, data, NULL, action_url_install_confirm_onresponse);
}

View File

@ -12,7 +12,6 @@
#include "../prompt.h"
#include "../ui.h"
#include "../../core/screen.h"
#include "../../core/util.h"
#include "../../quirc/quirc_internal.h"
#define IMAGE_WIDTH 400
@ -23,307 +22,13 @@
typedef struct {
struct quirc* qrContext;
char urls[URLS_MAX][URL_MAX];
u32 tex;
bool cdn;
bool cdnDecided;
u32 responseCode;
bool ticket;
u64 currTitleId;
volatile bool n3dsContinue;
ticket_info ticketInfo;
bool capturing;
capture_cam_data captureInfo;
data_op_data installInfo;
} qr_install_data;
static void qrinstall_cdn_check_onresponse(ui_view* view, void* data, bool response) {
qr_install_data* qrInstallData = (qr_install_data*) data;
qrInstallData->cdn = response;
qrInstallData->cdnDecided = true;
}
static void qrinstall_n3ds_onresponse(ui_view* view, void* data, bool response) {
((qr_install_data*) data)->n3dsContinue = response;
}
static Result qrinstall_is_src_directory(void* data, u32 index, bool* isDirectory) {
*isDirectory = false;
return 0;
}
static Result qrinstall_make_dst_directory(void* data, u32 index) {
return 0;
}
static Result qrinstall_open_src(void* data, u32 index, u32* handle) {
qr_install_data* qrInstallData = (qr_install_data*) data;
Result res = 0;
httpcContext* context = (httpcContext*) calloc(1, sizeof(httpcContext));
if(context != NULL) {
if(R_SUCCEEDED(res = httpcOpenContext(context, HTTPC_METHOD_GET, qrInstallData->urls[index], 1))) {
if(R_SUCCEEDED(res = httpcSetSSLOpt(context, SSLCOPT_DisableVerify)) && R_SUCCEEDED(res = httpcAddRequestHeaderField(context, "User-Agent", "Mozilla/5.0 (Nintendo 3DS; Mobile; rv:10.0) Gecko/20100101 FBI/%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO)) && R_SUCCEEDED(res = httpcBeginRequest(context)) && R_SUCCEEDED(res = httpcGetResponseStatusCode(context, &qrInstallData->responseCode, 0))) {
if(qrInstallData->responseCode == 200) {
*handle = (u32) context;
} else if(qrInstallData->responseCode == 301 || qrInstallData->responseCode == 302 || qrInstallData->responseCode == 303) {
memset(qrInstallData->urls[index], '\0', URL_MAX);
if(R_SUCCEEDED(res = httpcGetResponseHeader(context, "Location", qrInstallData->urls[index], URL_MAX))) {
httpcCloseContext(context);
free(context);
return qrinstall_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 qrinstall_close_src(void* data, u32 index, bool succeeded, u32 handle) {
return httpcCloseContext((httpcContext*) handle);
}
static Result qrinstall_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 qrinstall_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 qrinstall_open_dst(void* data, u32 index, void* initialReadBlock, u64 size, u32* handle) {
qr_install_data* qrInstallData = (qr_install_data*) data;
Result res = 0;
qrInstallData->responseCode = 0;
qrInstallData->ticket = false;
qrInstallData->currTitleId = 0;
qrInstallData->n3dsContinue = false;
memset(&qrInstallData->ticketInfo, 0, sizeof(qrInstallData->ticketInfo));
if(*(u16*) initialReadBlock == 0x0100) {
if(!qrInstallData->cdnDecided) {
ui_view* view = prompt_display("Optional", "Install ticket titles from CDN?", COLOR_TEXT, true, data, NULL, qrinstall_cdn_check_onresponse);
if(view != NULL) {
svcWaitSynchronization(view->active, U64_MAX);
}
}
qrInstallData->ticket = true;
qrInstallData->ticketInfo.titleId = util_get_ticket_title_id((u8*) initialReadBlock);
AM_DeleteTicket(qrInstallData->ticketInfo.titleId);
res = AM_InstallTicketBegin(handle);
} else if(*(u16*) initialReadBlock == 0x2020) {
u64 titleId = util_get_cia_title_id((u8*) initialReadBlock);
FS_MediaType dest = ((titleId >> 32) & 0x8010) != 0 ? MEDIATYPE_NAND : MEDIATYPE_SD;
bool n3ds = false;
if(R_SUCCEEDED(APT_CheckNew3DS(&n3ds)) && !n3ds && ((titleId >> 28) & 0xF) == 2) {
ui_view* view = prompt_display("Confirmation", "Title is intended for New 3DS systems.\nContinue?", COLOR_TEXT, true, data, NULL, qrinstall_n3ds_onresponse);
if(view != NULL) {
svcWaitSynchronization(view->active, U64_MAX);
}
if(!qrInstallData->n3dsContinue) {
return R_FBI_WRONG_SYSTEM;
}
}
// Deleting FBI before it reinstalls itself causes issues.
if(((titleId >> 8) & 0xFFFFF) != 0xF8001) {
AM_DeleteTitle(dest, titleId);
AM_DeleteTicket(titleId);
if(dest == MEDIATYPE_SD) {
AM_QueryAvailableExternalTitleDatabase(NULL);
}
}
if(R_SUCCEEDED(res = AM_StartCiaInstall(dest, handle))) {
qrInstallData->currTitleId = titleId;
}
} else {
res = R_FBI_BAD_DATA;
}
return res;
}
static Result qrinstall_close_dst(void* data, u32 index, bool succeeded, u32 handle) {
qr_install_data* qrInstallData = (qr_install_data*) data;
if(succeeded) {
Result res = 0;
if(qrInstallData->ticket) {
res = AM_InstallTicketFinish(handle);
if(R_SUCCEEDED(res) && qrInstallData->cdn) {
volatile bool done = false;
action_install_cdn_noprompt(&done, &qrInstallData->ticketInfo, false);
while(!done) {
svcSleepThread(100000000);
}
}
} else {
if(R_SUCCEEDED(res = AM_FinishCiaInstall(handle))) {
util_import_seed(qrInstallData->currTitleId);
if(qrInstallData->currTitleId == 0x0004013800000002 || qrInstallData->currTitleId == 0x0004013820000002) {
res = AM_InstallFirm(qrInstallData->currTitleId);
}
}
}
return res;
} else {
if(qrInstallData->ticket) {
return AM_InstallTicketAbort(handle);
} else {
return AM_CancelCIAInstall(handle);
}
}
}
static Result qrinstall_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 qrinstall_suspend_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) {
return 0;
}
static Result qrinstall_restore_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) {
return 0;
}
static Result qrinstall_suspend(void* data, u32 index) {
return 0;
}
static Result qrinstall_restore(void* data, u32 index) {
return 0;
}
static bool qrinstall_error(void* data, u32 index, Result res) {
qr_install_data* qrInstallData = (qr_install_data*) data;
if(res == R_FBI_CANCELLED) {
prompt_display("Failure", "Install cancelled.", COLOR_TEXT, false, NULL, NULL, NULL);
return false;
} else if(res != R_FBI_WRONG_SYSTEM) {
char* url = qrInstallData->urls[index];
ui_view* view = NULL;
if(res == R_FBI_HTTP_RESPONSE_CODE) {
if(strlen(url) > 38) {
view = error_display(NULL, NULL, "Failed to install from URL.\n%.35s...\nHTTP server returned response code %d", url, qrInstallData->responseCode);
} else {
view = error_display(NULL, NULL, "Failed to install from URL.\n%.38s\nHTTP server returned response code %d", url, qrInstallData->responseCode);
}
} else {
if(strlen(url) > 38) {
view = error_display_res(NULL, NULL, res, "Failed to install from URL.\n%.35s...", url);
} else {
view = error_display_res(NULL, NULL, res, "Failed to install from URL.\n%.38s", url);
}
}
if(view != NULL) {
svcWaitSynchronization(view->active, U64_MAX);
}
}
return index < qrInstallData->installInfo.total - 1;
}
static void qrinstall_install_update(ui_view* view, void* data, float* progress, char* text) {
qr_install_data* qrInstallData = (qr_install_data*) data;
if(qrInstallData->installInfo.finished) {
ui_pop();
info_destroy(view);
if(R_SUCCEEDED(qrInstallData->installInfo.result)) {
prompt_display("Success", "Install finished.", COLOR_TEXT, false, NULL, NULL, NULL);
}
for(u32 i = 0; i < qrInstallData->installInfo.total; i++) {
memset(qrInstallData->urls[i], '\0', URL_MAX);
}
qrInstallData->cdn = false;
qrInstallData->cdnDecided = false;
qrInstallData->responseCode = 0;
qrInstallData->ticket = false;
qrInstallData->currTitleId = 0;
qrInstallData->n3dsContinue = false;
memset(&qrInstallData->ticketInfo, 0, sizeof(qrInstallData->ticketInfo));
return;
}
if(hidKeysDown() & KEY_B) {
svcSignalEvent(qrInstallData->installInfo.cancelEvent);
}
*progress = qrInstallData->installInfo.currTotal != 0 ? (float) ((double) qrInstallData->installInfo.currProcessed / (double) qrInstallData->installInfo.currTotal) : 0;
snprintf(text, PROGRESS_TEXT_MAX, "%lu / %lu\n%.2f %s / %.2f %s", qrInstallData->installInfo.processed, qrInstallData->installInfo.total, util_get_display_size(qrInstallData->installInfo.currProcessed), util_get_display_size_units(qrInstallData->installInfo.currProcessed), util_get_display_size(qrInstallData->installInfo.currTotal), util_get_display_size_units(qrInstallData->installInfo.currTotal));
}
static void qrinstall_confirm_onresponse(ui_view* view, void* data, bool response) {
qr_install_data* qrInstallData = (qr_install_data*) data;
if(response) {
Result res = task_data_op(&qrInstallData->installInfo);
if(R_SUCCEEDED(res)) {
info_display("Installing From URL(s)", "Press B to cancel.", true, data, qrinstall_install_update, NULL);
} else {
error_display_res(NULL, NULL, res, "Failed to initiate installation.");
}
}
}
static void qrinstall_free_data(qr_install_data* data) {
if(!data->installInfo.finished) {
svcSignalEvent(data->installInfo.cancelEvent);
while(!data->installInfo.finished) {
svcSleepThread(1000000);
}
}
if(!data->captureInfo.finished) {
svcSignalEvent(data->captureInfo.cancelEvent);
while(!data->captureInfo.finished) {
@ -359,56 +64,6 @@ static void qrinstall_wait_draw_top(ui_view* view, void* data, float x1, float y
}
}
static void qrinstall_process_urls(qr_install_data* data, char* input) {
data->installInfo.total = 0;
size_t payloadLen = strlen(input);
if(payloadLen > 0) {
char* currStart = input;
while(data->installInfo.total < URLS_MAX && currStart - input < payloadLen) {
char* currEnd = strchr(currStart, '\n');
if(currEnd == NULL) {
currEnd = input + payloadLen;
}
u32 len = currEnd - currStart;
if((len < 7 || strncmp(currStart, "http://", 7) != 0) && (len < 8 || strncmp(currStart, "https://", 8) != 0)) {
if(len > URL_MAX - 7) {
len = URL_MAX - 7;
}
strncpy(data->urls[data->installInfo.total], "http://", 7);
strncpy(&data->urls[data->installInfo.total][7], currStart, len);
} else {
if(len > URL_MAX) {
len = URL_MAX;
}
strncpy(data->urls[data->installInfo.total], currStart, len);
}
data->installInfo.total++;
currStart = currEnd + 1;
}
if(data->installInfo.total > 0) {
if(!data->captureInfo.finished) {
svcSignalEvent(data->captureInfo.cancelEvent);
while(!data->captureInfo.finished) {
svcSleepThread(1000000);
}
}
data->capturing = false;
memset(data->captureInfo.buffer, 0, IMAGE_WIDTH * IMAGE_HEIGHT * sizeof(u16));
prompt_display("Confirmation", "Install from the provided URL(s)?", COLOR_TEXT, true, data, NULL, qrinstall_confirm_onresponse);
}
}
}
static void qrinstall_wait_update(ui_view* view, void* data, float* progress, char* text) {
qr_install_data* qrInstallData = (qr_install_data*) data;
@ -458,7 +113,17 @@ static void qrinstall_wait_update(ui_view* view, void* data, float* progress, ch
char textBuf[1024];
if(swkbdInputText(&swkbd, textBuf, sizeof(textBuf)) == SWKBD_BUTTON_CONFIRM) {
qrinstall_process_urls(qrInstallData, textBuf);
if(!qrInstallData->captureInfo.finished) {
svcSignalEvent(qrInstallData->captureInfo.cancelEvent);
while(!qrInstallData->captureInfo.finished) {
svcSleepThread(1000000);
}
}
qrInstallData->capturing = false;
memset(qrInstallData->captureInfo.buffer, 0, IMAGE_WIDTH * IMAGE_HEIGHT * sizeof(u16));
action_url_install("Install from the provided URL(s)?", textBuf);
return;
}
}
@ -496,7 +161,17 @@ static void qrinstall_wait_update(ui_view* view, void* data, float* progress, ch
quirc_decode_error_t err = quirc_decode(&qrCode, &qrData);
if(err == 0) {
qrinstall_process_urls(qrInstallData, (char*) qrData.payload);
if(!qrInstallData->captureInfo.finished) {
svcSignalEvent(qrInstallData->captureInfo.cancelEvent);
while(!qrInstallData->captureInfo.finished) {
svcSleepThread(1000000);
}
}
qrInstallData->capturing = false;
memset(qrInstallData->captureInfo.buffer, 0, IMAGE_WIDTH * IMAGE_HEIGHT * sizeof(u16));
action_url_install("Install from the scanned QR code?", (const char*) qrData.payload);
return;
}
}
@ -514,46 +189,6 @@ void qrinstall_open() {
data->tex = 0;
data->cdn = false;
data->cdnDecided = false;
data->responseCode = 0;
data->ticket = false;
data->currTitleId = 0;
data->n3dsContinue = false;
memset(&data->ticketInfo, 0, sizeof(data->ticketInfo));
data->installInfo.data = data;
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 256 * 1024;
data->installInfo.copyEmpty = false;
data->installInfo.total = 0;
data->installInfo.isSrcDirectory = qrinstall_is_src_directory;
data->installInfo.makeDstDirectory = qrinstall_make_dst_directory;
data->installInfo.openSrc = qrinstall_open_src;
data->installInfo.closeSrc = qrinstall_close_src;
data->installInfo.getSrcSize = qrinstall_get_src_size;
data->installInfo.readSrc = qrinstall_read_src;
data->installInfo.openDst = qrinstall_open_dst;
data->installInfo.closeDst = qrinstall_close_dst;
data->installInfo.writeDst = qrinstall_write_dst;
data->installInfo.suspendCopy = qrinstall_suspend_copy;
data->installInfo.restoreCopy = qrinstall_restore_copy;
data->installInfo.suspend = qrinstall_suspend;
data->installInfo.restore = qrinstall_restore;
data->installInfo.error = qrinstall_error;
data->installInfo.finished = true;
data->capturing = false;
data->captureInfo.width = IMAGE_WIDTH;

View File

@ -14,4 +14,5 @@ void qrinstall_open();
void systemsavedata_open();
void tickets_open();
void titles_open();
void titledb_open();
void update_open();

View File

@ -124,8 +124,8 @@ Result task_capture_cam(capture_cam_data* data) {
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, 1)) && R_SUCCEEDED(res = svcCreateMutex(&data->mutex, false))) {
if(threadCreate(task_capture_cam_thread, data, 0x10000, 0x19, 1, true) == NULL) {
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY)) && R_SUCCEEDED(res = svcCreateMutex(&data->mutex, false))) {
if(threadCreate(task_capture_cam_thread, data, 0x10000, 0x1A, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}
}

View File

@ -174,7 +174,7 @@ Result task_data_op(data_op_data* data) {
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, 1))) {
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_data_op_thread, data, 0x10000, 0x18, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}

View File

@ -171,8 +171,8 @@ Result task_populate_ext_save_data(populate_ext_save_data_data* data) {
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, 1))) {
if(threadCreate(task_populate_ext_save_data_thread, data, 0x10000, 0x18, 1, true) == NULL) {
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_populate_ext_save_data_thread, data, 0x10000, 0x19, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}
}

View File

@ -282,8 +282,8 @@ Result task_populate_files(populate_files_data* data) {
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, 1))) {
if(threadCreate(task_populate_files_thread, data, 0x10000, 0x18, 1, true) == NULL) {
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_populate_files_thread, data, 0x10000, 0x19, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}
}

View File

@ -136,8 +136,8 @@ Result task_populate_pending_titles(populate_pending_titles_data* data) {
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, 1))) {
if(threadCreate(task_populate_pending_titles_thread, data, 0x10000, 0x18, 1, true) == NULL) {
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_populate_pending_titles_thread, data, 0x10000, 0x19, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}
}

View File

@ -104,8 +104,8 @@ Result task_populate_system_save_data(populate_system_save_data_data* data) {
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, 1))) {
if(threadCreate(task_populate_system_save_data_thread, data, 0x10000, 0x18, 1, true) == NULL) {
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_populate_system_save_data_thread, data, 0x10000, 0x19, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}
}

View File

@ -110,8 +110,8 @@ Result task_populate_tickets(populate_tickets_data* data) {
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, 1))) {
if(threadCreate(task_populate_tickets_thread, data, 0x10000, 0x18, 1, true) == NULL) {
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_populate_tickets_thread, data, 0x10000, 0x19, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}
}

View File

@ -0,0 +1,256 @@
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <3ds.h>
#include "task.h"
#include "../../list.h"
#include "../../error.h"
#include "../../../core/linkedlist.h"
#include "../../../core/screen.h"
#include "../../../json/json.h"
#include "../../../stb_image/stb_image.h"
static Result task_populate_titledb_download(u32* downloadSize, void* buffer, u32 maxSize, const char* url) {
Result res = 0;
if(downloadSize != NULL) {
*downloadSize = 0;
}
httpcContext context;
if(R_SUCCEEDED(res = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 1))) {
char userAgent[128];
snprintf(userAgent, sizeof(userAgent), "Mozilla/5.0 (Nintendo 3DS; Mobile; rv:10.0) Gecko/20100101 FBI/%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
u32 responseCode = 0;
if(R_SUCCEEDED(res = httpcSetSSLOpt(&context, SSLCOPT_DisableVerify))
&& R_SUCCEEDED(res = httpcAddRequestHeaderField(&context, "User-Agent", userAgent))
&& R_SUCCEEDED(res = httpcBeginRequest(&context))
&& R_SUCCEEDED(res = httpcGetResponseStatusCode(&context, &responseCode, 0))) {
if(responseCode == 200) {
u32 size = 0;
u32 bytesRead = 0;
while(size < maxSize && (res = httpcDownloadData(&context, &((u8*) buffer)[size], maxSize - size < 0x1000 ? maxSize - size : 0x1000, &bytesRead)) == HTTPC_RESULTCODE_DOWNLOADPENDING) {
size += bytesRead;
}
size += bytesRead;
if(R_SUCCEEDED(res) && downloadSize != NULL) {
*downloadSize = size;
}
} else {
res = R_FBI_HTTP_RESPONSE_CODE;
}
}
httpcCloseContext(&context);
}
return res;
}
static void task_populate_titledb_thread(void* arg) {
populate_titledb_data* data = (populate_titledb_data*) arg;
Result res = 0;
u32 maxTextSize = 128 * 1024;
char* text = (char*) calloc(sizeof(char), maxTextSize);
if(text != NULL) {
u32 textSize = 0;
if(R_SUCCEEDED(res = task_populate_titledb_download(&textSize, text, maxTextSize, "https://api.titledb.com/v0/"))) {
json_value* json = json_parse(text, textSize);
if(json != NULL) {
if(json->type == json_array) {
for(u32 i = 0; i < json->u.array.length && R_SUCCEEDED(res); i++) {
svcWaitSynchronization(task_get_pause_event(), U64_MAX);
if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) {
break;
}
json_value* val = json->u.array.values[i];
if(val->type == json_object) {
list_item* item = (list_item*) calloc(1, sizeof(list_item));
if(item != NULL) {
titledb_info* titledbInfo = (titledb_info*) calloc(1, sizeof(titledb_info));
if(titledbInfo != NULL) {
for(u32 j = 0; j < val->u.object.length; j++) {
char* name = val->u.object.values[j].name;
u32 nameLen = val->u.object.values[j].name_length;
json_value* subVal = val->u.object.values[j].value;
if(subVal->type == json_string) {
if(strncmp(name, "titleid", nameLen) == 0) {
titledbInfo->titleId = strtoull(subVal->u.string.ptr, NULL, 16);
} else if(strncmp(name, "name", nameLen) == 0) {
strncpy(titledbInfo->meta.shortDescription, subVal->u.string.ptr, sizeof(titledbInfo->meta.shortDescription));
} else if(strncmp(name, "description", nameLen) == 0) {
strncpy(titledbInfo->meta.longDescription, subVal->u.string.ptr, sizeof(titledbInfo->meta.longDescription));
} else if(strncmp(name, "author", nameLen) == 0) {
strncpy(titledbInfo->meta.publisher, subVal->u.string.ptr, sizeof(titledbInfo->meta.publisher));
}
} else if(subVal->type == json_integer) {
if(strncmp(name, "size", nameLen) == 0) {
titledbInfo->size = (u64) subVal->u.integer;
}
}
}
if(strlen(titledbInfo->meta.shortDescription) > 0) {
strncpy(item->name, titledbInfo->meta.shortDescription, LIST_ITEM_NAME_MAX);
} else {
snprintf(item->name, LIST_ITEM_NAME_MAX, "%016llX", titledbInfo->titleId);
}
item->color = COLOR_TEXT;
item->data = titledbInfo;
linked_list_add(data->items, item);
} else {
free(item);
res = R_FBI_OUT_OF_MEMORY;
}
} else {
res = R_FBI_OUT_OF_MEMORY;
}
}
}
} else {
res = R_FBI_BAD_DATA;
}
} else {
res = R_FBI_PARSE_FAILED;
}
}
free(text);
} else {
res = R_FBI_OUT_OF_MEMORY;
}
if(R_SUCCEEDED(res)) {
linked_list_iter iter;
linked_list_iterate(data->items, &iter);
while(linked_list_iter_has_next(&iter)) {
svcWaitSynchronization(task_get_pause_event(), U64_MAX);
if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) {
break;
}
list_item* item = (list_item*) linked_list_iter_next(&iter);
titledb_info* titledbInfo = (titledb_info*) item->data;
u32 maxPngSize = 128 * 1024;
u8* png = (u8*) calloc(1, maxPngSize);
if(png != NULL) {
char pngUrl[128];
snprintf(pngUrl, sizeof(pngUrl), "https://api.titledb.com/images/%016llX.png", titledbInfo->titleId);
u32 pngSize = 0;
if(R_SUCCEEDED(task_populate_titledb_download(&pngSize, png, maxPngSize, pngUrl))) {
int width;
int height;
int depth;
u8* image = stbi_load_from_memory(png, (int) pngSize, &width, &height, &depth, STBI_rgb_alpha);
if(image != NULL && depth == STBI_rgb_alpha) {
for(u32 x = 0; x < width; x++) {
for(u32 y = 0; y < height; y++) {
u32 pos = (y * width + x) * 4;
u8 c1 = image[pos + 0];
u8 c2 = image[pos + 1];
u8 c3 = image[pos + 2];
u8 c4 = image[pos + 3];
image[pos + 0] = c4;
image[pos + 1] = c3;
image[pos + 2] = c2;
image[pos + 3] = c1;
}
}
titledbInfo->meta.texture = screen_load_texture_auto(image, (u32) (width * height * 4), (u32) width, (u32) height, GPU_RGBA8, false);
free(image);
}
}
free(png);
}
}
}
svcCloseHandle(data->cancelEvent);
data->result = res;
data->finished = true;
}
void task_free_titledb(list_item* item) {
if(item == NULL) {
return;
}
if(item->data != NULL) {
titledb_info* titledbInfo = (titledb_info*) item->data;
if(titledbInfo->meta.texture != 0) {
screen_unload_texture(titledbInfo->meta.texture);
titledbInfo->meta.texture = 0;
}
free(item->data);
}
free(item);
}
void task_clear_titledb(linked_list* items) {
if(items == NULL) {
return;
}
linked_list_iter iter;
linked_list_iterate(items, &iter);
while(linked_list_iter_has_next(&iter)) {
list_item* item = (list_item*) linked_list_iter_next(&iter);
linked_list_iter_remove(&iter);
task_free_titledb(item);
}
}
Result task_populate_titledb(populate_titledb_data* data) {
if(data == NULL || data->items == NULL) {
return R_FBI_INVALID_ARGUMENT;
}
task_clear_titledb(data->items);
data->finished = false;
data->result = 0;
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_populate_titledb_thread, data, 0x10000, 0x19, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}
}
if(R_FAILED(res)) {
data->finished = true;
if(data->cancelEvent != 0) {
svcCloseHandle(data->cancelEvent);
data->cancelEvent = 0;
}
}
return res;
}

View File

@ -360,8 +360,8 @@ Result task_populate_titles(populate_titles_data* data) {
data->cancelEvent = 0;
Result res = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, 1))) {
if(threadCreate(task_populate_titles_thread, data, 0x10000, 0x18, 1, true) == NULL) {
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_populate_titles_thread, data, 0x10000, 0x19, 1, true) == NULL) {
res = R_FBI_THREAD_CREATE_FAILED;
}
}

View File

@ -32,12 +32,12 @@ void task_init() {
Result res = 0;
if(R_FAILED(res = svcCreateEvent(&task_pause_event, 1))) {
if(R_FAILED(res = svcCreateEvent(&task_pause_event, RESET_STICKY))) {
util_panic("Failed to create task awake event: 0x%08lX", res);
return;
}
if(R_FAILED(res = svcCreateEvent(&task_suspend_event, 1))) {
if(R_FAILED(res = svcCreateEvent(&task_suspend_event, RESET_STICKY))) {
svcCloseHandle(task_pause_event);
util_panic("Failed to create task awake event: 0x%08lX", res);

View File

@ -68,6 +68,12 @@ typedef struct file_info_s {
ticket_info ticketInfo;
} file_info;
typedef struct titledb_info_s {
u64 titleId;
u64 size;
meta_info meta;
} titledb_info;
typedef struct {
u16* buffer;
s16 width;
@ -196,6 +202,14 @@ typedef struct {
Handle cancelEvent;
} populate_titles_data;
typedef struct {
linked_list* items;
volatile bool finished;
Result result;
Handle cancelEvent;
} populate_titledb_data;
void task_init();
void task_exit();
bool task_is_quit_all();
@ -229,4 +243,8 @@ Result task_populate_tickets(populate_tickets_data* data);
void task_free_title(list_item* item);
void task_clear_titles(linked_list* items);
Result task_populate_titles(populate_titles_data* data);
Result task_populate_titles(populate_titles_data* data);
void task_free_titledb(list_item* item);
void task_clear_titledb(linked_list* items);
Result task_populate_titledb(populate_titledb_data* data);

142
source/ui/section/titledb.c Normal file
View File

@ -0,0 +1,142 @@
#include <malloc.h>
#include <stdio.h>
#include <3ds.h>
#include "section.h"
#include "action/action.h"
#include "task/task.h"
#include "../error.h"
#include "../list.h"
#include "../ui.h"
#include "../../core/linkedlist.h"
#include "../../core/screen.h"
static list_item install = {"Install", COLOR_TEXT, action_install_titledb};
typedef struct {
populate_titledb_data populateData;
bool populated;
} titledb_data;
typedef struct {
linked_list* items;
list_item* selected;
} titledb_action_data;
static void titledb_action_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
ui_draw_titledb_info(view, ((titledb_action_data*) data)->selected->data, x1, y1, x2, y2);
}
static void titledb_action_update(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched) {
titledb_action_data* actionData = (titledb_action_data*) data;
if(hidKeysDown() & KEY_B) {
ui_pop();
list_destroy(view);
free(data);
return;
}
if(selected != NULL && selected->data != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) {
void(*action)(linked_list*, list_item*) = (void(*)(linked_list*, list_item*)) selected->data;
ui_pop();
list_destroy(view);
action(actionData->items, actionData->selected);
free(data);
return;
}
if(linked_list_size(items) == 0) {
linked_list_add(items, &install);
}
}
static void titledb_action_open(linked_list* items, list_item* selected) {
titledb_action_data* data = (titledb_action_data*) calloc(1, sizeof(titledb_action_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate TitleDB action data.");
return;
}
data->items = items;
data->selected = selected;
list_display("TitleDB Action", "A: Select, B: Return", data, titledb_action_update, titledb_action_draw_top);
}
static void titledb_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
if(selected != NULL && selected->data != NULL) {
ui_draw_titledb_info(view, selected->data, x1, y1, x2, y2);
}
}
static void titledb_update(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched) {
titledb_data* listData = (titledb_data*) data;
if(hidKeysDown() & KEY_B) {
if(!listData->populateData.finished) {
svcSignalEvent(listData->populateData.cancelEvent);
while(!listData->populateData.finished) {
svcSleepThread(1000000);
}
}
ui_pop();
task_clear_titledb(items);
list_destroy(view);
free(listData);
return;
}
if(!listData->populated || (hidKeysDown() & KEY_X)) {
if(!listData->populateData.finished) {
svcSignalEvent(listData->populateData.cancelEvent);
while(!listData->populateData.finished) {
svcSleepThread(1000000);
}
}
listData->populateData.items = items;
Result res = task_populate_titledb(&listData->populateData);
if(R_FAILED(res)) {
error_display_res(NULL, NULL, res, "Failed to initiate TitleDB list population.");
}
listData->populated = true;
}
if(listData->populateData.finished && R_FAILED(listData->populateData.result)) {
error_display_res(NULL, NULL, listData->populateData.result, "Failed to populate TitleDB list.");
listData->populateData.result = 0;
}
if(selected != NULL && selected->data != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) {
titledb_action_open(items, selected);
return;
}
}
void titledb_open() {
titledb_data* data = (titledb_data*) calloc(1, sizeof(titledb_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate TitleDB data.");
return;
}
data->populateData.finished = true;
list_display("TitleDB.com", "A: Select, B: Return, X: Refresh", data, titledb_update, titledb_draw_top);
}

View File

@ -184,18 +184,21 @@ static void update_check_update(ui_view* view, void* data, float* progress, char
httpcContext context;
if(R_SUCCEEDED(res = httpcOpenContext(&context, HTTPC_METHOD_GET, "https://api.github.com/repos/Steveice10/FBI/releases/latest", 1))) {
char userAgent[128];
snprintf(userAgent, sizeof(userAgent), "Mozilla/5.0 (Nintendo 3DS; Mobile; rv:10.0) Gecko/20100101 FBI/%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
if(R_SUCCEEDED(res = httpcSetSSLOpt(&context, SSLCOPT_DisableVerify))
&& R_SUCCEEDED(res = httpcAddRequestHeaderField(&context, "User-Agent", "FBI"))
&& R_SUCCEEDED(res = httpcAddRequestHeaderField(&context, "User-Agent", userAgent))
&& 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) {
char* jsonText = (char*) calloc(sizeof(char), size);
if(jsonText != NULL) {
u32 bytesRead = 0;
if(R_SUCCEEDED(res = httpcDownloadData(&context, (u8*) text, size, &bytesRead))) {
json_value* json = json_parse(text, size);
if(R_SUCCEEDED(res = httpcDownloadData(&context, (u8*) jsonText, size, &bytesRead))) {
json_value* json = json_parse(jsonText, size);
if(json != NULL) {
if(json->type == json_object) {
json_value* name = NULL;
@ -258,6 +261,8 @@ static void update_check_update(ui_view* view, void* data, float* progress, char
res = R_FBI_PARSE_FAILED;
}
}
free(jsonText);
} else {
res = R_FBI_OUT_OF_MEMORY;
}

View File

@ -38,7 +38,7 @@ ui_view* ui_create() {
}
Result res = 0;
if(R_FAILED(res = svcCreateEvent(&view->active, 1))) {
if(R_FAILED(res = svcCreateEvent(&view->active, RESET_STICKY))) {
util_panic("Failed to create view active event: 0x%08lX", res);
free(view);
@ -323,53 +323,61 @@ bool ui_update() {
return ui != NULL;
}
void ui_draw_meta_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) {
meta_info* info = (meta_info*) data;
u32 metaInfoBoxShadowWidth;
u32 metaInfoBoxShadowHeight;
screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW);
float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2;
float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2;
screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight);
u32 metaInfoBoxWidth;
u32 metaInfoBoxHeight;
screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX);
float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2;
float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2;
screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight);
if(info->texture != 0) {
u32 iconWidth;
u32 iconHeight;
screen_get_texture_size(&iconWidth, &iconHeight, info->texture);
float iconX = metaInfoBoxX + (64 - iconWidth) / 2;
float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2;
screen_draw_texture(info->texture, iconX, iconY, iconWidth, iconHeight);
}
float metaTextX = metaInfoBoxX + 64;
float shortDescriptionHeight;
screen_get_string_size_wrap(NULL, &shortDescriptionHeight, info->shortDescription, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX);
float longDescriptionHeight;
screen_get_string_size_wrap(NULL, &longDescriptionHeight, info->longDescription, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX);
float publisherHeight;
screen_get_string_size_wrap(NULL, &publisherHeight, info->publisher, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX);
float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2;
screen_draw_string_wrap(info->shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8);
float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2;
screen_draw_string_wrap(info->longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8);
float publisherY = longDescriptionY + longDescriptionHeight + 2;
screen_draw_string_wrap(info->publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8);
}
void ui_draw_ext_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) {
ext_save_data_info* info = (ext_save_data_info*) data;
if(info->hasMeta) {
u32 metaInfoBoxShadowWidth;
u32 metaInfoBoxShadowHeight;
screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW);
float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2;
float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2;
screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight);
u32 metaInfoBoxWidth;
u32 metaInfoBoxHeight;
screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX);
float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2;
float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2;
screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight);
u32 iconWidth;
u32 iconHeight;
screen_get_texture_size(&iconWidth, &iconHeight, info->meta.texture);
float iconX = metaInfoBoxX + (64 - iconWidth) / 2;
float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2;
screen_draw_texture(info->meta.texture, iconX, iconY, iconWidth, iconHeight);
float shortDescriptionHeight;
screen_get_string_size(NULL, &shortDescriptionHeight, info->meta.shortDescription, 0.5f, 0.5f);
float longDescriptionHeight;
screen_get_string_size(NULL, &longDescriptionHeight, info->meta.longDescription, 0.5f, 0.5f);
float publisherHeight;
screen_get_string_size(NULL, &publisherHeight, info->meta.publisher, 0.5f, 0.5f);
float metaTextX = metaInfoBoxX + 64;
float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2;
screen_draw_string(info->meta.shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false);
float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2;
screen_draw_string(info->meta.longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false);
float publisherY = longDescriptionY + longDescriptionHeight + 2;
screen_draw_string(info->meta.publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false);
ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2);
}
char infoText[512];
@ -447,49 +455,7 @@ void ui_draw_file_info(ui_view* view, void* data, float x1, float y1, float x2,
if(info->isCia) {
if(info->ciaInfo.hasMeta) {
u32 metaInfoBoxShadowWidth;
u32 metaInfoBoxShadowHeight;
screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW);
float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2;
float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2;
screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight);
u32 metaInfoBoxWidth;
u32 metaInfoBoxHeight;
screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX);
float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2;
float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2;
screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight);
u32 iconWidth;
u32 iconHeight;
screen_get_texture_size(&iconWidth, &iconHeight, info->ciaInfo.meta.texture);
float iconX = metaInfoBoxX + (64 - iconWidth) / 2;
float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2;
screen_draw_texture(info->ciaInfo.meta.texture, iconX, iconY, iconWidth, iconHeight);
float shortDescriptionHeight;
screen_get_string_size(NULL, &shortDescriptionHeight, info->ciaInfo.meta.shortDescription, 0.5f, 0.5f);
float longDescriptionHeight;
screen_get_string_size(NULL, &longDescriptionHeight, info->ciaInfo.meta.longDescription, 0.5f, 0.5f);
float publisherHeight;
screen_get_string_size(NULL, &publisherHeight, info->ciaInfo.meta.publisher, 0.5f, 0.5f);
float metaTextX = metaInfoBoxX + 64;
float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2;
screen_draw_string(info->ciaInfo.meta.shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false);
float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2;
screen_draw_string(info->ciaInfo.meta.longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false);
float publisherY = longDescriptionY + longDescriptionHeight + 2;
screen_draw_string(info->ciaInfo.meta.publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false);
ui_draw_meta_info(view, &info->ciaInfo.meta, x1, y1, x2, y2);
}
infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos,
@ -567,49 +533,7 @@ void ui_draw_title_info(ui_view* view, void* data, float x1, float y1, float x2,
title_info* info = (title_info*) data;
if(info->hasMeta) {
u32 metaInfoBoxShadowWidth;
u32 metaInfoBoxShadowHeight;
screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW);
float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2;
float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2;
screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight);
u32 metaInfoBoxWidth;
u32 metaInfoBoxHeight;
screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX);
float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2;
float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2;
screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight);
u32 iconWidth;
u32 iconHeight;
screen_get_texture_size(&iconWidth, &iconHeight, info->meta.texture);
float iconX = metaInfoBoxX + (64 - iconWidth) / 2;
float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2;
screen_draw_texture(info->meta.texture, iconX, iconY, iconWidth, iconHeight);
float shortDescriptionHeight;
screen_get_string_size(NULL, &shortDescriptionHeight, info->meta.shortDescription, 0.5f, 0.5f);
float longDescriptionHeight;
screen_get_string_size(NULL, &longDescriptionHeight, info->meta.longDescription, 0.5f, 0.5f);
float publisherHeight;
screen_get_string_size(NULL, &publisherHeight, info->meta.publisher, 0.5f, 0.5f);
float metaTextX = metaInfoBoxX + 64;
float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2;
screen_draw_string(info->meta.shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false);
float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2;
screen_draw_string(info->meta.longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false);
float publisherY = longDescriptionY + longDescriptionHeight + 2;
screen_draw_string(info->meta.publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false);
ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2);
}
char infoText[512];
@ -633,3 +557,24 @@ void ui_draw_title_info(ui_view* view, void* data, float x1, float y1, float x2,
float infoY = y1 + (y2 - y1) / 2 - 8;
screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true);
}
void ui_draw_titledb_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) {
titledb_info* info = (titledb_info*) data;
ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2);
char infoText[512];
snprintf(infoText, sizeof(infoText),
"Title ID: %016llX\n"
"Size: %.2f %s",
info->titleId,
util_get_display_size(info->size), util_get_display_size_units(info->size));
float infoWidth;
screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f);
float infoX = x1 + (x2 - x1 - infoWidth) / 2;
float infoY = y1 + (y2 - y1) / 2 - 8;
screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true);
}

View File

@ -24,9 +24,11 @@ bool ui_push(ui_view* view);
void ui_pop();
bool ui_update();
void ui_draw_meta_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_ext_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_file_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_pending_title_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_system_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_ticket_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_title_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_title_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_titledb_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);