Add updater.

This commit is contained in:
Steven Smith 2016-05-28 16:34:58 -07:00
parent cb0144f07a
commit c8c73f0872
9 changed files with 1632 additions and 2 deletions

View File

@ -300,7 +300,6 @@ Result util_import_seed(u64 titleId) {
} else {
res = R_FBI_OUT_OF_RANGE;
}
}
if(R_SUCCEEDED(res)) {

26
source/json/LICENSE Normal file
View File

@ -0,0 +1,26 @@
Copyright (C) 2012, 2013 James McLaughlin et al. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

1011
source/json/json.c Normal file

File diff suppressed because it is too large Load Diff

283
source/json/json.h Normal file
View File

@ -0,0 +1,283 @@
/* vim: set et ts=3 sw=3 sts=3 ft=c:
*
* Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved.
* https://github.com/udp/json-parser
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _JSON_H
#define _JSON_H
#ifndef json_char
#define json_char char
#endif
#ifndef json_int_t
#ifndef _MSC_VER
#include <inttypes.h>
#define json_int_t int64_t
#else
#define json_int_t __int64
#endif
#endif
#include <stdlib.h>
#ifdef __cplusplus
#include <string.h>
extern "C"
{
#endif
typedef struct
{
unsigned long max_memory;
int settings;
/* Custom allocator support (leave null to use malloc/free)
*/
void * (* mem_alloc) (size_t, int zero, void * user_data);
void (* mem_free) (void *, void * user_data);
void * user_data; /* will be passed to mem_alloc and mem_free */
size_t value_extra; /* how much extra space to allocate for values? */
} json_settings;
#define json_enable_comments 0x01
typedef enum
{
json_none,
json_object,
json_array,
json_integer,
json_double,
json_string,
json_boolean,
json_null
} json_type;
extern const struct _json_value json_value_none;
typedef struct _json_object_entry
{
json_char * name;
unsigned int name_length;
struct _json_value * value;
} json_object_entry;
typedef struct _json_value
{
struct _json_value * parent;
json_type type;
union
{
int boolean;
json_int_t integer;
double dbl;
struct
{
unsigned int length;
json_char * ptr; /* null terminated */
} string;
struct
{
unsigned int length;
json_object_entry * values;
#if defined(__cplusplus) && __cplusplus >= 201103L
decltype(values) begin () const
{ return values;
}
decltype(values) end () const
{ return values + length;
}
#endif
} object;
struct
{
unsigned int length;
struct _json_value ** values;
#if defined(__cplusplus) && __cplusplus >= 201103L
decltype(values) begin () const
{ return values;
}
decltype(values) end () const
{ return values + length;
}
#endif
} array;
} u;
union
{
struct _json_value * next_alloc;
void * object_mem;
} _reserved;
#ifdef JSON_TRACK_SOURCE
/* Location of the value in the source JSON
*/
unsigned int line, col;
#endif
/* Some C++ operator sugar */
#ifdef __cplusplus
public:
inline _json_value ()
{ memset (this, 0, sizeof (_json_value));
}
inline const struct _json_value &operator [] (int index) const
{
if (type != json_array || index < 0
|| ((unsigned int) index) >= u.array.length)
{
return json_value_none;
}
return *u.array.values [index];
}
inline const struct _json_value &operator [] (const char * index) const
{
if (type != json_object)
return json_value_none;
for (unsigned int i = 0; i < u.object.length; ++ i)
if (!strcmp (u.object.values [i].name, index))
return *u.object.values [i].value;
return json_value_none;
}
inline operator const char * () const
{
switch (type)
{
case json_string:
return u.string.ptr;
default:
return "";
};
}
inline operator json_int_t () const
{
switch (type)
{
case json_integer:
return u.integer;
case json_double:
return (json_int_t) u.dbl;
default:
return 0;
};
}
inline operator bool () const
{
if (type != json_boolean)
return false;
return u.boolean != 0;
}
inline operator double () const
{
switch (type)
{
case json_integer:
return (double) u.integer;
case json_double:
return u.dbl;
default:
return 0;
};
}
#endif
} json_value;
json_value * json_parse (const json_char * json,
size_t length);
#define json_error_max 128
json_value * json_parse_ex (json_settings * settings,
const json_char * json,
size_t length,
char * error);
void json_value_free (json_value *);
/* Not usually necessary, unless you used a custom mem_alloc and now want to
* use a custom mem_free.
*/
void json_value_free_ex (json_settings * settings,
json_value *);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@ -496,6 +496,10 @@ static const char* description_to_string(Result res) {
return "Invalid argument";
case R_FBI_THREAD_CREATE_FAILED:
return "Thread creation failed";
case R_FBI_PARSE_FAILED:
return "Parse failed";
case R_FBI_BAD_DATA:
return "Bad data";
default:
break;
}

View File

@ -6,6 +6,8 @@
#define R_FBI_WRONG_SYSTEM MAKERESULT(RL_PERMANENT, RS_NOTSUPPORTED, RM_APPLICATION, 4)
#define R_FBI_INVALID_ARGUMENT MAKERESULT(RL_PERMANENT, RS_INVALIDARG, RM_APPLICATION, 5)
#define R_FBI_THREAD_CREATE_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 6)
#define R_FBI_PARSE_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 7)
#define R_FBI_BAD_DATA MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 8)
#define R_FBI_OUT_OF_MEMORY MAKERESULT(RL_FATAL, RS_OUTOFRESOURCE, RM_APPLICATION, RD_OUT_OF_MEMORY)
#define R_FBI_OUT_OF_RANGE MAKERESULT(RL_PERMANENT, RS_INVALIDARG, RM_APPLICATION, RD_OUT_OF_RANGE)

View File

@ -23,6 +23,7 @@ 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 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};
static void mainmenu_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
u32 logoWidth;
@ -61,6 +62,7 @@ static void mainmenu_update(ui_view* view, void* data, linked_list* items, list_
linked_list_add(items, &system_save_data);
linked_list_add(items, &network_install);
linked_list_add(items, &qr_code_install);
linked_list_add(items, &update);
}
}

View File

@ -13,4 +13,5 @@ void pendingtitles_open();
void qrinstall_open();
void systemsavedata_open();
void tickets_open();
void titles_open();
void titles_open();
void update_open();

302
source/ui/section/update.c Normal file
View File

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