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
This commit is contained in:
LiquidFenrir
2018-04-01 02:31:46 +02:00
committed by Alex Taber
parent 5090da114f
commit a2b5788fe8
18 changed files with 1212 additions and 350 deletions

View File

@@ -65,13 +65,13 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
ASFLAGS := -g $(ARCH) ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 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 # list of directories containing libraries, this must be the top level containing
# include and lib # include and lib
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
LIBDIRS := $(CTRULIB) $(DEVKITPRO)/portlibs/armv6k LIBDIRS := $(CTRULIB) $(PORTLIBS)
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------

View File

@@ -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 To-do list here: https://trello.com/b/F1YSa1VK
# Dependencies # 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. * [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. * ~~[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. * Git needs to be on your PATH, if building in a non-*nix environment.
# Building # Building
First of all, make sure devkitPRO is properly installed and added to the PATH. 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`. 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 # License
This project is licensed under the GNU GPLv3. See LICENSE.md for details. Additional terms 7b and 7c apply to this project. This project is licensed under the GNU GPLv3. See LICENSE.md for details. Additional terms 7b and 7c apply to this project.

View File

@@ -44,6 +44,5 @@ typedef struct {
bool init_qr(EntryMode current_mode); bool init_qr(EntryMode current_mode);
void exit_qr(qr_data *data); void exit_qr(qr_data *data);
void take_picture(void); void take_picture(void);
Result http_get(char *url, const char *path);
#endif #endif

View File

@@ -33,8 +33,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#define ENTRIES_PER_SCREEN 4
#define DEBUG(...) fprintf(stderr, __VA_ARGS__) #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define POS() DEBUG("%s (line %d)...\n", __func__, __LINE__) #define POS() DEBUG("%s (line %d)...\n", __func__, __LINE__)
@@ -42,6 +40,8 @@
POS(); \ POS(); \
DEBUG(__VA_ARGS__) DEBUG(__VA_ARGS__)
#define FASTSCROLL_WAIT 1.5e8
typedef enum { typedef enum {
MODE_THEMES = 0, MODE_THEMES = 0,
MODE_SPLASHES, MODE_SPLASHES,
@@ -50,15 +50,22 @@ typedef enum {
} EntryMode; } EntryMode;
extern const char * main_paths[MODE_AMOUNT]; 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 { enum TextureID {
TEXTURE_FONT_RESERVED = 0, // used by pp2d for the font TEXTURE_FONT_RESERVED = 0, // used by pp2d for the font
TEXTURE_ARROW, TEXTURE_ARROW,
TEXTURE_ARROW_SIDE,
TEXTURE_SHUFFLE, TEXTURE_SHUFFLE,
TEXTURE_INSTALLED, TEXTURE_INSTALLED,
TEXTURE_PREVIEW_ICON, TEXTURE_PREVIEW_ICON,
TEXTURE_DOWNLOAD, TEXTURE_DOWNLOAD,
TEXTURE_RELOAD, TEXTURE_BROWSE,
TEXTURE_LIST,
TEXTURE_EXIT,
TEXTURE_BATTERY_0, TEXTURE_BATTERY_0,
TEXTURE_BATTERY_1, TEXTURE_BATTERY_1,
TEXTURE_BATTERY_2, TEXTURE_BATTERY_2,
@@ -68,10 +75,13 @@ enum TextureID {
TEXTURE_BATTERY_CHARGE, TEXTURE_BATTERY_CHARGE,
TEXTURE_QR, TEXTURE_QR,
TEXTURE_PREVIEW, TEXTURE_PREVIEW,
TEXTURE_REMOTE_PREVIEW,
TEXTURE_SELECT_BUTTON, TEXTURE_SELECT_BUTTON,
TEXTURE_START_BUTTON, TEXTURE_START_BUTTON,
TEXTURE_ICON, // always the last // always the last
TEXTURE_REMOTE_ICONS,
TEXTURE_ICON = TEXTURE_REMOTE_ICONS + 24,
}; };
#endif #endif

View File

@@ -47,6 +47,10 @@ typedef enum {
INSTALL_DOWNLOAD, INSTALL_DOWNLOAD,
INSTALL_ENTRY_DELETE, INSTALL_ENTRY_DELETE,
INSTALL_LOADING_REMOTE_THEMES,
INSTALL_LOADING_REMOTE_SPLASHES,
INSTALL_LOADING_REMOTE_PREVIEW,
INSTALL_NONE, INSTALL_NONE,
} InstallType; } InstallType;
@@ -84,11 +88,12 @@ void exit_screens(void);
void throw_error(char* error, ErrorLevel level); void throw_error(char* error, ErrorLevel level);
bool draw_confirm(const char* conf_msg, Entry_List_s* list); 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_install(InstallType type);
void draw_base_interface(void); 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); void draw_interface(Entry_List_s* list, Instructions_s instructions);
#endif #endif

View File

@@ -28,6 +28,7 @@
#define LOADING_H #define LOADING_H
#include "common.h" #include "common.h"
#include <jansson.h>
enum ICON_IDS_OFFSET { enum ICON_IDS_OFFSET {
ICONS_ABOVE = 0, ICONS_ABOVE = 0,
@@ -62,6 +63,8 @@ typedef struct {
bool in_shuffle; bool in_shuffle;
bool installed; bool installed;
json_int_t tp_download_id;
} Entry_s; } Entry_s;
typedef struct { typedef struct {
@@ -69,7 +72,7 @@ typedef struct {
int entries_count; int entries_count;
ssize_t texture_id_offset; ssize_t texture_id_offset;
ssize_t icons_ids[ICONS_OFFSET_AMOUNT][ENTRIES_PER_SCREEN]; ssize_t * icons_ids;
int previous_scroll; int previous_scroll;
int scroll; int scroll;
@@ -80,6 +83,14 @@ typedef struct {
int shuffle_count; int shuffle_count;
EntryMode mode; 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; } Entry_List_s;
typedef struct { typedef struct {
@@ -87,8 +98,8 @@ typedef struct {
volatile bool run_thread; volatile bool run_thread;
} Thread_Arg_s; } Thread_Arg_s;
void delete_entry(Entry_s entry); void delete_entry(Entry_s * entry, bool is_file);
Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mode); Result load_entries(const char * loading_path, Entry_List_s * list);
bool load_preview(Entry_List_s list, int * preview_offset); bool load_preview(Entry_List_s list, int * preview_offset);
void load_icons_first(Entry_List_s * current_list, bool silent); void load_icons_first(Entry_List_s * current_list, bool silent);
void handle_scrolling(Entry_List_s * list); void handle_scrolling(Entry_List_s * list);

57
include/remote.h Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* 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

BIN
romfs/arrow_side.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
romfs/browse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

BIN
romfs/exit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

BIN
romfs/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 B

View File

@@ -32,6 +32,7 @@
#include "draw.h" #include "draw.h"
#include "fs.h" #include "fs.h"
#include "loading.h" #include "loading.h"
#include "remote.h"
/* /*
static u32 transfer_size; static u32 transfer_size;
@@ -194,7 +195,25 @@ void update_qr(qr_data *data, EntryMode current_mode)
if (!quirc_decode(&code, &scan_data)) if (!quirc_decode(&code, &scan_data))
{ {
exit_qr(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; data->success = true;
} }
} }
@@ -216,155 +235,3 @@ bool init_qr(EntryMode current_mode)
return (bool)data->success; 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;
}

View File

@@ -40,11 +40,14 @@ void init_screens(void)
pp2d_set_screen_color(GFX_BOTTOM, COLOR_BACKGROUND); pp2d_set_screen_color(GFX_BOTTOM, COLOR_BACKGROUND);
pp2d_load_texture_png(TEXTURE_ARROW, "romfs:/arrow.png"); 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_SHUFFLE, "romfs:/shuffle.png");
pp2d_load_texture_png(TEXTURE_INSTALLED, "romfs:/installed.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_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_0, "romfs:/battery0.png");
pp2d_load_texture_png(TEXTURE_BATTERY_1, "romfs:/battery1.png"); pp2d_load_texture_png(TEXTURE_BATTERY_1, "romfs:/battery1.png");
pp2d_load_texture_png(TEXTURE_BATTERY_2, "romfs:/battery2.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; 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_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_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) void draw_install(InstallType type)
@@ -191,6 +194,15 @@ void draw_install(InstallType type)
case INSTALL_LOADING_ICONS: case INSTALL_LOADING_ICONS:
pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Loading icons, please wait..."); pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Loading icons, please wait...");
break; 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: case INSTALL_SINGLE:
pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Installing a single theme..."); pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Installing a single theme...");
break; 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) void draw_interface(Entry_List_s* list, Instructions_s instructions)
{ {
draw_base_interface(); draw_base_interface();
@@ -287,26 +402,22 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions)
pp2d_texture_blend(COLOR_YELLOW); pp2d_texture_blend(COLOR_YELLOW);
pp2d_texture_scale(1.25, 1.4); pp2d_texture_scale(1.25, 1.4);
pp2d_texture_draw(); 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; return;
} }
draw_instructions(instructions); draw_instructions(instructions);
int selected_entry = list->selected_entry; int selected_entry = list->selected_entry;
Entry_s current_entry = list->entries[selected_entry]; Entry_s * current_entry = &list->entries[selected_entry];
draw_entry_info(current_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);
pp2d_draw_on(GFX_BOTTOM, GFX_LEFT); pp2d_draw_on(GFX_BOTTOM, GFX_LEFT);
@@ -314,59 +425,65 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions)
{ {
case MODE_THEMES: 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_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; break;
default: default:
break; break;
} }
pp2d_draw_texture_blend(TEXTURE_RELOAD, 320-96, 0, COLOR_WHITE); pp2d_draw_texture_blend(TEXTURE_DOWNLOAD, 320-120, 0, COLOR_WHITE);
pp2d_draw_texture_blend(TEXTURE_PREVIEW_ICON, 320-72, 0, COLOR_WHITE); pp2d_draw_texture_blend(TEXTURE_BROWSE, 320-96, 0, COLOR_WHITE);
pp2d_draw_texture_blend(TEXTURE_DOWNLOAD, 320-48, 0, COLOR_WHITE); pp2d_draw_texture_blend(TEXTURE_EXIT, 320-72, 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_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 // Show arrows if there are themes out of bounds
//---------------------------------------------------------------- //----------------------------------------------------------------
if(list->scroll > 0) if(list->scroll > 0)
pp2d_draw_texture(TEXTURE_ARROW, 155, 6); pp2d_draw_texture(TEXTURE_ARROW, 152, 4);
if(list->scroll + ENTRIES_PER_SCREEN < list->entries_count) if(list->scroll + list->entries_loaded < list->entries_count)
pp2d_draw_texture_flip(TEXTURE_ARROW, 155, 224, VERTICAL); 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; if(i >= list->entries_count) break;
current_entry = list->entries[i]; current_entry = &list->entries[i];
wchar_t name[0x41] = {0}; 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; u32 font_color = COLOR_WHITE;
if(i == list->selected_entry) if(i == selected_entry)
{ {
font_color = COLOR_BLACK; 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; ssize_t id = 0;
if(list->entries_count > ICONS_OFFSET_AMOUNT*ENTRIES_PER_SCREEN) if(list->entries_count > list->entries_loaded*ICONS_OFFSET_AMOUNT)
id = list->icons_ids[ICONS_VISIBLE][i - list->scroll]; id = list->icons_ids[ICONS_VISIBLE*list->entries_loaded + (i - list->scroll)];
else else
id = ((size_t *)list->icons_ids)[i]; id = list->icons_ids[i];
pp2d_draw_texture(id, 0, 24 + vertical_offset); pp2d_draw_texture(id, horizontal_offset, vertical_offset);
} }
else else
pp2d_draw_rectangle(0, 24 + vertical_offset, 48, 48, current_entry.placeholder_color); pp2d_draw_rectangle(horizontal_offset, vertical_offset, list->entry_size, list->entry_size, 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);
} }
char entries_count_str[0x20] = {0}; char entries_count_str[0x20] = {0};

View File

@@ -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) u32 zip_file_to_buf(char *file_name, u16 *zip_path, char **buf)
{ {
ssize_t len = strulen(zip_path, 0x106); char path[0x107] = {0};
char *path = calloc(sizeof(char), len*sizeof(u16)); utf16_to_utf8((u8*)path, zip_path, 0x106);
utf16_to_utf8((u8*)path, zip_path, len*sizeof(u16));
unzFile zip_handle = unzOpen(path); unzFile zip_handle = unzOpen(path);
free(path);
if(zip_handle == NULL) if(zip_handle == NULL)
{ {
DEBUG("invalid zip being opened\n"); DEBUG("invalid zip being opened: %s, %s\n", path, file_name);
return 0; return 0;
} }

View File

@@ -30,12 +30,12 @@
#include "unicode.h" #include "unicode.h"
#include "draw.h" #include "draw.h"
void delete_entry(Entry_s entry) void delete_entry(Entry_s * entry, bool is_file)
{ {
if(entry.is_zip) if(is_file)
FSUSER_DeleteFile(ArchiveSD, fsMakePath(PATH_UTF16, entry.path)); FSUSER_DeleteFile(ArchiveSD, fsMakePath(PATH_UTF16, entry->path));
else 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) 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) 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; Handle dir_handle;
Result res = FSUSER_OpenDirectory(&dir_handle, ArchiveSD, fsMakePath(PATH_ASCII, loading_path)); Result res = FSUSER_OpenDirectory(&dir_handle, ArchiveSD, fsMakePath(PATH_ASCII, loading_path));
if(R_FAILED(res)) if(R_FAILED(res))
{
DEBUG("Failed to open folder: %s\n", loading_path);
return res; return res;
}
u32 entries_read = 1; u32 entries_read = 1;
@@ -143,6 +147,7 @@ Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mo
free(list->entries); free(list->entries);
list->entries = NULL; list->entries = NULL;
res = -1; res = -1;
DEBUG("break\n");
break; break;
} }
else 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); strucat(current_entry->path, dir_entry.name);
current_entry->is_zip = !strcmp(dir_entry.shortExt, "ZIP"); current_entry->is_zip = !strcmp(dir_entry.shortExt, "ZIP");
parse_smdh(current_entry, dir_entry.name); parse_smdh(current_entry, dir_entry.name);
} }
FSDIR_Close(dir_handle); FSDIR_Close(dir_handle);
sort_list(list); sort_list(list);
list->mode = mode;
return res; return res;
} }
@@ -176,7 +179,7 @@ void load_icons_first(Entry_List_s * list, bool silent)
int starti = 0, endi = 0; 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"); DEBUG("small load\n");
// if the list is one that doesnt need swapping, load everything at once // 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"); DEBUG("extended load\n");
// otherwise, load around to prepare for swapping // otherwise, load around to prepare for swapping
starti = list->scroll - ENTRIES_PER_SCREEN*ICONS_VISIBLE; starti = list->scroll - list->entries_loaded*ICONS_VISIBLE;
endi = starti + ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT; 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; 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++) for(int i = starti; i < endi; i++, id++)
{ {
int offset = i; int offset = i;
@@ -204,7 +208,8 @@ void load_icons_first(Entry_List_s * list, bool silent)
Entry_s current_entry = list->entries[offset]; Entry_s current_entry = list->entries[offset];
load_smdh_icon(current_entry, id); 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 // 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++) for(int i = 0; i < list->entries_count; i++)
{ {
int change = 0; 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; 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; change = 1;
} }
@@ -251,21 +256,21 @@ void handle_scrolling(Entry_List_s * list)
{ {
change = -1; 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; list->scroll += change;
if(list->scroll < 0) if(list->scroll < 0)
list->scroll = 0; list->scroll = 0;
else if(list->scroll > list->entries_count - ENTRIES_PER_SCREEN) else if(list->scroll > list->entries_count - list->entries_loaded)
list->scroll = list->entries_count - ENTRIES_PER_SCREEN; list->scroll = list->entries_count - list->entries_loaded;
if(!change) if(!change)
list->previous_selected = list->selected_entry; list->previous_selected = list->selected_entry;
@@ -283,13 +288,13 @@ static void load_icons(Entry_List_s * current_list)
handle_scrolling(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 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)) #define SIGN(x) (x > 0 ? 1 : ((x < 0) ? -1 : 0))
int delta = current_list->scroll - current_list->previous_scroll; 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)); delta = -SIGN(delta) * (current_list->entries_count - abs(delta));
int starti = current_list->scroll; 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)); ssize_t * ids = calloc(abs(delta), sizeof(ssize_t));
#define FIRST(arr) arr[0] #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++) for(int i = starti; i != endi; i++, ctr++)
{ {
ssize_t id = 0; ssize_t id = 0;
int offset = i; 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) if(delta > 0)
{ {
id = LAST(icons_ids); id = LAST(icons_ids);
offset += ENTRIES_PER_SCREEN*ICONS_UNDER - delta; offset += current_list->entries_loaded*ICONS_UNDER - delta;
} }
else else
{ {
id = FIRST(icons_ids); 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 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); svcSleepThread(1e6);
for(int i = 0; i < abs(delta); i++) 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(entries);
free(ids); free(ids);

View File

@@ -30,12 +30,12 @@
#include "splashes.h" #include "splashes.h"
#include "draw.h" #include "draw.h"
#include "camera.h" #include "camera.h"
#include "remote.h"
#include "instructions.h" #include "instructions.h"
#include "pp2d/pp2d/pp2d.h" #include "pp2d/pp2d/pp2d.h"
#include <time.h> #include <time.h>
#define FASTSCROLL_WAIT 1.5e8 bool quit = false;
static bool homebrew = false; static bool homebrew = false;
static bool installed_themes = false; static bool installed_themes = false;
@@ -56,6 +56,18 @@ const char * main_paths[MODE_AMOUNT] = {
"/Themes/", "/Themes/",
"/Splashes/", "/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) static void init_services(void)
{ {
@@ -89,10 +101,7 @@ static void stop_install_check(void)
{ {
for(int i = 0; i < MODE_AMOUNT; i++) 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]; Entry_List_s * current_list = &lists[i];
free(current_list->entries); free(current_list->entries);
free(current_list->icons_ids);
memset(current_list, 0, sizeof(Entry_List_s)); memset(current_list, 0, sizeof(Entry_List_s));
} }
exit_thread(); 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) static SwkbdCallbackResult jump_menu_callback(void* entries_count, const char** ppMessage, const char* text, size_t textlen)
{ {
int typed_value = atoi(text); int typed_value = atoi(text);
@@ -184,6 +258,9 @@ static void jump_menu(Entry_List_s * list)
if(button == SWKBD_BUTTON_CONFIRM) if(button == SWKBD_BUTTON_CONFIRM)
{ {
list->selected_entry = atoi(numbuf) - 1; 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; if(abs(change_value) >= list->entries_count) return;
list->selected_entry += change_value; int newval = list->selected_entry + change_value;
if(list->selected_entry < 0)
list->selected_entry += list->entries_count;
list->selected_entry %= list->entries_count;
}
static void start_thread(void) if(newval < 0)
{ newval += list->entries_count;
if(iconLoadingThread_arg.run_thread) newval %= list->entries_count;
{
DEBUG("starting thread\n");
iconLoadingThread = threadCreate(load_icons_thread, &iconLoadingThread_arg, __stacksize__, 0x38, -2, false);
}
}
static void load_lists(Entry_List_s * lists) list->selected_entry = newval;
{
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();
} }
static void toggle_shuffle(Entry_List_s * list) static void toggle_shuffle(Entry_List_s * list)
@@ -292,8 +316,6 @@ int main(void)
bool qr_mode = false; bool qr_mode = false;
bool install_mode = false; bool install_mode = false;
bool quit = false;
while(aptMainLoop()) while(aptMainLoop())
{ {
if(quit) if(quit)
@@ -323,7 +345,7 @@ int main(void)
instructions = install_instructions; instructions = install_instructions;
if(qr_mode) take_picture(); if(qr_mode) take_picture();
else if(preview_mode) draw_preview(preview_offset); else if(preview_mode) draw_preview(TEXTURE_PREVIEW, preview_offset);
else { else {
if(!iconLoadingThread_arg.run_thread) if(!iconLoadingThread_arg.run_thread)
{ {
@@ -363,7 +385,6 @@ int main(void)
ACU_GetWifiStatus(&out); ACU_GetWifiStatus(&out);
if(out) if(out)
{ {
if(init_qr(current_mode)) if(init_qr(current_mode))
{ {
load_lists(lists); load_lists(lists);
@@ -398,7 +419,7 @@ int main(void)
} }
if(qr_mode || preview_mode || current_list->entries == NULL) if(qr_mode || preview_mode || current_list->entries == NULL)
continue; goto touch;
int selected_entry = current_list->selected_entry; int selected_entry = current_list->selected_entry;
Entry_s * current_entry = &current_list->entries[selected_entry]; Entry_s * current_entry = &current_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)) if(draw_confirm("Are you sure you would like to delete this?", current_list))
{ {
draw_install(INSTALL_ENTRY_DELETE); draw_install(INSTALL_ENTRY_DELETE);
delete_entry(*current_entry); delete_entry(current_entry, current_entry->is_zip);
load_lists(lists); load_lists(lists);
} }
} }
@@ -564,11 +585,11 @@ int main(void)
// Quick moving // Quick moving
else if(kDown & KEY_LEFT) 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) 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 // Fast scroll using circle pad
@@ -584,16 +605,17 @@ int main(void)
} }
else if(kHeld & KEY_CPAD_LEFT) 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); svcSleepThread(FASTSCROLL_WAIT);
} }
else if(kHeld & KEY_CPAD_RIGHT) 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); svcSleepThread(FASTSCROLL_WAIT);
} }
// Movement using the touchscreen // Movement using the touchscreen
touch:
if((kDown | kHeld) & KEY_TOUCH) if((kDown | kHeld) & KEY_TOUCH)
{ {
touchPosition touch = {0}; touchPosition touch = {0};
@@ -611,38 +633,42 @@ int main(void)
{ {
if(y < 24) 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)) else if(BETWEEN(320-24, x, 320))
{ {
goto switch_mode; 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) 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); jump_menu(current_list);
} }
@@ -650,12 +676,12 @@ int main(void)
} }
else 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 miny = 24 + current_list->entry_size*i;
u16 maxy = miny + 48; u16 maxy = miny + current_list->entry_size;
if(BETWEEN(miny, y, maxy) && current_list->scroll + i < current_list->entries_count) if(BETWEEN(miny, y, maxy) && current_list->scroll + i < current_list->entries_count)
{ {
current_list->selected_entry = current_list->scroll + i; current_list->selected_entry = current_list->scroll + i;

763
source/remote.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* 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 = &current_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;
}