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:
4
Makefile
4
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)
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -33,8 +33,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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
|
||||
|
||||
@@ -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
|
||||
@@ -28,6 +28,7 @@
|
||||
#define LOADING_H
|
||||
|
||||
#include "common.h"
|
||||
#include <jansson.h>
|
||||
|
||||
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);
|
||||
|
||||
57
include/remote.h
Normal file
57
include/remote.h
Normal 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
BIN
romfs/arrow_side.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
BIN
romfs/browse.png
Normal file
BIN
romfs/browse.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 654 B |
BIN
romfs/exit.png
Normal file
BIN
romfs/exit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 390 B |
BIN
romfs/list.png
Normal file
BIN
romfs/list.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 B |
BIN
romfs/reload.png
BIN
romfs/reload.png
Binary file not shown.
|
Before Width: | Height: | Size: 358 B |
173
source/camera.c
173
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;
|
||||
}
|
||||
|
||||
209
source/draw.c
209
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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
224
source/main.c
224
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 <time.h>
|
||||
|
||||
#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;
|
||||
|
||||
763
source/remote.c
Normal file
763
source/remote.c
Normal 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 = ¤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;
|
||||
}
|
||||
Reference in New Issue
Block a user