Compare commits
34 Commits
rewrite
...
85d09107ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85d09107ed | ||
|
|
b16df2607c | ||
|
|
7745530764 | ||
|
|
0bab0f6700 | ||
|
|
38ab370cf1 | ||
|
|
5cea6c8df4 | ||
|
|
e4e0118c1a | ||
|
|
accdaaed2a | ||
|
|
3fb90e8197 | ||
|
|
aba2cd5f18 | ||
|
|
e5ea18e81a | ||
|
|
4720a49923 | ||
|
|
7871225999 | ||
|
|
1e5d09e85a | ||
|
|
a647494306 | ||
|
|
c801e28523 | ||
|
|
c9e1420a00 | ||
|
|
bc2f4ec581 | ||
|
|
9592d76c2c | ||
|
|
a266203a92 | ||
|
|
17afdbaae2 | ||
|
|
f6d7446eba | ||
|
|
c49129d408 | ||
|
|
f9a2ae5190 | ||
|
|
573b7d35e6 | ||
| a2b6eb94e4 | |||
|
|
95ff2dd3ba | ||
|
|
1c2e562dd6 | ||
|
|
9cb13f6be1 | ||
| 6a51b4eae5 | |||
| 14d9e99b7d | |||
| 36e8104dbb | |||
| 6bafa6a22e | |||
|
|
408b2903f2 |
Binary file not shown.
BIN
.flask/icon.t3x
BIN
.flask/icon.t3x
Binary file not shown.
@@ -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
39
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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!**
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* Dawid Eckert ([@daedreth](https://github.com/daedreth))
|
* Dawid Eckert ([@daedreth](https://github.com/daedreth))
|
||||||
* Dylan G. ([@helloman892](https://github.com/helloman892))
|
* Dylan G. ([@helloman892](https://github.com/helloman892))
|
||||||
* Nils P. ([@ZetaDesigns](https://github.com/ZetaDesigns))
|
* Nils P. ([@ZetaDesigns](https://github.com/ZetaDesigns))
|
||||||
* Matt Kenny ([@8kenn](https://github.com/8kenn))
|
* Matt Kenny ([@KennLDN](https://github.com/KennLDN))
|
||||||
|
|
||||||
# Minor Contributors
|
# Minor Contributors
|
||||||
* Nic ([@Wizzrobes](https://github.com/Wizzrobes))
|
* Nic ([@Wizzrobes](https://github.com/Wizzrobes))
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||

|

|
||||||
|
|
||||||
A Theme and Splashscreen Manager for the Nintendo 3DS, written in C.\
|
A Theme and Splashscreen Manager for the Nintendo 3DS, written in C.\
|
||||||
To-do list here: https://trello.com/b/F1YSa1VK
|
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
* devkitARM, which can be installed following the instructions [here](https://devkitpro.org/wiki/Getting_Started).
|
* devkitARM, which can be installed following the instructions [here](https://devkitpro.org/wiki/Getting_Started).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -31,21 +31,23 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u16* camera_buffer;
|
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;
|
struct quirc* context;
|
||||||
} qr_data;
|
} qr_data;
|
||||||
|
|
||||||
bool init_qr(void);
|
bool init_qr(void);
|
||||||
void exit_qr(qr_data *data);
|
|
||||||
void take_picture(void);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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_PREVIEW,
|
||||||
INSTALL_LOADING_REMOTE_BGM,
|
INSTALL_LOADING_REMOTE_BGM,
|
||||||
|
|
||||||
|
INSTALL_DUMPING_THEME,
|
||||||
|
|
||||||
INSTALL_NONE,
|
INSTALL_NONE,
|
||||||
} InstallType;
|
} InstallType;
|
||||||
|
|
||||||
@@ -81,6 +83,8 @@ typedef enum {
|
|||||||
TEXT_INSTALL_LOADING_REMOTE_PREVIEW,
|
TEXT_INSTALL_LOADING_REMOTE_PREVIEW,
|
||||||
TEXT_INSTALL_LOADING_REMOTE_BGM,
|
TEXT_INSTALL_LOADING_REMOTE_BGM,
|
||||||
|
|
||||||
|
TEXT_INSTALL_DUMPING_THEME,
|
||||||
|
|
||||||
// Other text
|
// Other text
|
||||||
TEXT_VERSION,
|
TEXT_VERSION,
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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 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_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 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);
|
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);
|
void remake_file(FS_Path path, FS_Archive archive, u32 size);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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",
|
"\uE07B Browse ThemePlaza",
|
||||||
NULL,
|
"\uE07C Dump Current Theme"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"\uE004 Sorting menu",
|
"\uE004 Sorting menu",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
#include "draw.h"
|
#include "draw.h"
|
||||||
#include <ctype.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_API_URL "/api/anemone/v1"
|
||||||
#define THEMEPLAZA_BASE_API_URL THEMEPLAZA_BASE_URL THEMEPLAZA_API_URL
|
#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
|
#define CACHE_PATH_FORMAT "/3ds/" APP_TITLE "/cache/%" JSON_INTEGER_FORMAT
|
||||||
|
|
||||||
bool themeplaza_browser(EntryMode mode);
|
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
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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 shuffle_install(Entry_List_s themes);
|
||||||
|
|
||||||
|
Result dump_theme(void);
|
||||||
|
|
||||||
void themes_check_installed(void * void_arg);
|
void themes_check_installed(void * void_arg);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
347
source/camera.c
347
source/camera.c
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -36,28 +36,63 @@
|
|||||||
#include <archive.h>
|
#include <archive.h>
|
||||||
#include <archive_entry.h>
|
#include <archive_entry.h>
|
||||||
|
|
||||||
void exit_qr(qr_data *data)
|
static void start_read(qr_data *data)
|
||||||
{
|
{
|
||||||
DEBUG("Exiting QR\n");
|
LightLock_Lock(&data->mut);
|
||||||
svcSignalEvent(data->cancel);
|
while(data->writer_waiting || data->writer_active)
|
||||||
while(!data->finished)
|
{
|
||||||
svcSleepThread(1000000);
|
CondVar_WaitTimeout(&data->cond, &data->mut, 1000000);
|
||||||
svcCloseHandle(data->cancel);
|
|
||||||
data->capturing = false;
|
|
||||||
|
|
||||||
free(data->camera_buffer);
|
|
||||||
free(data->tex);
|
|
||||||
quirc_destroy(data->context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
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();
|
camInit();
|
||||||
CAMU_SetSize(SELECT_OUT1, SIZE_CTR_TOP_LCD, CONTEXT_A);
|
CAMU_SetSize(SELECT_OUT1, SIZE_CTR_TOP_LCD, CONTEXT_A);
|
||||||
CAMU_SetOutputFormat(SELECT_OUT1, OUTPUT_RGB_565, 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_SetAutoExposure(SELECT_OUT1, true);
|
||||||
CAMU_SetAutoWhiteBalance(SELECT_OUT1, true);
|
CAMU_SetAutoWhiteBalance(SELECT_OUT1, true);
|
||||||
CAMU_Activate(SELECT_OUT1);
|
CAMU_Activate(SELECT_OUT1);
|
||||||
CAMU_GetBufferErrorInterruptEvent(&events[2], PORT_CAM1);
|
CAMU_GetBufferErrorInterruptEvent(&cam_events[2], PORT_CAM1);
|
||||||
CAMU_SetTrimming(PORT_CAM1, false);
|
CAMU_SetTrimming(PORT_CAM1, false);
|
||||||
CAMU_GetMaxBytes(&transferUnit, 400, 240);
|
CAMU_GetMaxBytes(&transferUnit, 400, 240);
|
||||||
CAMU_SetTransferBytes(PORT_CAM1, transferUnit, 400, 240);
|
CAMU_SetTransferBytes(PORT_CAM1, transferUnit, 400, 240);
|
||||||
CAMU_ClearBuffer(PORT_CAM1);
|
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);
|
CAMU_StartCapture(PORT_CAM1);
|
||||||
svcSignalEvent(data->started);
|
|
||||||
bool cancel = false;
|
bool cancel = false;
|
||||||
|
|
||||||
while (!cancel)
|
while (!cancel)
|
||||||
{
|
{
|
||||||
s32 index = 0;
|
s32 index = 0;
|
||||||
svcWaitSynchronizationN(&index, events, 3, false, U64_MAX);
|
svcWaitSynchronizationN(&index, cam_events, 3, false, U64_MAX);
|
||||||
switch(index) {
|
switch(index) {
|
||||||
case 0:
|
case 0:
|
||||||
DEBUG("Cancel event received\n");
|
DEBUG("Cancel event received\n");
|
||||||
cancel = true;
|
cancel = true;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
svcCloseHandle(events[1]);
|
svcCloseHandle(cam_events[1]);
|
||||||
events[1] = 0;
|
cam_events[1] = 0;
|
||||||
svcWaitSynchronization(data->mutex, U64_MAX);
|
|
||||||
memcpy(data->camera_buffer, buffer, 400 * 240 * sizeof(u16));
|
start_write(data);
|
||||||
GSPGPU_FlushDataCache(data->camera_buffer, 400 * 240 * sizeof(u16));
|
memcpy(data->camera_buffer, buffer, bufsz);
|
||||||
svcReleaseMutex(data->mutex);
|
data->any_update = true;
|
||||||
CAMU_SetReceiving(&events[1], buffer, PORT_CAM1, 400 * 240 * sizeof(u16), transferUnit);
|
stop_write(data);
|
||||||
|
|
||||||
|
CAMU_SetReceiving(&cam_events[1], buffer, PORT_CAM1, bufsz, transferUnit);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
svcCloseHandle(events[1]);
|
svcCloseHandle(cam_events[1]);
|
||||||
events[1] = 0;
|
cam_events[1] = 0;
|
||||||
|
|
||||||
CAMU_ClearBuffer(PORT_CAM1);
|
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);
|
CAMU_StartCapture(PORT_CAM1);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -115,118 +154,197 @@ void capture_cam_thread(void *arg)
|
|||||||
CAMU_ClearBuffer(PORT_CAM1);
|
CAMU_ClearBuffer(PORT_CAM1);
|
||||||
CAMU_Activate(SELECT_NONE);
|
CAMU_Activate(SELECT_NONE);
|
||||||
camExit();
|
camExit();
|
||||||
free(buffer);
|
|
||||||
for(int i = 0; i < 3; i++) {
|
linearFree(buffer);
|
||||||
if(events[i] != 0) {
|
for(int i = 1; i < 3; i++)
|
||||||
svcCloseHandle(events[i]);
|
{
|
||||||
events[i] = 0;
|
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;
|
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();
|
draw_base_interface();
|
||||||
|
|
||||||
// Untiled texture loading code adapted from FBI
|
// Untiled texture loading code adapted from FBI
|
||||||
svcWaitSynchronization(data->mutex, U64_MAX);
|
start_read(data);
|
||||||
for(u32 x = 0; x < 400 && !data->finished; x++) {
|
if(data->any_update)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
end_frame();
|
for(u32 y = 0; y < 240; y++) {
|
||||||
break;
|
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);
|
set_screen(bottom);
|
||||||
draw_text_center(GFX_BOTTOM, 4, 0.5, 0.5, 0.5, colors[COLOR_WHITE], "Press \uE005 To Quit");
|
draw_text_center(GFX_BOTTOM, 4, 0.5, 0.5, 0.5, colors[COLOR_WHITE], "Press \uE005 To Quit");
|
||||||
end_frame();
|
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;
|
if((data->cam_thread = threadCreate(capture_cam_thread, data, 0x10000, 0x1A, 1, false)) == NULL)
|
||||||
data->cancel = 0;
|
{
|
||||||
svcCreateEvent(&data->cancel, RESET_STICKY);
|
throw_error("Capture cam thread creation failed\nPlease report this to the developers", ERROR_LEVEL_ERROR);
|
||||||
svcCreateMutex(&data->mutex, false);
|
LightEvent_Signal(&data->event_cam_info);
|
||||||
if(threadCreate(capture_cam_thread, data, 0x10000, 0x1A, 1, true) == NULL)
|
LightEvent_Signal(&data->event_ui_info);
|
||||||
return false;
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
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 w;
|
||||||
int h;
|
int h;
|
||||||
u8 *image = (u8*) quirc_begin(data->context, &w, &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++) {
|
start_read(data);
|
||||||
u16 px = data->camera_buffer[y * 400 + x];
|
for (int y = 0; y < h; y++) {
|
||||||
image[y * w + x] = (u8)(((((px >> 11) & 0x1F) << 3) + (((px >> 5) & 0x3F) << 2) + ((px & 0x1F) << 3)) / 3);
|
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);
|
quirc_end(data->context);
|
||||||
if(quirc_count(data->context) > 0)
|
if(quirc_count(data->context) > 0)
|
||||||
{
|
{
|
||||||
struct quirc_code code;
|
struct quirc_code code;
|
||||||
struct quirc_data scan_data;
|
|
||||||
quirc_extract(data->context, 0, &code);
|
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);
|
draw_install(INSTALL_DOWNLOAD);
|
||||||
char * zip_buf = NULL;
|
char * zip_buf = NULL;
|
||||||
char * filename = 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)
|
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);
|
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);
|
buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf);
|
||||||
data->success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -301,33 +419,8 @@ void update_qr(qr_data *data)
|
|||||||
|
|
||||||
free(filename);
|
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;
|
return success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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_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_PREVIEW], staticBuf, "Downloading preview, please wait...");
|
||||||
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, "Downloading BGM, 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++)
|
for(int i = 0; i < TEXT_AMOUNT; i++)
|
||||||
C2D_TextOptimize(&text[i]);
|
C2D_TextOptimize(&text[i]);
|
||||||
@@ -300,7 +301,7 @@ void throw_error(char* error, ErrorLevel level)
|
|||||||
|
|
||||||
draw_base_interface();
|
draw_base_interface();
|
||||||
draw_text_center(GFX_TOP, 100, 0.5f, 0.6f, 0.6f, colors[text_color], error);
|
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();
|
end_frame();
|
||||||
|
|
||||||
if(kDown & KEY_A) break;
|
if(kDown & KEY_A) break;
|
||||||
@@ -552,13 +553,18 @@ void draw_grid_interface(Entry_List_s* list, Instructions_s instructions)
|
|||||||
vertical_offset += 24;
|
vertical_offset += 24;
|
||||||
horizontal_offset += 16;
|
horizontal_offset += 16;
|
||||||
|
|
||||||
|
// theoretically impossible to have no icon when from the api
|
||||||
|
/*
|
||||||
if(!current_entry->placeholder_color)
|
if(!current_entry->placeholder_color)
|
||||||
{
|
{
|
||||||
|
*/
|
||||||
C2D_Image * image = list->icons[i];
|
C2D_Image * image = list->icons[i];
|
||||||
C2D_DrawImageAt(*image, horizontal_offset, vertical_offset, 0.5f, NULL, 1.0f, 1.0f);
|
C2D_DrawImageAt(*image, horizontal_offset, vertical_offset, 0.5f, NULL, 1.0f, 1.0f);
|
||||||
|
/*
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
C2D_DrawRectSolid(horizontal_offset, vertical_offset, 0.5f, list->entry_size, list->entry_size, current_entry->placeholder_color);
|
C2D_DrawRectSolid(horizontal_offset, vertical_offset, 0.5f, list->entry_size, list->entry_size, current_entry->placeholder_color);
|
||||||
|
*/
|
||||||
|
|
||||||
if(i == selected_entry)
|
if(i == selected_entry)
|
||||||
{
|
{
|
||||||
|
|||||||
144
source/fs.c
144
source/fs.c
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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;
|
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)
|
void remake_file(FS_Path path, FS_Archive archive, u32 size)
|
||||||
{
|
{
|
||||||
Handle handle;
|
Handle handle;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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)
|
void parse_smdh(Icon_s *icon, Entry_s * entry, const u16 * fallback_name)
|
||||||
{
|
{
|
||||||
|
|
||||||
if(icon == NULL)
|
if(icon == NULL)
|
||||||
{
|
{
|
||||||
memcpy(entry->name, fallback_name, 0x80);
|
memcpy(entry->name, fallback_name, 0x80);
|
||||||
@@ -92,27 +93,12 @@ void parse_smdh(Icon_s *icon, Entry_s * entry, const u16 * fallback_name)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
memcpy(entry->name, icon->name, 0x40*sizeof(u16));
|
memcpy(entry->name, icon->name, 0x40*sizeof(u16));
|
||||||
memcpy(entry->desc, icon->desc, 0x80*sizeof(u16));
|
memcpy(entry->desc, icon->desc, 0x80*sizeof(u16));
|
||||||
memcpy(entry->author, icon->author, 0x40*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)
|
static C2D_Image * load_entry_icon(Entry_s entry)
|
||||||
{
|
{
|
||||||
char *info_buffer = NULL;
|
char *info_buffer = NULL;
|
||||||
@@ -120,7 +106,9 @@ static C2D_Image * load_entry_icon(Entry_s entry)
|
|||||||
if(!size) return NULL;
|
if(!size) return NULL;
|
||||||
|
|
||||||
Icon_s * smdh = (Icon_s *)info_buffer;
|
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 *);
|
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"))
|
if (!strcmp(dir_entry.shortExt, "ZIP"))
|
||||||
{
|
{
|
||||||
u32 size = zip_file_to_buf("info.smdh", path, &buf);
|
zip_file_to_buf("info.smdh", path, &buf);
|
||||||
if (size == 0) continue;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
const ssize_t len = strulen(path, 0x106);
|
||||||
struacat(path, "/info.smdh");
|
struacat(path, "/info.smdh");
|
||||||
u32 size = file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, &buf);
|
file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, &buf);
|
||||||
if (size == 0) continue;
|
memset(&path[len], 0, (0x106 - len) * sizeof(u16));
|
||||||
}
|
}
|
||||||
|
|
||||||
free(buf);
|
|
||||||
list->entries_count++;
|
list->entries_count++;
|
||||||
Entry_s * new_list = realloc(list->entries, list->entries_count * sizeof(Entry_s));
|
Entry_s * new_list = realloc(list->entries, list->entries_count * sizeof(Entry_s));
|
||||||
if(new_list == NULL)
|
if(new_list == NULL)
|
||||||
{
|
{
|
||||||
free(list->entries);
|
// out of memory: still allow use of currently loaded entries.
|
||||||
list->entries = NULL;
|
// Many things might die, depending on the heap layout after
|
||||||
res = -1;
|
list->entries_count--;
|
||||||
DEBUG("break\n");
|
free(buf);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
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]);
|
Entry_s * current_entry = &(list->entries[list->entries_count-1]);
|
||||||
memset(current_entry, 0, sizeof(Entry_s));
|
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);
|
memcpy(current_entry->path, path, 0x106 * sizeof(u16));
|
||||||
strucat(current_entry->path, dir_entry.name);
|
|
||||||
|
|
||||||
current_entry->is_zip = !strcmp(dir_entry.shortExt, "ZIP");
|
current_entry->is_zip = !strcmp(dir_entry.shortExt, "ZIP");
|
||||||
parse_entry_smdh(current_entry, dir_entry.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FSDIR_Close(dir_handle);
|
FSDIR_Close(dir_handle);
|
||||||
@@ -424,9 +410,12 @@ static bool load_icons(Entry_List_s * current_list, Handle mutex)
|
|||||||
int index = indexes[i];
|
int index = indexes[i];
|
||||||
|
|
||||||
C2D_Image * image = icons[index];
|
C2D_Image * image = icons[index];
|
||||||
|
if (icons[index] != NULL)
|
||||||
|
{
|
||||||
C3D_TexDelete(image->tex);
|
C3D_TexDelete(image->tex);
|
||||||
free(image->tex);
|
free(image->tex);
|
||||||
free(image);
|
free(image);
|
||||||
|
}
|
||||||
|
|
||||||
icons[index] = load_entry_icon(*current_entry);
|
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 ||
|
if(color_type == PNG_COLOR_TYPE_RGB ||
|
||||||
color_type == PNG_COLOR_TYPE_GRAY ||
|
color_type == PNG_COLOR_TYPE_GRAY ||
|
||||||
color_type == PNG_COLOR_TYPE_PALETTE)
|
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 ||
|
if(color_type == PNG_COLOR_TYPE_GRAY ||
|
||||||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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++)
|
for(int i = 0; i < amount; i++)
|
||||||
{
|
{
|
||||||
|
if (list->icons[i] == NULL) continue;
|
||||||
C3D_TexDelete(list->icons[i]->tex);
|
C3D_TexDelete(list->icons[i]->tex);
|
||||||
free(list->icons[i]->tex);
|
free(list->icons[i]->tex);
|
||||||
free(list->icons[i]);
|
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)
|
static SwkbdCallbackResult jump_menu_callback(void* entries_count, const char** ppMessage, const char* text, size_t textlen)
|
||||||
{
|
{
|
||||||
|
(void)textlen;
|
||||||
int typed_value = atoi(text);
|
int typed_value = atoi(text);
|
||||||
if(typed_value > *(int*)entries_count)
|
if(typed_value > *(int*)entries_count)
|
||||||
{
|
{
|
||||||
@@ -358,7 +360,6 @@ int main(void)
|
|||||||
bool preview_mode = false;
|
bool preview_mode = false;
|
||||||
int preview_offset = 0;
|
int preview_offset = 0;
|
||||||
|
|
||||||
bool qr_mode = false;
|
|
||||||
bool install_mode = false;
|
bool install_mode = false;
|
||||||
bool extra_mode = false;
|
bool extra_mode = false;
|
||||||
C2D_Image preview = {0};
|
C2D_Image preview = {0};
|
||||||
@@ -406,8 +407,7 @@ int main(void)
|
|||||||
instructions = extra_instructions[index];
|
instructions = extra_instructions[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(qr_mode) take_picture();
|
if(preview_mode)
|
||||||
else if(preview_mode)
|
|
||||||
{
|
{
|
||||||
draw_preview(preview, preview_offset);
|
draw_preview(preview, preview_offset);
|
||||||
}
|
}
|
||||||
@@ -439,14 +439,14 @@ int main(void)
|
|||||||
|
|
||||||
if(!install_mode && !extra_mode)
|
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:
|
switch_mode:
|
||||||
current_mode++;
|
current_mode++;
|
||||||
current_mode %= MODE_AMOUNT;
|
current_mode %= MODE_AMOUNT;
|
||||||
continue;
|
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:
|
enable_qr:
|
||||||
draw_base_interface();
|
draw_base_interface();
|
||||||
@@ -479,7 +479,7 @@ int main(void)
|
|||||||
|
|
||||||
continue;
|
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:
|
toggle_preview:
|
||||||
if(!preview_mode)
|
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;
|
goto touch;
|
||||||
|
|
||||||
int selected_entry = current_list->selected_entry;
|
int selected_entry = current_list->selected_entry;
|
||||||
@@ -648,6 +648,13 @@ int main(void)
|
|||||||
{
|
{
|
||||||
load_icons_first(current_list, false);
|
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)
|
else if(key_l)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
537
source/remote.c
537
source/remote.c
@@ -128,7 +128,12 @@ static C2D_Image * load_remote_smdh(Entry_s * entry, bool ignore_cache)
|
|||||||
smdh_buf = NULL;
|
smdh_buf = NULL;
|
||||||
char * api_url = NULL;
|
char * api_url = NULL;
|
||||||
asprintf(&api_url, THEMEPLAZA_SMDH_FORMAT, entry->tp_download_id);
|
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);
|
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 * page_json = NULL;
|
||||||
char * api_url = NULL;
|
char * api_url = NULL;
|
||||||
asprintf(&api_url, THEMEPLAZA_PAGE_FORMAT, page, mode + 1, list->tp_search);
|
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);
|
free(api_url);
|
||||||
|
if (R_FAILED(res))
|
||||||
|
{
|
||||||
|
free(page_json);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (json_len)
|
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);
|
list->tp_page_count = json_integer_value(value);
|
||||||
else if (json_is_array(value) && !strcmp(key, THEMEPLAZA_JSON_PAGE_IDS))
|
else if (json_is_array(value) && !strcmp(key, THEMEPLAZA_JSON_PAGE_IDS))
|
||||||
load_remote_entries(list, value, ignore_cache, loading_screen);
|
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);
|
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 u16 previous_path_preview[0x106] = { 0 };
|
||||||
|
|
||||||
static bool load_remote_preview(Entry_s * entry, C2D_Image * preview_image, int * preview_offset)
|
static bool load_remote_preview(Entry_s * entry, C2D_Image * preview_image, int * preview_offset)
|
||||||
{
|
{
|
||||||
bool not_cached = true;
|
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);
|
asprintf(&preview_url, THEMEPLAZA_PREVIEW_FORMAT, entry->tp_download_id);
|
||||||
|
|
||||||
draw_install(INSTALL_LOADING_REMOTE_PREVIEW);
|
draw_install(INSTALL_LOADING_REMOTE_PREVIEW);
|
||||||
|
Result res = http_get(preview_url, NULL, &preview_png, &preview_size, INSTALL_LOADING_REMOTE_PREVIEW, "image/png");
|
||||||
preview_size = http_get(preview_url, NULL, &preview_png, INSTALL_LOADING_REMOTE_PREVIEW);
|
|
||||||
free(preview_url);
|
free(preview_url);
|
||||||
|
if (R_FAILED(res))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preview_size)
|
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 u16 previous_path_bgm[0x106] = { 0 };
|
||||||
|
|
||||||
static void load_remote_bgm(Entry_s * entry)
|
static void load_remote_bgm(Entry_s * entry)
|
||||||
{
|
{
|
||||||
if (!memcmp(&previous_path_bgm, entry->path, 0x106 * sizeof(u16))) return;
|
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);
|
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);
|
free(bgm_url);
|
||||||
|
if (R_FAILED(res))
|
||||||
|
return;
|
||||||
|
|
||||||
u16 path[0x107] = { 0 };
|
u16 path[0x107] = { 0 };
|
||||||
strucat(path, entry->path);
|
strucat(path, entry->path);
|
||||||
@@ -332,7 +349,13 @@ static void download_remote_entry(Entry_s * entry, EntryMode mode)
|
|||||||
char * zip_buf = NULL;
|
char * zip_buf = NULL;
|
||||||
char * filename = NULL;
|
char * filename = NULL;
|
||||||
draw_install(INSTALL_DOWNLOAD);
|
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);
|
free(download_url);
|
||||||
|
|
||||||
char path_to_file[0x107] = { 0 };
|
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"))
|
if (extension == NULL || strcmp(extension, ".zip"))
|
||||||
strcat(path_to_file, ".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);
|
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);
|
buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf);
|
||||||
free(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);
|
int typed_value = atoi(text);
|
||||||
if (typed_value > *(json_int_t *)page_number)
|
if (typed_value > *(json_int_t *)page_number)
|
||||||
{
|
{
|
||||||
@@ -373,17 +398,19 @@ static void jump_menu(Entry_List_s * list)
|
|||||||
|
|
||||||
SwkbdState swkbd;
|
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);
|
int max_chars = strlen(numbuf);
|
||||||
swkbdInit(&swkbd, SWKBD_TYPE_NUMPAD, 2, max_chars);
|
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);
|
swkbdSetInitialText(&swkbd, numbuf);
|
||||||
|
|
||||||
sprintf(numbuf, "Which page do you want to jump to?");
|
sprintf(numbuf, "Which page do you want to jump to?");
|
||||||
swkbdSetHintText(&swkbd, numbuf);
|
swkbdSetHintText(&swkbd, numbuf);
|
||||||
|
|
||||||
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cance", false);
|
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false);
|
||||||
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Jump", true);
|
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Jump", true);
|
||||||
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars);
|
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars);
|
||||||
swkbdSetFilterCallback(&swkbd, jump_menu_callback, &list->tp_page_count);
|
swkbdSetFilterCallback(&swkbd, jump_menu_callback, &list->tp_page_count);
|
||||||
@@ -405,10 +432,10 @@ static void search_menu(Entry_List_s * list)
|
|||||||
|
|
||||||
SwkbdState swkbd;
|
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?");
|
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);
|
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Search", true);
|
||||||
swkbdSetValidation(&swkbd, SWKBD_NOTBLANK, 0, max_chars);
|
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;
|
newval += list->entries_per_screen_h;
|
||||||
if (newval / list->entries_per_screen_h != list->selected_entry / 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_per_screen_h * (-change_value);
|
||||||
|
newval %= list->entries_count;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -719,172 +747,389 @@ bool themeplaza_browser(EntryMode mode)
|
|||||||
return downloaded;
|
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;
|
Result ret;
|
||||||
httpcContext context;
|
httpcContext context;
|
||||||
char *new_url = NULL;
|
char redirect_url[0x824] = {0};
|
||||||
u32 status_code;
|
char new_url[0x824] = {0};
|
||||||
u32 content_size = 0;
|
|
||||||
u32 read_size = 0;
|
|
||||||
u32 size = 0;
|
|
||||||
char *last_buf;
|
|
||||||
|
|
||||||
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);
|
ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 1);
|
||||||
if (ret != 0)
|
if (R_FAILED(ret))
|
||||||
{
|
{
|
||||||
httpcCloseContext(&context);
|
httpcCloseContext(&context);
|
||||||
if (new_url != NULL) free(new_url);
|
|
||||||
DEBUG("httpcOpenContext %.8lx\n", ret);
|
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);
|
httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); // should let us do https
|
||||||
ret = httpcAddRequestHeaderField(&context, "User-Agent", USER_AGENT);
|
httpcSetKeepAlive(&context, HTTPC_KEEPALIVE_ENABLED);
|
||||||
ret = httpcAddRequestHeaderField(&context, "Connection", "Keep-Alive");
|
httpcAddRequestHeaderField(&context, "User-Agent", USER_AGENT);
|
||||||
|
httpcAddRequestHeaderField(&context, "Connection", "Keep-Alive");
|
||||||
|
if (acceptable_mime_types)
|
||||||
|
httpcAddRequestHeaderField(&context, "Accept", acceptable_mime_types);
|
||||||
|
|
||||||
ret = httpcBeginRequest(&context);
|
ret = httpcBeginRequest(&context);
|
||||||
if (ret != 0)
|
if (R_FAILED(ret))
|
||||||
{
|
{
|
||||||
httpcCloseContext(&context);
|
httpcCloseContext(&context);
|
||||||
if (new_url != NULL) free(new_url);
|
|
||||||
DEBUG("httpcBeginRequest %.8lx\n", ret);
|
DEBUG("httpcBeginRequest %.8lx\n", ret);
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = httpcGetResponseStatusCode(&context, &status_code);
|
#define ERROR_BUFFER_SIZE 0x80
|
||||||
if(ret!=0){
|
char err_buf[ERROR_BUFFER_SIZE];
|
||||||
httpcCloseContext(&context);
|
ParseResult parse = parse_header(&_header, &context, acceptable_mime_types);
|
||||||
if(new_url!=NULL) free(new_url);
|
switch (parse)
|
||||||
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);
|
case SUCCESS:
|
||||||
ret = httpcGetResponseHeader(&context, "Location", new_url, 0x1000);
|
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;
|
url = new_url;
|
||||||
httpcCloseContext(&context);
|
|
||||||
}
|
}
|
||||||
} while ((status_code >= 301 && status_code <= 303) || (status_code >= 307 && status_code <= 308));
|
else
|
||||||
|
|
||||||
if (status_code != 200)
|
|
||||||
{
|
{
|
||||||
httpcCloseContext(&context);
|
url = redirect_url;
|
||||||
if (new_url != NULL) free(new_url);
|
}
|
||||||
DEBUG("status_code, %lu\n", status_code);
|
DEBUG("HTTP Redirect: %s %s\n", new_url, *redirect_url == '/' ? "relative" : "absolute");
|
||||||
return 0;
|
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);
|
goto no_error;
|
||||||
if (ret != 0)
|
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);
|
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);
|
free(*buf);
|
||||||
DEBUG("httpcGetResponseHeader\n");
|
DEBUG("realloc failed in http_get - file possibly too large?\n");
|
||||||
return 0;
|
return MAKERESULT(RL_FATAL, RS_INTERNAL, RM_KERNEL, RD_OUT_OF_MEMORY);
|
||||||
}
|
}
|
||||||
|
*buf = new_buf;
|
||||||
|
|
||||||
char * tok = strstr(content_disposition, "filename=");
|
// download exactly chunk_size bytes and toss them into buf.
|
||||||
|
// size contains the current offset into buf.
|
||||||
if(!(tok))
|
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);
|
httpcCloseContext(&context);
|
||||||
free(new_url);
|
|
||||||
free(*buf);
|
free(*buf);
|
||||||
throw_error("Target is not valid!", ERROR_LEVEL_WARNING);
|
DEBUG("download failed in http_get\n");
|
||||||
DEBUG("filename\n");
|
return ret;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
*size += read_size;
|
||||||
|
|
||||||
tok += sizeof("filename=") - 1;
|
if (_header.file_size && install_type != INSTALL_NONE)
|
||||||
if(ispunct((int)*tok))
|
draw_loading_bar(*size, _header.file_size, install_type);
|
||||||
{
|
} while (ret == (Result)HTTPC_RESULTCODE_DOWNLOADPENDING);
|
||||||
tok++;
|
httpcCloseContext(&context);
|
||||||
}
|
|
||||||
char* last_char = tok + strlen(tok) - 1;
|
|
||||||
if(ispunct((int)*last_char))
|
|
||||||
{
|
|
||||||
*last_char = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
char *illegal_characters = "\"?;:/\\+";
|
// shrink to size
|
||||||
for (size_t i = 0; i < strlen(tok); i++)
|
new_buf = realloc(*buf, *size);
|
||||||
{
|
if (new_buf == NULL)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
httpcCloseContext(&context);
|
httpcCloseContext(&context);
|
||||||
free(new_url);
|
free(*buf);
|
||||||
free(last_buf);
|
DEBUG("shrinking realloc failed\n"); // 何?
|
||||||
DEBUG("NULL\n");
|
return MAKERESULT(RL_FATAL, RS_INTERNAL, RM_KERNEL, RD_OUT_OF_MEMORY);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
*buf = new_buf;
|
||||||
} while (ret == (s32)HTTPC_RESULTCODE_DOWNLOADPENDING);
|
|
||||||
|
|
||||||
last_buf = *buf;
|
DEBUG("size: %lu\n", *size);
|
||||||
*buf = realloc(*buf, size);
|
if (filename) { DEBUG("filename: %s\n", *filename); }
|
||||||
if (*buf == NULL)
|
return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
116
source/themes.c
116
source/themes.c
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* 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);
|
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);
|
remake_file(fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, BGM_MAX_SIZE);
|
||||||
res = buf_to_file(music_size, fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, music);
|
res = buf_to_file(music_size, fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, music);
|
||||||
free(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;
|
if(R_FAILED(res)) return res;
|
||||||
} else
|
} 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);
|
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)
|
void themes_check_installed(void * void_arg)
|
||||||
{
|
{
|
||||||
Thread_Arg_s * arg = (Thread_Arg_s *)void_arg;
|
Thread_Arg_s * arg = (Thread_Arg_s *)void_arg;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Anemone3DS
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
Reference in New Issue
Block a user