From 4e49914e1119de7687c68f7b81412939ffbd8d5e Mon Sep 17 00:00:00 2001 From: Steven Smith Date: Fri, 2 Feb 2018 18:33:07 -0800 Subject: [PATCH] Revamp TitleDB support, replace json library with jansson, initial work on replacing httpc with curl. --- Makefile | 4 +- README.md | 2 +- buildtools | 2 +- servefiles/README.md | 14 +- servefiles/setup.py | 30 +- source/core/screen.c | 1436 +++++++++---------- source/core/spi.c | 2 +- source/core/util.c | 111 +- source/core/util.h | 27 + source/json/LICENSE | 26 - source/json/json.c | 1011 ------------- source/json/json.h | 283 ---- source/{ => libs}/quirc/LICENSE | 0 source/{ => libs}/quirc/decode.c | 0 source/{ => libs}/quirc/identify.c | 0 source/{ => libs}/quirc/quirc.c | 0 source/{ => libs}/quirc/quirc.h | 0 source/{ => libs}/quirc/quirc_internal.h | 0 source/{ => libs}/quirc/version_db.c | 0 source/{ => libs}/stb_image/stb_image.c | 0 source/{ => libs}/stb_image/stb_image.h | 0 source/main.c | 385 ++--- source/ui/error.c | 8 + source/ui/error.h | 13 - source/ui/section/action/action.h | 2 +- source/ui/section/action/installtitledb.c | 86 +- source/ui/section/remoteinstall.c | 4 +- source/ui/section/task/capturecam.c | 2 +- source/ui/section/task/dataop.c | 2 +- source/ui/section/task/listextsavedata.c | 1 - source/ui/section/task/listfiles.c | 1 - source/ui/section/task/listpendingtitles.c | 1 - source/ui/section/task/listsystemsavedata.c | 1 - source/ui/section/task/listtickets.c | 1 - source/ui/section/task/listtitledb.c | 562 ++++---- source/ui/section/task/listtitles.c | 1 - source/ui/section/task/task.h | 578 ++++---- source/ui/section/titledb.c | 392 +++-- source/ui/section/update.c | 105 +- source/ui/ui.c | 1329 ++++++++--------- source/ui/ui.h | 4 +- 41 files changed, 2689 insertions(+), 3737 deletions(-) delete mode 100644 source/json/LICENSE delete mode 100644 source/json/json.c delete mode 100644 source/json/json.h rename source/{ => libs}/quirc/LICENSE (100%) rename source/{ => libs}/quirc/decode.c (100%) rename source/{ => libs}/quirc/identify.c (100%) rename source/{ => libs}/quirc/quirc.c (100%) rename source/{ => libs}/quirc/quirc.h (100%) rename source/{ => libs}/quirc/quirc_internal.h (100%) rename source/{ => libs}/quirc/version_db.c (100%) rename source/{ => libs}/stb_image/stb_image.c (100%) rename source/{ => libs}/stb_image/stb_image.h (100%) diff --git a/Makefile b/Makefile index d8e086c..e024ca0 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index c81b5e2..7412bbd 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/buildtools b/buildtools index 1ea00aa..adf5fa3 160000 --- a/buildtools +++ b/buildtools @@ -1 +1 @@ -Subproject commit 1ea00aaa369d80a825002e5d514dddff2305f88b +Subproject commit adf5fa3e8382a0a3847ec3f7dcff781417c8751c diff --git a/servefiles/README.md b/servefiles/README.md index 9308ea8..99903da 100644 --- a/servefiles/README.md +++ b/servefiles/README.md @@ -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 diff --git a/servefiles/setup.py b/servefiles/setup.py index aca5537..b65684e 100644 --- a/servefiles/setup.py +++ b/servefiles/setup.py @@ -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" +) diff --git a/source/core/screen.c b/source/core/screen.c index 13ab57f..aaef762 100644 --- a/source/core/screen.c +++ b/source/core/screen.c @@ -1,718 +1,718 @@ -#include -#include -#include -#include - -#include <3ds.h> -#include - -#include "../stb_image/stb_image.h" -#include "screen.h" -#include "util.h" - -#include "default_shbin.h" - -static bool c3d_initialized; - -static bool shader_initialized; -static DVLB_s* dvlb; -static shaderProgram_s program; - -static C3D_RenderTarget* target_top; -static C3D_RenderTarget* target_bottom; -static C3D_Mtx projection_top; -static C3D_Mtx projection_bottom; - -static C3D_Tex* glyph_sheets; -static float font_scale; - -static u8 base_alpha = 0xFF; - -static u32 color_config[MAX_COLORS] = {0xFF000000}; - -static struct { - bool allocated; - C3D_Tex tex; - u32 width; - u32 height; -} textures[MAX_TEXTURES]; - -static FILE* screen_open_resource(const char* path) { - u32 realPathSize = strlen(path) + 17; - char realPath[realPathSize]; - - snprintf(realPath, realPathSize, "sdmc:/fbi/theme/%s", path); - FILE* fd = fopen(realPath, "rb"); - - if(fd != NULL) { - return fd; - } else { - snprintf(realPath, realPathSize, "romfs:/%s", path); - - return fopen(realPath, "rb"); - } -} - -static void screen_set_blend(u32 color, bool rgb, bool alpha) { - C3D_TexEnv* env = C3D_GetTexEnv(0); - if(env == NULL) { - util_panic("Failed to retrieve combiner settings."); - return; - } - - if(rgb) { - C3D_TexEnvSrc(env, C3D_RGB, GPU_CONSTANT, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); - } else { - C3D_TexEnvSrc(env, C3D_RGB, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); - } - - if(alpha) { - C3D_TexEnvSrc(env, C3D_Alpha, GPU_TEXTURE0, GPU_CONSTANT, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Alpha, GPU_MODULATE); - } else { - C3D_TexEnvSrc(env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Alpha, GPU_REPLACE); - } - - C3D_TexEnvColor(env, color); -} - -void screen_init() { - if(!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE * 4)) { - util_panic("Failed to initialize the GPU."); - return; - } - - c3d_initialized = true; - - u32 displayFlags = GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO); - - target_top = C3D_RenderTargetCreate(TOP_SCREEN_HEIGHT, TOP_SCREEN_WIDTH, GPU_RB_RGB8, 0); - if(target_top == NULL) { - util_panic("Failed to initialize the top screen target."); - return; - } - - C3D_RenderTargetSetOutput(target_top, GFX_TOP, GFX_LEFT, displayFlags); - C3D_RenderTargetSetClear(target_top, C3D_CLEAR_ALL, 0, 0); - - target_bottom = C3D_RenderTargetCreate(BOTTOM_SCREEN_HEIGHT, BOTTOM_SCREEN_WIDTH, GPU_RB_RGB8, 0); - if(target_bottom == NULL) { - util_panic("Failed to initialize the bottom screen target."); - return; - } - - C3D_RenderTargetSetOutput(target_bottom, GFX_BOTTOM, GFX_LEFT, displayFlags); - C3D_RenderTargetSetClear(target_bottom, C3D_CLEAR_ALL, 0, 0); - - Mtx_OrthoTilt(&projection_top, 0.0, TOP_SCREEN_WIDTH, TOP_SCREEN_HEIGHT, 0.0, 0.0, 1.0, true); - Mtx_OrthoTilt(&projection_bottom, 0.0, BOTTOM_SCREEN_WIDTH, BOTTOM_SCREEN_HEIGHT, 0.0, 0.0, 1.0, true); - - dvlb = DVLB_ParseFile((u32*) default_shbin, default_shbin_len); - if(dvlb == NULL) { - util_panic("Failed to parse shader."); - return; - } - - Result progInitRes = shaderProgramInit(&program); - if(R_FAILED(progInitRes)) { - util_panic("Failed to initialize shader program: 0x%08lX", progInitRes); - return; - } - - shader_initialized = true; - - Result progSetVshRes = shaderProgramSetVsh(&program, &dvlb->DVLE[0]); - if(R_FAILED(progSetVshRes)) { - util_panic("Failed to set up vertex shader: 0x%08lX", progInitRes); - return; - } - - C3D_BindProgram(&program); - - C3D_AttrInfo* attrInfo = C3D_GetAttrInfo(); - if(attrInfo == NULL) { - util_panic("Failed to retrieve attribute info."); - return; - } - - AttrInfo_Init(attrInfo); - AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); - AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 2); - - C3D_DepthTest(true, GPU_GEQUAL, GPU_WRITE_ALL); - - screen_set_blend(0, false, false); - - Result fontMapRes = fontEnsureMapped(); - if(R_FAILED(fontMapRes)) { - util_panic("Failed to map system font: 0x%08lX", fontMapRes); - return; - } - - TGLP_s* glyphInfo = fontGetGlyphInfo(); - glyph_sheets = calloc(glyphInfo->nSheets, sizeof(C3D_Tex)); - if(glyph_sheets == NULL) { - util_panic("Failed to allocate font glyph texture data."); - return; - } - - for(int i = 0; i < glyphInfo->nSheets; i++) { - C3D_Tex* tex = &glyph_sheets[i]; - tex->data = fontGetGlyphSheetTex(i); - tex->fmt = (GPU_TEXCOLOR) glyphInfo->sheetFmt; - tex->size = glyphInfo->sheetSize; - tex->width = glyphInfo->sheetWidth; - tex->height = glyphInfo->sheetHeight; - tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE); - } - - font_scale = 30.0f / glyphInfo->cellHeight; // 30 is cellHeight in J machines - - FILE* fd = screen_open_resource("textcolor.cfg"); - if(fd == NULL) { - util_panic("Failed to open text color config: %s\n", strerror(errno)); - return; - } - - char line[128]; - while(fgets(line, sizeof(line), fd) != NULL) { - char key[64]; - u32 color = 0; - - sscanf(line, "%63[^=]=%lx", key, &color); - - if(strcasecmp(key, "text") == 0) { - color_config[COLOR_TEXT] = color; - } else if(strcasecmp(key, "nand") == 0) { - color_config[COLOR_NAND] = color; - } else if(strcasecmp(key, "sd") == 0) { - color_config[COLOR_SD] = color; - } else if(strcasecmp(key, "gamecard") == 0) { - color_config[COLOR_GAME_CARD] = color; - } else if(strcasecmp(key, "dstitle") == 0) { - color_config[COLOR_DS_TITLE] = color; - } else if(strcasecmp(key, "file") == 0) { - color_config[COLOR_FILE] = color; - } else if(strcasecmp(key, "directory") == 0) { - color_config[COLOR_DIRECTORY] = color; - } else if(strcasecmp(key, "enabled") == 0) { - color_config[COLOR_ENABLED] = color; - } else if(strcasecmp(key, "disabled") == 0) { - color_config[COLOR_DISABLED] = color; - } else if(strcasecmp(key, "titledbinstalled") == 0) { - color_config[COLOR_TITLEDB_INSTALLED] = color; - } else if(strcasecmp(key, "titledbnotinstalled") == 0) { - color_config[COLOR_TITLEDB_NOT_INSTALLED] = color; - } else if(strcasecmp(key, "ticketinuse") == 0) { - color_config[COLOR_TICKET_IN_USE] = color; - } else if(strcasecmp(key, "ticketnotinuse") == 0) { - color_config[COLOR_TICKET_NOT_IN_USE] = color; - } - } - - fclose(fd); - - screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_BG, "bottom_screen_bg.png", true); - screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_TOP_BAR, "bottom_screen_top_bar.png", true); - screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_TOP_BAR_SHADOW, "bottom_screen_top_bar_shadow.png", true); - screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR, "bottom_screen_bottom_bar.png", true); - screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR_SHADOW, "bottom_screen_bottom_bar_shadow.png", true); - screen_load_texture_file(TEXTURE_TOP_SCREEN_BG, "top_screen_bg.png", true); - screen_load_texture_file(TEXTURE_TOP_SCREEN_TOP_BAR, "top_screen_top_bar.png", true); - screen_load_texture_file(TEXTURE_TOP_SCREEN_TOP_BAR_SHADOW, "top_screen_top_bar_shadow.png", true); - screen_load_texture_file(TEXTURE_TOP_SCREEN_BOTTOM_BAR, "top_screen_bottom_bar.png", true); - screen_load_texture_file(TEXTURE_TOP_SCREEN_BOTTOM_BAR_SHADOW, "top_screen_bottom_bar_shadow.png", true); - screen_load_texture_file(TEXTURE_LOGO, "logo.png", true); - screen_load_texture_file(TEXTURE_SELECTION_OVERLAY, "selection_overlay.png", true); - screen_load_texture_file(TEXTURE_SCROLL_BAR, "scroll_bar.png", true); - screen_load_texture_file(TEXTURE_BUTTON, "button.png", true); - screen_load_texture_file(TEXTURE_PROGRESS_BAR_BG, "progress_bar_bg.png", true); - screen_load_texture_file(TEXTURE_PROGRESS_BAR_CONTENT, "progress_bar_content.png", true); - screen_load_texture_file(TEXTURE_META_INFO_BOX, "meta_info_box.png", true); - screen_load_texture_file(TEXTURE_META_INFO_BOX_SHADOW, "meta_info_box_shadow.png", true); - screen_load_texture_file(TEXTURE_BATTERY_CHARGING, "battery_charging.png", true); - screen_load_texture_file(TEXTURE_BATTERY_0, "battery0.png", true); - screen_load_texture_file(TEXTURE_BATTERY_1, "battery1.png", true); - screen_load_texture_file(TEXTURE_BATTERY_2, "battery2.png", true); - screen_load_texture_file(TEXTURE_BATTERY_3, "battery3.png", true); - screen_load_texture_file(TEXTURE_BATTERY_4, "battery4.png", true); - screen_load_texture_file(TEXTURE_BATTERY_5, "battery5.png", true); - screen_load_texture_file(TEXTURE_WIFI_DISCONNECTED, "wifi_disconnected.png", true); - screen_load_texture_file(TEXTURE_WIFI_0, "wifi0.png", true); - screen_load_texture_file(TEXTURE_WIFI_1, "wifi1.png", true); - screen_load_texture_file(TEXTURE_WIFI_2, "wifi2.png", true); - screen_load_texture_file(TEXTURE_WIFI_3, "wifi3.png", true); -} - -void screen_exit() { - for(u32 id = 0; id < MAX_TEXTURES; id++) { - screen_unload_texture(id); - } - - if(glyph_sheets != NULL) { - free(glyph_sheets); - glyph_sheets = NULL; - } - - if(shader_initialized) { - shaderProgramFree(&program); - shader_initialized = false; - } - - if(dvlb != NULL) { - DVLB_Free(dvlb); - dvlb = NULL; - } - - if(target_top != NULL) { - C3D_RenderTargetDelete(target_top); - target_top = NULL; - } - - if(target_bottom != NULL) { - C3D_RenderTargetDelete(target_bottom); - target_bottom = NULL; - } - - if(c3d_initialized) { - C3D_Fini(); - c3d_initialized = false; - } -} - -void screen_set_base_alpha(u8 alpha) { - base_alpha = alpha; -} - -static u32 screen_next_pow_2(u32 i) { - i--; - i |= i >> 1; - i |= i >> 2; - i |= i >> 4; - i |= i >> 8; - i |= i >> 16; - i++; - - return i; -} - -u32 screen_allocate_free_texture() { - u32 id = 0; - for(u32 i = 1; i < MAX_TEXTURES; i++) { - if(!textures[i].allocated) { - textures[i].allocated = true; - - id = i; - break; - } - } - - if(id == 0) { - util_panic("Out of free textures."); - return 0; - } - - return id; -} - -static void screen_prepare_texture(u32* pow2WidthOut, u32* pow2HeightOut, u32 id, u32 width, u32 height, GPU_TEXCOLOR format, bool linearFilter) { - if(id >= MAX_TEXTURES) { - util_panic("Attempted to prepare invalid texture ID \"%lu\".", id); - return; - } - - u32 pow2Width = screen_next_pow_2(width); - if(pow2Width < 64) { - pow2Width = 64; - } - - u32 pow2Height = screen_next_pow_2(height); - if(pow2Height < 64) { - pow2Height = 64; - } - - if(textures[id].tex.data != NULL && (textures[id].tex.width != pow2Width || textures[id].tex.height != pow2Height || textures[id].tex.fmt != format)) { - C3D_TexDelete(&textures[id].tex); - textures[id].tex.data = NULL; - } - - if(textures[id].tex.data == NULL && !C3D_TexInit(&textures[id].tex, (u16) pow2Width, (u16) pow2Height, format)) { - util_panic("Failed to initialize texture with ID \"%lu\".", id); - return; - } - - C3D_TexSetFilter(&textures[id].tex, linearFilter ? GPU_LINEAR : GPU_NEAREST, GPU_NEAREST); - - textures[id].allocated = true; - textures[id].width = width; - textures[id].height = height; - - if(pow2WidthOut != NULL) { - *pow2WidthOut = pow2Width; - } - - if(pow2HeightOut != NULL) { - *pow2HeightOut = pow2Height; - } -} - -void screen_load_texture_tiled(u32 id, void* data, u32 size, u32 width, u32 height, GPU_TEXCOLOR format, bool linearFilter) { - u32 pow2Width = 0; - u32 pow2Height = 0; - screen_prepare_texture(&pow2Width, &pow2Height, id, width, height, format, linearFilter); - - if(width != pow2Width || height != pow2Height) { - u32 pixelSize = size / width / height; - - memset(textures[id].tex.data, 0, textures[id].tex.size); - for(u32 y = 0; y < height; y += 8) { - u32 dstPos = y * pow2Width * pixelSize; - u32 srcPos = y * width * pixelSize; - - memcpy(&((u8*) textures[id].tex.data)[dstPos], &((u8*) data)[srcPos], width * 8 * pixelSize); - } - } else { - memcpy(textures[id].tex.data, data, textures[id].tex.size); - } - - C3D_TexFlush(&textures[id].tex); -} - -void screen_load_texture_untiled(u32 id, void* data, u32 size, u32 width, u32 height, GPU_TEXCOLOR format, bool linearFilter) { - u32 pow2Width = 0; - u32 pow2Height = 0; - screen_prepare_texture(&pow2Width, &pow2Height, id, width, height, format, linearFilter); - - u32 pixelSize = size / width / height; - - memset(textures[id].tex.data, 0, textures[id].tex.size); - for(u32 x = 0; x < width; x++) { - for(u32 y = 0; y < height; y++) { - u32 dstPos = ((((y >> 3) * (pow2Width >> 3) + (x >> 3)) << 6) + ((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) | ((x & 4) << 2) | ((y & 4) << 3))) * pixelSize; - u32 srcPos = (y * width + x) * pixelSize; - - memcpy(&((u8*) textures[id].tex.data)[dstPos], &((u8*) data)[srcPos], pixelSize); - } - } - - C3D_TexFlush(&textures[id].tex); -} - -void screen_load_texture_file(u32 id, const char* path, bool linearFilter) { - if(id >= MAX_TEXTURES) { - util_panic("Attempted to load path \"%s\" to invalid texture ID \"%lu\".", path, id); - return; - } - - FILE* fd = screen_open_resource(path); - if(fd == NULL) { - util_panic("Failed to load PNG file \"%s\": %s", path, strerror(errno)); - return; - } - - int width; - int height; - int depth; - u8* image = stbi_load_from_file(fd, &width, &height, &depth, STBI_rgb_alpha); - fclose(fd); - - if(image == NULL || depth != STBI_rgb_alpha) { - util_panic("Failed to load PNG file \"%s\".", path); - return; - } - - for(u32 x = 0; x < width; x++) { - for(u32 y = 0; y < height; y++) { - u32 pos = (y * width + x) * 4; - - u8 c1 = image[pos + 0]; - u8 c2 = image[pos + 1]; - u8 c3 = image[pos + 2]; - u8 c4 = image[pos + 3]; - - image[pos + 0] = c4; - image[pos + 1] = c3; - image[pos + 2] = c2; - image[pos + 3] = c1; - } - } - - screen_load_texture_untiled(id, image, (u32) (width * height * 4), (u32) width, (u32) height, GPU_RGBA8, linearFilter); - - free(image); -} - -void screen_unload_texture(u32 id) { - if(id >= MAX_TEXTURES) { - util_panic("Attempted to unload invalid texture ID \"%lu\".", id); - return; - } - - C3D_TexDelete(&textures[id].tex); - textures[id].tex.data = NULL; - - textures[id].allocated = false; - textures[id].width = 0; - textures[id].height = 0; -} - -void screen_get_texture_size(u32* width, u32* height, u32 id) { - if(id >= MAX_TEXTURES) { - util_panic("Attempted to get size of invalid texture ID \"%lu\".", id); - return; - } - - if(width) { - *width = textures[id].width; - } - - if(height) { - *height = textures[id].height; - } -} - -void screen_begin_frame() { - if(!C3D_FrameBegin(C3D_FRAME_SYNCDRAW)) { - util_panic("Failed to begin frame."); - return; - } -} - -void screen_end_frame() { - C3D_FrameEnd(0); -} - -void screen_select(gfxScreen_t screen) { - if(!C3D_FrameDrawOn(screen == GFX_TOP ? target_top : target_bottom)) { - util_panic("Failed to select render target."); - return; - } - - C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, shaderInstanceGetUniformLocation(program.vertexShader, "projection"), screen == GFX_TOP ? &projection_top : &projection_bottom); -} - -static void screen_draw_quad(float x1, float y1, float x2, float y2, float left, float bottom, float right, float top) { - C3D_ImmDrawBegin(GPU_TRIANGLE_STRIP); - - C3D_ImmSendAttrib(x1, y2, 0.5f, 0.0f); - C3D_ImmSendAttrib(left, bottom, 0.0f, 0.0f); - - C3D_ImmSendAttrib(x2, y2, 0.5f, 0.0f); - C3D_ImmSendAttrib(right, bottom, 0.0f, 0.0f); - - C3D_ImmSendAttrib(x1, y1, 0.5f, 0.0f); - C3D_ImmSendAttrib(left, top, 0.0f, 0.0f); - - C3D_ImmSendAttrib(x2, y1, 0.5f, 0.0f); - C3D_ImmSendAttrib(right, top, 0.0f, 0.0f); - - C3D_ImmDrawEnd(); -} - -void screen_draw_texture(u32 id, float x, float y, float width, float height) { - if(id >= MAX_TEXTURES) { - util_panic("Attempted to draw invalid texture ID \"%lu\".", id); - return; - } - - if(textures[id].tex.data == NULL) { - return; - } - - if(base_alpha != 0xFF) { - screen_set_blend(base_alpha << 24, false, true); - } - - C3D_TexBind(0, &textures[id].tex); - screen_draw_quad(x, y, x + width, y + height, 0, (float) (textures[id].tex.height - textures[id].height) / (float) textures[id].tex.height, (float) textures[id].width / (float) textures[id].tex.width, 1.0f); - - if(base_alpha != 0xFF) { - screen_set_blend(0, false, false); - } -} - -void screen_draw_texture_crop(u32 id, float x, float y, float width, float height) { - if(id >= MAX_TEXTURES) { - util_panic("Attempted to draw invalid texture ID \"%lu\".", id); - return; - } - - if(textures[id].tex.data == NULL) { - return; - } - - if(base_alpha != 0xFF) { - screen_set_blend(base_alpha << 24, false, true); - } - - C3D_TexBind(0, &textures[id].tex); - screen_draw_quad(x, y, x + width, y + height, 0, (float) (textures[id].tex.height - textures[id].height) / (float) textures[id].tex.height, width / (float) textures[id].tex.width, (textures[id].tex.height - textures[id].height + height) / (float) textures[id].tex.height); - - if(base_alpha != 0xFF) { - screen_set_blend(0, false, false); - } -} - -float screen_get_font_height(float scaleY) { - return scaleY * fontGetInfo()->lineFeed; -} - -static void screen_get_string_size_internal(float* width, float* height, const char* text, float scaleX, float scaleY, bool oneLine, bool wrap, float wrapWidth) { - scaleX *= font_scale; - scaleY *= font_scale; - - float w = 0; - float h = 0; - float lineWidth = 0; - - if(text != NULL) { - h = scaleY * fontGetInfo()->lineFeed; - - const uint8_t* p = (const uint8_t*) text; - const uint8_t* lastAlign = p; - u32 code = 0; - ssize_t units = -1; - while(*p && (units = decode_utf8(&code, p)) != -1 && code > 0) { - p += units; - - if(code == '\n' || (wrap && lineWidth + scaleX * fontGetCharWidthInfo(fontGlyphIndexFromCodePoint(code))->charWidth >= wrapWidth)) { - lastAlign = p; - - if(lineWidth > w) { - w = lineWidth; - } - - lineWidth = 0; - - if(oneLine) { - break; - } - - h += scaleY * fontGetInfo()->lineFeed; - } - - if(code != '\n') { - u32 num = 1; - if(code == '\t') { - code = ' '; - num = 4 - (p - units - lastAlign) % 4; - - lastAlign = p; - } - - lineWidth += (scaleX * fontGetCharWidthInfo(fontGlyphIndexFromCodePoint(code))->charWidth) * num; - } - } - } - - if(width) { - *width = lineWidth > w ? lineWidth : w; - } - - if(height) { - *height = h; - } -} - -void screen_get_string_size(float* width, float* height, const char* text, float scaleX, float scaleY) { - screen_get_string_size_internal(width, height, text, scaleX, scaleY, false, false, 0); -} - -void screen_get_string_size_wrap(float* width, float* height, const char* text, float scaleX, float scaleY, float wrapWidth) { - screen_get_string_size_internal(width, height, text, scaleX, scaleY, false, true, wrapWidth); -} - -static void screen_draw_string_internal(const char* text, float x, float y, float scaleX, float scaleY, u32 colorId, bool centerLines, bool wrap, float wrapX) { - // Note: Do not just multiply scaleX and scaleY by font_scale, as they would then be double-scaled when passed into screen_get_string_size_internal. - - if(text == NULL) { - return; - } - - if(colorId >= MAX_COLORS) { - util_panic("Attempted to draw string with invalid color ID \"%lu\".", colorId); - return; - } - - u32 blendColor = color_config[colorId]; - if(base_alpha != 0xFF) { - float alpha1 = ((blendColor >> 24) & 0xFF) / 255.0f; - float alpha2 = base_alpha / 255.0f; - float blendedAlpha = alpha1 * alpha2; - - blendColor = (((u32) (blendedAlpha * 0xFF)) << 24) | (blendColor & 0x00FFFFFF); - } - - screen_set_blend(blendColor, true, true); - - float stringWidth; - screen_get_string_size_internal(&stringWidth, NULL, text, scaleX, scaleY, false, wrap, wrapX - x); - - float lineWidth; - screen_get_string_size_internal(&lineWidth, NULL, text, scaleX, scaleY, true, wrap, wrapX - x); - - float currX = x; - if(centerLines) { - currX += (stringWidth - lineWidth) / 2; - } - - int lastSheet = -1; - - const uint8_t* p = (const uint8_t*) text; - const uint8_t* lastAlign = p; - u32 code = 0; - ssize_t units = -1; - while(*p && (units = decode_utf8(&code, p)) != -1 && code > 0) { - p += units; - - if(code == '\n' || (wrap && currX + scaleX * font_scale * fontGetCharWidthInfo(fontGlyphIndexFromCodePoint(code))->charWidth >= wrapX)) { - lastAlign = p; - - screen_get_string_size_internal(&lineWidth, NULL, (const char*) p, scaleX, scaleY, true, wrap, wrapX - x); - - currX = x; - if(centerLines) { - currX += (stringWidth - lineWidth) / 2; - } - - y += scaleY * font_scale * fontGetInfo()->lineFeed; - } - - if(code != '\n') { - u32 num = 1; - if(code == '\t') { - code = ' '; - num = 4 - (p - units - lastAlign) % 4; - - lastAlign = p; - } - - fontGlyphPos_s data; - fontCalcGlyphPos(&data, fontGlyphIndexFromCodePoint(code), GLYPH_POS_CALC_VTXCOORD, scaleX * font_scale, scaleY * font_scale); - - if(data.sheetIndex != lastSheet) { - lastSheet = data.sheetIndex; - C3D_TexBind(0, &glyph_sheets[lastSheet]); - } - - for(u32 i = 0; i < num; i++) { - screen_draw_quad(currX + data.vtxcoord.left, y + data.vtxcoord.top, currX + data.vtxcoord.right, y + data.vtxcoord.bottom, data.texcoord.left, data.texcoord.bottom, data.texcoord.right, data.texcoord.top); - - currX += data.xAdvance; - } - } - } - - screen_set_blend(0, false, false); -} - -void screen_draw_string(const char* text, float x, float y, float scaleX, float scaleY, u32 colorId, bool centerLines) { - screen_draw_string_internal(text, x, y, scaleX, scaleY, colorId, centerLines, false, 0); -} - -void screen_draw_string_wrap(const char* text, float x, float y, float scaleX, float scaleY, u32 colorId, bool centerLines, float wrapX) { - screen_draw_string_internal(text, x, y, scaleX, scaleY, colorId, centerLines, true, wrapX); -} +#include +#include +#include +#include + +#include <3ds.h> +#include + +#include "../libs/stb_image/stb_image.h" +#include "screen.h" +#include "util.h" + +#include "default_shbin.h" + +static bool c3d_initialized; + +static bool shader_initialized; +static DVLB_s* dvlb; +static shaderProgram_s program; + +static C3D_RenderTarget* target_top; +static C3D_RenderTarget* target_bottom; +static C3D_Mtx projection_top; +static C3D_Mtx projection_bottom; + +static C3D_Tex* glyph_sheets; +static float font_scale; + +static u8 base_alpha = 0xFF; + +static u32 color_config[MAX_COLORS] = {0xFF000000}; + +static struct { + bool allocated; + C3D_Tex tex; + u32 width; + u32 height; +} textures[MAX_TEXTURES]; + +static FILE* screen_open_resource(const char* path) { + u32 realPathSize = strlen(path) + 17; + char realPath[realPathSize]; + + snprintf(realPath, realPathSize, "sdmc:/fbi/theme/%s", path); + FILE* fd = fopen(realPath, "rb"); + + if(fd != NULL) { + return fd; + } else { + snprintf(realPath, realPathSize, "romfs:/%s", path); + + return fopen(realPath, "rb"); + } +} + +static void screen_set_blend(u32 color, bool rgb, bool alpha) { + C3D_TexEnv* env = C3D_GetTexEnv(0); + if(env == NULL) { + util_panic("Failed to retrieve combiner settings."); + return; + } + + if(rgb) { + C3D_TexEnvSrc(env, C3D_RGB, GPU_CONSTANT, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); + } else { + C3D_TexEnvSrc(env, C3D_RGB, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); + } + + if(alpha) { + C3D_TexEnvSrc(env, C3D_Alpha, GPU_TEXTURE0, GPU_CONSTANT, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Alpha, GPU_MODULATE); + } else { + C3D_TexEnvSrc(env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Alpha, GPU_REPLACE); + } + + C3D_TexEnvColor(env, color); +} + +void screen_init() { + if(!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE * 4)) { + util_panic("Failed to initialize the GPU."); + return; + } + + c3d_initialized = true; + + u32 displayFlags = GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO); + + target_top = C3D_RenderTargetCreate(TOP_SCREEN_HEIGHT, TOP_SCREEN_WIDTH, GPU_RB_RGB8, 0); + if(target_top == NULL) { + util_panic("Failed to initialize the top screen target."); + return; + } + + C3D_RenderTargetSetOutput(target_top, GFX_TOP, GFX_LEFT, displayFlags); + C3D_RenderTargetSetClear(target_top, C3D_CLEAR_ALL, 0, 0); + + target_bottom = C3D_RenderTargetCreate(BOTTOM_SCREEN_HEIGHT, BOTTOM_SCREEN_WIDTH, GPU_RB_RGB8, 0); + if(target_bottom == NULL) { + util_panic("Failed to initialize the bottom screen target."); + return; + } + + C3D_RenderTargetSetOutput(target_bottom, GFX_BOTTOM, GFX_LEFT, displayFlags); + C3D_RenderTargetSetClear(target_bottom, C3D_CLEAR_ALL, 0, 0); + + Mtx_OrthoTilt(&projection_top, 0.0, TOP_SCREEN_WIDTH, TOP_SCREEN_HEIGHT, 0.0, 0.0, 1.0, true); + Mtx_OrthoTilt(&projection_bottom, 0.0, BOTTOM_SCREEN_WIDTH, BOTTOM_SCREEN_HEIGHT, 0.0, 0.0, 1.0, true); + + dvlb = DVLB_ParseFile((u32*) default_shbin, default_shbin_len); + if(dvlb == NULL) { + util_panic("Failed to parse shader."); + return; + } + + Result progInitRes = shaderProgramInit(&program); + if(R_FAILED(progInitRes)) { + util_panic("Failed to initialize shader program: 0x%08lX", progInitRes); + return; + } + + shader_initialized = true; + + Result progSetVshRes = shaderProgramSetVsh(&program, &dvlb->DVLE[0]); + if(R_FAILED(progSetVshRes)) { + util_panic("Failed to set up vertex shader: 0x%08lX", progInitRes); + return; + } + + C3D_BindProgram(&program); + + C3D_AttrInfo* attrInfo = C3D_GetAttrInfo(); + if(attrInfo == NULL) { + util_panic("Failed to retrieve attribute info."); + return; + } + + AttrInfo_Init(attrInfo); + AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); + AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 2); + + C3D_DepthTest(true, GPU_GEQUAL, GPU_WRITE_ALL); + + screen_set_blend(0, false, false); + + Result fontMapRes = fontEnsureMapped(); + if(R_FAILED(fontMapRes)) { + util_panic("Failed to map system font: 0x%08lX", fontMapRes); + return; + } + + TGLP_s* glyphInfo = fontGetGlyphInfo(); + glyph_sheets = calloc(glyphInfo->nSheets, sizeof(C3D_Tex)); + if(glyph_sheets == NULL) { + util_panic("Failed to allocate font glyph texture data."); + return; + } + + for(int i = 0; i < glyphInfo->nSheets; i++) { + C3D_Tex* tex = &glyph_sheets[i]; + tex->data = fontGetGlyphSheetTex(i); + tex->fmt = (GPU_TEXCOLOR) glyphInfo->sheetFmt; + tex->size = glyphInfo->sheetSize; + tex->width = glyphInfo->sheetWidth; + tex->height = glyphInfo->sheetHeight; + tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE); + } + + font_scale = 30.0f / glyphInfo->cellHeight; // 30 is cellHeight in J machines + + FILE* fd = screen_open_resource("textcolor.cfg"); + if(fd == NULL) { + util_panic("Failed to open text color config: %s\n", strerror(errno)); + return; + } + + char line[128]; + while(fgets(line, sizeof(line), fd) != NULL) { + char key[64]; + u32 color = 0; + + sscanf(line, "%63[^=]=%lx", key, &color); + + if(strcasecmp(key, "text") == 0) { + color_config[COLOR_TEXT] = color; + } else if(strcasecmp(key, "nand") == 0) { + color_config[COLOR_NAND] = color; + } else if(strcasecmp(key, "sd") == 0) { + color_config[COLOR_SD] = color; + } else if(strcasecmp(key, "gamecard") == 0) { + color_config[COLOR_GAME_CARD] = color; + } else if(strcasecmp(key, "dstitle") == 0) { + color_config[COLOR_DS_TITLE] = color; + } else if(strcasecmp(key, "file") == 0) { + color_config[COLOR_FILE] = color; + } else if(strcasecmp(key, "directory") == 0) { + color_config[COLOR_DIRECTORY] = color; + } else if(strcasecmp(key, "enabled") == 0) { + color_config[COLOR_ENABLED] = color; + } else if(strcasecmp(key, "disabled") == 0) { + color_config[COLOR_DISABLED] = color; + } else if(strcasecmp(key, "titledbinstalled") == 0) { + color_config[COLOR_TITLEDB_INSTALLED] = color; + } else if(strcasecmp(key, "titledbnotinstalled") == 0) { + color_config[COLOR_TITLEDB_NOT_INSTALLED] = color; + } else if(strcasecmp(key, "ticketinuse") == 0) { + color_config[COLOR_TICKET_IN_USE] = color; + } else if(strcasecmp(key, "ticketnotinuse") == 0) { + color_config[COLOR_TICKET_NOT_IN_USE] = color; + } + } + + fclose(fd); + + screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_BG, "bottom_screen_bg.png", true); + screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_TOP_BAR, "bottom_screen_top_bar.png", true); + screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_TOP_BAR_SHADOW, "bottom_screen_top_bar_shadow.png", true); + screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR, "bottom_screen_bottom_bar.png", true); + screen_load_texture_file(TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR_SHADOW, "bottom_screen_bottom_bar_shadow.png", true); + screen_load_texture_file(TEXTURE_TOP_SCREEN_BG, "top_screen_bg.png", true); + screen_load_texture_file(TEXTURE_TOP_SCREEN_TOP_BAR, "top_screen_top_bar.png", true); + screen_load_texture_file(TEXTURE_TOP_SCREEN_TOP_BAR_SHADOW, "top_screen_top_bar_shadow.png", true); + screen_load_texture_file(TEXTURE_TOP_SCREEN_BOTTOM_BAR, "top_screen_bottom_bar.png", true); + screen_load_texture_file(TEXTURE_TOP_SCREEN_BOTTOM_BAR_SHADOW, "top_screen_bottom_bar_shadow.png", true); + screen_load_texture_file(TEXTURE_LOGO, "logo.png", true); + screen_load_texture_file(TEXTURE_SELECTION_OVERLAY, "selection_overlay.png", true); + screen_load_texture_file(TEXTURE_SCROLL_BAR, "scroll_bar.png", true); + screen_load_texture_file(TEXTURE_BUTTON, "button.png", true); + screen_load_texture_file(TEXTURE_PROGRESS_BAR_BG, "progress_bar_bg.png", true); + screen_load_texture_file(TEXTURE_PROGRESS_BAR_CONTENT, "progress_bar_content.png", true); + screen_load_texture_file(TEXTURE_META_INFO_BOX, "meta_info_box.png", true); + screen_load_texture_file(TEXTURE_META_INFO_BOX_SHADOW, "meta_info_box_shadow.png", true); + screen_load_texture_file(TEXTURE_BATTERY_CHARGING, "battery_charging.png", true); + screen_load_texture_file(TEXTURE_BATTERY_0, "battery0.png", true); + screen_load_texture_file(TEXTURE_BATTERY_1, "battery1.png", true); + screen_load_texture_file(TEXTURE_BATTERY_2, "battery2.png", true); + screen_load_texture_file(TEXTURE_BATTERY_3, "battery3.png", true); + screen_load_texture_file(TEXTURE_BATTERY_4, "battery4.png", true); + screen_load_texture_file(TEXTURE_BATTERY_5, "battery5.png", true); + screen_load_texture_file(TEXTURE_WIFI_DISCONNECTED, "wifi_disconnected.png", true); + screen_load_texture_file(TEXTURE_WIFI_0, "wifi0.png", true); + screen_load_texture_file(TEXTURE_WIFI_1, "wifi1.png", true); + screen_load_texture_file(TEXTURE_WIFI_2, "wifi2.png", true); + screen_load_texture_file(TEXTURE_WIFI_3, "wifi3.png", true); +} + +void screen_exit() { + for(u32 id = 0; id < MAX_TEXTURES; id++) { + screen_unload_texture(id); + } + + if(glyph_sheets != NULL) { + free(glyph_sheets); + glyph_sheets = NULL; + } + + if(shader_initialized) { + shaderProgramFree(&program); + shader_initialized = false; + } + + if(dvlb != NULL) { + DVLB_Free(dvlb); + dvlb = NULL; + } + + if(target_top != NULL) { + C3D_RenderTargetDelete(target_top); + target_top = NULL; + } + + if(target_bottom != NULL) { + C3D_RenderTargetDelete(target_bottom); + target_bottom = NULL; + } + + if(c3d_initialized) { + C3D_Fini(); + c3d_initialized = false; + } +} + +void screen_set_base_alpha(u8 alpha) { + base_alpha = alpha; +} + +static u32 screen_next_pow_2(u32 i) { + i--; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + i++; + + return i; +} + +u32 screen_allocate_free_texture() { + u32 id = 0; + for(u32 i = 1; i < MAX_TEXTURES; i++) { + if(!textures[i].allocated) { + textures[i].allocated = true; + + id = i; + break; + } + } + + if(id == 0) { + util_panic("Out of free textures."); + return 0; + } + + return id; +} + +static void screen_prepare_texture(u32* pow2WidthOut, u32* pow2HeightOut, u32 id, u32 width, u32 height, GPU_TEXCOLOR format, bool linearFilter) { + if(id >= MAX_TEXTURES) { + util_panic("Attempted to prepare invalid texture ID \"%lu\".", id); + return; + } + + u32 pow2Width = screen_next_pow_2(width); + if(pow2Width < 64) { + pow2Width = 64; + } + + u32 pow2Height = screen_next_pow_2(height); + if(pow2Height < 64) { + pow2Height = 64; + } + + if(textures[id].tex.data != NULL && (textures[id].tex.width != pow2Width || textures[id].tex.height != pow2Height || textures[id].tex.fmt != format)) { + C3D_TexDelete(&textures[id].tex); + textures[id].tex.data = NULL; + } + + if(textures[id].tex.data == NULL && !C3D_TexInit(&textures[id].tex, (u16) pow2Width, (u16) pow2Height, format)) { + util_panic("Failed to initialize texture with ID \"%lu\".", id); + return; + } + + C3D_TexSetFilter(&textures[id].tex, linearFilter ? GPU_LINEAR : GPU_NEAREST, GPU_NEAREST); + + textures[id].allocated = true; + textures[id].width = width; + textures[id].height = height; + + if(pow2WidthOut != NULL) { + *pow2WidthOut = pow2Width; + } + + if(pow2HeightOut != NULL) { + *pow2HeightOut = pow2Height; + } +} + +void screen_load_texture_tiled(u32 id, void* data, u32 size, u32 width, u32 height, GPU_TEXCOLOR format, bool linearFilter) { + u32 pow2Width = 0; + u32 pow2Height = 0; + screen_prepare_texture(&pow2Width, &pow2Height, id, width, height, format, linearFilter); + + if(width != pow2Width || height != pow2Height) { + u32 pixelSize = size / width / height; + + memset(textures[id].tex.data, 0, textures[id].tex.size); + for(u32 y = 0; y < height; y += 8) { + u32 dstPos = y * pow2Width * pixelSize; + u32 srcPos = y * width * pixelSize; + + memcpy(&((u8*) textures[id].tex.data)[dstPos], &((u8*) data)[srcPos], width * 8 * pixelSize); + } + } else { + memcpy(textures[id].tex.data, data, textures[id].tex.size); + } + + C3D_TexFlush(&textures[id].tex); +} + +void screen_load_texture_untiled(u32 id, void* data, u32 size, u32 width, u32 height, GPU_TEXCOLOR format, bool linearFilter) { + u32 pow2Width = 0; + u32 pow2Height = 0; + screen_prepare_texture(&pow2Width, &pow2Height, id, width, height, format, linearFilter); + + u32 pixelSize = size / width / height; + + memset(textures[id].tex.data, 0, textures[id].tex.size); + for(u32 x = 0; x < width; x++) { + for(u32 y = 0; y < height; y++) { + u32 dstPos = ((((y >> 3) * (pow2Width >> 3) + (x >> 3)) << 6) + ((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) | ((x & 4) << 2) | ((y & 4) << 3))) * pixelSize; + u32 srcPos = (y * width + x) * pixelSize; + + memcpy(&((u8*) textures[id].tex.data)[dstPos], &((u8*) data)[srcPos], pixelSize); + } + } + + C3D_TexFlush(&textures[id].tex); +} + +void screen_load_texture_file(u32 id, const char* path, bool linearFilter) { + if(id >= MAX_TEXTURES) { + util_panic("Attempted to load path \"%s\" to invalid texture ID \"%lu\".", path, id); + return; + } + + FILE* fd = screen_open_resource(path); + if(fd == NULL) { + util_panic("Failed to load PNG file \"%s\": %s", path, strerror(errno)); + return; + } + + int width; + int height; + int depth; + u8* image = stbi_load_from_file(fd, &width, &height, &depth, STBI_rgb_alpha); + fclose(fd); + + if(image == NULL || depth != STBI_rgb_alpha) { + util_panic("Failed to load PNG file \"%s\".", path); + return; + } + + for(u32 x = 0; x < width; x++) { + for(u32 y = 0; y < height; y++) { + u32 pos = (y * width + x) * 4; + + u8 c1 = image[pos + 0]; + u8 c2 = image[pos + 1]; + u8 c3 = image[pos + 2]; + u8 c4 = image[pos + 3]; + + image[pos + 0] = c4; + image[pos + 1] = c3; + image[pos + 2] = c2; + image[pos + 3] = c1; + } + } + + screen_load_texture_untiled(id, image, (u32) (width * height * 4), (u32) width, (u32) height, GPU_RGBA8, linearFilter); + + free(image); +} + +void screen_unload_texture(u32 id) { + if(id >= MAX_TEXTURES) { + util_panic("Attempted to unload invalid texture ID \"%lu\".", id); + return; + } + + C3D_TexDelete(&textures[id].tex); + textures[id].tex.data = NULL; + + textures[id].allocated = false; + textures[id].width = 0; + textures[id].height = 0; +} + +void screen_get_texture_size(u32* width, u32* height, u32 id) { + if(id >= MAX_TEXTURES) { + util_panic("Attempted to get size of invalid texture ID \"%lu\".", id); + return; + } + + if(width) { + *width = textures[id].width; + } + + if(height) { + *height = textures[id].height; + } +} + +void screen_begin_frame() { + if(!C3D_FrameBegin(C3D_FRAME_SYNCDRAW)) { + util_panic("Failed to begin frame."); + return; + } +} + +void screen_end_frame() { + C3D_FrameEnd(0); +} + +void screen_select(gfxScreen_t screen) { + if(!C3D_FrameDrawOn(screen == GFX_TOP ? target_top : target_bottom)) { + util_panic("Failed to select render target."); + return; + } + + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, shaderInstanceGetUniformLocation(program.vertexShader, "projection"), screen == GFX_TOP ? &projection_top : &projection_bottom); +} + +static void screen_draw_quad(float x1, float y1, float x2, float y2, float left, float bottom, float right, float top) { + C3D_ImmDrawBegin(GPU_TRIANGLE_STRIP); + + C3D_ImmSendAttrib(x1, y2, 0.5f, 0.0f); + C3D_ImmSendAttrib(left, bottom, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x2, y2, 0.5f, 0.0f); + C3D_ImmSendAttrib(right, bottom, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x1, y1, 0.5f, 0.0f); + C3D_ImmSendAttrib(left, top, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x2, y1, 0.5f, 0.0f); + C3D_ImmSendAttrib(right, top, 0.0f, 0.0f); + + C3D_ImmDrawEnd(); +} + +void screen_draw_texture(u32 id, float x, float y, float width, float height) { + if(id >= MAX_TEXTURES) { + util_panic("Attempted to draw invalid texture ID \"%lu\".", id); + return; + } + + if(textures[id].tex.data == NULL) { + return; + } + + if(base_alpha != 0xFF) { + screen_set_blend(base_alpha << 24, false, true); + } + + C3D_TexBind(0, &textures[id].tex); + screen_draw_quad(x, y, x + width, y + height, 0, (float) (textures[id].tex.height - textures[id].height) / (float) textures[id].tex.height, (float) textures[id].width / (float) textures[id].tex.width, 1.0f); + + if(base_alpha != 0xFF) { + screen_set_blend(0, false, false); + } +} + +void screen_draw_texture_crop(u32 id, float x, float y, float width, float height) { + if(id >= MAX_TEXTURES) { + util_panic("Attempted to draw invalid texture ID \"%lu\".", id); + return; + } + + if(textures[id].tex.data == NULL) { + return; + } + + if(base_alpha != 0xFF) { + screen_set_blend(base_alpha << 24, false, true); + } + + C3D_TexBind(0, &textures[id].tex); + screen_draw_quad(x, y, x + width, y + height, 0, (float) (textures[id].tex.height - textures[id].height) / (float) textures[id].tex.height, width / (float) textures[id].tex.width, (textures[id].tex.height - textures[id].height + height) / (float) textures[id].tex.height); + + if(base_alpha != 0xFF) { + screen_set_blend(0, false, false); + } +} + +float screen_get_font_height(float scaleY) { + return scaleY * fontGetInfo()->lineFeed; +} + +static void screen_get_string_size_internal(float* width, float* height, const char* text, float scaleX, float scaleY, bool oneLine, bool wrap, float wrapWidth) { + scaleX *= font_scale; + scaleY *= font_scale; + + float w = 0; + float h = 0; + float lineWidth = 0; + + if(text != NULL) { + h = scaleY * fontGetInfo()->lineFeed; + + const uint8_t* p = (const uint8_t*) text; + const uint8_t* lastAlign = p; + u32 code = 0; + ssize_t units = -1; + while(*p && (units = decode_utf8(&code, p)) != -1 && code > 0) { + p += units; + + if(code == '\n' || (wrap && lineWidth + scaleX * fontGetCharWidthInfo(fontGlyphIndexFromCodePoint(code))->charWidth >= wrapWidth)) { + lastAlign = p; + + if(lineWidth > w) { + w = lineWidth; + } + + lineWidth = 0; + + if(oneLine) { + break; + } + + h += scaleY * fontGetInfo()->lineFeed; + } + + if(code != '\n') { + u32 num = 1; + if(code == '\t') { + code = ' '; + num = 4 - (p - units - lastAlign) % 4; + + lastAlign = p; + } + + lineWidth += (scaleX * fontGetCharWidthInfo(fontGlyphIndexFromCodePoint(code))->charWidth) * num; + } + } + } + + if(width) { + *width = lineWidth > w ? lineWidth : w; + } + + if(height) { + *height = h; + } +} + +void screen_get_string_size(float* width, float* height, const char* text, float scaleX, float scaleY) { + screen_get_string_size_internal(width, height, text, scaleX, scaleY, false, false, 0); +} + +void screen_get_string_size_wrap(float* width, float* height, const char* text, float scaleX, float scaleY, float wrapWidth) { + screen_get_string_size_internal(width, height, text, scaleX, scaleY, false, true, wrapWidth); +} + +static void screen_draw_string_internal(const char* text, float x, float y, float scaleX, float scaleY, u32 colorId, bool centerLines, bool wrap, float wrapX) { + // Note: Do not just multiply scaleX and scaleY by font_scale, as they would then be double-scaled when passed into screen_get_string_size_internal. + + if(text == NULL) { + return; + } + + if(colorId >= MAX_COLORS) { + util_panic("Attempted to draw string with invalid color ID \"%lu\".", colorId); + return; + } + + u32 blendColor = color_config[colorId]; + if(base_alpha != 0xFF) { + float alpha1 = ((blendColor >> 24) & 0xFF) / 255.0f; + float alpha2 = base_alpha / 255.0f; + float blendedAlpha = alpha1 * alpha2; + + blendColor = (((u32) (blendedAlpha * 0xFF)) << 24) | (blendColor & 0x00FFFFFF); + } + + screen_set_blend(blendColor, true, true); + + float stringWidth; + screen_get_string_size_internal(&stringWidth, NULL, text, scaleX, scaleY, false, wrap, wrapX - x); + + float lineWidth; + screen_get_string_size_internal(&lineWidth, NULL, text, scaleX, scaleY, true, wrap, wrapX - x); + + float currX = x; + if(centerLines) { + currX += (stringWidth - lineWidth) / 2; + } + + int lastSheet = -1; + + const uint8_t* p = (const uint8_t*) text; + const uint8_t* lastAlign = p; + u32 code = 0; + ssize_t units = -1; + while(*p && (units = decode_utf8(&code, p)) != -1 && code > 0) { + p += units; + + if(code == '\n' || (wrap && currX + scaleX * font_scale * fontGetCharWidthInfo(fontGlyphIndexFromCodePoint(code))->charWidth >= wrapX)) { + lastAlign = p; + + screen_get_string_size_internal(&lineWidth, NULL, (const char*) p, scaleX, scaleY, true, wrap, wrapX - x); + + currX = x; + if(centerLines) { + currX += (stringWidth - lineWidth) / 2; + } + + y += scaleY * font_scale * fontGetInfo()->lineFeed; + } + + if(code != '\n') { + u32 num = 1; + if(code == '\t') { + code = ' '; + num = 4 - (p - units - lastAlign) % 4; + + lastAlign = p; + } + + fontGlyphPos_s data; + fontCalcGlyphPos(&data, fontGlyphIndexFromCodePoint(code), GLYPH_POS_CALC_VTXCOORD, scaleX * font_scale, scaleY * font_scale); + + if(data.sheetIndex != lastSheet) { + lastSheet = data.sheetIndex; + C3D_TexBind(0, &glyph_sheets[lastSheet]); + } + + for(u32 i = 0; i < num; i++) { + screen_draw_quad(currX + data.vtxcoord.left, y + data.vtxcoord.top, currX + data.vtxcoord.right, y + data.vtxcoord.bottom, data.texcoord.left, data.texcoord.bottom, data.texcoord.right, data.texcoord.top); + + currX += data.xAdvance; + } + } + } + + screen_set_blend(0, false, false); +} + +void screen_draw_string(const char* text, float x, float y, float scaleX, float scaleY, u32 colorId, bool centerLines) { + screen_draw_string_internal(text, x, y, scaleX, scaleY, colorId, centerLines, false, 0); +} + +void screen_draw_string_wrap(const char* text, float x, float y, float scaleX, float scaleY, u32 colorId, bool centerLines, float wrapX) { + screen_draw_string_internal(text, x, y, scaleX, scaleY, colorId, centerLines, true, wrapX); +} diff --git a/source/core/spi.c b/source/core/spi.c index 15f4290..1e6cb88 100644 --- a/source/core/spi.c +++ b/source/core/spi.c @@ -4,7 +4,7 @@ #include <3ds.h> #include "spi.h" -#include "../ui/error.h" +#include "util.h" /* * Based on information from TWLSaveTool, by TuxSH. diff --git a/source/core/util.c b/source/core/util.c index 231a60b..ca857be 100644 --- a/source/core/util.c +++ b/source/core/util.c @@ -5,9 +5,10 @@ #include #include <3ds.h> +#include +#include #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))) { diff --git a/source/core/util.h b/source/core/util.h index d60476a..8afff39 100644 --- a/source/core/util.h +++ b/source/core/util.h @@ -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); diff --git a/source/json/LICENSE b/source/json/LICENSE deleted file mode 100644 index 1aee375..0000000 --- a/source/json/LICENSE +++ /dev/null @@ -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. - diff --git a/source/json/json.c b/source/json/json.c deleted file mode 100644 index 6012bad..0000000 --- a/source/json/json.c +++ /dev/null @@ -1,1011 +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. - */ - -#include "json.h" - -#ifdef _MSC_VER - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif -#endif - -const struct _json_value json_value_none; - -#include -#include -#include -#include - -typedef unsigned int json_uchar; - -static unsigned char hex_value (json_char c) -{ - if (isdigit(c)) - return c - '0'; - - switch (c) { - case 'a': case 'A': return 0x0A; - case 'b': case 'B': return 0x0B; - case 'c': case 'C': return 0x0C; - case 'd': case 'D': return 0x0D; - case 'e': case 'E': return 0x0E; - case 'f': case 'F': return 0x0F; - default: return 0xFF; - } -} - -typedef struct -{ - unsigned long used_memory; - - unsigned int uint_max; - unsigned long ulong_max; - - json_settings settings; - int first_pass; - - const json_char * ptr; - unsigned int cur_line, cur_col; - -} json_state; - -static void * default_alloc (size_t size, int zero, void * user_data) -{ - return zero ? calloc (1, size) : malloc (size); -} - -static void default_free (void * ptr, void * user_data) -{ - free (ptr); -} - -static void * json_alloc (json_state * state, unsigned long size, int zero) -{ - if ((state->ulong_max - state->used_memory) < size) - return 0; - - if (state->settings.max_memory - && (state->used_memory += size) > state->settings.max_memory) - { - return 0; - } - - return state->settings.mem_alloc (size, zero, state->settings.user_data); -} - -static int new_value (json_state * state, - json_value ** top, json_value ** root, json_value ** alloc, - json_type type) -{ - json_value * value; - int values_size; - - if (!state->first_pass) - { - value = *top = *alloc; - *alloc = (*alloc)->_reserved.next_alloc; - - if (!*root) - *root = value; - - switch (value->type) - { - case json_array: - - if (value->u.array.length == 0) - break; - - if (! (value->u.array.values = (json_value **) json_alloc - (state, value->u.array.length * sizeof (json_value *), 0)) ) - { - return 0; - } - - value->u.array.length = 0; - break; - - case json_object: - - if (value->u.object.length == 0) - break; - - values_size = sizeof (*value->u.object.values) * value->u.object.length; - - if (! (value->u.object.values = (json_object_entry *) json_alloc - (state, values_size + ((unsigned long) value->u.object.values), 0)) ) - { - return 0; - } - - value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; - - value->u.object.length = 0; - break; - - case json_string: - - if (! (value->u.string.ptr = (json_char *) json_alloc - (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) - { - return 0; - } - - value->u.string.length = 0; - break; - - default: - break; - }; - - return 1; - } - - if (! (value = (json_value *) json_alloc - (state, sizeof (json_value) + state->settings.value_extra, 1))) - { - return 0; - } - - if (!*root) - *root = value; - - value->type = type; - value->parent = *top; - - #ifdef JSON_TRACK_SOURCE - value->line = state->cur_line; - value->col = state->cur_col; - #endif - - if (*alloc) - (*alloc)->_reserved.next_alloc = value; - - *alloc = *top = value; - - return 1; -} - -#define whitespace \ - case '\n': ++ state.cur_line; state.cur_col = 0; \ - case ' ': case '\t': case '\r' - -#define string_add(b) \ - do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); - -#define line_and_col \ - state.cur_line, state.cur_col - -static const long - flag_next = 1 << 0, - flag_reproc = 1 << 1, - flag_need_comma = 1 << 2, - flag_seek_value = 1 << 3, - flag_escaped = 1 << 4, - flag_string = 1 << 5, - flag_need_colon = 1 << 6, - flag_done = 1 << 7, - flag_num_negative = 1 << 8, - flag_num_zero = 1 << 9, - flag_num_e = 1 << 10, - flag_num_e_got_sign = 1 << 11, - flag_num_e_negative = 1 << 12, - flag_line_comment = 1 << 13, - flag_block_comment = 1 << 14; - -json_value * json_parse_ex (json_settings * settings, - const json_char * json, - size_t length, - char * error_buf) -{ - json_char error [json_error_max]; - const json_char * end; - json_value * top, * root, * alloc = 0; - json_state state = { 0 }; - long flags; - long num_digits = 0, num_e = 0; - json_int_t num_fraction = 0; - - /* Skip UTF-8 BOM - */ - if (length >= 3 && ((unsigned char) json [0]) == 0xEF - && ((unsigned char) json [1]) == 0xBB - && ((unsigned char) json [2]) == 0xBF) - { - json += 3; - length -= 3; - } - - error[0] = '\0'; - end = (json + length); - - memcpy (&state.settings, settings, sizeof (json_settings)); - - if (!state.settings.mem_alloc) - state.settings.mem_alloc = default_alloc; - - if (!state.settings.mem_free) - state.settings.mem_free = default_free; - - memset (&state.uint_max, 0xFF, sizeof (state.uint_max)); - memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max)); - - state.uint_max -= 8; /* limit of how much can be added before next check */ - state.ulong_max -= 8; - - for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) - { - json_uchar uchar; - unsigned char uc_b1, uc_b2, uc_b3, uc_b4; - json_char * string = 0; - unsigned int string_length = 0; - - top = root = 0; - flags = flag_seek_value; - - state.cur_line = 1; - - for (state.ptr = json ;; ++ state.ptr) - { - json_char b = (state.ptr == end ? 0 : *state.ptr); - - if (flags & flag_string) - { - if (!b) - { sprintf (error, "Unexpected EOF in string (at %d:%d)", line_and_col); - goto e_failed; - } - - if (string_length > state.uint_max) - goto e_overflow; - - if (flags & flag_escaped) - { - flags &= ~ flag_escaped; - - switch (b) - { - case 'b': string_add ('\b'); break; - case 'f': string_add ('\f'); break; - case 'n': string_add ('\n'); break; - case 'r': string_add ('\r'); break; - case 't': string_add ('\t'); break; - case 'u': - - if (end - state.ptr < 4 || - (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b4 = hex_value (*++ state.ptr)) == 0xFF) - { - sprintf (error, "Invalid character value `%c` (at %d:%d)", b, line_and_col); - goto e_failed; - } - - uc_b1 = (uc_b1 << 4) | uc_b2; - uc_b2 = (uc_b3 << 4) | uc_b4; - uchar = (uc_b1 << 8) | uc_b2; - - if ((uchar & 0xF800) == 0xD800) { - json_uchar uchar2; - - if (end - state.ptr < 6 || (*++ state.ptr) != '\\' || (*++ state.ptr) != 'u' || - (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || - (uc_b4 = hex_value (*++ state.ptr)) == 0xFF) - { - sprintf (error, "Invalid character value `%c` (at %d:%d)", b, line_and_col); - goto e_failed; - } - - uc_b1 = (uc_b1 << 4) | uc_b2; - uc_b2 = (uc_b3 << 4) | uc_b4; - uchar2 = (uc_b1 << 8) | uc_b2; - - uchar = 0x010000 | ((uchar & 0x3FF) << 10) | (uchar2 & 0x3FF); - } - - if (sizeof (json_char) >= sizeof (json_uchar) || (uchar <= 0x7F)) - { - string_add ((json_char) uchar); - break; - } - - if (uchar <= 0x7FF) - { - if (state.first_pass) - string_length += 2; - else - { string [string_length ++] = 0xC0 | (uchar >> 6); - string [string_length ++] = 0x80 | (uchar & 0x3F); - } - - break; - } - - if (uchar <= 0xFFFF) { - if (state.first_pass) - string_length += 3; - else - { string [string_length ++] = 0xE0 | (uchar >> 12); - string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F); - string [string_length ++] = 0x80 | (uchar & 0x3F); - } - - break; - } - - if (state.first_pass) - string_length += 4; - else - { string [string_length ++] = 0xF0 | (uchar >> 18); - string [string_length ++] = 0x80 | ((uchar >> 12) & 0x3F); - string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F); - string [string_length ++] = 0x80 | (uchar & 0x3F); - } - - break; - - default: - string_add (b); - }; - - continue; - } - - if (b == '\\') - { - flags |= flag_escaped; - continue; - } - - if (b == '"') - { - if (!state.first_pass) - string [string_length] = 0; - - flags &= ~ flag_string; - string = 0; - - switch (top->type) - { - case json_string: - - top->u.string.length = string_length; - flags |= flag_next; - - break; - - case json_object: - - if (state.first_pass) - (*(json_char **) &top->u.object.values) += string_length + 1; - else - { - top->u.object.values [top->u.object.length].name - = (json_char *) top->_reserved.object_mem; - - top->u.object.values [top->u.object.length].name_length - = string_length; - - (*(json_char **) &top->_reserved.object_mem) += string_length + 1; - } - - flags |= flag_seek_value | flag_need_colon; - continue; - - default: - break; - }; - } - else - { - string_add (b); - continue; - } - } - - if (state.settings.settings & json_enable_comments) - { - if (flags & (flag_line_comment | flag_block_comment)) - { - if (flags & flag_line_comment) - { - if (b == '\r' || b == '\n' || !b) - { - flags &= ~ flag_line_comment; - -- state.ptr; /* so null can be reproc'd */ - } - - continue; - } - - if (flags & flag_block_comment) - { - if (!b) - { sprintf (error, "%d:%d: Unexpected EOF in block comment", line_and_col); - goto e_failed; - } - - if (b == '*' && state.ptr < (end - 1) && state.ptr [1] == '/') - { - flags &= ~ flag_block_comment; - ++ state.ptr; /* skip closing sequence */ - } - - continue; - } - } - else if (b == '/') - { - if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object) - { sprintf (error, "%d:%d: Comment not allowed here", line_and_col); - goto e_failed; - } - - if (++ state.ptr == end) - { sprintf (error, "%d:%d: EOF unexpected", line_and_col); - goto e_failed; - } - - switch (b = *state.ptr) - { - case '/': - flags |= flag_line_comment; - continue; - - case '*': - flags |= flag_block_comment; - continue; - - default: - sprintf (error, "%d:%d: Unexpected `%c` in comment opening sequence", line_and_col, b); - goto e_failed; - }; - } - } - - if (flags & flag_done) - { - if (!b) - break; - - switch (b) - { - whitespace: - continue; - - default: - - sprintf (error, "%d:%d: Trailing garbage: `%c`", - state.cur_line, state.cur_col, b); - - goto e_failed; - }; - } - - if (flags & flag_seek_value) - { - switch (b) - { - whitespace: - continue; - - case ']': - - if (top && top->type == json_array) - flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; - else - { sprintf (error, "%d:%d: Unexpected ]", line_and_col); - goto e_failed; - } - - break; - - default: - - if (flags & flag_need_comma) - { - if (b == ',') - { flags &= ~ flag_need_comma; - continue; - } - else - { - sprintf (error, "%d:%d: Expected , before %c", - state.cur_line, state.cur_col, b); - - goto e_failed; - } - } - - if (flags & flag_need_colon) - { - if (b == ':') - { flags &= ~ flag_need_colon; - continue; - } - else - { - sprintf (error, "%d:%d: Expected : before %c", - state.cur_line, state.cur_col, b); - - goto e_failed; - } - } - - flags &= ~ flag_seek_value; - - switch (b) - { - case '{': - - if (!new_value (&state, &top, &root, &alloc, json_object)) - goto e_alloc_failure; - - continue; - - case '[': - - if (!new_value (&state, &top, &root, &alloc, json_array)) - goto e_alloc_failure; - - flags |= flag_seek_value; - continue; - - case '"': - - if (!new_value (&state, &top, &root, &alloc, json_string)) - goto e_alloc_failure; - - flags |= flag_string; - - string = top->u.string.ptr; - string_length = 0; - - continue; - - case 't': - - if ((end - state.ptr) < 3 || *(++ state.ptr) != 'r' || - *(++ state.ptr) != 'u' || *(++ state.ptr) != 'e') - { - goto e_unknown_value; - } - - if (!new_value (&state, &top, &root, &alloc, json_boolean)) - goto e_alloc_failure; - - top->u.boolean = 1; - - flags |= flag_next; - break; - - case 'f': - - if ((end - state.ptr) < 4 || *(++ state.ptr) != 'a' || - *(++ state.ptr) != 'l' || *(++ state.ptr) != 's' || - *(++ state.ptr) != 'e') - { - goto e_unknown_value; - } - - if (!new_value (&state, &top, &root, &alloc, json_boolean)) - goto e_alloc_failure; - - flags |= flag_next; - break; - - case 'n': - - if ((end - state.ptr) < 3 || *(++ state.ptr) != 'u' || - *(++ state.ptr) != 'l' || *(++ state.ptr) != 'l') - { - goto e_unknown_value; - } - - if (!new_value (&state, &top, &root, &alloc, json_null)) - goto e_alloc_failure; - - flags |= flag_next; - break; - - default: - - if (isdigit (b) || b == '-') - { - if (!new_value (&state, &top, &root, &alloc, json_integer)) - goto e_alloc_failure; - - if (!state.first_pass) - { - while (isdigit (b) || b == '+' || b == '-' - || b == 'e' || b == 'E' || b == '.') - { - if ( (++ state.ptr) == end) - { - b = 0; - break; - } - - b = *state.ptr; - } - - flags |= flag_next | flag_reproc; - break; - } - - flags &= ~ (flag_num_negative | flag_num_e | - flag_num_e_got_sign | flag_num_e_negative | - flag_num_zero); - - num_digits = 0; - num_fraction = 0; - num_e = 0; - - if (b != '-') - { - flags |= flag_reproc; - break; - } - - flags |= flag_num_negative; - continue; - } - else - { sprintf (error, "%d:%d: Unexpected %c when seeking value", line_and_col, b); - goto e_failed; - } - }; - }; - } - else - { - switch (top->type) - { - case json_object: - - switch (b) - { - whitespace: - continue; - - case '"': - - if (flags & flag_need_comma) - { sprintf (error, "%d:%d: Expected , before \"", line_and_col); - goto e_failed; - } - - flags |= flag_string; - - string = (json_char *) top->_reserved.object_mem; - string_length = 0; - - break; - - case '}': - - flags = (flags & ~ flag_need_comma) | flag_next; - break; - - case ',': - - if (flags & flag_need_comma) - { - flags &= ~ flag_need_comma; - break; - } - - default: - sprintf (error, "%d:%d: Unexpected `%c` in object", line_and_col, b); - goto e_failed; - }; - - break; - - case json_integer: - case json_double: - - if (isdigit (b)) - { - ++ num_digits; - - if (top->type == json_integer || flags & flag_num_e) - { - if (! (flags & flag_num_e)) - { - if (flags & flag_num_zero) - { sprintf (error, "%d:%d: Unexpected `0` before `%c`", line_and_col, b); - goto e_failed; - } - - if (num_digits == 1 && b == '0') - flags |= flag_num_zero; - } - else - { - flags |= flag_num_e_got_sign; - num_e = (num_e * 10) + (b - '0'); - continue; - } - - top->u.integer = (top->u.integer * 10) + (b - '0'); - continue; - } - - num_fraction = (num_fraction * 10) + (b - '0'); - continue; - } - - if (b == '+' || b == '-') - { - if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign)) - { - flags |= flag_num_e_got_sign; - - if (b == '-') - flags |= flag_num_e_negative; - - continue; - } - } - else if (b == '.' && top->type == json_integer) - { - if (!num_digits) - { sprintf (error, "%d:%d: Expected digit before `.`", line_and_col); - goto e_failed; - } - - top->type = json_double; - top->u.dbl = (double) top->u.integer; - - num_digits = 0; - continue; - } - - if (! (flags & flag_num_e)) - { - if (top->type == json_double) - { - if (!num_digits) - { sprintf (error, "%d:%d: Expected digit after `.`", line_and_col); - goto e_failed; - } - - top->u.dbl += ((double) num_fraction) / (pow (10.0, (double) num_digits)); - } - - if (b == 'e' || b == 'E') - { - flags |= flag_num_e; - - if (top->type == json_integer) - { - top->type = json_double; - top->u.dbl = (double) top->u.integer; - } - - num_digits = 0; - flags &= ~ flag_num_zero; - - continue; - } - } - else - { - if (!num_digits) - { sprintf (error, "%d:%d: Expected digit after `e`", line_and_col); - goto e_failed; - } - - top->u.dbl *= pow (10.0, (double) - (flags & flag_num_e_negative ? - num_e : num_e)); - } - - if (flags & flag_num_negative) - { - if (top->type == json_integer) - top->u.integer = - top->u.integer; - else - top->u.dbl = - top->u.dbl; - } - - flags |= flag_next | flag_reproc; - break; - - default: - break; - }; - } - - if (flags & flag_reproc) - { - flags &= ~ flag_reproc; - -- state.ptr; - } - - if (flags & flag_next) - { - flags = (flags & ~ flag_next) | flag_need_comma; - - if (!top->parent) - { - /* root value done */ - - flags |= flag_done; - continue; - } - - if (top->parent->type == json_array) - flags |= flag_seek_value; - - if (!state.first_pass) - { - json_value * parent = top->parent; - - switch (parent->type) - { - case json_object: - - parent->u.object.values - [parent->u.object.length].value = top; - - break; - - case json_array: - - parent->u.array.values - [parent->u.array.length] = top; - - break; - - default: - break; - }; - } - - if ( (++ top->parent->u.array.length) > state.uint_max) - goto e_overflow; - - top = top->parent; - - continue; - } - } - - alloc = root; - } - - return root; - -e_unknown_value: - - sprintf (error, "%d:%d: Unknown value", line_and_col); - goto e_failed; - -e_alloc_failure: - - strcpy (error, "Memory allocation failure"); - goto e_failed; - -e_overflow: - - sprintf (error, "%d:%d: Too long (caught overflow)", line_and_col); - goto e_failed; - -e_failed: - - if (error_buf) - { - if (*error) - strcpy (error_buf, error); - else - strcpy (error_buf, "Unknown error"); - } - - if (state.first_pass) - alloc = root; - - while (alloc) - { - top = alloc->_reserved.next_alloc; - state.settings.mem_free (alloc, state.settings.user_data); - alloc = top; - } - - if (!state.first_pass) - json_value_free_ex (&state.settings, root); - - return 0; -} - -json_value * json_parse (const json_char * json, size_t length) -{ - json_settings settings = { 0 }; - return json_parse_ex (&settings, json, length, 0); -} - -void json_value_free_ex (json_settings * settings, json_value * value) -{ - json_value * cur_value; - - if (!value) - return; - - value->parent = 0; - - while (value) - { - switch (value->type) - { - case json_array: - - if (!value->u.array.length) - { - settings->mem_free (value->u.array.values, settings->user_data); - break; - } - - value = value->u.array.values [-- value->u.array.length]; - continue; - - case json_object: - - if (!value->u.object.length) - { - settings->mem_free (value->u.object.values, settings->user_data); - break; - } - - value = value->u.object.values [-- value->u.object.length].value; - continue; - - case json_string: - - settings->mem_free (value->u.string.ptr, settings->user_data); - break; - - default: - break; - }; - - cur_value = value; - value = value->parent; - settings->mem_free (cur_value, settings->user_data); - } -} - -void json_value_free (json_value * value) -{ - json_settings settings = { 0 }; - settings.mem_free = default_free; - json_value_free_ex (&settings, value); -} - diff --git a/source/json/json.h b/source/json/json.h deleted file mode 100644 index f6549ec..0000000 --- a/source/json/json.h +++ /dev/null @@ -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 - #define json_int_t int64_t - #else - #define json_int_t __int64 - #endif -#endif - -#include - -#ifdef __cplusplus - - #include - - 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 - - diff --git a/source/quirc/LICENSE b/source/libs/quirc/LICENSE similarity index 100% rename from source/quirc/LICENSE rename to source/libs/quirc/LICENSE diff --git a/source/quirc/decode.c b/source/libs/quirc/decode.c similarity index 100% rename from source/quirc/decode.c rename to source/libs/quirc/decode.c diff --git a/source/quirc/identify.c b/source/libs/quirc/identify.c similarity index 100% rename from source/quirc/identify.c rename to source/libs/quirc/identify.c diff --git a/source/quirc/quirc.c b/source/libs/quirc/quirc.c similarity index 100% rename from source/quirc/quirc.c rename to source/libs/quirc/quirc.c diff --git a/source/quirc/quirc.h b/source/libs/quirc/quirc.h similarity index 100% rename from source/quirc/quirc.h rename to source/libs/quirc/quirc.h diff --git a/source/quirc/quirc_internal.h b/source/libs/quirc/quirc_internal.h similarity index 100% rename from source/quirc/quirc_internal.h rename to source/libs/quirc/quirc_internal.h diff --git a/source/quirc/version_db.c b/source/libs/quirc/version_db.c similarity index 100% rename from source/quirc/version_db.c rename to source/libs/quirc/version_db.c diff --git a/source/stb_image/stb_image.c b/source/libs/stb_image/stb_image.c similarity index 100% rename from source/stb_image/stb_image.c rename to source/libs/stb_image/stb_image.c diff --git a/source/stb_image/stb_image.h b/source/libs/stb_image/stb_image.h similarity index 100% rename from source/stb_image/stb_image.h rename to source/libs/stb_image/stb_image.h diff --git a/source/main.c b/source/main.c index 3d7ceca..edd1ec0 100644 --- a/source/main.c +++ b/source/main.c @@ -1,188 +1,197 @@ -#include -#include - -#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 +#include + +#include <3ds.h> +#include + +#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 +#include +#include + +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; +} \ No newline at end of file diff --git a/source/ui/error.c b/source/ui/error.c index cc05d1c..0dc9bc1 100644 --- a/source/ui/error.c +++ b/source/ui/error.c @@ -4,10 +4,12 @@ #include #include <3ds.h> +#include #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: diff --git a/source/ui/error.h b/source/ui/error.h index 42d10bc..d089f5f 100644 --- a/source/ui/error.h +++ b/source/ui/error.h @@ -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, ...); diff --git a/source/ui/section/action/action.h b/source/ui/section/action/action.h index b94febb..ea8e8f8 100644 --- a/source/ui/section/action/action.h +++ b/source/ui/section/action/action.h @@ -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); \ No newline at end of file +void action_install_titledb(linked_list* items, list_item* selected, bool cia); \ No newline at end of file diff --git a/source/ui/section/action/installtitledb.c b/source/ui/section/action/installtitledb.c index 6a63031..1fd9d6f 100644 --- a/source/ui/section/action/installtitledb.c +++ b/source/ui/section/action/installtitledb.c @@ -1,27 +1,61 @@ -#include - -#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 +#include + +#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); } \ No newline at end of file diff --git a/source/ui/section/remoteinstall.c b/source/ui/section/remoteinstall.c index 57cced0..9973c71 100644 --- a/source/ui/section/remoteinstall.c +++ b/source/ui/section/remoteinstall.c @@ -4,9 +4,9 @@ #include #include #include +#include #include <3ds.h> -#include #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) { diff --git a/source/ui/section/task/capturecam.c b/source/ui/section/task/capturecam.c index 3d2a684..cd2d974 100644 --- a/source/ui/section/task/capturecam.c +++ b/source/ui/section/task/capturecam.c @@ -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 diff --git a/source/ui/section/task/dataop.c b/source/ui/section/task/dataop.c index 25d8504..711f627 100644 --- a/source/ui/section/task/dataop.c +++ b/source/ui/section/task/dataop.c @@ -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; diff --git a/source/ui/section/task/listextsavedata.c b/source/ui/section/task/listextsavedata.c index 3a98fde..896c91d 100644 --- a/source/ui/section/task/listextsavedata.c +++ b/source/ui/section/task/listextsavedata.c @@ -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" diff --git a/source/ui/section/task/listfiles.c b/source/ui/section/task/listfiles.c index 819d964..d8d3b14 100644 --- a/source/ui/section/task/listfiles.c +++ b/source/ui/section/task/listfiles.c @@ -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" diff --git a/source/ui/section/task/listpendingtitles.c b/source/ui/section/task/listpendingtitles.c index e913a64..3492ede 100644 --- a/source/ui/section/task/listpendingtitles.c +++ b/source/ui/section/task/listpendingtitles.c @@ -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" diff --git a/source/ui/section/task/listsystemsavedata.c b/source/ui/section/task/listsystemsavedata.c index 3be327f..59b3979 100644 --- a/source/ui/section/task/listsystemsavedata.c +++ b/source/ui/section/task/listsystemsavedata.c @@ -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" diff --git a/source/ui/section/task/listtickets.c b/source/ui/section/task/listtickets.c index 83d3651..026eb02 100644 --- a/source/ui/section/task/listtickets.c +++ b/source/ui/section/task/listtickets.c @@ -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" diff --git a/source/ui/section/task/listtitledb.c b/source/ui/section/task/listtitledb.c index 630e70d..184725a 100644 --- a/source/ui/section/task/listtitledb.c +++ b/source/ui/section/task/listtitledb.c @@ -1,281 +1,283 @@ -#include -#include -#include -#include - -#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 +#include +#include +#include + +#include <3ds.h> +#include + +#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; } \ No newline at end of file diff --git a/source/ui/section/task/listtitles.c b/source/ui/section/task/listtitles.c index 7b9155b..f59665b 100644 --- a/source/ui/section/task/listtitles.c +++ b/source/ui/section/task/listtitles.c @@ -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" diff --git a/source/ui/section/task/task.h b/source/ui/section/task/task.h index 08a9410..668b6a0 100644 --- a/source/ui/section/task/task.h +++ b/source/ui/section/task/task.h @@ -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); \ No newline at end of file diff --git a/source/ui/section/titledb.c b/source/ui/section/titledb.c index 814818b..4d11fc0 100644 --- a/source/ui/section/titledb.c +++ b/source/ui/section/titledb.c @@ -1,151 +1,243 @@ -#include -#include - -#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 +#include +#include + +#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); } \ No newline at end of file diff --git a/source/ui/section/update.c b/source/ui/section/update.c index 7762d2a..08995a2 100644 --- a/source/ui/section/update.c +++ b/source/ui/section/update.c @@ -3,6 +3,7 @@ #include #include <3ds.h> +#include #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(); diff --git a/source/ui/ui.c b/source/ui/ui.c index 89d056e..defa5e9 100644 --- a/source/ui/ui.c +++ b/source/ui/ui.c @@ -1,639 +1,690 @@ -#include -#include -#include - -#include <3ds.h> -#include - -#include "ui.h" -#include "section/task/task.h" -#include "../core/screen.h" -#include "../core/util.h" - -#define MAX_UI_VIEWS 16 - -static ui_view* ui_stack[MAX_UI_VIEWS]; -static int ui_stack_top = -1; - -static Handle ui_stack_mutex = 0; - -static u64 ui_free_space_last_update = 0; -static char ui_free_space_buffer[128]; - -static u64 ui_fade_begin_time = 0; -static u8 ui_fade_alpha = 0; - -void ui_init() { - if(ui_stack_mutex == 0) { - svcCreateMutex(&ui_stack_mutex, false); - } - - ui_fade_begin_time = osGetTime(); -} - -void ui_exit() { - if(ui_stack_mutex != 0) { - svcCloseHandle(ui_stack_mutex); - ui_stack_mutex = 0; - } -} - -ui_view* ui_create() { - ui_view* view = (ui_view*) calloc(1, sizeof(ui_view)); - if(view == NULL) { - util_panic("Failed to allocate UI view."); - return NULL; - } - - Result res = 0; - if(R_FAILED(res = svcCreateEvent(&view->active, RESET_STICKY))) { - util_panic("Failed to create view active event: 0x%08lX", res); - - free(view); - return NULL; - } - - return view; -} - -void ui_destroy(ui_view* view) { - if(view != NULL) { - svcCloseHandle(view->active); - free(view); - } -} - -ui_view* ui_top() { - svcWaitSynchronization(ui_stack_mutex, U64_MAX); - - ui_view* ui = NULL; - if(ui_stack_top >= 0) { - ui = ui_stack[ui_stack_top]; - } - - svcReleaseMutex(ui_stack_mutex); - - return ui; -} - -bool ui_push(ui_view* view) { - if(view == NULL) { - return false; - } - - svcWaitSynchronization(ui_stack_mutex, U64_MAX); - - bool space = ui_stack_top < MAX_UI_VIEWS - 1; - if(space) { - ui_stack[++ui_stack_top] = view; - - svcClearEvent(view->active); - } - - svcReleaseMutex(ui_stack_mutex); - - return space; -} - -void ui_pop() { - svcWaitSynchronization(ui_stack_mutex, U64_MAX); - - if(ui_stack_top >= 0) { - svcSignalEvent(ui_stack[ui_stack_top]->active); - - ui_stack[ui_stack_top--] = NULL; - } - - svcReleaseMutex(ui_stack_mutex); -} - -static void ui_draw_top(ui_view* ui) { - screen_select(GFX_TOP); - - u32 topScreenBgWidth = 0; - u32 topScreenBgHeight = 0; - screen_get_texture_size(&topScreenBgWidth, &topScreenBgHeight, TEXTURE_TOP_SCREEN_BG); - - u32 topScreenTopBarWidth = 0; - u32 topScreenTopBarHeight = 0; - screen_get_texture_size(&topScreenTopBarWidth, &topScreenTopBarHeight, TEXTURE_TOP_SCREEN_TOP_BAR); - - u32 topScreenTopBarShadowWidth = 0; - u32 topScreenTopBarShadowHeight = 0; - screen_get_texture_size(&topScreenTopBarShadowWidth, &topScreenTopBarShadowHeight, TEXTURE_TOP_SCREEN_TOP_BAR_SHADOW); - - u32 topScreenBottomBarWidth = 0; - u32 topScreenBottomBarHeight = 0; - screen_get_texture_size(&topScreenBottomBarWidth, &topScreenBottomBarHeight, TEXTURE_TOP_SCREEN_BOTTOM_BAR); - - u32 topScreenBottomBarShadowWidth = 0; - u32 topScreenBottomBarShadowHeight = 0; - screen_get_texture_size(&topScreenBottomBarShadowWidth, &topScreenBottomBarShadowHeight, TEXTURE_TOP_SCREEN_BOTTOM_BAR_SHADOW); - - screen_draw_texture(TEXTURE_TOP_SCREEN_BG, (TOP_SCREEN_WIDTH - topScreenBgWidth) / 2, (TOP_SCREEN_HEIGHT - topScreenBgHeight) / 2, topScreenBgWidth, topScreenBgHeight); - - if(ui->drawTop != NULL) { - ui->drawTop(ui, ui->data, 0, topScreenTopBarHeight, TOP_SCREEN_WIDTH, TOP_SCREEN_HEIGHT - topScreenBottomBarHeight); - } - - float topScreenTopBarX = (TOP_SCREEN_WIDTH - topScreenTopBarWidth) / 2; - float topScreenTopBarY = 0; - screen_draw_texture(TEXTURE_TOP_SCREEN_TOP_BAR, topScreenTopBarX, topScreenTopBarY, topScreenTopBarWidth, topScreenTopBarHeight); - screen_draw_texture(TEXTURE_TOP_SCREEN_TOP_BAR_SHADOW, topScreenTopBarX, topScreenTopBarY + topScreenTopBarHeight, topScreenTopBarShadowWidth, topScreenTopBarShadowHeight); - - float topScreenBottomBarX = (TOP_SCREEN_WIDTH - topScreenBottomBarWidth) / 2; - float topScreenBottomBarY = TOP_SCREEN_HEIGHT - topScreenBottomBarHeight; - screen_draw_texture(TEXTURE_TOP_SCREEN_BOTTOM_BAR, topScreenBottomBarX, topScreenBottomBarY, topScreenBottomBarWidth, topScreenBottomBarHeight); - screen_draw_texture(TEXTURE_TOP_SCREEN_BOTTOM_BAR_SHADOW, topScreenBottomBarX, topScreenBottomBarY - topScreenBottomBarShadowHeight, topScreenBottomBarShadowWidth, topScreenBottomBarShadowHeight); - - screen_set_base_alpha(ui_fade_alpha); - - char verText[64]; - snprintf(verText, 64, "Ver. %d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); - - float verWidth; - float verHeight; - screen_get_string_size(&verWidth, &verHeight, verText, 0.5f, 0.5f); - screen_draw_string(verText, topScreenTopBarX + 2, topScreenTopBarY + (topScreenTopBarHeight - verHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); - - time_t t = time(NULL); - char* timeText = ctime(&t); - timeText[strlen(timeText) - 1] = '\0'; - - float timeTextWidth; - float timeTextHeight; - screen_get_string_size(&timeTextWidth, &timeTextHeight, timeText, 0.5f, 0.5f); - screen_draw_string(timeText, topScreenTopBarX + (topScreenTopBarWidth - timeTextWidth) / 2, topScreenTopBarY + (topScreenTopBarHeight - timeTextHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); - - u32 batteryIcon = 0; - u8 batteryChargeState = 0; - u8 batteryLevel = 0; - if(R_SUCCEEDED(PTMU_GetBatteryChargeState(&batteryChargeState)) && batteryChargeState) { - batteryIcon = TEXTURE_BATTERY_CHARGING; - } else if(R_SUCCEEDED(PTMU_GetBatteryLevel(&batteryLevel))) { - batteryIcon = TEXTURE_BATTERY_0 + batteryLevel; - } else { - batteryIcon = TEXTURE_BATTERY_0; - } - - u32 batteryWidth; - u32 batteryHeight; - screen_get_texture_size(&batteryWidth, &batteryHeight, batteryIcon); - - float batteryX = topScreenTopBarX + topScreenTopBarWidth - 2 - batteryWidth; - float batteryY = topScreenTopBarY + (topScreenTopBarHeight - batteryHeight) / 2; - screen_draw_texture(batteryIcon, batteryX, batteryY, batteryWidth, batteryHeight); - - u32 wifiIcon = 0; - u32 wifiStatus = 0; - if(R_SUCCEEDED(ACU_GetWifiStatus(&wifiStatus)) && wifiStatus) { - wifiIcon = TEXTURE_WIFI_0 + osGetWifiStrength(); - } else { - wifiIcon = TEXTURE_WIFI_DISCONNECTED; - } - - u32 wifiWidth; - u32 wifiHeight; - screen_get_texture_size(&wifiWidth, &wifiHeight, wifiIcon); - - float wifiX = topScreenTopBarX + topScreenTopBarWidth - 2 - batteryWidth - 4 - wifiWidth; - float wifiY = topScreenTopBarY + (topScreenTopBarHeight - wifiHeight) / 2; - screen_draw_texture(wifiIcon, wifiX, wifiY, wifiWidth, wifiHeight); - - if(osGetTime() >= ui_free_space_last_update + 1000) { - char* currBuffer = ui_free_space_buffer; - FS_ArchiveResource resource = {0}; - - if(R_SUCCEEDED(FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_SD)) && currBuffer < ui_free_space_buffer + sizeof(ui_free_space_buffer)) { - if(currBuffer != ui_free_space_buffer) { - snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), ", "); - currBuffer += strlen(currBuffer); - } - - u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize; - snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), "SD: %.1f %s", util_get_display_size(size), util_get_display_size_units(size)); - currBuffer += strlen(currBuffer); - } - - if(R_SUCCEEDED(FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_CTR_NAND)) && currBuffer < ui_free_space_buffer + sizeof(ui_free_space_buffer)) { - if(currBuffer != ui_free_space_buffer) { - snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), ", "); - currBuffer += strlen(currBuffer); - } - - u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize; - snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), "CTR NAND: %.1f %s", util_get_display_size(size), util_get_display_size_units(size)); - currBuffer += strlen(currBuffer); - } - - if(R_SUCCEEDED(FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_TWL_NAND)) && currBuffer < ui_free_space_buffer + sizeof(ui_free_space_buffer)) { - if(currBuffer != ui_free_space_buffer) { - snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), ", "); - currBuffer += strlen(currBuffer); - } - - u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize; - snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), "TWL NAND: %.1f %s", util_get_display_size(size), util_get_display_size_units(size)); - currBuffer += strlen(currBuffer); - } - - if(R_SUCCEEDED(FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_TWL_PHOTO)) && currBuffer < ui_free_space_buffer + sizeof(ui_free_space_buffer)) { - if(currBuffer != ui_free_space_buffer) { - snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), ", "); - currBuffer += strlen(currBuffer); - } - - u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize; - snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), "TWL Photo: %.1f %s", util_get_display_size(size), util_get_display_size_units(size)); - currBuffer += strlen(currBuffer); - } - - ui_free_space_last_update = osGetTime(); - } - - float freeSpaceHeight; - screen_get_string_size(NULL, &freeSpaceHeight, ui_free_space_buffer, 0.35f, 0.35f); - - screen_draw_string(ui_free_space_buffer, topScreenBottomBarX + 2, topScreenBottomBarY + (topScreenBottomBarHeight - freeSpaceHeight) / 2, 0.35f, 0.35f, COLOR_TEXT, true); - - screen_set_base_alpha(0xFF); -} - -static void ui_draw_bottom(ui_view* ui) { - screen_select(GFX_BOTTOM); - - u32 bottomScreenBgWidth = 0; - u32 bottomScreenBgHeight = 0; - screen_get_texture_size(&bottomScreenBgWidth, &bottomScreenBgHeight, TEXTURE_BOTTOM_SCREEN_BG); - - u32 bottomScreenTopBarWidth = 0; - u32 bottomScreenTopBarHeight = 0; - screen_get_texture_size(&bottomScreenTopBarWidth, &bottomScreenTopBarHeight, TEXTURE_BOTTOM_SCREEN_TOP_BAR); - - u32 bottomScreenTopBarShadowWidth = 0; - u32 bottomScreenTopBarShadowHeight = 0; - screen_get_texture_size(&bottomScreenTopBarShadowWidth, &bottomScreenTopBarShadowHeight, TEXTURE_BOTTOM_SCREEN_TOP_BAR_SHADOW); - - u32 bottomScreenBottomBarWidth = 0; - u32 bottomScreenBottomBarHeight = 0; - screen_get_texture_size(&bottomScreenBottomBarWidth, &bottomScreenBottomBarHeight, TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR); - - u32 bottomScreenBottomBarShadowWidth = 0; - u32 bottomScreenBottomBarShadowHeight = 0; - screen_get_texture_size(&bottomScreenBottomBarShadowWidth, &bottomScreenBottomBarShadowHeight, TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR_SHADOW); - - screen_draw_texture(TEXTURE_BOTTOM_SCREEN_BG, (BOTTOM_SCREEN_WIDTH - bottomScreenBgWidth) / 2, (BOTTOM_SCREEN_HEIGHT - bottomScreenBgHeight) / 2, bottomScreenBgWidth, bottomScreenBgHeight); - - screen_set_base_alpha(ui_fade_alpha); - - if(ui->drawBottom != NULL) { - ui->drawBottom(ui, ui->data, 0, bottomScreenTopBarHeight, BOTTOM_SCREEN_WIDTH, BOTTOM_SCREEN_HEIGHT - bottomScreenBottomBarHeight); - } - - screen_set_base_alpha(0xFF); - - float bottomScreenTopBarX = (BOTTOM_SCREEN_WIDTH - bottomScreenTopBarWidth) / 2; - float bottomScreenTopBarY = 0; - screen_draw_texture(TEXTURE_BOTTOM_SCREEN_TOP_BAR, bottomScreenTopBarX, bottomScreenTopBarY, bottomScreenTopBarWidth, bottomScreenTopBarHeight); - screen_draw_texture(TEXTURE_BOTTOM_SCREEN_TOP_BAR_SHADOW, bottomScreenTopBarX, bottomScreenTopBarY + bottomScreenTopBarHeight, bottomScreenTopBarShadowWidth, bottomScreenTopBarShadowHeight); - - float bottomScreenBottomBarX = (BOTTOM_SCREEN_WIDTH - bottomScreenBottomBarWidth) / 2; - float bottomScreenBottomBarY = BOTTOM_SCREEN_HEIGHT - bottomScreenBottomBarHeight; - screen_draw_texture(TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR, bottomScreenBottomBarX, bottomScreenBottomBarY, bottomScreenBottomBarWidth, bottomScreenBottomBarHeight); - screen_draw_texture(TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR_SHADOW, bottomScreenBottomBarX, bottomScreenBottomBarY - bottomScreenBottomBarShadowHeight, bottomScreenBottomBarShadowWidth, bottomScreenBottomBarShadowHeight); - - screen_set_base_alpha(ui_fade_alpha); - - if(ui->name != NULL) { - float nameWidth; - float nameHeight; - screen_get_string_size(&nameWidth, &nameHeight, ui->name, 0.5f, 0.5f); - screen_draw_string(ui->name, (BOTTOM_SCREEN_WIDTH - nameWidth) / 2, (bottomScreenTopBarHeight - nameHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); - } - - if(ui->info != NULL) { - float infoWidth; - float infoHeight; - screen_get_string_size(&infoWidth, &infoHeight, ui->info, 0.5f, 0.5f); - screen_draw_string(ui->info, (BOTTOM_SCREEN_WIDTH - infoWidth) / 2, BOTTOM_SCREEN_HEIGHT - (bottomScreenBottomBarHeight + infoHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); - } - - screen_set_base_alpha(0xFF); -} - -bool ui_update() { - ui_view* ui = NULL; - - hidScanInput(); - - ui = ui_top(); - if(ui != NULL && ui->update != NULL) { - u32 bottomScreenTopBarHeight = 0; - screen_get_texture_size(NULL, &bottomScreenTopBarHeight, TEXTURE_BOTTOM_SCREEN_TOP_BAR); - - u32 bottomScreenBottomBarHeight = 0; - screen_get_texture_size(NULL, &bottomScreenBottomBarHeight, TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR); - - ui->update(ui, ui->data, 0, bottomScreenTopBarHeight, BOTTOM_SCREEN_WIDTH, BOTTOM_SCREEN_HEIGHT - bottomScreenBottomBarHeight); - } - - u64 time = osGetTime(); - if(!envIsHomebrew() && time - ui_fade_begin_time < 500) { - ui_fade_alpha = (u8) (((time - ui_fade_begin_time) / 500.0f) * 0xFF); - } else { - ui_fade_alpha = 0xFF; - } - - ui = ui_top(); - if(ui != NULL) { - screen_begin_frame(); - ui_draw_top(ui); - ui_draw_bottom(ui); - screen_end_frame(); - } - - return ui != NULL; -} - -void ui_draw_meta_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { - meta_info* info = (meta_info*) data; - - u32 metaInfoBoxShadowWidth; - u32 metaInfoBoxShadowHeight; - screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW); - - float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2; - float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2; - screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight); - - u32 metaInfoBoxWidth; - u32 metaInfoBoxHeight; - screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX); - - float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2; - float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2; - screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight); - - if(info->texture != 0) { - u32 iconWidth; - u32 iconHeight; - screen_get_texture_size(&iconWidth, &iconHeight, info->texture); - - float iconX = metaInfoBoxX + (64 - iconWidth) / 2; - float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2; - screen_draw_texture(info->texture, iconX, iconY, iconWidth, iconHeight); - } - - float metaTextX = metaInfoBoxX + 64; - - float shortDescriptionHeight; - screen_get_string_size_wrap(NULL, &shortDescriptionHeight, info->shortDescription, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX); - - float longDescriptionHeight; - screen_get_string_size_wrap(NULL, &longDescriptionHeight, info->longDescription, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX); - - float publisherHeight; - screen_get_string_size_wrap(NULL, &publisherHeight, info->publisher, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX); - - float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2; - screen_draw_string_wrap(info->shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8); - - float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2; - screen_draw_string_wrap(info->longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8); - - float publisherY = longDescriptionY + longDescriptionHeight + 2; - screen_draw_string_wrap(info->publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8); -} - -void ui_draw_ext_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { - ext_save_data_info* info = (ext_save_data_info*) data; - - if(info->hasMeta) { - ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2); - } - - char infoText[512]; - - snprintf(infoText, sizeof(infoText), - "Ext Save Data ID: %016llX\n" - "Shared: %s", - info->extSaveDataId, - info->shared ? "Yes" : "No"); - - float infoWidth; - screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); - - float infoX = x1 + (x2 - x1 - infoWidth) / 2; - float infoY = y1 + (y2 - y1) / 2 - 8; - screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); -} - -void ui_draw_file_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { - file_info* info = (file_info*) data; - - char infoText[512]; - size_t infoTextPos = 0; - - if(strlen(info->name) > 48) { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Name: %.45s...\n", info->name); - } else { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Name: %.48s\n", info->name); - } - - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Attributes: "); - - if(info->attributes & (FS_ATTRIBUTE_DIRECTORY | FS_ATTRIBUTE_HIDDEN | FS_ATTRIBUTE_ARCHIVE | FS_ATTRIBUTE_READ_ONLY)) { - bool needsSeparator = false; - - if(info->attributes & FS_ATTRIBUTE_DIRECTORY) { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Directory"); - needsSeparator = true; - } - - if(info->attributes & FS_ATTRIBUTE_HIDDEN) { - if(needsSeparator) { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, ", "); - } - - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Hidden"); - needsSeparator = true; - } - - if(info->attributes & FS_ATTRIBUTE_ARCHIVE) { - if(needsSeparator) { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, ", "); - } - - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Archive"); - needsSeparator = true; - } - - if(info->attributes & FS_ATTRIBUTE_READ_ONLY) { - if(needsSeparator) { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, ", "); - } - - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Read Only"); - needsSeparator = true; - } - } else { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "None"); - } - - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "\n"); - - if(!(info->attributes & FS_ATTRIBUTE_DIRECTORY)) { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Size: %.2f %s\n", util_get_display_size(info->size), util_get_display_size_units(info->size)); - - if(info->isCia) { - char regionString[64]; - - if(info->ciaInfo.hasMeta) { - ui_draw_meta_info(view, &info->ciaInfo.meta, x1, y1, x2, y2); - - util_smdh_region_to_string(regionString, info->ciaInfo.meta.region, sizeof(regionString)); - } else { - snprintf(regionString, sizeof(regionString), "Unknown"); - } - - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, - "Title ID: %016llX\n" - "Version: %hu (%d.%d.%d)\n" - "Region: %s\n" - "Installed Size: %.2f %s", - info->ciaInfo.titleId, - info->ciaInfo.version, (info->ciaInfo.version >> 10) & 0x3F, (info->ciaInfo.version >> 4) & 0x3F, info->ciaInfo.version & 0xF, - regionString, - util_get_display_size(info->ciaInfo.installedSize), util_get_display_size_units(info->ciaInfo.installedSize)); - } else if(info->isTicket) { - infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Ticket ID: %016llX", info->ticketInfo.titleId); - } - } - - float infoWidth; - screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); - - float infoX = x1 + (x2 - x1 - infoWidth) / 2; - float infoY = y1 + (y2 - y1) / 2 - 8; - screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); -} - -void ui_draw_pending_title_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { - pending_title_info* info = (pending_title_info*) data; - - char infoText[512]; - - snprintf(infoText, sizeof(infoText), - "Pending Title ID: %016llX\n" - "Media Type: %s\n" - "Version: %hu (%d.%d.%d)", - info->titleId, - info->mediaType == MEDIATYPE_NAND ? "NAND" : info->mediaType == MEDIATYPE_SD ? "SD" : "Game Card", - info->version, (info->version >> 10) & 0x3F, (info->version >> 4) & 0x3F, info->version & 0xF); - - float infoWidth; - screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); - - float infoX = x1 + (x2 - x1 - infoWidth) / 2; - float infoY = y1 + (y2 - y1) / 2 - 8; - screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); -} - -void ui_draw_system_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { - system_save_data_info* info = (system_save_data_info*) data; - - char infoText[512]; - - snprintf(infoText, sizeof(infoText), "System Save Data ID: %08lX", info->systemSaveDataId); - - float infoWidth; - screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); - - float infoX = x1 + (x2 - x1 - infoWidth) / 2; - float infoY = y1 + (y2 - y1) / 2 - 8; - screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); -} - -void ui_draw_ticket_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { - ticket_info* info = (ticket_info*) data; - - char infoText[512]; - - snprintf(infoText, sizeof(infoText), "Title ID: %016llX", info->titleId); - - float infoWidth; - screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); - - float infoX = x1 + (x2 - x1 - infoWidth) / 2; - float infoY = y1 + (y2 - y1) / 2 - 8; - screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); -} - -void ui_draw_title_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { - title_info* info = (title_info*) data; - - char regionString[64]; - - if(info->hasMeta) { - ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2); - - util_smdh_region_to_string(regionString, info->meta.region, sizeof(regionString)); - } else { - snprintf(regionString, sizeof(regionString), "Unknown"); - } - - char infoText[512]; - - snprintf(infoText, sizeof(infoText), - "Title ID: %016llX\n" - "Media Type: %s\n" - "Version: %hu (%d.%d.%d)\n" - "Product Code: %s\n" - "Region: %s\n" - "Size: %.2f %s", - info->titleId, - info->mediaType == MEDIATYPE_NAND ? "NAND" : info->mediaType == MEDIATYPE_SD ? "SD" : "Game Card", - info->version, (info->version >> 10) & 0x3F, (info->version >> 4) & 0x3F, info->version & 0xF, - info->productCode, - regionString, - util_get_display_size(info->installedSize), util_get_display_size_units(info->installedSize)); - - float infoWidth; - screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); - - float infoX = x1 + (x2 - x1 - infoWidth) / 2; - float infoY = y1 + (y2 - y1) / 2 - 8; - screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); -} - -void ui_draw_titledb_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { - titledb_info* info = (titledb_info*) data; - - ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2); - - char updatedDate[32] = ""; - char updatedTime[32] = ""; - - sscanf(info->updatedAt, "%31[^T]T%31[^Z]Z", updatedDate, updatedTime); - - char infoText[512]; - - snprintf(infoText, sizeof(infoText), - "Title ID: %016llX\n" - "TitleDB Version: %s\n" - "Installed Version: %hu (%d.%d.%d)\n" - "Size: %.2f %s\n" - "Updated At: %s %s", - info->titleId, - info->version, - info->installedVersion, (info->installedVersion >> 10) & 0x3F, (info->installedVersion >> 4) & 0x3F, info->installedVersion & 0xF, - util_get_display_size(info->size), util_get_display_size_units(info->size), - updatedDate, updatedTime); - - float infoWidth; - screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); - - float infoX = x1 + (x2 - x1 - infoWidth) / 2; - float infoY = y1 + (y2 - y1) / 2 - 8; - screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); -} +#include +#include +#include + +#include <3ds.h> +#include + +#include "ui.h" +#include "section/task/task.h" +#include "../core/screen.h" +#include "../core/util.h" + +#define MAX_UI_VIEWS 16 + +static ui_view* ui_stack[MAX_UI_VIEWS]; +static int ui_stack_top = -1; + +static Handle ui_stack_mutex = 0; + +static u64 ui_free_space_last_update = 0; +static char ui_free_space_buffer[128]; + +static u64 ui_fade_begin_time = 0; +static u8 ui_fade_alpha = 0; + +void ui_init() { + if(ui_stack_mutex == 0) { + svcCreateMutex(&ui_stack_mutex, false); + } + + ui_fade_begin_time = osGetTime(); +} + +void ui_exit() { + if(ui_stack_mutex != 0) { + svcCloseHandle(ui_stack_mutex); + ui_stack_mutex = 0; + } +} + +ui_view* ui_create() { + ui_view* view = (ui_view*) calloc(1, sizeof(ui_view)); + if(view == NULL) { + util_panic("Failed to allocate UI view."); + return NULL; + } + + Result res = 0; + if(R_FAILED(res = svcCreateEvent(&view->active, RESET_STICKY))) { + util_panic("Failed to create view active event: 0x%08lX", res); + + free(view); + return NULL; + } + + return view; +} + +void ui_destroy(ui_view* view) { + if(view != NULL) { + svcCloseHandle(view->active); + free(view); + } +} + +ui_view* ui_top() { + svcWaitSynchronization(ui_stack_mutex, U64_MAX); + + ui_view* ui = NULL; + if(ui_stack_top >= 0) { + ui = ui_stack[ui_stack_top]; + } + + svcReleaseMutex(ui_stack_mutex); + + return ui; +} + +bool ui_push(ui_view* view) { + if(view == NULL) { + return false; + } + + svcWaitSynchronization(ui_stack_mutex, U64_MAX); + + bool space = ui_stack_top < MAX_UI_VIEWS - 1; + if(space) { + ui_stack[++ui_stack_top] = view; + + svcClearEvent(view->active); + } + + svcReleaseMutex(ui_stack_mutex); + + return space; +} + +void ui_pop() { + svcWaitSynchronization(ui_stack_mutex, U64_MAX); + + if(ui_stack_top >= 0) { + svcSignalEvent(ui_stack[ui_stack_top]->active); + + ui_stack[ui_stack_top--] = NULL; + } + + svcReleaseMutex(ui_stack_mutex); +} + +static void ui_draw_top(ui_view* ui) { + screen_select(GFX_TOP); + + u32 topScreenBgWidth = 0; + u32 topScreenBgHeight = 0; + screen_get_texture_size(&topScreenBgWidth, &topScreenBgHeight, TEXTURE_TOP_SCREEN_BG); + + u32 topScreenTopBarWidth = 0; + u32 topScreenTopBarHeight = 0; + screen_get_texture_size(&topScreenTopBarWidth, &topScreenTopBarHeight, TEXTURE_TOP_SCREEN_TOP_BAR); + + u32 topScreenTopBarShadowWidth = 0; + u32 topScreenTopBarShadowHeight = 0; + screen_get_texture_size(&topScreenTopBarShadowWidth, &topScreenTopBarShadowHeight, TEXTURE_TOP_SCREEN_TOP_BAR_SHADOW); + + u32 topScreenBottomBarWidth = 0; + u32 topScreenBottomBarHeight = 0; + screen_get_texture_size(&topScreenBottomBarWidth, &topScreenBottomBarHeight, TEXTURE_TOP_SCREEN_BOTTOM_BAR); + + u32 topScreenBottomBarShadowWidth = 0; + u32 topScreenBottomBarShadowHeight = 0; + screen_get_texture_size(&topScreenBottomBarShadowWidth, &topScreenBottomBarShadowHeight, TEXTURE_TOP_SCREEN_BOTTOM_BAR_SHADOW); + + screen_draw_texture(TEXTURE_TOP_SCREEN_BG, (TOP_SCREEN_WIDTH - topScreenBgWidth) / 2, (TOP_SCREEN_HEIGHT - topScreenBgHeight) / 2, topScreenBgWidth, topScreenBgHeight); + + if(ui->drawTop != NULL) { + ui->drawTop(ui, ui->data, 0, topScreenTopBarHeight, TOP_SCREEN_WIDTH, TOP_SCREEN_HEIGHT - topScreenBottomBarHeight); + } + + float topScreenTopBarX = (TOP_SCREEN_WIDTH - topScreenTopBarWidth) / 2; + float topScreenTopBarY = 0; + screen_draw_texture(TEXTURE_TOP_SCREEN_TOP_BAR, topScreenTopBarX, topScreenTopBarY, topScreenTopBarWidth, topScreenTopBarHeight); + screen_draw_texture(TEXTURE_TOP_SCREEN_TOP_BAR_SHADOW, topScreenTopBarX, topScreenTopBarY + topScreenTopBarHeight, topScreenTopBarShadowWidth, topScreenTopBarShadowHeight); + + float topScreenBottomBarX = (TOP_SCREEN_WIDTH - topScreenBottomBarWidth) / 2; + float topScreenBottomBarY = TOP_SCREEN_HEIGHT - topScreenBottomBarHeight; + screen_draw_texture(TEXTURE_TOP_SCREEN_BOTTOM_BAR, topScreenBottomBarX, topScreenBottomBarY, topScreenBottomBarWidth, topScreenBottomBarHeight); + screen_draw_texture(TEXTURE_TOP_SCREEN_BOTTOM_BAR_SHADOW, topScreenBottomBarX, topScreenBottomBarY - topScreenBottomBarShadowHeight, topScreenBottomBarShadowWidth, topScreenBottomBarShadowHeight); + + screen_set_base_alpha(ui_fade_alpha); + + char verText[64]; + snprintf(verText, 64, "Ver. %d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); + + float verWidth; + float verHeight; + screen_get_string_size(&verWidth, &verHeight, verText, 0.5f, 0.5f); + screen_draw_string(verText, topScreenTopBarX + 2, topScreenTopBarY + (topScreenTopBarHeight - verHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); + + time_t t = time(NULL); + char* timeText = ctime(&t); + timeText[strlen(timeText) - 1] = '\0'; + + float timeTextWidth; + float timeTextHeight; + screen_get_string_size(&timeTextWidth, &timeTextHeight, timeText, 0.5f, 0.5f); + screen_draw_string(timeText, topScreenTopBarX + (topScreenTopBarWidth - timeTextWidth) / 2, topScreenTopBarY + (topScreenTopBarHeight - timeTextHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); + + u32 batteryIcon = 0; + u8 batteryChargeState = 0; + u8 batteryLevel = 0; + if(R_SUCCEEDED(PTMU_GetBatteryChargeState(&batteryChargeState)) && batteryChargeState) { + batteryIcon = TEXTURE_BATTERY_CHARGING; + } else if(R_SUCCEEDED(PTMU_GetBatteryLevel(&batteryLevel))) { + batteryIcon = TEXTURE_BATTERY_0 + batteryLevel; + } else { + batteryIcon = TEXTURE_BATTERY_0; + } + + u32 batteryWidth; + u32 batteryHeight; + screen_get_texture_size(&batteryWidth, &batteryHeight, batteryIcon); + + float batteryX = topScreenTopBarX + topScreenTopBarWidth - 2 - batteryWidth; + float batteryY = topScreenTopBarY + (topScreenTopBarHeight - batteryHeight) / 2; + screen_draw_texture(batteryIcon, batteryX, batteryY, batteryWidth, batteryHeight); + + u32 wifiIcon = 0; + u32 wifiStatus = 0; + if(R_SUCCEEDED(ACU_GetWifiStatus(&wifiStatus)) && wifiStatus) { + wifiIcon = TEXTURE_WIFI_0 + osGetWifiStrength(); + } else { + wifiIcon = TEXTURE_WIFI_DISCONNECTED; + } + + u32 wifiWidth; + u32 wifiHeight; + screen_get_texture_size(&wifiWidth, &wifiHeight, wifiIcon); + + float wifiX = topScreenTopBarX + topScreenTopBarWidth - 2 - batteryWidth - 4 - wifiWidth; + float wifiY = topScreenTopBarY + (topScreenTopBarHeight - wifiHeight) / 2; + screen_draw_texture(wifiIcon, wifiX, wifiY, wifiWidth, wifiHeight); + + if(osGetTime() >= ui_free_space_last_update + 1000) { + char* currBuffer = ui_free_space_buffer; + FS_ArchiveResource resource = {0}; + + if(R_SUCCEEDED(FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_SD)) && currBuffer < ui_free_space_buffer + sizeof(ui_free_space_buffer)) { + if(currBuffer != ui_free_space_buffer) { + snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), ", "); + currBuffer += strlen(currBuffer); + } + + u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize; + snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), "SD: %.1f %s", util_get_display_size(size), util_get_display_size_units(size)); + currBuffer += strlen(currBuffer); + } + + if(R_SUCCEEDED(FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_CTR_NAND)) && currBuffer < ui_free_space_buffer + sizeof(ui_free_space_buffer)) { + if(currBuffer != ui_free_space_buffer) { + snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), ", "); + currBuffer += strlen(currBuffer); + } + + u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize; + snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), "CTR NAND: %.1f %s", util_get_display_size(size), util_get_display_size_units(size)); + currBuffer += strlen(currBuffer); + } + + if(R_SUCCEEDED(FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_TWL_NAND)) && currBuffer < ui_free_space_buffer + sizeof(ui_free_space_buffer)) { + if(currBuffer != ui_free_space_buffer) { + snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), ", "); + currBuffer += strlen(currBuffer); + } + + u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize; + snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), "TWL NAND: %.1f %s", util_get_display_size(size), util_get_display_size_units(size)); + currBuffer += strlen(currBuffer); + } + + if(R_SUCCEEDED(FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_TWL_PHOTO)) && currBuffer < ui_free_space_buffer + sizeof(ui_free_space_buffer)) { + if(currBuffer != ui_free_space_buffer) { + snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), ", "); + currBuffer += strlen(currBuffer); + } + + u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize; + snprintf(currBuffer, sizeof(ui_free_space_buffer) - (currBuffer - ui_free_space_buffer), "TWL Photo: %.1f %s", util_get_display_size(size), util_get_display_size_units(size)); + currBuffer += strlen(currBuffer); + } + + ui_free_space_last_update = osGetTime(); + } + + float freeSpaceHeight; + screen_get_string_size(NULL, &freeSpaceHeight, ui_free_space_buffer, 0.35f, 0.35f); + + screen_draw_string(ui_free_space_buffer, topScreenBottomBarX + 2, topScreenBottomBarY + (topScreenBottomBarHeight - freeSpaceHeight) / 2, 0.35f, 0.35f, COLOR_TEXT, true); + + screen_set_base_alpha(0xFF); +} + +static void ui_draw_bottom(ui_view* ui) { + screen_select(GFX_BOTTOM); + + u32 bottomScreenBgWidth = 0; + u32 bottomScreenBgHeight = 0; + screen_get_texture_size(&bottomScreenBgWidth, &bottomScreenBgHeight, TEXTURE_BOTTOM_SCREEN_BG); + + u32 bottomScreenTopBarWidth = 0; + u32 bottomScreenTopBarHeight = 0; + screen_get_texture_size(&bottomScreenTopBarWidth, &bottomScreenTopBarHeight, TEXTURE_BOTTOM_SCREEN_TOP_BAR); + + u32 bottomScreenTopBarShadowWidth = 0; + u32 bottomScreenTopBarShadowHeight = 0; + screen_get_texture_size(&bottomScreenTopBarShadowWidth, &bottomScreenTopBarShadowHeight, TEXTURE_BOTTOM_SCREEN_TOP_BAR_SHADOW); + + u32 bottomScreenBottomBarWidth = 0; + u32 bottomScreenBottomBarHeight = 0; + screen_get_texture_size(&bottomScreenBottomBarWidth, &bottomScreenBottomBarHeight, TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR); + + u32 bottomScreenBottomBarShadowWidth = 0; + u32 bottomScreenBottomBarShadowHeight = 0; + screen_get_texture_size(&bottomScreenBottomBarShadowWidth, &bottomScreenBottomBarShadowHeight, TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR_SHADOW); + + screen_draw_texture(TEXTURE_BOTTOM_SCREEN_BG, (BOTTOM_SCREEN_WIDTH - bottomScreenBgWidth) / 2, (BOTTOM_SCREEN_HEIGHT - bottomScreenBgHeight) / 2, bottomScreenBgWidth, bottomScreenBgHeight); + + screen_set_base_alpha(ui_fade_alpha); + + if(ui->drawBottom != NULL) { + ui->drawBottom(ui, ui->data, 0, bottomScreenTopBarHeight, BOTTOM_SCREEN_WIDTH, BOTTOM_SCREEN_HEIGHT - bottomScreenBottomBarHeight); + } + + screen_set_base_alpha(0xFF); + + float bottomScreenTopBarX = (BOTTOM_SCREEN_WIDTH - bottomScreenTopBarWidth) / 2; + float bottomScreenTopBarY = 0; + screen_draw_texture(TEXTURE_BOTTOM_SCREEN_TOP_BAR, bottomScreenTopBarX, bottomScreenTopBarY, bottomScreenTopBarWidth, bottomScreenTopBarHeight); + screen_draw_texture(TEXTURE_BOTTOM_SCREEN_TOP_BAR_SHADOW, bottomScreenTopBarX, bottomScreenTopBarY + bottomScreenTopBarHeight, bottomScreenTopBarShadowWidth, bottomScreenTopBarShadowHeight); + + float bottomScreenBottomBarX = (BOTTOM_SCREEN_WIDTH - bottomScreenBottomBarWidth) / 2; + float bottomScreenBottomBarY = BOTTOM_SCREEN_HEIGHT - bottomScreenBottomBarHeight; + screen_draw_texture(TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR, bottomScreenBottomBarX, bottomScreenBottomBarY, bottomScreenBottomBarWidth, bottomScreenBottomBarHeight); + screen_draw_texture(TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR_SHADOW, bottomScreenBottomBarX, bottomScreenBottomBarY - bottomScreenBottomBarShadowHeight, bottomScreenBottomBarShadowWidth, bottomScreenBottomBarShadowHeight); + + screen_set_base_alpha(ui_fade_alpha); + + if(ui->name != NULL) { + float nameWidth; + float nameHeight; + screen_get_string_size(&nameWidth, &nameHeight, ui->name, 0.5f, 0.5f); + screen_draw_string(ui->name, (BOTTOM_SCREEN_WIDTH - nameWidth) / 2, (bottomScreenTopBarHeight - nameHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); + } + + if(ui->info != NULL) { + float infoWidth; + float infoHeight; + screen_get_string_size(&infoWidth, &infoHeight, ui->info, 0.5f, 0.5f); + screen_draw_string(ui->info, (BOTTOM_SCREEN_WIDTH - infoWidth) / 2, BOTTOM_SCREEN_HEIGHT - (bottomScreenBottomBarHeight + infoHeight) / 2, 0.5f, 0.5f, COLOR_TEXT, true); + } + + screen_set_base_alpha(0xFF); +} + +bool ui_update() { + ui_view* ui = NULL; + + hidScanInput(); + + ui = ui_top(); + if(ui != NULL && ui->update != NULL) { + u32 bottomScreenTopBarHeight = 0; + screen_get_texture_size(NULL, &bottomScreenTopBarHeight, TEXTURE_BOTTOM_SCREEN_TOP_BAR); + + u32 bottomScreenBottomBarHeight = 0; + screen_get_texture_size(NULL, &bottomScreenBottomBarHeight, TEXTURE_BOTTOM_SCREEN_BOTTOM_BAR); + + ui->update(ui, ui->data, 0, bottomScreenTopBarHeight, BOTTOM_SCREEN_WIDTH, BOTTOM_SCREEN_HEIGHT - bottomScreenBottomBarHeight); + } + + u64 time = osGetTime(); + if(!envIsHomebrew() && time - ui_fade_begin_time < 500) { + ui_fade_alpha = (u8) (((time - ui_fade_begin_time) / 500.0f) * 0xFF); + } else { + ui_fade_alpha = 0xFF; + } + + ui = ui_top(); + if(ui != NULL) { + screen_begin_frame(); + ui_draw_top(ui); + ui_draw_bottom(ui); + screen_end_frame(); + } + + return ui != NULL; +} + +void ui_draw_meta_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + meta_info* info = (meta_info*) data; + + u32 metaInfoBoxShadowWidth; + u32 metaInfoBoxShadowHeight; + screen_get_texture_size(&metaInfoBoxShadowWidth, &metaInfoBoxShadowHeight, TEXTURE_META_INFO_BOX_SHADOW); + + float metaInfoBoxShadowX = x1 + (x2 - x1 - metaInfoBoxShadowWidth) / 2; + float metaInfoBoxShadowY = y1 + (y2 - y1) / 4 - metaInfoBoxShadowHeight / 2; + screen_draw_texture(TEXTURE_META_INFO_BOX_SHADOW, metaInfoBoxShadowX, metaInfoBoxShadowY, metaInfoBoxShadowWidth, metaInfoBoxShadowHeight); + + u32 metaInfoBoxWidth; + u32 metaInfoBoxHeight; + screen_get_texture_size(&metaInfoBoxWidth, &metaInfoBoxHeight, TEXTURE_META_INFO_BOX); + + float metaInfoBoxX = x1 + (x2 - x1 - metaInfoBoxWidth) / 2; + float metaInfoBoxY = y1 + (y2 - y1) / 4 - metaInfoBoxHeight / 2; + screen_draw_texture(TEXTURE_META_INFO_BOX, metaInfoBoxX, metaInfoBoxY, metaInfoBoxWidth, metaInfoBoxHeight); + + if(info->texture != 0) { + u32 iconWidth; + u32 iconHeight; + screen_get_texture_size(&iconWidth, &iconHeight, info->texture); + + float iconX = metaInfoBoxX + (64 - iconWidth) / 2; + float iconY = metaInfoBoxY + (metaInfoBoxHeight - iconHeight) / 2; + screen_draw_texture(info->texture, iconX, iconY, iconWidth, iconHeight); + } + + float metaTextX = metaInfoBoxX + 64; + + float shortDescriptionHeight; + screen_get_string_size_wrap(NULL, &shortDescriptionHeight, info->shortDescription, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX); + + float longDescriptionHeight; + screen_get_string_size_wrap(NULL, &longDescriptionHeight, info->longDescription, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX); + + float publisherHeight; + screen_get_string_size_wrap(NULL, &publisherHeight, info->publisher, 0.5f, 0.5f, metaInfoBoxX + metaInfoBoxWidth - 8 - metaTextX); + + float shortDescriptionY = metaInfoBoxY + (64 - shortDescriptionHeight - 2 - longDescriptionHeight - 2 - publisherHeight) / 2; + screen_draw_string_wrap(info->shortDescription, metaTextX, shortDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8); + + float longDescriptionY = shortDescriptionY + shortDescriptionHeight + 2; + screen_draw_string_wrap(info->longDescription, metaTextX, longDescriptionY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8); + + float publisherY = longDescriptionY + longDescriptionHeight + 2; + screen_draw_string_wrap(info->publisher, metaTextX, publisherY, 0.5f, 0.5f, COLOR_TEXT, false, metaInfoBoxX + metaInfoBoxWidth - 8); +} + +void ui_draw_ext_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + ext_save_data_info* info = (ext_save_data_info*) data; + + if(info->hasMeta) { + ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2); + } + + char infoText[512]; + + snprintf(infoText, sizeof(infoText), + "Ext Save Data ID: %016llX\n" + "Shared: %s", + info->extSaveDataId, + info->shared ? "Yes" : "No"); + + float infoWidth; + screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); + + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); +} + +void ui_draw_file_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + file_info* info = (file_info*) data; + + char infoText[512]; + size_t infoTextPos = 0; + + if(strlen(info->name) > 48) { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Name: %.45s...\n", info->name); + } else { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Name: %.48s\n", info->name); + } + + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Attributes: "); + + if(info->attributes & (FS_ATTRIBUTE_DIRECTORY | FS_ATTRIBUTE_HIDDEN | FS_ATTRIBUTE_ARCHIVE | FS_ATTRIBUTE_READ_ONLY)) { + bool needsSeparator = false; + + if(info->attributes & FS_ATTRIBUTE_DIRECTORY) { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Directory"); + needsSeparator = true; + } + + if(info->attributes & FS_ATTRIBUTE_HIDDEN) { + if(needsSeparator) { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, ", "); + } + + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Hidden"); + needsSeparator = true; + } + + if(info->attributes & FS_ATTRIBUTE_ARCHIVE) { + if(needsSeparator) { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, ", "); + } + + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Archive"); + needsSeparator = true; + } + + if(info->attributes & FS_ATTRIBUTE_READ_ONLY) { + if(needsSeparator) { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, ", "); + } + + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Read Only"); + needsSeparator = true; + } + } else { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "None"); + } + + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "\n"); + + if(!(info->attributes & FS_ATTRIBUTE_DIRECTORY)) { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Size: %.2f %s\n", util_get_display_size(info->size), util_get_display_size_units(info->size)); + + if(info->isCia) { + char regionString[64]; + + if(info->ciaInfo.hasMeta) { + ui_draw_meta_info(view, &info->ciaInfo.meta, x1, y1, x2, y2); + + util_smdh_region_to_string(regionString, info->ciaInfo.meta.region, sizeof(regionString)); + } else { + snprintf(regionString, sizeof(regionString), "Unknown"); + } + + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, + "Title ID: %016llX\n" + "Version: %hu (%d.%d.%d)\n" + "Region: %s\n" + "Installed Size: %.2f %s", + info->ciaInfo.titleId, + info->ciaInfo.version, (info->ciaInfo.version >> 10) & 0x3F, (info->ciaInfo.version >> 4) & 0x3F, info->ciaInfo.version & 0xF, + regionString, + util_get_display_size(info->ciaInfo.installedSize), util_get_display_size_units(info->ciaInfo.installedSize)); + } else if(info->isTicket) { + infoTextPos += snprintf(infoText + infoTextPos, sizeof(infoText) - infoTextPos, "Ticket ID: %016llX", info->ticketInfo.titleId); + } + } + + float infoWidth; + screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); + + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); +} + +void ui_draw_pending_title_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + pending_title_info* info = (pending_title_info*) data; + + char infoText[512]; + + snprintf(infoText, sizeof(infoText), + "Pending Title ID: %016llX\n" + "Media Type: %s\n" + "Version: %hu (%d.%d.%d)", + info->titleId, + info->mediaType == MEDIATYPE_NAND ? "NAND" : info->mediaType == MEDIATYPE_SD ? "SD" : "Game Card", + info->version, (info->version >> 10) & 0x3F, (info->version >> 4) & 0x3F, info->version & 0xF); + + float infoWidth; + screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); + + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); +} + +void ui_draw_system_save_data_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + system_save_data_info* info = (system_save_data_info*) data; + + char infoText[512]; + + snprintf(infoText, sizeof(infoText), "System Save Data ID: %08lX", info->systemSaveDataId); + + float infoWidth; + screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); + + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); +} + +void ui_draw_ticket_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + ticket_info* info = (ticket_info*) data; + + char infoText[512]; + + snprintf(infoText, sizeof(infoText), "Title ID: %016llX", info->titleId); + + float infoWidth; + screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); + + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); +} + +void ui_draw_title_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + title_info* info = (title_info*) data; + + char regionString[64]; + + if(info->hasMeta) { + ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2); + + util_smdh_region_to_string(regionString, info->meta.region, sizeof(regionString)); + } else { + snprintf(regionString, sizeof(regionString), "Unknown"); + } + + char infoText[512]; + + snprintf(infoText, sizeof(infoText), + "Title ID: %016llX\n" + "Media Type: %s\n" + "Version: %hu (%d.%d.%d)\n" + "Product Code: %s\n" + "Region: %s\n" + "Size: %.2f %s", + info->titleId, + info->mediaType == MEDIATYPE_NAND ? "NAND" : info->mediaType == MEDIATYPE_SD ? "SD" : "Game Card", + info->version, (info->version >> 10) & 0x3F, (info->version >> 4) & 0x3F, info->version & 0xF, + info->productCode, + regionString, + util_get_display_size(info->installedSize), util_get_display_size_units(info->installedSize)); + + float infoWidth; + screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); + + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); +} + +void ui_draw_titledb_info(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + titledb_info* info = (titledb_info*) data; + + ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2); + + char infoText[1024]; + + snprintf(infoText, sizeof(infoText), + "%s\n" + "\n" + "Category: %s\n", + info->headline, + info->category); + + float infoWidth; + screen_get_string_size_wrap(&infoWidth, NULL, infoText, 0.5f, 0.5f, x2 - x1 - 10); + + // TODO: Wrap by word, not character? + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string_wrap(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true, infoX + infoWidth + 1); +} + +void ui_draw_titledb_info_cia(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + titledb_info* info = (titledb_info*) data; + + ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2); + + char updatedDate[32] = ""; + char updatedTime[32] = ""; + + sscanf(info->cia.updatedAt, "%31[^T]T%31[^Z]Z", updatedDate, updatedTime); + + char infoText[512]; + + snprintf(infoText, sizeof(infoText), + "Title ID: %016llX\n" + "TitleDB Version: %s\n" + "Installed Version: %hu (%d.%d.%d)\n" + "Size: %.2f %s\n" + "Updated At: %s %s", + info->cia.titleId, + info->cia.version, + info->cia.installedVersion, (info->cia.installedVersion >> 10) & 0x3F, (info->cia.installedVersion >> 4) & 0x3F, info->cia.installedVersion & 0xF, + util_get_display_size(info->cia.size), util_get_display_size_units(info->cia.size), + updatedDate, updatedTime); + + float infoWidth; + screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); + + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); +} + +void ui_draw_titledb_info_tdsx(ui_view* view, void* data, float x1, float y1, float x2, float y2) { + titledb_info* info = (titledb_info*) data; + + ui_draw_meta_info(view, &info->meta, x1, y1, x2, y2); + + char updatedDate[32] = ""; + char updatedTime[32] = ""; + + sscanf(info->tdsx.updatedAt, "%31[^T]T%31[^Z]Z", updatedDate, updatedTime); + + char infoText[512]; + + snprintf(infoText, sizeof(infoText), + "TitleDB Version: %s\n" + "Size: %.2f %s\n" + "Updated At: %s %s", + info->tdsx.version, + util_get_display_size(info->tdsx.size), util_get_display_size_units(info->tdsx.size), + updatedDate, updatedTime); + + float infoWidth; + screen_get_string_size(&infoWidth, NULL, infoText, 0.5f, 0.5f); + + float infoX = x1 + (x2 - x1 - infoWidth) / 2; + float infoY = y1 + (y2 - y1) / 2 - 8; + screen_draw_string(infoText, infoX, infoY, 0.5f, 0.5f, COLOR_TEXT, true); +} \ No newline at end of file diff --git a/source/ui/ui.h b/source/ui/ui.h index 2548f5e..7543a87 100644 --- a/source/ui/ui.h +++ b/source/ui/ui.h @@ -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); \ No newline at end of file +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); \ No newline at end of file