Add support for installing 3DSX files remotely.

This commit is contained in:
Steveice10 2017-09-30 22:51:30 -07:00
parent 56b5848247
commit a49af523ba
9 changed files with 171 additions and 247 deletions

View File

@ -34,12 +34,12 @@ elif len(sys.argv) < 3 or len(sys.argv) > 6:
print('Usage: ' + sys.argv[0] + ' <target ip> <file / directory> [host ip] [host port]')
sys.exit(1)
accepted_extension = ('.cia', '.tik', '.cetk')
accepted_extension = ('.cia', '.tik', '.cetk', '.3dsx')
hostPort = 8080 # Default value
if interactive:
target_ip = input("The IP of your 3DS: ")
target_path = input("The file you want to send (.cia, .tik, or .cetk): ")
target_path = input("The file you want to send (.cia, .tik, .cetk, or .3dsx): ")
hostIp = input("Host IP (or press Enter to have the script detect host IP):")
if hostIp == '':

View File

@ -200,6 +200,18 @@ Result util_ensure_dir(FS_Archive archive, const char* path) {
return res;
}
void util_get_file_name(char* out, const char* file, u32 size) {
const char* end = file + strlen(file);
const char* curr = file - 1;
while((curr = strchr(curr + 1, '.')) != NULL) {
end = curr;
}
u32 terminatorPos = end - file < size - 1 ? end - file : size - 1;
strncpy(out, file, terminatorPos);
out[terminatorPos] = '\0';
}
void util_get_path_file(char* out, const char* path, u32 size) {
const char* start = NULL;
const char* end = NULL;
@ -761,6 +773,36 @@ Result util_http_get_size(httpcContext* context, u32* size) {
return httpcGetDownloadSizeState(context, NULL, size);
}
Result util_http_get_file_name(httpcContext* context, char* out, u32 size) {
if(context == NULL || out == NULL) {
return R_FBI_INVALID_ARGUMENT;
}
Result res = 0;
char* header = (char*) calloc(1, size + 64);
if(header != NULL) {
if(R_SUCCEEDED(res = httpcGetResponseHeader(context, "Content-Disposition", header, size + 64))) {
char* start = strstr(header, "filename=");
if(start != NULL) {
char format[32];
snprintf(format, sizeof(format), "filename=\"%%%lu[^\"]\"", size);
if(sscanf(start, format, out) != 1) {
res = R_FBI_BAD_DATA;
}
} else {
res = R_FBI_BAD_DATA;
}
}
free(header);
} else {
res = R_FBI_OUT_OF_MEMORY;
}
return res;
}
Result util_http_read(httpcContext* context, u32* bytesRead, void* buffer, u32 size) {
if(context == NULL || buffer == NULL) {
return R_FBI_INVALID_ARGUMENT;

View File

@ -48,6 +48,8 @@ FS_Path util_make_binary_path(const void* data, u32 size);
bool util_is_dir(FS_Archive archive, const char* path);
Result util_ensure_dir(FS_Archive archive, const char* path);
void util_get_file_name(char* out, const char* file, u32 size);
void util_get_path_file(char* out, const char* path, u32 size);
void util_get_parent_path(char* out, const char* path, u32 size);
@ -90,5 +92,6 @@ u16* util_select_bnr_title(BNR* bnr);
Result util_http_open(httpcContext* context, u32* responseCode, const char* url, bool userAgent);
Result util_http_open_ranged(httpcContext* context, u32* responseCode, const char* url, bool userAgent, u32 rangeStart, u32 rangeEnd);
Result util_http_get_size(httpcContext* context, u32* size);
Result util_http_get_file_name(httpcContext* context, char* out, u32 size);
Result util_http_read(httpcContext* context, u32* bytesRead, void* buffer, u32 size);
Result util_http_close(httpcContext* context);

View File

@ -53,8 +53,8 @@ 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_install_url(const char* confirmMessage, const char* urls, void* userData, void (*finished)(void* data),
void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2, u32 index));
void action_install_url(const char* confirmMessage, const char* urls, const char* path3dsx, void* userData, void (*finished)(void* data),
void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2, u32 index));
void action_install_titledb(linked_list* items, list_item* selected);
void action_update_titledb(linked_list* items, list_item* selected);

View File

@ -21,5 +21,5 @@ void action_install_titledb(linked_list* items, list_item* selected) {
char url[64];
snprintf(url, INSTALL_URL_MAX, "https://3ds.titledb.com/v1/cia/%lu/download", ((titledb_info*) selected->data)->id);
action_install_url("Install the selected title from TitleDB?", url, selected, action_update_titledb_finished, action_install_titledb_draw_top);
action_install_url("Install the selected title from TitleDB?", url, NULL, selected, action_update_titledb_finished, action_install_titledb_draw_top);
}

View File

@ -13,9 +13,17 @@
#include "../../../core/screen.h"
#include "../../../core/util.h"
typedef enum content_type_e {
CONTENT_CIA,
CONTENT_TICKET,
CONTENT_3DSX
} content_type;
typedef struct {
char urls[INSTALL_URLS_MAX][INSTALL_URL_MAX];
char path3dsx[FILE_PATH_MAX];
void* userData;
void (*finished)(void* data);
void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2, u32 index);
@ -25,10 +33,12 @@ typedef struct {
bool cdnDecided;
u32 responseCode;
bool ticket;
content_type contentType;
u64 currTitleId;
volatile bool n3dsContinue;
ticket_info ticketInfo;
httpcContext* currContext;
char curr3dsxPath[FILE_PATH_MAX];
data_op_data installInfo;
} install_url_data;
@ -105,6 +115,8 @@ static Result action_install_url_open_src(void* data, u32 index, u32* handle) {
if(context != NULL) {
if(R_SUCCEEDED(res = util_http_open(context, &installData->responseCode, installData->urls[index], true))) {
*handle = (u32) context;
installData->currContext = context;
} else {
free(context);
}
@ -116,6 +128,8 @@ static Result action_install_url_open_src(void* data, u32 index, u32* handle) {
}
static Result action_install_url_close_src(void* data, u32 index, bool succeeded, u32 handle) {
((install_url_data*) data)->currContext = NULL;
return util_http_close((httpcContext*) handle);
}
@ -137,28 +151,15 @@ static Result action_install_url_open_dst(void* data, u32 index, void* initialRe
Result res = 0;
installData->responseCode = 0;
installData->ticket = false;
installData->contentType = CONTENT_CIA;
installData->currTitleId = 0;
installData->n3dsContinue = false;
memset(&installData->ticketInfo, 0, sizeof(installData->ticketInfo));
memset(&installData->curr3dsxPath, 0, sizeof(installData->curr3dsxPath));
if(*(u16*) initialReadBlock == 0x0100) {
if(!installData->cdnDecided) {
static const char* options[3] = {"Default\nVersion", "Select\nVersion", "No"};
static u32 optionButtons[3] = {KEY_A, KEY_X, KEY_B};
ui_view* view = prompt_display_multi_choice("Optional", "Install ticket titles from CDN?", COLOR_TEXT, options, optionButtons, 3, data, action_install_url_draw_top, action_install_url_cdn_check_onresponse);
if(view != NULL) {
svcWaitSynchronization(view->active, U64_MAX);
}
}
if(*(u16*) initialReadBlock == 0x2020) {
installData->contentType = CONTENT_CIA;
installData->ticket = true;
installData->ticketInfo.titleId = util_get_ticket_title_id((u8*) initialReadBlock);
installData->ticketInfo.inUse = false;
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 = util_get_title_destination(titleId);
@ -191,6 +192,58 @@ static Result action_install_url_open_dst(void* data, u32 index, void* initialRe
if(R_SUCCEEDED(res = AM_StartCiaInstall(dest, handle))) {
installData->currTitleId = titleId;
}
} else if(*(u16*) initialReadBlock == 0x0100) {
installData->contentType = CONTENT_TICKET;
if(!installData->cdnDecided) {
static const char* options[3] = {"Default\nVersion", "Select\nVersion", "No"};
static u32 optionButtons[3] = {KEY_A, KEY_X, KEY_B};
ui_view* view = prompt_display_multi_choice("Optional", "Install ticket titles from CDN?", COLOR_TEXT, options, optionButtons, 3, data, action_install_url_draw_top, action_install_url_cdn_check_onresponse);
if(view != NULL) {
svcWaitSynchronization(view->active, U64_MAX);
}
}
installData->ticketInfo.titleId = util_get_ticket_title_id((u8*) initialReadBlock);
installData->ticketInfo.inUse = false;
AM_DeleteTicket(installData->ticketInfo.titleId);
res = AM_InstallTicketBegin(handle);
} else if(*(u32*) initialReadBlock == 0x58534433) {
installData->contentType = CONTENT_3DSX;
FS_Archive sdmcArchive = 0;
if(R_SUCCEEDED(res = FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")))) {
char dir[FILE_PATH_MAX];
if(strlen(installData->path3dsx) > 0) {
util_get_parent_path(dir, installData->path3dsx, FILE_PATH_MAX);
strncpy(installData->curr3dsxPath, installData->path3dsx, FILE_PATH_MAX);
} else {
char filename[FILE_NAME_MAX];
if(R_FAILED(util_http_get_file_name(installData->currContext, filename, FILE_NAME_MAX))) {
util_get_path_file(filename, installData->urls[index], FILE_NAME_MAX);
}
char name[FILE_NAME_MAX];
util_get_file_name(name, filename, FILE_NAME_MAX);
snprintf(dir, FILE_PATH_MAX, "/3ds/%s/", name);
snprintf(installData->curr3dsxPath, FILE_PATH_MAX, "/3ds/%s/%s.3dsx", name, name);
}
if(R_SUCCEEDED(res = util_ensure_dir(sdmcArchive, dir))) {
FS_Path* path = util_make_path_utf8(installData->curr3dsxPath);
if(path != NULL) {
res = FSUSER_OpenFileDirectly(handle, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), *path, FS_OPEN_WRITE | FS_OPEN_CREATE, 0);
util_free_path_utf8(path);
} else {
res = R_FBI_OUT_OF_MEMORY;
}
}
FSUSER_CloseArchive(sdmcArchive);
}
} else {
res = R_FBI_BAD_DATA;
}
@ -201,10 +254,18 @@ static Result action_install_url_open_dst(void* data, u32 index, void* initialRe
static Result action_install_url_close_dst(void* data, u32 index, bool succeeded, u32 handle) {
install_url_data* installData = (install_url_data*) data;
if(succeeded) {
Result res = 0;
Result res = 0;
if(installData->ticket) {
if(succeeded) {
if(installData->contentType == CONTENT_CIA) {
if(R_SUCCEEDED(res = AM_FinishCiaInstall(handle))) {
util_import_seed(NULL, installData->currTitleId);
if(installData->currTitleId == 0x0004013800000002 || installData->currTitleId == 0x0004013820000002) {
res = AM_InstallFirm(installData->currTitleId);
}
}
} else if(installData->contentType == CONTENT_TICKET) {
res = AM_InstallTicketFinish(handle);
if(R_SUCCEEDED(res) && installData->cdn) {
@ -215,24 +276,32 @@ static Result action_install_url_close_dst(void* data, u32 index, bool succeeded
svcSleepThread(100000000);
}
}
} else {
if(R_SUCCEEDED(res = AM_FinishCiaInstall(handle))) {
util_import_seed(NULL, installData->currTitleId);
} else if(installData->contentType == CONTENT_3DSX) {
res = FSFILE_Close(handle);
}
} else {
if(installData->contentType == CONTENT_CIA) {
res = AM_CancelCIAInstall(handle);
} else if(installData->contentType == CONTENT_TICKET) {
res = AM_InstallTicketAbort(handle);
} else if(installData->contentType == CONTENT_3DSX) {
res = FSFILE_Close(handle);
if(installData->currTitleId == 0x0004013800000002 || installData->currTitleId == 0x0004013820000002) {
res = AM_InstallFirm(installData->currTitleId);
FS_Archive sdmcArchive = 0;
if(R_SUCCEEDED(FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")))) {
FS_Path* path = util_make_path_utf8(installData->curr3dsxPath);
if(path != NULL) {
FSUSER_DeleteFile(sdmcArchive, *path);
util_free_path_utf8(path);
}
FSUSER_CloseArchive(sdmcArchive);
}
}
return res;
} else {
if(installData->ticket) {
return AM_InstallTicketAbort(handle);
} else {
return AM_CancelCIAInstall(handle);
}
}
return res;
}
static Result action_install_url_write_dst(void* data, u32 handle, u32* bytesWritten, void* buffer, u64 offset, u32 size) {
@ -329,8 +398,8 @@ static void action_install_url_confirm_onresponse(ui_view* view, void* data, u32
}
}
void action_install_url(const char* confirmMessage, const char* urls, void* userData, void (*finished)(void* data),
void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2, u32 index)) {
void action_install_url(const char* confirmMessage, const char* urls, const char* path3dsx, void* userData, void (*finished)(void* data),
void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2, u32 index)) {
install_url_data* data = (install_url_data*) calloc(1, sizeof(install_url_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate URL install data.");
@ -371,6 +440,10 @@ void action_install_url(const char* confirmMessage, const char* urls, void* user
}
}
if(path3dsx != NULL) {
strncpy(data->path3dsx, path3dsx, FILE_PATH_MAX);
}
data->userData = userData;
data->finished = finished;
data->drawTop = drawTop;
@ -380,10 +453,11 @@ void action_install_url(const char* confirmMessage, const char* urls, void* user
data->cdnDecided = false;
data->responseCode = 0;
data->ticket = false;
data->contentType = CONTENT_CIA;
data->currTitleId = 0;
data->n3dsContinue = false;
memset(&data->ticketInfo, 0, sizeof(data->ticketInfo));
memset(&data->curr3dsxPath, 0, sizeof(data->curr3dsxPath));
data->installInfo.data = data;

View File

@ -53,7 +53,7 @@ void action_update_titledb(linked_list* items, list_item* selected) {
}
}
action_install_url("Update installed titles from TitleDB?", urls, updates, action_update_titledb_finished, action_update_titledb_draw_top);
action_install_url("Update installed titles from TitleDB?", urls, NULL, updates, action_update_titledb_finished, action_update_titledb_draw_top);
free(urls);
} else {

View File

@ -181,7 +181,7 @@ static void remoteinstall_network_update(ui_view* view, void* data, float* progr
}
remoteinstall_set_last_urls(urls);
action_install_url("Install from the received URL(s)?", urls, data, remoteinstall_network_close_client, NULL);
action_install_url("Install from the received URL(s)?", urls, NULL, data, remoteinstall_network_close_client, NULL);
free(urls);
} else if(errno != EAGAIN) {
@ -374,7 +374,7 @@ static void remoteinstall_qr_update(ui_view* view, void* data, float* progress,
remoteinstall_set_last_urls((const char*) qrData.payload);
action_install_url("Install from the scanned QR code?", (const char*) qrData.payload, NULL, NULL, NULL);
action_install_url("Install from the scanned QR code?", (const char*) qrData.payload, NULL, NULL, NULL, NULL);
return;
}
}
@ -431,7 +431,7 @@ static void remoteinstall_manually_enter_urls_onresponse(ui_view* view, void* da
if(button == SWKBD_BUTTON_CONFIRM) {
remoteinstall_set_last_urls(response);
action_install_url("Install from the entered URL(s)?", response, NULL, NULL, NULL);
action_install_url("Install from the entered URL(s)?", response, NULL, NULL, NULL, NULL);
}
}
@ -443,7 +443,7 @@ static void remoteinstall_repeat_last_request() {
char* textBuf = (char*) calloc(1, INSTALL_URL_MAX * INSTALL_URLS_MAX);
if(textBuf != NULL) {
if(remoteinstall_get_last_urls(textBuf, INSTALL_URL_MAX * INSTALL_URLS_MAX)) {
action_install_url("Install from the last requested URL(s)?", textBuf, NULL, NULL, NULL);
action_install_url("Install from the last requested URL(s)?", textBuf, NULL, NULL, NULL, NULL);
} else {
prompt_display_notify("Failure", "No previously requested URL(s) could be found.", COLOR_TEXT, NULL, NULL, NULL);
}

View File

@ -5,7 +5,7 @@
#include <3ds.h>
#include "section.h"
#include "task/task.h"
#include "action/action.h"
#include "../error.h"
#include "../info.h"
#include "../prompt.h"
@ -14,150 +14,9 @@
#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 = util_http_open(context, &updateData->responseCode, updateData->url, true))) {
*handle = (u32) context;
} else {
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 util_http_close((httpcContext*) handle);
}
static Result update_get_src_size(void* data, u32 handle, u64* size) {
u32 downloadSize = 0;
Result res = util_http_get_size((httpcContext*) handle, &downloadSize);
*size = downloadSize;
return res;
}
static Result update_read_src(void* data, u32 handle, u32* bytesRead, void* buffer, u64 offset, u32 size) {
return util_http_read((httpcContext*) handle, bytesRead, buffer, size);
}
static Result update_open_dst(void* data, u32 index, void* initialReadBlock, u64 size, 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_notify("Failure", "Install cancelled.", COLOR_TEXT, NULL, NULL, NULL);
} else if(res == R_FBI_HTTP_RESPONSE_CODE) {
error_display(NULL, NULL, "Failed to update FBI.\nHTTP server returned response code %d", updateData->responseCode);
} else {
error_display_res(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_notify("Success", "Update complete.", COLOR_TEXT, 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 %s / %.2f %s\n%.2f %s/s, ETA %s", util_get_display_size(updateData->installInfo.currProcessed), util_get_display_size_units(updateData->installInfo.currProcessed), util_get_display_size(updateData->installInfo.currTotal), util_get_display_size_units(updateData->installInfo.currTotal), util_get_display_size(updateData->installInfo.copyBytesPerSecond), util_get_display_size_units(updateData->installInfo.copyBytesPerSecond), util_get_display_eta(updateData->installInfo.estimatedRemainingSeconds));
}
static void update_check_update(ui_view* view, void* data, float* progress, char* text) {
update_data* updateData = (update_data*) data;
bool hasUpdate = false;
char updateURL[INSTALL_URL_MAX];
Result res = 0;
u32 responseCode = 0;
@ -217,7 +76,7 @@ static void update_check_update(ui_view* view, void* data, float* progress, char
}
if(url != NULL) {
strncpy(updateData->url, url, URL_MAX);
strncpy(updateURL, url, INSTALL_URL_MAX);
hasUpdate = true;
} else {
res = R_FBI_BAD_DATA;
@ -252,11 +111,7 @@ static void update_check_update(ui_view* view, void* data, float* progress, char
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, res, "Failed to begin update.");
}
action_install_url("Update FBI to the latest version?", updateURL, util_get_3dsx_path(), NULL, NULL, NULL);
} else {
if(R_FAILED(res)) {
if(res == R_FBI_HTTP_RESPONSE_CODE) {
@ -267,59 +122,9 @@ static void update_check_update(ui_view* view, void* data, float* progress, char
} else {
prompt_display_notify("Success", "No updates available.", COLOR_TEXT, NULL, NULL, NULL);
}
free(data);
}
}
static void update_onresponse(ui_view* view, void* data, u32 response) {
if(response == PROMPT_YES) {
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, "Failed to allocate update check data.");
return;
}
data->responseCode = 0;
data->installInfo.data = data;
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 128 * 1024;
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_yes_no("Confirmation", "Check for FBI updates?", COLOR_TEXT, data, NULL, update_onresponse);
info_display("Checking For Updates", "", false, NULL, update_check_update, NULL);
}