From 6d7781b20992c2192bae62c68a24201510ff2a95 Mon Sep 17 00:00:00 2001 From: tobid7 Date: Wed, 22 May 2024 22:16:40 +0200 Subject: [PATCH] Update: - Add Support for Downloading Data/Files, do Api Requests - Refactor Tasks System - Now uses std::thread - The Net Functions Will Break IDBN server for now --- CHANGELOG.md | 2 + include/rd7.hpp | 1 + include/renderd7/Net.hpp | 60 +++++++ include/renderd7/Tasks.hpp | 9 +- include/renderd7/internal_db.hpp | 7 +- source/Net.cpp | 294 +++++++++++++++++++++++++++++++ source/Tasks.cpp | 29 ++- source/internal_db.cpp | 49 ++++-- source/renderd7.cpp | 3 +- 9 files changed, 415 insertions(+), 39 deletions(-) create mode 100644 include/renderd7/Net.hpp create mode 100644 source/Net.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d145ec..0656ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ - Fix Crash in FilsSystem - Implement basic Theme System - Remove 0.9.4 Security +- Tasks now based on std functional/thread +- Add Network Support (Download Files, APi Requests) ## 0.9.4 - Implement new Security System To prevent from crashes - Implement Functiontrace for better Timing Tests diff --git a/include/rd7.hpp b/include/rd7.hpp index 82f7936..312891a 100644 --- a/include/rd7.hpp +++ b/include/rd7.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/include/renderd7/Net.hpp b/include/renderd7/Net.hpp new file mode 100644 index 0000000..3f45ff0 --- /dev/null +++ b/include/renderd7/Net.hpp @@ -0,0 +1,60 @@ +/** + * This file is part of RenderD7 + * Copyright (C) 2021-2024 NPI-D7, tobid7 + * + * 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 . + */ + +#pragma once + +#include +#include + +namespace RenderD7 { +namespace Net { +// Define List of Errors +enum Error_ { + Error_None, // Function Executed Successfully + Error_Memory, // Memory Allocation Error + Error_Write, // Unable to Write File + Error_StatusCode, // Error with Status Code + Error_Git, // Git Error + Error_CtrStatus, // 3ds Result Code + Error_Curl, // Curl Error + Error_Busy, // Another Download Taskl is already running + Error_Invalid, // Invalid Json struct + Error_NoWifi, // Console not connected to wifi +}; +// Set an typedefine for Error code +using Error = unsigned long long; +// Extract Error_ from Error code +inline Error_ ErrorCode(Error err) { + return static_cast(static_cast(err & 0xffffffff)); +} +// Extract Http Status code, Curl Error Code or Ctr Result Code +inline int StatusCode(Error err) { + Error_ c = ErrorCode(err); + if (c != Error_StatusCode && c != Error_CtrStatus && c != Error_Curl) + return 0; + return static_cast(err >> 32); +} +Error Download(const std::string& url, std::string& data); +Error Download2File(const std::string& url, const std::string& path); +Error GitDownloadRelease(const std::string& url, const std::string& asset_name, + const std::string& path, bool prerelease = false); +Error JsonApiRequest(const std::string& api_url, nlohmann::json& res); +unsigned long long GetProgressCurrent(); +unsigned long long GetProgressTotal(); +} // namespace Net +} // namespace RenderD7 \ No newline at end of file diff --git a/include/renderd7/Tasks.hpp b/include/renderd7/Tasks.hpp index 14fbdf9..c9a2aa0 100644 --- a/include/renderd7/Tasks.hpp +++ b/include/renderd7/Tasks.hpp @@ -17,14 +17,15 @@ */ #pragma once -#include <3ds.h> +#include namespace RenderD7 { namespace Tasks { /// @brief Push A Task -/// @param entrypoint Function of Your Task -void create(ThreadFunc entrypoint); +/// @param fun Function of Your Task +/// @return index +int Create(std::function fun); /// @brief Destroy all Tasks -void destroy(void); +void DestroyAll(); } // namespace Tasks } // namespace RenderD7 diff --git a/include/renderd7/internal_db.hpp b/include/renderd7/internal_db.hpp index d896229..bae02d2 100644 --- a/include/renderd7/internal_db.hpp +++ b/include/renderd7/internal_db.hpp @@ -17,6 +17,7 @@ */ #pragma once +#include #include #include #include @@ -72,4 +73,8 @@ extern bool rd7i_fade_exit; extern bool rd7i_fade_scene_wait; extern bool rd7i_idb_running; extern bool rd7i_graphics_on; -extern bool rd7i_amdt; \ No newline at end of file +extern bool rd7i_amdt; +extern void* rd7i_soc_buf; + +RenderD7::Net::Error rd7i_soc_init(); +void rd7i_soc_deinit(); \ No newline at end of file diff --git a/source/Net.cpp b/source/Net.cpp new file mode 100644 index 0000000..6b0aca2 --- /dev/null +++ b/source/Net.cpp @@ -0,0 +1,294 @@ +/** + * This file is part of RenderD7 + * Copyright (C) 2021-2024 NPI-D7, tobid7 + * + * 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 . + */ + +// TODO: Make Download2File faster on large files + +#include <3ds.h> +#include +#include +#include + +#include +#include +#include +#include +#include + +static RenderD7::Net::Error rd7i_check_wifi() { + // if (rd7i_is_citra) return 0; + int s = osGetWifiStrength(); + return (s == 0); +} + +static size_t rd7i_handle_data(char* ptr, size_t size, size_t nmemb, + void* userdata) { + size_t ms = size * nmemb; + ((std::string*)userdata)->append(ptr, ms); + return ms; +} + +static size_t rd7i_handle_file(char* ptr, size_t size, size_t nmemb, + void* out) { + size_t ms = size * nmemb; + ((std::ofstream*)out)->write(reinterpret_cast(ptr), ms); + return ms; +} + +struct rd7i_net_dl { + unsigned long long current = 0; + unsigned long long total = 1; + void Reset() { + current = 0; + total = 1; + } +}; + +static rd7i_net_dl rd7i_net_dl_spec; + +static int rd7i_handle_curl_progress(CURL* hnd, curl_off_t dltotal, + curl_off_t dlnow, curl_off_t ultotal, + curl_off_t ulnow) { + rd7i_net_dl_spec.total = dltotal; + rd7i_net_dl_spec.current = dlnow; + return 0; +} + +static void rd7i_setup_curl_context(CURL* hnd, const std::string& url, + void* userptr, bool mem) { + std::string user_agent = + rd7i_app_name + "/RenderD7 (Version: " + std::string(RENDERD7VSTRING) + + ")"; + + if (!mem) { + curl_easy_setopt(hnd, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(hnd, CURLOPT_ACCEPT_ENCODING, "gzip"); + curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, rd7i_handle_curl_progress); + } + + curl_easy_setopt(hnd, CURLOPT_URL, url.c_str()); + curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(hnd, CURLOPT_USERAGENT, user_agent.c_str()); + curl_easy_setopt(hnd, CURLOPT_WRITEDATA, userptr); + 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, + mem ? rd7i_handle_data : rd7i_handle_file); + curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(hnd, CURLOPT_STDERR, stdout); +} + +static bool rd7i_curl_is_busy = false; +static bool rd7i_apir_is_busy = false; + +namespace RenderD7 { +namespace Net { +Error Download(const std::string& url, std::string& data) { + if (rd7i_curl_is_busy) return Error_Busy; + Error ret = rd7i_check_wifi(); + if (ret != 0) { + if (ret == 1) { + return Error_NoWifi; + } else { + return ret; + } + } + rd7i_curl_is_busy = true; + rd7i_net_dl_spec.Reset(); + ret = rd7i_soc_init(); + if (ret) { + rd7i_curl_is_busy = false; + return ret; + } + + auto hnd = curl_easy_init(); + rd7i_setup_curl_context(hnd, url, &data, true); + + CURLcode curl_res = curl_easy_perform(hnd); + curl_easy_cleanup(hnd); + + if (curl_res != CURLE_OK) { + data.clear(); + rd7i_soc_deinit(); + rd7i_curl_is_busy = false; + return ((static_cast(curl_res) << 32) | + static_cast(Error_Curl)); + } + + rd7i_soc_deinit(); + rd7i_curl_is_busy = false; + return 0; +} + +Error Download2File(const std::string& url, const std::string& path) { + if (rd7i_curl_is_busy) return Error_Busy; + Error ret = rd7i_check_wifi(); + if (ret != 0) { + if (ret == 1) { + return Error_NoWifi; + } else { + return ret; + } + } + rd7i_curl_is_busy = true; + rd7i_net_dl_spec.Reset(); + ret = rd7i_soc_init(); + if (ret) { + rd7i_curl_is_busy = false; + return ret; + } + + // std::filesystem::create_directories( + // std::filesystem::path(path).remove_filename()); + std::ofstream file(path, std::ios::binary); + + if (!file.is_open()) { + rd7i_curl_is_busy = false; + return Error_Write; + } + + auto hnd = curl_easy_init(); + rd7i_setup_curl_context(hnd, url, &file, false); + + CURLcode curl_res = curl_easy_perform(hnd); + curl_easy_cleanup(hnd); + + file.close(); + if (curl_res != CURLE_OK) { + rd7i_soc_deinit(); + if (RenderD7::FS::FileExist(path)) { + std::filesystem::remove(path); + } + rd7i_curl_is_busy = false; + return ((static_cast(curl_res) << 32) | + static_cast(Error_Curl)); + } + + rd7i_soc_deinit(); + rd7i_curl_is_busy = false; + return 0; +} + +Error GitDownloadRelease(const std::string& url, const std::string& asset_name, + const std::string& path, bool prerelease) { + if (rd7i_apir_is_busy) return Error_Busy; + Error ret = rd7i_check_wifi(); + if (ret != 0) { + if (ret == 1) { + return Error_NoWifi; + } else { + return ret; + } + } + rd7i_apir_is_busy = true; + std::regex parse("github\\.com\\/(.+)\\/(.+)"); + std::smatch res; + std::regex_search(url, res, parse); + + std::string user = res[1].str(); + std::string repo = res[2].str(); + + std::stringstream req; + req << "https://api.github.com/repos/" << user << "/" << repo + << (prerelease ? "/releases" : "/releases/latest"); + + std::string buf; + ret = Download(req.str(), buf); + if (ret) { + rd7i_apir_is_busy = false; + return ret; + } + + int pret = 0; + std::string freq; + if (nlohmann::json::accept(buf)) { + nlohmann::json api = nlohmann::json::parse(buf); + if (!api.size()) pret = -1; + if (pret != -1) { + if (prerelease) api = api[0]; + if (api["assets"].is_array()) { + for (const auto& asset : api["assets"]) { + if (asset.is_object() && asset["name"].is_string() && + asset["browser_download_url"].is_string()) { + if (std::regex_match(std::string(asset["name"]), + std::regex(asset_name))) { + freq = asset["browser_download_url"]; + break; + } + } + } + } + } + } else { + pret = -1; + } + + if (pret != 0 || freq.empty()) { + rd7i_apir_is_busy = false; + return Error_Git; + } + + ret = Download2File(freq, path); + rd7i_apir_is_busy = false; + return ret; +} + +Error JsonApiRequest(const std::string& api_url, nlohmann::json& res) { + if (rd7i_apir_is_busy) return Error_Busy; + Error ret = rd7i_check_wifi(); + if (ret != 0) { + if (ret == 1) { + return Error_NoWifi; + } else { + return ret; + } + } + rd7i_apir_is_busy = true; + + std::string buf; + ret = Download(api_url, buf); + if (ret) { + rd7i_apir_is_busy = false; + return ret; + } + + if (nlohmann::json::accept(buf)) { + res = nlohmann::json::parse(buf); + if (!res.size()) { + rd7i_apir_is_busy = false; + return Error_Invalid; + } + } else { + rd7i_apir_is_busy = false; + return Error_Invalid; + } + + rd7i_apir_is_busy = false; + return 0; +} + +unsigned long long GetProgressCurrent() { return rd7i_net_dl_spec.current; } +unsigned long long GetProgressTotal() { + // As curl sets total to 0 we need + // to return a 1 as devide by zeroi will crash + if (rd7i_net_dl_spec.total <= 0) return 1; + return rd7i_net_dl_spec.total; +} +} // namespace Net +} // namespace RenderD7 \ No newline at end of file diff --git a/source/Tasks.cpp b/source/Tasks.cpp index 5a9c2c3..fe5f512 100644 --- a/source/Tasks.cpp +++ b/source/Tasks.cpp @@ -16,26 +16,25 @@ * along with this program. If not, see . */ -#include <3ds.h> -#include -#include - +#include #include +#include #include -static std::vector threads; +static std::vector> threads; -void RenderD7::Tasks::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); +int RenderD7::Tasks::Create(std::function fun) { + auto thrd = std::make_shared(fun); + threads.push_back(thrd); + return threads.size(); } -void RenderD7::Tasks::destroy(void) { - for (u32 i = 0; i < threads.size(); i++) { - threadJoin(threads.at(i), U64_MAX); - threadFree(threads.at(i)); +void RenderD7::Tasks::DestroyAll() { + for (auto& it : threads) { + if (it && it->joinable()) { + it->join(); + } } + // Delete Pointers + threads.clear(); } diff --git a/source/internal_db.cpp b/source/internal_db.cpp index ddb6408..f0fd135 100644 --- a/source/internal_db.cpp +++ b/source/internal_db.cpp @@ -26,6 +26,7 @@ #include #include +#include #include /// Base /// @@ -71,6 +72,7 @@ bool rd7i_idb_running = false; bool rd7i_graphics_on = false; float rd7_draw2_tsm = 1.2f; bool rd7i_amdt = false; +void *rd7i_soc_buf = nullptr; /// Global /// // Outdated HidApi (HidV2Patched) @@ -94,6 +96,31 @@ C3D_RenderTarget *rd7_top; C3D_RenderTarget *rd7_top_right; C3D_RenderTarget *rd7_bottom; +RenderD7::Net::Error rd7i_soc_init() { + if (rd7i_soc_buf != nullptr) { + return 0; + } + rd7i_soc_buf = memalign(0x1000, 0x100000); + if (!rd7i_soc_buf) { + return RenderD7::Net::Error_Memory; + } + Result ret = socInit((u32 *)rd7i_soc_buf, 0x100000); + if (R_FAILED(ret)) { + free(rd7i_soc_buf); + return ((static_cast(ret) << 32) | + static_cast(RenderD7::Net::Error_CtrStatus)); + } + return 0; +} + +void rd7i_soc_deinit() { + if (rd7i_soc_buf) { + socExit(); + free(rd7i_soc_buf); + } + rd7i_soc_buf = nullptr; +} + class Logger { public: Logger() = default; @@ -157,10 +184,10 @@ class tcp_server { #define stupid(x) &x, sizeof(x) #define rd7i_reacttion(x) \ - ({ \ + { \ int code = x; \ server.snd(stupid(code)); \ - }) + } struct pak32 { pak32() {} @@ -206,30 +233,16 @@ struct pak32 { unsigned int tbs; }; -#define SOC_ALIGN 0x1000 -#define SOC_BUFFERSIZE 0x100000 - -static u32 *SOC_buffer = NULL; static bool rd7i_idb_fp = false; void KillIdbServer() { rd7i_idb_fp = true; rd7i_idb_server.join(100); - socExit(); + rd7i_soc_deinit(); } void ServerThread(RenderD7::Parameter param) { - Result ret; - SOC_buffer = (u32 *)memalign(SOC_ALIGN, SOC_BUFFERSIZE); - - if (SOC_buffer == NULL) { - return; - } - - // Now intialise soc:u service - if ((ret = socInit(SOC_buffer, SOC_BUFFERSIZE)) != 0) { - return; - } + if (rd7i_soc_init()) return; rd7i_idb_running = true; rd7i_idb_fp = false; atexit(KillIdbServer); diff --git a/source/renderd7.cpp b/source/renderd7.cpp index 2d89ff9..1d8c0da 100644 --- a/source/renderd7.cpp +++ b/source/renderd7.cpp @@ -451,7 +451,8 @@ int RenderD7::GetRandomInt(int b, int e) { } bool RenderD7::FS::FileExist(const std::string &path) { - return std::filesystem::exists(path); + return std::filesystem::exists(path) && + std::filesystem::is_regular_file(path); } int RenderD7::GetFps() { return (int)rd7i_framerate; }