UNIVERSAL-UPDATER IS BACK! Lmao.

This commit is contained in:
VoltZ
2019-10-31 03:23:05 +01:00
committed by GitHub
parent f64ab53907
commit c548cca57a
35 changed files with 25521 additions and 0 deletions
+670
View File
@@ -0,0 +1,670 @@
/*
* This file is part of Universal-Updater
* Copyright (C) 2019 VoltZ, Epicpkmn11, Flame, RocketRobz, TotallyNotGuy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gui.hpp"
#include "download/download.hpp"
#include "lang/lang.hpp"
#include "screens/screenCommon.hpp"
#include "utils/extract.hpp"
#include "utils/fileBrowse.h"
#include "utils/inifile.h"
#include "utils/thread.hpp"
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
extern "C" {
#include "utils/cia.h"
}
#define USER_AGENT APP_TITLE "-" V_STRING
static char* result_buf = NULL;
static size_t result_sz = 0;
static size_t result_written = 0;
std::vector<std::string> _topText;
std::string jsonName;
extern bool downloadNightlies;
extern int filesExtracted;
extern std::string extractingFile;
char progressBarMsg[128] = "";
bool showProgressBar = false;
bool progressBarType = 0; // 0 = Download | 1 = Extract
// following function is from
// https://github.com/angelsl/libctrfgh/blob/master/curl_test/src/main.c
static size_t handle_data(char* ptr, size_t size, size_t nmemb, void* userdata)
{
(void) userdata;
const size_t bsz = size*nmemb;
if (result_sz == 0 || !result_buf)
{
result_sz = 0x1000;
result_buf = (char*)malloc(result_sz);
}
bool need_realloc = false;
while (result_written + bsz > result_sz)
{
result_sz <<= 1;
need_realloc = true;
}
if (need_realloc)
{
char *new_buf = (char*)realloc(result_buf, result_sz);
if (!new_buf)
{
return 0;
}
result_buf = new_buf;
}
if (!result_buf)
{
return 0;
}
memcpy(result_buf + result_written, ptr, bsz);
result_written += bsz;
return bsz;
}
static Result setupContext(CURL *hnd, const char * url)
{
curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L);
curl_easy_setopt(hnd, CURLOPT_URL, url);
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(hnd, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(hnd, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, handle_data);
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(hnd, CURLOPT_STDERR, stdout);
return 0;
}
Result downloadToFile(std::string url, std::string path)
{
Result ret = 0;
printf("Downloading from:\n%s\nto:\n%s\n", url.c_str(), path.c_str());
void *socubuf = memalign(0x1000, 0x100000);
if (!socubuf)
{
return -1;
}
ret = socInit((u32*)socubuf, 0x100000);
if (R_FAILED(ret))
{
free(socubuf);
return ret;
}
CURL *hnd = curl_easy_init();
ret = setupContext(hnd, url.c_str());
if (ret != 0) {
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return ret;
}
Handle fileHandle;
u64 offset = 0;
u32 bytesWritten = 0;
ret = openFile(&fileHandle, path.c_str(), true);
if (R_FAILED(ret)) {
printf("Error: couldn't open file to write.\n");
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return DL_ERROR_WRITEFILE;
}
u64 startTime = osGetTime();
CURLcode cres = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
if (cres != CURLE_OK) {
printf("Error in:\ncurl\n");
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return -1;
}
FSFILE_Write(fileHandle, &bytesWritten, offset, result_buf, result_written, 0);
u64 endTime = osGetTime();
u64 totalTime = endTime - startTime;
printf("Download took %llu milliseconds.\n", totalTime);
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
FSFILE_Close(fileHandle);
return 0;
}
Result downloadFromRelease(std::string url, std::string asset, std::string path)
{
Result ret = 0;
void *socubuf = memalign(0x1000, 0x100000);
if (!socubuf)
{
return -1;
}
ret = socInit((u32*)socubuf, 0x100000);
if (R_FAILED(ret))
{
free(socubuf);
return ret;
}
std::regex parseUrl("github\\.com\\/(.+)\\/(.+)");
std::smatch result;
regex_search(url, result, parseUrl);
std::string repoOwner = result[1].str(), repoName = result[2].str();
std::stringstream apiurlStream;
apiurlStream << "https://api.github.com/repos/" << repoOwner << "/" << repoName << "/releases/latest";
std::string apiurl = apiurlStream.str();
printf("Downloading latest release from repo:\n%s\nby:\n%s\n", repoName.c_str(), repoOwner.c_str());
printf("Crafted API url:\n%s\n", apiurl.c_str());
CURL *hnd = curl_easy_init();
ret = setupContext(hnd, apiurl.c_str());
if (ret != 0) {
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return ret;
}
CURLcode cres = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
char* newbuf = (char*)realloc(result_buf, result_written + 1);
result_buf = newbuf;
result_buf[result_written] = 0; //nullbyte to end it as a proper C style string
if (cres != CURLE_OK) {
printf("Error in:\ncurl\n");
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return -1;
}
printf("Looking for asset with matching name:\n%s\n", asset.c_str());
std::string assetUrl;
json parsedAPI = json::parse(result_buf);
if (parsedAPI["assets"].is_array()) {
for (auto jsonAsset : parsedAPI["assets"]) {
if (jsonAsset.is_object() && jsonAsset["name"].is_string() && jsonAsset["browser_download_url"].is_string()) {
std::string assetName = jsonAsset["name"];
if (matchPattern(asset, assetName)) {
assetUrl = jsonAsset["browser_download_url"];
break;
}
}
}
}
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
if (assetUrl.empty())
ret = DL_ERROR_GIT;
else
ret = downloadToFile(assetUrl, path);
return ret;
}
/**
* Check Wi-Fi status.
* @return True if Wi-Fi is connected; false if not.
*/
bool checkWifiStatus(void) {
u32 wifiStatus;
bool res = false;
if (R_SUCCEEDED(ACU_GetWifiStatus(&wifiStatus)) && wifiStatus) {
res = true;
}
return res;
}
void downloadFailed(void) {
DisplayMsg(Lang::get("DOWNLOAD_FAILED"));
for (int i = 0; i < 60*2; i++) {
gspWaitForVBlank();
}
}
void notImplemented(void) {
DisplayMsg(Lang::get("NOT_IMPLEMENTED"));
for (int i = 0; i < 60*2; i++) {
gspWaitForVBlank();
}
}
void doneMsg(void) {
DisplayMsg(Lang::get("DONE"));
for (int i = 0; i < 60*2; i++) {
gspWaitForVBlank();
}
}
void notConnectedMsg(void) {
DisplayMsg(Lang::get("CONNECT_WIFI"));
for (int i = 0; i < 60*2; i++) {
gspWaitForVBlank();
}
}
std::string getLatestRelease(std::string repo, std::string item)
{
Result ret = 0;
void *socubuf = memalign(0x1000, 0x100000);
if (!socubuf)
{
return "";
}
ret = socInit((u32*)socubuf, 0x100000);
if (R_FAILED(ret))
{
free(socubuf);
return "";
}
std::stringstream apiurlStream;
apiurlStream << "https://api.github.com/repos/" << repo << "/releases/latest";
std::string apiurl = apiurlStream.str();
CURL *hnd = curl_easy_init();
ret = setupContext(hnd, apiurl.c_str());
if (ret != 0) {
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return "";
}
CURLcode cres = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
char* newbuf = (char*)realloc(result_buf, result_written + 1);
result_buf = newbuf;
result_buf[result_written] = 0; //nullbyte to end it as a proper C style string
if (cres != CURLE_OK) {
printf("Error in:\ncurl\n");
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return "";
}
std::string jsonItem;
json parsedAPI = json::parse(result_buf);
if (parsedAPI[item].is_string()) {
jsonItem = parsedAPI[item];
}
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return jsonItem;
}
std::string getLatestCommit(std::string repo, std::string item)
{
Result ret = 0;
void *socubuf = memalign(0x1000, 0x100000);
if (!socubuf)
{
return "";
}
ret = socInit((u32*)socubuf, 0x100000);
if (R_FAILED(ret))
{
free(socubuf);
return "";
}
std::stringstream apiurlStream;
apiurlStream << "https://api.github.com/repos/" << repo << "/commits/master";
std::string apiurl = apiurlStream.str();
CURL *hnd = curl_easy_init();
ret = setupContext(hnd, apiurl.c_str());
if (ret != 0) {
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return "";
}
CURLcode cres = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
char* newbuf = (char*)realloc(result_buf, result_written + 1);
result_buf = newbuf;
result_buf[result_written] = 0; //nullbyte to end it as a proper C style string
if (cres != CURLE_OK) {
printf("Error in:\ncurl\n");
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return "";
}
std::string jsonItem;
json parsedAPI = json::parse(result_buf);
if (parsedAPI[item].is_string()) {
jsonItem = parsedAPI[item];
}
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return jsonItem;
}
std::string getLatestCommit(std::string repo, std::string array, std::string item)
{
Result ret = 0;
void *socubuf = memalign(0x1000, 0x100000);
if (!socubuf)
{
return "";
}
ret = socInit((u32*)socubuf, 0x100000);
if (R_FAILED(ret))
{
free(socubuf);
return "";
}
std::stringstream apiurlStream;
apiurlStream << "https://api.github.com/repos/" << repo << "/commits/master";
std::string apiurl = apiurlStream.str();
CURL *hnd = curl_easy_init();
ret = setupContext(hnd, apiurl.c_str());
if (ret != 0) {
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return "";
}
CURLcode cres = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
char* newbuf = (char*)realloc(result_buf, result_written + 1);
result_buf = newbuf;
result_buf[result_written] = 0; //nullbyte to end it as a proper C style string
if (cres != CURLE_OK) {
printf("Error in:\ncurl\n");
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return "";
}
std::string jsonItem;
json parsedAPI = json::parse(result_buf);
if (parsedAPI[array][item].is_string()) {
jsonItem = parsedAPI[array][item];
}
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return jsonItem;
}
std::vector<ThemeEntry> getThemeList(std::string repo, std::string path)
{
Result ret = 0;
void *socubuf = memalign(0x1000, 0x100000);
std::vector<ThemeEntry> emptyVector;
if (!socubuf)
{
return emptyVector;
}
ret = socInit((u32*)socubuf, 0x100000);
if (R_FAILED(ret))
{
free(socubuf);
return emptyVector;
}
std::stringstream apiurlStream;
apiurlStream << "https://api.github.com/repos/" << repo << "/contents/" << path;
std::string apiurl = apiurlStream.str();
CURL *hnd = curl_easy_init();
ret = setupContext(hnd, apiurl.c_str());
if (ret != 0) {
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return emptyVector;
}
CURLcode cres = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
char* newbuf = (char*)realloc(result_buf, result_written + 1);
result_buf = newbuf;
result_buf[result_written] = 0; //nullbyte to end it as a proper C style string
if (cres != CURLE_OK) {
printf("Error in:\ncurl\n");
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return emptyVector;
}
std::vector<ThemeEntry> jsonItems;
json parsedAPI = json::parse(result_buf);
for(uint i=0;i<parsedAPI.size();i++) {
ThemeEntry themeEntry;
if (parsedAPI[i]["name"].is_string()) {
themeEntry.name = parsedAPI[i]["name"];
}
if (parsedAPI[i]["download_url"].is_string()) {
themeEntry.downloadUrl = parsedAPI[i]["download_url"];
}
if (parsedAPI[i]["path"].is_string()) {
themeEntry.sdPath = "sdmc:/";
themeEntry.sdPath += parsedAPI[i]["path"];
themeEntry.path = parsedAPI[i]["path"];
size_t pos;
while ((pos = themeEntry.path.find(" ")) != std::string::npos) {
themeEntry.path.replace(pos, 1, "%20");
}
}
jsonItems.push_back(themeEntry);
}
socExit();
free(result_buf);
free(socubuf);
result_buf = NULL;
result_sz = 0;
result_written = 0;
return jsonItems;
}
void downloadTheme(std::string path) {
std::vector<ThemeEntry> themeContents = getThemeList("Universal-Team/extras", path);
for(uint i=0;i<themeContents.size();i++) {
if(themeContents[i].downloadUrl != "") {
DisplayMsg((Lang::get("DOWNLOADING")+themeContents[i].name).c_str());
downloadToFile(themeContents[i].downloadUrl, themeContents[i].sdPath);
} else {
DisplayMsg((Lang::get("DOWNLOADING")+themeContents[i].name).c_str());
mkdir((themeContents[i].sdPath).c_str(), 0777);
downloadTheme(themeContents[i].path);
}
}
}
void displayProgressBar() {
char str[256];
while(showProgressBar) {
snprintf(str, sizeof(str), "%s\n%s%s\n%i %s", progressBarMsg, (!progressBarType ? "" : (Lang::get("CURRENTLY_EXTRACTING")).c_str()), (!progressBarType ? "" : extractingFile.c_str()), (!progressBarType ? (int)round(result_written/1000) : filesExtracted), (!progressBarType ? (Lang::get("KB_DOWNLOADED")).c_str() : (filesExtracted == 1 ? (Lang::get("FILE_EXTRACTED")).c_str() :(Lang::get("FILES_EXTRACTED")).c_str())));
DisplayMsg(str);
gspWaitForVBlank();
}
}
void updateBootstrap(bool nightly) {
if(nightly) {
snprintf(progressBarMsg, sizeof(progressBarMsg), (Lang::get("DOWNLOAD_NDSBOOTSTRAP_NIGHTLY")).c_str());
showProgressBar = true;
progressBarType = 0;
Threads::create((ThreadFunc)displayProgressBar);
if (downloadToFile("https://github.com/TWLBot/Builds/blob/master/nds-bootstrap.7z?raw=true", "/nds-bootstrap-nightly.7z") != 0) {
showProgressBar = false;
downloadFailed();
return;
}
snprintf(progressBarMsg, sizeof(progressBarMsg), (Lang::get("EXTRACT_NDSBOOTSTRAP_NIGHTLY")).c_str());
filesExtracted = 0;
progressBarType = 1;
extractArchive("/nds-bootstrap-nightly.7z", "nds-bootstrap/", "/_nds/");
showProgressBar = false;
deleteFile("sdmc:/nds-bootstrap-nightly.7z");
} else {
DisplayMsg(Lang::get("DOWNLOAD_NDSBOOTSTRAP_RELEASE"));
snprintf(progressBarMsg, sizeof(progressBarMsg), (Lang::get("DOWNLOAD_NDSBOOTSTRAP_RELEASE")).c_str());
showProgressBar = true;
progressBarType = 0;
Threads::create((ThreadFunc)displayProgressBar);
if (downloadFromRelease("https://github.com/ahezard/nds-bootstrap", "nds-bootstrap\\.zip", "/nds-bootstrap-release.zip") != 0) {
showProgressBar = false;
downloadFailed();
return;
}
snprintf(progressBarMsg, sizeof(progressBarMsg), (Lang::get("EXTRACT_NDSBOOTSTRAP_RELEASE")).c_str());
filesExtracted = 0;
progressBarType = 1;
extractArchive("/nds-bootstrap-release.zip", "/", "/_nds/");
showProgressBar = false;
deleteFile("sdmc:/nds-bootstrap-release.zip");
}
doneMsg();
}
+159
View File
@@ -0,0 +1,159 @@
/*
* This file is part of Universal-Updater
* Copyright (C) 2019 VoltZ, Epicpkmn11, Flame, RocketRobz, TotallyNotGuy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gui.hpp"
#include "screens/screenCommon.hpp"
#include <3ds.h>
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <stack>
C3D_RenderTarget* top;
C3D_RenderTarget* bottom;
C2D_TextBuf sizeBuf;
std::stack<std::unique_ptr<Screen>> screens;
bool currentScreen = false;
// Clear Text.
void Gui::clearTextBufs(void)
{
C2D_TextBufClear(sizeBuf);
}
// Initialize GUI.
Result Gui::init(void)
{
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
C2D_Init(C2D_DEFAULT_MAX_OBJECTS);
C2D_Prepare();
top = C2D_CreateScreenTarget(GFX_TOP, GFX_LEFT);
bottom = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT);
sizeBuf = C2D_TextBufNew(4096);
return 0;
}
// Exit the whole GUI.
void Gui::exit(void)
{
C2D_TextBufDelete(sizeBuf);
C2D_Fini();
C3D_Fini();
}
void DisplayMsg(std::string text) {
Gui::clearTextBufs();
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
C2D_TargetClear(top, BLACK);
C2D_TargetClear(bottom, BLACK);
Gui::DrawTop();
Gui::DrawString(10, 40, 0.45f, WHITE, text, 380);
Gui::DrawBottom();
C3D_FrameEnd(0);
}
void Gui::DrawStringCentered(float x, float y, float size, u32 color, std::string Text, int maxWidth) {
Gui::DrawString((currentScreen ? 200 : 160)+x-(std::min(maxWidth, (int)Gui::GetStringWidth(size, Text))/2), y, size, color, Text, maxWidth);
}
// Draw String or Text.
void Gui::DrawString(float x, float y, float size, u32 color, std::string Text, int maxWidth) {
C2D_Text c2d_text;
C2D_TextParse(&c2d_text, sizeBuf, Text.c_str());
C2D_TextOptimize(&c2d_text);
C2D_DrawText(&c2d_text, C2D_WithColor, x, y, 0.5f, std::min(size, size*(maxWidth/Gui::GetStringWidth(size, Text))), size, color);
}
// Get String or Text Width.
float Gui::GetStringWidth(float size, std::string Text) {
float width = 0;
GetStringSize(size, &width, NULL, Text);
return width;
}
// Get String or Text Size.
void Gui::GetStringSize(float size, float *width, float *height, std::string Text) {
C2D_Text c2d_text;
C2D_TextParse(&c2d_text, sizeBuf, Text.c_str());
C2D_TextGetDimensions(&c2d_text, size, size, width, height);
}
// Get String or Text Height.
float Gui::GetStringHeight(float size, std::string Text) {
float height = 0;
GetStringSize(size, NULL, &height, Text.c_str());
return height;
}
// Draw a Rectangle.
bool Gui::Draw_Rect(float x, float y, float w, float h, u32 color) {
return C2D_DrawRectSolid(x, y, 0.5f, w, h, color);
}
// Mainloop the GUI.
void Gui::mainLoop(u32 hDown, u32 hHeld, touchPosition touch) {
screens.top()->Draw();
screens.top()->Logic(hDown, hHeld, touch);
}
// Set the current Screen.
void Gui::setScreen(std::unique_ptr<Screen> screen)
{
screens.push(std::move(screen));
}
// Go a Screen back.
void Gui::screenBack()
{
screens.pop();
}
// Select, on which Screen should be drawn.
void Gui::ScreenDraw(C3D_RenderTarget * screen)
{
C2D_SceneBegin(screen);
currentScreen = screen == top ? 1 : 0;
}
void Gui::DrawTop(void) {
Gui::ScreenDraw(top);
Gui::Draw_Rect(0, 0, 400, 30, BarColor);
Gui::Draw_Rect(0, 30, 400, 180, TopBGColor);
Gui::Draw_Rect(0, 210, 400, 30, BarColor);
}
void Gui::DrawBottom(void) {
Gui::ScreenDraw(bottom);
Gui::Draw_Rect(0, 0, 320, 30, BarColor);
Gui::Draw_Rect(0, 30, 320, 180, BottomBGColor);
Gui::Draw_Rect(0, 210, 320, 30, BarColor);
}
+21
View File
@@ -0,0 +1,21 @@
#include "lang/lang.hpp"
#include <stdio.h>
nlohmann::json appJson;
std::string Lang::get(const std::string &key) {
if(!appJson.contains(key)) {
return "MISSING: " + key;
}
return appJson.at(key).get_ref<const std::string&>();
}
std::string langs[] = {"de", "en", "es", "fr", "it", "jp", "lt", "pt"};
void Lang::load(int lang) {
FILE* values;
values = fopen(("romfs:/lang/"+langs[lang]+"/app.json").c_str(), "rt");
if(values) appJson = nlohmann::json::parse(values, nullptr, false);
fclose(values);
}
+74
View File
@@ -0,0 +1,74 @@
/*
* This file is part of Universal-Updater
* Copyright (C) 2019 VoltZ, Epicpkmn11, Flame, RocketRobz, TotallyNotGuy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gui.hpp"
#include "lang/lang.hpp"
#include "screens/mainMenu.hpp"
#include "screens/screenCommon.hpp"
#include <3ds.h>
#include <unistd.h>
bool exiting = false;
touchPosition touch;
int main()
{
gfxInitDefault();
Gui::init();
romfsInit();
sdmcInit();
cfguInit();
Lang::load(1);
Gui::setScreen(std::make_unique<MainMenu>());
osSetSpeedupEnable(true); // Enable speed-up for New 3DS users
// Loop as long as the status is not exit
while (aptMainLoop() && !exiting)
{
hidScanInput();
u32 hHeld = hidKeysHeld();
u32 hDown = hidKeysDown();
hidTouchRead(&touch);
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
C2D_TargetClear(top, BLACK);
C2D_TargetClear(bottom, BLACK);
Gui::clearTextBufs();
Gui::mainLoop(hDown, hHeld, touch);
C3D_FrameEnd(0);
}
Gui::exit();
gfxExit();
cfguExit();
romfsExit();
sdmcExit();
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
/*
* This file is part of Universal-Updater
* Copyright (C) 2019 VoltZ, Epicpkmn11, Flame, RocketRobz, TotallyNotGuy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "download.hpp"
#include "screens/mainMenu.hpp"
#include "screens/screenCommon.hpp"
extern bool exiting;
void MainMenu::Draw(void) const {
Gui::DrawTop();
Gui::DrawStringCentered(0, 2, 0.7f, TextColor, "Universal-Updater", 400);
Gui::DrawString(395-Gui::GetStringWidth(0.72f, VERSION_STRING), 218, 0.72f, WHITE, VERSION_STRING);
Gui::DrawBottom();
}
void MainMenu::Logic(u32 hDown, u32 hHeld, touchPosition touch) {
if (hDown & KEY_START) {
exiting = true;
}
if (hDown & KEY_X) {
updateBootstrap(true);
}
}
+136
View File
@@ -0,0 +1,136 @@
#include "utils/cia.h"
bool updatingSelf;
static Result CIA_LaunchTitle(u64 titleId, FS_MediaType mediaType) {
Result ret = 0;
u8 param[0x300];
u8 hmac[0x20];
if (R_FAILED(ret = APT_PrepareToDoApplicationJump(0, titleId, mediaType))) {
printf("Error In:\nAPT_PrepareToDoApplicationJump");
return ret;
}
if (R_FAILED(ret = APT_DoApplicationJump(param, sizeof(param), hmac))) {
printf("Error In:\nAPT_DoApplicationJump");
return ret;
}
return 0;
}
Result deletePrevious(u64 titleid, FS_MediaType media)
{
Result ret = 0;
u32 titles_amount = 0;
ret = AM_GetTitleCount(media, &titles_amount);
if (R_FAILED(ret)) {
printf("Error in:\nAM_GetTitleCount\n");
return ret;
}
u32 read_titles = 0;
u64 * titleIDs = malloc(titles_amount * sizeof(u64));
ret = AM_GetTitleList(&read_titles, media, titles_amount, titleIDs);
if (R_FAILED(ret)) {
free(titleIDs);
printf("Error in:\nAM_GetTitleList\n");
return ret;
}
for (u32 i = 0; i < read_titles; i++) {
if (titleIDs[i] == titleid) {
ret = AM_DeleteAppTitle(media, titleid);
break;
}
}
free(titleIDs);
if (R_FAILED(ret)) {
printf("Error in:\nAM_DeleteAppTitle\n");
return ret;
}
return 0;
}
FS_MediaType getTitleDestination(u64 titleId) {
u16 platform = (u16) ((titleId >> 48) & 0xFFFF);
u16 category = (u16) ((titleId >> 32) & 0xFFFF);
u8 variation = (u8) (titleId & 0xFF);
// DSiWare 3DS DSiWare, System, DLP Application System Title
return platform == 0x0003 || (platform == 0x0004 && ((category & 0x8011) != 0 || (category == 0x0000 && variation == 0x02))) ? MEDIATYPE_NAND : MEDIATYPE_SD;
}
Result installCia(const char * ciaPath)
{
u64 size = 0;
u32 bytes;
Handle ciaHandle;
Handle fileHandle;
AM_TitleEntry info;
Result ret = 0;
FS_MediaType media = MEDIATYPE_SD;
ret = openFile(&fileHandle, ciaPath, false);
if (R_FAILED(ret)) {
printf("Error in:\nopenFile\n");
return ret;
}
ret = AM_GetCiaFileInfo(media, &info, fileHandle);
if (R_FAILED(ret)) {
printf("Error in:\nAM_GetCiaFileInfo\n");
return ret;
}
media = getTitleDestination(info.titleID);
if (!updatingSelf) {
ret = deletePrevious(info.titleID, media);
if (R_FAILED(ret))
return ret;
}
ret = FSFILE_GetSize(fileHandle, &size);
if (R_FAILED(ret)) {
printf("Error in:\nFSFILE_GetSize\n");
return ret;
}
ret = AM_StartCiaInstall(media, &ciaHandle);
if (R_FAILED(ret)) {
printf("Error in:\nAM_StartCiaInstall\n");
return ret;
}
u32 toRead = 0x1000;
u8 * cia_buffer = malloc(toRead);
for (u64 startSize = size; size != 0; size -= toRead) {
if (size < toRead) toRead = size;
FSFILE_Read(fileHandle, &bytes, startSize-size, cia_buffer, toRead);
FSFILE_Write(ciaHandle, &bytes, startSize-size, cia_buffer, toRead, 0);
}
free(cia_buffer);
ret = AM_FinishCiaInstall(ciaHandle);
if (R_FAILED(ret)) {
printf("Error in:\nAM_FinishCiaInstall\n");
return ret;
}
ret = FSFILE_Close(fileHandle);
if (R_FAILED(ret)) {
printf("Error in:\nFSFILE_Close\n");
return ret;
}
if (updatingSelf) {
if (R_FAILED(ret = CIA_LaunchTitle(info.titleID, MEDIATYPE_SD)))
return ret;
}
return 0;
}
+107
View File
@@ -0,0 +1,107 @@
/*
* This file is part of Universal-Updater
* Copyright (C) 2019 VoltZ, Epicpkmn11, Flame, RocketRobz, TotallyNotGuy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "utils/extract.hpp"
#include <archive.h>
#include <archive_entry.h>
int filesExtracted = 0;
std::string extractingFile = "";
Result extractArchive(std::string archivePath, std::string wantedFile, std::string outputPath)
{
int r;
struct archive_entry *entry;
struct archive *a = archive_read_new();
archive_read_support_format_all(a);
archive_read_support_format_raw(a);
r = archive_read_open_filename(a, archivePath.c_str(), 0x4000);
if (r != ARCHIVE_OK) {
return EXTRACT_ERROR_ARCHIVE;
}
Result ret = EXTRACT_ERROR_FIND;
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
std::string entryName(archive_entry_pathname(entry));
if (wantedFile == "/") wantedFile = "";
if (matchPattern(wantedFile, entryName.substr(0,wantedFile.length())) || wantedFile == "") {
extractingFile = (entryName.length() > wantedFile.length() ? entryName.substr(wantedFile.length()) : wantedFile);
ret = EXTRACT_ERROR_NONE;
Handle fileHandle;
std::string outputPathFinal = outputPath;
if (entryName.length() > wantedFile.length())
outputPathFinal += entryName.substr(wantedFile.length());
if (outputPathFinal.substr(outputPathFinal.length()-1) == "/") continue;
Result res = openFile(&fileHandle, outputPathFinal.c_str(), true);
if (R_FAILED(res)) {
ret = EXTRACT_ERROR_OPENFILE;
break;
}
u64 fileSize = archive_entry_size(entry);
u32 toRead = 0x4000;
u8 * buf = (u8 *)malloc(toRead);
if (buf == NULL) {
ret = EXTRACT_ERROR_ALLOC;
FSFILE_Close(fileHandle);
break;
}
u32 bytesWritten = 0;
u64 offset = 0;
do {
if (toRead > fileSize) toRead = fileSize;
ssize_t size = archive_read_data(a, buf, toRead);
if (size < 0) {
ret = EXTRACT_ERROR_READFILE;
break;
}
res = FSFILE_Write(fileHandle, &bytesWritten, offset, buf, toRead, 0);
if (R_FAILED(res)) {
ret = EXTRACT_ERROR_WRITEFILE;
break;
}
offset += bytesWritten;
fileSize -= bytesWritten;
} while(fileSize);
FSFILE_Close(fileHandle);
free(buf);
filesExtracted++;
}
}
archive_read_free(a);
return ret;
}
+95
View File
@@ -0,0 +1,95 @@
#include "utils/files.h"
FS_Path getPathInfo(const char * path, FS_ArchiveID * archive)
{
*archive = ARCHIVE_SDMC;
FS_Path filePath = {0};
unsigned int prefixlen = 0;
if (!strncmp(path, "ctrnand:/", 9)) {
*archive = ARCHIVE_NAND_CTR_FS;
prefixlen = 8;
}
else if (!strncmp(path, "twlp:/", 6)) {
*archive = ARCHIVE_TWL_PHOTO;
prefixlen = 5;
}
else if (!strncmp(path, "twln:/", 6)) {
*archive = ARCHIVE_NAND_TWL_FS;
prefixlen = 5;
}
else if (!strncmp(path, "sdmc:/", 6)) {
prefixlen = 5;
}
else if (*path != '/') {
//if the path is local (doesnt start with a slash), it needs to be appended to the working dir to be valid
char * actualPath = NULL;
asprintf(&actualPath, "%s%s", WORKING_DIR, path);
filePath = fsMakePath(PATH_ASCII, actualPath);
free(actualPath);
}
//if the filePath wasnt set above, set it
if (filePath.size == 0)
filePath = fsMakePath(PATH_ASCII, path+prefixlen);
return filePath;
}
Result makeDirs(FS_ArchiveID archiveID, char * path)
{
Result ret = 0;
FS_Archive archive;
ret = FSUSER_OpenArchive(&archive, archiveID, fsMakePath(PATH_EMPTY, ""));
for (char * slashpos = strchr(path+1, '/'); slashpos != NULL; slashpos = strchr(slashpos+1, '/')) {
char bak = *(slashpos);
*(slashpos) = '\0';
FS_Path dirpath = fsMakePath(PATH_ASCII, path);
Handle dirHandle;
ret = FSUSER_OpenDirectory(&dirHandle, archive, dirpath);
if (R_SUCCEEDED(ret))
FSDIR_Close(dirHandle);
else
ret = FSUSER_CreateDirectory(archive, dirpath, FS_ATTRIBUTE_DIRECTORY);
*(slashpos) = bak;
}
FSUSER_CloseArchive(archive);
free(path);
return ret;
}
Result openFile(Handle* fileHandle, const char * path, bool write)
{
FS_ArchiveID archive;
FS_Path filePath = getPathInfo(path, &archive);
u32 flags = (write ? (FS_OPEN_CREATE | FS_OPEN_WRITE) : FS_OPEN_READ);
Result ret = 0;
ret = makeDirs(archive, strdup(path));
ret = FSUSER_OpenFileDirectly(fileHandle, archive, fsMakePath(PATH_EMPTY, ""), filePath, flags, 0);
if (write)
ret = FSFILE_SetSize(*fileHandle, 0); //truncate the file to remove previous contents before writing
return ret;
}
Result deleteFile(const char * path)
{
FS_ArchiveID archiveID;
FS_Path filePath = getPathInfo(path, &archiveID);
FS_Archive archive;
Result ret = FSUSER_OpenArchive(&archive, archiveID, fsMakePath(PATH_EMPTY, ""));
if (R_FAILED(ret)) return ret;
ret = FSUSER_DeleteFile(archive, filePath);
FSUSER_CloseArchive(archive);
return ret;
}
+374
View File
@@ -0,0 +1,374 @@
#include "utils/inifile.h"
#include <cstdio>
#include <cstdlib>
static bool freadLine(FILE* f,std::string& str)
{
str.clear();
__read:
char p=0;
size_t readed=fread(&p,1,1,f);
if(0==readed)
{
str="";
return false;
}
if('\n'==p||'\r'==p)
{
str="";
return true;
}
while(p!='\n'&&p!='\r'&&readed)
{
str+=p;
readed=fread(&p,1,1,f);
}
if(str.empty()||""==str)
{
goto __read;
}
return true;
}
static void trimString(std::string& str)
{
size_t first=str.find_first_not_of(" \t"),last;
if(first==str.npos)
{
str="";
}
else
{
last=str.find_last_not_of(" \t");
if(first>0||(last+1)<str.length()) str=str.substr(first,last-first+1);
}
}
CIniFile::CIniFile()
{
m_bLastResult=false;
m_bModified=false;
m_bReadOnly=false;
}
CIniFile::CIniFile(const std::string& filename)
{
m_sFileName=filename;
m_bLastResult=false;
m_bModified=false;
m_bReadOnly=false;
LoadIniFile(m_sFileName);
}
CIniFile::~CIniFile()
{
if(m_FileContainer.size()>0)
{
m_FileContainer.clear();
}
}
void CIniFile::SetString(const std::string& Section,const std::string& Item,const std::string& Value)
{
if(GetFileString(Section,Item)!=Value)
{
SetFileString(Section,Item,Value);
m_bModified=true;
}
}
void CIniFile::SetInt(const std::string& Section,const std::string& Item,int Value)
{
char buf[16];
snprintf(buf, sizeof(buf), "%d", Value);
std::string strtemp(buf);
if(GetFileString(Section,Item)!=strtemp)
{
SetFileString(Section,Item,strtemp);
m_bModified=true;
}
}
std::string CIniFile::GetString(const std::string& Section,const std::string& Item)
{
return GetFileString(Section,Item);
}
std::string CIniFile::GetString(const std::string& Section,const std::string& Item,const std::string& DefaultValue)
{
std::string temp=GetString(Section,Item);
if(!m_bLastResult)
{
SetString(Section,Item,DefaultValue);
temp=DefaultValue;
}
return temp;
}
void CIniFile::GetStringVector(const std::string& Section,const std::string& Item,std::vector< std::string >& strings,char delimiter)
{
std::string strValue=GetFileString(Section,Item);
strings.clear();
size_t pos;
while((pos=strValue.find(delimiter),strValue.npos!=pos))
{
const std::string string=strValue.substr(0,pos);
if(string.length())
{
strings.push_back(string);
}
strValue=strValue.substr(pos+1,strValue.npos);
}
if(strValue.length())
{
strings.push_back(strValue);
}
}
void CIniFile::SetStringVector(const std::string& Section,const std::string& Item,std::vector<std::string>& strings,char delimiter)
{
std::string strValue;
for(size_t ii=0;ii<strings.size();++ii)
{
if(ii) strValue+=delimiter;
strValue+=strings[ii];
}
SetString(Section,Item,strValue);
}
int CIniFile::GetInt(const std::string& Section,const std::string& Item)
{
std::string value=GetFileString(Section,Item);
if(value.size()>2&&'0'==value[0]&&('x'==value[1]||'X'==value[1]))
return strtol(value.c_str(),NULL,16);
else
return strtol(value.c_str(),NULL,10);
}
int CIniFile::GetInt(const std::string& Section,const std::string& Item,int DefaultValue)
{
int temp;
temp=GetInt(Section,Item);
if(!m_bLastResult)
{
SetInt(Section,Item,DefaultValue);
temp=DefaultValue;
}
return temp;
}
bool CIniFile::LoadIniFile(const std::string& FileName)
{
//dbg_printf("load %s\n",FileName.c_str());
if(FileName!="") m_sFileName=FileName;
FILE* f=fopen(FileName.c_str(),"rb");
if(NULL==f) return false;
//check for utf8 bom.
char bom[3];
if(fread(bom,3,1,f)==1&&bom[0]==0xef&&bom[1]==0xbb&&bom[2]==0xbf) ;
else fseek(f,0,SEEK_SET);
std::string strline("");
m_FileContainer.clear();
while(freadLine(f,strline))
{
trimString(strline);
if(strline!=""&&';'!=strline[0]&&'/'!=strline[0]&&'!'!=strline[0]) m_FileContainer.push_back(strline);
}
fclose(f);
m_bLastResult=false;
m_bModified=false;
return true;
}
bool CIniFile::SaveIniFileModified(const std::string& FileName)
{
if(m_bModified==true)
{
return SaveIniFile(FileName);
}
return true;
}
bool CIniFile::SaveIniFile(const std::string& FileName)
{
if(FileName!="")
m_sFileName=FileName;
FILE* f=fopen(m_sFileName.c_str(),"wb");
if(NULL==f)
{
return false;
}
for(size_t ii=0;ii<m_FileContainer.size();ii++)
{
std::string& strline=m_FileContainer[ii];
size_t notSpace=strline.find_first_not_of(' ');
strline=strline.substr(notSpace);
if(strline.find('[')==0&&ii>0)
{
if(!m_FileContainer[ii-1].empty()&&m_FileContainer[ii-1]!="")
fwrite("\r\n",1,2,f);
}
if(!strline.empty()&&strline!="")
{
fwrite(strline.c_str(),1,strline.length(),f);
fwrite("\r\n",1,2,f);
}
}
fclose(f);
m_bModified=false;
return true;
}
std::string CIniFile::GetFileString(const std::string& Section,const std::string& Item)
{
std::string strline;
std::string strSection;
std::string strItem;
std::string strValue;
size_t ii=0;
size_t iFileLines=m_FileContainer.size();
if(m_bReadOnly)
{
cSectionCache::iterator it=m_Cache.find(Section);
if((it!=m_Cache.end())) ii=it->second;
}
m_bLastResult=false;
if(iFileLines>=0)
{
while(ii<iFileLines)
{
strline=m_FileContainer[ii++];
size_t rBracketPos=0;
if('['==strline[0]) rBracketPos=strline.find(']');
if(rBracketPos>0&&rBracketPos!=std::string::npos)
{
strSection=strline.substr(1,rBracketPos-1);
if(m_bReadOnly) m_Cache.insert(std::make_pair(strSection,ii-1));
if(strSection==Section)
{
while(ii<iFileLines)
{
strline=m_FileContainer[ii++];
size_t equalsignPos=strline.find('=');
if(equalsignPos!=strline.npos)
{
size_t last=equalsignPos?strline.find_last_not_of(" \t",equalsignPos-1):strline.npos;
if(last==strline.npos) strItem="";
else strItem=strline.substr(0,last+1);
if(strItem==Item)
{
size_t first=strline.find_first_not_of(" \t",equalsignPos+1);
if(first==strline.npos) strValue="";
else strValue=strline.substr(first);
m_bLastResult=true;
return strValue;
}
}
else if('['==strline[0])
{
break;
}
}
break;
}
}
}
}
return std::string("");
}
void CIniFile::SetFileString(const std::string& Section,const std::string& Item,const std::string& Value)
{
std::string strline;
std::string strSection;
std::string strItem;
if(m_bReadOnly) return;
size_t ii=0;
size_t iFileLines=m_FileContainer.size();
while(ii<iFileLines)
{
strline=m_FileContainer[ii++];
size_t rBracketPos=0;
if('['==strline[0]) rBracketPos=strline.find(']');
if(rBracketPos>0&&rBracketPos!=std::string::npos)
{
strSection=strline.substr(1,rBracketPos-1);
if(strSection==Section)
{
while(ii<iFileLines)
{
strline=m_FileContainer[ii++];
size_t equalsignPos=strline.find('=');
if(equalsignPos!=strline.npos)
{
size_t last=equalsignPos?strline.find_last_not_of(" \t",equalsignPos-1):strline.npos;
if(last==strline.npos) strItem="";
else strItem=strline.substr(0,last+1);
if(Item==strItem)
{
ReplaceLine(ii-1,Item+" = "+Value);
return;
}
}
else if('['==strline[0])
{
InsertLine(ii-1,Item+" = "+Value);
return;
}
}
InsertLine(ii,Item+" = "+Value);
return;
}
}
}
InsertLine(ii,"["+Section+"]");
InsertLine(ii+1,Item+" = "+Value);
return;
}
bool CIniFile::InsertLine(size_t line,const std::string& str)
{
m_FileContainer.insert(m_FileContainer.begin()+line,str);
return true;
}
bool CIniFile::ReplaceLine(size_t line,const std::string& str)
{
m_FileContainer[line]=str;
return true;
}
+18
View File
@@ -0,0 +1,18 @@
#include "utils/stringutils.hpp"
bool matchPattern(std::string pattern, std::string tested)
{
std::regex patternRegex(pattern);
return regex_match(tested, patternRegex);
}
std::string StringUtils::format(const std::string& fmt_str, ...)
{
va_list ap;
char* fp = NULL;
va_start(ap, fmt_str);
vasprintf(&fp, fmt_str.c_str(), ap);
va_end(ap);
std::unique_ptr<char, decltype(free)*> formatted(fp, free);
return std::string(formatted.get());
}
+23
View File
@@ -0,0 +1,23 @@
#include "utils/thread.hpp"
#include <3ds.h>
#include <stdio.h>
#include <string.h>
static std::vector<Thread> threads;
void Threads::create(ThreadFunc entrypoint)
{
s32 prio = 0;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
Thread thread = threadCreate((ThreadFunc)entrypoint, NULL, 64 * 1024, prio - 1, -2, false);
threads.push_back(thread);
}
void Threads::destroy(void)
{
for (u32 i = 0; i < threads.size(); i++) {
threadJoin(threads.at(i), U64_MAX);
threadFree(threads.at(i));
}
}