Initial Commit

This commit is contained in:
2026-03-05 20:18:29 +01:00
commit 5a4d3ee8e0
901 changed files with 296682 additions and 0 deletions

49
source/JC_page.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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);
}

View 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
View 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
View 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
View 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
View 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);
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}