Revamp TitleDB support, replace json library with jansson, initial work

on replacing httpc with curl.
This commit is contained in:
Steven Smith 2018-02-02 18:33:07 -08:00
parent 25541f2470
commit 4e49914e11
41 changed files with 2689 additions and 3737 deletions

View File

@ -45,8 +45,8 @@ endif
# 3DS CONFIGURATION # # 3DS CONFIGURATION #
ifeq ($(TARGET),3DS) ifeq ($(TARGET),3DS)
LIBRARY_DIRS += $(DEVKITPRO)/libctru LIBRARY_DIRS += $(DEVKITPRO)/libctru $(DEVKITPRO)/portlibs/armv6k $(DEVKITPRO)/portlibs/3ds
LIBRARIES += citro3d ctru LIBRARIES += jansson curl mbedtls mbedcrypto mbedx509 z citro3d ctru
PRODUCT_CODE := CTR-P-CFBI PRODUCT_CODE := CTR-P-CFBI
UNIQUE_ID := 0xF8001 UNIQUE_ID := 0xF8001

View File

@ -4,7 +4,7 @@ FBI is an open source title manager for the 3DS.
Download: https://github.com/Steveice10/FBI/releases Download: https://github.com/Steveice10/FBI/releases
Requires [devkitARM](http://sourceforge.net/projects/devkitpro/files/devkitARM/) and [citro3d](https://github.com/fincs/citro3d) to build. Requires [devkitARM](http://sourceforge.net/projects/devkitpro/files/devkitARM/), [citro3d](https://github.com/fincs/citro3d), and zlib, jansson, mbedtls, and curl from [3ds_portlibs](https://github.com/devkitPro/3ds_portlibs) to build.
# Credit # Credit

@ -1 +1 @@
Subproject commit 1ea00aaa369d80a825002e5d514dddff2305f88b Subproject commit adf5fa3e8382a0a3847ec3f7dcff781417c8751c

View File

@ -1,7 +1,7 @@
# servefiles # servefiles
Simple Python script for serving local files to FBI's remote installer. Requires [Python](https://www.python.org/downloads/). Simple Python script for serving local files to FBI's remote installer. Requires [Python](https://www.python.org/downloads/).
**Usage**: python servefiles.py (3ds ip) (file / directory) \[host ip\] \[host port\] **Usage**: python servefiles.py (3ds ip) (file / directory) \[host ip\] \[host port\]
- Supported file extensions: .cia, .tik, .cetk, .3dsx - Supported file extensions: .cia, .tik, .cetk, .3dsx

View File

@ -1,15 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding: utf-8 -*- # coding: utf-8 -*-
from setuptools import setup from setuptools import setup
setup( setup(
name="servefiles", name="servefiles",
version="2.4.12", version="2.4.12",
scripts=["servefiles.py", "sendurls.py"], scripts=["servefiles.py", "sendurls.py"],
author="Steveice10", author="Steveice10",
author_email="Steveice10@gmail.com", author_email="Steveice10@gmail.com",
description="Simple Python script for serving local files to FBI's remote installer.", description="Simple Python script for serving local files to FBI's remote installer.",
license="MIT", license="MIT",
url="https://github.com/Steveice10/FBI/tree/master/servefiles" url="https://github.com/Steveice10/FBI/tree/master/servefiles"
) )

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
#include <3ds.h> #include <3ds.h>
#include "spi.h" #include "spi.h"
#include "../ui/error.h" #include "util.h"
/* /*
* Based on information from TWLSaveTool, by TuxSH. * Based on information from TWLSaveTool, by TuxSH.

View File

@ -5,9 +5,10 @@
#include <string.h> #include <string.h>
#include <3ds.h> #include <3ds.h>
#include <curl/curl.h>
#include <jansson.h>
#include "util.h" #include "util.h"
#include "../ui/error.h"
#include "../ui/list.h" #include "../ui/list.h"
#include "../ui/section/task/task.h" #include "../ui/section/task/task.h"
#include "linkedlist.h" #include "linkedlist.h"
@ -268,6 +269,92 @@ bool util_is_string_empty(const char* str) {
return true; return true;
} }
typedef struct {
u8* buf;
u32 size;
u32 pos;
} download_data;
static size_t util_download_write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
download_data* data = (download_data*) userp;
size_t realSize = size * nmemb;
size_t remaining = data->size - data->pos;
size_t copy = realSize < remaining ? realSize : remaining;
memcpy(&data->buf[data->pos], contents, copy);
data->pos += copy;
return copy;
}
Result util_download(const char* url, u32* downloadedSize, void* buf, size_t size) {
if(url == NULL || buf == NULL) {
return R_FBI_INVALID_ARGUMENT;
}
Result res = 0;
CURL* curl = curl_easy_init();
if(curl != NULL) {
download_data readData = {buf, size, 0};
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, util_download_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*) &readData);
curl_easy_setopt(curl, CURLOPT_USERAGENT, HTTP_USER_AGENT);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, HTTP_CONNECT_TIMEOUT);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); // TODO: Certificates?
CURLcode ret = curl_easy_perform(curl);
if(ret == CURLE_OK) {
if(downloadedSize != NULL) {
*downloadedSize = readData.pos;
}
} else {
res = R_FBI_CURL_ERORR_BASE + ret;
}
curl_easy_cleanup(curl);
} else {
res = R_FBI_CURL_INIT_FAILED;
}
return res;
}
Result util_download_json(const char* url, json_t** json, size_t maxSize) {
if(url == NULL || json == NULL) {
return R_FBI_INVALID_ARGUMENT;
}
Result res = 0;
char* text = (char*) calloc(sizeof(char), maxSize);
if(text != NULL) {
u32 textSize = 0;
if(R_SUCCEEDED(res = util_download(url, &textSize, text, maxSize))) {
json_error_t error;
json_t* parsed = json_loads(text, 0, &error);
if(parsed != NULL) {
*json = parsed;
} else {
res = R_FBI_PARSE_FAILED;
}
}
free(text);
} else {
res = R_FBI_OUT_OF_MEMORY;
}
return res;
}
static Result FSUSER_AddSeed(u64 titleId, const void* seed) { static Result FSUSER_AddSeed(u64 titleId, const void* seed) {
u32 *cmdbuf = getThreadCommandBuffer(); u32 *cmdbuf = getThreadCommandBuffer();
@ -313,15 +400,9 @@ Result util_import_seed(u32* responseCode, u64 titleId) {
char url[128]; char url[128];
snprintf(url, 128, "https://kagiya-ctr.cdn.nintendo.net/title/0x%016llX/ext_key?country=%s", titleId, regionStrings[region]); snprintf(url, 128, "https://kagiya-ctr.cdn.nintendo.net/title/0x%016llX/ext_key?country=%s", titleId, regionStrings[region]);
httpcContext context; u32 downloadedSize = 0;
if(R_SUCCEEDED(res = util_http_open(&context, responseCode, url, false))) { if(R_SUCCEEDED(res = util_download(url, &downloadedSize, seed, sizeof(seed))) && downloadedSize != sizeof(seed)) {
u32 bytesRead = 0; res = R_FBI_BAD_DATA;
res = util_http_read(&context, &bytesRead, seed, sizeof(seed));
Result closeRes = util_http_close(&context);
if(R_SUCCEEDED(res)) {
res = closeRes;
}
} }
} else { } else {
res = R_FBI_OUT_OF_RANGE; res = R_FBI_OUT_OF_RANGE;
@ -694,11 +775,7 @@ u16* util_select_bnr_title(BNR* bnr) {
return bnr->titles[systemLanguage]; return bnr->titles[systemLanguage];
} }
#define HTTP_TIMEOUT 15000000000 #define HTTPC_TIMEOUT 15000000000
#define MAKE_HTTP_USER_AGENT_(major, minor, micro) ("Mozilla/5.0 (Nintendo 3DS; Mobile; rv:10.0) Gecko/20100101 FBI/" #major "." #minor "." #micro)
#define MAKE_HTTP_USER_AGENT(major, minor, micro) MAKE_HTTP_USER_AGENT_(major, minor, micro)
#define HTTP_USER_AGENT MAKE_HTTP_USER_AGENT(VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO)
Result util_http_open(httpcContext* context, u32* responseCode, const char* url, bool userAgent) { Result util_http_open(httpcContext* context, u32* responseCode, const char* url, bool userAgent) {
return util_http_open_ranged(context, responseCode, url, userAgent, 0, 0); return util_http_open_ranged(context, responseCode, url, userAgent, 0, 0);
@ -731,7 +808,7 @@ Result util_http_open_ranged(httpcContext* context, u32* responseCode, const cha
&& (rangeStart == 0 || R_SUCCEEDED(res = httpcAddRequestHeaderField(context, "Range", range))) && (rangeStart == 0 || R_SUCCEEDED(res = httpcAddRequestHeaderField(context, "Range", range)))
&& R_SUCCEEDED(res = httpcSetKeepAlive(context, HTTPC_KEEPALIVE_ENABLED)) && R_SUCCEEDED(res = httpcSetKeepAlive(context, HTTPC_KEEPALIVE_ENABLED))
&& R_SUCCEEDED(res = httpcBeginRequest(context)) && R_SUCCEEDED(res = httpcBeginRequest(context))
&& R_SUCCEEDED(res = httpcGetResponseStatusCodeTimeout(context, &response, HTTP_TIMEOUT))) { && R_SUCCEEDED(res = httpcGetResponseStatusCodeTimeout(context, &response, HTTPC_TIMEOUT))) {
if(response == 301 || response == 302 || response == 303) { if(response == 301 || response == 302 || response == 303) {
redirectCount++; redirectCount++;
@ -816,7 +893,7 @@ Result util_http_read(httpcContext* context, u32* bytesRead, void* buffer, u32 s
u32 outPos = 0; u32 outPos = 0;
while(res == HTTPC_RESULTCODE_DOWNLOADPENDING && outPos < size) { while(res == HTTPC_RESULTCODE_DOWNLOADPENDING && outPos < size) {
if(R_SUCCEEDED(res = httpcReceiveDataTimeout(context, &((u8*) buffer)[outPos], size - outPos, HTTP_TIMEOUT)) || res == HTTPC_RESULTCODE_DOWNLOADPENDING) { if(R_SUCCEEDED(res = httpcReceiveDataTimeout(context, &((u8*) buffer)[outPos], size - outPos, HTTPC_TIMEOUT)) || res == HTTPC_RESULTCODE_DOWNLOADPENDING) {
Result posRes = 0; Result posRes = 0;
u32 currPos = 0; u32 currPos = 0;
if(R_SUCCEEDED(posRes = httpcGetDownloadSizeState(context, &currPos, NULL))) { if(R_SUCCEEDED(posRes = httpcGetDownloadSizeState(context, &currPos, NULL))) {

View File

@ -1,5 +1,29 @@
#pragma once #pragma once
typedef struct json_t json_t;
#define R_FBI_CANCELLED MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, 1)
#define R_FBI_HTTP_RESPONSE_CODE MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 2)
#define R_FBI_WRONG_SYSTEM MAKERESULT(RL_PERMANENT, RS_NOTSUPPORTED, RM_APPLICATION, 3)
#define R_FBI_INVALID_ARGUMENT MAKERESULT(RL_PERMANENT, RS_INVALIDARG, RM_APPLICATION, 4)
#define R_FBI_THREAD_CREATE_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 5)
#define R_FBI_PARSE_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 6)
#define R_FBI_BAD_DATA MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 7)
#define R_FBI_TOO_MANY_REDIRECTS MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 8)
#define R_FBI_CURL_INIT_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 9)
#define R_FBI_CURL_ERORR_BASE MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 10)
#define R_FBI_NOT_IMPLEMENTED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, RD_NOT_IMPLEMENTED)
#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)
#define MAKE_HTTP_USER_AGENT_(major, minor, micro) ("Mozilla/5.0 (Nintendo 3DS; Mobile; rv:10.0) Gecko/20100101 FBI/" #major "." #minor "." #micro)
#define MAKE_HTTP_USER_AGENT(major, minor, micro) MAKE_HTTP_USER_AGENT_(major, minor, micro)
#define HTTP_USER_AGENT MAKE_HTTP_USER_AGENT(VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO)
#define HTTP_CONNECT_TIMEOUT 15
typedef struct { typedef struct {
u16 shortDescription[0x40]; u16 shortDescription[0x40];
u16 longDescription[0x80]; u16 longDescription[0x80];
@ -55,6 +79,9 @@ void util_get_parent_path(char* out, const char* path, u32 size);
bool util_is_string_empty(const char* str); bool util_is_string_empty(const char* str);
Result util_download(const char* url, u32* downloadedSize, void* buf, size_t size);
Result util_download_json(const char* url, json_t** json, size_t maxSize);
Result util_import_seed(u32* responseCode, u64 titleId); Result util_import_seed(u32* responseCode, u64 titleId);
FS_MediaType util_get_title_destination(u64 titleId); FS_MediaType util_get_title_destination(u64 titleId);

View File

@ -1,26 +0,0 @@
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.

File diff suppressed because it is too large Load Diff

View File

@ -1,283 +0,0 @@
/* 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

@ -1,188 +1,197 @@
#include <sys/iosupport.h> #include <sys/iosupport.h>
#include <malloc.h> #include <malloc.h>
#include <3ds.h> #include <3ds.h>
#include <curl/curl.h>
#include "core/clipboard.h"
#include "core/screen.h" #include "core/clipboard.h"
#include "core/util.h" #include "core/screen.h"
#include "ui/error.h" #include "core/util.h"
#include "ui/mainmenu.h" #include "ui/error.h"
#include "ui/ui.h" #include "ui/mainmenu.h"
#include "ui/section/task/task.h" #include "ui/ui.h"
#include "ui/section/task/task.h"
#define CURRENT_KPROCESS (*(void**) 0xFFFF9004)
#define CURRENT_KPROCESS (*(void**) 0xFFFF9004)
#define KPROCESS_PID_OFFSET_OLD (0xB4)
#define KPROCESS_PID_OFFSET_NEW (0xBC) #define KPROCESS_PID_OFFSET_OLD (0xB4)
#define KPROCESS_PID_OFFSET_NEW (0xBC)
static bool backdoor_ran = false;
static bool n3ds = false; static bool backdoor_ran = false;
static u32 old_pid = 0; static bool n3ds = false;
static u32 old_pid = 0;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type" #pragma GCC diagnostic push
static __attribute__((naked)) Result svcGlobalBackdoor(s32 (*callback)()) { #pragma GCC diagnostic ignored "-Wreturn-type"
asm volatile( static __attribute__((naked)) Result svcGlobalBackdoor(s32 (*callback)()) {
"svc 0x30\n" asm volatile(
"bx lr" "svc 0x30\n"
); "bx lr"
} );
#pragma GCC diagnostic pop }
#pragma GCC diagnostic pop
static s32 patch_pid_kernel() {
u32 *pidPtr = (u32*) (CURRENT_KPROCESS + (n3ds ? KPROCESS_PID_OFFSET_NEW : KPROCESS_PID_OFFSET_OLD)); static s32 patch_pid_kernel() {
u32 *pidPtr = (u32*) (CURRENT_KPROCESS + (n3ds ? KPROCESS_PID_OFFSET_NEW : KPROCESS_PID_OFFSET_OLD));
old_pid = *pidPtr;
*pidPtr = 0; old_pid = *pidPtr;
*pidPtr = 0;
backdoor_ran = true;
return 0; backdoor_ran = true;
} return 0;
}
static s32 restore_pid_kernel() {
u32 *pidPtr = (u32*) (CURRENT_KPROCESS + (n3ds ? KPROCESS_PID_OFFSET_NEW : KPROCESS_PID_OFFSET_OLD)); static s32 restore_pid_kernel() {
u32 *pidPtr = (u32*) (CURRENT_KPROCESS + (n3ds ? KPROCESS_PID_OFFSET_NEW : KPROCESS_PID_OFFSET_OLD));
*pidPtr = old_pid;
*pidPtr = old_pid;
backdoor_ran = true;
return 0; backdoor_ran = true;
} return 0;
}
static bool attempt_patch_pid() {
backdoor_ran = false; static bool attempt_patch_pid() {
APT_CheckNew3DS(&n3ds); backdoor_ran = false;
APT_CheckNew3DS(&n3ds);
svcGlobalBackdoor(patch_pid_kernel);
srvExit(); svcGlobalBackdoor(patch_pid_kernel);
srvInit(); srvExit();
svcGlobalBackdoor(restore_pid_kernel); srvInit();
svcGlobalBackdoor(restore_pid_kernel);
return backdoor_ran;
} return backdoor_ran;
}
static void (*exit_funcs[16])()= {NULL};
static u32 exit_func_count = 0; static void (*exit_funcs[16])()= {NULL};
static u32 exit_func_count = 0;
static void* soc_buffer = NULL;
static void* soc_buffer = NULL;
void cleanup_services() {
for(u32 i = 0; i < exit_func_count; i++) { void cleanup_services() {
if(exit_funcs[i] != NULL) { for(u32 i = 0; i < exit_func_count; i++) {
exit_funcs[i](); if(exit_funcs[i] != NULL) {
exit_funcs[i] = NULL; exit_funcs[i]();
} exit_funcs[i] = NULL;
} }
}
exit_func_count = 0;
exit_func_count = 0;
if(soc_buffer != NULL) {
free(soc_buffer); if(soc_buffer != NULL) {
soc_buffer = NULL; free(soc_buffer);
} soc_buffer = NULL;
} }
}
#define INIT_SERVICE(initStatement, exitFunc) (R_SUCCEEDED(res = (initStatement)) && (exit_funcs[exit_func_count++] = (exitFunc)))
#define INIT_SERVICE(initStatement, exitFunc) (R_SUCCEEDED(res = (initStatement)) && (exit_funcs[exit_func_count++] = (exitFunc)))
Result init_services() {
Result res = 0; Result init_services() {
Result res = 0;
soc_buffer = memalign(0x1000, 0x100000);
if(soc_buffer != NULL) { soc_buffer = memalign(0x1000, 0x100000);
Handle tempAM = 0; if(soc_buffer != NULL) {
if(R_SUCCEEDED(res = srvGetServiceHandle(&tempAM, "am:net"))) { Handle tempAM = 0;
svcCloseHandle(tempAM); if(R_SUCCEEDED(res = srvGetServiceHandle(&tempAM, "am:net"))) {
svcCloseHandle(tempAM);
if(INIT_SERVICE(amInit(), amExit)
&& INIT_SERVICE(cfguInit(), cfguExit) if(INIT_SERVICE(amInit(), amExit)
&& INIT_SERVICE(acInit(), acExit) && INIT_SERVICE(cfguInit(), cfguExit)
&& INIT_SERVICE(ptmuInit(), ptmuExit) && INIT_SERVICE(acInit(), acExit)
&& INIT_SERVICE(pxiDevInit(), pxiDevExit) && INIT_SERVICE(ptmuInit(), ptmuExit)
&& INIT_SERVICE(httpcInit(0), httpcExit) && INIT_SERVICE(pxiDevInit(), pxiDevExit)
&& INIT_SERVICE(socInit(soc_buffer, 0x100000), (void (*)()) socExit)); && INIT_SERVICE(httpcInit(0), httpcExit)
} && INIT_SERVICE(socInit(soc_buffer, 0x100000), (void (*)()) socExit));
} else { }
res = R_FBI_OUT_OF_MEMORY; } else {
} res = R_FBI_OUT_OF_MEMORY;
}
if(R_FAILED(res)) {
cleanup_services(); if(R_FAILED(res)) {
} cleanup_services();
}
return res;
} return res;
}
static u32 old_time_limit = UINT32_MAX;
static u32 old_time_limit = UINT32_MAX;
void init() {
gfxInitDefault(); #include <netinet/in.h>
#include <arpa/inet.h>
Result romfsRes = romfsInit(); #include <netdb.h>
if(R_FAILED(romfsRes)) {
util_panic("Failed to mount RomFS: %08lX", romfsRes); FILE* dbg;
return;
} void init() {
gfxInitDefault();
if(R_FAILED(init_services())) {
if(!attempt_patch_pid()) { Result romfsRes = romfsInit();
util_panic("Kernel backdoor not installed.\nPlease run a kernel exploit and try again."); if(R_FAILED(romfsRes)) {
return; util_panic("Failed to mount RomFS: %08lX", romfsRes);
} return;
}
Result initRes = init_services();
if(R_FAILED(initRes)) { if(R_FAILED(init_services())) {
util_panic("Failed to initialize services: %08lX", initRes); if(!attempt_patch_pid()) {
return; util_panic("Kernel backdoor not installed.\nPlease run a kernel exploit and try again.");
} return;
} }
osSetSpeedupEnable(true); Result initRes = init_services();
if(R_FAILED(initRes)) {
APT_GetAppCpuTimeLimit(&old_time_limit); util_panic("Failed to initialize services: %08lX", initRes);
Result cpuRes = APT_SetAppCpuTimeLimit(30); return;
if(R_FAILED(cpuRes)) { }
util_panic("Failed to set syscore CPU time limit: %08lX", cpuRes); }
return;
} osSetSpeedupEnable(true);
AM_InitializeExternalTitleDatabase(false); APT_GetAppCpuTimeLimit(&old_time_limit);
Result cpuRes = APT_SetAppCpuTimeLimit(30);
screen_init(); if(R_FAILED(cpuRes)) {
ui_init(); util_panic("Failed to set syscore CPU time limit: %08lX", cpuRes);
task_init(); return;
} }
void cleanup() { AM_InitializeExternalTitleDatabase(false);
clipboard_clear();
curl_global_init(CURL_GLOBAL_ALL);
task_exit();
ui_exit(); screen_init();
screen_exit(); ui_init();
task_init();
if(old_time_limit != UINT32_MAX) { }
APT_SetAppCpuTimeLimit(old_time_limit);
} void cleanup() {
clipboard_clear();
osSetSpeedupEnable(false);
task_exit();
cleanup_services(); ui_exit();
screen_exit();
romfsExit();
if(old_time_limit != UINT32_MAX) {
gfxExit(); APT_SetAppCpuTimeLimit(old_time_limit);
} }
int main(int argc, const char* argv[]) { osSetSpeedupEnable(false);
if(argc > 0 && envIsHomebrew()) {
util_set_3dsx_path(argv[0]); cleanup_services();
}
romfsExit();
init();
gfxExit();
mainmenu_open(); }
while(aptMainLoop() && ui_update());
int main(int argc, const char* argv[]) {
cleanup(); if(argc > 0 && envIsHomebrew()) {
util_set_3dsx_path(argv[0]);
return 0; }
}
init();
mainmenu_open();
while(aptMainLoop() && ui_update());
cleanup();
return 0;
}

View File

@ -4,10 +4,12 @@
#include <string.h> #include <string.h>
#include <3ds.h> #include <3ds.h>
#include <curl/curl.h>
#include "error.h" #include "error.h"
#include "prompt.h" #include "prompt.h"
#include "../core/screen.h" #include "../core/screen.h"
#include "../core/util.h"
static const char* level_to_string(Result res) { static const char* level_to_string(Result res) {
switch(R_LEVEL(res)) { switch(R_LEVEL(res)) {
@ -502,7 +504,13 @@ static const char* description_to_string(Result res) {
return "Bad data"; return "Bad data";
case R_FBI_TOO_MANY_REDIRECTS: case R_FBI_TOO_MANY_REDIRECTS:
return "Too many redirects"; return "Too many redirects";
case R_FBI_CURL_INIT_FAILED:
return "Failed to initialize CURL.";
default: default:
if(res >= R_FBI_CURL_ERORR_BASE && res < R_FBI_CURL_ERORR_BASE + CURL_LAST) {
return curl_easy_strerror((CURLcode) (res - R_FBI_CURL_ERORR_BASE));
}
break; break;
} }
default: default:

View File

@ -1,18 +1,5 @@
#pragma once #pragma once
#define R_FBI_CANCELLED MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, 1)
#define R_FBI_HTTP_RESPONSE_CODE MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 2)
#define R_FBI_WRONG_SYSTEM MAKERESULT(RL_PERMANENT, RS_NOTSUPPORTED, RM_APPLICATION, 3)
#define R_FBI_INVALID_ARGUMENT MAKERESULT(RL_PERMANENT, RS_INVALIDARG, RM_APPLICATION, 4)
#define R_FBI_THREAD_CREATE_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 5)
#define R_FBI_PARSE_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 6)
#define R_FBI_BAD_DATA MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 7)
#define R_FBI_TOO_MANY_REDIRECTS MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 8)
#define R_FBI_NOT_IMPLEMENTED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, RD_NOT_IMPLEMENTED)
#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)
typedef struct ui_view_s ui_view; typedef struct ui_view_s ui_view;
ui_view* error_display(void* data, void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2), const char* text, ...); ui_view* error_display(void* data, void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2), const char* text, ...);

View File

@ -56,4 +56,4 @@ void action_delete_secure_value(linked_list* items, list_item* selected);
void action_install_url(const char* confirmMessage, const char* urls, const char* path3dsx, void* userData, void (*finished)(void* data), 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 (*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_install_titledb(linked_list* items, list_item* selected, bool cia);

View File

@ -1,27 +1,61 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <3ds.h>
#include <3ds.h>
#include "action.h"
#include "../task/task.h" #include "action.h"
#include "../../error.h" #include "../task/task.h"
#include "../../list.h" #include "../../error.h"
#include "../../ui.h" #include "../../list.h"
#include "../../../core/linkedlist.h" #include "../../ui.h"
#include "../../../core/linkedlist.h"
static void action_install_titledb_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, u32 index) { #include "../../../core/util.h"
ui_draw_titledb_info(view, ((list_item*) data)->data, x1, y1, x2, y2);
} typedef struct {
list_item* selected;
static void action_update_titledb_finished(void* data) { bool cia;
task_populate_titledb_update_status((list_item*) data); } install_titledb_data;
}
static void action_install_titledb_draw_top(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) { install_titledb_data* installData = (install_titledb_data*) data;
titledb_info* info = (titledb_info*) selected->data;
if(installData->cia) {
char url[64]; ui_draw_titledb_info_cia(view, installData->selected->data, x1, y1, x2, y2);
snprintf(url, INSTALL_URL_MAX, "https://3ds.titledb.com/v1/cia/%lu/download", info->id); } else {
ui_draw_titledb_info_tdsx(view, installData->selected->data, x1, y1, x2, y2);
action_install_url("Install the selected title from TitleDB?", url, NULL, selected, action_update_titledb_finished, action_install_titledb_draw_top); }
}
static void action_update_titledb_finished(void* data) {
task_populate_titledb_update_status(((install_titledb_data*) data)->selected);
free(data);
}
void action_install_titledb(linked_list* items, list_item* selected, bool cia) {
install_titledb_data* data = (install_titledb_data*) calloc(1, sizeof(install_titledb_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate install TitleDB data.");
return;
}
data->selected = selected;
data->cia = cia;
titledb_info* info = (titledb_info*) selected->data;
char url[64];
char path3dsx[FILE_PATH_MAX];
if(data->cia) {
snprintf(url, INSTALL_URL_MAX, "https://3ds.titledb.com/v1/cia/%lu/download", info->cia.id);
} else {
snprintf(url, INSTALL_URL_MAX, "https://3ds.titledb.com/v1/tdsx/%lu/download", info->tdsx.id);
char name[FILE_NAME_MAX];
util_escape_file_name(name, info->meta.shortDescription, sizeof(name));
snprintf(path3dsx, sizeof(path3dsx), "/3ds/%s/%s.3dsx", name, name);
}
action_install_url("Install the selected title from TitleDB?", url, path3dsx, data, action_update_titledb_finished, action_install_titledb_draw_top);
} }

View File

@ -4,9 +4,9 @@
#include <malloc.h> #include <malloc.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <3ds.h> #include <3ds.h>
#include <unistd.h>
#include "section.h" #include "section.h"
#include "action/action.h" #include "action/action.h"
@ -20,7 +20,7 @@
#include "../../core/linkedlist.h" #include "../../core/linkedlist.h"
#include "../../core/screen.h" #include "../../core/screen.h"
#include "../../core/util.h" #include "../../core/util.h"
#include "../../quirc/quirc_internal.h" #include "../../libs/quirc/quirc_internal.h"
static bool remoteinstall_get_last_urls(char* out, size_t size) { static bool remoteinstall_get_last_urls(char* out, size_t size) {
if(out == NULL || size == 0) { if(out == NULL || size == 0) {

View File

@ -5,7 +5,7 @@
#include "task.h" #include "task.h"
#include "../../list.h" #include "../../list.h"
#include "../../error.h" #include "../../../core/util.h"
#define EVENT_CANCEL 0 #define EVENT_CANCEL 0
#define EVENT_RECV 1 #define EVENT_RECV 1

View File

@ -4,10 +4,10 @@
#include <3ds.h> #include <3ds.h>
#include "task.h" #include "task.h"
#include "../../error.h"
#include "../../prompt.h" #include "../../prompt.h"
#include "../../ui.h" #include "../../ui.h"
#include "../../../core/screen.h" #include "../../../core/screen.h"
#include "../../../core/util.h"
static Result task_data_op_check_running(data_op_data* data, u32 index, u32* srcHandle, u32* dstHandle) { static Result task_data_op_check_running(data_op_data* data, u32 index, u32* srcHandle, u32* dstHandle) {
Result res = 0; Result res = 0;

View File

@ -7,7 +7,6 @@
#include "task.h" #include "task.h"
#include "../../list.h" #include "../../list.h"
#include "../../error.h"
#include "../../../core/linkedlist.h" #include "../../../core/linkedlist.h"
#include "../../../core/screen.h" #include "../../../core/screen.h"
#include "../../../core/util.h" #include "../../../core/util.h"

View File

@ -7,7 +7,6 @@
#include "task.h" #include "task.h"
#include "../../list.h" #include "../../list.h"
#include "../../error.h"
#include "../../../core/linkedlist.h" #include "../../../core/linkedlist.h"
#include "../../../core/screen.h" #include "../../../core/screen.h"
#include "../../../core/util.h" #include "../../../core/util.h"

View File

@ -6,7 +6,6 @@
#include "task.h" #include "task.h"
#include "../../list.h" #include "../../list.h"
#include "../../error.h"
#include "../../../core/linkedlist.h" #include "../../../core/linkedlist.h"
#include "../../../core/screen.h" #include "../../../core/screen.h"
#include "../../../core/util.h" #include "../../../core/util.h"

View File

@ -6,7 +6,6 @@
#include "task.h" #include "task.h"
#include "../../list.h" #include "../../list.h"
#include "../../error.h"
#include "../../../core/linkedlist.h" #include "../../../core/linkedlist.h"
#include "../../../core/screen.h" #include "../../../core/screen.h"
#include "../../../core/util.h" #include "../../../core/util.h"

View File

@ -6,7 +6,6 @@
#include "task.h" #include "task.h"
#include "../../list.h" #include "../../list.h"
#include "../../error.h"
#include "../../../core/linkedlist.h" #include "../../../core/linkedlist.h"
#include "../../../core/screen.h" #include "../../../core/screen.h"
#include "../../../core/util.h" #include "../../../core/util.h"

View File

@ -1,281 +1,283 @@
#include <malloc.h> #include <malloc.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <3ds.h> #include <3ds.h>
#include <jansson.h>
#include "task.h"
#include "../../list.h" #include "task.h"
#include "../../error.h" #include "../../list.h"
#include "../../../core/linkedlist.h" #include "../../../core/linkedlist.h"
#include "../../../core/screen.h" #include "../../../core/screen.h"
#include "../../../core/util.h" #include "../../../core/util.h"
#include "../../../json/json.h" #include "../../../libs/stb_image/stb_image.h"
#include "../../../stb_image/stb_image.h"
#define json_object_get_string(obj, name, def) (json_is_string(json_object_get(obj, name)) ? json_string_value(json_object_get(obj, name)) : def)
void task_populate_titledb_update_status(list_item* item) { #define json_object_get_integer(obj, name, def) (json_is_integer(json_object_get(obj, name)) ? json_integer_value(json_object_get(obj, name)) : def)
titledb_info* info = (titledb_info*) item->data;
void task_populate_titledb_update_status(list_item* item) {
AM_TitleEntry entry; titledb_info* info = (titledb_info*) item->data;
info->installed = R_SUCCEEDED(AM_GetTitleInfo(util_get_title_destination(info->titleId), 1, &info->titleId, &entry));
info->installedVersion = info->installed ? entry.version : (u16) 0; if(info->cia.exists) {
AM_TitleEntry entry;
if(info->installed) { info->cia.installed = R_SUCCEEDED(AM_GetTitleInfo(util_get_title_destination(info->cia.titleId), 1, &info->cia.titleId, &entry));
item->color = COLOR_TITLEDB_INSTALLED; info->cia.installedVersion = info->cia.installed ? entry.version : (u16) 0;
} else { }
item->color = COLOR_TITLEDB_NOT_INSTALLED;
} if(info->tdsx.exists) {
} info->tdsx.installed = false;
static Result task_populate_titledb_download(u32* downloadSize, void* buffer, u32 maxSize, const char* url) { char name[FILE_NAME_MAX];
Result res = 0; util_escape_file_name(name, info->meta.shortDescription, sizeof(name));
httpcContext context; char path3dsx[FILE_PATH_MAX];
if(R_SUCCEEDED(res = util_http_open(&context, NULL, url, true))) { snprintf(path3dsx, sizeof(path3dsx), "/3ds/%s/%s.3dsx", name, name);
res = util_http_read(&context, downloadSize, buffer, maxSize);
FS_Path* fsPath = util_make_path_utf8(path3dsx);
Result closeRes = util_http_close(&context); if(fsPath != NULL) {
if(R_SUCCEEDED(res)) { Handle handle = 0;
res = closeRes; if(R_SUCCEEDED(FSUSER_OpenFileDirectly(&handle, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), *fsPath, FS_OPEN_READ, 0))) {
} FSFILE_Close(handle);
}
info->tdsx.installed = true;
return res; }
}
util_free_path_utf8(fsPath);
static int task_populate_titledb_compare(void* userData, const void* p1, const void* p2) { }
list_item* info1 = (list_item*) p1; }
list_item* info2 = (list_item*) p2;
// TODO: Outdated color(?)
return strncasecmp(info1->name, info2->name, LIST_ITEM_NAME_MAX); if((info->cia.exists && info->cia.installed) || (info->tdsx.exists && info->tdsx.installed)) {
} item->color = COLOR_TITLEDB_INSTALLED;
} else {
static void task_populate_titledb_thread(void* arg) { item->color = COLOR_TITLEDB_NOT_INSTALLED;
populate_titledb_data* data = (populate_titledb_data*) arg; }
}
Result res = 0;
static int task_populate_titledb_compare(void* userData, const void* p1, const void* p2) {
u32 maxTextSize = 256 * 1024; list_item* info1 = (list_item*) p1;
char* text = (char*) calloc(sizeof(char), maxTextSize); list_item* info2 = (list_item*) p2;
if(text != NULL) {
u32 textSize = 0; return strncasecmp(info1->name, info2->name, LIST_ITEM_NAME_MAX);
if(R_SUCCEEDED(res = task_populate_titledb_download(&textSize, text, maxTextSize, "https://api.titledb.com/v1/cia?only=id&only=size&only=updated_at&only=titleid&only=version&only=name_s&only=name_l&only=publisher"))) { }
json_value* json = json_parse(text, textSize);
if(json != NULL) { static void task_populate_titledb_thread(void* arg) {
if(json->type == json_array) { populate_titledb_data* data = (populate_titledb_data*) arg;
linked_list titles;
linked_list_init(&titles); Result res = 0;
for(u32 i = 0; i < json->u.array.length && R_SUCCEEDED(res); i++) { json_t* root = NULL;
svcWaitSynchronization(task_get_pause_event(), U64_MAX); if(R_SUCCEEDED(res = util_download_json("https://api.titledb.com/v1/entry?nested=true"
if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { "&only=id&only=name&only=author&only=headline&only=category"
break; "&only=cia.id&only=cia.updated_at&only=cia.version&only=cia.size&only=cia.titleid"
} "&only=tdsx.id&only=tdsx.updated_at&only=tdsx.version&only=tdsx.size&only=tdsx.smdh.id",
&root, 1024 * 1024))) {
json_value* val = json->u.array.values[i]; if(json_is_array(root)) {
if(val->type == json_object) { linked_list titles;
list_item* item = (list_item*) calloc(1, sizeof(list_item)); linked_list_init(&titles);
if(item != NULL) {
titledb_info* titledbInfo = (titledb_info*) calloc(1, sizeof(titledb_info)); for(u32 i = 0; i < json_array_size(root) && R_SUCCEEDED(res); i++) {
if(titledbInfo != NULL) { svcWaitSynchronization(task_get_pause_event(), U64_MAX);
for(u32 j = 0; j < val->u.object.length; j++) { if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) {
char* name = val->u.object.values[j].name; break;
u32 nameLen = val->u.object.values[j].name_length; }
json_value* subVal = val->u.object.values[j].value;
if(subVal->type == json_string) { json_t* entry = json_array_get(root, i);
if(strncmp(name, "updated_at", nameLen) == 0) { if(json_is_object(entry)) {
strncpy(titledbInfo->updatedAt, subVal->u.string.ptr, sizeof(titledbInfo->updatedAt)); list_item* item = (list_item*) calloc(1, sizeof(list_item));
} else if(strncmp(name, "titleid", nameLen) == 0) { if(item != NULL) {
titledbInfo->titleId = strtoull(subVal->u.string.ptr, NULL, 16); titledb_info* titledbInfo = (titledb_info*) calloc(1, sizeof(titledb_info));
} else if(strncmp(name, "version", nameLen) == 0) { if(titledbInfo != NULL) {
strncpy(titledbInfo->version, subVal->u.string.ptr, sizeof(titledbInfo->version)); titledbInfo->id = (u32) json_object_get_integer(entry, "id", 0);
} else if(strncmp(name, "name_s", nameLen) == 0) { strncpy(titledbInfo->category, json_object_get_string(entry, "category", "Unknown"), sizeof(titledbInfo->category));
strncpy(titledbInfo->meta.shortDescription, subVal->u.string.ptr, sizeof(titledbInfo->meta.shortDescription)); strncpy(titledbInfo->headline, json_object_get_string(entry, "headline", ""), sizeof(titledbInfo->headline));
} else if(strncmp(name, "name_l", nameLen) == 0) { strncpy(titledbInfo->meta.shortDescription, json_object_get_string(entry, "name", ""), sizeof(titledbInfo->meta.shortDescription));
strncpy(titledbInfo->meta.longDescription, subVal->u.string.ptr, sizeof(titledbInfo->meta.longDescription)); strncpy(titledbInfo->meta.publisher, json_object_get_string(entry, "author", ""), sizeof(titledbInfo->meta.publisher));
} else if(strncmp(name, "publisher", nameLen) == 0) {
strncpy(titledbInfo->meta.publisher, subVal->u.string.ptr, sizeof(titledbInfo->meta.publisher)); json_t* cias = json_object_get(entry, "cia");
} if(json_is_array(cias)) {
} else if(subVal->type == json_integer) { for(u32 j = 0; j < json_array_size(cias); j++) {
if(strncmp(name, "id", nameLen) == 0) { json_t* cia = json_array_get(cias, j);
titledbInfo->id = (u32) subVal->u.integer; if(json_is_object(cia)) {
} else if(strncmp(name, "size", nameLen) == 0) { const char* updatedAt = json_object_get_string(cia, "updated_at", "");
titledbInfo->size = (u64) subVal->u.integer; if(!titledbInfo->cia.exists || strncmp(updatedAt, titledbInfo->cia.updatedAt, sizeof(titledbInfo->cia.updatedAt)) >= 0) {
} titledbInfo->cia.exists = true;
}
} titledbInfo->cia.id = (u32) json_object_get_integer(cia, "id", 0);
strncpy(titledbInfo->cia.updatedAt, updatedAt, sizeof(titledbInfo->cia.updatedAt));
if(strlen(titledbInfo->meta.shortDescription) > 0) { strncpy(titledbInfo->cia.version, json_object_get_string(cia, "version", "Unknown"), sizeof(titledbInfo->cia.version));
strncpy(item->name, titledbInfo->meta.shortDescription, LIST_ITEM_NAME_MAX); titledbInfo->cia.size = (u32) json_object_get_integer(cia, "size", 0);
} else { titledbInfo->cia.titleId = strtoull(json_object_get_string(cia, "titleid", "0"), NULL, 16);
snprintf(item->name, LIST_ITEM_NAME_MAX, "%016llX", titledbInfo->titleId); }
} }
}
item->data = titledbInfo; }
task_populate_titledb_update_status(item); json_t* tdsxs = json_object_get(entry, "tdsx");
if(json_is_array(tdsxs)) {
linked_list_iter iter; for(u32 j = 0; j < json_array_size(tdsxs); j++) {
linked_list_iterate(&titles, &iter); json_t* tdsx = json_array_get(tdsxs, j);
if(json_is_object(tdsx)) {
bool add = true; const char* updatedAt = json_object_get_string(tdsx, "updated_at", "");
while(linked_list_iter_has_next(&iter)) { if(!titledbInfo->tdsx.exists || strncmp(updatedAt, titledbInfo->tdsx.updatedAt, sizeof(titledbInfo->tdsx.updatedAt)) >= 0) {
list_item* currItem = (list_item*) linked_list_iter_next(&iter); titledbInfo->tdsx.exists = true;
titledb_info* currTitledbInfo = (titledb_info*) currItem->data;
titledbInfo->tdsx.id = (u32) json_object_get_integer(tdsx, "id", 0);
if(titledbInfo->titleId == currTitledbInfo->titleId) { strncpy(titledbInfo->tdsx.updatedAt, updatedAt, sizeof(titledbInfo->tdsx.updatedAt));
if(strncmp(titledbInfo->updatedAt, currTitledbInfo->updatedAt, sizeof(titledbInfo->updatedAt)) >= 0) { strncpy(titledbInfo->tdsx.version, json_object_get_string(tdsx, "version", "Unknown"), sizeof(titledbInfo->tdsx.version));
linked_list_iter_remove(&iter); titledbInfo->tdsx.size = (u32) json_object_get_integer(tdsx, "size", 0);
task_free_titledb(currItem);
} else { json_t* smdh = json_object_get(tdsx, "smdh");
add = false; if(json_is_object(smdh)) {
} titledbInfo->tdsx.smdh.exists = true;
break; titledbInfo->tdsx.smdh.id = (u32) json_object_get_integer(smdh, "id", 0);
} }
} }
}
if(add) { }
linked_list_add_sorted(&titles, item, NULL, task_populate_titledb_compare); }
} else {
task_free_titledb(item); strncpy(item->name, titledbInfo->meta.shortDescription, LIST_ITEM_NAME_MAX);
} item->data = titledbInfo;
} else {
free(item); task_populate_titledb_update_status(item);
res = R_FBI_OUT_OF_MEMORY; linked_list_add_sorted(&titles, item, NULL, task_populate_titledb_compare);
} } else {
} else { free(item);
res = R_FBI_OUT_OF_MEMORY;
} res = R_FBI_OUT_OF_MEMORY;
} }
} } else {
res = R_FBI_OUT_OF_MEMORY;
linked_list_iter iter; }
linked_list_iterate(&titles, &iter); }
}
while(linked_list_iter_has_next(&iter)) {
list_item* item = linked_list_iter_next(&iter); linked_list_iter iter;
linked_list_iterate(&titles, &iter);
if(R_SUCCEEDED(res)) {
linked_list_add(data->items, item); while(linked_list_iter_has_next(&iter)) {
} else { list_item* item = linked_list_iter_next(&iter);
task_free_titledb(item);
} if(R_SUCCEEDED(res)) {
} linked_list_add(data->items, item);
} else {
linked_list_destroy(&titles); task_free_titledb(item);
} else { }
res = R_FBI_BAD_DATA; }
}
linked_list_destroy(&titles);
json_value_free(json); } else {
} else { res = R_FBI_BAD_DATA;
res = R_FBI_PARSE_FAILED; }
}
} json_decref(root);
}
free(text);
} else { data->itemsListed = true;
res = R_FBI_OUT_OF_MEMORY;
} if(R_SUCCEEDED(res)) {
linked_list_iter iter;
data->itemsListed = true; linked_list_iterate(data->items, &iter);
if(R_SUCCEEDED(res)) { while(linked_list_iter_has_next(&iter)) {
linked_list_iter iter; svcWaitSynchronization(task_get_pause_event(), U64_MAX);
linked_list_iterate(data->items, &iter); if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) {
break;
while(linked_list_iter_has_next(&iter)) { }
svcWaitSynchronization(task_get_pause_event(), U64_MAX);
if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) { list_item* item = (list_item*) linked_list_iter_next(&iter);
break; titledb_info* titledbInfo = (titledb_info*) item->data;
}
char url[128];
list_item* item = (list_item*) linked_list_iter_next(&iter); if(titledbInfo->cia.exists) {
titledb_info* titledbInfo = (titledb_info*) item->data; snprintf(url, sizeof(url), "https://3ds.titledb.com/v1/cia/%lu/icon_l.bin", titledbInfo->cia.id);
} else if(titledbInfo->tdsx.exists && titledbInfo->tdsx.smdh.exists) {
char url[128]; snprintf(url, sizeof(url), "https://3ds.titledb.com/v1/smdh/%lu/icon_l.bin", titledbInfo->tdsx.smdh.id);
snprintf(url, sizeof(url), "https://3ds.titledb.com/v1/cia/%lu/icon_l.bin", titledbInfo->id); } else {
continue;
u8 icon[0x1200]; }
u32 iconSize = 0;
if(R_SUCCEEDED(task_populate_titledb_download(&iconSize, &icon, sizeof(icon), url)) && iconSize == sizeof(icon)) { u8 icon[0x1200];
titledbInfo->meta.texture = screen_allocate_free_texture(); u32 iconSize = 0;
screen_load_texture_tiled(titledbInfo->meta.texture, icon, sizeof(icon), 48, 48, GPU_RGB565, false); if(R_SUCCEEDED(util_download(url, &iconSize, &icon, sizeof(icon))) && iconSize == sizeof(icon)) {
} titledbInfo->meta.texture = screen_allocate_free_texture();
} screen_load_texture_tiled(titledbInfo->meta.texture, icon, sizeof(icon), 48, 48, GPU_RGB565, false);
} }
}
svcCloseHandle(data->cancelEvent); }
data->result = res; svcCloseHandle(data->cancelEvent);
data->finished = true;
} data->result = res;
data->finished = true;
void task_free_titledb(list_item* item) { }
if(item == NULL) {
return; 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) { if(item->data != NULL) {
screen_unload_texture(titledbInfo->meta.texture); titledb_info* titledbInfo = (titledb_info*) item->data;
titledbInfo->meta.texture = 0; if(titledbInfo->meta.texture != 0) {
} screen_unload_texture(titledbInfo->meta.texture);
titledbInfo->meta.texture = 0;
free(item->data); }
}
free(item->data);
free(item); }
}
free(item);
void task_clear_titledb(linked_list* items) { }
if(items == NULL) {
return; void task_clear_titledb(linked_list* items) {
} if(items == NULL) {
return;
linked_list_iter iter; }
linked_list_iterate(items, &iter);
linked_list_iter iter;
while(linked_list_iter_has_next(&iter)) { linked_list_iterate(items, &iter);
list_item* item = (list_item*) linked_list_iter_next(&iter);
while(linked_list_iter_has_next(&iter)) {
linked_list_iter_remove(&iter); list_item* item = (list_item*) linked_list_iter_next(&iter);
task_free_titledb(item);
} 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; 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->itemsListed = false; task_clear_titledb(data->items);
data->finished = false;
data->result = 0; data->itemsListed = false;
data->cancelEvent = 0; data->finished = false;
data->result = 0;
Result res = 0; data->cancelEvent = 0;
if(R_SUCCEEDED(res = svcCreateEvent(&data->cancelEvent, RESET_STICKY))) {
if(threadCreate(task_populate_titledb_thread, data, 0x10000, 0x19, 1, true) == NULL) { Result res = 0;
res = R_FBI_THREAD_CREATE_FAILED; 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->itemsListed = true;
data->finished = true; if(R_FAILED(res)) {
data->itemsListed = true;
if(data->cancelEvent != 0) { data->finished = true;
svcCloseHandle(data->cancelEvent);
data->cancelEvent = 0; if(data->cancelEvent != 0) {
} svcCloseHandle(data->cancelEvent);
} data->cancelEvent = 0;
}
return res; }
return res;
} }

View File

@ -7,7 +7,6 @@
#include "task.h" #include "task.h"
#include "../../list.h" #include "../../list.h"
#include "../../error.h"
#include "../../../core/linkedlist.h" #include "../../../core/linkedlist.h"
#include "../../../core/screen.h" #include "../../../core/screen.h"
#include "../../../core/util.h" #include "../../../core/util.h"

View File

@ -1,275 +1,305 @@
#pragma once #pragma once
#define FILE_NAME_MAX 512 #define FILE_NAME_MAX 512
#define FILE_PATH_MAX 512 #define FILE_PATH_MAX 512
typedef struct linked_list_s linked_list; typedef struct linked_list_s linked_list;
typedef struct list_item_s list_item; typedef struct list_item_s list_item;
typedef struct ui_view_s ui_view; typedef struct ui_view_s ui_view;
typedef struct meta_info_s { typedef struct meta_info_s {
char shortDescription[0x100]; char shortDescription[0x100];
char longDescription[0x200]; char longDescription[0x200];
char publisher[0x100]; char publisher[0x100];
u32 region; u32 region;
u32 texture; u32 texture;
} meta_info; } meta_info;
typedef struct title_info_s { typedef struct title_info_s {
FS_MediaType mediaType; FS_MediaType mediaType;
u64 titleId; u64 titleId;
char productCode[0x10]; char productCode[0x10];
u16 version; u16 version;
u64 installedSize; u64 installedSize;
bool twl; bool twl;
bool hasMeta; bool hasMeta;
meta_info meta; meta_info meta;
} title_info; } title_info;
typedef struct pending_title_info_s { typedef struct pending_title_info_s {
FS_MediaType mediaType; FS_MediaType mediaType;
u64 titleId; u64 titleId;
u16 version; u16 version;
} pending_title_info; } pending_title_info;
typedef struct ticket_info_s { typedef struct ticket_info_s {
u64 titleId; u64 titleId;
bool inUse; bool inUse;
} ticket_info; } ticket_info;
typedef struct ext_save_data_info_s { typedef struct ext_save_data_info_s {
FS_MediaType mediaType; FS_MediaType mediaType;
u64 extSaveDataId; u64 extSaveDataId;
bool shared; bool shared;
bool hasMeta; bool hasMeta;
meta_info meta; meta_info meta;
} ext_save_data_info; } ext_save_data_info;
typedef struct system_save_data_info_s { typedef struct system_save_data_info_s {
u32 systemSaveDataId; u32 systemSaveDataId;
} system_save_data_info; } system_save_data_info;
typedef struct cia_info_s { typedef struct cia_info_s {
u64 titleId; u64 titleId;
u16 version; u16 version;
u64 installedSize; u64 installedSize;
bool hasMeta; bool hasMeta;
meta_info meta; meta_info meta;
} cia_info; } cia_info;
typedef struct file_info_s { typedef struct file_info_s {
FS_Archive archive; FS_Archive archive;
char name[FILE_NAME_MAX]; char name[FILE_NAME_MAX];
char path[FILE_PATH_MAX]; char path[FILE_PATH_MAX];
u32 attributes; u32 attributes;
// Files only // Files only
u64 size; u64 size;
bool isCia; bool isCia;
cia_info ciaInfo; cia_info ciaInfo;
bool isTicket; bool isTicket;
ticket_info ticketInfo; ticket_info ticketInfo;
} file_info; } file_info;
typedef struct titledb_info_s { typedef struct titledb_cia_info_s {
u32 id; bool exists;
u64 titleId;
char version[32]; u32 id;
u16 installedVersion; char updatedAt[32];
u64 size; char version[32];
char updatedAt[32]; u64 size;
bool installed; u64 titleId;
meta_info meta;
} titledb_info; bool installed;
u16 installedVersion;
typedef enum capture_cam_camera_e { } titledb_cia_info;
CAMERA_OUTER,
CAMERA_INNER typedef struct titledb_smdh_info_s {
} capture_cam_camera; bool exists;
typedef struct capture_cam_data_s { u32 id;
u16* buffer; } titledb_smdh_info;
s16 width;
s16 height; typedef struct titledb_tdsx_info_s {
capture_cam_camera camera; bool exists;
Handle mutex; u32 id;
char updatedAt[32];
volatile bool finished; char version[32];
Result result; u64 size;
Handle cancelEvent; titledb_smdh_info smdh;
} capture_cam_data;
bool installed;
typedef enum data_op_e { } titledb_tdsx_info;
DATAOP_COPY,
DATAOP_DELETE typedef struct titledb_info_s {
} data_op; u32 id;
char category[64];
typedef struct data_op_data_s { char headline[512];
void* data; titledb_cia_info cia;
titledb_tdsx_info tdsx;
data_op op;
meta_info meta;
// Copy } titledb_info;
u32 copyBufferSize;
bool copyEmpty; typedef enum capture_cam_camera_e {
CAMERA_OUTER,
u32 copyBytesPerSecond; CAMERA_INNER
u32 estimatedRemainingSeconds; } capture_cam_camera;
u32 processed; typedef struct capture_cam_data_s {
u32 total; u16* buffer;
s16 width;
u64 currProcessed; s16 height;
u64 currTotal; capture_cam_camera camera;
Result (*isSrcDirectory)(void* data, u32 index, bool* isDirectory); Handle mutex;
Result (*makeDstDirectory)(void* data, u32 index);
volatile bool finished;
Result (*openSrc)(void* data, u32 index, u32* handle); Result result;
Result (*closeSrc)(void* data, u32 index, bool succeeded, u32 handle); Handle cancelEvent;
} capture_cam_data;
Result (*getSrcSize)(void* data, u32 handle, u64* size);
Result (*readSrc)(void* data, u32 handle, u32* bytesRead, void* buffer, u64 offset, u32 size); typedef enum data_op_e {
DATAOP_COPY,
Result (*openDst)(void* data, u32 index, void* initialReadBlock, u64 size, u32* handle); DATAOP_DELETE
Result (*closeDst)(void* data, u32 index, bool succeeded, u32 handle); } data_op;
Result (*writeDst)(void* data, u32 handle, u32* bytesWritten, void* buffer, u64 offset, u32 size); typedef struct data_op_data_s {
void* data;
Result (*suspendCopy)(void* data, u32 index, u32* srcHandle, u32* dstHandle);
Result (*restoreCopy)(void* data, u32 index, u32* srcHandle, u32* dstHandle); data_op op;
// Delete // Copy
Result (*delete)(void* data, u32 index); u32 copyBufferSize;
bool copyEmpty;
// Suspend
Result (*suspend)(void* data, u32 index); u32 copyBytesPerSecond;
Result (*restore)(void* data, u32 index); u32 estimatedRemainingSeconds;
// Errors u32 processed;
bool (*error)(void* data, u32 index, Result res, ui_view** errorView); u32 total;
// General u64 currProcessed;
volatile bool finished; u64 currTotal;
Result result;
Handle cancelEvent; Result (*isSrcDirectory)(void* data, u32 index, bool* isDirectory);
Result (*makeDstDirectory)(void* data, u32 index);
// Internal
volatile bool retryResponse; Result (*openSrc)(void* data, u32 index, u32* handle);
} data_op_data; Result (*closeSrc)(void* data, u32 index, bool succeeded, u32 handle);
typedef struct populate_ext_save_data_data_s { Result (*getSrcSize)(void* data, u32 handle, u64* size);
linked_list* items; Result (*readSrc)(void* data, u32 handle, u32* bytesRead, void* buffer, u64 offset, u32 size);
void* userData; Result (*openDst)(void* data, u32 index, void* initialReadBlock, u64 size, u32* handle);
bool (*filter)(void* data, u64 extSaveDataId, FS_MediaType mediaType); Result (*closeDst)(void* data, u32 index, bool succeeded, u32 handle);
int (*compare)(void* data, const void* p1, const void* p2);
Result (*writeDst)(void* data, u32 handle, u32* bytesWritten, void* buffer, u64 offset, u32 size);
volatile bool finished;
Result result; Result (*suspendCopy)(void* data, u32 index, u32* srcHandle, u32* dstHandle);
Handle cancelEvent; Result (*restoreCopy)(void* data, u32 index, u32* srcHandle, u32* dstHandle);
} populate_ext_save_data_data;
// Delete
typedef struct populate_files_data_s { Result (*delete)(void* data, u32 index);
linked_list* items;
// Suspend
FS_Archive archive; Result (*suspend)(void* data, u32 index);
char path[FILE_PATH_MAX]; Result (*restore)(void* data, u32 index);
bool recursive; // Errors
bool includeBase; bool (*error)(void* data, u32 index, Result res, ui_view** errorView);
bool (*filter)(void* data, const char* name, u32 attributes); // General
void* filterData; volatile bool finished;
Result result;
volatile bool finished; Handle cancelEvent;
Result result;
Handle cancelEvent; // Internal
} populate_files_data; volatile bool retryResponse;
} data_op_data;
typedef struct populate_pending_titles_data_s {
linked_list* items; typedef struct populate_ext_save_data_data_s {
linked_list* items;
volatile bool finished;
Result result; void* userData;
Handle cancelEvent; bool (*filter)(void* data, u64 extSaveDataId, FS_MediaType mediaType);
} populate_pending_titles_data; int (*compare)(void* data, const void* p1, const void* p2);
typedef struct populate_system_save_data_data_s { volatile bool finished;
linked_list* items; Result result;
Handle cancelEvent;
volatile bool finished; } populate_ext_save_data_data;
Result result;
Handle cancelEvent; typedef struct populate_files_data_s {
} populate_system_save_data_data; linked_list* items;
typedef struct populate_tickets_data_s { FS_Archive archive;
linked_list* items; char path[FILE_PATH_MAX];
volatile bool finished; bool recursive;
Result result; bool includeBase;
Handle cancelEvent;
} populate_tickets_data; bool (*filter)(void* data, const char* name, u32 attributes);
void* filterData;
typedef struct populate_titles_data_s {
linked_list* items; volatile bool finished;
Result result;
void* userData; Handle cancelEvent;
bool (*filter)(void* data, u64 titleId, FS_MediaType mediaType); } populate_files_data;
int (*compare)(void* data, const void* p1, const void* p2);
typedef struct populate_pending_titles_data_s {
volatile bool finished; linked_list* items;
Result result;
Handle cancelEvent; volatile bool finished;
} populate_titles_data; Result result;
Handle cancelEvent;
typedef struct populate_titledb_data_s { } populate_pending_titles_data;
linked_list* items;
typedef struct populate_system_save_data_data_s {
volatile bool itemsListed; linked_list* items;
volatile bool finished;
Result result; volatile bool finished;
Handle cancelEvent; Result result;
} populate_titledb_data; Handle cancelEvent;
} populate_system_save_data_data;
void task_init();
void task_exit(); typedef struct populate_tickets_data_s {
bool task_is_quit_all(); linked_list* items;
Handle task_get_pause_event();
Handle task_get_suspend_event(); volatile bool finished;
Result result;
Result task_capture_cam(capture_cam_data* data); Handle cancelEvent;
} populate_tickets_data;
Result task_data_op(data_op_data* data);
typedef struct populate_titles_data_s {
void task_free_ext_save_data(list_item* item); linked_list* items;
void task_clear_ext_save_data(linked_list* items);
Result task_populate_ext_save_data(populate_ext_save_data_data* data); void* userData;
bool (*filter)(void* data, u64 titleId, FS_MediaType mediaType);
void task_free_file(list_item* item); int (*compare)(void* data, const void* p1, const void* p2);
void task_clear_files(linked_list* items);
Result task_create_file_item(list_item** out, FS_Archive archive, const char* path, u32 attributes); volatile bool finished;
Result task_populate_files(populate_files_data* data); Result result;
Handle cancelEvent;
void task_free_pending_title(list_item* item); } populate_titles_data;
void task_clear_pending_titles(linked_list* items);
Result task_populate_pending_titles(populate_pending_titles_data* data); typedef struct populate_titledb_data_s {
linked_list* items;
void task_free_system_save_data(list_item* item);
void task_clear_system_save_data(linked_list* items); volatile bool itemsListed;
Result task_populate_system_save_data(populate_system_save_data_data* data); volatile bool finished;
Result result;
void task_populate_tickets_update_use(list_item* item); Handle cancelEvent;
void task_free_ticket(list_item* item); } populate_titledb_data;
void task_clear_tickets(linked_list* items);
Result task_populate_tickets(populate_tickets_data* data); void task_init();
void task_exit();
void task_free_title(list_item* item); bool task_is_quit_all();
void task_clear_titles(linked_list* items); Handle task_get_pause_event();
Result task_populate_titles(populate_titles_data* data); Handle task_get_suspend_event();
void task_populate_titledb_update_status(list_item* item); Result task_capture_cam(capture_cam_data* data);
void task_free_titledb(list_item* item);
void task_clear_titledb(linked_list* items); Result task_data_op(data_op_data* data);
void task_free_ext_save_data(list_item* item);
void task_clear_ext_save_data(linked_list* items);
Result task_populate_ext_save_data(populate_ext_save_data_data* data);
void task_free_file(list_item* item);
void task_clear_files(linked_list* items);
Result task_create_file_item(list_item** out, FS_Archive archive, const char* path, u32 attributes);
Result task_populate_files(populate_files_data* data);
void task_free_pending_title(list_item* item);
void task_clear_pending_titles(linked_list* items);
Result task_populate_pending_titles(populate_pending_titles_data* data);
void task_free_system_save_data(list_item* item);
void task_clear_system_save_data(linked_list* items);
Result task_populate_system_save_data(populate_system_save_data_data* data);
void task_populate_tickets_update_use(list_item* item);
void task_free_ticket(list_item* item);
void task_clear_tickets(linked_list* items);
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);
void task_populate_titledb_update_status(list_item* item);
void task_free_titledb(list_item* item);
void task_clear_titledb(linked_list* items);
Result task_populate_titledb(populate_titledb_data* data); Result task_populate_titledb(populate_titledb_data* data);

View File

@ -1,151 +1,243 @@
#include <malloc.h> #include <malloc.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <3ds.h>
#include <3ds.h>
#include "section.h"
#include "action/action.h" #include "section.h"
#include "task/task.h" #include "action/action.h"
#include "../error.h" #include "task/task.h"
#include "../list.h" #include "../error.h"
#include "../ui.h" #include "../list.h"
#include "../../core/linkedlist.h" #include "../ui.h"
#include "../../core/screen.h" #include "../../core/linkedlist.h"
#include "../../core/screen.h"
static list_item install = {"Install", COLOR_TEXT, action_install_titledb};
static list_item install = {"Install", COLOR_TEXT, action_install_titledb};
typedef struct {
populate_titledb_data populateData; typedef struct {
populate_titledb_data populateData;
bool populated;
} titledb_data; bool populated;
} titledb_data;
typedef struct {
linked_list* items; typedef struct {
list_item* selected; linked_list* items;
} titledb_action_data; list_item* selected;
} titledb_entry_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); typedef struct {
} linked_list* items;
list_item* selected;
static void titledb_action_update(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched) { bool cia;
titledb_action_data* actionData = (titledb_action_data*) data; } titledb_action_data;
if(hidKeysDown() & KEY_B) { static void titledb_action_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
ui_pop(); titledb_action_data* actionData = (titledb_action_data*) data;
list_destroy(view);
if(actionData->cia) {
free(data); ui_draw_titledb_info_cia(view, actionData->selected->data, x1, y1, x2, y2);
} else {
return; ui_draw_titledb_info_tdsx(view, actionData->selected->data, x1, y1, x2, y2);
} }
}
if(selected != NULL && selected->data != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) {
void(*action)(linked_list*, list_item*) = (void(*)(linked_list*, list_item*)) selected->data; 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;
ui_pop();
list_destroy(view); if(hidKeysDown() & KEY_B) {
ui_pop();
action(actionData->items, actionData->selected); list_destroy(view);
free(data); free(data);
return; return;
} }
if(linked_list_size(items) == 0) { if(selected != NULL && selected->data != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) {
linked_list_add(items, &install); void(*action)(linked_list*, list_item*, bool) = (void(*)(linked_list*, list_item*, bool)) selected->data;
}
} ui_pop();
list_destroy(view);
static void titledb_action_open(linked_list* items, list_item* selected) {
titledb_action_data* data = (titledb_action_data*) calloc(1, sizeof(titledb_action_data)); action(actionData->items, actionData->selected, actionData->cia);
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate TitleDB action data."); free(data);
return; return;
} }
data->items = items; if(linked_list_size(items) == 0) {
data->selected = selected; linked_list_add(items, &install);
}
list_display("TitleDB Action", "A: Select, B: Return", data, titledb_action_update, titledb_action_draw_top); }
}
static void titledb_action_open(linked_list* items, list_item* selected, bool cia) {
static void titledb_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) { titledb_action_data* data = (titledb_action_data*) calloc(1, sizeof(titledb_action_data));
titledb_data* listData = (titledb_data*) data; if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate TitleDB action data.");
if(!listData->populateData.itemsListed) {
static const char* text = "Loading title list, please wait...\nNOTE: Cancelling may take up to 15 seconds."; return;
}
float textWidth;
float textHeight; data->items = items;
screen_get_string_size(&textWidth, &textHeight, text, 0.5f, 0.5f); data->selected = selected;
screen_draw_string(text, x1 + (x2 - x1 - textWidth) / 2, y1 + (y2 - y1 - textHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); data->cia = cia;
} else if(selected != NULL && selected->data != NULL) {
ui_draw_titledb_info(view, selected->data, x1, y1, x2, y2); list_display("TitleDB Action", "A: Select, B: Return", data, titledb_action_update, titledb_action_draw_top);
} }
}
static void titledb_entry_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
static void titledb_update(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched) { titledb_entry_data* entryData = (titledb_entry_data*) data;
titledb_data* listData = (titledb_data*) data;
if(selected != NULL) {
if(hidKeysDown() & KEY_B) { if(strncmp(selected->name, "CIA", sizeof(selected->name)) == 0) {
if(!listData->populateData.finished) { ui_draw_titledb_info_cia(view, entryData->selected->data, x1, y1, x2, y2);
svcSignalEvent(listData->populateData.cancelEvent); } else if(strncmp(selected->name, "3DSX", sizeof(selected->name)) == 0) {
while(!listData->populateData.finished) { ui_draw_titledb_info_tdsx(view, entryData->selected->data, x1, y1, x2, y2);
svcSleepThread(1000000); }
} }
} }
ui_pop(); static void titledb_entry_update(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched) {
titledb_entry_data* entryData = (titledb_entry_data*) data;
task_clear_titledb(items);
list_destroy(view); if(hidKeysDown() & KEY_B) {
ui_pop();
free(listData);
return; linked_list_iter iter;
} linked_list_iterate(items, &iter);
if(!listData->populated || (hidKeysDown() & KEY_X)) { while(linked_list_iter_has_next(&iter)) {
if(!listData->populateData.finished) { free(linked_list_iter_next(&iter));
svcSignalEvent(listData->populateData.cancelEvent); linked_list_iter_remove(&iter);
while(!listData->populateData.finished) { }
svcSleepThread(1000000);
} list_destroy(view);
} free(data);
listData->populateData.items = items; return;
Result res = task_populate_titledb(&listData->populateData); }
if(R_FAILED(res)) {
error_display_res(NULL, NULL, res, "Failed to initiate TitleDB list population."); if(selected != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) {
} titledb_action_open(entryData->items, entryData->selected, (bool) selected->data);
return;
listData->populated = true; }
}
if(linked_list_size(items) == 0) {
if(listData->populateData.finished && R_FAILED(listData->populateData.result)) { titledb_info* info = (titledb_info*) entryData->selected->data;
error_display_res(NULL, NULL, listData->populateData.result, "Failed to populate TitleDB list.");
if(info->cia.exists) {
listData->populateData.result = 0; list_item* item = (list_item*) calloc(1, sizeof(list_item));
} if(item != NULL) {
strncpy(item->name, "CIA", sizeof(item->name));
if(selected != NULL && selected->data != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) { item->data = (void*) true;
titledb_action_open(items, selected); item->color = info->cia.installed ? COLOR_TITLEDB_INSTALLED : COLOR_TITLEDB_NOT_INSTALLED;
return;
} linked_list_add(items, item);
} }
}
void titledb_open() {
titledb_data* data = (titledb_data*) calloc(1, sizeof(titledb_data)); if(info->tdsx.exists) {
if(data == NULL) { list_item* item = (list_item*) calloc(1, sizeof(list_item));
error_display(NULL, NULL, "Failed to allocate TitleDB data."); if(item != NULL) {
strncpy(item->name, "3DSX", sizeof(item->name));
return; item->data = (void*) false;
} item->color = info->tdsx.installed ? COLOR_TITLEDB_INSTALLED : COLOR_TITLEDB_NOT_INSTALLED;
data->populateData.finished = true; linked_list_add(items, item);
}
list_display("TitleDB.com", "A: Select, B: Return, X: Refresh", data, titledb_update, titledb_draw_top); }
}
}
static void titledb_entry_open(linked_list* items, list_item* selected) {
titledb_entry_data* data = (titledb_entry_data*) calloc(1, sizeof(titledb_entry_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate TitleDB entry data.");
return;
}
data->items = items;
data->selected = selected;
list_display("TitleDB Entry", "A: Select, B: Return", data, titledb_entry_update, titledb_entry_draw_top);
}
static void titledb_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
titledb_data* listData = (titledb_data*) data;
if(!listData->populateData.itemsListed) {
static const char* text = "Loading title list, please wait...\nNOTE: Cancelling may take up to 15 seconds.";
float textWidth;
float textHeight;
screen_get_string_size(&textWidth, &textHeight, text, 0.5f, 0.5f);
screen_draw_string(text, x1 + (x2 - x1 - textWidth) / 2, y1 + (y2 - y1 - textHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true);
} else 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_entry_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

@ -3,6 +3,7 @@
#include <string.h> #include <string.h>
#include <3ds.h> #include <3ds.h>
#include <jansson.h>
#include "section.h" #include "section.h"
#include "action/action.h" #include "action/action.h"
@ -12,7 +13,6 @@
#include "../ui.h" #include "../ui.h"
#include "../../core/screen.h" #include "../../core/screen.h"
#include "../../core/util.h" #include "../../core/util.h"
#include "../../json/json.h"
static void update_check_update(ui_view* view, void* data, float* progress, char* text) { static void update_check_update(ui_view* view, void* data, float* progress, char* text) {
bool hasUpdate = false; bool hasUpdate = false;
@ -21,90 +21,49 @@ static void update_check_update(ui_view* view, void* data, float* progress, char
Result res = 0; Result res = 0;
u32 responseCode = 0; u32 responseCode = 0;
httpcContext context; json_t* json = NULL;
if(R_SUCCEEDED(res = util_http_open(&context, &responseCode, "https://api.github.com/repos/Steveice10/FBI/releases/latest", true))) { if(R_SUCCEEDED(res = util_download_json("https://api.github.com/repos/Steveice10/FBI/releases/latest", &json, 16 * 1024))) {
u32 size = 0; if(json_is_object(json)) {
if(R_SUCCEEDED(res = util_http_get_size(&context, &size))) { json_t* name = json_object_get(json, "name");
char* jsonText = (char*) calloc(sizeof(char), size); json_t* assets = json_object_get(json, "assets");
if(jsonText != NULL) {
u32 bytesRead = 0;
if(R_SUCCEEDED(res = util_http_read(&context, &bytesRead, (u8*) jsonText, size))) {
json_value* json = json_parse(jsonText, 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++) { if(json_is_string(name) && json_is_array(assets)) {
json_value* val = json->u.object.values[i].value; char versionString[16];
if(strncmp(json->u.object.values[i].name, "name", json->u.object.values[i].name_length) == 0 && val->type == json_string) { snprintf(versionString, sizeof(versionString), "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
name = val;
} else if(strncmp(json->u.object.values[i].name, "assets", json->u.object.values[i].name_length) == 0 && val->type == json_array) { if(strncmp(json_string_value(name), versionString, json_string_length(name)) != 0) {
assets = val; const char* url = NULL;
for(u32 i = 0; i < json_array_size(assets); i++) {
json_t* val = json_array_get(assets, i);
if(json_is_object(val)) {
json_t* assetName = json_object_get(val, "name");
json_t* assetUrl = json_object_get(val, "browser_download_url");
if(json_is_string(assetName) && json_is_string(assetUrl)) {
if(strncmp(json_string_value(assetName), util_get_3dsx_path() != NULL ? "FBI.3dsx" : "FBI.cia", json_string_length(assetName)) == 0) {
url = json_string_value(assetUrl);
break;
} }
} }
if(name != NULL && assets != NULL) {
char versionString[16];
snprintf(versionString, sizeof(versionString), "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
if(strncmp(name->u.string.ptr, versionString, 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) {
if(strncmp(assetName->u.string.ptr, util_get_3dsx_path() != NULL ? "FBI.3dsx" : "FBI.cia", assetName->u.string.length) == 0) {
url = assetUrl->u.string.ptr;
break;
}
}
}
}
if(url != NULL) {
strncpy(updateURL, url, INSTALL_URL_MAX);
hasUpdate = true;
} else {
res = R_FBI_BAD_DATA;
}
}
} else {
res = R_FBI_BAD_DATA;
}
} else {
res = R_FBI_BAD_DATA;
} }
}
json_value_free(json); if(url != NULL) {
strncpy(updateURL, url, INSTALL_URL_MAX);
hasUpdate = true;
} else { } else {
res = R_FBI_PARSE_FAILED; res = R_FBI_BAD_DATA;
} }
} }
free(jsonText);
} else { } else {
res = R_FBI_OUT_OF_MEMORY; res = R_FBI_BAD_DATA;
} }
} else {
res = R_FBI_BAD_DATA;
} }
Result closeRes = util_http_close(&context); json_decref(json);
if(R_SUCCEEDED(res)) {
res = closeRes;
}
} }
ui_pop(); ui_pop();

File diff suppressed because it is too large Load Diff

View File

@ -31,4 +31,6 @@ void ui_draw_pending_title_info(ui_view* view, void* data, float x1, float y1, f
void ui_draw_system_save_data_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_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); void ui_draw_titledb_info(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_titledb_info_cia(ui_view* view, void* data, float x1, float y1, float x2, float y2);
void ui_draw_titledb_info_tdsx(ui_view* view, void* data, float x1, float y1, float x2, float y2);