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 #
ifeq ($(TARGET),3DS)
LIBRARY_DIRS += $(DEVKITPRO)/libctru
LIBRARIES += citro3d ctru
LIBRARY_DIRS += $(DEVKITPRO)/libctru $(DEVKITPRO)/portlibs/armv6k $(DEVKITPRO)/portlibs/3ds
LIBRARIES += jansson curl mbedtls mbedcrypto mbedx509 z citro3d ctru
PRODUCT_CODE := CTR-P-CFBI
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
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

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

View File

@ -1,7 +1,7 @@
# servefiles
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\]
- Supported file extensions: .cia, .tik, .cetk, .3dsx
# servefiles
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\]
- Supported file extensions: .cia, .tik, .cetk, .3dsx

View File

@ -1,15 +1,15 @@
#!/usr/bin/env python
# coding: utf-8 -*-
from setuptools import setup
setup(
name="servefiles",
version="2.4.12",
scripts=["servefiles.py", "sendurls.py"],
author="Steveice10",
author_email="Steveice10@gmail.com",
description="Simple Python script for serving local files to FBI's remote installer.",
license="MIT",
url="https://github.com/Steveice10/FBI/tree/master/servefiles"
)
#!/usr/bin/env python
# coding: utf-8 -*-
from setuptools import setup
setup(
name="servefiles",
version="2.4.12",
scripts=["servefiles.py", "sendurls.py"],
author="Steveice10",
author_email="Steveice10@gmail.com",
description="Simple Python script for serving local files to FBI's remote installer.",
license="MIT",
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 "spi.h"
#include "../ui/error.h"
#include "util.h"
/*
* Based on information from TWLSaveTool, by TuxSH.

View File

@ -5,9 +5,10 @@
#include <string.h>
#include <3ds.h>
#include <curl/curl.h>
#include <jansson.h>
#include "util.h"
#include "../ui/error.h"
#include "../ui/list.h"
#include "../ui/section/task/task.h"
#include "linkedlist.h"
@ -268,6 +269,92 @@ bool util_is_string_empty(const char* str) {
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) {
u32 *cmdbuf = getThreadCommandBuffer();
@ -313,15 +400,9 @@ Result util_import_seed(u32* responseCode, u64 titleId) {
char url[128];
snprintf(url, 128, "https://kagiya-ctr.cdn.nintendo.net/title/0x%016llX/ext_key?country=%s", titleId, regionStrings[region]);
httpcContext context;
if(R_SUCCEEDED(res = util_http_open(&context, responseCode, url, false))) {
u32 bytesRead = 0;
res = util_http_read(&context, &bytesRead, seed, sizeof(seed));
Result closeRes = util_http_close(&context);
if(R_SUCCEEDED(res)) {
res = closeRes;
}
u32 downloadedSize = 0;
if(R_SUCCEEDED(res = util_download(url, &downloadedSize, seed, sizeof(seed))) && downloadedSize != sizeof(seed)) {
res = R_FBI_BAD_DATA;
}
} else {
res = R_FBI_OUT_OF_RANGE;
@ -694,11 +775,7 @@ u16* util_select_bnr_title(BNR* bnr) {
return bnr->titles[systemLanguage];
}
#define HTTP_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)
#define HTTPC_TIMEOUT 15000000000
Result util_http_open(httpcContext* context, u32* responseCode, const char* url, bool userAgent) {
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)))
&& R_SUCCEEDED(res = httpcSetKeepAlive(context, HTTPC_KEEPALIVE_ENABLED))
&& 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) {
redirectCount++;
@ -816,7 +893,7 @@ Result util_http_read(httpcContext* context, u32* bytesRead, void* buffer, u32 s
u32 outPos = 0;
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;
u32 currPos = 0;
if(R_SUCCEEDED(posRes = httpcGetDownloadSizeState(context, &currPos, NULL))) {

View File

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

View File

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

View File

@ -1,18 +1,5 @@
#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;
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 (*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 <3ds.h>
#include "action.h"
#include "../task/task.h"
#include "../../error.h"
#include "../../list.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) {
ui_draw_titledb_info(view, ((list_item*) data)->data, x1, y1, x2, y2);
}
static void action_update_titledb_finished(void* data) {
task_populate_titledb_update_status((list_item*) data);
}
void action_install_titledb(linked_list* items, list_item* selected) {
titledb_info* info = (titledb_info*) selected->data;
char url[64];
snprintf(url, INSTALL_URL_MAX, "https://3ds.titledb.com/v1/cia/%lu/download", info->id);
action_install_url("Install the selected title from TitleDB?", url, NULL, selected, action_update_titledb_finished, action_install_titledb_draw_top);
#include <stdio.h>
#include <stdlib.h>
#include <3ds.h>
#include "action.h"
#include "../task/task.h"
#include "../../error.h"
#include "../../list.h"
#include "../../ui.h"
#include "../../../core/linkedlist.h"
#include "../../../core/util.h"
typedef struct {
list_item* selected;
bool cia;
} 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) {
install_titledb_data* installData = (install_titledb_data*) data;
if(installData->cia) {
ui_draw_titledb_info_cia(view, installData->selected->data, x1, y1, x2, y2);
} else {
ui_draw_titledb_info_tdsx(view, installData->selected->data, x1, y1, x2, y2);
}
}
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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <3ds.h>
#include <unistd.h>
#include "section.h"
#include "action/action.h"
@ -20,7 +20,7 @@
#include "../../core/linkedlist.h"
#include "../../core/screen.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) {
if(out == NULL || size == 0) {

View File

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

View File

@ -4,10 +4,10 @@
#include <3ds.h>
#include "task.h"
#include "../../error.h"
#include "../../prompt.h"
#include "../../ui.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) {
Result res = 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,151 +1,243 @@
#include <malloc.h>
#include <stdio.h>
#include <3ds.h>
#include "section.h"
#include "action/action.h"
#include "task/task.h"
#include "../error.h"
#include "../list.h"
#include "../ui.h"
#include "../../core/linkedlist.h"
#include "../../core/screen.h"
static list_item install = {"Install", COLOR_TEXT, action_install_titledb};
typedef struct {
populate_titledb_data populateData;
bool populated;
} titledb_data;
typedef struct {
linked_list* items;
list_item* selected;
} titledb_action_data;
static void titledb_action_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
ui_draw_titledb_info(view, ((titledb_action_data*) data)->selected->data, x1, y1, x2, y2);
}
static void titledb_action_update(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched) {
titledb_action_data* actionData = (titledb_action_data*) data;
if(hidKeysDown() & KEY_B) {
ui_pop();
list_destroy(view);
free(data);
return;
}
if(selected != NULL && selected->data != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) {
void(*action)(linked_list*, list_item*) = (void(*)(linked_list*, list_item*)) selected->data;
ui_pop();
list_destroy(view);
action(actionData->items, actionData->selected);
free(data);
return;
}
if(linked_list_size(items) == 0) {
linked_list_add(items, &install);
}
}
static void titledb_action_open(linked_list* items, list_item* selected) {
titledb_action_data* data = (titledb_action_data*) calloc(1, sizeof(titledb_action_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate TitleDB action data.");
return;
}
data->items = items;
data->selected = selected;
list_display("TitleDB Action", "A: Select, B: Return", data, titledb_action_update, titledb_action_draw_top);
}
static void titledb_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
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_action_open(items, selected);
return;
}
}
void titledb_open() {
titledb_data* data = (titledb_data*) calloc(1, sizeof(titledb_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate TitleDB data.");
return;
}
data->populateData.finished = true;
list_display("TitleDB.com", "A: Select, B: Return, X: Refresh", data, titledb_update, titledb_draw_top);
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <3ds.h>
#include "section.h"
#include "action/action.h"
#include "task/task.h"
#include "../error.h"
#include "../list.h"
#include "../ui.h"
#include "../../core/linkedlist.h"
#include "../../core/screen.h"
static list_item install = {"Install", COLOR_TEXT, action_install_titledb};
typedef struct {
populate_titledb_data populateData;
bool populated;
} titledb_data;
typedef struct {
linked_list* items;
list_item* selected;
} titledb_entry_data;
typedef struct {
linked_list* items;
list_item* selected;
bool cia;
} titledb_action_data;
static void titledb_action_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected) {
titledb_action_data* actionData = (titledb_action_data*) data;
if(actionData->cia) {
ui_draw_titledb_info_cia(view, actionData->selected->data, x1, y1, x2, y2);
} else {
ui_draw_titledb_info_tdsx(view, actionData->selected->data, x1, y1, x2, y2);
}
}
static void titledb_action_update(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched) {
titledb_action_data* actionData = (titledb_action_data*) data;
if(hidKeysDown() & KEY_B) {
ui_pop();
list_destroy(view);
free(data);
return;
}
if(selected != NULL && selected->data != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) {
void(*action)(linked_list*, list_item*, bool) = (void(*)(linked_list*, list_item*, bool)) selected->data;
ui_pop();
list_destroy(view);
action(actionData->items, actionData->selected, actionData->cia);
free(data);
return;
}
if(linked_list_size(items) == 0) {
linked_list_add(items, &install);
}
}
static void titledb_action_open(linked_list* items, list_item* selected, bool cia) {
titledb_action_data* data = (titledb_action_data*) calloc(1, sizeof(titledb_action_data));
if(data == NULL) {
error_display(NULL, NULL, "Failed to allocate TitleDB action data.");
return;
}
data->items = items;
data->selected = selected;
data->cia = cia;
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) {
titledb_entry_data* entryData = (titledb_entry_data*) data;
if(selected != NULL) {
if(strncmp(selected->name, "CIA", sizeof(selected->name)) == 0) {
ui_draw_titledb_info_cia(view, entryData->selected->data, x1, y1, x2, y2);
} else if(strncmp(selected->name, "3DSX", sizeof(selected->name)) == 0) {
ui_draw_titledb_info_tdsx(view, entryData->selected->data, x1, y1, x2, y2);
}
}
}
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;
if(hidKeysDown() & KEY_B) {
ui_pop();
linked_list_iter iter;
linked_list_iterate(items, &iter);
while(linked_list_iter_has_next(&iter)) {
free(linked_list_iter_next(&iter));
linked_list_iter_remove(&iter);
}
list_destroy(view);
free(data);
return;
}
if(selected != NULL && (selectedTouched || (hidKeysDown() & KEY_A))) {
titledb_action_open(entryData->items, entryData->selected, (bool) selected->data);
return;
}
if(linked_list_size(items) == 0) {
titledb_info* info = (titledb_info*) entryData->selected->data;
if(info->cia.exists) {
list_item* item = (list_item*) calloc(1, sizeof(list_item));
if(item != NULL) {
strncpy(item->name, "CIA", sizeof(item->name));
item->data = (void*) true;
item->color = info->cia.installed ? COLOR_TITLEDB_INSTALLED : COLOR_TITLEDB_NOT_INSTALLED;
linked_list_add(items, item);
}
}
if(info->tdsx.exists) {
list_item* item = (list_item*) calloc(1, sizeof(list_item));
if(item != NULL) {
strncpy(item->name, "3DSX", sizeof(item->name));
item->data = (void*) false;
item->color = info->tdsx.installed ? COLOR_TITLEDB_INSTALLED : COLOR_TITLEDB_NOT_INSTALLED;
linked_list_add(items, item);
}
}
}
}
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 <3ds.h>
#include <jansson.h>
#include "section.h"
#include "action/action.h"
@ -12,7 +13,6 @@
#include "../ui.h"
#include "../../core/screen.h"
#include "../../core/util.h"
#include "../../json/json.h"
static void update_check_update(ui_view* view, void* data, float* progress, char* text) {
bool hasUpdate = false;
@ -21,90 +21,49 @@ static void update_check_update(ui_view* view, void* data, float* progress, char
Result res = 0;
u32 responseCode = 0;
httpcContext context;
if(R_SUCCEEDED(res = util_http_open(&context, &responseCode, "https://api.github.com/repos/Steveice10/FBI/releases/latest", true))) {
u32 size = 0;
if(R_SUCCEEDED(res = util_http_get_size(&context, &size))) {
char* jsonText = (char*) calloc(sizeof(char), size);
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;
json_t* json = NULL;
if(R_SUCCEEDED(res = util_download_json("https://api.github.com/repos/Steveice10/FBI/releases/latest", &json, 16 * 1024))) {
if(json_is_object(json)) {
json_t* name = json_object_get(json, "name");
json_t* assets = json_object_get(json, "assets");
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(json_is_string(name) && json_is_array(assets)) {
char versionString[16];
snprintf(versionString, sizeof(versionString), "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
if(strncmp(json_string_value(name), versionString, json_string_length(name)) != 0) {
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 {
res = R_FBI_PARSE_FAILED;
res = R_FBI_BAD_DATA;
}
}
free(jsonText);
} else {
res = R_FBI_OUT_OF_MEMORY;
res = R_FBI_BAD_DATA;
}
} else {
res = R_FBI_BAD_DATA;
}
Result closeRes = util_http_close(&context);
if(R_SUCCEEDED(res)) {
res = closeRes;
}
json_decref(json);
}
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_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_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);