Initial Commit
This commit is contained in:
49
source/JC_page.cpp
Normal file
49
source/JC_page.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "JC_page.hpp"
|
||||
|
||||
#include "color_swapper.hpp"
|
||||
#include "confirm_page.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "worker_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
JCPage::JCPage() : AppletFrame(true, true)
|
||||
{
|
||||
this->setTitle("menus/joy_con/title"_i18n);
|
||||
list = new brls::List();
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, fmt::format("menus/joy_con/description"_i18n, JC_COLOR_PATH, COLOR_PICKER_URL), true);
|
||||
list->addView(label);
|
||||
|
||||
backup = new brls::ListItem("menus/joy_con/backup"_i18n);
|
||||
backup->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/joy_con/label"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/joy_con/backing_up"_i18n,
|
||||
[]() { JC::backupJCColor(JC_COLOR_PATH); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(backup);
|
||||
|
||||
list->addView(new brls::ListItemGroupSpacing(true));
|
||||
|
||||
auto profiles = JC::getProfiles(JC_COLOR_PATH);
|
||||
for (const auto& profile : profiles) {
|
||||
std::vector<int> value = profile.second;
|
||||
listItem = new brls::ListItem(profile.first);
|
||||
listItem->getClickEvent()->subscribe([value](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/joy_con/label"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/joy_con/changing"_i18n,
|
||||
[value]() { JC::changeJCColor(value); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/joy_con/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(listItem);
|
||||
}
|
||||
this->setContentView(list);
|
||||
}
|
||||
50
source/PC_page.cpp
Normal file
50
source/PC_page.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "PC_page.hpp"
|
||||
|
||||
#include "color_swapper.hpp"
|
||||
#include "confirm_page.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "worker_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
PCPage::PCPage() : AppletFrame(true, true)
|
||||
{
|
||||
this->setTitle("menus/pro_con/title"_i18n);
|
||||
list = new brls::List();
|
||||
std::string labelText = fmt::format("{}\n{}", "menus/pro_con/desc"_i18n, "menus/pro_con/warning"_i18n);
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, labelText, true);
|
||||
list->addView(label);
|
||||
|
||||
backup = new brls::ListItem("menus/joy_con/backup"_i18n);
|
||||
backup->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/pro_con/label"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/pro_con/backing_up"_i18n,
|
||||
[]() { PC::backupPCColor(PC_COLOR_PATH); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(backup);
|
||||
|
||||
list->addView(new brls::ListItemGroupSpacing(true));
|
||||
|
||||
auto profiles = PC::getProfiles(PC_COLOR_PATH);
|
||||
for (const auto& profile : profiles) {
|
||||
std::vector<int> value = profile.second;
|
||||
listItem = new brls::ListItem(profile.first);
|
||||
listItem->getClickEvent()->subscribe([value](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/pro_con/label"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/pro_con/changing"_i18n,
|
||||
[value]() { PC::changePCColor(value); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/pro_con/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(listItem);
|
||||
}
|
||||
this->setContentView(list);
|
||||
}
|
||||
31
source/about_tab.cpp
Normal file
31
source/about_tab.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "about_tab.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
AboutTab::AboutTab()
|
||||
{
|
||||
// Subtitle
|
||||
brls::Label* subTitle = new brls::Label(
|
||||
brls::LabelStyle::REGULAR,
|
||||
"menus/about/title"_i18n,
|
||||
true);
|
||||
subTitle->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->addView(subTitle);
|
||||
|
||||
// Copyright
|
||||
brls::Label* copyright = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/about/copyright"_i18n + "\n© 2020-2022 HamletDuFromage",
|
||||
true);
|
||||
copyright->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->addView(copyright);
|
||||
|
||||
// Links
|
||||
this->addView(new brls::Header("menus/about/disclaimers_title"_i18n));
|
||||
brls::Label* links = new brls::Label(
|
||||
brls::LabelStyle::SMALL,
|
||||
"menus/about/disclaimers"_i18n + "\n" + "menus/about/donate"_i18n,
|
||||
true);
|
||||
this->addView(links);
|
||||
}
|
||||
50
source/ams_bpc.c
Normal file
50
source/ams_bpc.c
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include "ams_bpc.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <switch.h>
|
||||
|
||||
#include "service_guard.h"
|
||||
|
||||
static Service g_amsBpcSrv;
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD(amsBpc);
|
||||
|
||||
Result _amsBpcInitialize(void)
|
||||
{
|
||||
Handle h;
|
||||
Result rc = svcConnectToNamedPort(&h, "bpc:ams"); /* TODO: ams:bpc */
|
||||
if (R_SUCCEEDED(rc)) serviceCreate(&g_amsBpcSrv, h);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void _amsBpcCleanup(void)
|
||||
{
|
||||
serviceClose(&g_amsBpcSrv);
|
||||
}
|
||||
|
||||
Service* amsBpcGetServiceSession(void)
|
||||
{
|
||||
return &g_amsBpcSrv;
|
||||
}
|
||||
|
||||
Result amsBpcSetRebootPayload(const void* src, size_t src_size)
|
||||
{
|
||||
return serviceDispatch(&g_amsBpcSrv, 65001,
|
||||
.buffer_attrs = {SfBufferAttr_In | SfBufferAttr_HipcMapAlias},
|
||||
.buffers = {{src, src_size}}, );
|
||||
}
|
||||
289
source/ams_tab.cpp
Normal file
289
source/ams_tab.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
#include "ams_tab.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "confirm_page.hpp"
|
||||
#include "current_cfw.hpp"
|
||||
#include "dialogue_page.hpp"
|
||||
#include "download.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "worker_page.hpp"
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
AmsTab::AmsTab(const nlohmann::ordered_json& nxlinks, const bool erista) : brls::List()
|
||||
{
|
||||
this->erista = erista;
|
||||
this->nxlinks = nxlinks;
|
||||
this->hekate = util::getValueFromKey(nxlinks, "hekate");
|
||||
}
|
||||
|
||||
void AmsTab::RegisterListItemAction(brls::ListItem* listItem) {}
|
||||
|
||||
bool AmsTab::CreateDownloadItems(const nlohmann::ordered_json& cfw_links, bool hekate, bool ams)
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> links;
|
||||
links = download::getLinksFromJson(cfw_links);
|
||||
if (links.size() && !this->hekate.empty()) { // non-empty this->hekate indicates internet connection
|
||||
auto hekate_link = download::getLinksFromJson(this->hekate);
|
||||
std::string hekate_url = hekate_link[0].second;
|
||||
std::string text_hekate = "menus/common/download"_i18n + hekate_link[0].first;
|
||||
|
||||
for (const auto& link : links) {
|
||||
bool pack = link.first.contains("[PACK]");
|
||||
std::string url = link.second;
|
||||
std::string text("menus/common/download"_i18n + link.first + "menus/common/from"_i18n + url);
|
||||
listItem = new brls::ListItem(link.first);
|
||||
listItem->setHeight(LISTITEM_HEIGHT);
|
||||
listItem->getClickEvent()->subscribe([this, text, text_hekate, url, hekate_url, hekate, pack, ams](brls::View* view) {
|
||||
if (!erista && !std::filesystem::exists(MARIKO_PAYLOAD_PATH)) {
|
||||
brls::Application::crash("menus/errors/mariko_payload_missing"_i18n);
|
||||
}
|
||||
else {
|
||||
CreateStagedFrames(text, url, erista, ams, hekate && !pack, text_hekate, hekate_url);
|
||||
}
|
||||
});
|
||||
this->RegisterListItemAction(listItem);
|
||||
this->addView(listItem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AmsTab::CreateStagedFrames(const std::string& text, const std::string& url, bool erista, bool ams, bool hekate, const std::string& text_hekate, const std::string& hekate_url)
|
||||
{
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle(this->type == contentType::ams_cfw ? "menus/ams_update/getting_ams"_i18n : "menus/ams_update/custom_download"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage(stagedFrame, text));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, [&, url]() { util::downloadArchive(url, this->type); }));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/extracting"_i18n, [&]() { util::extractArchive(this->type); }));
|
||||
if (hekate) {
|
||||
stagedFrame->addStage(
|
||||
new DialoguePage_ams(stagedFrame, text_hekate, erista));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, [hekate_url]() { util::downloadArchive(hekate_url, contentType::bootloaders); }));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/extracting"_i18n, []() { util::extractArchive(contentType::bootloaders); }));
|
||||
}
|
||||
if (ams)
|
||||
stagedFrame->addStage(new ConfirmPage_AmsUpdate(stagedFrame, "menus/ams_update/reboot_rcm"_i18n, erista));
|
||||
else
|
||||
stagedFrame->addStage(new ConfirmPage_Done(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
}
|
||||
|
||||
AmsTab_Regular::AmsTab_Regular(const nlohmann::ordered_json& nxlinks, const bool erista) : AmsTab(nxlinks, erista)
|
||||
{
|
||||
this->CreateLists();
|
||||
}
|
||||
|
||||
bool AmsTab_Regular::CreateDownloadItems(const nlohmann::ordered_json& cfw_links, bool hekate, bool ams)
|
||||
{
|
||||
if (!AmsTab::CreateDownloadItems(cfw_links, hekate, ams)) {
|
||||
brls::Label* description = new brls::Label(
|
||||
brls::LabelStyle::SMALL,
|
||||
"menus/main/links_not_found"_i18n,
|
||||
true);
|
||||
description->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->addView(description);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AmsTab_Regular::CreateLists()
|
||||
{
|
||||
this->type = contentType::ams_cfw;
|
||||
auto cfws = util::getValueFromKey(this->nxlinks, "cfws");
|
||||
|
||||
this->addView(new brls::Label(brls::LabelStyle::DESCRIPTION, "menus/main/ams_text"_i18n + (CurrentCfw::running_cfw == CFW::ams ? "\n" + "menus/ams_update/current_ams"_i18n + CurrentCfw::getAmsInfo() : "") + (erista ? "\n" + "menus/ams_update/erista_rev"_i18n : "\n" + "menus/ams_update/mariko_rev"_i18n), true));
|
||||
CreateDownloadItems(util::getValueFromKey(cfws, "Atmosphere"));
|
||||
|
||||
this->addView(new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/ams_update/deepsea_label"_i18n,
|
||||
true));
|
||||
listItem = new brls::ListItem("menus/ams_update/get_custom_deepsea"_i18n);
|
||||
listItem->setHeight(LISTITEM_HEIGHT);
|
||||
listItem->getClickEvent()->subscribe([this](brls::View* view) {
|
||||
nlohmann::ordered_json modules;
|
||||
download::getRequest(DEEPSEA_META_JSON, modules);
|
||||
this->ShowCustomDeepseaBuilder(modules);
|
||||
});
|
||||
this->addView(listItem);
|
||||
CreateDownloadItems(util::getValueFromKey(cfws, "DeepSea"), false);
|
||||
}
|
||||
|
||||
std::string AmsTab_Regular::GetRepoName(const std::string& repo)
|
||||
{
|
||||
return repo.substr(repo.find("/") + 1, repo.length());
|
||||
}
|
||||
|
||||
std::set<std::string> AmsTab_Regular::GetLastDownloadedModules(const std::string& json_path)
|
||||
{
|
||||
nlohmann::ordered_json package = fs::parseJsonFile(json_path);
|
||||
std::set<std::string> res;
|
||||
if (package.find("modules") != package.end()) {
|
||||
for (const auto& module : package.at("modules")) {
|
||||
res.insert(module.get<std::string>());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
nlohmann::ordered_json AmsTab_Regular::SortDeepseaModules(const nlohmann::ordered_json& modules)
|
||||
{
|
||||
nlohmann::ordered_json sorted_modules = nlohmann::ordered_json::object();
|
||||
if (modules.find("modules") != modules.end()) {
|
||||
for (const auto& module : modules.at("modules").items()) {
|
||||
sorted_modules[std::string(module.value().at("category"))][module.key()] = module.value();
|
||||
}
|
||||
}
|
||||
return sorted_modules;
|
||||
}
|
||||
|
||||
void AmsTab_Regular::ShowCustomDeepseaBuilder(nlohmann::ordered_json& modules)
|
||||
{
|
||||
modules = SortDeepseaModules(modules);
|
||||
std::map<std::string, std::string> name_map;
|
||||
|
||||
brls::TabFrame* appView = new brls::TabFrame();
|
||||
appView->setIcon("romfs:/deepsea_icon.png");
|
||||
|
||||
std::vector<brls::List*> lists;
|
||||
std::set<std::string> old_modules = GetLastDownloadedModules(DEEPSEA_PACKAGE_PATH);
|
||||
|
||||
brls::ToggleListItem* deepseaListItem;
|
||||
for (const auto& category : modules.items()) {
|
||||
brls::List* list = new brls::List();
|
||||
|
||||
for (const auto& module : category.value().items()) {
|
||||
auto module_value = module.value();
|
||||
std::string requirements = "";
|
||||
if (!module_value.at("requires").empty()) {
|
||||
requirements = "menus/ams_update/depends_on"_i18n;
|
||||
for (const auto& r : module.value().at("requires")) {
|
||||
requirements += " " + r.get<std::string>() + ",";
|
||||
}
|
||||
requirements.pop_back();
|
||||
}
|
||||
if (module_value.at("required")) {
|
||||
deepseaListItem = new UnTogglableListItem(module_value.at("displayName"), 1, requirements, "Required", "o");
|
||||
}
|
||||
else {
|
||||
deepseaListItem = new ::brls::ToggleListItem(module_value.at("displayName"),
|
||||
old_modules.find(module.key()) != old_modules.end() ? 1 : 0,
|
||||
requirements,
|
||||
"menus/common/selected"_i18n,
|
||||
"menus/common/off"_i18n);
|
||||
}
|
||||
name_map.insert(std::pair(module_value.at("displayName"), module.key()));
|
||||
deepseaListItem->registerAction("menus/ams_update/show_module_description"_i18n, brls::Key::Y, [module_value] {
|
||||
util::showDialogBoxInfo(fmt::format("{}:\n{}", module_value.at("repo"), module_value.at("description")));
|
||||
return true;
|
||||
});
|
||||
list->addView(deepseaListItem);
|
||||
}
|
||||
lists.push_back(list);
|
||||
appView->addTab(category.key(), list);
|
||||
}
|
||||
|
||||
appView->registerAction("menus/ams_update/download_deepsea_package"_i18n, brls::Key::X, [this, lists, name_map] {
|
||||
std::set<std::string> desired_modules;
|
||||
for (const auto& list : lists) {
|
||||
for (size_t i = 0; i < list->getViewsCount(); i++) {
|
||||
if (brls::ToggleListItem* item = dynamic_cast<brls::ToggleListItem*>(list->getChild(i))) {
|
||||
if (item->getToggleState()) {
|
||||
desired_modules.insert(name_map.at(item->getLabel()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string request_url = DEEPSEA_BUILD_URL;
|
||||
for (const auto& e : desired_modules)
|
||||
request_url += e + ";";
|
||||
|
||||
this->CreateStagedFrames("menus/common/download"_i18n + "Custom DeepSea package" + "menus/common/from"_i18n + request_url,
|
||||
request_url,
|
||||
this->erista);
|
||||
return true;
|
||||
});
|
||||
appView->registerAction("", brls::Key::PLUS, [this] { return true; });
|
||||
|
||||
brls::PopupFrame::open("menus/ams_update/deepsea_builder"_i18n, appView, modules.empty() ? "menus/ams_update/cant_fetch_deepsea"_i18n : "menus/ams_update/build_your_deepsea"_i18n, "");
|
||||
}
|
||||
|
||||
AmsTab_Custom::AmsTab_Custom(const nlohmann::ordered_json& nxlinks, const bool erista) : AmsTab(nxlinks, erista)
|
||||
{
|
||||
this->custom_packs = fs::parseJsonFile(CUSTOM_PACKS_PATH);
|
||||
this->CreateLists();
|
||||
}
|
||||
|
||||
void AmsTab_Custom::CreateLists()
|
||||
{
|
||||
this->addView(new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
fmt::format("menus/ams_update/custom_packs_label"_i18n, CUSTOM_PACKS_PATH),
|
||||
true));
|
||||
|
||||
this->type = contentType::ams_cfw;
|
||||
this->addView(new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/ams_update/custom_packs_ams"_i18n,
|
||||
true));
|
||||
CreateDownloadItems(util::getValueFromKey(this->custom_packs, "ams"), true);
|
||||
this->AddLinkCreator();
|
||||
|
||||
this->type = contentType::custom;
|
||||
this->addView(new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/ams_update/custom_packs_misc"_i18n,
|
||||
true));
|
||||
CreateDownloadItems(util::getValueFromKey(this->custom_packs, "misc"), false, false);
|
||||
this->AddLinkCreator();
|
||||
}
|
||||
|
||||
void AmsTab_Custom::AddLinkCreator()
|
||||
{
|
||||
std::string category = this->type == contentType::ams_cfw ? "ams" : "misc";
|
||||
listItem = new brls::ListItem("menus/ams_update/add_custom_link"_i18n);
|
||||
listItem->setHeight(LISTITEM_HEIGHT);
|
||||
listItem->getClickEvent()->subscribe([this, category](brls::View* view) {
|
||||
std::string title, link;
|
||||
brls::Swkbd::openForText([&title](std::string text) { title = text; }, "Enter title", "", 256, "", 0, "Submit", "Title");
|
||||
brls::Swkbd::openForText([&link](std::string text) { link = text; }, "Enter direct link", "", 256, "", 0, "Submit", "https://site/download.zip");
|
||||
auto links = util::getValueFromKey(this->custom_packs, category);
|
||||
links[title] = link;
|
||||
this->custom_packs[category] = links;
|
||||
fs::writeJsonToFile(this->custom_packs, CUSTOM_PACKS_PATH);
|
||||
util::restartApp();
|
||||
});
|
||||
this->addView(listItem);
|
||||
}
|
||||
|
||||
void AmsTab_Custom::RegisterListItemAction(brls::ListItem* listItem)
|
||||
{
|
||||
std::string label = listItem->getLabel();
|
||||
std::string category = this->type == contentType::ams_cfw ? "ams" : "misc";
|
||||
listItem->registerAction("menus/ams_update/delete_custom_link"_i18n, brls::Key::X, [this, label, category] {
|
||||
auto& links = this->custom_packs.at(category);
|
||||
links.erase(label);
|
||||
fs::writeJsonToFile(this->custom_packs, CUSTOM_PACKS_PATH);
|
||||
util::restartApp();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool UnTogglableListItem::onClick()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
377
source/app_page.cpp
Normal file
377
source/app_page.cpp
Normal file
@@ -0,0 +1,377 @@
|
||||
#include "app_page.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
|
||||
#include "confirm_page.hpp"
|
||||
#include "current_cfw.hpp"
|
||||
#include "download.hpp"
|
||||
#include "download_cheats_page.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "worker_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
AppPage::AppPage() : AppletFrame(true, true)
|
||||
{
|
||||
list = new brls::List();
|
||||
}
|
||||
|
||||
void AppPage::PopulatePage()
|
||||
{
|
||||
this->CreateLabel();
|
||||
|
||||
NsApplicationRecord* records = new NsApplicationRecord[MaxTitleCount];
|
||||
NsApplicationControlData* controlData = NULL;
|
||||
std::string name;
|
||||
|
||||
s32 recordCount = 0;
|
||||
u64 controlSize = 0;
|
||||
u64 tid;
|
||||
|
||||
if (!util::isApplet()) {
|
||||
if (R_SUCCEEDED(nsListApplicationRecord(records, MaxTitleCount, 0, &recordCount))) {
|
||||
for (s32 i = 0; i < recordCount; i++) {
|
||||
controlSize = 0;
|
||||
|
||||
if (R_FAILED(InitControlData(&controlData))) break;
|
||||
|
||||
tid = records[i].application_id;
|
||||
|
||||
if (R_FAILED(GetControlData(tid, controlData, controlSize, name)))
|
||||
continue;
|
||||
|
||||
listItem = new brls::ListItem(name, "", util::formatApplicationId(tid));
|
||||
listItem->setThumbnail(controlData->icon, sizeof(controlData->icon));
|
||||
this->AddListItem(name, tid);
|
||||
}
|
||||
free(controlData);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tid = GetCurrentApplicationId();
|
||||
if (R_SUCCEEDED(InitControlData(&controlData)) && R_SUCCEEDED(GetControlData(tid & 0xFFFFFFFFFFFFF000, controlData, controlSize, name))) {
|
||||
listItem = new brls::ListItem(name, "", util::formatApplicationId(tid));
|
||||
listItem->setThumbnail(controlData->icon, sizeof(controlData->icon));
|
||||
this->AddListItem(name, tid);
|
||||
}
|
||||
label = new brls::Label(brls::LabelStyle::SMALL, "menus/common/applet_mode_not_supported"_i18n, true);
|
||||
list->addView(label);
|
||||
}
|
||||
delete[] records;
|
||||
|
||||
brls::Logger::debug("count {}", list->getViewsCount());
|
||||
|
||||
if (!list->getViewsCount()) {
|
||||
list->addView(new brls::Label(brls::LabelStyle::DESCRIPTION, "menus/common/nothing_to_see"_i18n, true));
|
||||
}
|
||||
|
||||
this->setContentView(list);
|
||||
}
|
||||
|
||||
void AppPage::CreateDownloadAllButton()
|
||||
{
|
||||
std::string text("menus/cheats/downloading"_i18n);
|
||||
std::string url = "";
|
||||
switch (CurrentCfw::running_cfw) {
|
||||
case CFW::ams:
|
||||
url += CHEATS_URL_CONTENTS;
|
||||
break;
|
||||
case CFW::rnx:
|
||||
url += CHEATS_URL_CONTENTS;
|
||||
break;
|
||||
case CFW::sxos:
|
||||
url += CHEATS_URL_CONTENTS;
|
||||
break;
|
||||
}
|
||||
text += url;
|
||||
download = new brls::ListItem("menus/cheats/dl_latest"_i18n);
|
||||
download->getClickEvent()->subscribe([url, text](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/cheats/getting_cheats"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage(stagedFrame, text));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, [url]() { util::downloadArchive(url, contentType::cheats); }));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/extracting"_i18n, []() { util::extractArchive(contentType::cheats); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(download);
|
||||
}
|
||||
|
||||
u32 AppPage::InitControlData(NsApplicationControlData** controlData)
|
||||
{
|
||||
free(*controlData);
|
||||
*controlData = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData));
|
||||
if (*controlData == NULL) {
|
||||
return 300;
|
||||
}
|
||||
else {
|
||||
memset(*controlData, 0, sizeof(NsApplicationControlData));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 AppPage::GetControlData(u64 tid, NsApplicationControlData* controlData, u64& controlSize, std::string& name)
|
||||
{
|
||||
Result rc;
|
||||
NacpLanguageEntry* langEntry = NULL;
|
||||
rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, controlData, sizeof(NsApplicationControlData), &controlSize);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
if (controlSize < sizeof(controlData->nacp)) return 100;
|
||||
|
||||
rc = nacpGetLanguageEntry(&controlData->nacp, &langEntry);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
if (!langEntry->name) return 200;
|
||||
|
||||
name = langEntry->name;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AppPage::AddListItem(const std::string& name, u64 tid)
|
||||
{
|
||||
list->addView(listItem);
|
||||
}
|
||||
|
||||
uint64_t AppPage::GetCurrentApplicationId()
|
||||
{
|
||||
Result rc = 0;
|
||||
uint64_t pid = 0;
|
||||
uint64_t tid = 0;
|
||||
|
||||
rc = pmdmntGetApplicationProcessId(&pid);
|
||||
if (rc == 0x20f || R_FAILED(rc)) return 0;
|
||||
|
||||
rc = pminfoGetProgramId(&tid, pid);
|
||||
if (rc == 0x20f || R_FAILED(rc)) return 0;
|
||||
|
||||
return tid;
|
||||
}
|
||||
|
||||
AppPage_CheatSlips::AppPage_CheatSlips() : AppPage()
|
||||
{
|
||||
this->PopulatePage();
|
||||
this->registerAction("menus/cheats/cheatslips_logout"_i18n, brls::Key::X, [this] {
|
||||
std::filesystem::remove(TOKEN_PATH);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void AppPage_CheatSlips::CreateLabel()
|
||||
{
|
||||
this->setTitle("menus/cheats/cheatslips_title"_i18n);
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, "menus/cheats/cheatslips_select"_i18n, true);
|
||||
list->addView(label);
|
||||
}
|
||||
|
||||
void AppPage_CheatSlips::AddListItem(const std::string& name, u64 tid)
|
||||
{
|
||||
listItem->getClickEvent()->subscribe([tid, name](brls::View* view) { brls::Application::pushView(new DownloadCheatsPage_CheatSlips(tid, name)); });
|
||||
list->addView(listItem);
|
||||
}
|
||||
|
||||
AppPage_Gbatemp::AppPage_Gbatemp() : AppPage()
|
||||
{
|
||||
this->PopulatePage();
|
||||
this->setIcon("romfs:/gbatemp_icon.png");
|
||||
}
|
||||
|
||||
void AppPage_Gbatemp::CreateLabel()
|
||||
{
|
||||
this->setTitle("menus/cheats/gbatemp_title"_i18n);
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, "menus/cheats/cheatslips_select"_i18n, true);
|
||||
list->addView(label);
|
||||
}
|
||||
|
||||
void AppPage_Gbatemp::AddListItem(const std::string& name, u64 tid)
|
||||
{
|
||||
listItem->getClickEvent()->subscribe([tid, name](brls::View* view) { brls::Application::pushView(new DownloadCheatsPage_Gbatemp(tid, name)); });
|
||||
list->addView(listItem);
|
||||
}
|
||||
|
||||
AppPage_Gfx::AppPage_Gfx() : AppPage()
|
||||
{
|
||||
this->PopulatePage();
|
||||
}
|
||||
|
||||
void AppPage_Gfx::CreateLabel()
|
||||
{
|
||||
this->setTitle("menus/cheats/gfx_title"_i18n);
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, "menus/cheats/cheatslips_select"_i18n, true);
|
||||
list->addView(label);
|
||||
}
|
||||
|
||||
void AppPage_Gfx::AddListItem(const std::string& name, u64 tid)
|
||||
{
|
||||
listItem->getClickEvent()->subscribe([tid, name](brls::View* view) { brls::Application::pushView(new DownloadCheatsPage_Gfx(tid, name)); });
|
||||
list->addView(listItem);
|
||||
}
|
||||
|
||||
|
||||
AppPage_Exclude::AppPage_Exclude() : AppPage()
|
||||
{
|
||||
this->PopulatePage();
|
||||
}
|
||||
|
||||
void AppPage_Exclude::CreateLabel()
|
||||
{
|
||||
this->setTitle("menus/cheats/exclude_titles"_i18n);
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, "menus/cheats/exclude_titles_desc"_i18n, true);
|
||||
list->addView(label);
|
||||
}
|
||||
|
||||
void AppPage_Exclude::PopulatePage()
|
||||
{
|
||||
this->CreateLabel();
|
||||
|
||||
NsApplicationRecord* records = new NsApplicationRecord[MaxTitleCount];
|
||||
NsApplicationControlData* controlData = NULL;
|
||||
std::string name;
|
||||
|
||||
s32 recordCount = 0;
|
||||
u64 controlSize = 0;
|
||||
u64 tid;
|
||||
|
||||
auto titles = fs::readLineByLine(CHEATS_EXCLUDE);
|
||||
|
||||
if (!util::isApplet()) {
|
||||
if (R_SUCCEEDED(nsListApplicationRecord(records, MaxTitleCount, 0, &recordCount))) {
|
||||
for (s32 i = 0; i < recordCount; i++) {
|
||||
controlSize = 0;
|
||||
|
||||
if (R_FAILED(InitControlData(&controlData))) break;
|
||||
|
||||
tid = records[i].application_id;
|
||||
|
||||
if (R_FAILED(GetControlData(tid, controlData, controlSize, name)))
|
||||
continue;
|
||||
|
||||
brls::ToggleListItem* listItem;
|
||||
listItem = new brls::ToggleListItem(std::string(name), titles.find(util::formatApplicationId(tid)) != titles.end() ? 0 : 1);
|
||||
|
||||
listItem->setThumbnail(controlData->icon, sizeof(controlData->icon));
|
||||
items.insert(std::make_pair(listItem, util::formatApplicationId(tid)));
|
||||
list->addView(listItem);
|
||||
}
|
||||
free(controlData);
|
||||
}
|
||||
}
|
||||
else {
|
||||
label = new brls::Label(brls::LabelStyle::SMALL, "menus/common/applet_mode_not_supported"_i18n, true);
|
||||
list->addView(label);
|
||||
}
|
||||
delete[] records;
|
||||
|
||||
list->registerAction("menus/cheats/exclude_titles_save"_i18n, brls::Key::B, [this] {
|
||||
std::set<std::string> exclude;
|
||||
for (const auto& item : items) {
|
||||
if (!item.first->getToggleState()) {
|
||||
exclude.insert(item.second);
|
||||
}
|
||||
}
|
||||
extract::writeTitlesToFile(exclude, CHEATS_EXCLUDE);
|
||||
brls::Application::popView();
|
||||
return true;
|
||||
});
|
||||
|
||||
this->registerAction("menus/cheats/exclude_all"_i18n, brls::Key::X, [this] {
|
||||
std::set<std::string> exclude;
|
||||
for (const auto& item : items) {
|
||||
exclude.insert(item.second);
|
||||
}
|
||||
extract::writeTitlesToFile(exclude, CHEATS_EXCLUDE);
|
||||
brls::Application::popView();
|
||||
return true;
|
||||
});
|
||||
|
||||
this->registerAction("menus/cheats/exclude_none"_i18n, brls::Key::Y, [this] {
|
||||
extract::writeTitlesToFile({}, CHEATS_EXCLUDE);
|
||||
brls::Application::popView();
|
||||
return true;
|
||||
});
|
||||
|
||||
this->setContentView(list);
|
||||
}
|
||||
|
||||
AppPage_DownloadedCheats::AppPage_DownloadedCheats() : AppPage()
|
||||
{
|
||||
GetExistingCheatsTids();
|
||||
this->PopulatePage();
|
||||
}
|
||||
|
||||
void AppPage_DownloadedCheats::CreateLabel()
|
||||
{
|
||||
this->setTitle("menus/cheats/installed"_i18n);
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, "menus/cheats/label"_i18n, true);
|
||||
list->addView(label);
|
||||
}
|
||||
|
||||
void AppPage_DownloadedCheats::AddListItem(const std::string& name, u64 tid)
|
||||
{
|
||||
auto tid_str = util::formatApplicationId(tid);
|
||||
if (titles.find(tid_str) != titles.end()) {
|
||||
listItem->getClickEvent()->subscribe([tid, name](brls::View* view) { cheats_util::ShowCheatFiles(tid, name); });
|
||||
listItem->registerAction("menus/cheats/delete_cheats"_i18n, brls::Key::Y, [tid_str] {
|
||||
util::showDialogBoxInfo(extract::removeCheatsDirectory(fmt::format("{}{}", util::getContentsPath(), tid_str)) ? "menus/common/all_done"_i18n : fmt::format("menus/cheats/deletion_error"_i18n, tid_str));
|
||||
return true;
|
||||
});
|
||||
list->addView(listItem);
|
||||
}
|
||||
}
|
||||
|
||||
void AppPage_DownloadedCheats::GetExistingCheatsTids()
|
||||
{
|
||||
std::string path = util::getContentsPath();
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path)) {
|
||||
std::string cheatsPath = entry.path().string() + "/cheats";
|
||||
if (std::filesystem::exists(cheatsPath) && !std::filesystem::is_empty(cheatsPath)) {
|
||||
for (const auto& cheatFile : std::filesystem::directory_iterator(cheatsPath)) {
|
||||
if (extract::isBID(cheatFile.path().filename().stem())) {
|
||||
titles.insert(util::upperCase(cheatsPath.substr(cheatsPath.length() - 7 - 16, 16)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppPage_OutdatedTitles::AppPage_OutdatedTitles() : AppPage()
|
||||
{
|
||||
download::getRequest(LOOKUP_TABLE_URL, versions);
|
||||
if (versions.empty()) {
|
||||
list->addView(new brls::Label(brls::LabelStyle::DESCRIPTION, "menus/main/links_not_found"_i18n, true));
|
||||
this->setContentView(list);
|
||||
}
|
||||
else {
|
||||
this->PopulatePage();
|
||||
}
|
||||
}
|
||||
|
||||
void AppPage_OutdatedTitles::AddListItem(const std::string& name, u64 tid)
|
||||
{
|
||||
u32 version = cheats_util::GetVersion(tid);
|
||||
std::string tid_string = util::formatApplicationId(tid);
|
||||
if (versions.find(tid_string) != versions.end()) {
|
||||
u32 latest = versions.at(tid_string).at("latest");
|
||||
if (version < latest) {
|
||||
listItem->setSubLabel(fmt::format("{}\t|\t v{} (local) → v{} (latest)", tid_string, version, latest));
|
||||
list->addView(listItem);
|
||||
}
|
||||
}
|
||||
/* else {
|
||||
listItem->setSubLabel(fmt::format("{}\t|\t {}", tid_string, "menus/tools/latest_version_not_found"_i18n));
|
||||
list->addView(listItem);
|
||||
} */
|
||||
}
|
||||
79
source/cheats_page.cpp
Normal file
79
source/cheats_page.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "cheats_page.hpp"
|
||||
|
||||
#include "app_page.hpp"
|
||||
#include "confirm_page.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "current_cfw.hpp"
|
||||
#include "download.hpp"
|
||||
#include "exclude_page.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "worker_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
CheatsPage::CheatsPage() : AppletFrame(true, true)
|
||||
{
|
||||
this->setTitle("menus/cheats/menu"_i18n);
|
||||
list = new brls::List();
|
||||
|
||||
item = new brls::ListItem("menus/cheats/view"_i18n);
|
||||
item->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::Application::pushView(new AppPage_DownloadedCheats());
|
||||
});
|
||||
list->addView(item);
|
||||
|
||||
item = new brls::ListItem("menus/cheats/exclude"_i18n);
|
||||
item->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::Application::pushView(new AppPage_Exclude());
|
||||
});
|
||||
list->addView(item);
|
||||
|
||||
item = new brls::ListItem("menus/cheats/delete_existing"_i18n);
|
||||
item->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/cheats/delete_existing"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/cheats/deleting"_i18n, []() { extract::removeCheats(); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(item);
|
||||
|
||||
item = new brls::ListItem("menus/cheats/delete_orphaned"_i18n);
|
||||
item->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/cheats/delete_orphaned"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/cheats/deleting"_i18n, []() { extract::removeOrphanedCheats(); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(item);
|
||||
|
||||
std::string cheatsVer = util::getCheatsVersion();
|
||||
if (cheatsVer != "") {
|
||||
item = new brls::ListItem("menus/cheats/dl_all"_i18n);
|
||||
item->getClickEvent()->subscribe([cheatsVer](brls::View* view) {
|
||||
std::string url = CurrentCfw::running_cfw == CFW::sxos ? CHEATS_URL_TITLES : CHEATS_URL_CONTENTS;
|
||||
std::string text(fmt::format("menus/main/get_cheats"_i18n, cheatsVer));
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/cheats/dl_all"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage(stagedFrame, text));
|
||||
if (cheatsVer != "offline") {
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, [url]() { util::downloadArchive(url, contentType::cheats); }));
|
||||
}
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/extracting"_i18n, [cheatsVer]() { extract::extractAllCheats(CHEATS_FILENAME, CurrentCfw::running_cfw, cheatsVer); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(item);
|
||||
}
|
||||
this->setContentView(list);
|
||||
}
|
||||
50
source/choice_page.cpp
Normal file
50
source/choice_page.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "choice_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
ChoicePage::ChoicePage(brls::StagedAppletFrame* frame, const std::string text)
|
||||
{
|
||||
this->yes = (new brls::Button(brls::ButtonStyle::BORDERLESS))->setLabel("yes");
|
||||
this->yes->setParent(this);
|
||||
this->no = (new brls::Button(brls::ButtonStyle::BORDERLESS))->setLabel("no");
|
||||
this->no->setParent(this);
|
||||
|
||||
this->label = new brls::Label(brls::LabelStyle::DIALOG, text, true);
|
||||
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->label->setParent(this);
|
||||
}
|
||||
|
||||
void ChoicePage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
|
||||
{
|
||||
}
|
||||
|
||||
brls::View* ChoicePage::getDefaultFocus()
|
||||
{
|
||||
return this->no;
|
||||
}
|
||||
|
||||
void ChoicePage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
|
||||
{
|
||||
this->label->setWidth(this->width);
|
||||
this->label->invalidate(true);
|
||||
|
||||
this->label->setBoundaries(
|
||||
this->x + this->width / 2 - this->label->getWidth() / 2,
|
||||
this->y + (this->height - this->label->getHeight() - this->y - style->CrashFrame.buttonHeight) / 2,
|
||||
this->label->getWidth(),
|
||||
this->label->getHeight());
|
||||
|
||||
this->yes->setBoundaries(
|
||||
this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2,
|
||||
this->y + (this->height - style->CrashFrame.buttonHeight * 3),
|
||||
style->CrashFrame.buttonWidth,
|
||||
style->CrashFrame.buttonHeight);
|
||||
this->yes->invalidate();
|
||||
|
||||
this->no->setBoundaries(
|
||||
this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2,
|
||||
this->y + (this->height - style->CrashFrame.buttonHeight * 3),
|
||||
style->CrashFrame.buttonWidth,
|
||||
style->CrashFrame.buttonHeight);
|
||||
this->no->invalidate();
|
||||
}
|
||||
377
source/color_swapper.cpp
Normal file
377
source/color_swapper.cpp
Normal file
@@ -0,0 +1,377 @@
|
||||
#include "color_swapper.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "download.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "progress_event.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char BACKUP[] = "_backup";
|
||||
|
||||
int hexToBGR(const std::string& hex)
|
||||
{
|
||||
std::string R = hex.substr(0, 2);
|
||||
std::string G = hex.substr(2, 2);
|
||||
std::string B = hex.substr(4, 2);
|
||||
return std::stoi(B + G + R, 0, 16);
|
||||
}
|
||||
|
||||
std::string BGRToHex(int v)
|
||||
{
|
||||
std::stringstream ss;
|
||||
v = ((v & 0xFF) << 16) + (v & 0xFF00) + (v >> 16) + 256;
|
||||
ss << std::setfill('0') << std::setw(6) << std::right << std::hex << v;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool isHexaAnd3Bytes(const std::string& str)
|
||||
{
|
||||
if (str.size() != 6) return false;
|
||||
for (char const& c : str) {
|
||||
if (!isxdigit(c)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace JC {
|
||||
|
||||
int setColor(const std::vector<int>& colors)
|
||||
{
|
||||
Result pads, ljc, rjc;
|
||||
int res = 0;
|
||||
s32 nbEntries;
|
||||
HidsysUniquePadId UniquePadIds[2] = {};
|
||||
|
||||
pads = hidsysGetUniquePadsFromNpad(HidNpadIdType_Handheld, UniquePadIds, 2, &nbEntries);
|
||||
if (R_SUCCEEDED(pads)) {
|
||||
ljc = hiddbgUpdateControllerColor(colors[0], colors[1], UniquePadIds[0]);
|
||||
if (R_FAILED(ljc)) res += 1;
|
||||
rjc = hiddbgUpdateControllerColor(colors[2], colors[3], UniquePadIds[1]);
|
||||
if (R_FAILED(rjc)) res += 2;
|
||||
}
|
||||
else {
|
||||
res += 4;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int backupToJSON(json& profiles, const std::string& path)
|
||||
{
|
||||
HidNpadControllerColor color_left;
|
||||
HidNpadControllerColor color_right;
|
||||
Result res = hidGetNpadControllerColorSplit(HidNpadIdType_Handheld, &color_left, &color_right);
|
||||
std::vector<int> oldBackups;
|
||||
if (R_SUCCEEDED(res)) {
|
||||
int i = 0;
|
||||
for (const auto& x : profiles.items()) {
|
||||
if (x.value()["name"] == BACKUP) {
|
||||
oldBackups.push_back(i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
for (auto& k : oldBackups) {
|
||||
profiles.erase(profiles.begin() + k);
|
||||
}
|
||||
json newBackup = json::object(
|
||||
{{"name", BACKUP},
|
||||
{"L_JC", BGRToHex(color_left.main)},
|
||||
{"L_BTN", BGRToHex(color_left.sub)},
|
||||
{"R_JC", BGRToHex(color_right.main)},
|
||||
{"R_BTN", BGRToHex(color_right.sub)}});
|
||||
profiles.push_back(newBackup);
|
||||
fs::writeJsonToFile(profiles, path);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
json backupProfile()
|
||||
{
|
||||
json newBackup;
|
||||
HidNpadControllerColor color_left;
|
||||
HidNpadControllerColor color_right;
|
||||
Result res = hidGetNpadControllerColorSplit(HidNpadIdType_Handheld, &color_left, &color_right);
|
||||
if (R_SUCCEEDED(res)) {
|
||||
newBackup = json::object(
|
||||
{{"name", BACKUP},
|
||||
{"L_JC", BGRToHex(color_left.main)},
|
||||
{"L_BTN", BGRToHex(color_left.sub)},
|
||||
{"R_JC", BGRToHex(color_right.main)},
|
||||
{"R_BTN", BGRToHex(color_right.sub)}});
|
||||
}
|
||||
return newBackup;
|
||||
}
|
||||
|
||||
std::deque<std::pair<std::string, std::vector<int>>> getProfiles(const std::string& path)
|
||||
{
|
||||
std::deque<std::pair<std::string, std::vector<int>>> res;
|
||||
bool properData;
|
||||
std::fstream profilesFile;
|
||||
nlohmann::ordered_json profilesJson;
|
||||
download::getRequest(JC_COLOR_URL, profilesJson);
|
||||
if (profilesJson.empty()) {
|
||||
profilesJson = {{{"L_BTN", "0A1E0A"},
|
||||
{"L_JC", "82FF96"},
|
||||
{"R_BTN", "0A1E28"},
|
||||
{"R_JC", "96F5F5"},
|
||||
{"name", "Animal Crossing: New Horizons"}}};
|
||||
}
|
||||
for (const auto& profiles : {fs::parseJsonFile(path), profilesJson}) {
|
||||
for (const auto& x : profiles.items()) {
|
||||
std::string name = x.value()["name"];
|
||||
brls::Logger::warning(name);
|
||||
std::vector<std::string> values = {
|
||||
std::string(x.value()["L_JC"]),
|
||||
std::string(x.value()["L_BTN"]),
|
||||
std::string(x.value()["R_JC"]),
|
||||
std::string(x.value()["R_BTN"])};
|
||||
properData = true;
|
||||
for (auto& str : values) {
|
||||
if (!isHexaAnd3Bytes(str)) {
|
||||
properData = false;
|
||||
}
|
||||
}
|
||||
if (properData) {
|
||||
if (name == "") name = "Unamed";
|
||||
auto profile = std::make_pair(name, (std::vector<int>){
|
||||
hexToBGR(values[0]),
|
||||
hexToBGR(values[1]),
|
||||
hexToBGR(values[2]),
|
||||
hexToBGR(values[3])});
|
||||
if (name == BACKUP) {
|
||||
res.push_front(profile);
|
||||
}
|
||||
else {
|
||||
res.push_back(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void changeJCColor(const std::vector<int>& values)
|
||||
{
|
||||
hiddbgInitialize();
|
||||
hidsysInitialize();
|
||||
ProgressEvent::instance().setStep(1);
|
||||
int res = setColor(values);
|
||||
if (res != 0) {
|
||||
util::showDialogBoxBlocking("Could not change the Joy-Cons color. Make sure they're docked and try again.\nError :" + std::to_string(res), "Ok");
|
||||
}
|
||||
hiddbgExit();
|
||||
hidsysExit();
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
|
||||
void backupJCColor(const std::string& path)
|
||||
{
|
||||
hiddbgInitialize();
|
||||
hidsysInitialize();
|
||||
ProgressEvent::instance().setStep(1);
|
||||
json backup;
|
||||
json profiles;
|
||||
std::fstream profilesFile;
|
||||
if (std::filesystem::exists(path)) {
|
||||
profilesFile.open(path, std::fstream::in);
|
||||
profilesFile >> profiles;
|
||||
profilesFile.close();
|
||||
}
|
||||
|
||||
std::vector<int> oldBackups;
|
||||
int i = 0;
|
||||
for (const auto& x : profiles.items()) {
|
||||
if (x.value()["name"] == BACKUP) {
|
||||
oldBackups.push_back(i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
for (auto& k : oldBackups) {
|
||||
profiles.erase(profiles.begin() + k);
|
||||
}
|
||||
|
||||
while (backup.empty()) {
|
||||
backup = backupProfile();
|
||||
}
|
||||
|
||||
profiles.push_back(backup);
|
||||
//backup.push_back(profiles);
|
||||
fs::writeJsonToFile(profiles, path);
|
||||
hiddbgExit();
|
||||
hidsysExit();
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
|
||||
} // namespace JC
|
||||
|
||||
namespace PC {
|
||||
|
||||
int setColor(const std::vector<int>& colors)
|
||||
{
|
||||
Result pads, pc;
|
||||
int res = 0;
|
||||
s32 nbEntries;
|
||||
HidsysUniquePadId UniquePadIds[1] = {};
|
||||
|
||||
pads = hidsysGetUniquePadsFromNpad(HidNpadIdType_No1, UniquePadIds, 1, &nbEntries);
|
||||
if (R_SUCCEEDED(pads)) {
|
||||
pc = hiddbgUpdateControllerColor(colors[0], colors[1], UniquePadIds[0]);
|
||||
if (R_FAILED(pc)) res += 1;
|
||||
}
|
||||
else {
|
||||
res += 4;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int backupToJSON(json& profiles, const std::string& path)
|
||||
{
|
||||
HidNpadControllerColor color;
|
||||
Result res = hidGetNpadControllerColorSingle(HidNpadIdType_No1, &color);
|
||||
std::vector<int> oldBackups;
|
||||
if (R_SUCCEEDED(res)) {
|
||||
int i = 0;
|
||||
for (const auto& x : profiles.items()) {
|
||||
if (x.value()["name"] == BACKUP) {
|
||||
oldBackups.push_back(i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
for (auto& k : oldBackups) {
|
||||
profiles.erase(profiles.begin() + k);
|
||||
}
|
||||
json newBackup = json::object(
|
||||
{{"name", BACKUP},
|
||||
{"BODY", BGRToHex(color.main)},
|
||||
{"BTN", BGRToHex(color.sub)}});
|
||||
profiles.push_back(newBackup);
|
||||
fs::writeJsonToFile(profiles, path);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
json backupProfile()
|
||||
{
|
||||
json newBackup;
|
||||
HidNpadControllerColor color;
|
||||
Result res = hidGetNpadControllerColorSingle(HidNpadIdType_No1, &color);
|
||||
if (R_SUCCEEDED(res)) {
|
||||
newBackup = json::object(
|
||||
{{"name", BACKUP},
|
||||
{"BODY", BGRToHex(color.main)},
|
||||
{"BTN", BGRToHex(color.sub)}});
|
||||
}
|
||||
return newBackup;
|
||||
}
|
||||
|
||||
std::deque<std::pair<std::string, std::vector<int>>> getProfiles(const std::string& path)
|
||||
{
|
||||
std::deque<std::pair<std::string, std::vector<int>>> res;
|
||||
bool properData;
|
||||
std::fstream profilesFile;
|
||||
nlohmann::ordered_json profilesJson;
|
||||
download::getRequest(PC_COLOR_URL, profilesJson);
|
||||
if (profilesJson.empty()) {
|
||||
profilesJson = {{{"BTN", "e6e6e6"},
|
||||
{"BODY", "2d2d2d"},
|
||||
{"name", "Default black"}}};
|
||||
}
|
||||
for (const auto& profiles : {fs::parseJsonFile(path), profilesJson}) {
|
||||
for (const auto& x : profiles.items()) {
|
||||
std::string name = x.value()["name"];
|
||||
std::vector<std::string> values = {
|
||||
std::string(x.value()["BODY"]),
|
||||
std::string(x.value()["BTN"])};
|
||||
properData = true;
|
||||
for (auto& str : values) {
|
||||
if (!isHexaAnd3Bytes(str)) {
|
||||
properData = false;
|
||||
}
|
||||
}
|
||||
if (properData) {
|
||||
if (name == "") name = "Unamed";
|
||||
auto profile = std::make_pair(name, (std::vector<int>){
|
||||
hexToBGR(values[0]),
|
||||
hexToBGR(values[1])});
|
||||
if (name == BACKUP) {
|
||||
res.push_front(profile);
|
||||
}
|
||||
else {
|
||||
res.push_back(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void changePCColor(const std::vector<int>& values)
|
||||
{
|
||||
hiddbgInitialize();
|
||||
hidsysInitialize();
|
||||
ProgressEvent::instance().setStep(1);
|
||||
int res = setColor(values);
|
||||
if (res != 0) {
|
||||
util::showDialogBoxBlocking("Could not change the Pro-Con color. Make they're connected to P1. This feature may not work on unofficial controllers. \nError :" + std::to_string(res), "Ok");
|
||||
}
|
||||
hiddbgExit();
|
||||
hidsysExit();
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
|
||||
void backupPCColor(const std::string& path)
|
||||
{
|
||||
hiddbgInitialize();
|
||||
hidsysInitialize();
|
||||
ProgressEvent::instance().setStep(1);
|
||||
json backup;
|
||||
json profiles;
|
||||
std::fstream profilesFile;
|
||||
if (std::filesystem::exists(path)) {
|
||||
profilesFile.open(path, std::fstream::in);
|
||||
profilesFile >> profiles;
|
||||
profilesFile.close();
|
||||
}
|
||||
|
||||
std::vector<int> oldBackups;
|
||||
int i = 0;
|
||||
for (const auto& x : profiles.items()) {
|
||||
if (x.value()["name"] == BACKUP) {
|
||||
oldBackups.push_back(i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
for (auto& k : oldBackups) {
|
||||
profiles.erase(profiles.begin() + k);
|
||||
}
|
||||
|
||||
while (backup.empty()) {
|
||||
backup = backupProfile();
|
||||
}
|
||||
|
||||
profiles.push_back(backup);
|
||||
//backup.push_back(profiles);
|
||||
fs::writeJsonToFile(profiles, path);
|
||||
hiddbgExit();
|
||||
hidsysExit();
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
|
||||
} // namespace PC
|
||||
118
source/confirm_page.cpp
Normal file
118
source/confirm_page.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "confirm_page.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "main_frame.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
ConfirmPage::ConfirmPage(brls::StagedAppletFrame* frame, const std::string& text)
|
||||
{
|
||||
this->button = (new brls::Button(brls::ButtonStyle::REGULAR))->setLabel("menus/common/back"_i18n);
|
||||
this->button->setParent(this);
|
||||
this->button->getClickEvent()->subscribe([frame, this](View* view) {
|
||||
if (!frame->isLastStage())
|
||||
frame->nextStage();
|
||||
else
|
||||
brls::Application::pushView(new MainFrame());
|
||||
});
|
||||
|
||||
this->label = new brls::Label(brls::LabelStyle::DIALOG, text, true);
|
||||
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->label->setParent(this);
|
||||
}
|
||||
|
||||
ConfirmPage_Done::ConfirmPage_Done(brls::StagedAppletFrame* frame, const std::string& text) : ConfirmPage(frame, text)
|
||||
{
|
||||
this->button->setLabel("menus/common/back"_i18n);
|
||||
this->done = true;
|
||||
}
|
||||
|
||||
ConfirmPage_AppUpdate::ConfirmPage_AppUpdate(brls::StagedAppletFrame* frame, const std::string& text) : ConfirmPage_Done(frame, text)
|
||||
{
|
||||
this->button->getClickEvent()->subscribe([](View* view) {
|
||||
envSetNextLoad(FORWARDER_PATH, FORWARDER_PATH);
|
||||
romfsExit();
|
||||
brls::Application::quit();
|
||||
});
|
||||
this->registerAction("", brls::Key::B, [this] { return true; });
|
||||
}
|
||||
|
||||
ConfirmPage_AmsUpdate::ConfirmPage_AmsUpdate(brls::StagedAppletFrame* frame, const std::string& text, bool erista) : ConfirmPage_Done(frame, text)
|
||||
{
|
||||
this->button->getClickEvent()->subscribe([this, erista](View* view) {
|
||||
if (erista) {
|
||||
util::rebootToPayload(RCM_PAYLOAD_PATH);
|
||||
}
|
||||
else {
|
||||
if (std::filesystem::exists(UPDATE_BIN_PATH)) {
|
||||
fs::copyFile(UPDATE_BIN_PATH, MARIKO_PAYLOAD_PATH_TEMP);
|
||||
}
|
||||
else {
|
||||
fs::copyFile(REBOOT_PAYLOAD_PATH, MARIKO_PAYLOAD_PATH_TEMP);
|
||||
}
|
||||
fs::copyFile(RCM_PAYLOAD_PATH, MARIKO_PAYLOAD_PATH);
|
||||
util::shutDown(true);
|
||||
}
|
||||
});
|
||||
this->registerAction("", brls::Key::B, [this] { return true; });
|
||||
};
|
||||
|
||||
void ConfirmPage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
|
||||
{
|
||||
if (!this->done) {
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto missing = std::max(1l - std::chrono::duration_cast<std::chrono::seconds>(end - start).count(), 0l);
|
||||
auto text = std::string("menus/common/continue"_i18n);
|
||||
if (missing > 0) {
|
||||
this->button->setLabel(fmt::format("{} ({})", text, missing));
|
||||
this->button->setState(brls::ButtonState::DISABLED);
|
||||
}
|
||||
else {
|
||||
this->button->setLabel(text);
|
||||
this->button->setState(brls::ButtonState::ENABLED);
|
||||
}
|
||||
this->button->invalidate();
|
||||
}
|
||||
else {
|
||||
this->button->setState(brls::ButtonState::ENABLED);
|
||||
}
|
||||
this->label->frame(ctx);
|
||||
this->button->frame(ctx);
|
||||
}
|
||||
|
||||
brls::View* ConfirmPage::getDefaultFocus()
|
||||
{
|
||||
return this->button;
|
||||
}
|
||||
|
||||
void ConfirmPage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
|
||||
{
|
||||
this->label->setWidth(this->width);
|
||||
this->label->invalidate(true);
|
||||
// this->label->setBackground(brls::ViewBackground::DEBUG);
|
||||
this->label->setBoundaries(
|
||||
this->x + this->width / 2 - this->label->getWidth() / 2,
|
||||
this->y + (this->height - this->label->getHeight() - this->y - style->CrashFrame.buttonHeight) / 2,
|
||||
this->label->getWidth(),
|
||||
this->label->getHeight());
|
||||
|
||||
this->button->setBoundaries(
|
||||
this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2,
|
||||
this->y + (this->height - style->CrashFrame.buttonHeight * 3),
|
||||
style->CrashFrame.buttonWidth,
|
||||
style->CrashFrame.buttonHeight);
|
||||
this->button->invalidate();
|
||||
|
||||
start = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(150);
|
||||
}
|
||||
|
||||
ConfirmPage::~ConfirmPage()
|
||||
{
|
||||
delete this->label;
|
||||
delete this->button;
|
||||
}
|
||||
76
source/current_cfw.cpp
Normal file
76
source/current_cfw.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "current_cfw.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace CurrentCfw {
|
||||
|
||||
namespace {
|
||||
|
||||
bool isServiceRunning(const char* serviceName)
|
||||
{
|
||||
Handle handle;
|
||||
SmServiceName service_name = smEncodeName(serviceName);
|
||||
bool running = R_FAILED(smRegisterService(&handle, service_name, false, 1));
|
||||
svcCloseHandle(handle);
|
||||
if (!running)
|
||||
smUnregisterService(service_name);
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
Result smAtmosphereHasService(bool* out, SmServiceName name, bool v019)
|
||||
{
|
||||
u8 tmp = 0;
|
||||
Result rc = v019 ? tipcDispatchInOut(smGetServiceSessionTipc(), 65100, name, tmp) : serviceDispatchInOut(smGetServiceSession(), 65100, name, tmp);
|
||||
if (R_SUCCEEDED(rc) && out)
|
||||
*out = tmp;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool isPost019()
|
||||
{
|
||||
u64 version;
|
||||
if (R_SUCCEEDED(splGetConfig((SplConfigItem)65000, &version))) {
|
||||
if (((version >> 56) & ((1 << 8) - 1)) > 0 || ((version >> 48) & ((1 << 8) - 1)) >= 19) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CFW getCFW()
|
||||
{
|
||||
bool res = false;
|
||||
bool v019 = isPost019(); //AMS v0.19 introduced sm changes, and as such old AMS versions have to be treated differently
|
||||
if (R_SUCCEEDED(smAtmosphereHasService(&res, smEncodeName("rnx"), v019))) {
|
||||
if (res)
|
||||
return CFW::rnx;
|
||||
smAtmosphereHasService(&res, smEncodeName("tx"), v019);
|
||||
if (res)
|
||||
return CFW::sxos;
|
||||
}
|
||||
else { // use old method just in case
|
||||
if (isServiceRunning("rnx")) return CFW::rnx;
|
||||
if (isServiceRunning("tx")) return CFW::sxos;
|
||||
}
|
||||
return CFW::ams;
|
||||
}
|
||||
|
||||
std::string getAmsInfo()
|
||||
{
|
||||
u64 version;
|
||||
std::string res;
|
||||
if (R_SUCCEEDED(splGetConfig((SplConfigItem)65000, &version))) {
|
||||
res += std::to_string((version >> 56) & ((1 << 8) - 1)) + "." +
|
||||
std::to_string((version >> 48) & ((1 << 8) - 1)) + "." +
|
||||
std::to_string((version >> 40) & ((1 << 8) - 1));
|
||||
if (R_SUCCEEDED(splGetConfig((SplConfigItem)65007, &version)))
|
||||
res += version ? "|E" : "|S";
|
||||
return res;
|
||||
}
|
||||
else
|
||||
return "Couldn't retrieve AMS version";
|
||||
}
|
||||
|
||||
} // namespace CurrentCfw
|
||||
164
source/dialogue_page.cpp
Normal file
164
source/dialogue_page.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "dialogue_page.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "main_frame.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
void DialoguePage::CreateView()
|
||||
{
|
||||
this->button1 = (new brls::Button(brls::ButtonStyle::REGULAR))->setLabel("menus/common/yes"_i18n);
|
||||
this->button1->setParent(this);
|
||||
this->button2 = (new brls::Button(brls::ButtonStyle::REGULAR))->setLabel("menus/common/no"_i18n);
|
||||
this->button2->setParent(this);
|
||||
|
||||
this->instantiateButtons();
|
||||
|
||||
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->label->setParent(this);
|
||||
|
||||
this->navigationMap.add(
|
||||
this->button1,
|
||||
brls::FocusDirection::RIGHT,
|
||||
this->button2);
|
||||
|
||||
this->navigationMap.add(
|
||||
this->button2,
|
||||
brls::FocusDirection::LEFT,
|
||||
this->button1);
|
||||
|
||||
this->registerAction("", brls::Key::B, [this] { return true; });
|
||||
}
|
||||
|
||||
void DialoguePage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
|
||||
{
|
||||
this->label->frame(ctx);
|
||||
this->button1->frame(ctx);
|
||||
this->button2->frame(ctx);
|
||||
}
|
||||
|
||||
void DialoguePage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
|
||||
{
|
||||
this->label->setWidth(this->width);
|
||||
this->label->invalidate(true);
|
||||
this->label->setBoundaries(
|
||||
this->x + this->width / 2 - this->label->getWidth() / 2,
|
||||
this->y + (this->height - this->label->getHeight() - this->y - style->CrashFrame.buttonHeight) / 2,
|
||||
this->label->getWidth(),
|
||||
this->label->getHeight());
|
||||
this->button1->setBoundaries(
|
||||
this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2 - 200,
|
||||
this->y + (this->height - style->CrashFrame.buttonHeight * 3),
|
||||
style->CrashFrame.buttonWidth,
|
||||
style->CrashFrame.buttonHeight);
|
||||
this->button1->invalidate();
|
||||
|
||||
this->button2->setBoundaries(
|
||||
this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2 + 200,
|
||||
this->y + (this->height - style->CrashFrame.buttonHeight * 3),
|
||||
style->CrashFrame.buttonWidth,
|
||||
style->CrashFrame.buttonHeight);
|
||||
this->button2->invalidate();
|
||||
}
|
||||
|
||||
brls::View* DialoguePage::getDefaultFocus()
|
||||
{
|
||||
return this->button1;
|
||||
}
|
||||
|
||||
brls::View* DialoguePage::getNextFocus(brls::FocusDirection direction, brls::View* currentView)
|
||||
{
|
||||
return this->navigationMap.getNextFocus(direction, currentView);
|
||||
}
|
||||
|
||||
void DialoguePage_ams::instantiateButtons()
|
||||
{
|
||||
this->button1->getClickEvent()->subscribe([this](View* view) {
|
||||
if (!frame->isLastStage())
|
||||
frame->nextStage();
|
||||
else {
|
||||
brls::Application::pushView(new MainFrame());
|
||||
}
|
||||
});
|
||||
|
||||
this->button2->getClickEvent()->subscribe([this](View* view) {
|
||||
if (this->erista) {
|
||||
util::rebootToPayload(RCM_PAYLOAD_PATH);
|
||||
}
|
||||
else {
|
||||
if (std::filesystem::exists(UPDATE_BIN_PATH)) {
|
||||
fs::copyFile(UPDATE_BIN_PATH, MARIKO_PAYLOAD_PATH_TEMP);
|
||||
}
|
||||
else {
|
||||
fs::copyFile(REBOOT_PAYLOAD_PATH, MARIKO_PAYLOAD_PATH_TEMP);
|
||||
}
|
||||
fs::copyFile(RCM_PAYLOAD_PATH, MARIKO_PAYLOAD_PATH);
|
||||
util::shutDown(true);
|
||||
}
|
||||
brls::Application::popView();
|
||||
});
|
||||
|
||||
this->label = new brls::Label(brls::LabelStyle::DIALOG, "menus/ams_update/install_hekate"_i18n + "\n\n" + this->text, true);
|
||||
start = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(150);
|
||||
}
|
||||
|
||||
void DialoguePage_ams::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
|
||||
{
|
||||
this->label->frame(ctx);
|
||||
this->button1->frame(ctx);
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto missing = std::max(1l - std::chrono::duration_cast<std::chrono::seconds>(end - start).count(), 0l);
|
||||
auto text = std::string("menus/common/no"_i18n);
|
||||
if (missing > 0) {
|
||||
this->button2->setLabel(text + " (" + std::to_string(missing) + ")");
|
||||
this->button2->setState(brls::ButtonState::DISABLED);
|
||||
}
|
||||
else {
|
||||
this->button2->setLabel(text);
|
||||
this->button2->setState(brls::ButtonState::ENABLED);
|
||||
}
|
||||
this->button2->invalidate();
|
||||
this->button2->frame(ctx);
|
||||
}
|
||||
|
||||
void DialoguePage_fw::instantiateButtons()
|
||||
{
|
||||
this->button2->getClickEvent()->subscribe([this](View* view) {
|
||||
if (!frame->isLastStage())
|
||||
frame->nextStage();
|
||||
else {
|
||||
brls::Application::pushView(new MainFrame());
|
||||
}
|
||||
});
|
||||
|
||||
this->button1->getClickEvent()->subscribe([this](View* view) {
|
||||
envSetNextLoad(DAYBREAK_PATH, fmt::format("\"{}\" \"/firmware\"", DAYBREAK_PATH).c_str());
|
||||
romfsExit();
|
||||
brls::Application::quit();
|
||||
});
|
||||
|
||||
this->label = new brls::Label(brls::LabelStyle::DIALOG, fmt::format("{}\n\n{}", this->text, "menus/firmware/launch_daybreak"_i18n), true);
|
||||
}
|
||||
|
||||
void DialoguePage_confirm::instantiateButtons()
|
||||
{
|
||||
this->button1->getClickEvent()->subscribe([this](View* view) {
|
||||
if (!frame->isLastStage())
|
||||
frame->nextStage();
|
||||
else {
|
||||
brls::Application::pushView(new MainFrame());
|
||||
}
|
||||
});
|
||||
|
||||
this->button2->getClickEvent()->subscribe([this](View* view) {
|
||||
brls::Application::pushView(new MainFrame());
|
||||
});
|
||||
|
||||
this->label = new brls::Label(brls::LabelStyle::DIALOG, this->text, true);
|
||||
}
|
||||
472
source/download.cpp
Normal file
472
source/download.cpp
Normal file
@@ -0,0 +1,472 @@
|
||||
#include "download.hpp"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <math.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <switch.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "progress_event.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
constexpr const char API_AGENT[] = "HamletDuFromage";
|
||||
constexpr int _1MiB = 0x100000;
|
||||
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
namespace download {
|
||||
|
||||
namespace {
|
||||
|
||||
std::chrono::_V2::steady_clock::time_point time_old;
|
||||
double dlold;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* memory;
|
||||
size_t size;
|
||||
} MemoryStruct_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u_int8_t* data;
|
||||
size_t data_size;
|
||||
u_int64_t offset;
|
||||
FILE* out;
|
||||
Aes128CtrContext* aes;
|
||||
} ntwrk_struct_t;
|
||||
|
||||
static size_t WriteMemoryCallback(void* contents, size_t size, size_t num_files, void* userp)
|
||||
{
|
||||
if (ProgressEvent::instance().getInterupt()) {
|
||||
return 0;
|
||||
}
|
||||
ntwrk_struct_t* data_struct = (ntwrk_struct_t*)userp;
|
||||
size_t realsize = size * num_files;
|
||||
|
||||
if (realsize + data_struct->offset >= data_struct->data_size) {
|
||||
fwrite(data_struct->data, data_struct->offset, 1, data_struct->out);
|
||||
data_struct->offset = 0;
|
||||
}
|
||||
|
||||
if (data_struct->aes)
|
||||
aes128CtrCrypt(data_struct->aes, &data_struct->data[data_struct->offset], contents, realsize);
|
||||
else
|
||||
memcpy(&data_struct->data[data_struct->offset], contents, realsize);
|
||||
|
||||
data_struct->offset += realsize;
|
||||
data_struct->data[data_struct->offset] = 0;
|
||||
return realsize;
|
||||
}
|
||||
|
||||
int download_progress(void* p, double dltotal, double dlnow, double ultotal, double ulnow)
|
||||
{
|
||||
if (dltotal <= 0.0) return 0;
|
||||
|
||||
double fractionDownloaded = dlnow / dltotal;
|
||||
int counter = (int)(fractionDownloaded * ProgressEvent::instance().getMax()); //20 is the number of increments
|
||||
ProgressEvent::instance().setStep(std::min(ProgressEvent::instance().getMax() - 1, counter));
|
||||
ProgressEvent::instance().setNow(dlnow);
|
||||
ProgressEvent::instance().setTotalCount(dltotal);
|
||||
auto time_now = std::chrono::steady_clock::now();
|
||||
double elasped_time = ((std::chrono::duration<double>)(time_now - time_old)).count();
|
||||
if (elasped_time > 1.2f) {
|
||||
ProgressEvent::instance().setSpeed((dlnow - dlold) / elasped_time);
|
||||
dlold = dlnow;
|
||||
time_old = time_now;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct MemoryStruct
|
||||
{
|
||||
char* memory;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static size_t WriteMemoryCallback2(void* contents, size_t size, size_t nmemb, void* userp)
|
||||
{
|
||||
size_t realsize = size * nmemb;
|
||||
struct MemoryStruct* mem = (struct MemoryStruct*)userp;
|
||||
|
||||
char* ptr = static_cast<char*>(realloc(mem->memory, mem->size + realsize + 1));
|
||||
if (ptr == NULL) {
|
||||
/* out of memory! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
mem->memory = ptr;
|
||||
memcpy(&(mem->memory[mem->size]), contents, realsize);
|
||||
mem->size += realsize;
|
||||
mem->memory[mem->size] = 0;
|
||||
|
||||
return realsize;
|
||||
}
|
||||
|
||||
bool checkSize(CURL* curl, const std::string& url)
|
||||
{
|
||||
curl_off_t dl;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, API_AGENT);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
|
||||
curl_easy_perform(curl);
|
||||
auto res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &dl);
|
||||
if (!res) {
|
||||
s64 freeStorage;
|
||||
if (R_SUCCEEDED(fs::getFreeStorageSD(freeStorage)) && dl * 2.5 > freeStorage) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string mega_id(std::string url)
|
||||
{
|
||||
auto len = url.length();
|
||||
|
||||
if (len < 52)
|
||||
throw std::invalid_argument("Invalid URL.");
|
||||
|
||||
bool old_link = url.find("#!") < len;
|
||||
|
||||
/* Start from the last '/' and add 2 characters if old link format starting with '#!' */
|
||||
auto init_pos = url.find_last_of('/') + (old_link ? 2 : 0) + 1;
|
||||
|
||||
/* End with the last '#' or "!" if its the old link format */
|
||||
auto end_pos = url.find_last_of(old_link ? '!' : '#');
|
||||
|
||||
/* Finally crop the url */
|
||||
std::string id = url.substr(init_pos, end_pos - init_pos);
|
||||
|
||||
if (id.length() != 8)
|
||||
throw std::invalid_argument("Invalid URL ID.");
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
std::string mega_url(std::string url)
|
||||
{
|
||||
std::string id = mega_id(url);
|
||||
|
||||
json request = json::array({{
|
||||
{"a", "g"},
|
||||
{"g", 1},
|
||||
{"p", id},
|
||||
}});
|
||||
|
||||
std::string body = request.dump();
|
||||
std::string output;
|
||||
|
||||
auto curl = curl_easy_init();
|
||||
curl_easy_setopt(curl, CURLOPT_URL, "https://g.api.mega.co.nz/cs");
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &output);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, API_AGENT);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(
|
||||
curl,
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
+[](void* buffer, size_t size, size_t nmemb, void* userp) -> size_t {
|
||||
std::string* output = reinterpret_cast<std::string*>(userp);
|
||||
size_t actual_size = size * nmemb;
|
||||
output->append(reinterpret_cast<char*>(buffer), actual_size);
|
||||
return actual_size;
|
||||
});
|
||||
|
||||
curl_easy_perform(curl);
|
||||
|
||||
json response = json::parse(output);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
s64 freeStorage;
|
||||
s64 fileSize = response[0]["s"];
|
||||
if (R_SUCCEEDED(fs::getFreeStorageSD(freeStorage)) && fileSize * 2.5 > freeStorage)
|
||||
return "";
|
||||
|
||||
return response[0]["g"];
|
||||
}
|
||||
|
||||
std::string mega_node_key(std::string url)
|
||||
{
|
||||
/* Check if old link format */
|
||||
auto len = url.length();
|
||||
|
||||
if (len < 52)
|
||||
throw std::invalid_argument("Invalid URL.");
|
||||
|
||||
bool old_link = url.find("#!") < len;
|
||||
|
||||
/* End with the last '#' or "!" if its the old link format */
|
||||
auto end_pos = url.find_last_of(old_link ? '!' : '#') + 1;
|
||||
|
||||
/* Crop the URL to get the file key */
|
||||
std::string key = url.substr(end_pos, len - end_pos);
|
||||
|
||||
/* Replace URL characters with B64 characters */
|
||||
std::replace(key.begin(), key.end(), '_', '/');
|
||||
std::replace(key.begin(), key.end(), '-', '+');
|
||||
|
||||
/* Add padding */
|
||||
auto key_len = key.length();
|
||||
unsigned pad = 4 - key_len % 4;
|
||||
key.append(pad, '=');
|
||||
|
||||
/* The encoded key should have 44 characters to produce a 32 byte node key */
|
||||
if (key.length() != 44)
|
||||
throw std::invalid_argument("Invalid URL key.");
|
||||
|
||||
std::string decoded(key.size() * 3 / 4, 0);
|
||||
size_t olen = 0;
|
||||
|
||||
mbedtls_base64_decode(
|
||||
reinterpret_cast<unsigned char*>(decoded.data()),
|
||||
decoded.size(),
|
||||
&olen,
|
||||
reinterpret_cast<const unsigned char*>(key.c_str()),
|
||||
key.size());
|
||||
|
||||
/**
|
||||
* The encoded base64 is (usually?) 43 characters long. With padding it goes
|
||||
* to 44. When we calculate the decoded size, we need to allocate 33 bytes.
|
||||
* But the last encoded character is padding, and combined with the last
|
||||
* valid character, it should produce a 32 byte node key.
|
||||
*/
|
||||
decoded.resize(olen);
|
||||
if (decoded.size() != 32)
|
||||
throw std::invalid_argument("Invalid node key.");
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
std::string mega_key(const std::string& node_key)
|
||||
{
|
||||
std::string key(16, 0);
|
||||
|
||||
reinterpret_cast<uint64_t*>(key.data())[0] =
|
||||
reinterpret_cast<const uint64_t*>(node_key.data())[0] ^
|
||||
reinterpret_cast<const uint64_t*>(node_key.data())[2];
|
||||
reinterpret_cast<uint64_t*>(key.data())[1] =
|
||||
reinterpret_cast<const uint64_t*>(node_key.data())[1] ^
|
||||
reinterpret_cast<const uint64_t*>(node_key.data())[3];
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
std::string mega_iv(const std::string& node_key)
|
||||
{
|
||||
std::string iv(16, 0);
|
||||
reinterpret_cast<uint64_t*>(iv.data())[0] =
|
||||
reinterpret_cast<const uint64_t*>(node_key.data())[2];
|
||||
|
||||
return iv;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
long downloadFile(const std::string& url, const std::string& output, int api)
|
||||
{
|
||||
std::vector<std::uint8_t> dummy;
|
||||
return downloadFile(url, dummy, output, api);
|
||||
}
|
||||
|
||||
long downloadFile(const std::string& url, std::vector<std::uint8_t>& res, const std::string& output, int api)
|
||||
{
|
||||
const char* out = output.c_str();
|
||||
CURL* curl = curl_easy_init();
|
||||
ntwrk_struct_t chunk = {0};
|
||||
long status_code;
|
||||
time_old = std::chrono::steady_clock::now();
|
||||
dlold = 0.0f;
|
||||
bool can_download = true;
|
||||
bool is_mega = (url.find("mega.nz") != std::string::npos);
|
||||
std::string real_url = is_mega ? mega_url(url) : url;
|
||||
|
||||
if (is_mega) {
|
||||
std::string node_key = mega_node_key(url);
|
||||
std::string key = mega_key(node_key);
|
||||
std::string iv = mega_iv(node_key);
|
||||
|
||||
chunk.aes = static_cast<Aes128CtrContext*>(malloc(sizeof(Aes128CtrContext)));
|
||||
aes128CtrContextCreate(chunk.aes, key.c_str(), iv.c_str());
|
||||
}
|
||||
|
||||
if (curl) {
|
||||
FILE* fp = fopen(out, "wb");
|
||||
if (fp || *out == 0) {
|
||||
chunk.data = static_cast<u_int8_t*>(malloc(_1MiB));
|
||||
chunk.data_size = _1MiB;
|
||||
chunk.out = fp;
|
||||
|
||||
if (*out != 0) {
|
||||
can_download = is_mega ? !real_url.empty() : checkSize(curl, url);
|
||||
}
|
||||
|
||||
if (can_download) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, real_url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, API_AGENT);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk);
|
||||
|
||||
if (api == OFF) {
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, download_progress);
|
||||
}
|
||||
curl_easy_perform(curl);
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code);
|
||||
|
||||
if (fp && chunk.offset && can_download)
|
||||
fwrite(chunk.data, 1, chunk.offset, fp);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(chunk.out);
|
||||
if (!can_download) {
|
||||
brls::Application::crash("menus/errors/insufficient_storage"_i18n);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(2000000));
|
||||
brls::Application::quit();
|
||||
res = {};
|
||||
}
|
||||
|
||||
if (*out == 0) {
|
||||
res.assign(chunk.data, chunk.data + chunk.offset);
|
||||
}
|
||||
|
||||
free(chunk.data);
|
||||
free(chunk.aes);
|
||||
|
||||
return status_code;
|
||||
}
|
||||
|
||||
std::string fetchTitle(const std::string& url)
|
||||
{
|
||||
CURL* curl_handle;
|
||||
struct MemoryStruct chunk;
|
||||
|
||||
chunk.memory = static_cast<char*>(malloc(1)); /* will be grown as needed by the realloc above */
|
||||
chunk.size = 0; /* no data at this point */
|
||||
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
curl_handle = curl_easy_init();
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback2);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)&chunk);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, API_AGENT);
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_perform(curl_handle);
|
||||
|
||||
/* check for errors */
|
||||
std::string ver = "-1";
|
||||
std::string s = std::string(chunk.memory);
|
||||
std::regex rgx("<title>.+</title>");
|
||||
std::smatch match;
|
||||
if (std::regex_search(s, match, rgx)) {
|
||||
//ver = std::stoi(match[0]);
|
||||
//std::cout << match[0].str().substr(match[0].str().find(" ") + 1, 6) << std::endl;
|
||||
ver = match[0].str().substr(match[0].str().find(" ") + 1, 5);
|
||||
}
|
||||
curl_easy_cleanup(curl_handle);
|
||||
free(chunk.memory);
|
||||
|
||||
curl_global_cleanup();
|
||||
|
||||
return ver;
|
||||
}
|
||||
|
||||
long downloadPage(const std::string& url, std::string& res, const std::vector<std::string>& headers, const std::string& body)
|
||||
{
|
||||
CURL* curl_handle;
|
||||
struct MemoryStruct chunk;
|
||||
struct curl_slist* list = NULL;
|
||||
long status_code;
|
||||
|
||||
chunk.memory = static_cast<char*>(malloc(1)); /* will be grown as needed by the realloc above */
|
||||
chunk.size = 0; /* no data at this point */
|
||||
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
curl_handle = curl_easy_init();
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
|
||||
if (!headers.empty()) {
|
||||
for (auto& h : headers) {
|
||||
list = curl_slist_append(list, h.c_str());
|
||||
}
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, list);
|
||||
}
|
||||
if (body != "") {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, body.c_str());
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback2);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)&chunk);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, API_AGENT);
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_perform(curl_handle);
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &status_code);
|
||||
curl_easy_cleanup(curl_handle);
|
||||
res = std::string(chunk.memory);
|
||||
free(chunk.memory);
|
||||
|
||||
curl_global_cleanup();
|
||||
return status_code;
|
||||
}
|
||||
|
||||
long getRequest(const std::string& url, nlohmann::ordered_json& res, const std::vector<std::string>& headers, const std::string& body)
|
||||
{
|
||||
std::string request;
|
||||
long status_code = downloadPage(url, request, headers, body);
|
||||
|
||||
if (json::accept(request))
|
||||
res = nlohmann::ordered_json::parse(request);
|
||||
else
|
||||
res = nlohmann::ordered_json::object();
|
||||
|
||||
return status_code;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> getLinks(const std::string& url)
|
||||
{
|
||||
nlohmann::ordered_json request;
|
||||
getRequest(url, request);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> res;
|
||||
for (auto it = request.begin(); it != request.end(); ++it) {
|
||||
res.push_back(std::make_pair(it.key(), it.value()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> getLinksFromJson(const nlohmann::ordered_json& json_object)
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> res;
|
||||
for (auto it = json_object.begin(); it != json_object.end(); ++it) {
|
||||
res.push_back(std::make_pair(it.key(), it.value()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace download
|
||||
385
source/download_cheats_page.cpp
Normal file
385
source/download_cheats_page.cpp
Normal file
@@ -0,0 +1,385 @@
|
||||
#include "download_cheats_page.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "current_cfw.hpp"
|
||||
#include "download.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
namespace cheats_util {
|
||||
u32 GetVersion(uint64_t title_id)
|
||||
{
|
||||
u32 res = 0;
|
||||
NsApplicationContentMetaStatus* MetaSatus = new NsApplicationContentMetaStatus[100U];
|
||||
s32 out;
|
||||
nsListApplicationContentMetaStatus(title_id, 0, MetaSatus, 100, &out);
|
||||
for (int i = 0; i < out; i++) {
|
||||
if (res < MetaSatus[i].version) res = MetaSatus[i].version;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void ShowCheatSheet(u64 tid, const std::string& bid, const std::string& name)
|
||||
{
|
||||
std::string path = fmt::format("{}{}/cheats/{}.txt", util::getContentsPath(), util::formatApplicationId(tid), bid);
|
||||
brls::AppletFrame* appView = new brls::AppletFrame(true, true);
|
||||
brls::List* cheatsList = new brls::List();
|
||||
if (std::filesystem::exists(path) && CreateCheatList(path, &cheatsList)) {
|
||||
appView->setContentView(cheatsList);
|
||||
appView->registerAction("menus/cheats/delete_file"_i18n, brls::Key::X, [tid, bid] {
|
||||
DeleteCheats(tid, bid);
|
||||
util::showDialogBoxInfo("menus/common/all_done"_i18n);
|
||||
return true;
|
||||
});
|
||||
brls::PopupFrame::open(name, appView, "");
|
||||
}
|
||||
else {
|
||||
util::showDialogBoxInfo("menus/cheats/not_found"_i18n);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowCheatFiles(u64 tid, const std::string& name)
|
||||
{
|
||||
std::string path = util::getContentsPath();
|
||||
path += util::formatApplicationId(tid) + "/cheats/";
|
||||
|
||||
brls::TabFrame* appView = new brls::TabFrame();
|
||||
bool is_populated = false;
|
||||
if (std::filesystem::exists(path)) {
|
||||
for (const auto& cheatFile : std::filesystem::directory_iterator(path)) {
|
||||
brls::List* cheatsList = new brls::List();
|
||||
is_populated |= CreateCheatList(cheatFile.path(), &cheatsList);
|
||||
if (is_populated) {
|
||||
appView->addTab(util::upperCase(cheatFile.path().stem()), cheatsList);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_populated) {
|
||||
brls::PopupFrame::open(name, appView, "");
|
||||
}
|
||||
else {
|
||||
util::showDialogBoxInfo("menus/cheats/not_found"_i18n);
|
||||
}
|
||||
}
|
||||
|
||||
bool CreateCheatList(const std::filesystem::path& path, brls::List** cheatsList)
|
||||
{
|
||||
bool res = false;
|
||||
if (extract::isBID(path.filename().stem())) {
|
||||
(*cheatsList)->addView(new brls::Label(brls::LabelStyle::DESCRIPTION, fmt::format("menus/cheats/cheatfile_label"_i18n, path.filename().string()), true));
|
||||
|
||||
std::string str;
|
||||
std::regex cheats_expr(R"(\[.+\]|\{.+\})");
|
||||
std::ifstream in(path);
|
||||
if (in) {
|
||||
while (std::getline(in, str)) {
|
||||
if (str.size() > 0) {
|
||||
if (std::regex_search(str, cheats_expr)) {
|
||||
(*cheatsList)->addView(new brls::ListItem(str));
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void DeleteCheats(u64 tid, const std::string& bid)
|
||||
{
|
||||
std::filesystem::remove(fmt::format("{}{:016X}/cheats/{}.txt", util::getContentsPath(), tid, bid));
|
||||
}
|
||||
|
||||
} // namespace cheats_util
|
||||
|
||||
DownloadCheatsPage::DownloadCheatsPage(uint64_t tid, const std::string& name) : AppletFrame(true, true), tid(tid), name(name)
|
||||
{
|
||||
this->list = new brls::List();
|
||||
this->version = cheats_util::GetVersion(this->tid);
|
||||
this->GetBuildID();
|
||||
this->setTitle(this->name);
|
||||
this->setFooterText("v" + std::to_string(this->version / 0x10000));
|
||||
this->brls::AppletFrame::registerAction("menus/cheats/show_existing"_i18n, brls::Key::X, [this] {
|
||||
cheats_util::ShowCheatSheet(this->tid, this->bid, this->name);
|
||||
return true;
|
||||
});
|
||||
this->rebuildHints();
|
||||
}
|
||||
|
||||
void DownloadCheatsPage::GetBuildID()
|
||||
{
|
||||
if (util::isApplet() && CurrentCfw::running_cfw == CFW::ams)
|
||||
this->GetBuildIDFromDmnt();
|
||||
if (this->bid == "")
|
||||
this->GetBuildIDFromFile();
|
||||
}
|
||||
|
||||
void DownloadCheatsPage::GetBuildIDFromDmnt()
|
||||
{
|
||||
static Service g_dmntchtSrv;
|
||||
DmntCheatProcessMetadata metadata;
|
||||
smGetService(&g_dmntchtSrv, "dmnt:cht");
|
||||
serviceDispatch(&g_dmntchtSrv, 65003);
|
||||
serviceDispatchOut(&g_dmntchtSrv, 65002, metadata);
|
||||
serviceClose(&g_dmntchtSrv);
|
||||
if (metadata.title_id == this->tid) {
|
||||
u64 buildID = 0;
|
||||
memcpy(&buildID, metadata.main_nso_build_id, sizeof(u64));
|
||||
this->bid = fmt::format("{:016X}", __builtin_bswap64(buildID));
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadCheatsPage::GetBuildIDFromFile()
|
||||
{
|
||||
nlohmann::ordered_json versions_json;
|
||||
download::getRequest(VERSIONS_DIRECTORY + util::formatApplicationId(this->tid) + ".json", versions_json);
|
||||
|
||||
std::string version_str = std::to_string(this->version);
|
||||
if (versions_json.find(version_str) != versions_json.end()) {
|
||||
this->bid = versions_json.at(version_str).get<std::string>();
|
||||
}
|
||||
else {
|
||||
this->bid = "";
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadCheatsPage::WriteCheats(const std::string& cheatContent)
|
||||
{
|
||||
std::string path = util::getContentsPath() + util::formatApplicationId(this->tid) + "/cheats/";
|
||||
fs::createTree(path);
|
||||
std::ofstream cheatFile(path + this->bid + ".txt", std::ios::app);
|
||||
cheatFile << "\n\n"
|
||||
<< cheatContent;
|
||||
}
|
||||
|
||||
void DownloadCheatsPage::AddCheatsfileListItem()
|
||||
{
|
||||
brls::ListItem* item = new brls::ListItem("menus/cheats/show_cheat_files"_i18n);
|
||||
item->getClickEvent()->subscribe([this](brls::View* view) {
|
||||
cheats_util::ShowCheatFiles(this->tid, this->name);
|
||||
});
|
||||
this->list->addView(item);
|
||||
}
|
||||
|
||||
void DownloadCheatsPage::ShowBidNotFound()
|
||||
{
|
||||
brls::Label* label = new brls::Label(
|
||||
brls::LabelStyle::REGULAR,
|
||||
"menus/cheats/bid_not_found"_i18n,
|
||||
true);
|
||||
this->list->addView(label);
|
||||
}
|
||||
|
||||
void DownloadCheatsPage::ShowCheatsNotFound(const std::string& versionsWithCheats)
|
||||
{
|
||||
brls::Label* label = new brls::Label(
|
||||
brls::LabelStyle::REGULAR,
|
||||
"menus/cheats/cheats_not_found"_i18n + (!versionsWithCheats.empty() ? "\n" + fmt::format("menus/cheats/old_cheats_found"_i18n, versionsWithCheats) : ""),
|
||||
true);
|
||||
this->list->addView(label);
|
||||
}
|
||||
|
||||
DownloadCheatsPage_CheatSlips::DownloadCheatsPage_CheatSlips(uint64_t tid, const std::string& name) : DownloadCheatsPage(tid, name)
|
||||
{
|
||||
this->label = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/cheats/cheatslips_dl"_i18n +
|
||||
"\n\uE016 Title ID: " + util::formatApplicationId(this->tid) +
|
||||
"\n\uE016 Build ID: " + this->bid,
|
||||
true);
|
||||
this->list->addView(this->label);
|
||||
|
||||
if (this->bid != "") {
|
||||
std::vector<std::string> headers = {"accept: application/json"};
|
||||
nlohmann::ordered_json cheatsInfo;
|
||||
download::getRequest(CHEATSLIPS_CHEATS_URL + util::formatApplicationId(this->tid) + "/" + this->bid, cheatsInfo, headers);
|
||||
if (cheatsInfo.find("cheats") != cheatsInfo.end()) {
|
||||
for (const auto& p : cheatsInfo.at("cheats").items()) {
|
||||
json cheat = p.value();
|
||||
listItem = new brls::ToggleListItem(GetCheatsTitle(cheat), 0, "", "\uE016", "o");
|
||||
this->listItem->registerAction("menus/cheats/cheatslips_see_more"_i18n, brls::Key::Y, [this, cheat] {
|
||||
if (cheat.find("titles") != cheat.end()) {
|
||||
ShowCheatsContent(cheat.at("titles"));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this->toggles.push_back(std::make_pair(listItem, cheat.at("id")));
|
||||
list->addView(listItem);
|
||||
}
|
||||
if (this->list->getViewsCount() > 1)
|
||||
this->list->addView(new brls::ListItemGroupSpacing(true));
|
||||
}
|
||||
else {
|
||||
ShowCheatsNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
ShowBidNotFound();
|
||||
}
|
||||
|
||||
this->list->registerAction((this->bid != "") ? "menus/cheats/cheatslips_dl_cheats"_i18n : "brls/hints/back"_i18n, brls::Key::B, [this] {
|
||||
std::vector<int> ids;
|
||||
for (auto& e : toggles) {
|
||||
if (e.first->getToggleState()) {
|
||||
ids.push_back(e.second);
|
||||
}
|
||||
}
|
||||
int error = 0;
|
||||
if (!ids.empty()) {
|
||||
json token;
|
||||
std::ifstream tokenFile(TOKEN_PATH);
|
||||
tokenFile >> token;
|
||||
tokenFile.close();
|
||||
std::vector<std::string> headers = {"accept: application/json"};
|
||||
if (token.find("token") != token.end()) {
|
||||
headers.push_back("X-API-TOKEN: " + token.at("token").get<std::string>());
|
||||
}
|
||||
nlohmann::ordered_json cheatsInfo;
|
||||
download::getRequest("https://www.cheatslips.com/api/v1/cheats/" + util::formatApplicationId(this->tid) + "/" + this->bid, cheatsInfo, headers);
|
||||
if (cheatsInfo.find("cheats") != cheatsInfo.end()) {
|
||||
for (const auto& p : cheatsInfo.at("cheats").items()) {
|
||||
if (std::find(ids.begin(), ids.end(), p.value().at("id")) != ids.end()) {
|
||||
if (p.value().at("content").get<std::string>() == "Quota exceeded for today !") {
|
||||
error = 1;
|
||||
}
|
||||
else {
|
||||
WriteCheats(p.value().at("content"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
error = 2;
|
||||
}
|
||||
|
||||
if (error != 0) {
|
||||
std::string error_message;
|
||||
switch (error) {
|
||||
case 1:
|
||||
error_message = "menus/cheats/quota"_i18n;
|
||||
break;
|
||||
case 2:
|
||||
error_message = "menus/cheats/cheatslips_error"_i18n;
|
||||
break;
|
||||
}
|
||||
util::showDialogBoxInfo(error_message);
|
||||
}
|
||||
}
|
||||
if (error == 0) {
|
||||
/* brls::Dialog* dialog = new brls::Dialog("menus/cheatslips_success"_i18n);
|
||||
bool dialogResult = false;
|
||||
bool result = false;
|
||||
brls::GenericEvent::Callback callback = [dialog, &dialogResult](brls::View* view) {
|
||||
dialogResult = true;
|
||||
dialog->close();
|
||||
};
|
||||
dialog->addButton("menus/common/ok"_i18n, callback);
|
||||
dialog->setCancelable(true);
|
||||
dialog->open();
|
||||
while(result == false){
|
||||
usleep(1);
|
||||
result = dialogResult;
|
||||
}
|
||||
dialogResult = false; */
|
||||
brls::Application::popView();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this->AddCheatsfileListItem();
|
||||
this->setContentView(list);
|
||||
}
|
||||
|
||||
std::string DownloadCheatsPage_CheatSlips::GetCheatsTitle(json cheat)
|
||||
{
|
||||
std::string res = "";
|
||||
if (cheat.find("titles") != cheat.end()) {
|
||||
for (auto& p : cheat.at("titles")) {
|
||||
res += fmt::format("[{}] - ", p.get<std::string>());
|
||||
}
|
||||
}
|
||||
if (res != "") res = res.substr(0, res.size() - 3);
|
||||
return res;
|
||||
}
|
||||
|
||||
void DownloadCheatsPage_CheatSlips::ShowCheatsContent(nlohmann::ordered_json titles)
|
||||
{
|
||||
brls::AppletFrame* appView = new brls::AppletFrame(true, true);
|
||||
brls::List* list = new brls::List();
|
||||
brls::ListItem* titlesItem;
|
||||
for (auto& p : titles) {
|
||||
titlesItem = new brls::ListItem(p.get<std::string>());
|
||||
titlesItem->registerAction("", brls::Key::A, [this] {
|
||||
return true;
|
||||
});
|
||||
list->addView(titlesItem);
|
||||
}
|
||||
appView->setContentView(list);
|
||||
brls::PopupFrame::open("menus/cheats/sheet_content"_i18n, appView, "", "");
|
||||
}
|
||||
|
||||
DownloadCheatsPage_Github::DownloadCheatsPage_Github(uint64_t tid, const std::string& name) : DownloadCheatsPage(tid, name)
|
||||
{
|
||||
}
|
||||
|
||||
void DownloadCheatsPage_Github::PopulateList(uint64_t tid, const std::string& name)
|
||||
{
|
||||
this->label = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/cheats/bid_tid_info"_i18n +
|
||||
"\n\uE016 Title ID: " + util::formatApplicationId(this->tid) +
|
||||
"\n\uE016 Build ID: " + this->bid,
|
||||
true);
|
||||
this->list->addView(label);
|
||||
|
||||
if (this->bid != "") {
|
||||
nlohmann::ordered_json cheatsJson;
|
||||
download::getRequest(this->get_url() + util::formatApplicationId(this->tid) + ".json", cheatsJson);
|
||||
if (cheatsJson.find(this->bid) != cheatsJson.end()) {
|
||||
for (auto& [key, val] : cheatsJson[this->bid].items()) {
|
||||
auto title = key;
|
||||
auto content = val;
|
||||
this->listItem = new brls::ListItem(title);
|
||||
listItem->registerAction("menus/cheats/dl_cheatcodes"_i18n, brls::Key::A, [this, content, title] {
|
||||
WriteCheats(content);
|
||||
util::showDialogBoxInfo(fmt::format("menus/cheats/dl_successful"_i18n, title));
|
||||
return true;
|
||||
});
|
||||
this->list->addView(listItem);
|
||||
}
|
||||
this->list->addView(new brls::ListItemGroupSpacing(true));
|
||||
}
|
||||
else {
|
||||
std::string versionsWithCheats;
|
||||
for (auto& [key, val] : cheatsJson.items()) {
|
||||
if (key != "attribution")
|
||||
versionsWithCheats += key + " ";
|
||||
}
|
||||
ShowCheatsNotFound(versionsWithCheats);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
ShowBidNotFound();
|
||||
}
|
||||
this->AddCheatsfileListItem();
|
||||
this->setContentView(this->list);
|
||||
}
|
||||
|
||||
DownloadCheatsPage_Gbatemp::DownloadCheatsPage_Gbatemp(uint64_t tid, const std::string& name) : DownloadCheatsPage_Github(tid, name)
|
||||
{
|
||||
this->PopulateList(tid, name);
|
||||
}
|
||||
|
||||
DownloadCheatsPage_Gfx::DownloadCheatsPage_Gfx(uint64_t tid, const std::string& name) : DownloadCheatsPage_Github(tid, name)
|
||||
{
|
||||
this->PopulateList(tid, name);
|
||||
}
|
||||
57
source/download_payload_page.cpp
Normal file
57
source/download_payload_page.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "download_payload_page.hpp"
|
||||
|
||||
#include "confirm_page.hpp"
|
||||
#include "download.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "worker_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
DownloadPayloadPage::DownloadPayloadPage(const nlohmann::ordered_json& payloads) : AppletFrame(true, true)
|
||||
{
|
||||
this->setTitle("menus/payloads/dl_payloads"_i18n);
|
||||
list = new brls::List();
|
||||
label = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/payloads/select"_i18n + std::string(BOOTLOADER_PL_PATH) + ".",
|
||||
true);
|
||||
list->addView(label);
|
||||
|
||||
auto links = download::getLinksFromJson(payloads);
|
||||
if (links.size()) {
|
||||
for (const auto& link : links) {
|
||||
std::string url = link.second;
|
||||
std::string path = std::string(BOOTLOADER_PL_PATH) + link.first;
|
||||
std::string text("menus/common/download"_i18n + link.first + "menus/common/from"_i18n + url);
|
||||
listItem = new brls::ListItem(link.first);
|
||||
listItem->getClickEvent()->subscribe([text, url, path](brls::View* view) {
|
||||
fs::createTree(BOOTLOADER_PL_PATH);
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/tools/getting_payload"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage(stagedFrame, text));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, [url, path]() { download::downloadFile(url, path, OFF); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_Done(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
list->addView(listItem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
notFound = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/main/links_not_found"_i18n,
|
||||
true);
|
||||
notFound->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
list->addView(notFound);
|
||||
brls::ListItem* back = new brls::ListItem("menus/common/back"_i18n);
|
||||
back->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::Application::popView();
|
||||
});
|
||||
list->addView(back);
|
||||
}
|
||||
this->setContentView(list);
|
||||
}
|
||||
85
source/exclude_page.cpp
Normal file
85
source/exclude_page.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "exclude_page.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "extract.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
ExcludePage::ExcludePage() : AppletFrame(true, true)
|
||||
{
|
||||
this->setTitle("menus/cheats/exclude_titles"_i18n);
|
||||
list = new brls::List();
|
||||
label = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/cheats/exclude_titles_desc"_i18n,
|
||||
true);
|
||||
list->addView(label);
|
||||
|
||||
NsApplicationRecord record;
|
||||
uint64_t tid;
|
||||
NsApplicationControlData controlData;
|
||||
NacpLanguageEntry* langEntry = NULL;
|
||||
|
||||
Result rc;
|
||||
size_t i = 0;
|
||||
int recordCount = 0;
|
||||
size_t controlSize = 0;
|
||||
|
||||
titles = fs::readLineByLine(CHEATS_EXCLUDE);
|
||||
|
||||
while (true) {
|
||||
rc = nsListApplicationRecord(&record, sizeof(record), i, &recordCount);
|
||||
if (R_FAILED(rc)) break;
|
||||
|
||||
if (recordCount <= 0)
|
||||
break;
|
||||
|
||||
tid = record.application_id;
|
||||
rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, &controlData, sizeof(controlData), &controlSize);
|
||||
if (R_FAILED(rc)) break;
|
||||
rc = nacpGetLanguageEntry(&controlData.nacp, &langEntry);
|
||||
if (R_FAILED(rc)) break;
|
||||
if (!langEntry->name) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
util::app* app = (util::app*)malloc(sizeof(util::app));
|
||||
app->tid = tid;
|
||||
|
||||
memset(app->name, 0, sizeof(app->name));
|
||||
strncpy(app->name, langEntry->name, sizeof(app->name) - 1);
|
||||
|
||||
memcpy(app->icon, controlData.icon, sizeof(app->icon));
|
||||
|
||||
brls::ToggleListItem* listItem;
|
||||
if (titles.find(util::formatApplicationId(tid)) != titles.end())
|
||||
listItem = new brls::ToggleListItem(std::string(app->name), 0);
|
||||
else
|
||||
listItem = new brls::ToggleListItem(std::string(app->name), 1);
|
||||
|
||||
listItem->setThumbnail(app->icon, sizeof(app->icon));
|
||||
items.insert(std::make_pair(listItem, util::formatApplicationId(app->tid)));
|
||||
list->addView(listItem);
|
||||
i++;
|
||||
}
|
||||
|
||||
list->registerAction("menus/cheats/exclude_titles_save"_i18n, brls::Key::B, [this] {
|
||||
std::set<std::string> exclude;
|
||||
for (const auto& item : items) {
|
||||
if (!item.first->getToggleState()) {
|
||||
exclude.insert(item.second);
|
||||
}
|
||||
}
|
||||
extract::writeTitlesToFile(exclude, CHEATS_EXCLUDE);
|
||||
brls::Application::popView();
|
||||
return true;
|
||||
});
|
||||
this->setContentView(list);
|
||||
}
|
||||
354
source/extract.cpp
Normal file
354
source/extract.cpp
Normal file
@@ -0,0 +1,354 @@
|
||||
#include "extract.hpp"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <minizip/unzip.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <ranges>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "current_cfw.hpp"
|
||||
#include "download.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "main_frame.hpp"
|
||||
#include "progress_event.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
constexpr size_t WRITE_BUFFER_SIZE = 0x10000;
|
||||
|
||||
namespace extract {
|
||||
|
||||
namespace {
|
||||
bool caselessCompare(const std::string& a, const std::string& b)
|
||||
{
|
||||
return strcasecmp(a.c_str(), b.c_str()) == 0;
|
||||
}
|
||||
|
||||
s64 getUncompressedSize(const std::string& archivePath)
|
||||
{
|
||||
s64 size = 0;
|
||||
unzFile zfile = unzOpen(archivePath.c_str());
|
||||
unz_global_info gi;
|
||||
unzGetGlobalInfo(zfile, &gi);
|
||||
for (uLong i = 0; i < gi.number_entry; ++i) {
|
||||
unz_file_info fi;
|
||||
unzOpenCurrentFile(zfile);
|
||||
unzGetCurrentFileInfo(zfile, &fi, NULL, 0, NULL, 0, NULL, 0);
|
||||
size += fi.uncompressed_size;
|
||||
unzCloseCurrentFile(zfile);
|
||||
unzGoToNextFile(zfile);
|
||||
}
|
||||
unzClose(zfile);
|
||||
return size; // in B
|
||||
}
|
||||
|
||||
void ensureAvailableStorage(const std::string& archivePath)
|
||||
{
|
||||
s64 uncompressedSize = getUncompressedSize(archivePath);
|
||||
s64 freeStorage;
|
||||
|
||||
if (R_SUCCEEDED(fs::getFreeStorageSD(freeStorage))) {
|
||||
brls::Logger::info("Uncompressed size of archive {}: {}. Available: {}", archivePath, uncompressedSize, freeStorage);
|
||||
if (uncompressedSize * 1.1 > freeStorage) {
|
||||
brls::Application::crash("menus/errors/insufficient_storage"_i18n);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(2000000));
|
||||
brls::Application::quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void extractEntry(std::string filename, unzFile& zfile, bool forceCreateTree = false)
|
||||
{
|
||||
if (filename.back() == '/') {
|
||||
fs::createTree(filename);
|
||||
return;
|
||||
}
|
||||
if (forceCreateTree) {
|
||||
fs::createTree(filename);
|
||||
}
|
||||
void* buf = malloc(WRITE_BUFFER_SIZE);
|
||||
FILE* outfile;
|
||||
outfile = fopen(filename.c_str(), "wb");
|
||||
for (int j = unzReadCurrentFile(zfile, buf, WRITE_BUFFER_SIZE); j > 0; j = unzReadCurrentFile(zfile, buf, WRITE_BUFFER_SIZE)) {
|
||||
fwrite(buf, 1, j, outfile);
|
||||
}
|
||||
free(buf);
|
||||
fclose(outfile);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void extract(const std::string& archivePath, const std::string& workingPath, bool preserveInis, std::function<void()> func)
|
||||
{
|
||||
ensureAvailableStorage(archivePath);
|
||||
|
||||
unzFile zfile = unzOpen(archivePath.c_str());
|
||||
unz_global_info gi;
|
||||
unzGetGlobalInfo(zfile, &gi);
|
||||
|
||||
ProgressEvent::instance().setTotalSteps(gi.number_entry);
|
||||
ProgressEvent::instance().setStep(0);
|
||||
|
||||
std::set<std::string> ignoreList = fs::readLineByLine(FILES_IGNORE);
|
||||
std::string appPath = util::getAppPath();
|
||||
|
||||
for (uLong i = 0; i < gi.number_entry; ++i) {
|
||||
char szFilename[0x301] = "";
|
||||
unzOpenCurrentFile(zfile);
|
||||
unzGetCurrentFileInfo(zfile, NULL, szFilename, sizeof(szFilename), NULL, 0, NULL, 0);
|
||||
std::string filename = workingPath + szFilename;
|
||||
|
||||
if (ProgressEvent::instance().getInterupt()) {
|
||||
unzCloseCurrentFile(zfile);
|
||||
break;
|
||||
}
|
||||
if (appPath != filename) {
|
||||
if ((preserveInis == true && filename.substr(filename.length() - 4) == ".ini") || std::find_if(ignoreList.begin(), ignoreList.end(), [&filename](std::string ignored) {
|
||||
u8 res = filename.find(ignored);
|
||||
return (res == 0 || res == 1); }) != ignoreList.end()) {
|
||||
if (!std::filesystem::exists(filename)) {
|
||||
extractEntry(filename, zfile);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((filename == "/atmosphere/package3") || (filename == "/atmosphere/stratosphere.romfs")) {
|
||||
extractEntry(filename + ".aio", zfile);
|
||||
}
|
||||
else {
|
||||
extractEntry(filename, zfile);
|
||||
if (filename.substr(0, 14) == "/hekate_ctcaer") {
|
||||
fs::copyFile(filename, UPDATE_BIN_PATH);
|
||||
if (CurrentCfw::running_cfw == CFW::ams && util::showDialogBoxBlocking(fmt::format("menus/utils/set_hekate_reboot_payload"_i18n, UPDATE_BIN_PATH, REBOOT_PAYLOAD_PATH), "menus/common/yes"_i18n, "menus/common/no"_i18n) == 0) {
|
||||
fs::copyFile(UPDATE_BIN_PATH, REBOOT_PAYLOAD_PATH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ProgressEvent::instance().setStep(i);
|
||||
unzCloseCurrentFile(zfile);
|
||||
unzGoToNextFile(zfile);
|
||||
}
|
||||
unzClose(zfile);
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
|
||||
std::vector<std::string> getInstalledTitlesNs()
|
||||
{
|
||||
std::vector<std::string> titles;
|
||||
|
||||
NsApplicationRecord* records = new NsApplicationRecord[MaxTitleCount]();
|
||||
NsApplicationControlData* controlData = NULL;
|
||||
|
||||
s32 recordCount = 0;
|
||||
u64 controlSize = 0;
|
||||
|
||||
if (R_SUCCEEDED(nsListApplicationRecord(records, MaxTitleCount, 0, &recordCount))) {
|
||||
for (s32 i = 0; i < recordCount; i++) {
|
||||
controlSize = 0;
|
||||
free(controlData);
|
||||
controlData = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData));
|
||||
if (controlData == NULL) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
memset(controlData, 0, sizeof(NsApplicationControlData));
|
||||
}
|
||||
|
||||
if (R_FAILED(nsGetApplicationControlData(NsApplicationControlSource_Storage, records[i].application_id, controlData, sizeof(NsApplicationControlData), &controlSize))) continue;
|
||||
|
||||
if (controlSize < sizeof(controlData->nacp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
titles.push_back(util::formatApplicationId(records[i].application_id));
|
||||
}
|
||||
free(controlData);
|
||||
}
|
||||
delete[] records;
|
||||
std::sort(titles.begin(), titles.end());
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::vector<std::string> excludeTitles(const std::string& path, const std::vector<std::string>& listedTitles)
|
||||
{
|
||||
std::vector<std::string> titles;
|
||||
std::ifstream file(path);
|
||||
std::string name;
|
||||
|
||||
if (file.is_open()) {
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
std::transform(line.begin(), line.end(), line.begin(), ::toupper);
|
||||
for (size_t i = 0; i < listedTitles.size(); i++) {
|
||||
if (line == listedTitles[i]) {
|
||||
titles.push_back(line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(titles.begin(), titles.end());
|
||||
|
||||
std::vector<std::string> diff;
|
||||
std::set_difference(listedTitles.begin(), listedTitles.end(), titles.begin(), titles.end(),
|
||||
std::inserter(diff, diff.begin()));
|
||||
return diff;
|
||||
}
|
||||
|
||||
int computeOffset(CFW cfw)
|
||||
{
|
||||
switch (cfw) {
|
||||
case CFW::ams:
|
||||
std::filesystem::create_directory(AMS_PATH);
|
||||
std::filesystem::create_directory(AMS_CONTENTS);
|
||||
chdir(AMS_PATH);
|
||||
return std::string(CONTENTS_PATH).length();
|
||||
break;
|
||||
case CFW::rnx:
|
||||
std::filesystem::create_directory(REINX_PATH);
|
||||
std::filesystem::create_directory(REINX_CONTENTS);
|
||||
chdir(REINX_PATH);
|
||||
return std::string(CONTENTS_PATH).length();
|
||||
break;
|
||||
case CFW::sxos:
|
||||
std::filesystem::create_directory(SXOS_PATH);
|
||||
std::filesystem::create_directory(SXOS_TITLES);
|
||||
chdir(SXOS_PATH);
|
||||
return std::string(TITLES_PATH).length();
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void extractCheats(const std::string& archivePath, const std::vector<std::string>& titles, CFW cfw, const std::string& version, bool extractAll)
|
||||
{
|
||||
ensureAvailableStorage(archivePath);
|
||||
|
||||
unzFile zfile = unzOpen(archivePath.c_str());
|
||||
unz_global_info gi;
|
||||
unzGetGlobalInfo(zfile, &gi);
|
||||
|
||||
ProgressEvent::instance().setTotalSteps(gi.number_entry);
|
||||
ProgressEvent::instance().setStep(0);
|
||||
|
||||
int offset = computeOffset(cfw);
|
||||
|
||||
for (uLong i = 0; i < gi.number_entry; ++i) {
|
||||
char szFilename[0x301] = "";
|
||||
unzOpenCurrentFile(zfile);
|
||||
unzGetCurrentFileInfo(zfile, NULL, szFilename, sizeof(szFilename), NULL, 0, NULL, 0);
|
||||
std::string filename = szFilename;
|
||||
|
||||
if (ProgressEvent::instance().getInterupt()) {
|
||||
unzCloseCurrentFile(zfile);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((int)filename.size() > offset + 16 + 7 && caselessCompare(filename.substr(offset + 16, 7), "/cheats")) {
|
||||
if (extractAll) {
|
||||
extractEntry(filename, zfile);
|
||||
}
|
||||
else {
|
||||
if (std::find_if(titles.begin(), titles.end(), [&filename, offset](std::string title) {
|
||||
return caselessCompare((title.substr(0, 13)), filename.substr(offset, 13));
|
||||
}) != titles.end()) {
|
||||
extractEntry(filename, zfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProgressEvent::instance().setStep(i);
|
||||
unzCloseCurrentFile(zfile);
|
||||
unzGoToNextFile(zfile);
|
||||
}
|
||||
unzClose(zfile);
|
||||
if (version != "offline" && version != "") {
|
||||
util::saveToFile(version, CHEATS_VERSION);
|
||||
}
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
|
||||
void extractAllCheats(const std::string& archivePath, CFW cfw, const std::string& version)
|
||||
{
|
||||
extractCheats(archivePath, {}, cfw, version, true);
|
||||
}
|
||||
|
||||
bool isBID(const std::string& bid)
|
||||
{
|
||||
for (char const& c : bid) {
|
||||
if (!isxdigit(c)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeTitlesToFile(const std::set<std::string>& titles, const std::string& path)
|
||||
{
|
||||
std::ofstream updatedTitlesFile;
|
||||
std::set<std::string>::iterator it = titles.begin();
|
||||
updatedTitlesFile.open(path, std::ofstream::out | std::ofstream::trunc);
|
||||
if (updatedTitlesFile.is_open()) {
|
||||
while (it != titles.end()) {
|
||||
updatedTitlesFile << (*it) << std::endl;
|
||||
it++;
|
||||
}
|
||||
updatedTitlesFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
void removeCheats()
|
||||
{
|
||||
std::string path = util::getContentsPath();
|
||||
ProgressEvent::instance().setTotalSteps(std::distance(std::filesystem::directory_iterator(path), std::filesystem::directory_iterator()) + 1);
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path)) {
|
||||
if (ProgressEvent::instance().getInterupt()) {
|
||||
break;
|
||||
}
|
||||
removeCheatsDirectory(entry.path().string());
|
||||
ProgressEvent::instance().incrementStep(1);
|
||||
}
|
||||
std::filesystem::remove(CHEATS_VERSION);
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
|
||||
void removeOrphanedCheats()
|
||||
{
|
||||
auto path = util::getContentsPath();
|
||||
std::vector<std::string> titles = getInstalledTitlesNs();
|
||||
ProgressEvent::instance().setTotalSteps(std::distance(std::filesystem::directory_iterator(path), std::filesystem::directory_iterator()) + 1);
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path)) {
|
||||
if (ProgressEvent::instance().getInterupt()) {
|
||||
break;
|
||||
}
|
||||
if (std::find_if(titles.begin(), titles.end(), [&entry](std::string title) {
|
||||
return caselessCompare(entry.path().filename(), title);
|
||||
}) == titles.end()) {
|
||||
removeCheatsDirectory(entry.path().string());
|
||||
}
|
||||
ProgressEvent::instance().incrementStep(1);
|
||||
}
|
||||
std::filesystem::remove(CHEATS_VERSION);
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
|
||||
bool removeCheatsDirectory(const std::string& entry)
|
||||
{
|
||||
bool res = true;
|
||||
std::string cheatsPath = fmt::format("{}/cheats", entry);
|
||||
if (std::filesystem::exists(cheatsPath)) res &= fs::removeDir(cheatsPath);
|
||||
if (std::filesystem::is_empty(entry)) res &= fs::removeDir(entry);
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace extract
|
||||
129
source/fs.cpp
Normal file
129
source/fs.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "fs.hpp"
|
||||
|
||||
#include <borealis.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "constants.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
namespace fs {
|
||||
std::vector<std::string> splitString(const std::string& s, char delimiter)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
std::istringstream tokenStream(s);
|
||||
while (std::getline(tokenStream, token, delimiter)) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
bool removeDir(const std::string& path)
|
||||
{
|
||||
Result ret = 0;
|
||||
FsFileSystem* fs = fsdevGetDeviceFileSystem("sdmc");
|
||||
if (R_FAILED(ret = fsFsDeleteDirectoryRecursively(fs, path.c_str())))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
nlohmann::ordered_json parseJsonFile(const std::string& path)
|
||||
{
|
||||
std::ifstream file(path);
|
||||
|
||||
std::string fileContent((std::istreambuf_iterator<char>(file)),
|
||||
(std::istreambuf_iterator<char>()));
|
||||
|
||||
if (nlohmann::ordered_json::accept(fileContent))
|
||||
return nlohmann::ordered_json::parse(fileContent);
|
||||
else
|
||||
return nlohmann::ordered_json::object();
|
||||
}
|
||||
|
||||
void writeJsonToFile(nlohmann::ordered_json& data, const std::string& path)
|
||||
{
|
||||
std::ofstream out(path);
|
||||
out << data.dump(4);
|
||||
}
|
||||
|
||||
bool copyFile(const std::string& from, const std::string& to)
|
||||
{
|
||||
std::ifstream src(from, std::ios::binary);
|
||||
std::ofstream dst(to, std::ios::binary);
|
||||
|
||||
if (src.good() && dst.good()) {
|
||||
dst << src.rdbuf();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void createTree(std::string path)
|
||||
{
|
||||
std::string delimiter = "/";
|
||||
size_t pos = 0;
|
||||
std::string token;
|
||||
std::string directories("");
|
||||
while ((pos = path.find(delimiter)) != std::string::npos) {
|
||||
token = path.substr(0, pos);
|
||||
directories += token + "/";
|
||||
std::filesystem::create_directory(directories);
|
||||
path.erase(0, pos + delimiter.length());
|
||||
}
|
||||
}
|
||||
|
||||
std::string copyFiles(const std::string& path)
|
||||
{
|
||||
std::string error = "";
|
||||
if (std::filesystem::exists(path)) {
|
||||
std::string str;
|
||||
std::ifstream in(path);
|
||||
if (in) {
|
||||
while (std::getline(in, str)) {
|
||||
if (str.size() > 0) {
|
||||
auto toMove = splitString(str, '|');
|
||||
if (std::filesystem::exists(toMove[0]) && toMove.size() > 1) {
|
||||
copyFile(toMove[0], toMove[1]);
|
||||
}
|
||||
else {
|
||||
error += toMove[0] + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error == "") {
|
||||
error = "menus/common/all_done"_i18n;
|
||||
}
|
||||
else {
|
||||
error = "menus/tools/batch_copy_not_found"_i18n + error;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
std::set<std::string> readLineByLine(const std::string& path)
|
||||
{
|
||||
std::set<std::string> res;
|
||||
std::ifstream lines(path);
|
||||
std::string line;
|
||||
if (lines) {
|
||||
while (std::getline(lines, line)) {
|
||||
if (line.size() > 0) {
|
||||
if (line.back() == '\r')
|
||||
line.pop_back();
|
||||
res.insert(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Result getFreeStorageSD(s64& free)
|
||||
{
|
||||
return nsGetFreeSpaceSize(NcmStorageId_SdCard, &free);
|
||||
}
|
||||
|
||||
} // namespace fs
|
||||
98
source/hide_tabs_page.cpp
Normal file
98
source/hide_tabs_page.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "hide_tabs_page.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <json.hpp>
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
HideTabsPage::HideTabsPage() : AppletFrame(true, true)
|
||||
{
|
||||
this->setTitle("menus/hide/title"_i18n);
|
||||
list = new brls::List();
|
||||
label = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/hide/desc"_i18n,
|
||||
true);
|
||||
list->addView(label);
|
||||
|
||||
json hideStatus = fs::parseJsonFile(HIDE_TABS_JSON);
|
||||
|
||||
about = new brls::ToggleListItem("menus/main/about"_i18n, util::getBoolValue(hideStatus, "about"));
|
||||
list->addView(about);
|
||||
|
||||
ams = new brls::ToggleListItem("menus/main/update_ams"_i18n, util::getBoolValue(hideStatus, "atmosphere"));
|
||||
list->addView(ams);
|
||||
|
||||
cfws = new brls::ToggleListItem("menus/main/update_bootloaders"_i18n, util::getBoolValue(hideStatus, "cfw"));
|
||||
list->addView(cfws);
|
||||
|
||||
fws = new brls::ToggleListItem("menus/main/download_firmware"_i18n, util::getBoolValue(hideStatus, "firmwares"));
|
||||
list->addView(fws);
|
||||
|
||||
cheats = new brls::ToggleListItem("menus/main/download_cheats"_i18n, util::getBoolValue(hideStatus, "cheats"));
|
||||
list->addView(cheats);
|
||||
|
||||
custom = new brls::ToggleListItem("menus/main/custom_downloads"_i18n, util::getBoolValue(hideStatus, "custom"));
|
||||
list->addView(custom);
|
||||
|
||||
outdatedTitles = new brls::ToggleListItem("menus/tools/outdated_titles"_i18n, util::getBoolValue(hideStatus, "outdatedtitles"));
|
||||
list->addView(outdatedTitles);
|
||||
|
||||
jccolor = new brls::ToggleListItem("menus/tools/joy_cons"_i18n, util::getBoolValue(hideStatus, "jccolor"));
|
||||
list->addView(jccolor);
|
||||
|
||||
pccolor = new brls::ToggleListItem("menus/tools/pro_cons"_i18n, util::getBoolValue(hideStatus, "pccolor"));
|
||||
list->addView(pccolor);
|
||||
|
||||
downloadpayload = new brls::ToggleListItem("menus/tools/dl_payloads"_i18n, util::getBoolValue(hideStatus, "downloadpayload"));
|
||||
list->addView(downloadpayload);
|
||||
|
||||
rebootpayload = new brls::ToggleListItem("menus/tools/inject_payloads"_i18n, util::getBoolValue(hideStatus, "rebootpayload"));
|
||||
list->addView(rebootpayload);
|
||||
|
||||
netsettings = new brls::ToggleListItem("menus/tools/internet_settings"_i18n, util::getBoolValue(hideStatus, "netsettings"));
|
||||
list->addView(netsettings);
|
||||
|
||||
browser = new brls::ToggleListItem("menus/tools/browser"_i18n, util::getBoolValue(hideStatus, "browser"));
|
||||
list->addView(browser);
|
||||
|
||||
move = new brls::ToggleListItem("menus/tools/batch_copy"_i18n, util::getBoolValue(hideStatus, "move"));
|
||||
list->addView(move);
|
||||
|
||||
cleanup = new brls::ToggleListItem("menus/tools/clean_up"_i18n, util::getBoolValue(hideStatus, "cleanup"));
|
||||
list->addView(cleanup);
|
||||
|
||||
language = new brls::ToggleListItem("menus/tools/language"_i18n, util::getBoolValue(hideStatus, "language"));
|
||||
list->addView(language);
|
||||
|
||||
list->registerAction("menus/cheats/exclude_titles_save"_i18n, brls::Key::B, [this] {
|
||||
json updatedStatus = json::object();
|
||||
updatedStatus["about"] = about->getToggleState();
|
||||
updatedStatus["atmosphere"] = ams->getToggleState();
|
||||
updatedStatus["cfw"] = cfws->getToggleState();
|
||||
updatedStatus["firmwares"] = fws->getToggleState();
|
||||
updatedStatus["cheats"] = cheats->getToggleState();
|
||||
updatedStatus["custom"] = custom->getToggleState();
|
||||
updatedStatus["outdatedtitles"] = outdatedTitles->getToggleState();
|
||||
updatedStatus["jccolor"] = jccolor->getToggleState();
|
||||
updatedStatus["pccolor"] = pccolor->getToggleState();
|
||||
updatedStatus["downloadpayload"] = downloadpayload->getToggleState();
|
||||
updatedStatus["rebootpayload"] = rebootpayload->getToggleState();
|
||||
updatedStatus["netsettings"] = netsettings->getToggleState();
|
||||
updatedStatus["browser"] = browser->getToggleState();
|
||||
updatedStatus["move"] = move->getToggleState();
|
||||
updatedStatus["cleanup"] = cleanup->getToggleState();
|
||||
updatedStatus["language"] = language->getToggleState();
|
||||
|
||||
fs::writeJsonToFile(updatedStatus, HIDE_TABS_JSON);
|
||||
brls::Application::popView();
|
||||
return true;
|
||||
});
|
||||
this->setContentView(list);
|
||||
}
|
||||
215
source/list_download_tab.cpp
Normal file
215
source/list_download_tab.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include "list_download_tab.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include "app_page.hpp"
|
||||
#include "confirm_page.hpp"
|
||||
#include "current_cfw.hpp"
|
||||
#include "dialogue_page.hpp"
|
||||
#include "download.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "worker_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
ListDownloadTab::ListDownloadTab(const contentType type, const nlohmann::ordered_json& nxlinks) : brls::List(), type(type), nxlinks(nxlinks)
|
||||
{
|
||||
this->setDescription();
|
||||
|
||||
this->createList();
|
||||
|
||||
if (this->type == contentType::cheats) {
|
||||
brls::Label* cheatsLabel = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/cheats/cheats_label"_i18n,
|
||||
true);
|
||||
this->addView(cheatsLabel);
|
||||
this->createGbatempItem();
|
||||
this->createGfxItem();
|
||||
this->createCheatSlipItem();
|
||||
}
|
||||
|
||||
if (this->type == contentType::bootloaders) {
|
||||
this->setDescription(contentType::hekate_ipl);
|
||||
this->createList(contentType::hekate_ipl);
|
||||
this->setDescription(contentType::payloads);
|
||||
this->createList(contentType::payloads);
|
||||
}
|
||||
}
|
||||
|
||||
void ListDownloadTab::createList()
|
||||
{
|
||||
ListDownloadTab::createList(this->type);
|
||||
}
|
||||
|
||||
void ListDownloadTab::createList(contentType type)
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> links;
|
||||
if (type == contentType::cheats && this->newCheatsVer != "") {
|
||||
links.push_back(std::make_pair(fmt::format("menus/main/get_cheats"_i18n, this->newCheatsVer), CurrentCfw::running_cfw == CFW::sxos ? CHEATS_URL_TITLES : CHEATS_URL_CONTENTS));
|
||||
// links.push_back(std::make_pair("menus/main/get_cheats_gfx"_i18n, CurrentCfw::running_cfw == CFW::sxos ? GFX_CHEATS_URL_TITLES : GFX_CHEATS_URL_CONTENTS));
|
||||
}
|
||||
else
|
||||
links = download::getLinksFromJson(util::getValueFromKey(this->nxlinks, contentTypeNames[(int)type].data()));
|
||||
|
||||
if (links.size()) {
|
||||
for (const auto& link : links) {
|
||||
const std::string title = link.first;
|
||||
const std::string url = link.second;
|
||||
const std::string text("menus/common/download"_i18n + link.first);
|
||||
listItem = new brls::ListItem(link.first);
|
||||
listItem->setHeight(LISTITEM_HEIGHT);
|
||||
listItem->getClickEvent()->subscribe([this, type, text, url, title](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle(fmt::format("menus/main/getting"_i18n, contentTypeNames[(int)type].data()));
|
||||
stagedFrame->addStage(new ConfirmPage(stagedFrame, text));
|
||||
if (type == contentType::fw) {
|
||||
std::string contentsPath = util::getContentsPath();
|
||||
for (const auto& tid : {"0100000000001000", "0100000000001007", "0100000000001013"}) {
|
||||
if (std::filesystem::exists(contentsPath + tid) && !std::filesystem::is_empty(contentsPath + tid)) {
|
||||
stagedFrame->addStage(new DialoguePage_confirm(stagedFrame, "menus/main/theme_warning"_i18n));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type != contentType::payloads && type != contentType::hekate_ipl) {
|
||||
if (type != contentType::cheats || (this->newCheatsVer != this->currentCheatsVer && this->newCheatsVer != "offline")) {
|
||||
stagedFrame->addStage(new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, [this, type, url]() { util::downloadArchive(url, type); }));
|
||||
}
|
||||
stagedFrame->addStage(new WorkerPage(stagedFrame, "menus/common/extracting"_i18n, [this, type]() { util::extractArchive(type, this->newCheatsVer); }));
|
||||
}
|
||||
else if (type == contentType::payloads) {
|
||||
fs::createTree(BOOTLOADER_PL_PATH);
|
||||
std::string path = std::string(BOOTLOADER_PL_PATH) + title;
|
||||
stagedFrame->addStage(new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, [url, path]() { download::downloadFile(url, path, OFF); }));
|
||||
}
|
||||
else if (type == contentType::hekate_ipl) {
|
||||
fs::createTree(BOOTLOADER_PATH);
|
||||
std::string path = std::string(BOOTLOADER_PATH) + title;
|
||||
stagedFrame->addStage(new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, [url, path]() { download::downloadFile(url, path, OFF); }));
|
||||
}
|
||||
|
||||
std::string doneMsg = "menus/common/all_done"_i18n;
|
||||
if (type == contentType::fw && std::filesystem::exists(DAYBREAK_PATH)) {
|
||||
stagedFrame->addStage(new DialoguePage_fw(stagedFrame, doneMsg));
|
||||
}
|
||||
else {
|
||||
stagedFrame->addStage(new ConfirmPage_Done(stagedFrame, doneMsg));
|
||||
}
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
this->addView(listItem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->displayNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
void ListDownloadTab::displayNotFound()
|
||||
{
|
||||
brls::Label* notFound = new brls::Label(
|
||||
brls::LabelStyle::SMALL,
|
||||
"menus/main/links_not_found"_i18n,
|
||||
true);
|
||||
notFound->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->addView(notFound);
|
||||
}
|
||||
|
||||
void ListDownloadTab::setDescription()
|
||||
{
|
||||
this->setDescription(this->type);
|
||||
}
|
||||
|
||||
void ListDownloadTab::setDescription(contentType type)
|
||||
{
|
||||
brls::Label* description = new brls::Label(brls::LabelStyle::DESCRIPTION, "", true);
|
||||
|
||||
switch (type) {
|
||||
case contentType::fw: {
|
||||
SetSysFirmwareVersion ver;
|
||||
description->setText(fmt::format("{}{}", "menus/main/firmware_text"_i18n, R_SUCCEEDED(setsysGetFirmwareVersion(&ver)) ? ver.display_version : "menus/main/not_found"_i18n));
|
||||
break;
|
||||
}
|
||||
case contentType::bootloaders:
|
||||
description->setText(
|
||||
"menus/main/bootloaders_text"_i18n);
|
||||
break;
|
||||
case contentType::cheats:
|
||||
this->newCheatsVer = util::getCheatsVersion();
|
||||
this->currentCheatsVer = util::readFile(CHEATS_VERSION);
|
||||
description->setText("menus/main/cheats_text"_i18n + this->currentCheatsVer);
|
||||
break;
|
||||
case contentType::payloads:
|
||||
description->setText(fmt::format("menus/main/payloads_label"_i18n, BOOTLOADER_PL_PATH));
|
||||
break;
|
||||
case contentType::hekate_ipl:
|
||||
description->setText("menus/main/hekate_ipl_label"_i18n);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this->addView(description);
|
||||
}
|
||||
|
||||
void ListDownloadTab::createCheatSlipItem()
|
||||
{
|
||||
brls::ListItem* cheatslipsItem = new brls::ListItem("menus/cheats/get_cheatslips"_i18n);
|
||||
cheatslipsItem->setHeight(LISTITEM_HEIGHT);
|
||||
cheatslipsItem->getClickEvent()->subscribe([](brls::View* view) {
|
||||
if (std::filesystem::exists(TOKEN_PATH)) {
|
||||
brls::Application::pushView(new AppPage_CheatSlips());
|
||||
}
|
||||
else {
|
||||
std::string usr, pwd;
|
||||
// Result rc = swkbdCreate(&kbd, 0);
|
||||
brls::Swkbd::openForText([&usr](std::string text) { usr = text; }, "cheatslips.com e-mail", "", 64, "", 0, "Submit", "cheatslips.com e-mail");
|
||||
brls::Swkbd::openForText([&pwd](std::string text) { pwd = text; }, "cheatslips.com password", "", 64, "", 0, "Submit", "cheatslips.com password", true);
|
||||
std::string body = "{\"email\":\"" + std::string(usr) + "\",\"password\":\"" + std::string(pwd) + "\"}";
|
||||
nlohmann::ordered_json token;
|
||||
download::getRequest(CHEATSLIPS_TOKEN_URL, token,
|
||||
{"Accept: application/json",
|
||||
"Content-Type: application/json",
|
||||
"charset: utf-8"},
|
||||
body);
|
||||
if (token.find("token") != token.end()) {
|
||||
std::ofstream tokenFile(TOKEN_PATH);
|
||||
tokenFile << token.dump();
|
||||
tokenFile.close();
|
||||
brls::Application::pushView(new AppPage_CheatSlips());
|
||||
}
|
||||
else {
|
||||
util::showDialogBoxInfo("menus/cheats/cheatslips_wrong_id"_i18n + "\n" + "menus/cheats/kb_error"_i18n);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// this->addView(cheatslipsItem);
|
||||
}
|
||||
|
||||
void ListDownloadTab::createGbatempItem()
|
||||
{
|
||||
brls::ListItem* gbatempItem = new brls::ListItem("menus/cheats/get_gbatemp"_i18n);
|
||||
gbatempItem->setHeight(LISTITEM_HEIGHT);
|
||||
gbatempItem->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::Application::pushView(new AppPage_Gbatemp());
|
||||
return true;
|
||||
});
|
||||
this->addView(gbatempItem);
|
||||
}
|
||||
|
||||
void ListDownloadTab::createGfxItem()
|
||||
{
|
||||
brls::ListItem* gfxItem = new brls::ListItem("menus/cheats/get_gfx"_i18n);
|
||||
gfxItem->setHeight(LISTITEM_HEIGHT);
|
||||
gfxItem->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::Application::pushView(new AppPage_Gfx());
|
||||
return true;
|
||||
});
|
||||
// this->addView(gfxItem);
|
||||
}
|
||||
77
source/main.cpp
Normal file
77
source/main.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <switch.h>
|
||||
|
||||
#include <borealis.hpp>
|
||||
#include <filesystem>
|
||||
#include <json.hpp>
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "current_cfw.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "main_frame.hpp"
|
||||
#include "warning_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
//TimeServiceType __nx_time_service_type = TimeServiceType_System;
|
||||
|
||||
CFW CurrentCfw::running_cfw;
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
// Init the app
|
||||
if (!brls::Application::init(APP_TITLE)) {
|
||||
brls::Logger::error("Unable to init Borealis application");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
nlohmann::ordered_json languageFile = fs::parseJsonFile(LANGUAGE_JSON);
|
||||
if (languageFile.find("language") != languageFile.end())
|
||||
i18n::loadTranslations(languageFile["language"]);
|
||||
else
|
||||
i18n::loadTranslations();
|
||||
|
||||
//appletInitializeGamePlayRecording();
|
||||
|
||||
// Setup verbose logging on PC
|
||||
#ifndef __SWITCH__
|
||||
brls::Logger::setLogLevel(brls::LogLevel::DEBUG);
|
||||
#endif
|
||||
|
||||
setsysInitialize();
|
||||
plInitialize(PlServiceType_User);
|
||||
nsInitialize();
|
||||
socketInitializeDefault();
|
||||
nxlinkStdio();
|
||||
pmdmntInitialize();
|
||||
pminfoInitialize();
|
||||
splInitialize();
|
||||
romfsInit();
|
||||
|
||||
CurrentCfw::running_cfw = CurrentCfw::getCFW();
|
||||
|
||||
fs::createTree(CONFIG_PATH);
|
||||
|
||||
brls::Logger::setLogLevel(brls::LogLevel::DEBUG);
|
||||
brls::Logger::debug("Start");
|
||||
|
||||
if (std::filesystem::exists(HIDDEN_AIO_FILE)) {
|
||||
brls::Application::pushView(new MainFrame());
|
||||
}
|
||||
else {
|
||||
brls::Application::pushView(new WarningPage("menus/main/launch_warning"_i18n));
|
||||
}
|
||||
|
||||
while (brls::Application::mainLoop())
|
||||
;
|
||||
|
||||
romfsExit();
|
||||
splExit();
|
||||
pminfoExit();
|
||||
pmdmntExit();
|
||||
socketExit();
|
||||
nsExit();
|
||||
setsysExit();
|
||||
plExit();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
62
source/main_frame.cpp
Normal file
62
source/main_frame.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "main_frame.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <json.hpp>
|
||||
|
||||
#include "about_tab.hpp"
|
||||
#include "ams_tab.hpp"
|
||||
#include "download.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "list_download_tab.hpp"
|
||||
#include "tools_tab.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
namespace {
|
||||
constexpr const char AppTitle[] = APP_TITLE;
|
||||
constexpr const char AppVersion[] = APP_VERSION;
|
||||
} // namespace
|
||||
|
||||
MainFrame::MainFrame() : TabFrame()
|
||||
{
|
||||
this->setIcon("romfs:/gui_icon.png");
|
||||
this->setTitle(AppTitle);
|
||||
|
||||
s64 freeStorage;
|
||||
std::string tag = util::getLatestTag(TAGS_INFO);
|
||||
this->setFooterText(fmt::format("menus/main/footer_text"_i18n,
|
||||
(!tag.empty() && tag != AppVersion) ? AppVersion + "menus/main/new_update"_i18n : AppVersion,
|
||||
R_SUCCEEDED(fs::getFreeStorageSD(freeStorage)) ? (float)freeStorage / 0x40000000 : -1));
|
||||
|
||||
json hideStatus = fs::parseJsonFile(HIDE_TABS_JSON);
|
||||
nlohmann::ordered_json nxlinks;
|
||||
download::getRequest(NXLINKS_URL, nxlinks);
|
||||
|
||||
bool erista = util::isErista();
|
||||
|
||||
// if (!util::getBoolValue(hideStatus, "about"))
|
||||
// this->addTab("menus/main/about"_i18n, new AboutTab());
|
||||
|
||||
// if (!util::getBoolValue(hideStatus, "atmosphere"))
|
||||
// this->addTab("menus/main/update_ams"_i18n, new AmsTab_Regular(nxlinks, erista));
|
||||
|
||||
// if (!util::getBoolValue(hideStatus, "cfw"))
|
||||
// this->addTab("menus/main/update_bootloaders"_i18n, new ListDownloadTab(contentType::bootloaders, nxlinks));
|
||||
|
||||
// if (!util::getBoolValue(hideStatus, "firmwares"))
|
||||
// this->addTab("menus/main/download_firmware"_i18n, new ListDownloadTab(contentType::fw, nxlinks));
|
||||
|
||||
if (!util::getBoolValue(hideStatus, "cheats"))
|
||||
this->addTab("menus/main/download_cheats"_i18n, new ListDownloadTab(contentType::cheats));
|
||||
|
||||
// if (!util::getBoolValue(hideStatus, "custom"))
|
||||
// this->addTab("menus/main/custom_downloads"_i18n, new AmsTab_Custom(nxlinks, erista));
|
||||
|
||||
if (!util::getBoolValue(hideStatus, "tools"))
|
||||
this->addTab("menus/main/tools"_i18n, new ToolsTab(tag, util::getValueFromKey(nxlinks, "payloads"), erista, hideStatus));
|
||||
|
||||
this->registerAction("", brls::Key::B, [this] { return true; });
|
||||
}
|
||||
209
source/net_page.cpp
Normal file
209
source/net_page.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
#include "net_page.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <switch.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <thread>
|
||||
#include <json.hpp>
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "main_frame.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
NetPage::NetPage() : AppletFrame(true, true)
|
||||
{
|
||||
this->setTitle("menus/net/title"_i18n);
|
||||
list = new brls::List();
|
||||
|
||||
nifmInitialize(NifmServiceType_User);
|
||||
NifmNetworkProfileData profile;
|
||||
nifmGetCurrentNetworkProfile(&profile);
|
||||
nifmExit();
|
||||
|
||||
int uuid = std::accumulate(profile.uuid.uuid, profile.uuid.uuid + 16, 0);
|
||||
|
||||
std::string labelText;
|
||||
|
||||
if (!uuid || !profile.ip_setting_data.mtu) {
|
||||
labelText = "Please connect to internet to use this feature.";
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, labelText, true);
|
||||
list->addView(label);
|
||||
cancel = new brls::ListItem("menus/common/go_back"_i18n);
|
||||
cancel->getClickEvent()->subscribe([](brls::View* view) { brls::Application::pushView(new MainFrame()); });
|
||||
list->addView(cancel);
|
||||
}
|
||||
else {
|
||||
if (profile.ip_setting_data.ip_address_setting.is_automatic) {
|
||||
labelText = "IP Adress: Automatic";
|
||||
}
|
||||
else {
|
||||
labelText = fmt::format(
|
||||
"IP Adress: {}\nSubnet Mask: {}\nGateway: {}",
|
||||
ipToString(profile.ip_setting_data.ip_address_setting.current_addr.addr),
|
||||
ipToString(profile.ip_setting_data.ip_address_setting.subnet_mask.addr),
|
||||
ipToString(profile.ip_setting_data.ip_address_setting.gateway.addr));
|
||||
}
|
||||
labelText = fmt::format("{}\nLocal IP addr: {}\nMTU: {}", labelText, std::string(inet_ntoa({(in_addr_t)gethostid()})), std::to_string(unsigned(profile.ip_setting_data.mtu)));
|
||||
|
||||
if (profile.ip_setting_data.dns_setting.is_automatic) {
|
||||
labelText = fmt::format("{}\nDNS: Automatic", labelText);
|
||||
}
|
||||
else {
|
||||
labelText = fmt::format(
|
||||
"{}\nPrimary DNS: {}\nSecondary DNS: {}",
|
||||
labelText,
|
||||
ipToString(profile.ip_setting_data.dns_setting.primary_dns_server.addr),
|
||||
ipToString(profile.ip_setting_data.dns_setting.secondary_dns_server.addr));
|
||||
}
|
||||
|
||||
label = new brls::Label(brls::LabelStyle::DESCRIPTION, labelText, true);
|
||||
list->addView(label);
|
||||
|
||||
//ip_addr
|
||||
//subnet_mask
|
||||
//gateway
|
||||
//dns1
|
||||
//dns2
|
||||
//mtu
|
||||
//ip_auto
|
||||
//dns_auto
|
||||
|
||||
json profiles = fs::parseJsonFile(INTERNET_JSON);
|
||||
if (profiles.empty()) {
|
||||
profiles = json::array();
|
||||
}
|
||||
|
||||
profiles.push_back(
|
||||
json::object({{"name", "lan-play"},
|
||||
{"ip_addr", fmt::format("10.13.{}.{}", std::rand() % 256, std::rand() % 253 + 2)},
|
||||
{"subnet_mask", "255.255.0.0"},
|
||||
{"gateway", "10.13.37.1"}}));
|
||||
|
||||
profiles.push_back(
|
||||
json::object({{"name", "Automatic IP Address"},
|
||||
{"ip_auto", true}}));
|
||||
|
||||
profiles.push_back(
|
||||
json::object({{"name", "Automatic DNS"},
|
||||
{"dns_auto", true}}));
|
||||
|
||||
profiles.push_back(
|
||||
json::object({{"name", "90DNS (Europe)"},
|
||||
{"dns1", "163.172.141.219"},
|
||||
{"dns2", "207.246.121.77"}}));
|
||||
|
||||
profiles.push_back(
|
||||
json::object({{"name", "90DNS (USA)"},
|
||||
{"dns1", "207.246.121.77"},
|
||||
{"dns2", "163.172.141.219"}}));
|
||||
|
||||
profiles.push_back(
|
||||
json::object({{"name", "Google DNS"},
|
||||
{"dns1", "8.8.8.8"},
|
||||
{"dns2", "8.8.4.4"}}));
|
||||
|
||||
profiles.push_back(
|
||||
json::object({{"name", "ACNH mtu"},
|
||||
{"mtu", 1500}}));
|
||||
|
||||
for (const auto& p : profiles.items()) {
|
||||
json values = p.value();
|
||||
if (values.find("name") != values.end())
|
||||
listItem = new brls::ListItem(values["name"]);
|
||||
else
|
||||
listItem = new brls::ListItem("Unnamed");
|
||||
listItem->getClickEvent()->subscribe([this, values](brls::View* view) {
|
||||
brls::Dialog* dialog = new brls::Dialog(values.dump(0).substr(1, values.dump(0).size() - 2));
|
||||
brls::GenericEvent::Callback callbackOk = [this, dialog, values](brls::View* view) {
|
||||
nifmInitialize(NifmServiceType_Admin);
|
||||
NifmNetworkProfileData profile;
|
||||
nifmGetCurrentNetworkProfile(&profile);
|
||||
unsigned char buf[sizeof(struct in6_addr)];
|
||||
if (values.find("ip_addr") != values.end()) {
|
||||
if (inet_pton(AF_INET, std::string(values["ip_addr"]).c_str(), buf)) {
|
||||
profile.ip_setting_data.ip_address_setting.is_automatic = u8(0);
|
||||
stringToIp(std::string(values["ip_addr"]), profile.ip_setting_data.ip_address_setting.current_addr.addr);
|
||||
}
|
||||
}
|
||||
if (values.find("subnet_mask") != values.end()) {
|
||||
if (inet_pton(AF_INET, std::string(values["subnet_mask"]).c_str(), buf)) {
|
||||
stringToIp(std::string(values["subnet_mask"]), profile.ip_setting_data.ip_address_setting.subnet_mask.addr);
|
||||
}
|
||||
}
|
||||
if (values.find("gateway") != values.end()) {
|
||||
if (inet_pton(AF_INET, std::string(values["gateway"]).c_str(), buf)) {
|
||||
stringToIp(std::string(values["gateway"]), profile.ip_setting_data.ip_address_setting.gateway.addr);
|
||||
}
|
||||
}
|
||||
if (values.find("dns1") != values.end()) {
|
||||
if (inet_pton(AF_INET, std::string(values["dns1"]).c_str(), buf)) {
|
||||
profile.ip_setting_data.dns_setting.is_automatic = u8(0);
|
||||
stringToIp(std::string(values["dns1"]), profile.ip_setting_data.dns_setting.primary_dns_server.addr);
|
||||
}
|
||||
}
|
||||
if (values.find("dns2") != values.end()) {
|
||||
if (inet_pton(AF_INET, std::string(values["dns2"]).c_str(), buf)) {
|
||||
profile.ip_setting_data.dns_setting.is_automatic = u8(0);
|
||||
stringToIp(std::string(values["dns2"]), profile.ip_setting_data.dns_setting.secondary_dns_server.addr);
|
||||
}
|
||||
}
|
||||
if (values.find("mtu") != values.end()) {
|
||||
profile.ip_setting_data.mtu = u16(values["mtu"]);
|
||||
}
|
||||
if (values.find("ip_auto") != values.end()) {
|
||||
profile.ip_setting_data.ip_address_setting.is_automatic = u8(values["ip_auto"]);
|
||||
}
|
||||
if (values.find("dns_auto") != values.end()) {
|
||||
profile.ip_setting_data.dns_setting.is_automatic = u8(values["dns_auto"]);
|
||||
}
|
||||
|
||||
nifmSetNetworkProfile(&profile, &profile.uuid);
|
||||
nifmSetWirelessCommunicationEnabled(true);
|
||||
nifmExit();
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(2500000)); //Wait to avoid crashes when leaving too fast
|
||||
dialog->close();
|
||||
};
|
||||
brls::GenericEvent::Callback callbackNo = [dialog](brls::View* view) {
|
||||
dialog->close();
|
||||
};
|
||||
dialog->addButton("menus/common/confirm"_i18n, callbackOk);
|
||||
dialog->addButton("menus/common/cancel"_i18n, callbackNo);
|
||||
dialog->setCancelable(false);
|
||||
dialog->open();
|
||||
});
|
||||
list->addView(listItem);
|
||||
}
|
||||
}
|
||||
this->setContentView(list);
|
||||
}
|
||||
|
||||
std::string NetPage::ipToString(u8* ip)
|
||||
{
|
||||
std::string res = "";
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
res += std::to_string(unsigned(ip[i]));
|
||||
res += ".";
|
||||
}
|
||||
res += std::to_string(unsigned(ip[4]));
|
||||
return res;
|
||||
}
|
||||
|
||||
int NetPage::stringToIp(const std::string& ip, u8* out)
|
||||
{
|
||||
size_t start;
|
||||
size_t end = 0;
|
||||
int i = 0;
|
||||
while ((start = ip.find_first_not_of(".", end)) != std::string::npos) {
|
||||
end = ip.find(".", start);
|
||||
out[i] = u8(std::stoi(ip.substr(start, end - start)));
|
||||
i++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
288
source/ntcp.cpp.meh
Normal file
288
source/ntcp.cpp.meh
Normal file
@@ -0,0 +1,288 @@
|
||||
/* This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org>
|
||||
|
||||
Additionally, the ntp_packet struct uses code licensed under the BSD 3-clause.*/
|
||||
/* This homebrew uses code from https://github.com/lettier/ntpclient under the following license:
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2014, David Lettier
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// This code comes from https://github.com/thedax/NX-ntpc, thank you for your work
|
||||
|
||||
#include "ntcp.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <time.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#define NTP_TIMESTAMP_DELTA 2208988800ull
|
||||
|
||||
/* ------------- BEGIN BSD 3-CLAUSE LICENSED CODE-------------------- */
|
||||
// https://www.cisco.com/c/en/us/about/press/internet-protocol-journal/back-issues/table-contents-58/154-ntp.html
|
||||
// Struct adapted from https://github.com/lettier/ntpclient , see LICENSE-THIRD-PARTY for more information.
|
||||
typedef struct
|
||||
{
|
||||
uint8_t li_vn_mode; // li: two bits, leap indicator. vn: three bits, protocol version number. mode: three bits, client mode.
|
||||
|
||||
uint8_t stratum; // Stratum level of the local clock.
|
||||
uint8_t poll; // Maximum interval between successive messages.
|
||||
uint8_t precision; // Precision of the local clock.
|
||||
|
||||
uint32_t rootDelay; // Total round trip delay time.
|
||||
uint32_t rootDispersion; // Max error allowed from primary clock source.
|
||||
uint32_t refId; // Reference clock identifier.
|
||||
|
||||
uint32_t refTm_s; // Reference time-stamp seconds.
|
||||
uint32_t refTm_f; // Reference time-stamp fraction of a second.
|
||||
|
||||
uint32_t origTm_s; // Originate time-stamp seconds.
|
||||
uint32_t origTm_f; // Originate time-stamp fraction of a second.
|
||||
|
||||
uint32_t rxTm_s; // Received time-stamp seconds.
|
||||
uint32_t rxTm_f; // Received time-stamp fraction of a second.
|
||||
|
||||
uint32_t txTm_s; // Transmit time-stamp seconds.
|
||||
uint32_t txTm_f; // Transmit time-stamp fraction of a second.
|
||||
} ntp_packet;
|
||||
/* ------------- END BSD 3-CLAUSE LICENSED CODE-------------------- */
|
||||
|
||||
void serviceCleanup(void)
|
||||
{
|
||||
nifmExit();
|
||||
setsysExit();
|
||||
timeExit();
|
||||
}
|
||||
|
||||
void print(const std::string& msg)
|
||||
{
|
||||
puts(msg);
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
void printWithArgs(const std::string& msg, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
vprintf(msg, args);
|
||||
va_end(args);
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
// We need system service access, not just user (the default)
|
||||
|
||||
std::string syncTime()
|
||||
{
|
||||
const std::string& server_name = "0.pool.ntp.org";
|
||||
const uint16_t port = 123;
|
||||
int sockfd = -1;
|
||||
std::stringstream out;
|
||||
|
||||
int res = 0;
|
||||
time_t tim;
|
||||
|
||||
Result rs = timeInitialize();
|
||||
if(R_FAILED(rs))
|
||||
{
|
||||
//printWithArgs("Failed to init time services, error code %x\n", rs);
|
||||
out << "Failed to init time services, error code " << rs << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
rs = nifmInitialize(NifmServiceType_User);
|
||||
if(R_FAILED(rs))
|
||||
{
|
||||
//printWithArgs("Failed to init nifm services, with error code %x\n", rs);
|
||||
out << "Failed to init nifm services, with error code " << rs << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
NifmInternetConnectionStatus nifmICS;
|
||||
rs = nifmGetInternetConnectionStatus(NULL, NULL, &nifmICS);
|
||||
if(R_FAILED(rs))
|
||||
{
|
||||
//printWithArgs("Failed to get internet connection status, with error code %x\nPlease ensure your console is connected to the internet, and try again.\n", rs);
|
||||
out << "Failed to get internet connection status, with error code " << res << ". Please ensure your console is connected to the internet, and try again." << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if(nifmICS != NifmInternetConnectionStatus_Connected)
|
||||
{
|
||||
//print("You're not connected to the internet. Please run this application again after connecting.");
|
||||
out << "You're not connected to the internet. Please run this application again after connecting." << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ntp_packet packet;
|
||||
memset(&packet, 0, sizeof(ntp_packet));
|
||||
|
||||
packet.li_vn_mode = (0 << 6) | (4 << 3) | 3; // LI 0 | Client version 4 | Mode 3
|
||||
|
||||
packet.txTm_s = htonl(NTP_TIMESTAMP_DELTA + time(NULL)); // Current networktime on the console
|
||||
|
||||
struct sockaddr_in serv_addr;
|
||||
struct hostent *server;
|
||||
|
||||
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(sockfd < 0)
|
||||
{
|
||||
//printWithArgs("Failed to open socket with error code %x\n", errno);
|
||||
out << "Failed to open socket with error code " << errno << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
//print("Opened socket");
|
||||
//printWithArgs("Attempting to connect to %s\n", server_name);
|
||||
errno = 0;
|
||||
if((server = gethostbyname(server_name)) == NULL)
|
||||
{
|
||||
//printWithArgs("Gethostbyname failed: %x\n", errno);
|
||||
out << "Gethostbyname failed: " << errno << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
memset(&serv_addr, 0, sizeof(struct sockaddr_in));
|
||||
|
||||
serv_addr.sin_family = AF_INET;
|
||||
|
||||
memcpy((char *)&serv_addr.sin_addr.s_addr, (char *)server->h_addr_list[0], 4);
|
||||
|
||||
serv_addr.sin_port = htons(port);
|
||||
|
||||
errno = 0;
|
||||
if((res = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0)
|
||||
{
|
||||
//printWithArgs("Connect failed: %x %x\n", res, errno);
|
||||
out << "Connect failed: " << res << " " << errno << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
if((res = send(sockfd, (char *)&packet, sizeof(ntp_packet), 0)) < 0)
|
||||
{
|
||||
//printWithArgs("Error writing to socket: %x %x\n", res, errno);
|
||||
out << "Error writing to socket: " << res << " " << errno << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
printWithArgs("Sent time request with result: %x %x, waiting for response...\n", res, errno);
|
||||
|
||||
errno = 0;
|
||||
if((res = recv(sockfd, (char *)&packet, sizeof(ntp_packet), 0)) < sizeof(ntp_packet))
|
||||
{
|
||||
//printWithArgs("Error reading from socket: %x %x\n", res, errno);
|
||||
out << "Error reading from socket: " << res << " " << errno << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
packet.txTm_s = ntohl(packet.txTm_s);
|
||||
|
||||
tim = (time_t) (packet.txTm_s - NTP_TIMESTAMP_DELTA);
|
||||
|
||||
rs = timeSetCurrentTime(TimeType_NetworkSystemClock, (uint64_t)tim);
|
||||
if(R_FAILED(rs))
|
||||
{
|
||||
//printWithArgs("Failed to set NetworkSystemClock, %x\n", rs);
|
||||
out << "Failed to set NetworkSystemClock " << rs << std::endl;
|
||||
}
|
||||
else
|
||||
out << "Successfully set NetworkSystemClock" << std::endl;
|
||||
|
||||
rs = setsysInitialize();
|
||||
if(R_FAILED(rs))
|
||||
{
|
||||
//printWithArgs("setsysInitialize failed, %x\n", rs);
|
||||
out << "setsysInitialize failed, " << rs << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
bool internetTimeSync;
|
||||
|
||||
rs = setsysIsUserSystemClockAutomaticCorrectionEnabled(&internetTimeSync);
|
||||
if(R_FAILED(rs) || internetTimeSync == false)
|
||||
{
|
||||
//printWithArgs("Unable to detect if internet time sync is enabled, %x\n", rs);
|
||||
out << "Note: internet time sync is not enabled (enabling it will correct the time on the home screen)" << std::endl;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* if(internetTimeSync == false)
|
||||
{
|
||||
print("Internet time sync is not enabled (enabling it will correct the time on the home screen, and this prompt will not appear again).\nPress A to enable internet time sync (and restart the console), or PLUS to quit.");
|
||||
rs = setsysSetUserSystemClockAutomaticCorrectionEnabled(true);
|
||||
if(R_SUCCEEDED(rs))
|
||||
{
|
||||
serviceCleanup();
|
||||
bpcInitialize();
|
||||
bpcRebootSystem();
|
||||
bpcExit();
|
||||
}
|
||||
} */
|
||||
|
||||
done:
|
||||
|
||||
cleanup:
|
||||
if(sockfd != -1)
|
||||
close(sockfd);
|
||||
serviceCleanup();
|
||||
return out.str();
|
||||
}
|
||||
63
source/payload_page.cpp
Normal file
63
source/payload_page.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "payload_page.hpp"
|
||||
|
||||
#include "current_cfw.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
PayloadPage::PayloadPage() : AppletFrame(true, true)
|
||||
{
|
||||
this->updateActionHint(brls::Key::B, "");
|
||||
this->updateActionHint(brls::Key::PLUS, "");
|
||||
|
||||
this->list = new brls::List();
|
||||
this->label = new brls::Label(
|
||||
brls::LabelStyle::DESCRIPTION,
|
||||
"menus/payloads/select"_i18n,
|
||||
true);
|
||||
this->list->addView(this->label);
|
||||
std::vector<std::string> payloads = util::fetchPayloads();
|
||||
for (const auto& payload : payloads) {
|
||||
std::string payload_path = payload;
|
||||
this->listItem = new brls::ListItem(payload_path);
|
||||
this->listItem->getClickEvent()->subscribe([payload_path](brls::View* view) {
|
||||
util::rebootToPayload(payload_path);
|
||||
brls::Application::popView();
|
||||
});
|
||||
if (CurrentCfw::running_cfw == CFW::ams) {
|
||||
this->RegisterCopyAction(brls::Key::X, payload_path, REBOOT_PAYLOAD_PATH, "menus/payloads/set_reboot_payload"_i18n);
|
||||
}
|
||||
this->RegisterCopyAction(brls::Key::Y, payload_path, UPDATE_BIN_PATH, "menus/payloads/set_update_bin"_i18n);
|
||||
this->list->addView(this->listItem);
|
||||
}
|
||||
this->list->addView(new brls::ListItemGroupSpacing(true));
|
||||
|
||||
this->listItem = new brls::ListItem("menus/common/shut_down"_i18n);
|
||||
this->listItem->getClickEvent()->subscribe([](brls::View* view) {
|
||||
util::shutDown(false);
|
||||
brls::Application::popView();
|
||||
});
|
||||
this->list->addView(this->listItem);
|
||||
|
||||
this->listItem = new brls::ListItem("menus/common/reboot"_i18n);
|
||||
this->listItem->getClickEvent()->subscribe([](brls::View* view) {
|
||||
util::shutDown(true);
|
||||
brls::Application::popView();
|
||||
});
|
||||
list->addView(this->listItem);
|
||||
|
||||
this->setContentView(list);
|
||||
}
|
||||
|
||||
void PayloadPage::RegisterCopyAction(brls::Key key, const std::string& payload_path, const std::string& payload_dest, const std::string& action_name)
|
||||
{
|
||||
this->listItem->registerAction(action_name, key, [payload_path, payload_dest] {
|
||||
std::string res = fs::copyFile(payload_path, payload_dest)
|
||||
? fmt::format("menus/payloads/copy_success"_i18n, payload_path, payload_dest)
|
||||
: "Failed.";
|
||||
util::showDialogBoxInfo(res);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
87
source/reboot_payload.c
Normal file
87
source/reboot_payload.c
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "reboot_payload.h"
|
||||
|
||||
#include "ams_bpc.h"
|
||||
|
||||
#define IRAM_PAYLOAD_MAX_SIZE 0x2F000
|
||||
#define IRAM_PAYLOAD_BASE 0x40010000
|
||||
|
||||
static alignas(0x1000) u8 g_reboot_payload[IRAM_PAYLOAD_MAX_SIZE];
|
||||
static alignas(0x1000) u8 g_ff_page[0x1000];
|
||||
static alignas(0x1000) u8 g_work_page[0x1000];
|
||||
|
||||
void do_iram_dram_copy(void* buf, uintptr_t iram_addr, size_t size, int option)
|
||||
{
|
||||
memcpy(g_work_page, buf, size);
|
||||
|
||||
SecmonArgs args = {0};
|
||||
args.X[0] = 0xF0000201; /* smcAmsIramCopy */
|
||||
args.X[1] = (uintptr_t)g_work_page; /* DRAM Address */
|
||||
args.X[2] = iram_addr; /* IRAM Address */
|
||||
args.X[3] = size; /* Copy size */
|
||||
args.X[4] = option; /* 0 = Read, 1 = Write */
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
memcpy(buf, g_work_page, size);
|
||||
}
|
||||
|
||||
void copy_to_iram(uintptr_t iram_addr, void* buf, size_t size)
|
||||
{
|
||||
do_iram_dram_copy(buf, iram_addr, size, 1);
|
||||
}
|
||||
|
||||
void copy_from_iram(void* buf, uintptr_t iram_addr, size_t size)
|
||||
{
|
||||
do_iram_dram_copy(buf, iram_addr, size, 0);
|
||||
}
|
||||
|
||||
static void clear_iram(void)
|
||||
{
|
||||
memset(g_ff_page, 0xFF, sizeof(g_ff_page));
|
||||
for (size_t i = 0; i < IRAM_PAYLOAD_MAX_SIZE; i += sizeof(g_ff_page)) {
|
||||
copy_to_iram(IRAM_PAYLOAD_BASE + i, g_ff_page, sizeof(g_ff_page));
|
||||
}
|
||||
}
|
||||
|
||||
static void inject_payload(void)
|
||||
{
|
||||
printf("injecting\n");
|
||||
spsmInitialize();
|
||||
smExit();
|
||||
if (R_SUCCEEDED(amsBpcInitialize()) && R_SUCCEEDED(amsBpcSetRebootPayload(g_reboot_payload, 0x24000))) {
|
||||
spsmShutdown(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void inject_payload_legacy(void)
|
||||
{
|
||||
printf("injecting (legacy)\n");
|
||||
clear_iram();
|
||||
for (size_t i = 0; i < IRAM_PAYLOAD_MAX_SIZE; i += 0x1000) {
|
||||
copy_to_iram(IRAM_PAYLOAD_BASE + i, &g_reboot_payload[i], 0x1000);
|
||||
}
|
||||
splSetConfig((SplConfigItem)65001, 2);
|
||||
}
|
||||
|
||||
int reboot_to_payload(const char* path, bool legacy)
|
||||
{
|
||||
bool can_reboot = true;
|
||||
FILE* f;
|
||||
f = fopen(path, "rb");
|
||||
if (f == NULL)
|
||||
can_reboot = false;
|
||||
else {
|
||||
fread(g_reboot_payload, 1, sizeof(g_reboot_payload), f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
if (can_reboot) {
|
||||
if (legacy)
|
||||
inject_payload_legacy();
|
||||
else
|
||||
inject_payload();
|
||||
}
|
||||
if (can_reboot)
|
||||
splExit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
200
source/tools_tab.cpp
Normal file
200
source/tools_tab.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#include "tools_tab.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "JC_page.hpp"
|
||||
#include "PC_page.hpp"
|
||||
#include "app_page.hpp"
|
||||
#include "cheats_page.hpp"
|
||||
#include "confirm_page.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "hide_tabs_page.hpp"
|
||||
#include "net_page.hpp"
|
||||
#include "payload_page.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "worker_page.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
namespace {
|
||||
constexpr const char AppVersion[] = APP_VERSION;
|
||||
}
|
||||
|
||||
ToolsTab::ToolsTab(const std::string& tag, const nlohmann::ordered_json& payloads, bool erista, const nlohmann::ordered_json& hideStatus) : brls::List()
|
||||
{
|
||||
if (!tag.empty() && tag != AppVersion) {
|
||||
brls::ListItem* updateApp = new brls::ListItem(fmt::format("menus/tools/update_app"_i18n, tag));
|
||||
std::string text("menus/tools/dl_app"_i18n + std::string(APP_URL));
|
||||
updateApp->getClickEvent()->subscribe([text, tag](brls::View* view) {
|
||||
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
|
||||
stagedFrame->setTitle("menus/common/updating"_i18n);
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage(stagedFrame, text));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/downloading"_i18n, []() { util::downloadArchive(APP_URL, contentType::app); }));
|
||||
stagedFrame->addStage(
|
||||
new WorkerPage(stagedFrame, "menus/common/extracting"_i18n, []() { util::extractArchive(contentType::app); }));
|
||||
stagedFrame->addStage(
|
||||
new ConfirmPage_AppUpdate(stagedFrame, "menus/common/all_done"_i18n));
|
||||
brls::Application::pushView(stagedFrame);
|
||||
});
|
||||
updateApp->setHeight(LISTITEM_HEIGHT);
|
||||
this->addView(updateApp);
|
||||
}
|
||||
|
||||
brls::ListItem* cheats = new brls::ListItem("menus/tools/cheats"_i18n);
|
||||
cheats->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::PopupFrame::open("menus/cheats/menu"_i18n, new CheatsPage(), "", "");
|
||||
});
|
||||
cheats->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* outdatedTitles = new brls::ListItem("menus/tools/outdated_titles"_i18n);
|
||||
outdatedTitles->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::PopupFrame::open("menus/tools/outdated_titles"_i18n, new AppPage_OutdatedTitles(), "menus/tools/outdated_titles_desc"_i18n, "");
|
||||
});
|
||||
outdatedTitles->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* JCcolor = new brls::ListItem("menus/tools/joy_cons"_i18n);
|
||||
JCcolor->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::Application::pushView(new JCPage());
|
||||
});
|
||||
JCcolor->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* PCcolor = new brls::ListItem("menus/tools/pro_cons"_i18n);
|
||||
PCcolor->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::Application::pushView(new PCPage());
|
||||
});
|
||||
PCcolor->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* rebootPayload = new brls::ListItem("menus/tools/inject_payloads"_i18n);
|
||||
rebootPayload->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::PopupFrame::open("menus/tools/inject_payloads"_i18n, new PayloadPage(), "", "");
|
||||
});
|
||||
rebootPayload->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* netSettings = new brls::ListItem("menus/tools/internet_settings"_i18n);
|
||||
netSettings->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::PopupFrame::open("menus/tools/internet_settings"_i18n, new NetPage(), "", "");
|
||||
});
|
||||
netSettings->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* browser = new brls::ListItem("menus/tools/browser"_i18n);
|
||||
browser->getClickEvent()->subscribe([](brls::View* view) {
|
||||
std::string url;
|
||||
if (brls::Swkbd::openForText([&url](std::string text) { url = text; }, "cheatslips.com e-mail", "", 256, "https://duckduckgo.com", 0, "Submit", "https://website.tld")) {
|
||||
util::openWebBrowser(url);
|
||||
}
|
||||
});
|
||||
browser->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* move = new brls::ListItem("menus/tools/batch_copy"_i18n);
|
||||
move->getClickEvent()->subscribe([](brls::View* view) {
|
||||
chdir("/");
|
||||
std::string error = "";
|
||||
if (std::filesystem::exists(COPY_FILES_TXT)) {
|
||||
error = fs::copyFiles(COPY_FILES_TXT);
|
||||
}
|
||||
else {
|
||||
error = "menus/tools/batch_copy_config_not_found"_i18n;
|
||||
}
|
||||
util::showDialogBoxInfo(error);
|
||||
});
|
||||
move->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* cleanUp = new brls::ListItem("menus/tools/clean_up"_i18n);
|
||||
cleanUp->getClickEvent()->subscribe([](brls::View* view) {
|
||||
std::filesystem::remove(AMS_FILENAME);
|
||||
std::filesystem::remove(APP_FILENAME);
|
||||
std::filesystem::remove(FIRMWARE_FILENAME);
|
||||
std::filesystem::remove(CHEATS_FILENAME);
|
||||
std::filesystem::remove(BOOTLOADER_FILENAME);
|
||||
std::filesystem::remove(CHEATS_VERSION);
|
||||
std::filesystem::remove(CUSTOM_FILENAME);
|
||||
fs::removeDir(AMS_DIRECTORY_PATH);
|
||||
fs::removeDir(SEPT_DIRECTORY_PATH);
|
||||
fs::removeDir(FW_DIRECTORY_PATH);
|
||||
util::showDialogBoxInfo("menus/common/all_done"_i18n);
|
||||
});
|
||||
cleanUp->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* language = new brls::ListItem("menus/tools/language"_i18n);
|
||||
language->getClickEvent()->subscribe([](brls::View* view) {
|
||||
std::vector<std::pair<std::string, std::string>> languages{
|
||||
std::make_pair("American English ({})", "en-US"),
|
||||
std::make_pair("日本語 ({})", "ja"),
|
||||
std::make_pair("Français ({})", "fr"),
|
||||
std::make_pair("Deutsch ({})", "de"),
|
||||
std::make_pair("Italiano ({})", "it"),
|
||||
std::make_pair("Español ({})", "es"),
|
||||
std::make_pair("Português ({})", "pt-BR"),
|
||||
std::make_pair("Nederlands ({})", "nl"),
|
||||
std::make_pair("Русский ({})", "ru"),
|
||||
std::make_pair("Română ({})", "ro"),
|
||||
std::make_pair("한국어 ({})", "ko"),
|
||||
std::make_pair("Polski ({})", "pl"),
|
||||
std::make_pair("简体中文 ({})", "zh-CN"),
|
||||
std::make_pair("繁體中文 ({})", "zh-TW"),
|
||||
std::make_pair("English (Great Britain) ({})", "en-GB"),
|
||||
std::make_pair("Français (Canada) ({})", "fr-CA"),
|
||||
std::make_pair("Español (Latinoamérica) ({})", "es-419"),
|
||||
std::make_pair("Português brasileiro ({})", "pt-BR"),
|
||||
std::make_pair("Traditional Chinese ({})", "zh-Hant"),
|
||||
std::make_pair("Simplified Chinese ({})", "zh-Hans")};
|
||||
brls::AppletFrame* appView = new brls::AppletFrame(true, true);
|
||||
brls::List* list = new brls::List();
|
||||
brls::ListItem* listItem;
|
||||
listItem = new brls::ListItem(fmt::format("System Default ({})", i18n::getCurrentLocale()));
|
||||
listItem->registerAction("menus/tools/language"_i18n, brls::Key::A, [] {
|
||||
std::filesystem::remove(LANGUAGE_JSON);
|
||||
brls::Application::quit();
|
||||
return true;
|
||||
});
|
||||
list->addView(listItem);
|
||||
for (auto& language : languages) {
|
||||
if (std::filesystem::exists(fmt::format(LOCALISATION_FILE, language.second))) {
|
||||
listItem = new brls::ListItem(fmt::format(language.first, language.second));
|
||||
listItem->registerAction("menus/tools/language"_i18n, brls::Key::A, [language] {
|
||||
json updatedLanguage = json::object();
|
||||
updatedLanguage["language"] = language.second;
|
||||
std::ofstream out(LANGUAGE_JSON);
|
||||
out << updatedLanguage.dump();
|
||||
brls::Application::quit();
|
||||
return true;
|
||||
});
|
||||
list->addView(listItem);
|
||||
}
|
||||
}
|
||||
appView->setContentView(list);
|
||||
brls::PopupFrame::open("menus/tools/language"_i18n, appView, "", "");
|
||||
});
|
||||
language->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* hideTabs = new brls::ListItem("menus/tools/hide_tabs"_i18n);
|
||||
hideTabs->getClickEvent()->subscribe([](brls::View* view) {
|
||||
brls::PopupFrame::open("menus/tools/hide_tabs"_i18n, new HideTabsPage(), "", "");
|
||||
});
|
||||
hideTabs->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
brls::ListItem* changelog = new brls::ListItem("menus/tools/changelog"_i18n);
|
||||
changelog->getClickEvent()->subscribe([](brls::View* view) {
|
||||
util::openWebBrowser(CHANGELOG_URL);
|
||||
});
|
||||
changelog->setHeight(LISTITEM_HEIGHT);
|
||||
|
||||
if (!util::getBoolValue(hideStatus, "cheats")) this->addView(cheats);
|
||||
// if (!util::getBoolValue(hideStatus, "outdatedtitles")) this->addView(outdatedTitles);
|
||||
// if (!util::getBoolValue(hideStatus, "jccolor")) this->addView(JCcolor);
|
||||
// if (!util::getBoolValue(hideStatus, "pccolor")) this->addView(PCcolor);
|
||||
// if (erista && !util::getBoolValue(hideStatus, "rebootpayload")) this->addView(rebootPayload);
|
||||
// if (!util::getBoolValue(hideStatus, "netsettings")) this->addView(netSettings);
|
||||
// if (!util::getBoolValue(hideStatus, "browser")) this->addView(browser);
|
||||
// if (!util::getBoolValue(hideStatus, "move")) this->addView(move);
|
||||
// if (!util::getBoolValue(hideStatus, "cleanup")) this->addView(cleanUp);
|
||||
// if (!util::getBoolValue(hideStatus, "language")) this->addView(language);
|
||||
// this->addView(hideTabs);
|
||||
// this->addView(changelog);
|
||||
}
|
||||
439
source/utils.cpp
Normal file
439
source/utils.cpp
Normal file
@@ -0,0 +1,439 @@
|
||||
#include "utils.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
#include "current_cfw.hpp"
|
||||
#include "download.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "main_frame.hpp"
|
||||
#include "progress_event.hpp"
|
||||
#include "reboot_payload.h"
|
||||
#include "unistd.h"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
namespace util {
|
||||
|
||||
bool isArchive(const std::string& path)
|
||||
{
|
||||
if (std::filesystem::exists(path)) {
|
||||
std::ifstream is(path, std::ifstream::binary);
|
||||
char zip_signature[4] = {0x50, 0x4B, 0x03, 0x04}; // zip signature header PK\3\4
|
||||
char signature[4];
|
||||
is.read(signature, 4);
|
||||
if (is.good() && std::equal(std::begin(signature), std::end(signature), std::begin(zip_signature), std::end(zip_signature))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void downloadArchive(const std::string& url, contentType type)
|
||||
{
|
||||
long status_code;
|
||||
downloadArchive(url, type, status_code);
|
||||
}
|
||||
|
||||
void downloadArchive(const std::string& url, contentType type, long& status_code)
|
||||
{
|
||||
fs::createTree(DOWNLOAD_PATH);
|
||||
switch (type) {
|
||||
case contentType::custom:
|
||||
status_code = download::downloadFile(url, CUSTOM_FILENAME, OFF);
|
||||
break;
|
||||
case contentType::cheats:
|
||||
status_code = download::downloadFile(url, CHEATS_FILENAME, OFF);
|
||||
break;
|
||||
case contentType::fw:
|
||||
status_code = download::downloadFile(url, FIRMWARE_FILENAME, OFF);
|
||||
break;
|
||||
case contentType::app:
|
||||
status_code = download::downloadFile(url, APP_FILENAME, OFF);
|
||||
break;
|
||||
case contentType::bootloaders:
|
||||
status_code = download::downloadFile(url, BOOTLOADER_FILENAME, OFF);
|
||||
break;
|
||||
case contentType::ams_cfw:
|
||||
status_code = download::downloadFile(url, AMS_FILENAME, OFF);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ProgressEvent::instance().setStatusCode(status_code);
|
||||
}
|
||||
|
||||
void showDialogBoxInfo(const std::string& text)
|
||||
{
|
||||
brls::Dialog* dialog;
|
||||
dialog = new brls::Dialog(text);
|
||||
brls::GenericEvent::Callback callback = [dialog](brls::View* view) {
|
||||
dialog->close();
|
||||
};
|
||||
dialog->addButton("menus/common/ok"_i18n, callback);
|
||||
dialog->setCancelable(true);
|
||||
dialog->open();
|
||||
}
|
||||
|
||||
int showDialogBoxBlocking(const std::string& text, const std::string& opt)
|
||||
{
|
||||
int result = -1;
|
||||
brls::Dialog* dialog = new brls::Dialog(text);
|
||||
brls::GenericEvent::Callback callback = [dialog, &result](brls::View* view) {
|
||||
result = 0;
|
||||
dialog->close();
|
||||
};
|
||||
dialog->addButton(opt, callback);
|
||||
dialog->setCancelable(false);
|
||||
dialog->open();
|
||||
while (result == -1) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(800000));
|
||||
return result;
|
||||
}
|
||||
|
||||
int showDialogBoxBlocking(const std::string& text, const std::string& opt1, const std::string& opt2)
|
||||
{
|
||||
int result = -1;
|
||||
brls::Dialog* dialog = new brls::Dialog(text);
|
||||
brls::GenericEvent::Callback callback1 = [dialog, &result](brls::View* view) {
|
||||
result = 0;
|
||||
dialog->close();
|
||||
};
|
||||
brls::GenericEvent::Callback callback2 = [dialog, &result](brls::View* view) {
|
||||
result = 1;
|
||||
dialog->close();
|
||||
};
|
||||
dialog->addButton(opt1, callback1);
|
||||
dialog->addButton(opt2, callback2);
|
||||
dialog->setCancelable(false);
|
||||
dialog->open();
|
||||
while (result == -1) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(800000));
|
||||
return result;
|
||||
}
|
||||
|
||||
void crashIfNotArchive(contentType type)
|
||||
{
|
||||
std::string filename;
|
||||
switch (type) {
|
||||
case contentType::custom:
|
||||
filename = CUSTOM_FILENAME;
|
||||
break;
|
||||
case contentType::cheats:
|
||||
filename = CHEATS_FILENAME;
|
||||
break;
|
||||
case contentType::fw:
|
||||
filename = FIRMWARE_FILENAME;
|
||||
break;
|
||||
case contentType::app:
|
||||
filename = APP_FILENAME;
|
||||
break;
|
||||
case contentType::bootloaders:
|
||||
filename = BOOTLOADER_FILENAME;
|
||||
break;
|
||||
case contentType::ams_cfw:
|
||||
filename = AMS_FILENAME;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
if (!isArchive(filename)) {
|
||||
ProgressEvent::instance().setStatusCode(406);
|
||||
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
|
||||
}
|
||||
}
|
||||
|
||||
void extractArchive(contentType type, const std::string& version)
|
||||
{
|
||||
chdir(ROOT_PATH);
|
||||
crashIfNotArchive(type);
|
||||
switch (type) {
|
||||
case contentType::cheats: {
|
||||
std::vector<std::string> titles = extract::getInstalledTitlesNs();
|
||||
titles = extract::excludeTitles(CHEATS_EXCLUDE, titles);
|
||||
extract::extractCheats(CHEATS_FILENAME, titles, CurrentCfw::running_cfw, version);
|
||||
break;
|
||||
}
|
||||
case contentType::fw:
|
||||
fs::removeDir(FIRMWARE_PATH);
|
||||
fs::createTree(FIRMWARE_PATH);
|
||||
extract::extract(FIRMWARE_FILENAME, FIRMWARE_PATH);
|
||||
break;
|
||||
case contentType::app:
|
||||
extract::extract(APP_FILENAME, CONFIG_PATH);
|
||||
fs::copyFile(ROMFS_FORWARDER, FORWARDER_PATH);
|
||||
break;
|
||||
case contentType::custom: {
|
||||
int preserveInis = showDialogBoxBlocking("menus/utils/overwrite_inis"_i18n, "menus/common/yes"_i18n, "menus/common/no"_i18n);
|
||||
extract::extract(CUSTOM_FILENAME, ROOT_PATH, preserveInis);
|
||||
break;
|
||||
}
|
||||
case contentType::bootloaders: {
|
||||
int preserveInis = showDialogBoxBlocking("menus/utils/overwrite_inis"_i18n, "menus/common/yes"_i18n, "menus/common/no"_i18n);
|
||||
extract::extract(BOOTLOADER_FILENAME, ROOT_PATH, preserveInis);
|
||||
break;
|
||||
}
|
||||
case contentType::ams_cfw: {
|
||||
int preserveInis = showDialogBoxBlocking("menus/utils/overwrite_inis"_i18n, "menus/common/yes"_i18n, "menus/common/no"_i18n);
|
||||
int deleteContents = showDialogBoxBlocking("menus/ams_update/delete_sysmodules_flags"_i18n, "menus/common/no"_i18n, "menus/common/yes"_i18n);
|
||||
if (deleteContents == 1)
|
||||
removeSysmodulesFlags(AMS_CONTENTS);
|
||||
extract::extract(AMS_FILENAME, ROOT_PATH, preserveInis);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (type == contentType::ams_cfw || type == contentType::bootloaders || type == contentType::custom)
|
||||
fs::copyFiles(COPY_FILES_TXT);
|
||||
}
|
||||
|
||||
std::string formatListItemTitle(const std::string& str, size_t maxScore)
|
||||
{
|
||||
size_t score = 0;
|
||||
for (size_t i = 0; i < str.length(); i++) {
|
||||
score += std::isupper(str[i]) ? 4 : 3;
|
||||
if (score > maxScore) {
|
||||
return str.substr(0, i - 1) + "\u2026";
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string formatApplicationId(u64 ApplicationId)
|
||||
{
|
||||
return fmt::format("{:016X}", ApplicationId);
|
||||
}
|
||||
|
||||
std::vector<std::string> fetchPayloads()
|
||||
{
|
||||
std::vector<std::string> payloadPaths;
|
||||
payloadPaths.push_back(ROOT_PATH);
|
||||
if (std::filesystem::exists(PAYLOAD_PATH)) payloadPaths.push_back(PAYLOAD_PATH);
|
||||
if (std::filesystem::exists(AMS_PATH)) payloadPaths.push_back(AMS_PATH);
|
||||
if (std::filesystem::exists(REINX_PATH)) payloadPaths.push_back(REINX_PATH);
|
||||
if (std::filesystem::exists(BOOTLOADER_PATH)) payloadPaths.push_back(BOOTLOADER_PATH);
|
||||
if (std::filesystem::exists(BOOTLOADER_PL_PATH)) payloadPaths.push_back(BOOTLOADER_PL_PATH);
|
||||
if (std::filesystem::exists(SXOS_PATH)) payloadPaths.push_back(SXOS_PATH);
|
||||
if (std::filesystem::exists(ROMFS_PATH)) payloadPaths.push_back(ROMFS_PATH);
|
||||
std::vector<std::string> res;
|
||||
for (const auto& path : payloadPaths) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path)) {
|
||||
if (entry.path().extension().string() == ".bin") {
|
||||
if (entry.path().string() != FUSEE_SECONDARY && entry.path().string() != FUSEE_MTC)
|
||||
res.push_back(entry.path().string().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void shutDown(bool reboot)
|
||||
{
|
||||
spsmInitialize();
|
||||
spsmShutdown(reboot);
|
||||
}
|
||||
|
||||
void rebootToPayload(const std::string& path)
|
||||
{
|
||||
reboot_to_payload(path.c_str(), CurrentCfw::running_cfw != CFW::ams);
|
||||
}
|
||||
|
||||
std::string getLatestTag(const std::string& url)
|
||||
{
|
||||
nlohmann::ordered_json tag;
|
||||
download::getRequest(url, tag, {"accept: application/vnd.github.v3+json"});
|
||||
if (tag.find("tag_name") != tag.end())
|
||||
return tag["tag_name"];
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string downloadFileToString(const std::string& url)
|
||||
{
|
||||
std::vector<uint8_t> bytes;
|
||||
download::downloadFile(url, bytes);
|
||||
std::string str(bytes.begin(), bytes.end());
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string getCheatsVersion()
|
||||
{
|
||||
std::string res = util::downloadFileToString(CHEATS_URL_VERSION);
|
||||
/* if (res == "" && isArchive(CHEATS_FILENAME)) {
|
||||
res = "offline";
|
||||
} */
|
||||
return res;
|
||||
}
|
||||
|
||||
void saveToFile(const std::string& text, const std::string& path)
|
||||
{
|
||||
std::ofstream file(path);
|
||||
file << text << std::endl;
|
||||
}
|
||||
|
||||
std::string readFile(const std::string& path)
|
||||
{
|
||||
std::string text = "";
|
||||
std::ifstream file(path);
|
||||
if (file.good()) {
|
||||
file >> text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string getAppPath()
|
||||
{
|
||||
if (envHasArgv()) {
|
||||
std::smatch match;
|
||||
std::string argv = (char*)envGetArgv();
|
||||
if (std::regex_match(argv, match, std::regex(NRO_PATH_REGEX))) {
|
||||
if (match.size() >= 2) {
|
||||
return match[1].str();
|
||||
}
|
||||
}
|
||||
}
|
||||
return NRO_PATH;
|
||||
}
|
||||
|
||||
void restartApp()
|
||||
{
|
||||
std::string path = "sdmc:" + getAppPath();
|
||||
std::string argv = "\"" + path + "\"";
|
||||
envSetNextLoad(path.c_str(), argv.c_str());
|
||||
romfsExit();
|
||||
brls::Application::quit();
|
||||
}
|
||||
|
||||
bool isErista()
|
||||
{
|
||||
SetSysProductModel model;
|
||||
setsysGetProductModel(&model);
|
||||
return (model == SetSysProductModel_Nx || model == SetSysProductModel_Copper);
|
||||
}
|
||||
|
||||
void removeSysmodulesFlags(const std::string& directory)
|
||||
{
|
||||
for (const auto& e : std::filesystem::recursive_directory_iterator(directory)) {
|
||||
if (e.path().string().find("boot2.flag") != std::string::npos) {
|
||||
std::filesystem::remove(e.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string lowerCase(const std::string& str)
|
||||
{
|
||||
std::string res = str;
|
||||
std::for_each(res.begin(), res.end(), [](char& c) {
|
||||
c = std::tolower(c);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string upperCase(const std::string& str)
|
||||
{
|
||||
std::string res = str;
|
||||
std::for_each(res.begin(), res.end(), [](char& c) {
|
||||
c = std::toupper(c);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string getErrorMessage(long status_code)
|
||||
{
|
||||
std::string res;
|
||||
switch (status_code) {
|
||||
case 500:
|
||||
res = fmt::format("{0:}: Internal Server Error", status_code);
|
||||
break;
|
||||
case 503:
|
||||
res = fmt::format("{0:}: Service Temporarily Unavailable", status_code);
|
||||
break;
|
||||
default:
|
||||
res = fmt::format("error: {0:}", status_code);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool isApplet()
|
||||
{
|
||||
AppletType at = appletGetAppletType();
|
||||
return at != AppletType_Application && at != AppletType_SystemApplication;
|
||||
}
|
||||
|
||||
std::set<std::string> getExistingCheatsTids()
|
||||
{
|
||||
std::string path = getContentsPath();
|
||||
std::set<std::string> res;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path)) {
|
||||
std::string cheatsPath = entry.path().string() + "/cheats";
|
||||
if (std::filesystem::exists(cheatsPath)) {
|
||||
res.insert(util::upperCase(cheatsPath.substr(cheatsPath.length() - 7 - 16, 16)));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string getContentsPath()
|
||||
{
|
||||
std::string path;
|
||||
switch (CurrentCfw::running_cfw) {
|
||||
case CFW::ams:
|
||||
path = std::string(AMS_PATH) + std::string(CONTENTS_PATH);
|
||||
break;
|
||||
case CFW::rnx:
|
||||
path = std::string(REINX_PATH) + std::string(CONTENTS_PATH);
|
||||
break;
|
||||
case CFW::sxos:
|
||||
path = std::string(SXOS_PATH) + std::string(TITLES_PATH);
|
||||
break;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool getBoolValue(const nlohmann::ordered_json& jsonFile, const std::string& key)
|
||||
{
|
||||
return (jsonFile.find(key) != jsonFile.end()) ? jsonFile.at(key).get<bool>() : false;
|
||||
}
|
||||
|
||||
const nlohmann::ordered_json getValueFromKey(const nlohmann::ordered_json& jsonFile, const std::string& key)
|
||||
{
|
||||
return (jsonFile.find(key) != jsonFile.end()) ? jsonFile.at(key) : nlohmann::ordered_json::object();
|
||||
}
|
||||
|
||||
int openWebBrowser(const std::string url)
|
||||
{
|
||||
Result rc = 0;
|
||||
int at = appletGetAppletType();
|
||||
if (at == AppletType_Application) { // Running as a title
|
||||
WebCommonConfig conf;
|
||||
WebCommonReply out;
|
||||
rc = webPageCreate(&conf, url.c_str());
|
||||
if (R_FAILED(rc))
|
||||
return rc;
|
||||
webConfigSetJsExtension(&conf, true);
|
||||
webConfigSetPageCache(&conf, true);
|
||||
webConfigSetBootLoadingIcon(&conf, true);
|
||||
webConfigSetWhitelist(&conf, ".*");
|
||||
rc = webConfigShow(&conf, &out);
|
||||
if (R_FAILED(rc))
|
||||
return rc;
|
||||
}
|
||||
else { // Running under applet
|
||||
showDialogBoxInfo("menus/utils/applet_webbrowser"_i18n);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
79
source/warning_page.cpp
Normal file
79
source/warning_page.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "warning_page.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "main_frame.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
WarningPage::WarningPage(const std::string& text)
|
||||
{
|
||||
fs::createTree(CONFIG_PATH);
|
||||
std::ofstream hiddenFile(HIDDEN_AIO_FILE);
|
||||
this->button = (new brls::Button(brls::ButtonStyle::PRIMARY))->setLabel("menus/common/continue"_i18n);
|
||||
this->button->setParent(this);
|
||||
this->button->getClickEvent()->subscribe([this](View* view) {
|
||||
brls::Application::pushView(new MainFrame());
|
||||
});
|
||||
|
||||
this->label = new brls::Label(brls::LabelStyle::REGULAR, text, true);
|
||||
this->label->setHorizontalAlign(NVG_ALIGN_LEFT);
|
||||
//this->setBackground(brls::ViewBackground::DEBUG);
|
||||
this->label->setParent(this);
|
||||
|
||||
this->registerAction("", brls::Key::B, [this] { return true; });
|
||||
}
|
||||
|
||||
void WarningPage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
|
||||
{
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto missing = std::max(1l - std::chrono::duration_cast<std::chrono::seconds>(end - start).count(), 0l);
|
||||
auto text = std::string("menus/common/continue"_i18n);
|
||||
if (missing > 0) {
|
||||
this->button->setLabel(text + " (" + std::to_string(missing) + ")");
|
||||
this->button->setState(brls::ButtonState::DISABLED);
|
||||
}
|
||||
else {
|
||||
this->button->setLabel(text);
|
||||
this->button->setState(brls::ButtonState::ENABLED);
|
||||
}
|
||||
this->button->invalidate();
|
||||
|
||||
this->label->frame(ctx);
|
||||
this->button->frame(ctx);
|
||||
}
|
||||
|
||||
brls::View* WarningPage::getDefaultFocus()
|
||||
{
|
||||
return this->button;
|
||||
}
|
||||
|
||||
void WarningPage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
|
||||
{
|
||||
this->label->setWidth(0.8f * this->width);
|
||||
this->label->invalidate(true);
|
||||
this->label->setBoundaries(
|
||||
this->x + this->width / 2 - this->label->getWidth() / 2,
|
||||
this->y + (this->height - this->label->getHeight() - this->y - style->CrashFrame.buttonHeight) / 2,
|
||||
this->label->getWidth(),
|
||||
this->label->getHeight());
|
||||
|
||||
this->button->setBoundaries(
|
||||
this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2,
|
||||
this->y + (this->height - style->CrashFrame.buttonHeight * 3),
|
||||
style->CrashFrame.buttonWidth,
|
||||
style->CrashFrame.buttonHeight);
|
||||
this->button->invalidate();
|
||||
|
||||
start = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(150);
|
||||
}
|
||||
|
||||
WarningPage::~WarningPage()
|
||||
{
|
||||
delete this->label;
|
||||
delete this->button;
|
||||
}
|
||||
136
source/worker_page.cpp
Normal file
136
source/worker_page.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "worker_page.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "download.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "main_frame.hpp"
|
||||
#include "progress_event.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace i18n = brls::i18n;
|
||||
using namespace i18n::literals;
|
||||
|
||||
WorkerPage::WorkerPage(brls::StagedAppletFrame* frame, const std::string& text, worker_func_t worker_func) : frame(frame), workerFunc(worker_func), text(text)
|
||||
{
|
||||
this->progressDisp = new brls::ProgressDisplay();
|
||||
this->progressDisp->setParent(this);
|
||||
|
||||
this->label = new brls::Label(brls::LabelStyle::DIALOG, text, true);
|
||||
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
|
||||
this->label->setParent(this);
|
||||
|
||||
this->button = new brls::Button(brls::ButtonStyle::REGULAR);
|
||||
this->button->setParent(this);
|
||||
|
||||
this->registerAction("menus/common/cancel"_i18n, brls::Key::B, [this] {
|
||||
ProgressEvent::instance().setInterupt(true);
|
||||
return true;
|
||||
});
|
||||
this->registerAction("", brls::Key::A, [this] { return true; });
|
||||
this->registerAction("", brls::Key::PLUS, [this] { return true; });
|
||||
}
|
||||
|
||||
std::string formatLabelText(double speed, double fileSizeCurrent, double fileSizeFinal)
|
||||
{
|
||||
double fileSizeCurrentMB = fileSizeCurrent / 0x100000;
|
||||
double fileSizeFinalMB = fileSizeFinal / 0x100000;
|
||||
double speedMB = speed / 0x100000;
|
||||
|
||||
double timeRemaining = (fileSizeFinal - fileSizeCurrent) / speed;
|
||||
int hours = static_cast<int>(timeRemaining / 3600);
|
||||
int minutes = static_cast<int>((timeRemaining - hours * 3600) / 60);
|
||||
int seconds = static_cast<int>(timeRemaining - hours * 3600 - minutes * 60);
|
||||
|
||||
std::string labelText = fmt::format("menus/worker/download_progress"_i18n, fileSizeCurrentMB, fileSizeFinalMB, speedMB);
|
||||
if (speedMB > 0) {
|
||||
std::string eta;
|
||||
if (hours > 0)
|
||||
eta += fmt::format("{}h ", hours);
|
||||
if (minutes > 0)
|
||||
eta += fmt::format("{}m ", minutes);
|
||||
|
||||
eta += fmt::format("{}s", seconds);
|
||||
labelText += "\n" + fmt::format("menus/worker/time_left"_i18n, eta);
|
||||
}
|
||||
|
||||
return labelText;
|
||||
}
|
||||
|
||||
void WorkerPage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
|
||||
{
|
||||
if (this->draw_page) {
|
||||
if (!this->workStarted) {
|
||||
this->workStarted = true;
|
||||
appletSetMediaPlaybackState(true);
|
||||
appletBeginBlockingHomeButton(0);
|
||||
ProgressEvent::instance().reset();
|
||||
workerThread = new std::thread(&WorkerPage::doWork, this);
|
||||
}
|
||||
else if (ProgressEvent::instance().finished()) {
|
||||
appletEndBlockingHomeButton();
|
||||
appletSetMediaPlaybackState(false);
|
||||
if (ProgressEvent::instance().getStatusCode() > 399) {
|
||||
this->draw_page = false;
|
||||
brls::Application::crash(fmt::format("menus/errors/error_message"_i18n, util::getErrorMessage(ProgressEvent::instance().getStatusCode())));
|
||||
}
|
||||
if (ProgressEvent::instance().getInterupt()) {
|
||||
brls::Application::pushView(new MainFrame());
|
||||
}
|
||||
else {
|
||||
ProgressEvent::instance().setStatusCode(0);
|
||||
frame->nextStage();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->progressDisp->setProgress(ProgressEvent::instance().getStep(), ProgressEvent::instance().getMax());
|
||||
this->progressDisp->frame(ctx);
|
||||
if (ProgressEvent::instance().getTotal()) {
|
||||
this->label->setText(formatLabelText(ProgressEvent::instance().getSpeed(), ProgressEvent::instance().getNow(), ProgressEvent::instance().getTotal()));
|
||||
}
|
||||
this->label->frame(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerPage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
|
||||
{
|
||||
this->label->setWidth(roundf((float)this->width * style->CrashFrame.labelWidth));
|
||||
|
||||
this->label->setBoundaries(
|
||||
this->x + this->width / 2 - this->label->getWidth() / 2,
|
||||
this->y + (this->height - style->AppletFrame.footerHeight) / 2 - 30,
|
||||
this->label->getWidth(),
|
||||
this->label->getHeight());
|
||||
|
||||
this->progressDisp->setBoundaries(
|
||||
this->x + this->width / 2 - style->CrashFrame.buttonWidth,
|
||||
this->y + this->height / 2,
|
||||
style->CrashFrame.buttonWidth * 2,
|
||||
style->CrashFrame.buttonHeight);
|
||||
}
|
||||
|
||||
void WorkerPage::doWork()
|
||||
{
|
||||
if (this->workerFunc)
|
||||
this->workerFunc();
|
||||
}
|
||||
|
||||
brls::View* WorkerPage::getDefaultFocus()
|
||||
{
|
||||
return this->button;
|
||||
}
|
||||
|
||||
WorkerPage::~WorkerPage()
|
||||
{
|
||||
if (this->workStarted && workerThread->joinable()) {
|
||||
this->workerThread->join();
|
||||
if (this->workerThread)
|
||||
delete this->workerThread;
|
||||
}
|
||||
delete this->progressDisp;
|
||||
delete this->label;
|
||||
delete this->button;
|
||||
}
|
||||
Reference in New Issue
Block a user