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
|
||||||
@@ -30,22 +30,24 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u16 *camera_buffer;
|
u16* camera_buffer;
|
||||||
C2D_Image image;
|
|
||||||
C3D_Tex *tex;
|
Handle event_stop;
|
||||||
Handle mutex;
|
Thread cam_thread, ui_thread;
|
||||||
volatile bool finished;
|
|
||||||
volatile bool closed;
|
LightEvent event_cam_info, event_ui_info;
|
||||||
volatile bool success;
|
|
||||||
Handle cancel;
|
CondVar cond;
|
||||||
Handle started;
|
LightLock mut;
|
||||||
|
u32 num_readers_active;
|
||||||
|
bool writer_waiting;
|
||||||
|
bool writer_active;
|
||||||
|
|
||||||
|
bool any_update;
|
||||||
|
|
||||||
bool capturing;
|
|
||||||
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",
|
||||||
@@ -142,4 +142,4 @@ Instructions_s extra_instructions[3] = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#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
|
||||||
@@ -127,4 +127,4 @@ void handle_scrolling(Entry_List_s * list);
|
|||||||
void load_icons_thread(void * void_arg);
|
void load_icons_thread(void * void_arg);
|
||||||
u32 load_data(char * filename, Entry_s entry, char ** buf);
|
u32 load_data(char * filename, Entry_s entry, char ** buf);
|
||||||
|
|
||||||
#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
|
||||||
@@ -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
|
||||||
|
|||||||
485
source/camera.c
485
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);
|
AtomicIncrement(&data->num_readers_active);
|
||||||
free(data->tex);
|
LightLock_Unlock(&data->mut);
|
||||||
quirc_destroy(data->context);
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void capture_cam_thread(void *arg)
|
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,219 +154,273 @@ 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;
|
LightEvent_Signal(&data->event_cam_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_ui(void *arg)
|
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);
|
|
||||||
svcCreateMutex(&data->mutex, false);
|
|
||||||
if(threadCreate(capture_cam_thread, data, 0x10000, 0x1A, 1, true) == NULL)
|
|
||||||
return false;
|
|
||||||
svcWaitSynchronization(data->started, U64_MAX);
|
|
||||||
if(threadCreate(update_ui, data, 0x10000, 0x1A, 1, true) == NULL)
|
|
||||||
{
|
{
|
||||||
exit_qr(data);
|
throw_error("Capture cam thread creation failed\nPlease report this to the developers", ERROR_LEVEL_ERROR);
|
||||||
|
LightEvent_Signal(&data->event_cam_info);
|
||||||
|
LightEvent_Signal(&data->event_ui_info);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if((data->ui_thread = threadCreate(update_ui, data, 0x10000, 0x1A, 1, false)) == NULL)
|
||||||
|
{
|
||||||
|
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);
|
|
||||||
|
|
||||||
draw_install(INSTALL_DOWNLOAD);
|
|
||||||
char * zip_buf = NULL;
|
|
||||||
char * filename = NULL;
|
|
||||||
u32 zip_size = http_get((char*)scan_data.payload, &filename, &zip_buf, INSTALL_DOWNLOAD);
|
|
||||||
|
|
||||||
if(zip_size != 0)
|
|
||||||
{
|
|
||||||
draw_install(INSTALL_CHECKING_DOWNLOAD);
|
|
||||||
|
|
||||||
struct archive *a = archive_read_new();
|
|
||||||
archive_read_support_format_zip(a);
|
|
||||||
|
|
||||||
int r = archive_read_open_memory(a, zip_buf, zip_size);
|
|
||||||
archive_read_free(a);
|
|
||||||
|
|
||||||
if(r == ARCHIVE_OK)
|
|
||||||
{
|
|
||||||
EntryMode mode = MODE_AMOUNT;
|
|
||||||
|
|
||||||
char * buf = NULL;
|
|
||||||
do {
|
|
||||||
if(zip_memory_to_buf("body_LZ.bin", zip_buf, zip_size, &buf) != 0)
|
|
||||||
{
|
|
||||||
mode = MODE_THEMES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(buf);
|
|
||||||
buf = NULL;
|
|
||||||
if(zip_memory_to_buf("splash.bin", zip_buf, zip_size, &buf) != 0)
|
|
||||||
{
|
|
||||||
mode = MODE_SPLASHES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(buf);
|
|
||||||
buf = NULL;
|
|
||||||
if(zip_memory_to_buf("splashbottom.bin", zip_buf, zip_size, &buf) != 0)
|
|
||||||
{
|
|
||||||
mode = MODE_SPLASHES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while(false);
|
|
||||||
|
|
||||||
free(buf);
|
|
||||||
buf = NULL;
|
|
||||||
|
|
||||||
if(mode != MODE_AMOUNT)
|
|
||||||
{
|
|
||||||
char path_to_file[0x107] = {0};
|
|
||||||
sprintf(path_to_file, "%s%s", main_paths[mode], filename);
|
|
||||||
char * extension = strrchr(path_to_file, '.');
|
|
||||||
if (extension == NULL || strcmp(extension, ".zip"))
|
|
||||||
strcat(path_to_file, ".zip");
|
|
||||||
|
|
||||||
remake_file(fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_size);
|
|
||||||
buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf);
|
|
||||||
data->success = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw_error("Zip downloaded is neither\na splash nor a theme.", ERROR_LEVEL_WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw_error("File downloaded isn't a zip.", ERROR_LEVEL_WARNING);
|
|
||||||
}
|
|
||||||
free(zip_buf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw_error("Download failed.", ERROR_LEVEL_WARNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(filename);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
bool init_qr(void)
|
||||||
{
|
{
|
||||||
qr_data *data = calloc(1, sizeof(qr_data));
|
qr_data 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));
|
memset(&data, 0, sizeof(data));
|
||||||
|
|
||||||
data->tex = (C3D_Tex*)malloc(sizeof(C3D_Tex));
|
start_qr(&data);
|
||||||
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);
|
struct quirc_data* scan_data = calloc(1, sizeof(struct quirc_data));
|
||||||
bool success = data->success;
|
const bool ready = start_capture_cam(&data);
|
||||||
while (!data->closed) svcSleepThread(1000000);
|
bool finished = !ready;
|
||||||
free(data);
|
|
||||||
|
|
||||||
|
while(!finished)
|
||||||
|
{
|
||||||
|
hidScanInput();
|
||||||
|
if (hidKeysDown() & (KEY_R | KEY_B | KEY_TOUCH))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
finished = update_qr(&data, scan_data);
|
||||||
|
svcSleepThread(50 * 1000 * 1000ULL); // only scan every 50ms
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_qr(&data);
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
if(finished && ready)
|
||||||
|
{
|
||||||
|
draw_install(INSTALL_DOWNLOAD);
|
||||||
|
char * zip_buf = NULL;
|
||||||
|
char * filename = NULL;
|
||||||
|
u32 zip_size;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
draw_install(INSTALL_CHECKING_DOWNLOAD);
|
||||||
|
|
||||||
|
struct archive *a = archive_read_new();
|
||||||
|
archive_read_support_format_zip(a);
|
||||||
|
|
||||||
|
int r = archive_read_open_memory(a, zip_buf, zip_size);
|
||||||
|
archive_read_free(a);
|
||||||
|
|
||||||
|
if(r == ARCHIVE_OK)
|
||||||
|
{
|
||||||
|
EntryMode mode = MODE_AMOUNT;
|
||||||
|
|
||||||
|
char * buf = NULL;
|
||||||
|
do {
|
||||||
|
if(zip_memory_to_buf("body_LZ.bin", zip_buf, zip_size, &buf) != 0)
|
||||||
|
{
|
||||||
|
mode = MODE_THEMES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
buf = NULL;
|
||||||
|
if(zip_memory_to_buf("splash.bin", zip_buf, zip_size, &buf) != 0)
|
||||||
|
{
|
||||||
|
mode = MODE_SPLASHES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
buf = NULL;
|
||||||
|
if(zip_memory_to_buf("splashbottom.bin", zip_buf, zip_size, &buf) != 0)
|
||||||
|
{
|
||||||
|
mode = MODE_SPLASHES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(false);
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
buf = NULL;
|
||||||
|
|
||||||
|
if(mode != MODE_AMOUNT)
|
||||||
|
{
|
||||||
|
char path_to_file[0x107] = {0};
|
||||||
|
sprintf(path_to_file, "%s%s", main_paths[mode], filename);
|
||||||
|
char * extension = strrchr(path_to_file, '.');
|
||||||
|
if (extension == NULL || strcmp(extension, ".zip"))
|
||||||
|
strcat(path_to_file, ".zip");
|
||||||
|
|
||||||
|
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);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw_error("Zip downloaded is neither\na splash nor a theme.", ERROR_LEVEL_WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw_error("File downloaded isn't a zip.", ERROR_LEVEL_WARNING);
|
||||||
|
}
|
||||||
|
free(zip_buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw_error("Download failed.", ERROR_LEVEL_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
|||||||
174
source/fs.c
174
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
|
||||||
@@ -25,29 +25,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
#include "unicode.h"
|
#include "unicode.h"
|
||||||
|
|
||||||
#include <archive.h>
|
#include <archive.h>
|
||||||
#include <archive_entry.h>
|
#include <archive_entry.h>
|
||||||
|
|
||||||
FS_Archive ArchiveSD;
|
FS_Archive ArchiveSD;
|
||||||
FS_Archive ArchiveHomeExt;
|
FS_Archive ArchiveHomeExt;
|
||||||
FS_Archive ArchiveThemeExt;
|
FS_Archive ArchiveThemeExt;
|
||||||
|
|
||||||
Result open_archives(void)
|
Result open_archives(void)
|
||||||
{
|
{
|
||||||
romfsInit();
|
romfsInit();
|
||||||
u8 regionCode;
|
u8 regionCode;
|
||||||
u32 archive1;
|
u32 archive1;
|
||||||
u32 archive2;
|
u32 archive2;
|
||||||
|
|
||||||
Result res = 0;
|
Result res = 0;
|
||||||
|
|
||||||
FS_Path home;
|
FS_Path home;
|
||||||
FS_Path theme;
|
FS_Path theme;
|
||||||
|
|
||||||
CFGU_SecureInfoGetRegion(®ionCode);
|
CFGU_SecureInfoGetRegion(®ionCode);
|
||||||
switch(regionCode)
|
switch(regionCode)
|
||||||
{
|
{
|
||||||
@@ -67,7 +67,7 @@ Result open_archives(void)
|
|||||||
archive1 = 0x00;
|
archive1 = 0x00;
|
||||||
archive2 = 0x00;
|
archive2 = 0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveSD, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")))) return res;
|
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveSD, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")))) return res;
|
||||||
|
|
||||||
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Themes"), FS_ATTRIBUTE_DIRECTORY);
|
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Themes"), FS_ATTRIBUTE_DIRECTORY);
|
||||||
@@ -75,43 +75,43 @@ Result open_archives(void)
|
|||||||
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds"), FS_ATTRIBUTE_DIRECTORY);
|
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds"), FS_ATTRIBUTE_DIRECTORY);
|
||||||
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE), FS_ATTRIBUTE_DIRECTORY);
|
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE), FS_ATTRIBUTE_DIRECTORY);
|
||||||
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/cache"), FS_ATTRIBUTE_DIRECTORY);
|
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/cache"), FS_ATTRIBUTE_DIRECTORY);
|
||||||
|
|
||||||
u32 homeMenuPath[3] = {MEDIATYPE_SD, archive2, 0};
|
u32 homeMenuPath[3] = {MEDIATYPE_SD, archive2, 0};
|
||||||
home.type = PATH_BINARY;
|
home.type = PATH_BINARY;
|
||||||
home.size = 0xC;
|
home.size = 0xC;
|
||||||
home.data = homeMenuPath;
|
home.data = homeMenuPath;
|
||||||
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveHomeExt, ARCHIVE_EXTDATA, home))) return res;
|
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveHomeExt, ARCHIVE_EXTDATA, home))) return res;
|
||||||
|
|
||||||
u32 themePath[3] = {MEDIATYPE_SD, archive1, 0};
|
u32 themePath[3] = {MEDIATYPE_SD, archive1, 0};
|
||||||
theme.type = PATH_BINARY;
|
theme.type = PATH_BINARY;
|
||||||
theme.size = 0xC;
|
theme.size = 0xC;
|
||||||
theme.data = themePath;
|
theme.data = themePath;
|
||||||
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveThemeExt, ARCHIVE_EXTDATA, theme))) return res;
|
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveThemeExt, ARCHIVE_EXTDATA, theme))) return res;
|
||||||
|
|
||||||
Handle test_handle;
|
Handle test_handle;
|
||||||
if(R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveThemeExt, fsMakePath(PATH_ASCII, "/ThemeManage.bin"), FS_OPEN_READ, 0))) return res;
|
if(R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveThemeExt, fsMakePath(PATH_ASCII, "/ThemeManage.bin"), FS_OPEN_READ, 0))) return res;
|
||||||
FSFILE_Close(test_handle);
|
FSFILE_Close(test_handle);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result close_archives(void)
|
Result close_archives(void)
|
||||||
{
|
{
|
||||||
Result res;
|
Result res;
|
||||||
|
|
||||||
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveSD))) return res;
|
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveSD))) return res;
|
||||||
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveHomeExt))) return res;
|
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveHomeExt))) return res;
|
||||||
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveThemeExt))) return res;
|
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveThemeExt))) return res;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 file_to_buf(FS_Path path, FS_Archive archive, char** buf)
|
u32 file_to_buf(FS_Path path, FS_Archive archive, char** buf)
|
||||||
{
|
{
|
||||||
Handle file;
|
Handle file;
|
||||||
Result res = 0;
|
Result res = 0;
|
||||||
if (R_FAILED(res = FSUSER_OpenFile(&file, archive, path, FS_OPEN_READ, 0))) return 0;
|
if (R_FAILED(res = FSUSER_OpenFile(&file, archive, path, FS_OPEN_READ, 0))) return 0;
|
||||||
|
|
||||||
u64 size;
|
u64 size;
|
||||||
FSFILE_GetSize(file, &size);
|
FSFILE_GetSize(file, &size);
|
||||||
if(size != 0)
|
if(size != 0)
|
||||||
@@ -195,7 +195,149 @@ Result buf_to_file(u32 size, FS_Path path, FS_Archive archive, char *buf)
|
|||||||
if (R_FAILED(res = FSFILE_Close(handle))) return res;
|
if (R_FAILED(res = FSFILE_Close(handle))) return res;
|
||||||
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];
|
||||||
C3D_TexDelete(image->tex);
|
if (icons[index] != NULL)
|
||||||
free(image->tex);
|
{
|
||||||
free(image);
|
C3D_TexDelete(image->tex);
|
||||||
|
free(image->tex);
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
@@ -316,7 +318,7 @@ static void toggle_shuffle(Entry_List_s * list)
|
|||||||
current_entry->no_bgm_shuffle = true;
|
current_entry->no_bgm_shuffle = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
current_entry->in_shuffle = true;
|
current_entry->in_shuffle = true;
|
||||||
list->shuffle_count++;
|
list->shuffle_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
|
||||||
|
|||||||
807
source/remote.c
807
source/remote.c
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
|||||||
124
source/themes.c
124
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
|
||||||
@@ -184,16 +184,30 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
|
|||||||
if(installmode & THEME_INSTALL_BGM)
|
if(installmode & THEME_INSTALL_BGM)
|
||||||
{
|
{
|
||||||
music_size = load_data("/bgm.bcstm", current_theme, &music);
|
music_size = load_data("/bgm.bcstm", current_theme, &music);
|
||||||
if(music_size > BGM_MAX_SIZE)
|
if (music_size > BGM_MAX_SIZE)
|
||||||
{
|
{
|
||||||
free(music);
|
free(music);
|
||||||
DEBUG("bgm too big\n");
|
DEBUG("bgm too big\n");
|
||||||
return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_TOO_LARGE);
|
return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_TOO_LARGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
remake_file(fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, BGM_MAX_SIZE);
|
if (music_size != 0)
|
||||||
res = buf_to_file(music_size, fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, music);
|
{
|
||||||
free(music);
|
remake_file(fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, BGM_MAX_SIZE);
|
||||||
|
res = buf_to_file(music_size, fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, music);
|
||||||
|
free(music);
|
||||||
|
|
||||||
|
char *body_buf = NULL;
|
||||||
|
u32 uncompressed_size = decompress_lz_file(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, &body_buf);
|
||||||
|
if (body_buf[5] != 1)
|
||||||
|
{
|
||||||
|
installmode |= THEME_INSTALL_BODY;
|
||||||
|
body_buf[5] = 1;
|
||||||
|
body_size = compress_lz_file_fast(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, body_buf, uncompressed_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(body_buf);
|
||||||
|
}
|
||||||
|
|
||||||
if(R_FAILED(res)) return res;
|
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