34 Commits

Author SHA1 Message Date
Dylan G
85d09107ed response to feedback 2021-03-16 16:24:54 +00:00
Dylan G
b16df2607c Add issue templates
We've needed this for a while, due to bug reports that don't really mean anything.
2021-03-11 18:38:00 +00:00
Dylan G
7745530764 Fixed 4720a499 (TP returns 303 currently, not 404) => handle 303 well(?); pushed some code around 2021-03-11 13:47:24 +00:00
Dylan G
0bab0f6700 Patched httpc error message to actually be readable on console; also provides Result code 2021-03-11 12:34:03 +00:00
Dylan G
38ab370cf1 Merge pull request #247 from KennLDN/patch-1
Fix broken link in CONTRIBUTORS.md
2021-02-08 13:40:16 +00:00
kenn
5cea6c8df4 Fix broken link in CONTRIBUTORS.md 2021-02-08 13:38:32 +00:00
Alex Taber
e4e0118c1a Prevent out of bounds scrolling in the browser 2021-01-01 23:51:51 -05:00
Dylan G
accdaaed2a Reduced code duplication 2020-12-31 21:28:10 +00:00
Alex Taber
3fb90e8197 Remove unused variables 2020-12-24 19:03:47 -05:00
Dylan G
aba2cd5f18 Finally update copyright; Flask meta files removed 2020-12-24 23:38:58 +00:00
Dylan G
e5ea18e81a Cleared TODO 2020-12-24 23:34:17 +00:00
Dylan G
4720a49923 QoL: alert the user if they attempt to download a theme that has not been approved.
Stopgap solution; TP does not want to expose approval status in headers for security reasons.
2020-12-24 23:34:17 +00:00
Dylan G
7871225999 Fix potential null dereference 2020-12-24 23:34:17 +00:00
Dylan G
1e5d09e85a Major refactor to networking, including a fix for an incorrect error message on cancelling the swkbd for downloads 2020-12-24 23:34:17 +00:00
Alex Taber
a647494306 Fix another null dereference 2020-12-24 18:27:48 -05:00
Alex Taber
c801e28523 Prevent free(NULL) in free_icons 2020-12-24 17:13:06 -05:00
Alex Taber
c9e1420a00 re-add s upport for themes without an SMDH
Also change theme dump random color algo
2020-12-24 16:58:06 -05:00
Dylan G
bc2f4ec581 Finished up software keyboard input for filenames in http_get.
Closes #220.
Worth noting that the callback may be shared between this whatever @astronautlevel2 is working on (theme dumping, I think).
2020-12-24 20:01:38 +00:00
Alex Taber
9592d76c2c Fix color generation 2020-12-24 14:52:08 -05:00
Alex Taber
a266203a92 Add theme dump functionality 2020-12-24 14:34:56 -05:00
Alex Taber
17afdbaae2 Fix regression in c40696e982
Extra menu works again
2020-12-23 19:59:18 -05:00
Alex Taber
f6d7446eba Update themeplaza url 2020-12-23 18:46:03 -05:00
Alex Taber
c49129d408 Remove trello link
We don't use trello for feature tracking, link is very outdated
2020-12-23 17:37:29 -05:00
Alex Taber
f9a2ae5190 Fix RGB previews having inverted colors
Closes #227
2020-12-23 17:30:55 -05:00
Alex Taber
573b7d35e6 Fix potential hang in QR reader 2020-12-23 16:51:57 -05:00
a2b6eb94e4 Fix regressions introduced in 1c2e562dd6 2020-12-23 16:20:49 -05:00
Théo B
95ff2dd3ba Memory leak fix and camera multithreading safety improvement (#239)
* memory leak fix and attempt at optimizing space

* camera rework

try to use better locking algorithm (MRSW lock on wikipedia)

* add time print (toggleable) and stuff

remove old mixed qr thingss from main

* remove the dumb 3 bytes saving

* remove useless time measure code

* forgot to close the stop event handle

* fix memory leak when loading icon from smdh

* fix entry path on folders

optimization using memcpy cause it to have the "/info.smdh" when the entry is a folder. simply remove that with a memset to 0.

Co-authored-by: Alex Taber <astronautlevel2@users.noreply.github.com>
2020-12-21 22:31:38 -05:00
Dylan G
1c2e562dd6 Networking rework (#240)
* Began refactoring http_get. Still a few cases to handle, but it works.

* Introduced error enum; handle error 406; handle a misbehaving server; redirect_url leaked memory (L888)

* Allowed calls to http_get to pass NULL as acceptable_mime_types

* Removed status_code from header struct; eventually planning to move most of parse_header's logic back to http_get, so this makes sense

* Moved some more logic back into http_get
Note: currently behaves weirdly with some QR codes, including but probably not limited to ones pointing to TinyURL.

* Handle redirects correctly; other rearrangements

* Formatting

* Handle HTTP 303 See Other correctly

* Removed "Download failed" error in camera.c due to all related failures being handled in http_get

* Fixed missing loading bar; started working on unchunked download

* Added unchunked download

* Reintroduced the download progress bar, rolled back to always doing chunked download

* URL length caps at 2083 chars

* Slightly more efficient

* Correctly handle incorrect filesize header
TODO: reorder clauses to remove the goto?

* Fixed illegal characters, removed unnecessary logic (technically a regression, as we always have to realloc now)

* Finally finished up status code handling. Fixed a memory leak when the server fails conneg.
2020-12-21 21:11:11 -05:00
Dylan G
9cb13f6be1 Fix #326
Typo in button string.
2020-11-18 19:37:46 +00:00
6a51b4eae5 Fix misc. QR code bugs
Close data->started properly, don't double close data->cancel
Should fix the QR code hang
2020-06-22 12:27:14 -04:00
14d9e99b7d Properly set body_size
Fix regression in 36e8104dbb
2020-06-17 14:09:23 -04:00
36e8104dbb Automatically set BGM flag to enabled when doing a BGM install 2020-06-16 18:45:59 -04:00
6bafa6a22e Add lz decompression code
Also add byte search code
2020-06-15 23:10:30 -04:00
Dylan G
408b2903f2 Finally void-out textlen in the callbacks (no more compiler warnings) 2020-06-15 23:11:28 +01:00
30 changed files with 1244 additions and 585 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +0,0 @@
{
"user": "astronautlevel2",
"name": "Anemone3DS",
"description": "A theme and boot splash manager for the Nintendo 3DS console"
}

39
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,39 @@
---
name: Bug report
about: Found a bug? Report it here
title: ''
labels: ''
assignees: ''
---
**Description**
A clear and concise description of what the bug is.
**Steps to reproduce**
***If you cannot reproduce the bug, please describe what you were doing in as much detail as possible***
*If you were scanning using the QR scanner, ensure you include a link to the theme you were attempting to download.*
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
Add screenshots of any error screens you encountered. Bug reports without screenshots will take longer to solve, so we recommend you get some!
**Crash dumps**
If your bug causes a crash in Luma3DS, please upload any crash dumps generated. You can find these in `SD:/luma/dumps/armX` (`X = 9 or 11`) with the name Luma3DS's exception handlers give you.
**System information**
*System model* e.g. new3DS XL
*System firmware version* e.g. 11.14.0 (this can be found in the System Settings applet by default)
*Anemone3DS version* e.g. v2.2.0 (found in the bottom-left of Anemone3DS)
*Luma3DS version* e.g. v10.2.1 (found in the menu when you hold Select on boot)
**If you are not on the [latest version of Anemone3DS](https://github.com/astronautlevel2/Anemone3DS/releases/latest), your bug report will likely be closed. Ensure the bug occurs in the latest build!**

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -4,7 +4,7 @@
* Dawid Eckert ([@daedreth](https://github.com/daedreth))
* Dylan G. ([@helloman892](https://github.com/helloman892))
* Nils P. ([@ZetaDesigns](https://github.com/ZetaDesigns))
* Matt Kenny ([@8kenn](https://github.com/8kenn))
* Matt Kenny ([@KennLDN](https://github.com/KennLDN))
# Minor Contributors
* Nic ([@Wizzrobes](https://github.com/Wizzrobes))

View File

@@ -1,7 +1,6 @@
![# Anemone3DS](https://github.com/astronautlevel2/Anemone3DS/blob/master/meta/banner.png)
A Theme and Splashscreen Manager for the Nintendo 3DS, written in C.\
To-do list here: https://trello.com/b/F1YSa1VK
# Dependencies
* devkitARM, which can be installed following the instructions [here](https://devkitpro.org/wiki/Getting_Started).

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -31,21 +31,23 @@
typedef struct {
u16* camera_buffer;
C2D_Image image;
C3D_Tex *tex;
Handle mutex;
volatile bool finished;
volatile bool closed;
volatile bool success;
Handle cancel;
Handle started;
bool capturing;
Handle event_stop;
Thread cam_thread, ui_thread;
LightEvent event_cam_info, event_ui_info;
CondVar cond;
LightLock mut;
u32 num_readers_active;
bool writer_waiting;
bool writer_active;
bool any_update;
struct quirc* context;
} qr_data;
bool init_qr(void);
void exit_qr(qr_data *data);
void take_picture(void);
#endif

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -55,6 +55,8 @@ typedef enum {
INSTALL_LOADING_REMOTE_PREVIEW,
INSTALL_LOADING_REMOTE_BGM,
INSTALL_DUMPING_THEME,
INSTALL_NONE,
} InstallType;
@@ -81,6 +83,8 @@ typedef enum {
TEXT_INSTALL_LOADING_REMOTE_PREVIEW,
TEXT_INSTALL_LOADING_REMOTE_BGM,
TEXT_INSTALL_DUMPING_THEME,
// Other text
TEXT_VERSION,

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -39,6 +39,8 @@ Result close_archives(void);
u32 file_to_buf(FS_Path path, FS_Archive archive, char** buf);
u32 zip_memory_to_buf(char *file_name, void * zip_memory, size_t zip_size, char ** buf);
u32 zip_file_to_buf(char *file_name, u16 *zip_path, char **buf);
u32 decompress_lz_file(FS_Path file_name, FS_Archive archive, char **buf);
u32 compress_lz_file_fast(FS_Path path, FS_Archive archive, char *in_buf, u32 size);
Result buf_to_file(u32 size, FS_Path path, FS_Archive archive, char *buf);
void remake_file(FS_Path path, FS_Archive archive, u32 size);

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -128,7 +128,7 @@ Instructions_s extra_instructions[3] = {
},
{
"\uE07B Browse ThemePlaza",
NULL,
"\uE07C Dump Current Theme"
},
{
"\uE004 Sorting menu",

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -31,7 +31,7 @@
#include "draw.h"
#include <ctype.h>
#define THEMEPLAZA_BASE_URL "https://themeplaza.eu"
#define THEMEPLAZA_BASE_URL "https://themeplaza.art"
#define THEMEPLAZA_API_URL "/api/anemone/v1"
#define THEMEPLAZA_BASE_API_URL THEMEPLAZA_BASE_URL THEMEPLAZA_API_URL
@@ -52,6 +52,6 @@
#define CACHE_PATH_FORMAT "/3ds/" APP_TITLE "/cache/%" JSON_INTEGER_FORMAT
bool themeplaza_browser(EntryMode mode);
u32 http_get(const char *url, char ** filename, char ** buf, InstallType install_type);
Result http_get(const char *url, char ** filename, char ** buf, u32 * size, InstallType install_type, const char * acceptable_mime_types);
#endif

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -75,6 +75,8 @@ Result bgm_install(Entry_s theme);
Result shuffle_install(Entry_List_s themes);
Result dump_theme(void);
void themes_check_installed(void * void_arg);
#endif

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -36,28 +36,63 @@
#include <archive.h>
#include <archive_entry.h>
void exit_qr(qr_data *data)
static void start_read(qr_data *data)
{
DEBUG("Exiting QR\n");
svcSignalEvent(data->cancel);
while(!data->finished)
svcSleepThread(1000000);
svcCloseHandle(data->cancel);
data->capturing = false;
free(data->camera_buffer);
free(data->tex);
quirc_destroy(data->context);
LightLock_Lock(&data->mut);
while(data->writer_waiting || data->writer_active)
{
CondVar_WaitTimeout(&data->cond, &data->mut, 1000000);
}
void capture_cam_thread(void *arg)
AtomicIncrement(&data->num_readers_active);
LightLock_Unlock(&data->mut);
}
static void stop_read(qr_data *data)
{
LightLock_Lock(&data->mut);
AtomicDecrement(&data->num_readers_active);
if(data->num_readers_active == 0)
{
CondVar_Signal(&data->cond);
}
LightLock_Unlock(&data->mut);
}
static void start_write(qr_data *data)
{
LightLock_Lock(&data->mut);
data->writer_waiting = true;
while(data->num_readers_active)
{
CondVar_WaitTimeout(&data->cond, &data->mut, 1000000);
}
data->writer_waiting = false;
data->writer_active = true;
LightLock_Unlock(&data->mut);
}
static void stop_write(qr_data *data)
{
LightLock_Lock(&data->mut);
data->writer_active = false;
CondVar_Broadcast(&data->cond);
LightLock_Unlock(&data->mut);
}
static void capture_cam_thread(void *arg)
{
qr_data *data = (qr_data *) arg;
Handle events[3] = {0};
events[0] = data->cancel;
u32 transferUnit;
u16 *buffer = calloc(1, 400 * 240 * sizeof(u16));
Handle cam_events[3] = {0};
cam_events[0] = data->event_stop;
u32 transferUnit;
const u32 bufsz = 400 * 240 * sizeof(u16);
u16 *buffer = linearAlloc(bufsz);
camInit();
CAMU_SetSize(SELECT_OUT1, SIZE_CTR_TOP_LCD, CONTEXT_A);
CAMU_SetOutputFormat(SELECT_OUT1, OUTPUT_RGB_565, CONTEXT_A);
@@ -66,38 +101,42 @@ void capture_cam_thread(void *arg)
CAMU_SetAutoExposure(SELECT_OUT1, true);
CAMU_SetAutoWhiteBalance(SELECT_OUT1, true);
CAMU_Activate(SELECT_OUT1);
CAMU_GetBufferErrorInterruptEvent(&events[2], PORT_CAM1);
CAMU_GetBufferErrorInterruptEvent(&cam_events[2], PORT_CAM1);
CAMU_SetTrimming(PORT_CAM1, false);
CAMU_GetMaxBytes(&transferUnit, 400, 240);
CAMU_SetTransferBytes(PORT_CAM1, transferUnit, 400, 240);
CAMU_ClearBuffer(PORT_CAM1);
CAMU_SetReceiving(&events[1], buffer, PORT_CAM1, 400 * 240 * sizeof(u16), (s16) transferUnit);
CAMU_SetReceiving(&cam_events[1], buffer, PORT_CAM1, bufsz, transferUnit);
CAMU_StartCapture(PORT_CAM1);
svcSignalEvent(data->started);
bool cancel = false;
while (!cancel)
{
s32 index = 0;
svcWaitSynchronizationN(&index, events, 3, false, U64_MAX);
svcWaitSynchronizationN(&index, cam_events, 3, false, U64_MAX);
switch(index) {
case 0:
DEBUG("Cancel event received\n");
cancel = true;
break;
case 1:
svcCloseHandle(events[1]);
events[1] = 0;
svcWaitSynchronization(data->mutex, U64_MAX);
memcpy(data->camera_buffer, buffer, 400 * 240 * sizeof(u16));
GSPGPU_FlushDataCache(data->camera_buffer, 400 * 240 * sizeof(u16));
svcReleaseMutex(data->mutex);
CAMU_SetReceiving(&events[1], buffer, PORT_CAM1, 400 * 240 * sizeof(u16), transferUnit);
svcCloseHandle(cam_events[1]);
cam_events[1] = 0;
start_write(data);
memcpy(data->camera_buffer, buffer, bufsz);
data->any_update = true;
stop_write(data);
CAMU_SetReceiving(&cam_events[1], buffer, PORT_CAM1, bufsz, transferUnit);
break;
case 2:
svcCloseHandle(events[1]);
events[1] = 0;
svcCloseHandle(cam_events[1]);
cam_events[1] = 0;
CAMU_ClearBuffer(PORT_CAM1);
CAMU_SetReceiving(&events[1], buffer, PORT_CAM1, 400 * 240 * sizeof(u16), transferUnit);
CAMU_SetReceiving(&cam_events[1], buffer, PORT_CAM1, bufsz, transferUnit);
CAMU_StartCapture(PORT_CAM1);
break;
default:
@@ -115,118 +154,197 @@ void capture_cam_thread(void *arg)
CAMU_ClearBuffer(PORT_CAM1);
CAMU_Activate(SELECT_NONE);
camExit();
free(buffer);
for(int i = 0; i < 3; i++) {
if(events[i] != 0) {
svcCloseHandle(events[i]);
events[i] = 0;
linearFree(buffer);
for(int i = 1; i < 3; i++)
{
if(cam_events[i] != 0) {
svcCloseHandle(cam_events[i]);
cam_events[i] = 0;
}
}
svcCloseHandle(data->mutex);
data->finished = true;
}
void update_ui(void *arg)
LightEvent_Signal(&data->event_cam_info);
}
static void update_ui(void *arg)
{
qr_data* data = (qr_data*) arg;
C3D_Tex tex;
while (!data->finished)
static const Tex3DS_SubTexture subt3x = { 400, 240, 0.0f, 1.0f, 400.0f/512.0f, 1.0f - (240.0f/256.0f) };
C3D_TexInit(&tex, 512, 256, GPU_RGB565);
C3D_TexSetFilter(&tex, GPU_LINEAR, GPU_LINEAR);
while(svcWaitSynchronization(data->event_stop, 2 * 1000 * 1000ULL) == 0x09401BFE) // timeout of 2ms occured, still have 14 for copy and render
{
draw_base_interface();
// Untiled texture loading code adapted from FBI
svcWaitSynchronization(data->mutex, U64_MAX);
for(u32 x = 0; x < 400 && !data->finished; x++) {
for(u32 y = 0; y < 256 && !data->finished; y++) {
u32 dstPos = ((((y >> 3) * (512 >> 3) + (x >> 3)) << 6) + ((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) | ((x & 4) << 2) | ((y & 4) << 3))) * sizeof(u16);
u32 srcPos = (y * 400 + x) * sizeof(u16);
memcpy(&((u8*) data->image.tex->data)[dstPos], &((u8*) data->camera_buffer)[srcPos], sizeof(u16));
}
}
svcReleaseMutex(data->mutex);
if (data->finished)
start_read(data);
if(data->any_update)
{
end_frame();
break;
}
for(u32 y = 0; y < 240; y++) {
const u32 srcPos = y * 400;
for(u32 x = 0; x < 400; x++) {
const u32 dstPos = ((((y >> 3) * (512 >> 3) + (x >> 3)) << 6) + ((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) | ((x & 4) << 2) | ((y & 4) << 3)));
C2D_DrawImageAt(data->image, 0.0f, 0.0f, 0.4f, NULL, 1.0f, 1.0f);
((u16*)tex.data)[dstPos] = data->camera_buffer[srcPos + x];
}
}
data->any_update = false;
}
stop_read(data);
C2D_DrawImageAt((C2D_Image){ &tex, &subt3x }, 0.0f, 0.0f, 0.4f, NULL, 1.0f, 1.0f);
set_screen(bottom);
draw_text_center(GFX_BOTTOM, 4, 0.5, 0.5, 0.5, colors[COLOR_WHITE], "Press \uE005 To Quit");
end_frame();
}
data->closed = true;
C3D_TexDelete(&tex);
LightEvent_Signal(&data->event_ui_info);
}
bool start_capture_cam(qr_data *data)
static bool start_capture_cam(qr_data *data)
{
data->mutex = 0;
data->cancel = 0;
svcCreateEvent(&data->cancel, RESET_STICKY);
svcCreateMutex(&data->mutex, false);
if(threadCreate(capture_cam_thread, data, 0x10000, 0x1A, 1, true) == NULL)
if((data->cam_thread = threadCreate(capture_cam_thread, data, 0x10000, 0x1A, 1, false)) == NULL)
{
throw_error("Capture cam thread creation failed\nPlease report this to the developers", ERROR_LEVEL_ERROR);
LightEvent_Signal(&data->event_cam_info);
LightEvent_Signal(&data->event_ui_info);
return false;
svcWaitSynchronization(data->started, U64_MAX);
if(threadCreate(update_ui, data, 0x10000, 0x1A, 1, true) == NULL)
}
if((data->ui_thread = threadCreate(update_ui, data, 0x10000, 0x1A, 1, false)) == NULL)
{
exit_qr(data);
LightEvent_Signal(&data->event_ui_info);
return false;
}
return true;
}
void update_qr(qr_data *data)
static bool update_qr(qr_data *data, struct quirc_data* scan_data)
{
hidScanInput();
if (hidKeysDown() & (KEY_R | KEY_B | KEY_TOUCH)) {
exit_qr(data);
return;
}
if (!data->capturing) {
if(start_capture_cam(data))
data->capturing = true;
else {
exit_qr(data);
return;
}
}
if (data->finished) {
exit_qr(data);
return;
}
int w;
int h;
u8 *image = (u8*) quirc_begin(data->context, &w, &h);
for (ssize_t x = 0; x < w; x++) {
for (ssize_t y = 0; y < h; y++) {
u16 px = data->camera_buffer[y * 400 + x];
image[y * w + x] = (u8)(((((px >> 11) & 0x1F) << 3) + (((px >> 5) & 0x3F) << 2) + ((px & 0x1F) << 3)) / 3);
start_read(data);
for (int y = 0; y < h; y++) {
const int actual_y = y * w;
for (int x = 0; x < w; x++) {
const int actual_off = actual_y + x;
const u16 px = data->camera_buffer[actual_off];
image[actual_off] = (u8)(((((px >> 11) & 0x1F) << 3) + (((px >> 5) & 0x3F) << 2) + ((px & 0x1F) << 3)) / 3);
}
}
stop_read(data);
quirc_end(data->context);
if(quirc_count(data->context) > 0)
{
struct quirc_code code;
struct quirc_data scan_data;
quirc_extract(data->context, 0, &code);
if (!quirc_decode(&code, &scan_data))
if (!quirc_decode(&code, scan_data))
{
exit_qr(data);
return true;
}
}
while (!data->finished) svcSleepThread(1000000);
return false;
}
static void start_qr(qr_data *data)
{
svcCreateEvent(&data->event_stop, RESET_STICKY);
LightEvent_Init(&data->event_cam_info, RESET_STICKY);
LightEvent_Init(&data->event_ui_info, RESET_STICKY);
LightLock_Init(&data->mut);
CondVar_Init(&data->cond);
data->cam_thread = NULL;
data->ui_thread = NULL;
data->any_update = false;
data->context = quirc_new();
quirc_resize(data->context, 400, 240);
data->camera_buffer = calloc(1, 400 * 240 * sizeof(u16));
}
static void exit_qr(qr_data *data)
{
svcSignalEvent(data->event_stop);
LightEvent_Wait(&data->event_ui_info);
LightEvent_Clear(&data->event_ui_info);
if(data->ui_thread != NULL)
{
threadJoin(data->ui_thread, U64_MAX);
threadFree(data->ui_thread);
data->ui_thread = NULL;
}
LightEvent_Wait(&data->event_cam_info);
LightEvent_Clear(&data->event_cam_info);
if(data->cam_thread != NULL)
{
threadJoin(data->cam_thread, U64_MAX);
threadFree(data->cam_thread);
data->cam_thread = NULL;
}
free(data->camera_buffer);
data->camera_buffer = NULL;
svcCloseHandle(data->event_stop);
data->event_stop = 0;
}
bool init_qr(void)
{
qr_data data;
memset(&data, 0, sizeof(data));
start_qr(&data);
struct quirc_data* scan_data = calloc(1, sizeof(struct quirc_data));
const bool ready = start_capture_cam(&data);
bool finished = !ready;
while(!finished)
{
hidScanInput();
if (hidKeysDown() & (KEY_R | KEY_B | KEY_TOUCH))
{
break;
}
finished = update_qr(&data, scan_data);
svcSleepThread(50 * 1000 * 1000ULL); // only scan every 50ms
}
exit_qr(&data);
bool success = false;
if(finished && ready)
{
draw_install(INSTALL_DOWNLOAD);
char * zip_buf = NULL;
char * filename = NULL;
u32 zip_size = http_get((char*)scan_data.payload, &filename, &zip_buf, INSTALL_DOWNLOAD);
u32 zip_size;
Result res = http_get((char*)scan_data->payload, &filename, &zip_buf, &zip_size, INSTALL_DOWNLOAD, "application/zip");
if (R_FAILED(res))
{
free(filename);
free(zip_buf);
return false;
}
else if (R_DESCRIPTION(res) == RD_NO_DATA || R_DESCRIPTION(res) == RD_CANCEL_REQUESTED)
{
free(filename);
return true;
}
if(zip_size != 0)
{
@@ -281,7 +399,7 @@ void update_qr(qr_data *data)
remake_file(fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_size);
buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf);
data->success = true;
success = true;
}
else
{
@@ -301,33 +419,8 @@ void update_qr(qr_data *data)
free(filename);
}
}
}
bool init_qr(void)
{
qr_data *data = calloc(1, sizeof(qr_data));
data->capturing = false;
data->finished = false;
data->closed = false;
svcCreateEvent(&data->started, RESET_STICKY);
data->context = quirc_new();
quirc_resize(data->context, 400, 240);
data->camera_buffer = calloc(1, 400 * 240 * sizeof(u16));
data->tex = (C3D_Tex*)malloc(sizeof(C3D_Tex));
static const Tex3DS_SubTexture subt3x = { 512, 256, 0.0f, 1.0f, 1.0f, 0.0f };
data->image = (C2D_Image){ data->tex, &subt3x };
C3D_TexInit(data->image.tex, 512, 256, GPU_RGB565);
C3D_TexSetFilter(data->image.tex, GPU_LINEAR, GPU_LINEAR);
while (!data->finished) update_qr(data);
bool success = data->success;
while (!data->closed) svcSleepThread(1000000);
free(data);
free(scan_data);
quirc_destroy(data.context);
return success;
}

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -125,6 +125,7 @@ void init_screens(void)
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_SPLASHES], staticBuf, "Downloading splash list, please wait...");
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_PREVIEW], staticBuf, "Downloading preview, please wait...");
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, "Downloading BGM, please wait...");
C2D_TextParse(&text[TEXT_INSTALL_DUMPING_THEME], staticBuf, "Dumping theme, please wait...");
for(int i = 0; i < TEXT_AMOUNT; i++)
C2D_TextOptimize(&text[i]);
@@ -300,7 +301,7 @@ void throw_error(char* error, ErrorLevel level)
draw_base_interface();
draw_text_center(GFX_TOP, 100, 0.5f, 0.6f, 0.6f, colors[text_color], error);
draw_c2d_text_center(GFX_TOP, 150, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[bottom_text]);
draw_c2d_text_center(GFX_TOP, 170, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[bottom_text]);
end_frame();
if(kDown & KEY_A) break;
@@ -552,13 +553,18 @@ void draw_grid_interface(Entry_List_s* list, Instructions_s instructions)
vertical_offset += 24;
horizontal_offset += 16;
// theoretically impossible to have no icon when from the api
/*
if(!current_entry->placeholder_color)
{
*/
C2D_Image * image = list->icons[i];
C2D_DrawImageAt(*image, horizontal_offset, vertical_offset, 0.5f, NULL, 1.0f, 1.0f);
/*
}
else
C2D_DrawRectSolid(horizontal_offset, vertical_offset, 0.5f, list->entry_size, list->entry_size, current_entry->placeholder_color);
*/
if(i == selected_entry)
{

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -196,6 +196,148 @@ Result buf_to_file(u32 size, FS_Path path, FS_Archive archive, char *buf)
return 0;
}
u32 decompress_lz_file(FS_Path file_name, FS_Archive archive, char **buf)
{
Handle handle;
Result res = 0;
if (R_FAILED(res = FSUSER_OpenFile(&handle, archive, file_name, FS_OPEN_READ, 0))) {
DEBUG("%lu\n", res);
return 0;
}
u64 size;
FSFILE_GetSize(handle, &size);
char *temp_buf = NULL;
if(size != 0)
{
temp_buf = calloc(1, size);
FSFILE_Read(handle, NULL, 0, temp_buf, size);
}
FSFILE_Close(handle);
if (temp_buf[0] != 0x11) {
free(temp_buf);
return 0;
}
u32 output_size = temp_buf[1] | ((temp_buf[2] << 8) & 0xFF00) | ((temp_buf[3] << 16) & 0xFF0000);
printf("%ld\n", output_size);
*buf = calloc(1, output_size);
u32 pos = 4;
u32 cur_written = 0;
u8 counter = 0;
u8 mask = 0;
while (cur_written < output_size)
{
if (counter == 0) // read mask
{
mask = temp_buf[pos++];
counter++;
continue;
}
if ((mask >> (8 - counter)) & 0x01) // compressed block
{
int len = 0;
int disp = 0;
switch (temp_buf[pos] >> 4)
{
case 0:
len = temp_buf[pos++] << 4;
len |= temp_buf[pos] >> 4;
len += 0x11;
break;
case 1:
len = (temp_buf[pos++] & 0x0F) << 12;
len |= temp_buf[pos++] << 4;
len |= temp_buf[pos] >> 4;
len += 0x111;
break;
default:
len = (temp_buf[pos] >> 4) + 1;
}
disp = (temp_buf[pos++] & 0x0F) << 8;
disp |= temp_buf[pos++];
for (int i = 0; i < len; ++i)
{
*(*buf + cur_written + i) = *(*buf + cur_written - disp - 1 + i);
}
cur_written += len;
}
else // byte literal
{
*(*buf + cur_written) = temp_buf[pos++];
cur_written++;
}
if (++counter > 8) counter = 0;
}
free(temp_buf);
return cur_written;
}
// This is an awful algorithm to "compress" LZ11 data.
// We do this instead of actual LZ11 compression because of time -
// LZ11 requires a lot of mem searching which is painfully slow on 3DS.
// This process is nearly instant but means the resulting file is actually larger
// than the input file. I don't think this is a problem as the only file we compress like this
// is the theme data installed to extdata in some rare cases, which means only 1 file at most
// is ever compressed like this. I don't think 400 KB is that big a sacrifice for probably
// half a minute or so of time save - I may change my mind on this in the future, especially
// if i figure out a dynamic programming algorithm which ends up being significantly
// faster. Otherwise, I think this is probably a fine implementation.
u32 compress_lz_file_fast(FS_Path path, FS_Archive archive, char *in_buf, u32 size)
{
char *output_buf = calloc(1, size * 2);
u32 output_size = 0;
u32 mask_pos = 0;
u32 bytes_processed = 0;
u8 counter = 0;
if (output_buf == NULL) return 0;
// Set header data for the LZ11 file - 0x11 is version (LZ11), next 3 bytes are size
output_buf[0] = 0x11;
output_buf[3] = (size & 0xFF0000) >> 16;
output_buf[2] = (size & 0xFF00) >> 8;
output_buf[1] = (size & 0xFF);
output_size += 4;
while (bytes_processed < size)
{
if (counter == 0)
{
mask_pos = output_size++;
output_buf[mask_pos] = 0;
}
output_buf[output_size++] = in_buf[bytes_processed++];
if (++counter == 8) counter = 0;
}
buf_to_file(output_size, path, archive, output_buf);
free(output_buf);
return output_size;
}
void remake_file(FS_Path path, FS_Archive archive, u32 size)
{
Handle handle;

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -83,6 +83,7 @@ C2D_Image * loadTextureIcon(Icon_s *icon)
void parse_smdh(Icon_s *icon, Entry_s * entry, const u16 * fallback_name)
{
if(icon == NULL)
{
memcpy(entry->name, fallback_name, 0x80);
@@ -92,27 +93,12 @@ void parse_smdh(Icon_s *icon, Entry_s * entry, const u16 * fallback_name)
return;
}
memcpy(entry->name, icon->name, 0x40*sizeof(u16));
memcpy(entry->desc, icon->desc, 0x80*sizeof(u16));
memcpy(entry->author, icon->author, 0x40*sizeof(u16));
}
static void parse_entry_smdh(Entry_s * entry, const u16 * fallback_name)
{
char *info_buffer = NULL;
u64 size = load_data("/info.smdh", *entry, &info_buffer);
if(!size)
{
free(info_buffer);
info_buffer = NULL;
}
Icon_s * smdh = (Icon_s *)info_buffer;
parse_smdh(smdh, entry, fallback_name);
}
static C2D_Image * load_entry_icon(Entry_s entry)
{
char *info_buffer = NULL;
@@ -120,7 +106,9 @@ static C2D_Image * load_entry_icon(Entry_s entry)
if(!size) return NULL;
Icon_s * smdh = (Icon_s *)info_buffer;
return loadTextureIcon(smdh);
C2D_Image* out = loadTextureIcon(smdh);
free(info_buffer);
return out;
}
typedef int (*sort_comparator)(const void *, const void *);
@@ -197,25 +185,24 @@ Result load_entries(const char * loading_path, Entry_List_s * list)
if (!strcmp(dir_entry.shortExt, "ZIP"))
{
u32 size = zip_file_to_buf("info.smdh", path, &buf);
if (size == 0) continue;
zip_file_to_buf("info.smdh", path, &buf);
}
else
{
const ssize_t len = strulen(path, 0x106);
struacat(path, "/info.smdh");
u32 size = file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, &buf);
if (size == 0) continue;
file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, &buf);
memset(&path[len], 0, (0x106 - len) * sizeof(u16));
}
free(buf);
list->entries_count++;
Entry_s * new_list = realloc(list->entries, list->entries_count * sizeof(Entry_s));
if(new_list == NULL)
{
free(list->entries);
list->entries = NULL;
res = -1;
DEBUG("break\n");
// out of memory: still allow use of currently loaded entries.
// Many things might die, depending on the heap layout after
list->entries_count--;
free(buf);
break;
}
else
@@ -223,12 +210,11 @@ Result load_entries(const char * loading_path, Entry_List_s * list)
Entry_s * current_entry = &(list->entries[list->entries_count-1]);
memset(current_entry, 0, sizeof(Entry_s));
parse_smdh((Icon_s *)buf, current_entry, dir_entry.name);
free(buf);
struacat(current_entry->path, loading_path);
strucat(current_entry->path, dir_entry.name);
memcpy(current_entry->path, path, 0x106 * sizeof(u16));
current_entry->is_zip = !strcmp(dir_entry.shortExt, "ZIP");
parse_entry_smdh(current_entry, dir_entry.name);
}
FSDIR_Close(dir_handle);
@@ -424,9 +410,12 @@ static bool load_icons(Entry_List_s * current_list, Handle mutex)
int index = indexes[i];
C2D_Image * image = icons[index];
if (icons[index] != NULL)
{
C3D_TexDelete(image->tex);
free(image->tex);
free(image);
}
icons[index] = load_entry_icon(*current_entry);
@@ -509,7 +498,7 @@ bool load_preview_from_buffer(void * buf, u32 size, C2D_Image * preview_image, i
if(color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
png_set_add_alpha(png, 0xFF, PNG_FILLER_BEFORE);
if(color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -132,6 +132,7 @@ static void free_icons(Entry_List_s * list)
for(int i = 0; i < amount; i++)
{
if (list->icons[i] == NULL) continue;
C3D_TexDelete(list->icons[i]->tex);
free(list->icons[i]->tex);
free(list->icons[i]);
@@ -239,6 +240,7 @@ static void load_lists(Entry_List_s * lists)
static SwkbdCallbackResult jump_menu_callback(void* entries_count, const char** ppMessage, const char* text, size_t textlen)
{
(void)textlen;
int typed_value = atoi(text);
if(typed_value > *(int*)entries_count)
{
@@ -358,7 +360,6 @@ int main(void)
bool preview_mode = false;
int preview_offset = 0;
bool qr_mode = false;
bool install_mode = false;
bool extra_mode = false;
C2D_Image preview = {0};
@@ -406,8 +407,7 @@ int main(void)
instructions = extra_instructions[index];
}
if(qr_mode) take_picture();
else if(preview_mode)
if(preview_mode)
{
draw_preview(preview, preview_offset);
}
@@ -439,14 +439,14 @@ int main(void)
if(!install_mode && !extra_mode)
{
if(!preview_mode && !qr_mode && kDown & KEY_L) //toggle between splashes and themes
if(!preview_mode && kDown & KEY_L) //toggle between splashes and themes
{
switch_mode:
current_mode++;
current_mode %= MODE_AMOUNT;
continue;
}
else if(!qr_mode && !preview_mode && kDown & KEY_R) //toggle QR mode
else if(!preview_mode && kDown & KEY_R) //toggle QR mode
{
enable_qr:
draw_base_interface();
@@ -479,7 +479,7 @@ int main(void)
continue;
}
else if(!qr_mode && kDown & KEY_Y && current_list->entries != NULL) //toggle preview mode
else if(kDown & KEY_Y && current_list->entries != NULL) //toggle preview mode
{
toggle_preview:
if(!preview_mode)
@@ -524,7 +524,7 @@ int main(void)
}
}
if(qr_mode || preview_mode || current_list->entries == NULL)
if(preview_mode || current_list->entries == NULL)
goto touch;
int selected_entry = current_list->selected_entry;
@@ -648,6 +648,13 @@ int main(void)
{
load_icons_first(current_list, false);
}
else if((kDown | kHeld) & KEY_DRIGHT)
{
draw_install(INSTALL_DUMPING_THEME);
Result res = dump_theme();
if (R_FAILED(res)) DEBUG("Dump theme result: %lx\n", res);
else load_lists(lists);
}
}
else if(key_l)
{

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -128,7 +128,12 @@ static C2D_Image * load_remote_smdh(Entry_s * entry, bool ignore_cache)
smdh_buf = NULL;
char * api_url = NULL;
asprintf(&api_url, THEMEPLAZA_SMDH_FORMAT, entry->tp_download_id);
smdh_size = http_get(api_url, NULL, &smdh_buf, INSTALL_NONE);
Result res = http_get(api_url, NULL, &smdh_buf, &smdh_size, INSTALL_NONE, "application/octet-stream");
if (R_FAILED(res))
{
free(smdh_buf);
return false;
}
free(api_url);
}
@@ -205,8 +210,14 @@ static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mod
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, INSTALL_NONE);
u32 json_len;
Result res = http_get(api_url, NULL, &page_json, &json_len, INSTALL_NONE, "application/json");
free(api_url);
if (R_FAILED(res))
{
free(page_json);
return;
}
if (json_len)
{
@@ -228,7 +239,8 @@ static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mod
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, ignore_cache, loading_screen);
else if(json_is_string(value) && !strcmp(key, THEMEPLAZA_JSON_ERROR_MESSAGE) && !strcmp(json_string_value(value), THEMEPLAZA_JSON_ERROR_MESSAGE_NOT_FOUND))
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);
}
}
@@ -244,6 +256,7 @@ static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mod
}
static u16 previous_path_preview[0x106] = { 0 };
static bool load_remote_preview(Entry_s * entry, C2D_Image * preview_image, int * preview_offset)
{
bool not_cached = true;
@@ -264,9 +277,10 @@ static bool load_remote_preview(Entry_s * entry, C2D_Image* preview_image, int *
asprintf(&preview_url, THEMEPLAZA_PREVIEW_FORMAT, entry->tp_download_id);
draw_install(INSTALL_LOADING_REMOTE_PREVIEW);
preview_size = http_get(preview_url, NULL, &preview_png, INSTALL_LOADING_REMOTE_PREVIEW);
Result res = http_get(preview_url, NULL, &preview_png, &preview_size, INSTALL_LOADING_REMOTE_PREVIEW, "image/png");
free(preview_url);
if (R_FAILED(res))
return false;
}
if (!preview_size)
@@ -292,6 +306,7 @@ static bool load_remote_preview(Entry_s * entry, C2D_Image* preview_image, int *
}
static u16 previous_path_bgm[0x106] = { 0 };
static void load_remote_bgm(Entry_s * entry)
{
if (!memcmp(&previous_path_bgm, entry->path, 0x106 * sizeof(u16))) return;
@@ -309,8 +324,10 @@ static void load_remote_bgm(Entry_s * entry)
draw_install(INSTALL_LOADING_REMOTE_BGM);
bgm_size = http_get(bgm_url, NULL, &bgm_ogg, INSTALL_LOADING_REMOTE_BGM);
Result res = http_get(bgm_url, NULL, &bgm_ogg, &bgm_size, INSTALL_LOADING_REMOTE_BGM, "application/ogg, audio/ogg");
free(bgm_url);
if (R_FAILED(res))
return;
u16 path[0x107] = { 0 };
strucat(path, entry->path);
@@ -332,7 +349,13 @@ static void download_remote_entry(Entry_s * entry, EntryMode mode)
char * zip_buf = NULL;
char * filename = NULL;
draw_install(INSTALL_DOWNLOAD);
u32 zip_size = http_get(download_url, &filename, &zip_buf, INSTALL_DOWNLOAD);
u32 zip_size;
if(R_FAILED(http_get(download_url, &filename, &zip_buf, &zip_size, INSTALL_DOWNLOAD, "application/zip")))
{
free(download_url);
free(filename);
return;
}
free(download_url);
char path_to_file[0x107] = { 0 };
@@ -343,14 +366,16 @@ static void download_remote_entry(Entry_s * entry, EntryMode mode)
if (extension == NULL || strcmp(extension, ".zip"))
strcat(path_to_file, ".zip");
DEBUG("Saving to sd: %s\n", path_to_file);
DEBUG("Saving to SD: %s\n", path_to_file);
remake_file(fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_size);
buf_to_file(zip_size, fsMakePath(PATH_ASCII, 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)
static SwkbdCallbackResult
jump_menu_callback(void * page_number, const char ** ppMessage, const char * text, size_t textlen)
{
(void)textlen;
int typed_value = atoi(text);
if (typed_value > *(json_int_t *)page_number)
{
@@ -373,17 +398,19 @@ static void jump_menu(Entry_List_s * list)
SwkbdState swkbd;
sprintf(numbuf, "%" JSON_INTEGER_FORMAT, list->tp_page_count);
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);
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, "Cance", false);
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);
@@ -405,10 +432,10 @@ static void search_menu(Entry_List_s * list)
SwkbdState swkbd;
swkbdInit(&swkbd, SWKBD_TYPE_WESTERN, 2, max_chars);
swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 2, max_chars);
swkbdSetHintText(&swkbd, "Which tags do you want to search for?");
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cance", false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Search", true);
swkbdSetValidation(&swkbd, SWKBD_NOTBLANK, 0, max_chars);
@@ -442,6 +469,7 @@ static void change_selected(Entry_List_s * list, int change_value)
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);
newval %= list->entries_count;
}
else
{
@@ -719,172 +747,389 @@ bool themeplaza_browser(EntryMode mode)
return downloaded;
}
u32 http_get(const char *url, char ** filename, char ** buf, InstallType install_type)
typedef struct header
{
char ** filename; // pointer to location for filename; if NULL, no filename is parsed
u32 file_size; // if == 0, fall back to chunked read
Result result_code;
} header;
typedef enum ParseResult
{
SUCCESS, // 200/203 (203 indicates a successful request with a transformation applied by a proxy)
REDIRECT, // 301/302/307/308
HTTPC_ERROR,
ABORTED,
SERVER_IS_MISBEHAVING,
SEE_OTHER = 303, // Theme Plaza returns these
HTTP_UNAUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_UNACCEPTABLE = 406, // like 204, usually doesn't happen
HTTP_PROXY_UNAUTHORIZED = 407,
HTTP_GONE = 410,
HTTP_URI_TOO_LONG = 414,
HTTP_IM_A_TEAPOT = 418, // Note that a combined coffee/tea pot that is temporarily out of coffee should instead return 503.
HTTP_UPGRADE_REQUIRED = 426, // the 3DS doesn't support HTTP/2, so we can't upgrade - inform and return
HTTP_LEGAL_REASONS = 451,
HTTP_INTERNAL_SERVER_ERROR = 500,
HTTP_BAD_GATEWAY = 502,
HTTP_SERVICE_UNAVAILABLE = 503,
HTTP_GATEWAY_TIMEOUT = 504,
} ParseResult;
static SwkbdCallbackResult fat32filter(void *user, const char **ppMessage, const char *text, size_t textlen)
{
(void)textlen;
(void)user;
*ppMessage = "Input must not contain:\n><\"?;:/\\+,.|[=]";
if(strpbrk(text, "><\"?;:/\\+,.|[=]"))
{
DEBUG("illegal filename: %s\n", text);
return SWKBD_CALLBACK_CONTINUE;
}
return SWKBD_CALLBACK_OK;
}
// the good paths for this function return SUCCESS, ABORTED, or REDIRECT;
// all other paths are failures
static ParseResult parse_header(struct header * out, httpcContext * context, const char * mime)
{
// status code
u32 status_code;
out->result_code = httpcGetResponseStatusCode(context, &status_code);
if (R_FAILED(out->result_code))
{
DEBUG("httpcGetResponseStatusCode\n");
return HTTPC_ERROR;
}
DEBUG("HTTP %lu\n", status_code);
switch (status_code)
{
case 301:
case 302:
case 307:
case 308:
return REDIRECT;
case 200:
case 203:
break;
default:
return (ParseResult)status_code;
}
char content_buf[1024] = {0};
// Content-Type
if (mime)
{
out->result_code = httpcGetResponseHeader(context, "Content-Type", content_buf, 1024);
if (R_FAILED(out->result_code))
{
return HTTPC_ERROR;
}
if (!strstr(mime, content_buf))
{
return SERVER_IS_MISBEHAVING;
}
}
// Content-Length
out->result_code = httpcGetDownloadSizeState(context, NULL, &out->file_size);
if (R_FAILED(out->result_code))
{
DEBUG("httpcGetDownloadSizeState\n");
return HTTPC_ERROR; // no need to free, program dies anyway
}
// Content-Disposition
if (out->filename)
{
out->result_code = httpcGetResponseHeader(context, "Content-Disposition", content_buf, 1024);
if (R_FAILED(out->result_code))
{
DEBUG("httpcGetResponseHeader\n");
return HTTPC_ERROR;
}
// content_buf: Content-Disposition: attachment; ... filename=<filename>;? ...
char * filename = strstr(content_buf, "filename="); // filename=<filename>;? ...
if (!filename)
{
const int max_chars = 250;
// needs to be heap allocated only because the call site is expected to free it
*out->filename = malloc(max_chars + 5); // + .zip and the null term
SwkbdState swkbd;
swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 2, max_chars / 2);
swkbdSetHintText(&swkbd, "Choose a filename");
swkbdSetFeatures(&swkbd, SWKBD_PREDICTIVE_INPUT | SWKBD_DARKEN_TOP_SCREEN);
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Download", true);
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, SWKBD_FILTER_CALLBACK, -1);
swkbdSetFilterCallback(&swkbd, &fat32filter, NULL);
SwkbdButton button = swkbdInputText(&swkbd, *out->filename, max_chars);
if (button != SWKBD_BUTTON_CONFIRM)
{
out->result_code = swkbdGetResult(&swkbd);
return ABORTED;
}
strcat(*out->filename, ".zip");
return SUCCESS;
}
filename = strpbrk(filename, "=") + 1; // <filename>;?
char * end = strpbrk(filename, ";");
if (end)
*end = '\0'; // <filename>
if (filename[0] == '"')
// safe to assume the filename is quoted
{
filename[strlen(filename) - 1] = '\0';
filename++;
}
char * illegal_char;
// filter out characters illegal in FAT32 filenames
while ((illegal_char = strpbrk(filename, "><\"?;:/\\+,.|[=]")))
*illegal_char = '-';
*out->filename = malloc(strlen(filename) + 1);
strcpy(*out->filename, filename);
}
return SUCCESS;
}
#define ZIP_NOT_AVAILABLE "ZIP not found at this URL\nIf you believe this is an error, please\ncontact the site administrator"
/*
* call example: written = http_get("url", &filename, &buffer_to_download_to, &filesize, INSTALL_DOWNLOAD, "application/json");
*/
Result http_get(const char * url, char ** filename, char ** buf, u32 * size, InstallType install_type, const char * acceptable_mime_types)
{
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;
char redirect_url[0x824] = {0};
char new_url[0x824] = {0};
do {
struct header _header = { .filename = filename };
DEBUG("Original URL: %s\n", url);
redirect: // goto here if we need to redirect
ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 1);
if (ret != 0)
if (R_FAILED(ret))
{
httpcCloseContext(&context);
if (new_url != NULL) free(new_url);
DEBUG("httpcOpenContext %.8lx\n", ret);
return 0;
return ret;
}
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");
httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); // should let us do https
httpcSetKeepAlive(&context, HTTPC_KEEPALIVE_ENABLED);
httpcAddRequestHeaderField(&context, "User-Agent", USER_AGENT);
httpcAddRequestHeaderField(&context, "Connection", "Keep-Alive");
if (acceptable_mime_types)
httpcAddRequestHeaderField(&context, "Accept", acceptable_mime_types);
ret = httpcBeginRequest(&context);
if (ret != 0)
if (R_FAILED(ret))
{
httpcCloseContext(&context);
if (new_url != NULL) free(new_url);
DEBUG("httpcBeginRequest %.8lx\n", ret);
return 0;
return ret;
}
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))
#define ERROR_BUFFER_SIZE 0x80
char err_buf[ERROR_BUFFER_SIZE];
ParseResult parse = parse_header(&_header, &context, acceptable_mime_types);
switch (parse)
{
if (new_url == NULL) new_url = malloc(0x1000);
ret = httpcGetResponseHeader(&context, "Location", new_url, 0x1000);
case SUCCESS:
break;
case ABORTED:
ret = httpcCloseContext(&context);
if(R_FAILED(ret))
return ret;
return MAKERESULT(RL_SUCCESS, RS_CANCELED, RM_APPLICATION, RD_CANCEL_REQUESTED);
case HTTPC_ERROR:
DEBUG("httpc error %lx\n", _header.result_code);
snprintf(err_buf, ERROR_BUFFER_SIZE, "Error in HTTPC sysmodule - 0x%08lx.\nIf you are seeing this, please contact an\nAnemone developer on the Theme Plaza Discord.", _header.result_code);
throw_error(err_buf, ERROR_LEVEL_ERROR);
quit = true;
httpcCloseContext(&context);
return _header.result_code;
case SEE_OTHER:
if (strstr(url, THEMEPLAZA_BASE_URL))
{
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 303 See Other (Theme Plaza)\nHas this theme been approved?");
goto error;
}
else
{
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 303 See Other\nDownload the resource directly\nor contact the site administrator.");
goto error;
}
case REDIRECT:
httpcGetResponseHeader(&context, "Location", redirect_url, 0x824);
httpcCloseContext(&context);
if (*redirect_url == '/') // if relative URL
{
strcpy(new_url, url);
char * last_slash = strchr(strchr(strchr(new_url, '/') + 1, '/') + 1, '/');
if (last_slash) *last_slash = '\0'; // prevents a NULL deref in case the original domain was not /-delimited
strncat(new_url, redirect_url, 0x824 - strlen(new_url));
url = new_url;
httpcCloseContext(&context);
}
} while ((status_code >= 301 && status_code <= 303) || (status_code >= 307 && status_code <= 308));
if (status_code != 200)
else
{
httpcCloseContext(&context);
if (new_url != NULL) free(new_url);
DEBUG("status_code, %lu\n", status_code);
return 0;
url = redirect_url;
}
DEBUG("HTTP Redirect: %s %s\n", new_url, *redirect_url == '/' ? "relative" : "absolute");
goto redirect;
case SERVER_IS_MISBEHAVING:
DEBUG("Server is misbehaving (provided resource with incorrect MIME)\n");
snprintf(err_buf, ERROR_BUFFER_SIZE, ZIP_NOT_AVAILABLE);
goto error;
case HTTP_NOT_FOUND:
case HTTP_GONE: ;
const char * http_error = parse == HTTP_NOT_FOUND ? "404 Not Found" : "410 Gone";
DEBUG("HTTP %s; URL: %s\n", http_error, url);
if (strstr(url, THEMEPLAZA_BASE_URL) && parse == HTTP_NOT_FOUND)
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 404 Not Found\nHas this theme been approved?");
else
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP %s\nCheck that the URL is correct.", http_error);
goto error;
case HTTP_UNACCEPTABLE:
DEBUG("HTTP 406 Unacceptable; Accept: %s\n", acceptable_mime_types);
snprintf(err_buf, ERROR_BUFFER_SIZE, ZIP_NOT_AVAILABLE);
goto error;
case HTTP_UNAUTHORIZED:
case HTTP_FORBIDDEN:
case HTTP_PROXY_UNAUTHORIZED:
DEBUG("HTTP %u: device not authenticated\n", parse);
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP %s\nContact the site administrator.", parse == HTTP_UNAUTHORIZED
? "401 Unauthorized"
: parse == HTTP_FORBIDDEN
? "403 Forbidden"
: "407 Proxy Authentication Required");
goto error;
case HTTP_URI_TOO_LONG:
DEBUG("HTTP 414; URL is too long, maybe too many redirects?\n");
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 414 URI Too Long\nThe QR code points to a really long URL.\nDownload the file directly.");
goto error;
case HTTP_IM_A_TEAPOT:
DEBUG("HTTP 418 I'm a teapot\n");
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 418 I'm a teapot\nContact the site administrator.");
goto error;
case HTTP_UPGRADE_REQUIRED:
DEBUG("HTTP 426; HTTP/2 required\n");
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 426 Upgrade Required\nThe 3DS cannot connect to this server.\nContact the site administrator.");
goto error;
case HTTP_LEGAL_REASONS:
DEBUG("HTTP 451; URL: %s\n", url);
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 451 Unavailable for Legal Reasons\nSome entity is preventing access\nto the host server for legal reasons.");
goto error;
case HTTP_INTERNAL_SERVER_ERROR:
DEBUG("HTTP 500; URL: %s\n", url);
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 500 Internal Server Error\nContact the site administrator.");
goto error;
case HTTP_BAD_GATEWAY:
DEBUG("HTTP 502; URL: %s\n", url);
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 502 Bad Gateway\nContact the site administrator.");
goto error;
case HTTP_SERVICE_UNAVAILABLE:
DEBUG("HTTP 503; URL: %s\n", url);
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 503 Service Unavailable\nContact the site administrator.");
goto error;
case HTTP_GATEWAY_TIMEOUT:
DEBUG("HTTP 504; URL: %s\n", url);
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 504 Gateway Timeout\nContact the site administrator.");
goto error;
default:
DEBUG("HTTP %u; URL: %s\n", parse, url);
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP %u\nIf you believe this is unexpected, please\ncontact the site administrator.", parse);
goto error;
}
ret = httpcGetDownloadSizeState(&context, NULL, &content_size);
if (ret != 0)
goto no_error;
error:
throw_error(err_buf, ERROR_LEVEL_WARNING);
Result res = httpcCloseContext(&context);
if (R_FAILED(res)) return res;
return MAKERESULT(RL_TEMPORARY, RS_CANCELED, RM_APPLICATION, RD_NO_DATA);
no_error:;
u32 chunk_size;
if (_header.file_size)
// the only reason we chunk this at all is for the download bar;
// in terms of efficiency, allocating the full size
// would avoid 3 reallocs whenever the server isn't lying
chunk_size = _header.file_size / 4;
else
chunk_size = 0x80000;
*buf = NULL;
char * new_buf;
*size = 0;
u32 read_size = 0;
do
{
new_buf = realloc(*buf, *size + chunk_size);
if (new_buf == NULL)
{
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;
DEBUG("realloc failed in http_get - file possibly too large?\n");
return MAKERESULT(RL_FATAL, RS_INTERNAL, RM_KERNEL, RD_OUT_OF_MEMORY);
}
*buf = new_buf;
char * tok = strstr(content_disposition, "filename=");
if(!(tok))
// download exactly chunk_size bytes and toss them into buf.
// size contains the current offset into buf.
ret = httpcDownloadData(&context, (u8*)(*buf) + *size, chunk_size, &read_size);
/* FIXME: I have no idea why this doesn't work, but it causes problems. Look into it later
if (R_FAILED(ret))
{
free(content_disposition);
free(new_url);
httpcCloseContext(&context);
free(*buf);
throw_error("Target is not valid!", ERROR_LEVEL_WARNING);
DEBUG("filename\n");
return 0;
DEBUG("download failed in http_get\n");
return ret;
}
*/
*size += read_size;
tok += sizeof("filename=") - 1;
if(ispunct((int)*tok))
{
tok++;
}
char* last_char = tok + strlen(tok) - 1;
if(ispunct((int)*last_char))
{
*last_char = '\0';
}
if (_header.file_size && install_type != INSTALL_NONE)
draw_loading_bar(*size, _header.file_size, install_type);
} while (ret == (Result)HTTPC_RESULTCODE_DOWNLOADPENDING);
httpcCloseContext(&context);
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(content_size && install_type != INSTALL_NONE)
draw_loading_bar(size, content_size, install_type);
if (ret == (s32)HTTPC_RESULTCODE_DOWNLOADPENDING)
{
last_buf = *buf;
*buf = realloc(*buf, size + 0x1000);
if (*buf == NULL)
// shrink to size
new_buf = realloc(*buf, *size);
if (new_buf == NULL)
{
httpcCloseContext(&context);
free(new_url);
free(last_buf);
DEBUG("NULL\n");
return 0;
free(*buf);
DEBUG("shrinking realloc failed\n"); // 何?
return MAKERESULT(RL_FATAL, RS_INTERNAL, RM_KERNEL, RD_OUT_OF_MEMORY);
}
}
} while (ret == (s32)HTTPC_RESULTCODE_DOWNLOADPENDING);
*buf = new_buf;
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);
DEBUG("size: %lu\n", size);
return size;
DEBUG("size: %lu\n", *size);
if (filename) { DEBUG("filename: %s\n", *filename); }
return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS);
}

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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
@@ -191,10 +191,24 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_TOO_LARGE);
}
if (music_size != 0)
{
remake_file(fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, BGM_MAX_SIZE);
res = buf_to_file(music_size, fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, music);
free(music);
char *body_buf = NULL;
u32 uncompressed_size = decompress_lz_file(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, &body_buf);
if (body_buf[5] != 1)
{
installmode |= THEME_INSTALL_BODY;
body_buf[5] = 1;
body_size = compress_lz_file_fast(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, body_buf, uncompressed_size);
}
free(body_buf);
}
if(R_FAILED(res)) return res;
} else
{
@@ -301,6 +315,106 @@ inline Result shuffle_install(Entry_List_s themes)
return install_theme_internal(themes, THEME_INSTALL_SHUFFLE | THEME_INSTALL_BODY | THEME_INSTALL_BGM);
}
static SwkbdCallbackResult
dir_name_callback(void *data, const char ** ppMessage, const char * text, size_t textlen)
{
(void)textlen;
(void)data;
if(strpbrk(text, "><\"?;:/\\+,.|[=]"))
{
*ppMessage = "Illegal character used.";
return SWKBD_CALLBACK_CONTINUE;
}
return SWKBD_CALLBACK_OK;
}
Result dump_theme(void)
{
const int max_chars = 255;
char * output_dir = calloc(max_chars + 1, sizeof(char));
SwkbdState swkbd;
swkbdInit(&swkbd, SWKBD_TYPE_WESTERN, 2, max_chars);
swkbdSetHintText(&swkbd, "Name of output folder");
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Done", true);
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars);
swkbdSetFilterCallback(&swkbd, dir_name_callback, NULL);
SwkbdButton button = swkbdInputText(&swkbd, output_dir, max_chars);
if (button != SWKBD_BUTTON_CONFIRM)
{
DEBUG("<dump_theme> Something went wrong with getting swkbd\n");
return MAKERESULT(RL_FATAL, RS_CANCELED, RM_UTIL, RD_CANCEL_REQUESTED);
}
u16 path[0x107] = { 0 };
struacat(path, "/themes/");
struacat(path, output_dir);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_UTF16, path), FS_ATTRIBUTE_DIRECTORY);
char *thememanage_buf = NULL;
file_to_buf(fsMakePath(PATH_ASCII, "/ThemeManage.bin"), ArchiveThemeExt, &thememanage_buf);
ThemeManage_bin_s * theme_manage = (ThemeManage_bin_s *)thememanage_buf;
u32 theme_size = theme_manage->body_size;
u32 bgm_size = theme_manage->music_size;
free(thememanage_buf);
char *temp_buf = NULL;
file_to_buf(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, &temp_buf);
u16 path_output[0x107] = { 0 };
memcpy(path_output, path, 0x107);
struacat(path_output, "/body_LZ.bin");
remake_file(fsMakePath(PATH_UTF16, path_output), ArchiveSD, theme_size);
buf_to_file(theme_size, fsMakePath(PATH_UTF16, path_output), ArchiveSD, temp_buf);
free(temp_buf);
temp_buf = NULL;
file_to_buf(fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, &temp_buf);
memcpy(path_output, path, 0x107);
struacat(path_output, "/bgm.bcstm");
remake_file(fsMakePath(PATH_UTF16, path_output), ArchiveSD, bgm_size);
buf_to_file(bgm_size, fsMakePath(PATH_UTF16, path_output), ArchiveSD, temp_buf);
free(temp_buf);
temp_buf = NULL;
char *smdh_file = calloc(1, 0x36c0);
smdh_file[0] = 0x53; // SMDH magic
smdh_file[1] = 0x4d;
smdh_file[2] = 0x44;
smdh_file[3] = 0x48;
struacat((u16 *) (smdh_file + 0x8), output_dir);
struacat((u16 *) (smdh_file + 0x88), "No description");
struacat((u16 *) (smdh_file + 0x188), "Unknown Author");
free(output_dir);
u8 r = rand() % 255;
u8 g = rand() % 255;
u8 b = rand() % 255;
u16 color = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3);
for (int i = 0x2040; i < 0x36c0; i += 2)
{
*(smdh_file + i) = (color & 0xFF00) >> 8;
*(smdh_file + i + 1) = color & 0x00FF;
}
memcpy(path_output, path, 0x107);
struacat(path_output, "/info.smdh");
remake_file(fsMakePath(PATH_UTF16, path_output), ArchiveSD, 0x36c0);
buf_to_file(0x36c0, fsMakePath(PATH_UTF16, path_output), ArchiveSD, smdh_file);
free(smdh_file);
return 0;
}
void themes_check_installed(void * void_arg)
{
Thread_Arg_s * arg = (Thread_Arg_s *)void_arg;

View File

@@ -1,6 +1,6 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2018 Contributors in CONTRIBUTORS.md
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* 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