From a2b5788fe8d3896f77b087d003dc074c0488b6f2 Mon Sep 17 00:00:00 2001 From: LiquidFenrir Date: Sun, 1 Apr 2018 02:31:46 +0200 Subject: [PATCH] Themeplaza browser (#140) * builds at least * meh, multithreading will come later. or never * movement added, and correct grid mode * switching splash/themes when in browser mode * closer to the actual themeplaza menu * bring back downloading from qr * show a download screen when downloading from browser * fix selecting with touchscreen in browser mode * update readme for jansson * fix quitting with start in browser mode * add jump menu for browser mode * rotate is broken, add working touchscreen page changing * allow quitting preview mode with B in browser mode * proper way to have portlibs * add searching * show error when search has no results * always free entries and icon ids --- Makefile | 4 +- README.md | 4 +- include/camera.h | 1 - include/common.h | 18 +- include/draw.h | 7 +- include/loading.h | 17 +- include/remote.h | 57 ++++ romfs/arrow_side.png | Bin 0 -> 2961 bytes romfs/browse.png | Bin 0 -> 654 bytes romfs/exit.png | Bin 0 -> 390 bytes romfs/list.png | Bin 0 -> 179 bytes romfs/reload.png | Bin 358 -> 0 bytes source/camera.c | 173 ++-------- source/draw.c | 209 +++++++++--- source/fs.c | 8 +- source/loading.c | 77 +++-- source/main.c | 224 +++++++------ source/remote.c | 763 +++++++++++++++++++++++++++++++++++++++++++ 18 files changed, 1212 insertions(+), 350 deletions(-) create mode 100644 include/remote.h create mode 100644 romfs/arrow_side.png create mode 100644 romfs/browse.png create mode 100644 romfs/exit.png create mode 100644 romfs/list.png delete mode 100644 romfs/reload.png create mode 100644 source/remote.c diff --git a/Makefile b/Makefile index a495d37..b3609ed 100644 --- a/Makefile +++ b/Makefile @@ -65,13 +65,13 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 ASFLAGS := -g $(ARCH) LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) -LIBS := -lcitro3d -lctrud -lm -lz +LIBS := -ljansson -lcitro3d -lctrud -lm -lz #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing # include and lib #--------------------------------------------------------------------------------- -LIBDIRS := $(CTRULIB) $(DEVKITPRO)/portlibs/armv6k +LIBDIRS := $(CTRULIB) $(PORTLIBS) #--------------------------------------------------------------------------------- diff --git a/README.md b/README.md index b4d9354..3771914 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ A Theme and Splashscreen Manager for the Nintendo 3DS, written in C.\ To-do list here: https://trello.com/b/F1YSa1VK # Dependencies - * zlib, which can be retrieved from the [3ds_portlibs](https://github.com/devkitPro/3ds_portlibs). + * zlib and jansson, which can be retrieved from the [3ds_portlibs](https://github.com/devkitPro/3ds_portlibs). * [makerom](https://github.com/profi200/Project_CTR) and [bannertool](https://github.com/Steveice10/buildtools), which can be retrieved from [SteveIce10's](https://github.com/Steveice10) buildtools repo. These must be added to your PATH. * ~~[pp2d](https://github.com/BernardoGiordano/pp2d), which is included in the repo if you do a git clone --recursive.~~ Due to circumstances surrounding the privacy settings on the pp2d repo, the source files are now included directly within the repo. * Git needs to be on your PATH, if building in a non-*nix environment. # Building First of all, make sure devkitPRO is properly installed and added to the PATH. After that, open the directory you want to clone the repo into, and type: `git clone https://github.com/astronautlevel2/Anemone3DS/ --recursive`. -Instructions for installing zlib can be found on the [3ds_portlibs repo](https://github.com/devkitPro/3ds_portlibs) (its easy, just run `make` and `make install-zlib`). After also adding [makerom](https://github.com/profi200/Project_CTR) and [bannertool](https://github.com/Steveice10/buildtools) to your PATH, just enter your directory and run `make`. All built files will be in `/out/`. +Instructions for installing zlib and jansson can be found on the [3ds_portlibs repo](https://github.com/devkitPro/3ds_portlibs) (its easy, just run `make` and `make install-zlib`). After also adding [makerom](https://github.com/profi200/Project_CTR) and [bannertool](https://github.com/Steveice10/buildtools) to your PATH, just enter your directory and run `make`. All built files will be in `/out/`. # License This project is licensed under the GNU GPLv3. See LICENSE.md for details. Additional terms 7b and 7c apply to this project. diff --git a/include/camera.h b/include/camera.h index 90da56a..bf9b4f4 100644 --- a/include/camera.h +++ b/include/camera.h @@ -44,6 +44,5 @@ typedef struct { bool init_qr(EntryMode current_mode); void exit_qr(qr_data *data); void take_picture(void); -Result http_get(char *url, const char *path); #endif \ No newline at end of file diff --git a/include/common.h b/include/common.h index 4b35d1e..b8fc25b 100644 --- a/include/common.h +++ b/include/common.h @@ -33,8 +33,6 @@ #include #include -#define ENTRIES_PER_SCREEN 4 - #define DEBUG(...) fprintf(stderr, __VA_ARGS__) #define POS() DEBUG("%s (line %d)...\n", __func__, __LINE__) @@ -42,6 +40,8 @@ POS(); \ DEBUG(__VA_ARGS__) +#define FASTSCROLL_WAIT 1.5e8 + typedef enum { MODE_THEMES = 0, MODE_SPLASHES, @@ -50,15 +50,22 @@ typedef enum { } EntryMode; extern const char * main_paths[MODE_AMOUNT]; +extern const int entries_per_screen_v[MODE_AMOUNT]; +extern const int entries_per_screen_h[MODE_AMOUNT]; +extern const int entry_size[MODE_AMOUNT]; +extern bool quit; enum TextureID { TEXTURE_FONT_RESERVED = 0, // used by pp2d for the font TEXTURE_ARROW, + TEXTURE_ARROW_SIDE, TEXTURE_SHUFFLE, TEXTURE_INSTALLED, TEXTURE_PREVIEW_ICON, TEXTURE_DOWNLOAD, - TEXTURE_RELOAD, + TEXTURE_BROWSE, + TEXTURE_LIST, + TEXTURE_EXIT, TEXTURE_BATTERY_0, TEXTURE_BATTERY_1, TEXTURE_BATTERY_2, @@ -68,10 +75,13 @@ enum TextureID { TEXTURE_BATTERY_CHARGE, TEXTURE_QR, TEXTURE_PREVIEW, + TEXTURE_REMOTE_PREVIEW, TEXTURE_SELECT_BUTTON, TEXTURE_START_BUTTON, - TEXTURE_ICON, // always the last + // always the last + TEXTURE_REMOTE_ICONS, + TEXTURE_ICON = TEXTURE_REMOTE_ICONS + 24, }; #endif diff --git a/include/draw.h b/include/draw.h index dba47cc..8db1c8c 100644 --- a/include/draw.h +++ b/include/draw.h @@ -47,6 +47,10 @@ typedef enum { INSTALL_DOWNLOAD, INSTALL_ENTRY_DELETE, + INSTALL_LOADING_REMOTE_THEMES, + INSTALL_LOADING_REMOTE_SPLASHES, + INSTALL_LOADING_REMOTE_PREVIEW, + INSTALL_NONE, } InstallType; @@ -84,11 +88,12 @@ void exit_screens(void); void throw_error(char* error, ErrorLevel level); bool draw_confirm(const char* conf_msg, Entry_List_s* list); -void draw_preview(int preview_offset); +void draw_preview(ssize_t previewID, int preview_offset); void draw_install(InstallType type); void draw_base_interface(void); +void draw_grid_interface(Entry_List_s* list, Instructions_s instructions); void draw_interface(Entry_List_s* list, Instructions_s instructions); #endif \ No newline at end of file diff --git a/include/loading.h b/include/loading.h index 40df206..f63bf33 100644 --- a/include/loading.h +++ b/include/loading.h @@ -28,6 +28,7 @@ #define LOADING_H #include "common.h" +#include enum ICON_IDS_OFFSET { ICONS_ABOVE = 0, @@ -62,6 +63,8 @@ typedef struct { bool in_shuffle; bool installed; + + json_int_t tp_download_id; } Entry_s; typedef struct { @@ -69,7 +72,7 @@ typedef struct { int entries_count; ssize_t texture_id_offset; - ssize_t icons_ids[ICONS_OFFSET_AMOUNT][ENTRIES_PER_SCREEN]; + ssize_t * icons_ids; int previous_scroll; int scroll; @@ -80,6 +83,14 @@ typedef struct { int shuffle_count; EntryMode mode; + int entries_per_screen_v; + int entries_per_screen_h; + int entries_loaded; + int entry_size; + + json_int_t tp_current_page; + json_int_t tp_page_count; + char * tp_search; } Entry_List_s; typedef struct { @@ -87,8 +98,8 @@ typedef struct { volatile bool run_thread; } Thread_Arg_s; -void delete_entry(Entry_s entry); -Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mode); +void delete_entry(Entry_s * entry, bool is_file); +Result load_entries(const char * loading_path, Entry_List_s * list); bool load_preview(Entry_List_s list, int * preview_offset); void load_icons_first(Entry_List_s * current_list, bool silent); void handle_scrolling(Entry_List_s * list); diff --git a/include/remote.h b/include/remote.h new file mode 100644 index 0000000..9fc9593 --- /dev/null +++ b/include/remote.h @@ -0,0 +1,57 @@ +/* +* This file is part of Anemone3DS +* Copyright (C) 2016-2017 Alex Taber ("astronautlevel"), Dawid Eckert ("daedreth") +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#ifndef REMOTE_H +#define REMOTE_H + +#include "common.h" + +#define THEMEPLAZA_BASE_URL "https://themeplaza.eu" +#define THEMEPLAZA_API_URL "/api/anemone/v1" +#define THEMEPLAZA_BASE_API_URL THEMEPLAZA_BASE_URL THEMEPLAZA_API_URL + +#define THEMEPLAZA_PAGE_FORMAT THEMEPLAZA_BASE_API_URL "/list?page=%" JSON_INTEGER_FORMAT "&category=%i&query=%s" +#define THEMEPLAZA_JSON_PAGE_COUNT "pages" +#define THEMEPLAZA_JSON_PAGE_IDS "items" + +#define THEMEPLAZA_JSON_ERROR_MESSAGE "message" +#define THEMEPLAZA_JSON_ERROR_MESSAGE_NOT_FOUND "No items found" + + +#define THEMEPLAZA_ENTRY_FORMAT THEMEPLAZA_BASE_API_URL "/query?item_id=%" JSON_INTEGER_FORMAT +#define THEMEPLAZA_JSON_ENTRY_NAME "title" +#define THEMEPLAZA_JSON_ENTRY_DESC "description" +#define THEMEPLAZA_JSON_ENTRY_AUTH "author" + +#define THEMEPLAZA_DOWNLOAD_FORMAT THEMEPLAZA_BASE_URL "/download/%" JSON_INTEGER_FORMAT +#define THEMEPLAZA_PREVIEW_FORMAT THEMEPLAZA_DOWNLOAD_FORMAT "/preview" +#define THEMEPLAZA_BGM_FORMAT THEMEPLAZA_DOWNLOAD_FORMAT "/bgm" +#define THEMEPLAZA_ICON_FORMAT THEMEPLAZA_DOWNLOAD_FORMAT "/preview/icon" + +bool themeplaza_browser(EntryMode mode); +u32 http_get(const char *url, char ** filename, char ** buf); + +#endif diff --git a/romfs/arrow_side.png b/romfs/arrow_side.png new file mode 100644 index 0000000000000000000000000000000000000000..d44c0bfbdd88924a68ba38eb20ad0d0185c19884 GIT binary patch literal 2961 zcmV;C3vTp@P)004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( zX+uL$P-t&-Z*ypGa3D!TLm+T+Z)Rz1WdHz3$DNjUR8-d%htIutdZEoQ0#b(FyTAa_ zdy`&8VVD_UC<6{NG_fI~0ue<-nj%P0#DLLIBvwSR5EN9f2P6n6F&ITuEN@2Ei>|D^ z_ww@lRz|vC zuzLs)$;-`!o*{AqUjza0dRV*yaMRE;fKCVhpQKsoe1Yhg01=zBIT!&C1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz*<$V$@wQAMN5qJ)hzm2h zoGcOF60t^#FqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su;^hF~NC{tY+*d5%WDCTX za!E_i;d2ub1#}&jF5T4HnnCyEWTkKf0>c0%E1Ah>(_PY1)0w;+02c53Su*0<(nUqK zG_|(0G&D0Z{i;y^b@OjZ+}lNZ8Th$p5Uu}MTtq^NHl*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*PvzoDiKdLpOAxi2$L0#SX*@cY z_n(^h55xYX#km%V()bZjV~l{*bt*u9?FT3d5g^g~#a;iSZ@&02Abxq_DwB(I|L-^b zXThc7C4-yrInE_0gw7K3GZ**7&k~>k0Z0NWkO#^@9q0fwx1%qj zZ=)yBuQ3=54Wo^*!gyjLF-e%Um=erBOdIALW)L%unZshS@>qSW9o8Sq#0s#5*edK% z>{;v(b^`kbN5rY%%y90wC>#%$kE_5P!JWYk;U;klcqzOl-UjcFXXA75rT9jCH~u<) z0>40zCTJ7v2qAyk54cquI@7b&LHdZ`+zlTss6bJ7%PQ)z$cROu4wBhpu-r)01) zS~6}jY?%U?gEALn#wiFzo#H}aQ8rT=DHkadR18&{>P1bW7E`~Y4p3)hWn`DhhRJ5j z*2tcg9i<^OEt(fCg;q*CP8+7ZTcWhYX$fb^_9d-LhL+6BEtPYWVlfKTBusSTASKKb%HuWJzl+By+?gkLq)?+BTu761 zjmyXF)a;mc^>(B7bo*HQ1NNg1st!zt28YLv>W*y3CdWx9U8f|cqfXDAO`Q48?auQq zHZJR2&bcD49Ip>EY~kKEPV6Wm+eXFV)D)_R=tM0@&p?(!V*Qu1PXHG9o^ zTY0bZ?)4%01p8F`JoeS|<@=<@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK z8LKk71XR(_RKHM1xJ5XYX`fk>`6eqY>qNG6HZQwBM=xi4&Sb88?zd}EYguc1@>KIS z<&CX#T35dwS|7K*XM_5Nf(;WJJvJWRMA($P>8E^?{IdL4o5MGE7bq2MEEwP7v8AO@ zqL5!WvekBL-8R%V?zVyL=G&{be=K4bT`e{#t|)$A!YaA?jp;X)-+bB;zhj`(vULAW z%ue3U;av{94wp%n<(7@__S@Z2PA@Mif3+uO&y|X06?J#oSi8M;ejj_^(0<4Lt#wLu#dYrva1Y$6_o(k^&}yhSh&h;f@JVA>W8b%o zZ=0JGnu?n~9O4}sJsfnnx7n(>`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8N zo_-(u{qS+0<2@%BCt82d{Gqm;(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-U zsyQuty7Ua;Ou?B?XLHZaol8GAb3Wnxcu!2v{R_`T4=x`(GvqLI{-*2AOSimk zUAw*F_TX^n@STz9kDQ z$NC=!KfXWC8h`dn#xL(D3Z9UkR7|Q&Hcy#Notk!^zVUSB(}`#4&lYA1f0h2V_PNgU zAAWQEt$#LRcH#y9#i!p(Udq2b^lI6wp1FXzN3T;~FU%Lck$-deE#qz9yYP3D3t8{6 z?<+s(e(3(_^YOu_)K8!O1p}D#{JO;G(*OVf32;bRa{vGf5&!@T5&_cPe*6Fc00(qQ zO+^Rc2?P`hAOCiFasU7T8FWQhbW?9;ba!ELWdLwtX>N2bZe?^JG%heMGBAcGSpxt7 z0E$UOK~yMHW55Rf|NjrhP_`Ek2ZA}+08ml|h?f8{2*g5#A*R3pkUbrUe}hy&K`gQr zARG>lQ6T;c#4vzRhz@{~mOy+8tPGbzpahf!W|#l}|34f6|DFy@y%fJA00000NkvXX Hu0mjf`$Bcx literal 0 HcmV?d00001 diff --git a/romfs/browse.png b/romfs/browse.png new file mode 100644 index 0000000000000000000000000000000000000000..a31564aa52998c8b79a1e590c0e2b14f3abb487c GIT binary patch literal 654 zcmV;90&)F`P)(^b8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10t-n*K~zYI?Ul<*RZ$eie_JxNh|(~#ODRzi;jFO+iZm!{R-@(- z)u`xCsB!duOe835l9L8KNvH-k5bq!&YN`hLC@3Z4*Wj!R$HVnrtEu1Qu)g!{^{u`4 zI_tnH>LAq#Ko8Iw^fWL645gHw{FAe~Pkn2K-|>8-juyDr>fXOien6d3r_@vG zusWxbSa1U8nv~FXuGZgKJVtYFYiN?keV6Q^n&p&;mRL z&H^1kGtjAC3QZ}v8^9zm4s-)If&N<65BUG+%jaw;cpL|2fTk3Ps=lNGLU%0}Hv_Yg zV_Ab9;CV`EA;)b2CaP?B5@@dkDW!SfCD5A}74M(}*rdM8al3)Cx)C@E909t39YCp| z-3~O>GH3wi$+lm?VpKQ>3<85{H*g5(W9cch0UN69HiP;>eOTaz)v;XM63wu-%KH22 z`&tHfqMsXc+%0u77pEv>EgIUYsP3s{P_IoVBFVV|zo?F==hfzA4Z`GAEV`mz zP_IUOFXEEfxL7Ed{8CTWOdbZ6QBhVq)%EI6bE6`34+;ilwXLo}EbfiFm{CuwkJMSS zVYgN7Qv0hM;b*fBTan35^;s;ejOI~wOucA!*&oeD`eF5ZXr8XfAZIk7-c>)T^Ko1+ o!r+Y2HR^t||3!PfoU2%ZKj?;?j(aT63IG5A07*qoM6N<$f-~+V8UO$Q literal 0 HcmV?d00001 diff --git a/romfs/exit.png b/romfs/exit.png new file mode 100644 index 0000000000000000000000000000000000000000..b933ba9923864890570cc46985f01ab176a544a8 GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#Vf4nJ za3z8;64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq!<_&nLS+` zLp(aKPTlLv>?qQ*-*I|Wlh_J|lZu_z3I*%}I~ctR7}d74w758VF-~bv%_?AaE8=9~|{OJ8k}o4Sy%TdglAYjO}vB z=S$TmCf+xh<;eGaTfXYv=Nz%>I~nff+~-Z*aD9i|r3WfSk~+Eh%wK!OeeWhP*RZy% h{$slF_Isy|%sOG)@=t!9_y`zg44$rjF6*2UngAJXn@j)z literal 0 HcmV?d00001 diff --git a/romfs/list.png b/romfs/list.png new file mode 100644 index 0000000000000000000000000000000000000000..2c2cab028c92ef57003a5c2de197068311004791 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#Vf4nJ za3z8;64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1SHJY5_^ zJUWvTBv=p287LM1`~SbbL95Nk$m+lipDP9hB0PBpv%1_EpC*`y_BJUpFyyfEaM^@U Rwg&2D@O1TaS?83{1OP75F;f5l literal 0 HcmV?d00001 diff --git a/romfs/reload.png b/romfs/reload.png deleted file mode 100644 index 23c6ca93a126e810554743d0dcea2ed4565b5d69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR40`{g=uU#K7NaQs*DL6VbDaEiIRXh;V!2VMpcQWtN+hw=8 zgxuEtF#i1i-`$D-Ca~l*Jrzpja8HO z?BQIrq`Oemb@6X&0Wq;KLGudngpgIj?9IXk9_KzECjQ6gVIraW;-vRV4gQu&X%Q~loCIHiui8ufN diff --git a/source/camera.c b/source/camera.c index 3f6c617..2e4726a 100644 --- a/source/camera.c +++ b/source/camera.c @@ -32,6 +32,7 @@ #include "draw.h" #include "fs.h" #include "loading.h" +#include "remote.h" /* static u32 transfer_size; @@ -194,7 +195,25 @@ void update_qr(qr_data *data, EntryMode current_mode) if (!quirc_decode(&code, &scan_data)) { exit_qr(data); - http_get((char*)scan_data.payload, main_paths[current_mode]); + + draw_install(INSTALL_DOWNLOAD); + char * zip_buf = NULL; + char * filename = NULL; + u32 zip_size = http_get((char*)scan_data.payload, &filename, &zip_buf); + + char path_to_file[0x107] = {0}; + sprintf(path_to_file, "%s%s", main_paths[current_mode], filename); + free(filename); + + char * extension = strrchr(path_to_file, '.'); + if (extension == NULL || strcmp(extension, ".zip")) + strcat(path_to_file, ".zip"); + + DEBUG("Saving to sd: %s\n", path_to_file); + remake_file(path_to_file, ArchiveSD, zip_size); + buf_to_file(zip_size, path_to_file, ArchiveSD, zip_buf); + free(zip_buf); + data->success = true; } } @@ -216,155 +235,3 @@ bool init_qr(EntryMode current_mode) return (bool)data->success; } - -/* -Putting this in camera because I'm too lazy to make a network.c -This'll probably get refactored later -*/ -Result http_get(char *url, const char *path) -{ - Result ret; - httpcContext context; - char *new_url = NULL; - u32 status_code; - u32 content_size = 0; - u32 read_size = 0; - u32 size = 0; - u8 *buf; - u8 *last_buf; - - do { - ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 1); - ret = httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); // should let us do https - ret = httpcSetKeepAlive(&context, HTTPC_KEEPALIVE_ENABLED); - ret = httpcAddRequestHeaderField(&context, "User-Agent", USER_AGENT); - ret = httpcAddRequestHeaderField(&context, "Connection", "Keep-Alive"); - draw_install(INSTALL_DOWNLOAD); - - ret = httpcBeginRequest(&context); - if (ret != 0) - { - httpcCloseContext(&context); - if (new_url != NULL) free(new_url); - return ret; - } - - ret = httpcGetResponseStatusCode(&context, &status_code); - if(ret!=0){ - httpcCloseContext(&context); - if(new_url!=NULL) free(new_url); - return ret; - } - - if ((status_code >= 301 && status_code <= 303) || (status_code >= 307 && status_code <= 308)) - { - if (new_url == NULL) new_url = malloc(0x1000); - ret = httpcGetResponseHeader(&context, "Location", new_url, 0x1000); - url = new_url; - httpcCloseContext(&context); - } - } while ((status_code >= 301 && status_code <= 303) || (status_code >= 307 && status_code <= 308)); - - if (status_code != 200) - { - httpcCloseContext(&context); - if (new_url != NULL) free(new_url); - return ret; - } - - ret = httpcGetDownloadSizeState(&context, NULL, &content_size); - if (ret != 0) - { - httpcCloseContext(&context); - if (new_url != NULL) free(new_url); - return ret; - } - - buf = malloc(0x1000); - if (buf == NULL) - { - httpcCloseContext(&context); - free(new_url); - return -2; - } - - char *content_disposition = malloc(1024); - ret = httpcGetResponseHeader(&context, "Content-Disposition", content_disposition, 1024); - if (ret != 0) - { - free(content_disposition); - free(new_url); - free(buf); - return ret; - } - - char *filename; - filename = strtok(content_disposition, "\""); - filename = strtok(NULL, "\""); - - char *illegal_characters = "\"?;:/\\+"; - if(!filename) - { - free(content_disposition); - free(new_url); - free(buf); - throw_error("Target is not valid!", ERROR_LEVEL_WARNING); - return -1; - } - for (size_t i = 0; i < strlen(filename); i++) - { - for (size_t n = 0; n < strlen(illegal_characters); n++) - { - if (filename[i] == illegal_characters[n]) - { - filename[i] = '-'; - } - } - } - - do { - ret = httpcDownloadData(&context, buf + size, 0x1000, &read_size); - size += read_size; - - if (ret == (s32)HTTPC_RESULTCODE_DOWNLOADPENDING) - { - last_buf = buf; - buf = realloc(buf, size + 0x1000); - if (buf == NULL) - { - httpcCloseContext(&context); - free(content_disposition); - free(new_url); - free(last_buf); - return ret; - } - } - } while (ret == (s32)HTTPC_RESULTCODE_DOWNLOADPENDING); - - last_buf = buf; - buf = realloc(buf, size); - if (buf == NULL) - { - httpcCloseContext(&context); - free(content_disposition); - free(new_url); - free(last_buf); - return -1; - } - - char path_to_file[0x106] = {0}; - strcpy(path_to_file, path); - strcat(path_to_file, filename); - char * extension = strrchr(path_to_file, '.'); - if (extension == NULL || strcmp(extension, ".zip")) - strcat(path_to_file, ".zip"); - - remake_file(path_to_file, ArchiveSD, size); - buf_to_file(size, path_to_file, ArchiveSD, (char*)buf); - - free(content_disposition); - free(new_url); - free(buf); - - return 0; -} diff --git a/source/draw.c b/source/draw.c index 6f3d5b2..c046868 100644 --- a/source/draw.c +++ b/source/draw.c @@ -40,11 +40,14 @@ void init_screens(void) pp2d_set_screen_color(GFX_BOTTOM, COLOR_BACKGROUND); pp2d_load_texture_png(TEXTURE_ARROW, "romfs:/arrow.png"); + pp2d_load_texture_png(TEXTURE_ARROW_SIDE, "romfs:/arrow_side.png"); pp2d_load_texture_png(TEXTURE_SHUFFLE, "romfs:/shuffle.png"); pp2d_load_texture_png(TEXTURE_INSTALLED, "romfs:/installed.png"); - pp2d_load_texture_png(TEXTURE_RELOAD, "romfs:/reload.png"); - pp2d_load_texture_png(TEXTURE_PREVIEW_ICON, "romfs:/preview.png"); pp2d_load_texture_png(TEXTURE_DOWNLOAD, "romfs:/download.png"); + pp2d_load_texture_png(TEXTURE_PREVIEW_ICON, "romfs:/preview.png"); + pp2d_load_texture_png(TEXTURE_BROWSE, "romfs:/browse.png"); + pp2d_load_texture_png(TEXTURE_LIST, "romfs:/list.png"); + pp2d_load_texture_png(TEXTURE_EXIT, "romfs:/exit.png"); pp2d_load_texture_png(TEXTURE_BATTERY_0, "romfs:/battery0.png"); pp2d_load_texture_png(TEXTURE_BATTERY_1, "romfs:/battery1.png"); pp2d_load_texture_png(TEXTURE_BATTERY_2, "romfs:/battery2.png"); @@ -169,12 +172,12 @@ bool draw_confirm(const char* conf_msg, Entry_List_s* list) return false; } -void draw_preview(int preview_offset) +void draw_preview(ssize_t previewID, int preview_offset) { pp2d_begin_draw(GFX_TOP, GFX_LEFT); - pp2d_draw_texture_part(TEXTURE_PREVIEW, 0, 0, preview_offset, 0, 400, 240); + pp2d_draw_texture_part(previewID, 0, 0, preview_offset, 0, 400, 240); pp2d_draw_on(GFX_BOTTOM, GFX_LEFT); - pp2d_draw_texture_part(TEXTURE_PREVIEW, 0, 0, 40 + preview_offset, 240, 320, 240); + pp2d_draw_texture_part(previewID, 0, 0, 40 + preview_offset, 240, 320, 240); } void draw_install(InstallType type) @@ -191,6 +194,15 @@ void draw_install(InstallType type) case INSTALL_LOADING_ICONS: pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Loading icons, please wait..."); break; + case INSTALL_LOADING_REMOTE_THEMES: + pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Downloading theme list, please wait..."); + break; + case INSTALL_LOADING_REMOTE_SPLASHES: + pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Downloading splash list, please wait..."); + break; + case INSTALL_LOADING_REMOTE_PREVIEW: + pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Downloading preview, please wait..."); + break; case INSTALL_SINGLE: pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Installing a single theme..."); break; @@ -257,6 +269,109 @@ static void draw_instructions(Instructions_s instructions) } } +static void draw_entry_info(Entry_s * entry) +{ + float wrap = 363; + + wchar_t author[0x41] = {0}; + utf16_to_utf32((u32*)author, entry->author, 0x40); + pp2d_draw_text(20, 35, 0.5, 0.5, COLOR_WHITE, "By "); + pp2d_draw_wtext_wrap(40, 35, 0.5, 0.5, COLOR_WHITE, wrap, author); + + wchar_t title[0x41] = {0}; + utf16_to_utf32((u32*)title, entry->name, 0x40); + pp2d_draw_wtext_wrap(20, 50, 0.7, 0.7, COLOR_WHITE, wrap, title); + + int width = (int)pp2d_get_wtext_width(title, 0.7, 0.7); + int height = (int)pp2d_get_wtext_height(title, 0.7, 0.7); + int count = ((width - (width % (int)wrap))/wrap) + 1; + + wchar_t description[0x81] = {0}; + utf16_to_utf32((u32*)description, entry->desc, 0x80); + pp2d_draw_wtext_wrap(20, 50+count*height, 0.5, 0.5, COLOR_WHITE, wrap, description); +} + +void draw_grid_interface(Entry_List_s* list, Instructions_s instructions) +{ + draw_base_interface(); + EntryMode current_mode = list->mode; + + const char* mode_string[MODE_AMOUNT] = { + "ThemePlaza Theme mode", + "ThemePlaza Splash mode", + }; + + pp2d_draw_text_center(GFX_TOP, 4, 0.5, 0.5, COLOR_WHITE, mode_string[current_mode]); + + draw_instructions(instructions); + + int selected_entry = list->selected_entry; + Entry_s * current_entry = &list->entries[selected_entry]; + draw_entry_info(current_entry); + + pp2d_draw_on(GFX_BOTTOM, GFX_LEFT); + + pp2d_draw_text(7, 3, 0.6, 0.6, COLOR_WHITE, "Search..."); + + pp2d_draw_texture_blend(TEXTURE_LIST, 320-96, 0, COLOR_WHITE); + pp2d_draw_texture_blend(TEXTURE_EXIT, 320-72, 0, COLOR_WHITE); + pp2d_draw_texture_blend(TEXTURE_PREVIEW_ICON, 320-48, 0, COLOR_WHITE); + pp2d_draw_textf(320-24+2.5, -3, 1, 1, COLOR_WHITE, "%c", mode_string[!list->mode][11]); + + pp2d_draw_texture(TEXTURE_ARROW_SIDE, 3, 114); + pp2d_draw_texture_flip(TEXTURE_ARROW_SIDE, 308, 114, HORIZONTAL); + + for(int i = list->scroll; i < (list->entries_loaded + list->scroll); i++) + { + if(i >= list->entries_count) break; + + current_entry = &list->entries[i]; + + wchar_t name[0x41] = {0}; + utf16_to_utf32((u32*)name, current_entry->name, 0x40); + + int vertical_offset = 0; + int horizontal_offset = i - list->scroll; + vertical_offset = horizontal_offset/list->entries_per_screen_h; + horizontal_offset %= list->entries_per_screen_h; + + horizontal_offset *= list->entry_size; + vertical_offset *= list->entry_size; + vertical_offset += 24; + horizontal_offset += 16; + + if(!current_entry->placeholder_color) + { + ssize_t id = list->icons_ids[i]; + pp2d_draw_texture(id, horizontal_offset, vertical_offset); + } + else + pp2d_draw_rectangle(horizontal_offset, vertical_offset, list->entry_size, list->entry_size, current_entry->placeholder_color); + + if(i == selected_entry) + { + unsigned int border_width = 3; + pp2d_draw_rectangle(horizontal_offset, vertical_offset, border_width, list->entry_size, COLOR_CURSOR); + pp2d_draw_rectangle(horizontal_offset, vertical_offset, list->entry_size, border_width, COLOR_CURSOR); + pp2d_draw_rectangle(horizontal_offset, vertical_offset+list->entry_size-border_width, list->entry_size, border_width, COLOR_CURSOR); + pp2d_draw_rectangle(horizontal_offset+list->entry_size-border_width, vertical_offset, border_width, list->entry_size, COLOR_CURSOR); + } + } + + char entries_count_str[0x20] = {0}; + sprintf(entries_count_str, "/%" JSON_INTEGER_FORMAT, list->tp_page_count); + float x = 316; + x -= pp2d_get_text_width(entries_count_str, 0.6, 0.6); + pp2d_draw_text(x, 219, 0.6, 0.6, COLOR_WHITE, entries_count_str); + + char selected_entry_str[0x20] = {0}; + sprintf(selected_entry_str, "%" JSON_INTEGER_FORMAT, list->tp_current_page); + x -= pp2d_get_text_width(selected_entry_str, 0.6, 0.6); + pp2d_draw_text(x, 219, 0.6, 0.6, COLOR_WHITE, selected_entry_str); + + pp2d_draw_text(176, 219, 0.6, 0.6, COLOR_WHITE, "Page:"); +} + void draw_interface(Entry_List_s* list, Instructions_s instructions) { draw_base_interface(); @@ -287,26 +402,22 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions) pp2d_texture_blend(COLOR_YELLOW); pp2d_texture_scale(1.25, 1.4); pp2d_texture_draw(); + + pp2d_draw_on(GFX_BOTTOM, GFX_LEFT); + + pp2d_draw_texture_blend(TEXTURE_EXIT, 320-120, 0, COLOR_WHITE); + pp2d_draw_texture_blend(TEXTURE_DOWNLOAD, 320-96, 0, COLOR_WHITE); + for(int i = 0; i < MODE_AMOUNT; i++) + pp2d_draw_textf(320-(24*(i+1))+2.5, -3, 1, 1, COLOR_WHITE, "%c", mode_string[i][0]); + return; } draw_instructions(instructions); int selected_entry = list->selected_entry; - Entry_s current_entry = list->entries[selected_entry]; - - wchar_t title[0x41] = {0}; - utf16_to_utf32((u32*)title, current_entry.name, 0x40); - pp2d_draw_wtext_wrap(20, 30, 0.7, 0.7, COLOR_WHITE, 380, title); - - wchar_t author[0x41] = {0}; - utf16_to_utf32((u32*)author, current_entry.author, 0x40); - pp2d_draw_text(20, 50, 0.5, 0.5, COLOR_WHITE, "By: "); - pp2d_draw_wtext_wrap(44, 50, 0.5, 0.5, COLOR_WHITE, 380, author); - - wchar_t description[0x81] = {0}; - utf16_to_utf32((u32*)description, current_entry.desc, 0x80); - pp2d_draw_wtext_wrap(20, 65, 0.5, 0.5, COLOR_WHITE, 363, description); + Entry_s * current_entry = &list->entries[selected_entry]; + draw_entry_info(current_entry); pp2d_draw_on(GFX_BOTTOM, GFX_LEFT); @@ -314,59 +425,65 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions) { case MODE_THEMES: pp2d_draw_textf(7, 3, 0.6, 0.6, list->shuffle_count <= 10 && list->shuffle_count >= 2 ? COLOR_WHITE : COLOR_RED, "Shuffle: %i/10", list->shuffle_count); - pp2d_draw_texture_blend(TEXTURE_SHUFFLE, 320-120, 0, COLOR_WHITE); break; default: break; } - pp2d_draw_texture_blend(TEXTURE_RELOAD, 320-96, 0, COLOR_WHITE); - pp2d_draw_texture_blend(TEXTURE_PREVIEW_ICON, 320-72, 0, COLOR_WHITE); - pp2d_draw_texture_blend(TEXTURE_DOWNLOAD, 320-48, 0, COLOR_WHITE); - pp2d_draw_textf(320-24+2.5, -3, 1, 1, COLOR_WHITE, "%c", *mode_string[!list->mode]); - + pp2d_draw_texture_blend(TEXTURE_DOWNLOAD, 320-120, 0, COLOR_WHITE); + pp2d_draw_texture_blend(TEXTURE_BROWSE, 320-96, 0, COLOR_WHITE); + pp2d_draw_texture_blend(TEXTURE_EXIT, 320-72, 0, COLOR_WHITE); + pp2d_draw_texture_blend(TEXTURE_PREVIEW_ICON, 320-48, 0, COLOR_WHITE); + pp2d_draw_textf(320-24+2.5, -3, 1, 1, COLOR_WHITE, "%c", mode_string[!list->mode][0]); // Show arrows if there are themes out of bounds //---------------------------------------------------------------- if(list->scroll > 0) - pp2d_draw_texture(TEXTURE_ARROW, 155, 6); - if(list->scroll + ENTRIES_PER_SCREEN < list->entries_count) - pp2d_draw_texture_flip(TEXTURE_ARROW, 155, 224, VERTICAL); + pp2d_draw_texture(TEXTURE_ARROW, 152, 4); + if(list->scroll + list->entries_loaded < list->entries_count) + pp2d_draw_texture_flip(TEXTURE_ARROW, 152, 220, VERTICAL); - for(int i = list->scroll; i < (ENTRIES_PER_SCREEN + list->scroll); i++) + for(int i = list->scroll; i < (list->entries_loaded + list->scroll); i++) { if(i >= list->entries_count) break; - current_entry = list->entries[i]; + current_entry = &list->entries[i]; wchar_t name[0x41] = {0}; - utf16_to_utf32((u32*)name, current_entry.name, 0x40); + utf16_to_utf32((u32*)name, current_entry->name, 0x40); + + int vertical_offset = i - list->scroll; + int horizontal_offset = 0; + horizontal_offset *= list->entry_size; + vertical_offset *= list->entry_size; + vertical_offset += 24; - int vertical_offset = 48 * (i - list->scroll); u32 font_color = COLOR_WHITE; - if(i == list->selected_entry) + if(i == selected_entry) { font_color = COLOR_BLACK; - pp2d_draw_rectangle(0, 24 + vertical_offset, 320, 48, COLOR_CURSOR); + pp2d_draw_rectangle(0, vertical_offset, 320, list->entry_size, COLOR_CURSOR); } - pp2d_draw_wtext(54, 40 + vertical_offset, 0.55, 0.55, font_color, name); - if(!current_entry.placeholder_color) + + pp2d_draw_wtext(list->entry_size+6, vertical_offset + 16, 0.55, 0.55, font_color, name); + + if(current_entry->in_shuffle) + pp2d_draw_texture_blend(TEXTURE_SHUFFLE, 320-24-4, vertical_offset, font_color); + if(current_entry->installed) + pp2d_draw_texture_blend(TEXTURE_INSTALLED, 320-24-4, vertical_offset + 22, font_color); + + if(!current_entry->placeholder_color) { ssize_t id = 0; - if(list->entries_count > ICONS_OFFSET_AMOUNT*ENTRIES_PER_SCREEN) - id = list->icons_ids[ICONS_VISIBLE][i - list->scroll]; + if(list->entries_count > list->entries_loaded*ICONS_OFFSET_AMOUNT) + id = list->icons_ids[ICONS_VISIBLE*list->entries_loaded + (i - list->scroll)]; else - id = ((size_t *)list->icons_ids)[i]; - pp2d_draw_texture(id, 0, 24 + vertical_offset); + id = list->icons_ids[i]; + pp2d_draw_texture(id, horizontal_offset, vertical_offset); } else - pp2d_draw_rectangle(0, 24 + vertical_offset, 48, 48, current_entry.placeholder_color); - - if(current_entry.in_shuffle) - pp2d_draw_texture_blend(TEXTURE_SHUFFLE, 320-24-4, 24 + vertical_offset, font_color); - if(current_entry.installed) - pp2d_draw_texture_blend(TEXTURE_INSTALLED, 320-24-4, 24 + 22 + vertical_offset, font_color); + pp2d_draw_rectangle(horizontal_offset, vertical_offset, list->entry_size, list->entry_size, current_entry->placeholder_color); } char entries_count_str[0x20] = {0}; diff --git a/source/fs.c b/source/fs.c index 8a890c8..d460aaa 100644 --- a/source/fs.c +++ b/source/fs.c @@ -122,16 +122,14 @@ u32 file_to_buf(FS_Path path, FS_Archive archive, char** buf) u32 zip_file_to_buf(char *file_name, u16 *zip_path, char **buf) { - ssize_t len = strulen(zip_path, 0x106); - char *path = calloc(sizeof(char), len*sizeof(u16)); - utf16_to_utf8((u8*)path, zip_path, len*sizeof(u16)); + char path[0x107] = {0}; + utf16_to_utf8((u8*)path, zip_path, 0x106); unzFile zip_handle = unzOpen(path); - free(path); if(zip_handle == NULL) { - DEBUG("invalid zip being opened\n"); + DEBUG("invalid zip being opened: %s, %s\n", path, file_name); return 0; } diff --git a/source/loading.c b/source/loading.c index 3486b55..7f3ceb6 100644 --- a/source/loading.c +++ b/source/loading.c @@ -30,12 +30,12 @@ #include "unicode.h" #include "draw.h" -void delete_entry(Entry_s entry) +void delete_entry(Entry_s * entry, bool is_file) { - if(entry.is_zip) - FSUSER_DeleteFile(ArchiveSD, fsMakePath(PATH_UTF16, entry.path)); + if(is_file) + FSUSER_DeleteFile(ArchiveSD, fsMakePath(PATH_UTF16, entry->path)); else - FSUSER_DeleteDirectoryRecursively(ArchiveSD, fsMakePath(PATH_UTF16, entry.path)); + FSUSER_DeleteDirectoryRecursively(ArchiveSD, fsMakePath(PATH_UTF16, entry->path)); } u32 load_data(char * filename, Entry_s entry, char ** buf) @@ -114,15 +114,19 @@ static int compare_entries(const void * a, const void * b) static void sort_list(Entry_List_s * list) { - qsort(list->entries, list->entries_count, sizeof(Entry_s), compare_entries); //alphabet sort + if(list->entries != NULL) + qsort(list->entries, list->entries_count, sizeof(Entry_s), compare_entries); //alphabet sort } -Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mode) +Result load_entries(const char * loading_path, Entry_List_s * list) { Handle dir_handle; Result res = FSUSER_OpenDirectory(&dir_handle, ArchiveSD, fsMakePath(PATH_ASCII, loading_path)); if(R_FAILED(res)) + { + DEBUG("Failed to open folder: %s\n", loading_path); return res; + } u32 entries_read = 1; @@ -133,7 +137,7 @@ Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mo if(R_FAILED(res) || entries_read == 0) break; - if(!(dir_entry.attributes & FS_ATTRIBUTE_DIRECTORY) && strcmp(dir_entry.shortExt, "ZIP")) + if(!(dir_entry.attributes & FS_ATTRIBUTE_DIRECTORY) && strcmp(dir_entry.shortExt, "ZIP")) continue; list->entries_count++; @@ -143,6 +147,7 @@ Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mo free(list->entries); list->entries = NULL; res = -1; + DEBUG("break\n"); break; } else @@ -155,14 +160,12 @@ Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mo strucat(current_entry->path, dir_entry.name); current_entry->is_zip = !strcmp(dir_entry.shortExt, "ZIP"); - parse_smdh(current_entry, dir_entry.name); } FSDIR_Close(dir_handle); sort_list(list); - list->mode = mode; return res; } @@ -176,7 +179,7 @@ void load_icons_first(Entry_List_s * list, bool silent) int starti = 0, endi = 0; - if(list->entries_count <= ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT) + if(list->entries_count <= list->entries_loaded*ICONS_OFFSET_AMOUNT) { DEBUG("small load\n"); // if the list is one that doesnt need swapping, load everything at once @@ -186,14 +189,15 @@ void load_icons_first(Entry_List_s * list, bool silent) { DEBUG("extended load\n"); // otherwise, load around to prepare for swapping - starti = list->scroll - ENTRIES_PER_SCREEN*ICONS_VISIBLE; - endi = starti + ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT; + starti = list->scroll - list->entries_loaded*ICONS_VISIBLE; + endi = starti + list->entries_loaded*ICONS_OFFSET_AMOUNT; } - ssize_t * icon_ids = (ssize_t *)list->icons_ids; + list->icons_ids = calloc(endi-starti, sizeof(ssize_t)); + + ssize_t * icons_ids = list->icons_ids; ssize_t id = list->texture_id_offset; - memset(icon_ids, 0, ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT*sizeof(ssize_t)); for(int i = starti; i < endi; i++, id++) { int offset = i; @@ -204,7 +208,8 @@ void load_icons_first(Entry_List_s * list, bool silent) Entry_s current_entry = list->entries[offset]; load_smdh_icon(current_entry, id); - icon_ids[i-starti] = id; + + icons_ids[i-starti] = id; } } @@ -229,21 +234,21 @@ void handle_scrolling(Entry_List_s * list) { // Scroll the menu up or down if the selected theme is out of its bounds //---------------------------------------------------------------- - if(list->entries_count > ENTRIES_PER_SCREEN) + if(list->entries_count > list->entries_loaded) { for(int i = 0; i < list->entries_count; i++) { int change = 0; - if(list->entries_count > ENTRIES_PER_SCREEN*2 && list->previous_scroll < ENTRIES_PER_SCREEN && list->selected_entry >= list->entries_count - ENTRIES_PER_SCREEN) + if(list->entries_count > list->entries_loaded*2 && list->previous_scroll < list->entries_loaded && list->selected_entry >= list->entries_count - list->entries_loaded) { - list->scroll = list->entries_count - ENTRIES_PER_SCREEN; + list->scroll = list->entries_count - list->entries_loaded; } - else if(list->entries_count > ENTRIES_PER_SCREEN*2 && list->selected_entry < ENTRIES_PER_SCREEN && list->previous_selected >= list->entries_count - ENTRIES_PER_SCREEN) + else if(list->entries_count > list->entries_loaded*2 && list->selected_entry < list->entries_loaded && list->previous_selected >= list->entries_count - list->entries_loaded) { list->scroll = 0; } - else if(list->selected_entry == list->previous_selected+1 && list->selected_entry == list->scroll+ENTRIES_PER_SCREEN) + else if(list->selected_entry == list->previous_selected+1 && list->selected_entry == list->scroll+list->entries_loaded) { change = 1; } @@ -251,21 +256,21 @@ void handle_scrolling(Entry_List_s * list) { change = -1; } - else if(list->selected_entry == list->previous_selected+ENTRIES_PER_SCREEN || list->selected_entry >= list->scroll + ENTRIES_PER_SCREEN) + else if(list->selected_entry == list->previous_selected+list->entries_loaded || list->selected_entry >= list->scroll + list->entries_loaded) { - change = ENTRIES_PER_SCREEN; + change = list->entries_loaded; } - else if(list->selected_entry == list->previous_selected-ENTRIES_PER_SCREEN || list->selected_entry < list->scroll) + else if(list->selected_entry == list->previous_selected-list->entries_loaded || list->selected_entry < list->scroll) { - change = -ENTRIES_PER_SCREEN; + change = -list->entries_loaded; } list->scroll += change; if(list->scroll < 0) list->scroll = 0; - else if(list->scroll > list->entries_count - ENTRIES_PER_SCREEN) - list->scroll = list->entries_count - ENTRIES_PER_SCREEN; + else if(list->scroll > list->entries_count - list->entries_loaded) + list->scroll = list->entries_count - list->entries_loaded; if(!change) list->previous_selected = list->selected_entry; @@ -283,13 +288,13 @@ static void load_icons(Entry_List_s * current_list) handle_scrolling(current_list); - if(current_list->entries_count <= ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT || current_list->previous_scroll == current_list->scroll) + if(current_list->entries_count <= current_list->entries_loaded*ICONS_OFFSET_AMOUNT || current_list->previous_scroll == current_list->scroll) return; // return if the list is one that doesnt need swapping, or if nothing changed #define SIGN(x) (x > 0 ? 1 : ((x < 0) ? -1 : 0)) int delta = current_list->scroll - current_list->previous_scroll; - if(abs(delta) >= current_list->entries_count - ENTRIES_PER_SCREEN*(ICONS_OFFSET_AMOUNT-1)) + if(abs(delta) >= current_list->entries_count - current_list->entries_loaded*(ICONS_OFFSET_AMOUNT-1)) delta = -SIGN(delta) * (current_list->entries_count - abs(delta)); int starti = current_list->scroll; @@ -306,26 +311,26 @@ static void load_icons(Entry_List_s * current_list) ssize_t * ids = calloc(abs(delta), sizeof(ssize_t)); #define FIRST(arr) arr[0] - #define LAST(arr) arr[ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT - 1] + #define LAST(arr) arr[current_list->entries_loaded*ICONS_OFFSET_AMOUNT - 1] - ssize_t * icons_ids = (ssize_t *)current_list->icons_ids; + ssize_t * icons_ids = current_list->icons_ids; for(int i = starti; i != endi; i++, ctr++) { ssize_t id = 0; int offset = i; - rotate(icons_ids, ICONS_OFFSET_AMOUNT*ENTRIES_PER_SCREEN, -1*SIGN(delta)); + rotate(icons_ids, ICONS_OFFSET_AMOUNT*current_list->entries_loaded, -1*SIGN(delta)); if(delta > 0) { id = LAST(icons_ids); - offset += ENTRIES_PER_SCREEN*ICONS_UNDER - delta; + offset += current_list->entries_loaded*ICONS_UNDER - delta; } else { id = FIRST(icons_ids); - offset -= ENTRIES_PER_SCREEN*ICONS_VISIBLE; + offset -= current_list->entries_loaded*ICONS_VISIBLE; i -= 2; //i-- twice to counter the i++, needed only for this case } @@ -344,7 +349,11 @@ static void load_icons(Entry_List_s * current_list) svcSleepThread(1e6); for(int i = 0; i < abs(delta); i++) - load_smdh_icon(*entries[i], ids[i]); + { + Entry_s current_entry = *entries[i]; + ssize_t id = ids[i]; + load_smdh_icon(current_entry, id); + } free(entries); free(ids); diff --git a/source/main.c b/source/main.c index ac354c6..6ebe6b0 100644 --- a/source/main.c +++ b/source/main.c @@ -30,12 +30,12 @@ #include "splashes.h" #include "draw.h" #include "camera.h" +#include "remote.h" #include "instructions.h" #include "pp2d/pp2d/pp2d.h" #include -#define FASTSCROLL_WAIT 1.5e8 - +bool quit = false; static bool homebrew = false; static bool installed_themes = false; @@ -56,6 +56,18 @@ const char * main_paths[MODE_AMOUNT] = { "/Themes/", "/Splashes/", }; +const int entries_per_screen_v[MODE_AMOUNT] = { + 4, + 4, +}; +const int entries_per_screen_h[MODE_AMOUNT] = { //for themeplaza browser + 6, + 6, +}; +const int entry_size[MODE_AMOUNT] = { + 48, + 48, +}; static void init_services(void) { @@ -89,10 +101,7 @@ static void stop_install_check(void) { for(int i = 0; i < MODE_AMOUNT; i++) { - if(installCheckThreads_arg[i].run_thread) - { - installCheckThreads_arg[i].run_thread = false; - } + installCheckThreads_arg[i].run_thread = false; } } @@ -115,6 +124,7 @@ void free_lists(void) { Entry_List_s * current_list = &lists[i]; free(current_list->entries); + free(current_list->icons_ids); memset(current_list, 0, sizeof(Entry_List_s)); } exit_thread(); @@ -140,6 +150,70 @@ void exit_function(bool power_pressed) } } +static void start_thread(void) +{ + if(iconLoadingThread_arg.run_thread) + { + DEBUG("starting thread\n"); + iconLoadingThread = threadCreate(load_icons_thread, &iconLoadingThread_arg, __stacksize__, 0x38, -2, false); + } +} + +static void load_lists(Entry_List_s * lists) +{ + ssize_t texture_id_offset = TEXTURE_ICON; + + free_lists(); + for(int i = 0; i < MODE_AMOUNT; i++) + { + InstallType loading_screen = INSTALL_NONE; + if(i == MODE_THEMES) + loading_screen = INSTALL_LOADING_THEMES; + else if(i == MODE_SPLASHES) + loading_screen = INSTALL_LOADING_SPLASHES; + + draw_install(loading_screen); + draw_install(loading_screen); + + Entry_List_s * current_list = &lists[i]; + current_list->mode = i; + current_list->entries_per_screen_v = entries_per_screen_v[i]; + current_list->entries_per_screen_h = 1; + current_list->entries_loaded = current_list->entries_per_screen_v * current_list->entries_per_screen_h; + current_list->entry_size = entry_size[i]; + Result res = load_entries(main_paths[i], current_list); + if(R_SUCCEEDED(res)) + { + if(current_list->entries_count > current_list->entries_loaded*ICONS_OFFSET_AMOUNT) + iconLoadingThread_arg.run_thread = true; + + DEBUG("total: %i\n", current_list->entries_count); + + current_list->texture_id_offset = texture_id_offset; + load_icons_first(current_list, false); + + texture_id_offset += current_list->entries_loaded*ICONS_OFFSET_AMOUNT; + + void (*install_check_function)(void*) = NULL; + if(i == MODE_THEMES) + install_check_function = themes_check_installed; + else if(i == MODE_SPLASHES) + install_check_function = splash_check_installed; + + Thread_Arg_s * current_arg = &installCheckThreads_arg[i]; + current_arg->run_thread = true; + current_arg->thread_arg = (void**)current_list; + + if(install_check_function != NULL) + { + installCheckThreads[i] = threadCreate(install_check_function, current_arg, __stacksize__, 0x3f, -2, true); + svcSleepThread(1e8); + } + } + } + start_thread(); +} + static SwkbdCallbackResult jump_menu_callback(void* entries_count, const char** ppMessage, const char* text, size_t textlen) { int typed_value = atoi(text); @@ -184,6 +258,9 @@ static void jump_menu(Entry_List_s * list) if(button == SWKBD_BUTTON_CONFIRM) { list->selected_entry = atoi(numbuf) - 1; + list->scroll = list->selected_entry; + if(list->scroll >= list->entries_count - list->entries_loaded) + list->scroll = list->entries_count - list->entries_loaded - 1; } } @@ -191,66 +268,13 @@ static void change_selected(Entry_List_s * list, int change_value) { if(abs(change_value) >= list->entries_count) return; - list->selected_entry += change_value; - if(list->selected_entry < 0) - list->selected_entry += list->entries_count; - list->selected_entry %= list->entries_count; -} + int newval = list->selected_entry + change_value; -static void start_thread(void) -{ - if(iconLoadingThread_arg.run_thread) - { - DEBUG("starting thread\n"); - iconLoadingThread = threadCreate(load_icons_thread, &iconLoadingThread_arg, __stacksize__, 0x38, -2, false); - } -} + if(newval < 0) + newval += list->entries_count; + newval %= list->entries_count; -static void load_lists(Entry_List_s * lists) -{ - ssize_t texture_id_offset = TEXTURE_ICON; - - free_lists(); - for(int i = 0; i < MODE_AMOUNT; i++) - { - InstallType loading_screen = INSTALL_NONE; - if(i == MODE_THEMES) - loading_screen = INSTALL_LOADING_THEMES; - else if(i == MODE_SPLASHES) - loading_screen = INSTALL_LOADING_SPLASHES; - - draw_install(loading_screen); - draw_install(loading_screen); - - Entry_List_s * current_list = &lists[i]; - Result res = load_entries(main_paths[i], current_list, i); - if(R_SUCCEEDED(res)) - { - if(current_list->entries_count > ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT) - iconLoadingThread_arg.run_thread = true; - - DEBUG("total: %i\n", current_list->entries_count); - - current_list->texture_id_offset = texture_id_offset; - load_icons_first(current_list, false); - - texture_id_offset += ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT; - - void (*install_check_function)(void*) = NULL; - if(i == MODE_THEMES) - install_check_function = themes_check_installed; - else if(i == MODE_SPLASHES) - install_check_function = splash_check_installed; - - Thread_Arg_s * current_arg = &installCheckThreads_arg[i]; - current_arg->run_thread = true; - current_arg->thread_arg = (void**)current_list; - - installCheckThreads[i] = threadCreate(install_check_function, current_arg, __stacksize__, 0x3f, -2, true); - svcSleepThread(1e8); - } - } - start_thread(); + list->selected_entry = newval; } static void toggle_shuffle(Entry_List_s * list) @@ -292,8 +316,6 @@ int main(void) bool qr_mode = false; bool install_mode = false; - bool quit = false; - while(aptMainLoop()) { if(quit) @@ -323,7 +345,7 @@ int main(void) instructions = install_instructions; if(qr_mode) take_picture(); - else if(preview_mode) draw_preview(preview_offset); + else if(preview_mode) draw_preview(TEXTURE_PREVIEW, preview_offset); else { if(!iconLoadingThread_arg.run_thread) { @@ -363,7 +385,6 @@ int main(void) ACU_GetWifiStatus(&out); if(out) { - if(init_qr(current_mode)) { load_lists(lists); @@ -398,7 +419,7 @@ int main(void) } if(qr_mode || preview_mode || current_list->entries == NULL) - continue; + goto touch; int selected_entry = current_list->selected_entry; Entry_s * current_entry = ¤t_list->entries[selected_entry]; @@ -547,7 +568,7 @@ int main(void) if(draw_confirm("Are you sure you would like to delete this?", current_list)) { draw_install(INSTALL_ENTRY_DELETE); - delete_entry(*current_entry); + delete_entry(current_entry, current_entry->is_zip); load_lists(lists); } } @@ -564,11 +585,11 @@ int main(void) // Quick moving else if(kDown & KEY_LEFT) { - change_selected(current_list, -ENTRIES_PER_SCREEN); + change_selected(current_list, -current_list->entries_per_screen_v); } else if(kDown & KEY_RIGHT) { - change_selected(current_list, ENTRIES_PER_SCREEN); + change_selected(current_list, current_list->entries_per_screen_v); } // Fast scroll using circle pad @@ -584,16 +605,17 @@ int main(void) } else if(kHeld & KEY_CPAD_LEFT) { - change_selected(current_list, -ENTRIES_PER_SCREEN); + change_selected(current_list, -current_list->entries_per_screen_v); svcSleepThread(FASTSCROLL_WAIT); } else if(kHeld & KEY_CPAD_RIGHT) { - change_selected(current_list, ENTRIES_PER_SCREEN); + change_selected(current_list, current_list->entries_per_screen_v); svcSleepThread(FASTSCROLL_WAIT); } // Movement using the touchscreen + touch: if((kDown | kHeld) & KEY_TOUCH) { touchPosition touch = {0}; @@ -611,38 +633,42 @@ int main(void) { if(y < 24) { - if(BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll > 0) + if(current_list->entries != NULL && BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll > 0) { - change_selected(current_list, -ENTRIES_PER_SCREEN); + change_selected(current_list, -current_list->entries_per_screen_v); + } + else if(BETWEEN(320-120, x, 320-96)) + { + goto enable_qr; + } + else if(BETWEEN(320-96, x, 320-72)) + { + if(themeplaza_browser(current_mode)) + { + current_mode = MODE_THEMES; + load_lists(lists); + } + } + else if(BETWEEN(320-72, x, 320-48)) + { + quit = true; + } + else if(BETWEEN(320-48, x, 320-24)) + { + goto toggle_preview; } else if(BETWEEN(320-24, x, 320)) { goto switch_mode; } - else if(BETWEEN(320-48, x, 320-24)) - { - goto enable_qr; - } - else if(BETWEEN(320-72, x, 320-48)) - { - goto toggle_preview; - } - else if(BETWEEN(320-96, x, 320-72)) - { - load_icons_first(current_list, false); - } - else if(BETWEEN(320-120, x, 320-96) && current_mode == MODE_THEMES) - { - toggle_shuffle(current_list); - } } else if(y >= 216) { - if(BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll < current_list->entries_count - ENTRIES_PER_SCREEN) + if(current_list->entries != NULL && BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll < current_list->entries_count - current_list->entries_per_screen_v) { - change_selected(current_list, ENTRIES_PER_SCREEN); + change_selected(current_list, current_list->entries_per_screen_v); } - else if(BETWEEN(176, x, 320)) + else if(current_list->entries != NULL && BETWEEN(176, x, 320)) { jump_menu(current_list); } @@ -650,12 +676,12 @@ int main(void) } else { - if(BETWEEN(24, y, 216)) + if(current_list->entries != NULL && BETWEEN(24, y, 216)) { - for(int i = 0; i < ENTRIES_PER_SCREEN; i++) + for(int i = 0; i < current_list->entries_loaded; i++) { - u16 miny = 24 + 48*i; - u16 maxy = miny + 48; + u16 miny = 24 + current_list->entry_size*i; + u16 maxy = miny + current_list->entry_size; if(BETWEEN(miny, y, maxy) && current_list->scroll + i < current_list->entries_count) { current_list->selected_entry = current_list->scroll + i; diff --git a/source/remote.c b/source/remote.c new file mode 100644 index 0000000..a4c311c --- /dev/null +++ b/source/remote.c @@ -0,0 +1,763 @@ +/* +* This file is part of Anemone3DS +* Copyright (C) 2016-2017 Alex Taber ("astronautlevel"), Dawid Eckert ("daedreth") +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include "remote.h" +#include "loading.h" +#include "draw.h" +#include "fs.h" +#include "pp2d/pp2d/pp2d.h" + +static Instructions_s browser_instructions[MODE_AMOUNT] = { + { + .info_line = NULL, + .instructions = { + { + L"\uE000 Download theme", + L"\uE001 Go back" + }, + { + NULL, + L"\uE003 Preview theme" + }, + { + L"\uE004 Previous page", + L"\uE005 Next page" + }, + { + L"Exit", + NULL + } + } + }, + { + .info_line = NULL, + .instructions = { + { + L"\uE000 Download splash", + L"\uE001 Go back" + }, + { + NULL, + L"\uE003 Preview splash" + }, + { + L"\uE004 Previous page", + L"\uE005 Next page" + }, + { + L"Exit", + NULL + } + } + } +}; + +static void load_remote_entry(Entry_s * entry) +{ + char * entry_json = NULL; + char * api_url = NULL; + asprintf(&api_url, THEMEPLAZA_ENTRY_FORMAT, entry->tp_download_id); + u32 json_len = http_get(api_url, NULL, &entry_json); + free(api_url); + + if(json_len) + { + json_error_t error; + json_t *root = json_loadb(entry_json, json_len, 0, &error); + if(root) + { + const char *key; + json_t *value; + json_object_foreach(root, key, value) + { + if(json_is_string(value)) + { + if(!strcmp(key, THEMEPLAZA_JSON_ENTRY_NAME)) + { + utf8_to_utf16(entry->name, (u8*)json_string_value(value), 0x40); + + } + else if(!strcmp(key, THEMEPLAZA_JSON_ENTRY_DESC)) + utf8_to_utf16(entry->desc, (u8*)json_string_value(value), 0x80); + else if(!strcmp(key, THEMEPLAZA_JSON_ENTRY_AUTH)) + utf8_to_utf16(entry->author, (u8*)json_string_value(value), 0x40); + } + } + } + else + DEBUG("json error on line %d: %s\n", error.line, error.text); + + json_decref(root); + } + free(entry_json); +} + +static void load_remote_icon(size_t textureID, json_int_t id) +{ + char * icon_data = NULL; + char * icon_url = NULL; + asprintf(&icon_url, THEMEPLAZA_ICON_FORMAT, id); + u32 icon_size = http_get(icon_url, NULL, &icon_data); + free(icon_url); + + pp2d_free_texture(textureID); + pp2d_load_texture_png_memory(textureID, icon_data, icon_size); + free(icon_data); +} + +static void load_remote_entries(Entry_List_s * list, json_t *ids_array) +{ + list->entries_count = json_array_size(ids_array); + free(list->entries); + list->entries = calloc(list->entries_count, sizeof(Entry_s)); + free(list->icons_ids); + list->icons_ids = calloc(list->entries_count, sizeof(ssize_t)); + list->entries_loaded = list->entries_count; + + size_t i = 0; + json_t * id = NULL; + json_array_foreach(ids_array, i, id) + { + size_t offset = i; + Entry_s * current_entry = &list->entries[offset]; + current_entry->tp_download_id = json_integer_value(id); + load_remote_entry(current_entry); + + size_t textureID = list->texture_id_offset + i; + load_remote_icon(textureID, current_entry->tp_download_id); + list->icons_ids[offset] = textureID; + } +} + +static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mode) +{ + if(page > list->tp_page_count) + page = 1; + if(page <= 0) + page = list->tp_page_count; + + InstallType loading_screen = INSTALL_NONE; + if(mode == MODE_THEMES) + loading_screen = INSTALL_LOADING_REMOTE_THEMES; + else if(mode == MODE_SPLASHES) + loading_screen = INSTALL_LOADING_REMOTE_SPLASHES; + draw_install(loading_screen); + + char * page_json = NULL; + char * api_url = NULL; + asprintf(&api_url, THEMEPLAZA_PAGE_FORMAT, page, mode+1, list->tp_search); + u32 json_len = http_get(api_url, NULL, &page_json); + free(api_url); + + if(json_len) + { + list->texture_id_offset = TEXTURE_REMOTE_ICONS; + list->tp_current_page = page; + list->mode = mode; + list->entry_size = entry_size[mode]; + list->entries_per_screen_v = entries_per_screen_v[mode]; + list->entries_per_screen_h = entries_per_screen_h[mode]; + + json_error_t error; + json_t *root = json_loadb(page_json, json_len, 0, &error); + if(root) + { + const char *key; + json_t *value; + json_object_foreach(root, key, value) + { + if(json_is_integer(value) && !strcmp(key, THEMEPLAZA_JSON_PAGE_COUNT)) + list->tp_page_count = json_integer_value(value); + else if(json_is_array(value) && !strcmp(key, THEMEPLAZA_JSON_PAGE_IDS)) + load_remote_entries(list, value); + else if(json_is_string(value) && !strcmp(key, THEMEPLAZA_JSON_ERROR_MESSAGE) && !strcmp(json_string_value(value), THEMEPLAZA_JSON_ERROR_MESSAGE_NOT_FOUND)) + throw_error("No results for this search.", ERROR_LEVEL_WARNING); + } + } + else + DEBUG("json error on line %d: %s\n", error.line, error.text); + + json_decref(root); + } + else + throw_error("Couldn't download ThemePlaza data.\nMake sure WiFi is on.", ERROR_LEVEL_WARNING); + + free(page_json); +} + +static char previous_preview_url[0x100] = {0}; +static bool load_remote_preview(Entry_List_s list, int * preview_offset) +{ + Entry_s entry = list.entries[list.selected_entry]; + + char * preview_url = NULL; + asprintf(&preview_url, THEMEPLAZA_PREVIEW_FORMAT, entry.tp_download_id); + if(!strncmp(previous_preview_url, preview_url, 0x100)) + { + free(preview_url); + return true; + } + + draw_install(INSTALL_LOADING_REMOTE_PREVIEW); + char * preview_png = NULL; + u32 preview_size = http_get(preview_url, NULL, &preview_png); + + if(!preview_size) + { + free(preview_png); + return false; + } + + bool ret = false; + u8 * image = NULL; + unsigned int width = 0, height = 0; + + if((lodepng_decode32(&image, &width, &height, (u8*)preview_png, preview_size)) == 0) // no error + { + for(u32 i = 0; i < width; i++) + { + for(u32 j = 0; j < height; j++) + { + u32* pixel = (u32*)(image + (i + j*width) * 4); + *pixel = __builtin_bswap32(*pixel); //swap from RGBA to ABGR, needed for pp2d + } + } + + // mark the new preview as loaded for optimisation + strncpy(previous_preview_url, preview_url, 0x100); + // free the previously loaded preview. wont do anything if there wasnt one + pp2d_free_texture(TEXTURE_REMOTE_PREVIEW); + + pp2d_load_texture_memory(TEXTURE_REMOTE_PREVIEW, image, (u32)width, (u32)height); + + *preview_offset = (width-400)/2; + ret = true; + } + else + { + throw_error("Corrupted/invalid preview.png", ERROR_LEVEL_WARNING); + } + + free(image); + free(preview_url); + free(preview_png); + + return ret; +} + +static void download_remote_entry(Entry_s * entry, EntryMode mode) +{ + char * download_url = NULL; + asprintf(&download_url, THEMEPLAZA_DOWNLOAD_FORMAT, entry->tp_download_id); + + char * zip_buf = NULL; + char * filename = NULL; + draw_install(INSTALL_DOWNLOAD); + u32 zip_size = http_get(download_url, &filename, &zip_buf); + free(download_url); + + char path_to_file[0x107] = {0}; + sprintf(path_to_file, "%s%s", main_paths[mode], filename); + free(filename); + + char * extension = strrchr(path_to_file, '.'); + if (extension == NULL || strcmp(extension, ".zip")) + strcat(path_to_file, ".zip"); + + DEBUG("Saving to sd: %s\n", path_to_file); + remake_file(path_to_file, ArchiveSD, zip_size); + buf_to_file(zip_size, path_to_file, ArchiveSD, zip_buf); + free(zip_buf); +} + +static SwkbdCallbackResult jump_menu_callback(void* page_number, const char** ppMessage, const char* text, size_t textlen) +{ + int typed_value = atoi(text); + if(typed_value > *(json_int_t*)page_number) + { + *ppMessage = "The new page has to be\nsmaller or equal to the\nnumber of pages!"; + return SWKBD_CALLBACK_CONTINUE; + } + else if(typed_value == 0) + { + *ppMessage = "The new position has to\nbe positive!"; + return SWKBD_CALLBACK_CONTINUE; + } + return SWKBD_CALLBACK_OK; +} + +static void jump_menu(Entry_List_s * list) +{ + if(list == NULL) return; + + char numbuf[64] = {0}; + + SwkbdState swkbd; + + sprintf(numbuf, "%" JSON_INTEGER_FORMAT, list->tp_page_count); + int max_chars = strlen(numbuf); + swkbdInit(&swkbd, SWKBD_TYPE_NUMPAD, 2, max_chars); + + sprintf(numbuf, "%" JSON_INTEGER_FORMAT, list->tp_current_page); + swkbdSetInitialText(&swkbd, numbuf); + + sprintf(numbuf, "Which page do you want to jump to?"); + swkbdSetHintText(&swkbd, numbuf); + + swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false); + swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Jump", true); + swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars); + swkbdSetFilterCallback(&swkbd, jump_menu_callback, &list->tp_page_count); + + memset(numbuf, 0, sizeof(numbuf)); + SwkbdButton button = swkbdInputText(&swkbd, numbuf, sizeof(numbuf)); + if(button == SWKBD_BUTTON_CONFIRM) + { + json_int_t newpage = (json_int_t)atoi(numbuf); + if(newpage != list->tp_current_page) + load_remote_list(list, newpage, list->mode); + } +} + +static void search_menu(Entry_List_s * list) +{ + const int max_chars = 256; + char * search = calloc(max_chars+1, sizeof(char)); + + SwkbdState swkbd; + + swkbdInit(&swkbd, SWKBD_TYPE_WESTERN, 2, max_chars); + swkbdSetHintText(&swkbd, "Which tags do you want to search for?"); + + swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false); + swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Search", true); + swkbdSetValidation(&swkbd, SWKBD_NOTBLANK, 0, max_chars); + + SwkbdButton button = swkbdInputText(&swkbd, search, max_chars); + if(button == SWKBD_BUTTON_CONFIRM) + { + free(list->tp_search); + for(unsigned int i = 0; i < strlen(search); i++) + { + if(search[i] == ' ') + search[i] = '+'; + } + list->tp_search = search; + load_remote_list(list, 1, list->mode); + } + else + { + free(search); + } +} + +static void change_selected(Entry_List_s * list, int change_value) +{ + if(abs(change_value) >= list->entries_count) return; + + int newval = list->selected_entry + change_value; + + if(abs(change_value) == 1) + { + if(newval < 0) + newval += list->entries_per_screen_h; + if(newval/list->entries_per_screen_h != list->selected_entry/list->entries_per_screen_h) + newval += list->entries_per_screen_h*(-change_value); + } + else + { + if(newval < 0) + newval += list->entries_per_screen_h*list->entries_per_screen_v; + newval %= list->entries_count; + } + list->selected_entry = newval; +} + +bool themeplaza_browser(EntryMode mode) +{ + bool downloaded = false; + + bool preview_mode = false; + int preview_offset = 0; + + Entry_List_s list = {0}; + Entry_List_s * current_list = &list; + current_list->tp_search = strdup(""); + load_remote_list(current_list, 1, mode); + + while(aptMainLoop()) + { + if(current_list->entries == NULL) + break; + + if(preview_mode) + draw_preview(TEXTURE_REMOTE_PREVIEW, preview_offset); + else + draw_grid_interface(current_list, browser_instructions[mode]); + pp2d_end_draw(); + + hidScanInput(); + u32 kDown = hidKeysDown(); + u32 kHeld = hidKeysHeld(); + u32 kUp = hidKeysUp(); + + if(kDown & KEY_START) + { + quit = true; + downloaded = false; + break; + } + + int selected_entry = current_list->selected_entry; + Entry_s * current_entry = ¤t_list->entries[selected_entry]; + + if(kDown & KEY_Y) + { + toggle_preview: + if(!preview_mode) + preview_mode = load_remote_preview(*current_list, &preview_offset); + else + preview_mode = false; + } + else if(kDown & KEY_B) + { + if(preview_mode) + preview_mode = false; + else + break; + } + + if(preview_mode) + goto touch; + + if(kDown & KEY_A) + { + download_remote_entry(current_entry, mode); + downloaded = true; + } + + else if(kDown & KEY_L) + { + load_remote_list(current_list, current_list->tp_current_page-1, mode); + } + else if(kDown & KEY_R) + { + load_remote_list(current_list, current_list->tp_current_page+1, mode); + } + + // Movement in the UI + else if(kDown & KEY_UP) + { + change_selected(current_list, -current_list->entries_per_screen_h); + } + else if(kDown & KEY_DOWN) + { + change_selected(current_list, current_list->entries_per_screen_h); + } + // Quick moving + else if(kDown & KEY_LEFT) + { + change_selected(current_list, -1); + } + else if(kDown & KEY_RIGHT) + { + change_selected(current_list, 1); + } + + // Fast scroll using circle pad + else if(kHeld & KEY_CPAD_UP) + { + change_selected(current_list, -1); + svcSleepThread(FASTSCROLL_WAIT); + } + else if(kHeld & KEY_CPAD_DOWN) + { + change_selected(current_list, 1); + svcSleepThread(FASTSCROLL_WAIT); + } + else if(kHeld & KEY_CPAD_LEFT) + { + change_selected(current_list, -current_list->entries_per_screen_v); + svcSleepThread(FASTSCROLL_WAIT); + } + else if(kHeld & KEY_CPAD_RIGHT) + { + change_selected(current_list, current_list->entries_per_screen_v); + svcSleepThread(FASTSCROLL_WAIT); + } + + touch: + if((kDown | kHeld) & KEY_TOUCH) + { + touchPosition touch = {0}; + hidTouchRead(&touch); + + u16 x = touch.px; + u16 y = touch.py; + + #define BETWEEN(min, x, max) (min < x && x < max) + + int border = 16; + if(kDown & KEY_TOUCH) + { + if(preview_mode) + { + preview_mode = false; + continue; + } + else if(y < 24) + { + if(BETWEEN(0, x, 80)) + { + search_menu(current_list); + } + else if(BETWEEN(320-96, x, 320-72)) + { + break; + } + else if(BETWEEN(320-72, x, 320-48)) + { + quit = true; + downloaded = false; + break; + } + else if(BETWEEN(320-48, x, 320-24)) + { + goto toggle_preview; + } + else if(BETWEEN(320-24, x, 320)) + { + mode++; + mode %= MODE_AMOUNT; + + free(current_list->tp_search); + current_list->tp_search = strdup(""); + + load_remote_list(current_list, 1, mode); + } + } + else if(BETWEEN(240-24, y, 240) && BETWEEN(176, x, 320)) + { + jump_menu(current_list); + } + else + { + if(BETWEEN(0, x, border)) + { + load_remote_list(current_list, current_list->tp_current_page-1, mode); + } + else if(BETWEEN(320-border, x, 320)) + { + load_remote_list(current_list, current_list->tp_current_page+1, mode); + } + } + } + else + { + if(preview_mode) + { + preview_mode = false; + continue; + } + else if(BETWEEN(24, y, 240-24)) + { + if(BETWEEN(border, x, 320-border)) + { + x -= border; + x /= current_list->entry_size; + y -= 24; + y /= current_list->entry_size; + int new_selected = y*current_list->entries_per_screen_h + x; + if(new_selected < current_list->entries_count) + current_list->selected_entry = new_selected; + } + } + } + } + } + + free(current_list->entries); + free(current_list->icons_ids); + free(current_list->tp_search); + + return downloaded; +} + +u32 http_get(const char *url, char ** filename, char ** buf) +{ + Result ret; + httpcContext context; + char *new_url = NULL; + u32 status_code; + u32 content_size = 0; + u32 read_size = 0; + u32 size = 0; + char *last_buf; + + do { + ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 1); + if (ret != 0) + { + httpcCloseContext(&context); + if (new_url != NULL) free(new_url); + DEBUG("httpcOpenContext %.8lx\n", ret); + return 0; + } + ret = httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); // should let us do https + ret = httpcSetKeepAlive(&context, HTTPC_KEEPALIVE_ENABLED); + ret = httpcAddRequestHeaderField(&context, "User-Agent", USER_AGENT); + ret = httpcAddRequestHeaderField(&context, "Connection", "Keep-Alive"); + + ret = httpcBeginRequest(&context); + if (ret != 0) + { + httpcCloseContext(&context); + if (new_url != NULL) free(new_url); + DEBUG("httpcBeginRequest %.8lx\n", ret); + return 0; + } + + ret = httpcGetResponseStatusCode(&context, &status_code); + if(ret!=0){ + httpcCloseContext(&context); + if(new_url!=NULL) free(new_url); + DEBUG("httpcGetResponseStatusCode\n"); + return 0; + } + + if ((status_code >= 301 && status_code <= 303) || (status_code >= 307 && status_code <= 308)) + { + if (new_url == NULL) new_url = malloc(0x1000); + ret = httpcGetResponseHeader(&context, "Location", new_url, 0x1000); + url = new_url; + httpcCloseContext(&context); + } + } while ((status_code >= 301 && status_code <= 303) || (status_code >= 307 && status_code <= 308)); + + if (status_code != 200) + { + httpcCloseContext(&context); + if (new_url != NULL) free(new_url); + DEBUG("status_code\n"); + return 0; + } + + ret = httpcGetDownloadSizeState(&context, NULL, &content_size); + if (ret != 0) + { + httpcCloseContext(&context); + if (new_url != NULL) free(new_url); + DEBUG("httpcGetDownloadSizeState\n"); + return 0; + } + + *buf = malloc(0x1000); + if (*buf == NULL) + { + httpcCloseContext(&context); + free(new_url); + DEBUG("malloc\n"); + return 0; + } + + if(filename) + { + char *content_disposition = calloc(1024, sizeof(char)); + ret = httpcGetResponseHeader(&context, "Content-Disposition", content_disposition, 1024); + if (ret != 0) + { + free(content_disposition); + free(new_url); + free(*buf); + DEBUG("httpcGetResponseHeader\n"); + return 0; + } + + char * tok = strtok(content_disposition, "\""); + tok = strtok(NULL, "\""); + + if(!(tok)) + { + free(content_disposition); + free(new_url); + free(*buf); + throw_error("Target is not valid!", ERROR_LEVEL_WARNING); + DEBUG("filename\n"); + return 0; + } + + char *illegal_characters = "\"?;:/\\+"; + for (size_t i = 0; i < strlen(tok); i++) + { + for (size_t n = 0; n < strlen(illegal_characters); n++) + { + if ((tok)[i] == illegal_characters[n]) + { + (tok)[i] = '-'; + } + } + } + + *filename = calloc(1024, sizeof(char)); + strcpy(*filename, tok); + free(content_disposition); + } + + do { + ret = httpcDownloadData(&context, (*(u8**)buf) + size, 0x1000, &read_size); + size += read_size; + + if (ret == (s32)HTTPC_RESULTCODE_DOWNLOADPENDING) + { + last_buf = *buf; + *buf = realloc(*buf, size + 0x1000); + if (*buf == NULL) + { + httpcCloseContext(&context); + free(new_url); + free(last_buf); + DEBUG("NULL\n"); + return 0; + } + } + } while (ret == (s32)HTTPC_RESULTCODE_DOWNLOADPENDING); + + last_buf = *buf; + *buf = realloc(*buf, size); + if (*buf == NULL) + { + httpcCloseContext(&context); + free(new_url); + free(last_buf); + DEBUG("realloc\n"); + return 0; + } + + httpcCloseContext(&context); + free(new_url); + + return size; +}