add gc_menu, add progress, icon, time remaining to progress bar (see full commit message).
- fix ignore distribution bit doing nothing. - fix yati failing to parse control nca causing the transfer to abort. - yati now uses ncm rather than ns to get the latest app version. - improve ui::list input handling (it handles directional buttons now). - progress bar displays speed and time remaining. - added gc menu (taken from my gc installer nx and gci).
This commit is contained in:
@@ -1,7 +1,18 @@
|
||||
#include "ui/menus/main_menu.hpp"
|
||||
#include "ui/error_box.hpp"
|
||||
#include "ui/option_box.hpp"
|
||||
#include "ui/bubbles.hpp"
|
||||
#include "ui/sidebar.hpp"
|
||||
#include "ui/popup_list.hpp"
|
||||
#include "ui/option_box.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
#include "ui/error_box.hpp"
|
||||
|
||||
#include "ui/menus/main_menu.hpp"
|
||||
#include "ui/menus/irs_menu.hpp"
|
||||
#include "ui/menus/themezer.hpp"
|
||||
#include "ui/menus/ghdl.hpp"
|
||||
#include "ui/menus/usb_menu.hpp"
|
||||
#include "ui/menus/ftp_menu.hpp"
|
||||
#include "ui/menus/gc_menu.hpp"
|
||||
|
||||
#include "app.hpp"
|
||||
#include "log.hpp"
|
||||
@@ -15,6 +26,7 @@
|
||||
#include "defines.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "ftpsrv_helper.hpp"
|
||||
#include "web.hpp"
|
||||
|
||||
#include <nanovg_dk.h>
|
||||
#include <minIni.h>
|
||||
@@ -1374,15 +1386,6 @@ App::App(const char* argv0) {
|
||||
const long old_launch_count = ini_getl(GetExePath(), "launch_count", 0, App::PLAYLOG_PATH);
|
||||
ini_putl(GetExePath(), "launch_count", old_launch_count + 1, App::PLAYLOG_PATH);
|
||||
|
||||
s64 sd_free_space;
|
||||
if (R_SUCCEEDED(fs.GetFreeSpace("/", &sd_free_space))) {
|
||||
log_write("sd_free_space: %zd\n", sd_free_space);
|
||||
}
|
||||
s64 sd_total_space;
|
||||
if (R_SUCCEEDED(fs.GetTotalSpace("/", &sd_total_space))) {
|
||||
log_write("sd_total_space: %zd\n", sd_total_space);
|
||||
}
|
||||
|
||||
// load default image
|
||||
if (R_SUCCEEDED(romfsInit())) {
|
||||
ON_SCOPE_EXIT(romfsExit());
|
||||
@@ -1437,6 +1440,179 @@ void App::PlaySoundEffect(SoundEffect effect) {
|
||||
plsrPlayerPlay(id);
|
||||
}
|
||||
|
||||
void App::DisplayThemeOptions(bool left_side) {
|
||||
ui::SidebarEntryArray::Items theme_items{};
|
||||
const auto theme_meta = App::GetThemeMetaList();
|
||||
for (auto& p : theme_meta) {
|
||||
theme_items.emplace_back(p.name);
|
||||
}
|
||||
|
||||
auto options = std::make_shared<ui::Sidebar>("Theme Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Select Theme"_i18n, theme_items, [theme_items](s64& index_out){
|
||||
App::SetTheme(index_out);
|
||||
}, App::GetThemeIndex()));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Music"_i18n, App::GetThemeMusicEnable(), [](bool& enable){
|
||||
App::SetThemeMusicEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("12 Hour Time"_i18n, App::Get12HourTimeEnable(), [](bool& enable){
|
||||
App::Set12HourTimeEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
}
|
||||
|
||||
void App::DisplayNetworkOptions(bool left_side) {
|
||||
|
||||
}
|
||||
|
||||
void App::DisplayMiscOptions(bool left_side) {
|
||||
auto options = std::make_shared<ui::Sidebar>("Misc Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Themezer"_i18n, [](){
|
||||
App::Push(std::make_shared<ui::menu::themezer::Menu>());
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("GitHub"_i18n, [](){
|
||||
App::Push(std::make_shared<ui::menu::gh::Menu>());
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Irs"_i18n, [](){
|
||||
App::Push(std::make_shared<ui::menu::irs::Menu>());
|
||||
}));
|
||||
|
||||
if (App::IsApplication()) {
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Web"_i18n, [](){
|
||||
WebShow("https://lite.duckduckgo.com/lite");
|
||||
}));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_install.Get()) {
|
||||
if (App::GetFtpEnable()) {
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Ftp Install"_i18n, [](){
|
||||
App::Push(std::make_shared<ui::menu::ftp::Menu>());
|
||||
}));
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Usb Install"_i18n, [](){
|
||||
App::Push(std::make_shared<ui::menu::usb::Menu>());
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("GameCard Install"_i18n, [](){
|
||||
App::Push(std::make_shared<ui::menu::gc::Menu>());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void App::DisplayAdvancedOptions(bool left_side) {
|
||||
auto options = std::make_shared<ui::Sidebar>("Advanced Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
ui::SidebarEntryArray::Items text_scroll_speed_items;
|
||||
text_scroll_speed_items.push_back("Slow"_i18n);
|
||||
text_scroll_speed_items.push_back("Normal"_i18n);
|
||||
text_scroll_speed_items.push_back("Fast"_i18n);
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){
|
||||
App::SetLogEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Replace hbmenu on exit"_i18n, App::GetReplaceHbmenuEnable(), [](bool& enable){
|
||||
App::SetReplaceHbmenuEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [](s64& index_out){
|
||||
App::SetTextScrollSpeed(index_out);
|
||||
}, (s64)App::GetTextScrollSpeed()));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Install options"_i18n, [left_side](){
|
||||
App::DisplayInstallOptions(left_side);
|
||||
}));
|
||||
}
|
||||
|
||||
void App::DisplayInstallOptions(bool left_side) {
|
||||
auto options = std::make_shared<ui::Sidebar>("Install Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
ui::SidebarEntryArray::Items install_items;
|
||||
install_items.push_back("System memory"_i18n);
|
||||
install_items.push_back("microSD card"_i18n);
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Enable"_i18n, App::GetInstallEnable(), [](bool& enable){
|
||||
App::SetInstallEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Show install warning"_i18n, App::GetInstallPrompt(), [](bool& enable){
|
||||
App::SetInstallPrompt(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Install location"_i18n, install_items, [](s64& index_out){
|
||||
App::SetInstallSdEnable(index_out);
|
||||
}, (s64)App::GetInstallSdEnable()));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [](bool& enable){
|
||||
App::GetApp()->m_allow_downgrade.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip if already installed"_i18n, App::GetApp()->m_skip_if_already_installed.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_if_already_installed.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Ticket only"_i18n, App::GetApp()->m_ticket_only.Get(), [](bool& enable){
|
||||
App::GetApp()->m_ticket_only.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip base"_i18n, App::GetApp()->m_skip_base.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_base.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip Patch"_i18n, App::GetApp()->m_skip_patch.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_patch.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip addon"_i18n, App::GetApp()->m_skip_addon.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_addon.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip data patch"_i18n, App::GetApp()->m_skip_data_patch.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_data_patch.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip ticket"_i18n, App::GetApp()->m_skip_ticket.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_ticket.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("skip NCA hash verify"_i18n, App::GetApp()->m_skip_nca_hash_verify.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_nca_hash_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip RSA header verify"_i18n, App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_rsa_header_fixed_key_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip RSA NPDM verify"_i18n, App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Ignore distribution bit"_i18n, App::GetApp()->m_ignore_distribution_bit.Get(), [](bool& enable){
|
||||
App::GetApp()->m_ignore_distribution_bit.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Convert to standard crypto"_i18n, App::GetApp()->m_convert_to_standard_crypto.Get(), [](bool& enable){
|
||||
App::GetApp()->m_convert_to_standard_crypto.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Lower master key"_i18n, App::GetApp()->m_lower_master_key.Get(), [](bool& enable){
|
||||
App::GetApp()->m_lower_master_key.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Lower system version"_i18n, App::GetApp()->m_lower_system_version.Get(), [](bool& enable){
|
||||
App::GetApp()->m_lower_system_version.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
}
|
||||
|
||||
App::~App() {
|
||||
log_write("starting to exit\n");
|
||||
|
||||
|
||||
@@ -1005,7 +1005,7 @@ auto install_forwarder(ui::ProgressBox* pbox, OwoConfig& config, NcmStorageId st
|
||||
}
|
||||
|
||||
auto install_forwarder(OwoConfig& config, NcmStorageId storage_id) -> Result {
|
||||
App::Push(std::make_shared<ui::ProgressBox>("Installing Forwarder"_i18n, [config, storage_id](auto pbox) mutable -> bool {
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing Forwarder"_i18n, config.name, [config, storage_id](auto pbox) mutable -> bool {
|
||||
return R_SUCCEEDED(install_forwarder(pbox, config, storage_id));
|
||||
}));
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -34,8 +34,35 @@ auto List::ClampY(float y, s64 count) const -> float {
|
||||
return y;
|
||||
}
|
||||
|
||||
void List::OnUpdate(Controller* controller, TouchInfo* touch, s64 count, TouchCallback callback) {
|
||||
if (touch->is_clicked && touch->in_range(GetPos())) {
|
||||
void List::OnUpdate(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback) {
|
||||
const auto page_up_button = m_row == 1 ? Button::DPAD_LEFT : Button::L2;
|
||||
const auto page_down_button = m_row == 1 ? Button::DPAD_RIGHT : Button::R2;
|
||||
|
||||
if (controller->GotDown(Button::DOWN)) {
|
||||
if (ScrollDown(index, m_row, count)) {
|
||||
callback(false, index);
|
||||
}
|
||||
} else if (controller->GotDown(Button::UP)) {
|
||||
if (ScrollUp(index, m_row, count)) {
|
||||
callback(false, index);
|
||||
}
|
||||
} else if (controller->GotDown(page_down_button)) {
|
||||
if (ScrollDown(index, m_page, count)) {
|
||||
callback(false, index);
|
||||
}
|
||||
} else if (controller->GotDown(page_up_button)) {
|
||||
if (ScrollUp(index, m_page, count)) {
|
||||
callback(false, index);
|
||||
}
|
||||
} else if (m_row > 1 && controller->GotDown(Button::RIGHT)) {
|
||||
if (count && index < (count - 1) && (index + 1) % m_row != 0) {
|
||||
callback(false, index + 1);
|
||||
}
|
||||
} else if (m_row > 1 && controller->GotDown(Button::LEFT)) {
|
||||
if (count && index != 0 && (index % m_row) != 0) {
|
||||
callback(false, index - 1);
|
||||
}
|
||||
} else if (touch->is_clicked && touch->in_range(GetPos())) {
|
||||
auto v = m_v;
|
||||
v.y -= ClampY(m_yoff + m_y_prog, count);
|
||||
|
||||
@@ -63,7 +90,7 @@ void List::OnUpdate(Controller* controller, TouchInfo* touch, s64 count, TouchCa
|
||||
vv.h = std::min(v.y + v.h, m_pos.y + m_pos.h) - v.y;
|
||||
|
||||
if (touch->in_range(vv)) {
|
||||
callback(i);
|
||||
callback(true, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,18 +236,15 @@ void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, float x, f
|
||||
bool crop = false;
|
||||
if (iw < w || ih < h) {
|
||||
rounded_image = false;
|
||||
gfx::drawRect(vg, x, y, w, h, nvgRGB(i.first_pixel[0], i.first_pixel[1], i.first_pixel[2]), rounded);
|
||||
gfx::drawRect(vg, x, y, w, h, nvgRGB(i.first_pixel[0], i.first_pixel[1], i.first_pixel[2]), rounded ? 15 : 0);
|
||||
}
|
||||
if (iw > w || ih > h) {
|
||||
crop = true;
|
||||
nvgSave(vg);
|
||||
nvgIntersectScissor(vg, x, y, w, h);
|
||||
}
|
||||
if (rounded_image) {
|
||||
gfx::drawImageRounded(vg, ix, iy, iw, ih, i.image);
|
||||
} else {
|
||||
gfx::drawImage(vg, ix, iy, iw, ih, i.image);
|
||||
}
|
||||
|
||||
gfx::drawImage(vg, ix, iy, iw, ih, i.image, rounded_image ? 15 : 0);
|
||||
if (crop) {
|
||||
nvgRestore(vg);
|
||||
}
|
||||
@@ -769,7 +766,7 @@ void EntryMenu::UpdateOptions() {
|
||||
};
|
||||
|
||||
const auto install = [this](){
|
||||
App::Push(std::make_shared<ProgressBox>("Installing "_i18n + m_entry.title, [this](auto pbox){
|
||||
App::Push(std::make_shared<ProgressBox>(m_entry.image.image, "Downloading "_i18n, m_entry.title, [this](auto pbox){
|
||||
return InstallApp(pbox, m_entry);
|
||||
}, [this](bool success){
|
||||
if (success) {
|
||||
@@ -782,7 +779,7 @@ void EntryMenu::UpdateOptions() {
|
||||
};
|
||||
|
||||
const auto uninstall = [this](){
|
||||
App::Push(std::make_shared<ProgressBox>("Uninstalling "_i18n + m_entry.title, [this](auto pbox){
|
||||
App::Push(std::make_shared<ProgressBox>(m_entry.image.image, "Uninstalling "_i18n, m_entry.title, [this](auto pbox){
|
||||
return UninstallApp(pbox, m_entry);
|
||||
}, [this](bool success){
|
||||
if (success) {
|
||||
@@ -854,48 +851,6 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
|
||||
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/screens");
|
||||
|
||||
this->SetActions(
|
||||
std::make_pair(Button::RIGHT, Action{[this](){
|
||||
if (m_entries_current.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_index < (m_entries_current.size() - 1) && (m_index + 1) % 3 != 0) {
|
||||
SetIndex(m_index + 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
log_write("moved right\n");
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::LEFT, Action{[this](){
|
||||
if (m_entries_current.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_index != 0 && (m_index % 3) != 0) {
|
||||
SetIndex(m_index - 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
log_write("moved left\n");
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 3, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 3, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::R2, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 9, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::L2, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 9, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::A, Action{"Info"_i18n, [this](){
|
||||
if (m_entries_current.empty()) {
|
||||
// log_write("pushing A when empty: size: %zu count: %zu\n", repo_json.size(), m_entries_current.size());
|
||||
@@ -983,8 +938,8 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_entries_current.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
m_list->OnUpdate(controller, touch, m_index, m_entries_current.size(), [this](bool touch, auto i) {
|
||||
if (touch && m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
@@ -1096,16 +1051,16 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
float i_size = 22;
|
||||
switch (e.status) {
|
||||
case EntryStatus::Get:
|
||||
gfx::drawImageRounded(vg, x + w - 30.f, y + 110, i_size, i_size, m_get.image);
|
||||
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_get.image, 15);
|
||||
break;
|
||||
case EntryStatus::Installed:
|
||||
gfx::drawImageRounded(vg, x + w - 30.f, y + 110, i_size, i_size, m_installed.image);
|
||||
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_installed.image, 15);
|
||||
break;
|
||||
case EntryStatus::Local:
|
||||
gfx::drawImageRounded(vg, x + w - 30.f, y + 110, i_size, i_size, m_local.image);
|
||||
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_local.image, 15);
|
||||
break;
|
||||
case EntryStatus::Update:
|
||||
gfx::drawImageRounded(vg, x + w - 30.f, y + 110, i_size, i_size, m_update.image);
|
||||
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_update.image, 15);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -279,26 +279,6 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
m_selected_count--;
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 1, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 1, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_RIGHT, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 8, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_LEFT, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 8, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::A, Action{"Open"_i18n, [this](){
|
||||
if (m_entries_current.empty()) {
|
||||
return;
|
||||
@@ -626,8 +606,8 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_entries_current.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
m_list->OnUpdate(controller, touch, m_index, m_entries_current.size(), [this](bool touch, auto i) {
|
||||
if (touch && m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
@@ -802,7 +782,7 @@ void Menu::InstallForwarder() {
|
||||
if (op_index) {
|
||||
const auto assoc = assoc_list[*op_index];
|
||||
log_write("pushing it\n");
|
||||
App::Push(std::make_shared<ProgressBox>("Installing Forwarder"_i18n, [assoc, this](auto pbox) -> bool {
|
||||
App::Push(std::make_shared<ProgressBox>(0, "Installing Forwarder"_i18n, GetEntry().name, [assoc, this](auto pbox) -> bool {
|
||||
log_write("inside callback\n");
|
||||
|
||||
NroEntry nro{};
|
||||
@@ -829,6 +809,7 @@ void Menu::InstallForwarder() {
|
||||
// config.name = file_name;
|
||||
config.nacp = nro.nacp;
|
||||
config.icon = GetRomIcon(m_fs.get(), pbox, file_name, db_indexs, nro);
|
||||
pbox->SetImageDataConst(config.icon);
|
||||
|
||||
return R_SUCCEEDED(App::Install(pbox, config));
|
||||
}));
|
||||
@@ -849,7 +830,7 @@ void Menu::InstallFiles(const std::vector<FileEntry>& targets) {
|
||||
if (op_index && *op_index) {
|
||||
App::PopToMenu();
|
||||
|
||||
App::Push(std::make_shared<ui::ProgressBox>("Installing App"_i18n, [this, targets](auto pbox) mutable -> bool {
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this, targets](auto pbox) mutable -> bool {
|
||||
for (auto& e : targets) {
|
||||
const auto rc = yati::InstallFromFile(pbox, &m_fs->m_fs, GetNewPath(e));
|
||||
if (rc == yati::Result_Cancelled) {
|
||||
@@ -1226,7 +1207,7 @@ void Menu::OnDeleteCallback() {
|
||||
Scan(m_path);
|
||||
log_write("did delete\n");
|
||||
} else {
|
||||
App::Push(std::make_shared<ProgressBox>("Deleting"_i18n, [this](auto pbox){
|
||||
App::Push(std::make_shared<ProgressBox>(0, "Deleting"_i18n, "", [this](auto pbox){
|
||||
FsDirCollections collections;
|
||||
|
||||
// build list of dirs / files
|
||||
@@ -1319,7 +1300,7 @@ void Menu::OnPasteCallback() {
|
||||
Scan(m_path);
|
||||
log_write("did paste\n");
|
||||
} else {
|
||||
App::Push(std::make_shared<ProgressBox>("Pasting"_i18n, [this](auto pbox){
|
||||
App::Push(std::make_shared<ProgressBox>(0, "Pasting"_i18n, "", [this](auto pbox){
|
||||
|
||||
if (m_selected_type == SelectedType::Cut) {
|
||||
for (const auto& p : m_selected_files) {
|
||||
|
||||
@@ -146,6 +146,10 @@ Menu::Menu() : MenuBase{"FTP Install (EXPERIMENTAL)"_i18n} {
|
||||
SetPop();
|
||||
}});
|
||||
|
||||
SetAction(Button::X, Action{"Options"_i18n, [this](){
|
||||
App::DisplayInstallOptions(false);
|
||||
}});
|
||||
|
||||
mutexInit(&m_mutex);
|
||||
ftpsrv::InitInstallMode(this, OnInstallStart, OnInstallWrite, OnInstallClose);
|
||||
|
||||
@@ -183,7 +187,7 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
log_write("set to progress\n");
|
||||
m_state = State::Progress;
|
||||
log_write("got connection\n");
|
||||
App::Push(std::make_shared<ui::ProgressBox>("Installing App"_i18n, [this](auto pbox) mutable -> bool {
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) mutable -> bool {
|
||||
log_write("inside progress box\n");
|
||||
const auto rc = yati::InstallFromSource(pbox, m_source, m_source->m_path);
|
||||
if (R_FAILED(rc)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ui/menus/gc_menu.hpp"
|
||||
#include "yati/yati.hpp"
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "app.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
@@ -10,26 +11,103 @@
|
||||
namespace sphaira::ui::menu::gc {
|
||||
namespace {
|
||||
|
||||
const char *g_option_list[] = {
|
||||
"Nand Install",
|
||||
"SD Card Install",
|
||||
"Exit",
|
||||
};
|
||||
|
||||
struct HashStr {
|
||||
char str[0x21];
|
||||
};
|
||||
|
||||
HashStr hexIdToStr(auto id) {
|
||||
HashStr str{};
|
||||
const auto id_lower = std::byteswap(*(u64*)id.c);
|
||||
const auto id_upper = std::byteswap(*(u64*)(id.c + 0x8));
|
||||
std::snprintf(str.str, 0x21, "%016lx%016lx", id_lower, id_upper);
|
||||
return str;
|
||||
}
|
||||
|
||||
// @Gc is the mount point, S is for secure partion, the remaining is the
|
||||
// the gamecard handle value in lower-case hex.
|
||||
auto BuildGcPath(const char* name, const FsGameCardHandle* handle, FsGameCardPartition partiton = FsGameCardPartition_Secure) -> fs::FsPath {
|
||||
static const char mount_parition[] = {
|
||||
[FsGameCardPartition_Update] = 'U',
|
||||
[FsGameCardPartition_Normal] = 'N',
|
||||
[FsGameCardPartition_Secure] = 'S',
|
||||
[FsGameCardPartition_Logo] = 'L',
|
||||
};
|
||||
|
||||
fs::FsPath path;
|
||||
std::snprintf(path, sizeof(path), "@Gc%c%08x://%s", mount_parition[partiton], handle->value, name);
|
||||
return path;
|
||||
}
|
||||
|
||||
auto InRange(u64 off, u64 offset, u64 size) -> bool {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
|
||||
struct GcSource final : yati::source::Base {
|
||||
GcSource(const yati::container::Collections& collections, fs::FsNativeGameCard* fs);
|
||||
GcSource(const ApplicationEntry& entry, fs::FsNativeGameCard* fs, bool sd_install);
|
||||
~GcSource();
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read);
|
||||
|
||||
const yati::container::Collections& m_collections;
|
||||
yati::container::Collections m_collections{};
|
||||
yati::ConfigOverride m_config{};
|
||||
fs::FsNativeGameCard* m_fs{};
|
||||
FsFile m_file{};
|
||||
s64 m_offset{};
|
||||
s64 m_size{};
|
||||
};
|
||||
|
||||
GcSource::GcSource(const yati::container::Collections& collections, fs::FsNativeGameCard* fs)
|
||||
: m_collections{collections}
|
||||
, m_fs{fs} {
|
||||
GcSource::GcSource(const ApplicationEntry& entry, fs::FsNativeGameCard* fs, bool sd_install)
|
||||
: m_fs{fs} {
|
||||
m_offset = -1;
|
||||
|
||||
s64 offset{};
|
||||
const auto add_collections = [&](const auto& collections) {
|
||||
for (auto collection : collections) {
|
||||
collection.offset = offset;
|
||||
m_collections.emplace_back(collection);
|
||||
offset += collection.size;
|
||||
}
|
||||
};
|
||||
|
||||
const auto add_entries = [&](const auto& entries) {
|
||||
for (auto& e : entries) {
|
||||
add_collections(e);
|
||||
}
|
||||
};
|
||||
|
||||
// yati can handle all of this for use, however, yati lacks information
|
||||
// for ncas until it installs the cnmt and parses it.
|
||||
// as we already have this info, we can only send yati what we want to install.
|
||||
if (App::GetApp()->m_ticket_only.Get()) {
|
||||
add_collections(entry.tickets);
|
||||
} else {
|
||||
if (!App::GetApp()->m_skip_base.Get()) {
|
||||
add_entries(entry.application);
|
||||
}
|
||||
if (!App::GetApp()->m_skip_patch.Get()) {
|
||||
add_entries(entry.patch);
|
||||
}
|
||||
if (!App::GetApp()->m_skip_addon.Get()) {
|
||||
add_entries(entry.add_on);
|
||||
}
|
||||
if (!App::GetApp()->m_skip_data_patch.Get()) {
|
||||
add_entries(entry.data_patch);
|
||||
}
|
||||
if (!App::GetApp()->m_skip_ticket.Get()) {
|
||||
add_collections(entry.tickets);
|
||||
}
|
||||
}
|
||||
|
||||
// we don't need to verify the nca's, this speeds up installs.
|
||||
m_config.sd_card_install = sd_install;
|
||||
m_config.skip_nca_hash_verify = true;
|
||||
m_config.skip_rsa_header_fixed_key_verify = true;
|
||||
m_config.skip_rsa_npdm_fixed_key_verify = true;
|
||||
}
|
||||
|
||||
GcSource::~GcSource() {
|
||||
@@ -63,86 +141,138 @@ Result GcSource::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Menu::Menu() : MenuBase{"GameCard"_i18n} {
|
||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
}});
|
||||
auto ApplicationEntry::GetSize(const std::vector<GcCollections>& entries) const -> s64 {
|
||||
s64 size{};
|
||||
for (auto& e : entries) {
|
||||
for (auto& collection : e) {
|
||||
size += collection.size;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
SetAction(Button::X, Action{"Refresh"_i18n, [this](){
|
||||
m_state = State::None;
|
||||
}});
|
||||
auto ApplicationEntry::GetSize() const -> s64 {
|
||||
s64 size{};
|
||||
size += GetSize(application);
|
||||
size += GetSize(patch);
|
||||
size += GetSize(add_on);
|
||||
size += GetSize(data_patch);
|
||||
return size;
|
||||
}
|
||||
|
||||
Menu::Menu() : MenuBase{"GameCard"_i18n} {
|
||||
this->SetActions(
|
||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
}}),
|
||||
std::make_pair(Button::X, Action{"Options"_i18n, [this](){
|
||||
App::DisplayInstallOptions(false);
|
||||
}})
|
||||
);
|
||||
|
||||
const Vec4 v{485, 275, 720, 70};
|
||||
const Vec2 pad{0, 125 - v.h};
|
||||
m_list = std::make_unique<List>(1, 3, m_pos, v, pad);
|
||||
|
||||
fsOpenDeviceOperator(std::addressof(m_dev_op));
|
||||
UpdateStorageSize();
|
||||
}
|
||||
|
||||
Menu::~Menu() {
|
||||
// manually close this as it needs(?) to be closed before dev_op.
|
||||
m_fs.reset();
|
||||
GcUnmount();
|
||||
fsDeviceOperatorClose(std::addressof(m_dev_op));
|
||||
}
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
|
||||
switch (m_state) {
|
||||
case State::None: {
|
||||
bool gc_inserted;
|
||||
if (R_FAILED(fsDeviceOperatorIsGameCardInserted(std::addressof(m_dev_op), std::addressof(gc_inserted)))) {
|
||||
m_state = State::Failed;
|
||||
} else {
|
||||
if (!gc_inserted) {
|
||||
m_state = State::NotFound;
|
||||
} else {
|
||||
if (R_FAILED(ScanGamecard())) {
|
||||
m_state = State::Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case State::Progress:
|
||||
case State::Done:
|
||||
case State::NotFound:
|
||||
case State::Failed:
|
||||
break;
|
||||
// poll for the gamecard first before handling inputs as the gamecard
|
||||
// may have been removed, thus pressing A would fail.
|
||||
bool inserted{};
|
||||
GcPoll(&inserted);
|
||||
if (m_mounted != inserted) {
|
||||
log_write("gc state changed\n");
|
||||
m_mounted = inserted;
|
||||
if (m_mounted) {
|
||||
log_write("trying to mount\n");
|
||||
m_mounted = R_SUCCEEDED(GcMount());
|
||||
} else {
|
||||
log_write("trying to unmount\n");
|
||||
GcUnmount();
|
||||
}
|
||||
}
|
||||
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_option_index, std::size(g_option_list), [this](bool touch, auto i) {
|
||||
if (touch && m_option_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
m_option_index = i;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
MenuBase::Draw(vg, theme);
|
||||
|
||||
switch (m_state) {
|
||||
case State::None:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Waiting for connection..."_i18n.c_str());
|
||||
break;
|
||||
#define STORAGE_BAR_W 325
|
||||
#define STORAGE_BAR_H 14
|
||||
|
||||
case State::Progress:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Transferring data..."_i18n.c_str());
|
||||
break;
|
||||
const auto size_sd_gb = (double)m_size_free_sd / 0x40000000;
|
||||
const auto size_nand_gb = (double)m_size_free_nand / 0x40000000;
|
||||
|
||||
case State::NotFound:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "No GameCard inserted, press X to refresh"_i18n.c_str());
|
||||
break;
|
||||
gfx::drawTextArgs(vg, 490, 135, 23.f, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "System memory %.1f GB", size_nand_gb);
|
||||
gfx::drawRect(vg, 480, 170, STORAGE_BAR_W, STORAGE_BAR_H, theme->GetColour(ThemeEntryID_TEXT));
|
||||
gfx::drawRect(vg, 480 + 1, 170 + 1, STORAGE_BAR_W - 2, STORAGE_BAR_H - 2, theme->GetColour(ThemeEntryID_BACKGROUND));
|
||||
gfx::drawRect(vg, 480 + 2, 170 + 2, STORAGE_BAR_W - (((double)m_size_free_nand / (double)m_size_total_nand) * STORAGE_BAR_W) - 4, STORAGE_BAR_H - 4, theme->GetColour(ThemeEntryID_TEXT));
|
||||
|
||||
case State::Done:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Installed GameCard, press B to exit..."_i18n.c_str());
|
||||
break;
|
||||
gfx::drawTextArgs(vg, 870, 135, 23.f, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "microSD card %.1f GB", size_sd_gb);
|
||||
gfx::drawRect(vg, 860, 170, STORAGE_BAR_W, STORAGE_BAR_H, theme->GetColour(ThemeEntryID_TEXT));
|
||||
gfx::drawRect(vg, 860 + 1, 170 + 1, STORAGE_BAR_W - 2, STORAGE_BAR_H - 2, theme->GetColour(ThemeEntryID_BACKGROUND));
|
||||
gfx::drawRect(vg, 860 + 2, 170 + 2, STORAGE_BAR_W - (((double)m_size_free_sd / (double)m_size_total_sd) * STORAGE_BAR_W) - 4, STORAGE_BAR_H - 4, theme->GetColour(ThemeEntryID_TEXT));
|
||||
|
||||
case State::Failed:
|
||||
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Failed to scan GameCard..."_i18n.c_str());
|
||||
break;
|
||||
gfx::drawRect(vg, 30, 90, 375, 555, theme->GetColour(ThemeEntryID_GRID));
|
||||
|
||||
if (!m_entries.empty()) {
|
||||
const auto& e = m_entries[m_entry_index];
|
||||
const auto size = e.GetSize();
|
||||
gfx::drawImage(vg, 90, 130, 256, 256, m_icon ? m_icon : App::GetDefaultImage());
|
||||
|
||||
nvgSave(vg);
|
||||
nvgIntersectScissor(vg, 50, 90, 325, 555);
|
||||
gfx::drawTextArgs(vg, 50, 415, 18.f, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%s", m_lang_entry.name);
|
||||
gfx::drawTextArgs(vg, 50, 455, 18.f, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%s", m_lang_entry.author);
|
||||
gfx::drawTextArgs(vg, 50, 495, 18.f, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "App-ID: 0%lX", e.app_id);
|
||||
gfx::drawTextArgs(vg, 50, 535, 18.f, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "Key-Gen: %u (%s)", e.key_gen, nca::GetKeyGenStr(e.key_gen));
|
||||
gfx::drawTextArgs(vg, 50, 575, 18.f, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "Size: %.2f GB", (double)size / 0x40000000);
|
||||
gfx::drawTextArgs(vg, 50, 615, 18.f, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "Base: %zu Patch: %zu Addon: %zu Data: %zu", e.application.size(), e.patch.size(), e.add_on.size(), e.data_patch.size());
|
||||
nvgRestore(vg);
|
||||
}
|
||||
|
||||
m_list->Draw(vg, theme, std::size(g_option_list), [this](auto* vg, auto* theme, auto v, auto i) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
const auto text_y = y + (h / 2.f);
|
||||
auto colour = ThemeEntryID_TEXT;
|
||||
if (i == m_option_index) {
|
||||
gfx::drawRectOutline(vg, theme, 4.f, v);
|
||||
// g_background.selected_bar = create_shape(Colour_Nintendo_Cyan, 90, 230, 4, 45, true);
|
||||
// draw_shape_position(&g_background.selected_bar, 485, g_options[i].text->rect.y - 10);
|
||||
gfx::drawRect(vg, 490, text_y - 45.f / 2.f, 2, 45, theme->GetColour(ThemeEntryID_TEXT_SELECTED));
|
||||
colour = ThemeEntryID_TEXT_SELECTED;
|
||||
}
|
||||
if (i != 2 && !m_mounted) {
|
||||
colour = ThemeEntryID_TEXT_INFO;
|
||||
}
|
||||
|
||||
gfx::drawTextArgs(vg, x + 15, y + (h / 2.f), 23.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(colour), "%s", g_option_list[i]);
|
||||
});
|
||||
}
|
||||
|
||||
Result Menu::ScanGamecard() {
|
||||
m_state = State::None;
|
||||
m_fs.reset();
|
||||
m_collections.clear();
|
||||
Result Menu::GcMount() {
|
||||
GcUnmount();
|
||||
|
||||
FsGameCardHandle gc_handle;
|
||||
R_TRY(fsDeviceOperatorGetGameCardHandle(std::addressof(m_dev_op), std::addressof(gc_handle)));
|
||||
R_TRY(fsDeviceOperatorGetGameCardHandle(std::addressof(m_dev_op), std::addressof(m_handle)));
|
||||
|
||||
m_fs = std::make_unique<fs::FsNativeGameCard>(std::addressof(gc_handle), FsGameCardPartition_Secure, false);
|
||||
m_fs = std::make_unique<fs::FsNativeGameCard>(std::addressof(m_handle), FsGameCardPartition_Secure, false);
|
||||
R_TRY(m_fs->GetFsOpenResult());
|
||||
|
||||
FsDir dir;
|
||||
@@ -155,33 +285,215 @@ Result Menu::ScanGamecard() {
|
||||
std::vector<FsDirectoryEntry> buf(count);
|
||||
s64 total_entries;
|
||||
R_TRY(m_fs->DirRead(std::addressof(dir), std::addressof(total_entries), buf.size(), buf.data()));
|
||||
m_collections.reserve(total_entries);
|
||||
R_UNLESS(buf.size() == total_entries, 0x1);
|
||||
|
||||
s64 offset{};
|
||||
for (s64 i = 0; i < total_entries; i++) {
|
||||
yati::container::CollectionEntry entry{};
|
||||
entry.name = buf[i].name;
|
||||
entry.offset = offset;
|
||||
entry.size = buf[i].file_size;
|
||||
m_collections.emplace_back(entry);
|
||||
offset += buf[i].file_size;
|
||||
yati::container::Collections ticket_collections;
|
||||
for (const auto& e : buf) {
|
||||
if (!std::string_view(e.name).ends_with(".tik") && !std::string_view(e.name).ends_with(".cert")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ticket_collections.emplace_back(e.name, 0, e.file_size);
|
||||
}
|
||||
|
||||
m_state = State::Progress;
|
||||
App::Push(std::make_shared<ui::ProgressBox>("Installing App"_i18n, [this](auto pbox) mutable -> bool {
|
||||
auto source = std::make_shared<GcSource>(m_collections, m_fs.get());
|
||||
return R_SUCCEEDED(yati::InstallFromCollections(pbox, source, m_collections));
|
||||
}, [this](bool result){
|
||||
if (result) {
|
||||
App::Notify("Gc install success!"_i18n);
|
||||
m_state = State::Done;
|
||||
} else {
|
||||
App::Notify("Gc install failed!"_i18n);
|
||||
m_state = State::Failed;
|
||||
for (const auto& e : buf) {
|
||||
// we could use ncm to handle finding all the ncas for us
|
||||
// however, we can parse faster than ncm.
|
||||
// not only that, the first few calls trying to mount ncm db for
|
||||
// the gamecard will fail as it has not yet been parsed (or it's locked?).
|
||||
// we could, of course, just wait until ncm is ready, which is about
|
||||
// 32ms, but i already have code for manually parsing cnmt so lets re-use it.
|
||||
if (!std::string_view(e.name).ends_with(".cnmt.nca")) {
|
||||
continue;
|
||||
}
|
||||
}));
|
||||
|
||||
// we don't yet use the header or extended header.
|
||||
ncm::PackagedContentMeta header;
|
||||
std::vector<u8> extended_header;
|
||||
std::vector<NcmPackagedContentInfo> infos;
|
||||
const auto path = BuildGcPath(e.name, &m_handle);
|
||||
R_TRY(yati::ParseCnmtNca(path, header, extended_header, infos));
|
||||
|
||||
u8 key_gen;
|
||||
FsRightsId rights_id;
|
||||
R_TRY(fsGetRightsIdAndKeyGenerationByPath(path, FsContentAttributes_All, &key_gen, &rights_id));
|
||||
|
||||
// always add tickets, yati will ignore them if not needed.
|
||||
GcCollections collections;
|
||||
// add cnmt file.
|
||||
collections.emplace_back(e.name, e.file_size, NcmContentType_Meta);
|
||||
|
||||
for (const auto& info : infos) {
|
||||
// these don't exist for gamecards, however i may copy/paste this code
|
||||
// somewhere so i'm future proofing against myself.
|
||||
if (info.info.content_type == NcmContentType_DeltaFragment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// find the nca file, this will never fail for gamecards, see above comment.
|
||||
const auto str = hexIdToStr(info.info.content_id);
|
||||
const auto it = std::find_if(buf.cbegin(), buf.cend(), [str](auto& e){
|
||||
return !std::strncmp(str.str, e.name, std::strlen(str.str));
|
||||
});
|
||||
|
||||
R_UNLESS(it != buf.cend(), yati::Result_NcaNotFound);
|
||||
collections.emplace_back(it->name, it->file_size, info.info.content_type);
|
||||
}
|
||||
|
||||
const auto app_id = ncm::GetAppId(header);
|
||||
ApplicationEntry* app_entry{};
|
||||
for (auto& app : m_entries) {
|
||||
if (app.app_id == app_id) {
|
||||
app_entry = &app;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!app_entry) {
|
||||
app_entry = &m_entries.emplace_back(app_id, header.title_version);
|
||||
}
|
||||
|
||||
app_entry->version = std::max(app_entry->version, header.title_version);
|
||||
app_entry->key_gen = std::max(app_entry->key_gen, key_gen);
|
||||
|
||||
if (header.meta_type == NcmContentMetaType_Application) {
|
||||
app_entry->application.emplace_back(collections);
|
||||
} else if (header.meta_type == NcmContentMetaType_Patch) {
|
||||
app_entry->patch.emplace_back(collections);
|
||||
} else if (header.meta_type == NcmContentMetaType_AddOnContent) {
|
||||
app_entry->add_on.emplace_back(collections);
|
||||
} else if (header.meta_type == NcmContentMetaType_DataPatch) {
|
||||
app_entry->data_patch.emplace_back(collections);
|
||||
}
|
||||
}
|
||||
|
||||
R_UNLESS(m_entries.size(), 0x1);
|
||||
|
||||
// append tickets to every application, yati will ignore if undeeded.
|
||||
for (auto& e : m_entries) {
|
||||
e.tickets = ticket_collections;
|
||||
}
|
||||
|
||||
SetAction(Button::A, Action{"OK"_i18n, [this](){
|
||||
if (m_option_index == 2) {
|
||||
SetPop();
|
||||
} else {
|
||||
if (m_mounted) {
|
||||
App::Push(std::make_shared<ui::ProgressBox>(m_icon, "Installing "_i18n, m_lang_entry.name, [this](auto pbox) mutable -> bool {
|
||||
auto source = std::make_shared<GcSource>(m_entries[m_entry_index], m_fs.get(), m_option_index == 1);
|
||||
return R_SUCCEEDED(yati::InstallFromCollections(pbox, source, source->m_collections, source->m_config));
|
||||
}, [this](bool result){
|
||||
if (result) {
|
||||
App::Notify("Gc install success!"_i18n);
|
||||
} else {
|
||||
App::Notify("Gc install failed!"_i18n);
|
||||
}
|
||||
|
||||
UpdateStorageSize();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}});
|
||||
|
||||
if (m_entries.size() > 1) {
|
||||
SetAction(Button::L, Action{"Prev"_i18n, [this](){
|
||||
if (m_entry_index != 0) {
|
||||
OnChangeIndex(m_entry_index - 1);
|
||||
}
|
||||
}});
|
||||
SetAction(Button::R, Action{"Next"_i18n, [this](){
|
||||
if (m_entry_index < m_entries.size()) {
|
||||
OnChangeIndex(m_entry_index + 1);
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
OnChangeIndex(0);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Menu::GcUnmount() {
|
||||
m_fs.reset();
|
||||
m_entries.clear();
|
||||
m_entry_index = 0;
|
||||
m_mounted = false;
|
||||
m_lang_entry = {};
|
||||
FreeImage();
|
||||
|
||||
RemoveAction(Button::L);
|
||||
RemoveAction(Button::R);
|
||||
}
|
||||
|
||||
Result Menu::GcPoll(bool* inserted) {
|
||||
R_TRY(fsDeviceOperatorIsGameCardInserted(&m_dev_op, inserted));
|
||||
|
||||
// if the handle changed, re-mount the game card.
|
||||
if (*inserted && m_mounted) {
|
||||
FsGameCardHandle handle;
|
||||
R_TRY(fsDeviceOperatorGetGameCardHandle(std::addressof(m_dev_op), std::addressof(handle)));
|
||||
if (handle.value != m_handle.value) {
|
||||
R_TRY(GcMount());
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Menu::UpdateStorageSize() {
|
||||
fs::FsNativeContentStorage fs_nand{FsContentStorageId_User};
|
||||
fs::FsNativeContentStorage fs_sd{FsContentStorageId_SdCard};
|
||||
|
||||
R_TRY(fs_sd.GetFreeSpace("/", &m_size_free_sd));
|
||||
R_TRY(fs_sd.GetTotalSpace("/", &m_size_total_sd));
|
||||
R_TRY(fs_nand.GetFreeSpace("/", &m_size_free_nand));
|
||||
R_TRY(fs_nand.GetTotalSpace("/", &m_size_total_nand));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Menu::FreeImage() {
|
||||
if (m_icon) {
|
||||
nvgDeleteImage(App::GetVg(), m_icon);
|
||||
m_icon = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::OnChangeIndex(s64 new_index) {
|
||||
FreeImage();
|
||||
m_entry_index = new_index;
|
||||
|
||||
const auto index = m_entries.empty() ? 0 : m_entry_index + 1;
|
||||
this->SetSubHeading(std::to_string(index) + " / " + std::to_string(m_entries.size()));
|
||||
|
||||
// nsGetApplicationControlData() will fail if it's the first time
|
||||
// mounting a gamecard if the image is not already cached.
|
||||
// waiting 1-2s after mount, then calling seems to work.
|
||||
// however, we can just manually parse the nca to get the data we need,
|
||||
// which always works and *is* faster too ;)
|
||||
for (auto& e : m_entries[m_entry_index].application) {
|
||||
for (auto& collection : e) {
|
||||
if (collection.type == NcmContentType_Control) {
|
||||
NacpStruct nacp;
|
||||
std::vector<u8> icon;
|
||||
const auto path = BuildGcPath(collection.name.c_str(), &m_handle);
|
||||
if (R_SUCCEEDED(yati::ParseControlNca(path, m_entries[m_entry_index].app_id, &nacp, sizeof(nacp), &icon))) {
|
||||
log_write("managed to parse control nca %s\n", path.s);
|
||||
NacpLanguageEntry* lang_entry{};
|
||||
nacpGetLanguageEntry(&nacp, &lang_entry);
|
||||
|
||||
if (lang_entry) {
|
||||
m_lang_entry = *lang_entry;
|
||||
}
|
||||
|
||||
m_icon = nvgCreateImageMem(App::GetVg(), 0, icon.data(), icon.size());
|
||||
if (m_icon > 0) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
log_write("\tFAILED to parse control nca %s\n", path.s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::gc
|
||||
|
||||
@@ -247,27 +247,6 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
|
||||
fs::FsNativeSd().CreateDirectoryRecursively(CACHE_PATH);
|
||||
|
||||
this->SetActions(
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 1, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 1, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_RIGHT, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 8, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_LEFT, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 8, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
|
||||
std::make_pair(Button::A, Action{"Download"_i18n, [this](){
|
||||
if (m_entries.empty()) {
|
||||
return;
|
||||
@@ -277,7 +256,7 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
|
||||
static GhApiEntry gh_entry;
|
||||
gh_entry = {};
|
||||
|
||||
App::Push(std::make_shared<ProgressBox>("Downloading "_i18n + GetEntry().repo, [this](auto pbox){
|
||||
App::Push(std::make_shared<ProgressBox>(0, "Downloading "_i18n, GetEntry().repo, [this](auto pbox){
|
||||
return DownloadAssetJson(pbox, GenerateApiUrl(GetEntry()), gh_entry);
|
||||
}, [this](bool success){
|
||||
if (success) {
|
||||
@@ -325,7 +304,7 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
|
||||
}
|
||||
|
||||
const auto func = [this, &asset_entry, ptr](){
|
||||
App::Push(std::make_shared<ProgressBox>("Downloading "_i18n + GetEntry().repo, [this, &asset_entry, ptr](auto pbox){
|
||||
App::Push(std::make_shared<ProgressBox>(0, "Downloading "_i18n, GetEntry().repo, [this, &asset_entry, ptr](auto pbox){
|
||||
return DownloadApp(pbox, asset_entry, ptr);
|
||||
}, [this, ptr](bool success){
|
||||
if (success) {
|
||||
@@ -373,8 +352,8 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_entries.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
m_list->OnUpdate(controller, touch, m_index, m_entries.size(), [this](bool touch, auto i) {
|
||||
if (touch && m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
|
||||
@@ -28,40 +28,6 @@ auto GenerateStarPath(const fs::FsPath& nro_path) -> fs::FsPath {
|
||||
|
||||
Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
||||
this->SetActions(
|
||||
std::make_pair(Button::RIGHT, Action{[this](){
|
||||
if (m_index < (m_entries.size() - 1) && (m_index + 1) % 3 != 0) {
|
||||
SetIndex(m_index + 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
log_write("moved right\n");
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::LEFT, Action{[this](){
|
||||
if (m_index != 0 && (m_index % 3) != 0) {
|
||||
SetIndex(m_index - 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
log_write("moved left\n");
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 3, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 3, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::R2, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 9, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::L2, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 9, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::A, Action{"Launch"_i18n, [this](){
|
||||
nro_launch(m_entries[m_index].path);
|
||||
}}),
|
||||
@@ -157,8 +123,8 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_entries.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
m_list->OnUpdate(controller, touch, m_index, m_entries.size(), [this](bool touch, auto i) {
|
||||
if (touch && m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
@@ -202,7 +168,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
}
|
||||
|
||||
const float image_size = 115;
|
||||
gfx::drawImageRounded(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage());
|
||||
gfx::drawImage(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage(), 15);
|
||||
|
||||
nvgSave(vg);
|
||||
nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
#include "ui/menus/main_menu.hpp"
|
||||
#include "ui/menus/irs_menu.hpp"
|
||||
#include "ui/menus/themezer.hpp"
|
||||
#include "ui/menus/ghdl.hpp"
|
||||
#include "ui/menus/usb_menu.hpp"
|
||||
#include "ui/menus/ftp_menu.hpp"
|
||||
#include "ui/menus/gc_menu.hpp"
|
||||
|
||||
#include "ui/sidebar.hpp"
|
||||
#include "ui/popup_list.hpp"
|
||||
@@ -16,7 +10,6 @@
|
||||
#include "log.hpp"
|
||||
#include "download.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "web.hpp"
|
||||
#include "i18n.hpp"
|
||||
|
||||
#include <cstring>
|
||||
@@ -235,48 +228,29 @@ MainMenu::MainMenu() {
|
||||
language_items.push_back("Swedish"_i18n);
|
||||
language_items.push_back("Vietnamese"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Theme"_i18n, [this](){
|
||||
SidebarEntryArray::Items theme_items{};
|
||||
const auto theme_meta = App::GetThemeMetaList();
|
||||
for (auto& p : theme_meta) {
|
||||
theme_items.emplace_back(p.name);
|
||||
}
|
||||
|
||||
auto options = std::make_shared<Sidebar>("Theme Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Select Theme"_i18n, theme_items, [this, theme_items](s64& index_out){
|
||||
App::SetTheme(index_out);
|
||||
}, App::GetThemeIndex()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Music"_i18n, App::GetThemeMusicEnable(), [this](bool& enable){
|
||||
App::SetThemeMusicEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("12 Hour Time"_i18n, App::Get12HourTimeEnable(), [this](bool& enable){
|
||||
App::Set12HourTimeEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Theme"_i18n, [](){
|
||||
App::DisplayThemeOptions();
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Network"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Network Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Ftp"_i18n, App::GetFtpEnable(), [this](bool& enable){
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Ftp"_i18n, App::GetFtpEnable(), [](bool& enable){
|
||||
App::SetFtpEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Mtp"_i18n, App::GetMtpEnable(), [this](bool& enable){
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Mtp"_i18n, App::GetMtpEnable(), [](bool& enable){
|
||||
App::SetMtpEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Nxlink"_i18n, App::GetNxlinkEnable(), [this](bool& enable){
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Nxlink"_i18n, App::GetNxlinkEnable(), [](bool& enable){
|
||||
App::SetNxlinkEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
if (m_update_state == UpdateState::Update) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Download update: "_i18n + m_update_version, [this](){
|
||||
App::Push(std::make_shared<ProgressBox>("Downloading "_i18n + m_update_version, [this](auto pbox){
|
||||
App::Push(std::make_shared<ProgressBox>(0, "Downloading "_i18n, "Sphaira v" + m_update_version, [this](auto pbox){
|
||||
return InstallUpdate(pbox, m_update_url, m_update_version);
|
||||
}, [this](bool success){
|
||||
if (success) {
|
||||
@@ -295,150 +269,16 @@ MainMenu::MainMenu() {
|
||||
}
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Language"_i18n, language_items, [this](s64& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Language"_i18n, language_items, [](s64& index_out){
|
||||
App::SetLanguage(index_out);
|
||||
}, (s64)App::GetLanguage()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Misc"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Misc Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Themezer"_i18n, [](){
|
||||
App::Push(std::make_shared<menu::themezer::Menu>());
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("GitHub"_i18n, [](){
|
||||
App::Push(std::make_shared<menu::gh::Menu>());
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Irs"_i18n, [](){
|
||||
App::Push(std::make_shared<menu::irs::Menu>());
|
||||
}));
|
||||
|
||||
if (App::IsApplication()) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Web"_i18n, [](){
|
||||
WebShow("https://lite.duckduckgo.com/lite");
|
||||
}));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_install.Get()) {
|
||||
if (App::GetFtpEnable()) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Ftp Install"_i18n, [](){
|
||||
App::Push(std::make_shared<menu::ftp::Menu>());
|
||||
}));
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Usb Install"_i18n, [](){
|
||||
App::Push(std::make_shared<menu::usb::Menu>());
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("GameCard Install"_i18n, [](){
|
||||
App::Push(std::make_shared<menu::gc::Menu>());
|
||||
}));
|
||||
}
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Misc"_i18n, [](){
|
||||
App::DisplayMiscOptions();
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Advanced"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Advanced Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
SidebarEntryArray::Items text_scroll_speed_items;
|
||||
text_scroll_speed_items.push_back("Slow"_i18n);
|
||||
text_scroll_speed_items.push_back("Normal"_i18n);
|
||||
text_scroll_speed_items.push_back("Fast"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [this](bool& enable){
|
||||
App::SetLogEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Replace hbmenu on exit"_i18n, App::GetReplaceHbmenuEnable(), [this](bool& enable){
|
||||
App::SetReplaceHbmenuEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [this](s64& index_out){
|
||||
App::SetTextScrollSpeed(index_out);
|
||||
}, (s64)App::GetTextScrollSpeed()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Install options"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Install Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
SidebarEntryArray::Items install_items;
|
||||
install_items.push_back("System memory"_i18n);
|
||||
install_items.push_back("microSD card"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Enable"_i18n, App::GetInstallEnable(), [this](bool& enable){
|
||||
App::SetInstallEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Show install warning"_i18n, App::GetInstallPrompt(), [this](bool& enable){
|
||||
App::SetInstallPrompt(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Install location"_i18n, install_items, [this](s64& index_out){
|
||||
App::SetInstallSdEnable(index_out);
|
||||
}, (s64)App::GetInstallSdEnable()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_allow_downgrade.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip if already installed"_i18n, App::GetApp()->m_skip_if_already_installed.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_if_already_installed.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Ticket only"_i18n, App::GetApp()->m_ticket_only.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_ticket_only.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip base"_i18n, App::GetApp()->m_skip_base.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_base.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip Patch"_i18n, App::GetApp()->m_skip_patch.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_patch.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip addon"_i18n, App::GetApp()->m_skip_addon.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_addon.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip data patch"_i18n, App::GetApp()->m_skip_data_patch.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_data_patch.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip ticket"_i18n, App::GetApp()->m_skip_ticket.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_ticket.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("skip NCA hash verify"_i18n, App::GetApp()->m_skip_nca_hash_verify.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_nca_hash_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip RSA header verify"_i18n, App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_rsa_header_fixed_key_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip RSA NPDM verify"_i18n, App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Ignore distribution bit"_i18n, App::GetApp()->m_ignore_distribution_bit.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_ignore_distribution_bit.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Convert to standard crypto"_i18n, App::GetApp()->m_convert_to_standard_crypto.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_convert_to_standard_crypto.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Lower master key"_i18n, App::GetApp()->m_lower_master_key.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_lower_master_key.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Lower system version"_i18n, App::GetApp()->m_lower_system_version.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_lower_system_version.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
}));
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Advanced"_i18n, [](){
|
||||
App::DisplayAdvancedOptions();
|
||||
}));
|
||||
}})
|
||||
);
|
||||
|
||||
@@ -375,45 +375,6 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
||||
}});
|
||||
|
||||
this->SetActions(
|
||||
std::make_pair(Button::RIGHT, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (m_index < (page.m_packList.size() - 1) && (m_index + 1) % 3 != 0) {
|
||||
SetIndex(m_index + 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
log_write("moved right\n");
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::LEFT, Action{[this](){
|
||||
if (m_index != 0 && (m_index % 3) != 0) {
|
||||
SetIndex(m_index - 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
log_write("moved left\n");
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (m_list->ScrollDown(m_index, 3, page.m_packList.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (m_list->ScrollUp(m_index, 3, page.m_packList.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::R2, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (m_list->ScrollDown(m_index, 6, page.m_packList.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::L2, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (m_list->ScrollUp(m_index, 6, page.m_packList.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::A, Action{"Download"_i18n, [this](){
|
||||
App::Push(std::make_shared<OptionBox>(
|
||||
"Download theme?"_i18n,
|
||||
@@ -424,7 +385,7 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
||||
const auto& entry = page.m_packList[m_index];
|
||||
const auto url = apiBuildUrlDownloadPack(entry);
|
||||
|
||||
App::Push(std::make_shared<ProgressBox>("Installing "_i18n + entry.details.name, [this, &entry](auto pbox){
|
||||
App::Push(std::make_shared<ProgressBox>(entry.themes[0].preview.lazy_image.image, "Downloading "_i18n, entry.details.name, [this, &entry](auto pbox){
|
||||
return InstallTheme(pbox, entry);
|
||||
}, [this, &entry](bool success){
|
||||
if (success) {
|
||||
@@ -532,8 +493,8 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_list->OnUpdate(controller, touch, page.m_packList.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
m_list->OnUpdate(controller, touch, m_index, page.m_packList.size(), [this](bool touch, auto i) {
|
||||
if (touch && m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
@@ -642,7 +603,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
}
|
||||
}
|
||||
|
||||
gfx::drawImageRounded(vg, x + xoff, y, 320, 180, image.image ? image.image : App::GetDefaultImage());
|
||||
gfx::drawImage(vg, x + xoff, y, 320, 180, image.image ? image.image : App::GetDefaultImage(), 15);
|
||||
}
|
||||
|
||||
nvgSave(vg);
|
||||
|
||||
@@ -43,6 +43,10 @@ Menu::Menu() : MenuBase{"USB"_i18n} {
|
||||
SetPop();
|
||||
}});
|
||||
|
||||
SetAction(Button::X, Action{"Options"_i18n, [this](){
|
||||
App::DisplayInstallOptions(false);
|
||||
}});
|
||||
|
||||
// if mtp is enabled, disable it for now.
|
||||
m_was_mtp_enabled = App::GetMtpEnable();
|
||||
if (m_was_mtp_enabled) {
|
||||
@@ -99,7 +103,7 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
log_write("set to progress\n");
|
||||
m_state = State::Progress;
|
||||
log_write("got connection\n");
|
||||
App::Push(std::make_shared<ui::ProgressBox>("Installing App"_i18n, [this](auto pbox) mutable -> bool {
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) mutable -> bool {
|
||||
log_write("inside progress box\n");
|
||||
for (u32 i = 0; i < m_usb_count; i++) {
|
||||
std::string file_name;
|
||||
|
||||
@@ -34,24 +34,16 @@ constexpr std::array buttons = {
|
||||
};
|
||||
|
||||
// NEW ---------------------
|
||||
void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGcolor& c, bool rounded) {
|
||||
void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGcolor& c, float rounded) {
|
||||
nvgBeginPath(vg);
|
||||
if (rounded) {
|
||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, 15);
|
||||
} else {
|
||||
nvgRect(vg, v.x, v.y, v.w, v.h);
|
||||
}
|
||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded);
|
||||
nvgFillColor(vg, c);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGpaint& p, bool rounded) {
|
||||
void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGpaint& p, float rounded) {
|
||||
nvgBeginPath(vg);
|
||||
if (rounded) {
|
||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, 15);
|
||||
} else {
|
||||
nvgRect(vg, v.x, v.y, v.w, v.h);
|
||||
}
|
||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded);
|
||||
nvgFillPaint(vg, p);
|
||||
nvgFill(vg);
|
||||
}
|
||||
@@ -164,25 +156,13 @@ void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, const
|
||||
drawText(vg, x, y, size, buffer, nullptr, align, c);
|
||||
}
|
||||
|
||||
void drawImage(NVGcontext* vg, const Vec4& v, int texture) {
|
||||
void drawImage(NVGcontext* vg, const Vec4& v, int texture, float rounded) {
|
||||
const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f);
|
||||
drawRect(vg, v, paint, false);
|
||||
drawRect(vg, v, paint, rounded);
|
||||
}
|
||||
|
||||
void drawImage(NVGcontext* vg, float x, float y, float w, float h, int texture) {
|
||||
drawImage(vg, Vec4(x, y, w, h), texture);
|
||||
}
|
||||
|
||||
void drawImageRounded(NVGcontext* vg, const Vec4& v, int texture) {
|
||||
const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f);
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, 15);
|
||||
nvgFillPaint(vg, paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void drawImageRounded(NVGcontext* vg, float x, float y, float w, float h, int texture) {
|
||||
drawImageRounded(vg, Vec4(x, y, w, h), texture);
|
||||
void drawImage(NVGcontext* vg, float x, float y, float w, float h, int texture, float rounded) {
|
||||
drawImage(vg, Vec4(x, y, w, h), texture, rounded);
|
||||
}
|
||||
|
||||
void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, const NVGcolor& c, const char* str, int align, const char* end) {
|
||||
@@ -208,19 +188,19 @@ void dimBackground(NVGcontext* vg) {
|
||||
drawRectIntenal(vg, {0.f,0.f,SCREEN_WIDTH,SCREEN_HEIGHT}, nvgRGBA(0, 0, 0, 180), false);
|
||||
}
|
||||
|
||||
void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGcolor& c, bool rounded) {
|
||||
void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGcolor& c, float rounded) {
|
||||
drawRectIntenal(vg, {x,y,w,h}, c, rounded);
|
||||
}
|
||||
|
||||
void drawRect(NVGcontext* vg, const Vec4& v, const NVGcolor& c, bool rounded) {
|
||||
void drawRect(NVGcontext* vg, const Vec4& v, const NVGcolor& c, float rounded) {
|
||||
drawRectIntenal(vg, v, c, rounded);
|
||||
}
|
||||
|
||||
void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGpaint& p, bool rounded) {
|
||||
void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGpaint& p, float rounded) {
|
||||
drawRectIntenal(vg, {x,y,w,h}, p, rounded);
|
||||
}
|
||||
|
||||
void drawRect(NVGcontext* vg, const Vec4& v, const NVGpaint& p, bool rounded) {
|
||||
void drawRect(NVGcontext* vg, const Vec4& v, const NVGpaint& p, float rounded) {
|
||||
drawRectIntenal(vg, v, p, rounded);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,16 +62,6 @@ PopupList::PopupList(std::string title, Items items, Callback cb, s64 index)
|
||||
, m_callback{cb}
|
||||
, m_index{index} {
|
||||
this->SetActions(
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 1, m_items.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 1, m_items.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::A, Action{"Select"_i18n, [this](){
|
||||
if (m_callback) {
|
||||
m_callback(m_index);
|
||||
@@ -103,9 +93,11 @@ PopupList::PopupList(std::string title, Items items, Callback cb, s64 index)
|
||||
|
||||
auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
Widget::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_items.size(), [this](auto i) {
|
||||
m_list->OnUpdate(controller, touch, m_index, m_items.size(), [this](bool touch, auto i) {
|
||||
SetIndex(i);
|
||||
FireAction(Button::A);
|
||||
if (touch) {
|
||||
FireAction(Button::A);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace sphaira::ui {
|
||||
namespace {
|
||||
@@ -17,7 +18,7 @@ void threadFunc(void* arg) {
|
||||
|
||||
} // namespace
|
||||
|
||||
ProgressBox::ProgressBox(const std::string& title, ProgressBoxCallback callback, ProgressBoxDoneCallback done, int cpuid, int prio, int stack_size) {
|
||||
ProgressBox::ProgressBox(int image, const std::string& action, const std::string& title, ProgressBoxCallback callback, ProgressBoxDoneCallback done, int cpuid, int prio, int stack_size) {
|
||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
||||
App::Push(std::make_shared<OptionBox>("Are you sure you wish to cancel?"_i18n, "No"_i18n, "Yes"_i18n, 1, [this](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
@@ -27,11 +28,6 @@ ProgressBox::ProgressBox(const std::string& title, ProgressBoxCallback callback,
|
||||
}));
|
||||
}});
|
||||
|
||||
m_pos.w = 770.f;
|
||||
m_pos.h = 430.f;
|
||||
m_pos.x = 255;
|
||||
m_pos.y = 145;
|
||||
|
||||
m_pos.w = 770.f;
|
||||
m_pos.h = 295.f;
|
||||
m_pos.x = (SCREEN_WIDTH / 2.f) - (m_pos.w / 2.f);
|
||||
@@ -39,6 +35,8 @@ ProgressBox::ProgressBox(const std::string& title, ProgressBoxCallback callback,
|
||||
|
||||
m_done = done;
|
||||
m_title = title;
|
||||
m_action = action;
|
||||
m_image = image;
|
||||
|
||||
m_thread_data.pbox = this;
|
||||
m_thread_data.callback = callback;
|
||||
@@ -60,6 +58,7 @@ ProgressBox::~ProgressBox() {
|
||||
log_write("failed to close thread\n");
|
||||
}
|
||||
|
||||
FreeImage();
|
||||
m_done(m_thread_data.result);
|
||||
}
|
||||
|
||||
@@ -73,12 +72,28 @@ auto ProgressBox::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
|
||||
auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
mutexLock(&m_mutex);
|
||||
std::vector<u8> image_data{};
|
||||
std::swap(m_image_data, image_data);
|
||||
if (m_timestamp.GetSeconds()) {
|
||||
m_timestamp.Update();
|
||||
m_speed = m_offset - m_last_offset;
|
||||
m_last_offset = m_offset;
|
||||
}
|
||||
|
||||
const auto title = m_title;
|
||||
const auto transfer = m_transfer;
|
||||
const auto size = m_size;
|
||||
const auto offset = m_offset;
|
||||
const auto speed = m_speed;
|
||||
const auto last_offset = m_last_offset;
|
||||
mutexUnlock(&m_mutex);
|
||||
|
||||
if (!image_data.empty()) {
|
||||
FreeImage();
|
||||
m_image = nvgCreateImageMem(vg, 0, image_data.data(), image_data.size());
|
||||
m_own_image = true;
|
||||
}
|
||||
|
||||
gfx::dimBackground(vg);
|
||||
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
|
||||
|
||||
@@ -86,20 +101,62 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
// const Vec4 box = { 255, 145, 770, 430 };
|
||||
const auto center_x = m_pos.x + m_pos.w/2;
|
||||
const auto end_y = m_pos.y + m_pos.h;
|
||||
const Vec4 prog_bar = { 400, end_y - 80, 480, 12 };
|
||||
const auto progress_bar_w = m_pos.w - 230;
|
||||
const Vec4 prog_bar = { center_x - progress_bar_w / 2, end_y - 100, progress_bar_w, 12 };
|
||||
|
||||
nvgSave(vg);
|
||||
nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
|
||||
|
||||
if (m_image) {
|
||||
gfx::drawImage(vg, GetX() + 30, GetY() + 30, 128, 128, m_image, 10);
|
||||
}
|
||||
|
||||
// shapes.
|
||||
if (offset && size) {
|
||||
gfx::drawRect(vg, prog_bar, theme->GetColour(ThemeEntryID_PROGRESSBAR_BACKGROUND));
|
||||
const auto font_size = 18.F;
|
||||
const auto pad = 15.F;
|
||||
const float rounding = 5;
|
||||
|
||||
gfx::drawRect(vg, prog_bar, theme->GetColour(ThemeEntryID_PROGRESSBAR_BACKGROUND), rounding);
|
||||
const u32 percentage = ((double)offset / (double)size) * 100.0;
|
||||
gfx::drawRect(vg, prog_bar.x, prog_bar.y, ((float)offset / (float)size) * prog_bar.w, prog_bar.h, theme->GetColour(ThemeEntryID_PROGRESSBAR));
|
||||
gfx::drawTextArgs(vg, prog_bar.x + prog_bar.w + 10, prog_bar.y, 20, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%u%%", percentage);
|
||||
gfx::drawRect(vg, prog_bar.x, prog_bar.y, ((float)offset / (float)size) * prog_bar.w, prog_bar.h, theme->GetColour(ThemeEntryID_PROGRESSBAR), rounding);
|
||||
gfx::drawTextArgs(vg, prog_bar.x + prog_bar.w + pad, prog_bar.y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%u%%", percentage);
|
||||
|
||||
const double speed_mb = (double)speed / (1024.0 * 1024.0);
|
||||
const double speed_kb = (double)speed / (1024.0);
|
||||
|
||||
char speed_str[32];
|
||||
if (speed_mb >= 0.01) {
|
||||
std::snprintf(speed_str, sizeof(speed_str), "%.2f MiB/s", speed_mb);
|
||||
} else {
|
||||
std::snprintf(speed_str, sizeof(speed_str), "%.2f KiB/s", speed_kb);
|
||||
}
|
||||
|
||||
const auto left = size - last_offset;
|
||||
const auto left_seconds = left / speed;
|
||||
const auto hours = left_seconds / (60 * 60);
|
||||
const auto minutes = left_seconds % (60 * 60) / 60;
|
||||
const auto seconds = left_seconds % 60;
|
||||
|
||||
char time_str[64];
|
||||
if (hours) {
|
||||
std::snprintf(time_str, sizeof(time_str), "%zu hours %zu minutes remaining", hours, minutes);
|
||||
} else if (minutes) {
|
||||
std::snprintf(time_str, sizeof(time_str), "%zu minutes %zu seconds remaining", minutes, seconds);
|
||||
} else {
|
||||
std::snprintf(time_str, sizeof(time_str), "%zu seconds remaining", seconds);
|
||||
}
|
||||
|
||||
gfx::drawTextArgs(vg, center_x, prog_bar.y + prog_bar.h + 30, 18, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%s (%s)", time_str, speed_str);
|
||||
}
|
||||
|
||||
gfx::drawTextArgs(vg, center_x, m_pos.y + 60, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), title.c_str());
|
||||
gfx::drawTextArgs(vg, center_x, m_pos.y + 40, 24, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), m_action.c_str());
|
||||
gfx::drawTextArgs(vg, center_x, m_pos.y + 100, 22, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), title.c_str());
|
||||
if (!transfer.empty()) {
|
||||
gfx::drawTextArgs(vg, center_x, prog_bar.y - 15 - 20 * 1.5F, 20, NVG_ALIGN_CENTER, theme->GetColour(ThemeEntryID_TEXT), "%s", transfer.c_str());
|
||||
gfx::drawTextArgs(vg, center_x, m_pos.y + 150, 18, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT_INFO), "%s", transfer.c_str());
|
||||
}
|
||||
|
||||
nvgRestore(vg);
|
||||
}
|
||||
|
||||
auto ProgressBox::SetTitle(const std::string& title) -> ProgressBox& {
|
||||
@@ -115,6 +172,8 @@ auto ProgressBox::NewTransfer(const std::string& transfer) -> ProgressBox& {
|
||||
m_transfer = transfer;
|
||||
m_size = 0;
|
||||
m_offset = 0;
|
||||
m_last_offset = 0;
|
||||
m_timestamp.Update();
|
||||
mutexUnlock(&m_mutex);
|
||||
Yield();
|
||||
return *this;
|
||||
@@ -129,6 +188,21 @@ auto ProgressBox::UpdateTransfer(s64 offset, s64 size) -> ProgressBox& {
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto ProgressBox::SetImageData(std::vector<u8>& data) -> ProgressBox& {
|
||||
mutexLock(&m_mutex);
|
||||
std::swap(m_image_data, data);
|
||||
mutexUnlock(&m_mutex);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto ProgressBox::SetImageDataConst(std::span<const u8> data) -> ProgressBox& {
|
||||
mutexLock(&m_mutex);
|
||||
m_image_data.resize(data.size());
|
||||
std::memcpy(m_image_data.data(), data.data(), m_image_data.size());
|
||||
mutexUnlock(&m_mutex);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ProgressBox::RequestExit() {
|
||||
m_stop_source.request_stop();
|
||||
}
|
||||
@@ -184,4 +258,13 @@ void ProgressBox::Yield() {
|
||||
svcSleepThread(YieldType_WithoutCoreMigration);
|
||||
}
|
||||
|
||||
void ProgressBox::FreeImage() {
|
||||
if (m_image && m_own_image) {
|
||||
nvgDeleteImage(App::GetVg(), m_image);
|
||||
}
|
||||
|
||||
m_image = 0;
|
||||
m_own_image = false;
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -259,9 +259,11 @@ auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
if (touch->is_clicked && !touch->in_range(GetPos())) {
|
||||
App::PopToMenu();
|
||||
} else {
|
||||
m_list->OnUpdate(controller, touch, m_items.size(), [this](auto i) {
|
||||
m_list->OnUpdate(controller, touch, m_index, m_items.size(), [this](bool touch, auto i) {
|
||||
SetIndex(i);
|
||||
FireAction(Button::A);
|
||||
if (touch) {
|
||||
FireAction(Button::A);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -334,18 +336,6 @@ void Sidebar::SetupButtons() {
|
||||
|
||||
// add default actions, overriding if needed.
|
||||
this->SetActions(
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
auto index = m_index;
|
||||
if (m_list->ScrollDown(index, 1, m_items.size())) {
|
||||
SetIndex(index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
auto index = m_index;
|
||||
if (m_list->ScrollUp(index, 1, m_items.size())) {
|
||||
SetIndex(index);
|
||||
}
|
||||
}}),
|
||||
// each item has it's own Action, but we take over B
|
||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
|
||||
@@ -151,4 +151,30 @@ Result VerifyFixedKey(const Header& header) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
auto GetKeyGenStr(u8 key_gen) -> const char* {
|
||||
switch (key_gen) {
|
||||
case KeyGenerationOld_100: return "1.0.0";
|
||||
case KeyGenerationOld_300: return "3.0.0";
|
||||
case KeyGeneration_301: return "3.0.1";
|
||||
case KeyGeneration_400: return "4.0.0";
|
||||
case KeyGeneration_500: return "5.0.0";
|
||||
case KeyGeneration_600: return "6.0.0";
|
||||
case KeyGeneration_620: return "6.2.0";
|
||||
case KeyGeneration_700: return "7.0.0";
|
||||
case KeyGeneration_810: return "8.1.0";
|
||||
case KeyGeneration_900: return "9.0.0";
|
||||
case KeyGeneration_910: return "9.1.0";
|
||||
case KeyGeneration_1210: return "12.1.0";
|
||||
case KeyGeneration_1300: return "13.0.0";
|
||||
case KeyGeneration_1400: return "14.0.0";
|
||||
case KeyGeneration_1500: return "15.0.0";
|
||||
case KeyGeneration_1600: return "16.0.0";
|
||||
case KeyGeneration_1700: return "17.0.0";
|
||||
case KeyGeneration_1800: return "18.0.0";
|
||||
case KeyGeneration_1900: return "19.0.0";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
} // namespace sphaira::nca
|
||||
|
||||
@@ -7,16 +7,24 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
auto GetAppId(const NcmContentMetaKey& key) -> u64 {
|
||||
if (key.type == NcmContentMetaType_Patch) {
|
||||
return key.id ^ 0x800;
|
||||
} else if (key.type == NcmContentMetaType_AddOnContent) {
|
||||
return (key.id ^ 0x1000) & ~0xFFF;
|
||||
auto GetAppId(u8 meta_type, u64 id) -> u64 {
|
||||
if (meta_type == NcmContentMetaType_Patch) {
|
||||
return id ^ 0x800;
|
||||
} else if (meta_type == NcmContentMetaType_AddOnContent) {
|
||||
return (id ^ 0x1000) & ~0xFFF;
|
||||
} else {
|
||||
return key.id;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
auto GetAppId(const NcmContentMetaKey& key) -> u64 {
|
||||
return GetAppId(key.type, key.id);
|
||||
}
|
||||
|
||||
auto GetAppId(const PackagedContentMeta& meta) -> u64 {
|
||||
return GetAppId(meta.meta_type, meta.title_id);
|
||||
}
|
||||
|
||||
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id) {
|
||||
bool has;
|
||||
R_TRY(ncmContentStorageHas(cs, std::addressof(has), content_id));
|
||||
|
||||
@@ -274,7 +274,7 @@ struct Yati {
|
||||
Yati(ui::ProgressBox*, std::shared_ptr<source::Base>);
|
||||
~Yati();
|
||||
|
||||
Result Setup();
|
||||
Result Setup(const ConfigOverride& override);
|
||||
Result InstallNca(std::span<TikCollection> tickets, NcaCollection& nca);
|
||||
Result InstallCnmtNca(std::span<TikCollection> tickets, CnmtCollection& cnmt, const container::Collections& collections);
|
||||
Result InstallControlNca(std::span<TikCollection> tickets, const CnmtCollection& cnmt, NcaCollection& nca);
|
||||
@@ -549,7 +549,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
t->write_size = header.size;
|
||||
R_TRY(ncmContentStorageSetPlaceHolderSize(std::addressof(cs), std::addressof(t->nca->placeholder_id), header.size));
|
||||
|
||||
if (header.distribution_type == nca::DistributionType_GameCard) {
|
||||
if (!config.ignore_distribution_bit && header.distribution_type == nca::DistributionType_GameCard) {
|
||||
header.distribution_type = nca::DistributionType_System;
|
||||
t->nca->modified = true;
|
||||
}
|
||||
@@ -792,8 +792,8 @@ Yati::~Yati() {
|
||||
appletSetMediaPlaybackState(false);
|
||||
}
|
||||
|
||||
Result Yati::Setup() {
|
||||
config.sd_card_install = App::GetApp()->m_install_sd.Get();
|
||||
Result Yati::Setup(const ConfigOverride& override) {
|
||||
config.sd_card_install = override.sd_card_install.value_or(App::GetApp()->m_install_sd.Get());
|
||||
config.allow_downgrade = App::GetApp()->m_allow_downgrade.Get();
|
||||
config.skip_if_already_installed = App::GetApp()->m_skip_if_already_installed.Get();
|
||||
config.ticket_only = App::GetApp()->m_ticket_only.Get();
|
||||
@@ -802,13 +802,13 @@ Result Yati::Setup() {
|
||||
config.skip_addon = App::GetApp()->m_skip_addon.Get();
|
||||
config.skip_data_patch = App::GetApp()->m_skip_data_patch.Get();
|
||||
config.skip_ticket = App::GetApp()->m_skip_ticket.Get();
|
||||
config.skip_nca_hash_verify = App::GetApp()->m_skip_nca_hash_verify.Get();
|
||||
config.skip_rsa_header_fixed_key_verify = App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get();
|
||||
config.skip_rsa_npdm_fixed_key_verify = App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get();
|
||||
config.ignore_distribution_bit = App::GetApp()->m_ignore_distribution_bit.Get();
|
||||
config.convert_to_standard_crypto = App::GetApp()->m_convert_to_standard_crypto.Get();
|
||||
config.lower_master_key = App::GetApp()->m_lower_master_key.Get();
|
||||
config.lower_system_version = App::GetApp()->m_lower_system_version.Get();
|
||||
config.skip_nca_hash_verify = override.skip_nca_hash_verify.value_or(App::GetApp()->m_skip_nca_hash_verify.Get());
|
||||
config.skip_rsa_header_fixed_key_verify = override.skip_rsa_header_fixed_key_verify.value_or(App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get());
|
||||
config.skip_rsa_npdm_fixed_key_verify = override.skip_rsa_npdm_fixed_key_verify.value_or(App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get());
|
||||
config.ignore_distribution_bit = override.ignore_distribution_bit.value_or(App::GetApp()->m_ignore_distribution_bit.Get());
|
||||
config.convert_to_standard_crypto = override.convert_to_standard_crypto.value_or(App::GetApp()->m_convert_to_standard_crypto.Get());
|
||||
config.lower_master_key = override.lower_master_key.value_or(App::GetApp()->m_lower_master_key.Get());
|
||||
config.lower_system_version = override.lower_system_version.value_or(App::GetApp()->m_lower_system_version.Get());
|
||||
storage_id = config.sd_card_install ? NcmStorageId_SdCard : NcmStorageId_BuiltInUser;
|
||||
|
||||
R_TRY(source->GetOpenResult());
|
||||
@@ -925,37 +925,9 @@ Result Yati::InstallCnmtNca(std::span<TikCollection> tickets, CnmtCollection& cn
|
||||
R_TRY(ncmContentStorageFlushPlaceHolder(std::addressof(cs)));
|
||||
R_TRY(ncmContentStorageGetPlaceHolderPath(std::addressof(cs), path, sizeof(path), std::addressof(cnmt.placeholder_id)));
|
||||
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenFileSystem(std::addressof(fs), FsFileSystemType_ContentMeta, path));
|
||||
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
|
||||
|
||||
FsDir dir;
|
||||
R_TRY(fsFsOpenDirectory(std::addressof(fs), fs::FsPath{"/"}, FsDirOpenMode_ReadFiles, std::addressof(dir)));
|
||||
ON_SCOPE_EXIT(fsDirClose(std::addressof(dir)));
|
||||
|
||||
s64 total_entries;
|
||||
FsDirectoryEntry buf;
|
||||
R_TRY(fsDirRead(std::addressof(dir), std::addressof(total_entries), 1, std::addressof(buf)));
|
||||
|
||||
FsFile file;
|
||||
R_TRY(fsFsOpenFile(std::addressof(fs), fs::AppendPath("/", buf.name), FsOpenMode_Read, std::addressof(file)));
|
||||
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
|
||||
|
||||
s64 offset{};
|
||||
u64 bytes_read;
|
||||
ncm::PackagedContentMeta header;
|
||||
R_TRY(fsFileRead(std::addressof(file), offset, std::addressof(header), sizeof(header), 0, std::addressof(bytes_read)));
|
||||
offset += bytes_read;
|
||||
|
||||
// read extended header
|
||||
cnmt.extended_header.resize(header.meta_header.extended_header_size);
|
||||
R_TRY(fsFileRead(std::addressof(file), offset, cnmt.extended_header.data(), cnmt.extended_header.size(), 0, std::addressof(bytes_read)));
|
||||
offset += bytes_read;
|
||||
|
||||
// read infos.
|
||||
std::vector<NcmPackagedContentInfo> infos(header.meta_header.content_count);
|
||||
R_TRY(fsFileRead(std::addressof(file), offset, infos.data(), infos.size() * sizeof(NcmPackagedContentInfo), 0, std::addressof(bytes_read)));
|
||||
offset += bytes_read;
|
||||
std::vector<NcmPackagedContentInfo> infos;
|
||||
R_TRY(ParseCnmtNca(path, header, cnmt.extended_header, infos));
|
||||
|
||||
for (const auto& info : infos) {
|
||||
if (info.info.content_type == NcmContentType_DeltaFragment) {
|
||||
@@ -1020,21 +992,15 @@ Result Yati::InstallControlNca(std::span<TikCollection> tickets, const CnmtColle
|
||||
R_TRY(ncmContentStorageFlushPlaceHolder(std::addressof(cs)));
|
||||
R_TRY(ncmContentStorageGetPlaceHolderPath(std::addressof(cs), path, sizeof(path), std::addressof(nca.placeholder_id)));
|
||||
|
||||
log_write("got control path: %s\n", path.s);
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), ncm::GetAppId(cnmt.key), FsFileSystemType_ContentControl, path, FsContentAttributes_All));
|
||||
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
|
||||
log_write("opened control path fs: %s\n", path.s);
|
||||
|
||||
FsFile file;
|
||||
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/control.nacp"}, FsOpenMode_Read, std::addressof(file)));
|
||||
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
|
||||
log_write("got control path file: %s\n", path.s);
|
||||
|
||||
// this can fail if it's not a valid control nca, examples are mario 3d all stars.
|
||||
// there are 4 control ncas, only 1 is valid (InvalidNcaId 0x235E02).
|
||||
NacpLanguageEntry entry;
|
||||
u64 bytes_read;
|
||||
R_TRY(fsFileRead(&file, 0, &entry, sizeof(entry), 0, &bytes_read));
|
||||
pbox->SetTitle("Installing "_i18n + entry.name);
|
||||
std::vector<u8> icon;
|
||||
if (R_SUCCEEDED(yati::ParseControlNca(path, ncm::GetAppId(cnmt.key), &entry, sizeof(entry), &icon))) {
|
||||
pbox->SetTitle(entry.name).SetImageData(icon);
|
||||
} else {
|
||||
log_write("\tWARNING: failed to parse control nca!\n");
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -1070,41 +1036,37 @@ Result Yati::ParseTicketsIntoCollection(std::vector<TikCollection>& tickets, con
|
||||
|
||||
Result Yati::GetLatestVersion(const CnmtCollection& cnmt, u32& version_out, bool& skip) {
|
||||
const auto app_id = ncm::GetAppId(cnmt.key);
|
||||
|
||||
bool has_records;
|
||||
R_TRY(nsIsAnyApplicationEntityInstalled(app_id, &has_records));
|
||||
|
||||
// TODO: fix this when gamecard is inserted as it will only return records
|
||||
// for the gamecard...
|
||||
// may have to use ncm directly to get the keys, then parse that.
|
||||
version_out = cnmt.key.version;
|
||||
if (has_records) {
|
||||
s32 meta_count{};
|
||||
R_TRY(nsCountApplicationContentMeta(app_id, &meta_count));
|
||||
R_UNLESS(meta_count > 0, 0x1);
|
||||
|
||||
std::vector<ncm::ContentStorageRecord> records(meta_count);
|
||||
s32 count;
|
||||
R_TRY(ns::ListApplicationRecordContentMeta(std::addressof(ns_app), 0, app_id, records.data(), records.size(), &count));
|
||||
R_UNLESS(count == records.size(), 0x1);
|
||||
|
||||
for (auto& record : records) {
|
||||
log_write("found record: 0x%016lX type: %u version: %u\n", record.key.id, record.key.type, record.key.version);
|
||||
log_write("cnmt record: 0x%016lX type: %u version: %u\n", cnmt.key.id, cnmt.key.type, cnmt.key.version);
|
||||
|
||||
if (record.key.id == cnmt.key.id && cnmt.key.version == record.key.version && config.skip_if_already_installed) {
|
||||
log_write("skipping as already installed\n");
|
||||
skip = true;
|
||||
for (auto& db : ncm_db) {
|
||||
s32 db_list_total;
|
||||
s32 db_list_count;
|
||||
std::vector<NcmContentMetaKey> keys(1);
|
||||
if (R_SUCCEEDED(ncmContentMetaDatabaseList(std::addressof(db), std::addressof(db_list_total), std::addressof(db_list_count), keys.data(), keys.size(), NcmContentMetaType_Unknown, app_id, 0, UINT64_MAX, NcmContentInstallType_Full))) {
|
||||
if (db_list_total != keys.size()) {
|
||||
keys.resize(db_list_total);
|
||||
if (keys.size()) {
|
||||
R_TRY(ncmContentMetaDatabaseList(std::addressof(db), std::addressof(db_list_total), std::addressof(db_list_count), keys.data(), keys.size(), NcmContentMetaType_Unknown, app_id, 0, UINT64_MAX, NcmContentInstallType_Full));
|
||||
}
|
||||
}
|
||||
|
||||
// check if we are downgrading
|
||||
if (cnmt.key.type == NcmContentMetaType_Patch) {
|
||||
if (cnmt.key.type == record.key.type && cnmt.key.version < record.key.version && !config.allow_downgrade) {
|
||||
log_write("skipping due to it being lower\n");
|
||||
for (auto& key : keys) {
|
||||
log_write("found record: %016lX type: %u version: %u\n", key.id, key.type, key.version);
|
||||
|
||||
if (key.id == cnmt.key.id && cnmt.key.version == key.version && config.skip_if_already_installed) {
|
||||
log_write("skipping as already installed\n");
|
||||
skip = true;
|
||||
}
|
||||
} else {
|
||||
version_out = std::max(version_out, record.key.version);
|
||||
|
||||
// check if we are downgrading
|
||||
if (cnmt.key.type == NcmContentMetaType_Patch) {
|
||||
if (cnmt.key.type == key.type && cnmt.key.version < key.version && !config.allow_downgrade) {
|
||||
log_write("skipping due to it being lower\n");
|
||||
skip = true;
|
||||
}
|
||||
} else {
|
||||
version_out = std::max(version_out, key.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1160,7 +1122,6 @@ Result Yati::RemoveInstalledNcas(const CnmtCollection& cnmt) {
|
||||
s32 db_list_count;
|
||||
u64 id_min = cnmt.key.id;
|
||||
u64 id_max = cnmt.key.id;
|
||||
std::vector<NcmContentMetaKey> keys(1);
|
||||
|
||||
// if installing a patch, remove all previously installed patches.
|
||||
if (cnmt.key.type == NcmContentMetaType_Patch) {
|
||||
@@ -1263,9 +1224,9 @@ Result Yati::RegisterNcasAndPushRecord(const CnmtCollection& cnmt, u32 latest_ve
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InstallInternal(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections) {
|
||||
Result InstallInternal(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections, const ConfigOverride& override) {
|
||||
auto yati = std::make_unique<Yati>(pbox, source);
|
||||
R_TRY(yati->Setup());
|
||||
R_TRY(yati->Setup(override));
|
||||
|
||||
std::vector<TikCollection> tickets{};
|
||||
R_TRY(yati->ParseTicketsIntoCollection(tickets, collections, true));
|
||||
@@ -1318,9 +1279,9 @@ Result InstallInternal(ui::ProgressBox* pbox, std::shared_ptr<source::Base> sour
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, container::Collections collections) {
|
||||
Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, container::Collections collections, const ConfigOverride& override) {
|
||||
auto yati = std::make_unique<Yati>(pbox, source);
|
||||
R_TRY(yati->Setup());
|
||||
R_TRY(yati->Setup(override));
|
||||
|
||||
// not supported with stream installs (yet).
|
||||
yati->config.convert_to_standard_crypto = false;
|
||||
@@ -1415,40 +1376,107 @@ Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base
|
||||
|
||||
} // namespace
|
||||
|
||||
Result InstallFromFile(ui::ProgressBox* pbox, FsFileSystem* fs, const fs::FsPath& path) {
|
||||
return InstallFromSource(pbox, std::make_shared<source::File>(fs, path), path);
|
||||
// return InstallFromSource(pbox, std::make_shared<source::StreamFile>(fs, path), path);
|
||||
Result InstallFromFile(ui::ProgressBox* pbox, FsFileSystem* fs, const fs::FsPath& path, const ConfigOverride& override) {
|
||||
return InstallFromSource(pbox, std::make_shared<source::File>(fs, path), path, override);
|
||||
// return InstallFromSource(pbox, std::make_shared<source::StreamFile>(fs, path), path, override);
|
||||
}
|
||||
|
||||
Result InstallFromStdioFile(ui::ProgressBox* pbox, const fs::FsPath& path) {
|
||||
return InstallFromSource(pbox, std::make_shared<source::Stdio>(path), path);
|
||||
Result InstallFromStdioFile(ui::ProgressBox* pbox, const fs::FsPath& path, const ConfigOverride& override) {
|
||||
return InstallFromSource(pbox, std::make_shared<source::Stdio>(path), path, override);
|
||||
}
|
||||
|
||||
Result InstallFromSource(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const fs::FsPath& path) {
|
||||
Result InstallFromSource(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const fs::FsPath& path, const ConfigOverride& override) {
|
||||
const auto ext = std::strrchr(path.s, '.');
|
||||
R_UNLESS(ext, Result_ContainerNotFound);
|
||||
|
||||
if (!strcasecmp(ext, ".nsp") || !strcasecmp(ext, ".nsz")) {
|
||||
return InstallFromContainer(pbox, std::make_unique<container::Nsp>(source));
|
||||
return InstallFromContainer(pbox, std::make_unique<container::Nsp>(source), override);
|
||||
} else if (!strcasecmp(ext, ".xci") || !strcasecmp(ext, ".xcz")) {
|
||||
return InstallFromContainer(pbox, std::make_unique<container::Xci>(source));
|
||||
return InstallFromContainer(pbox, std::make_unique<container::Xci>(source), override);
|
||||
}
|
||||
|
||||
R_THROW(Result_ContainerNotFound);
|
||||
}
|
||||
|
||||
Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr<container::Base> container) {
|
||||
Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr<container::Base> container, const ConfigOverride& override) {
|
||||
container::Collections collections;
|
||||
R_TRY(container->GetCollections(collections));
|
||||
return InstallFromCollections(pbox, container->GetSource(), collections);
|
||||
}
|
||||
|
||||
Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections) {
|
||||
Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections, const ConfigOverride& override) {
|
||||
if (source->IsStream()) {
|
||||
return InstallInternalStream(pbox, source, collections);
|
||||
return InstallInternalStream(pbox, source, collections, override);
|
||||
} else {
|
||||
return InstallInternal(pbox, source, collections);
|
||||
return InstallInternal(pbox, source, collections, override);
|
||||
}
|
||||
}
|
||||
|
||||
Result ParseCnmtNca(const fs::FsPath& path, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos) {
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenFileSystem(std::addressof(fs), FsFileSystemType_ContentMeta, path));
|
||||
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
|
||||
|
||||
FsDir dir;
|
||||
R_TRY(fsFsOpenDirectory(std::addressof(fs), fs::FsPath{"/"}, FsDirOpenMode_ReadFiles, std::addressof(dir)));
|
||||
ON_SCOPE_EXIT(fsDirClose(std::addressof(dir)));
|
||||
|
||||
s64 total_entries;
|
||||
FsDirectoryEntry buf;
|
||||
R_TRY(fsDirRead(std::addressof(dir), std::addressof(total_entries), 1, std::addressof(buf)));
|
||||
|
||||
FsFile file;
|
||||
R_TRY(fsFsOpenFile(std::addressof(fs), fs::AppendPath("/", buf.name), FsOpenMode_Read, std::addressof(file)));
|
||||
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
|
||||
|
||||
s64 offset{};
|
||||
u64 bytes_read;
|
||||
R_TRY(fsFileRead(std::addressof(file), offset, std::addressof(header), sizeof(header), 0, std::addressof(bytes_read)));
|
||||
offset += bytes_read;
|
||||
|
||||
// read extended header
|
||||
extended_header.resize(header.meta_header.extended_header_size);
|
||||
R_TRY(fsFileRead(std::addressof(file), offset, extended_header.data(), extended_header.size(), 0, std::addressof(bytes_read)));
|
||||
offset += bytes_read;
|
||||
|
||||
// read infos.
|
||||
infos.resize(header.meta_header.content_count);
|
||||
R_TRY(fsFileRead(std::addressof(file), offset, infos.data(), infos.size() * sizeof(NcmPackagedContentInfo), 0, std::addressof(bytes_read)));
|
||||
offset += bytes_read;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ParseControlNca(const fs::FsPath& path, u64 id, void* nacp_out, s64 nacp_size, std::vector<u8>* icon_out) {
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), id, FsFileSystemType_ContentControl, path, FsContentAttributes_All));
|
||||
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
|
||||
|
||||
// read nacp.
|
||||
if (nacp_out) {
|
||||
FsFile file;
|
||||
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/control.nacp"}, FsOpenMode_Read, std::addressof(file)));
|
||||
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
|
||||
|
||||
u64 bytes_read;
|
||||
R_TRY(fsFileRead(&file, 0, nacp_out, nacp_size, 0, &bytes_read));
|
||||
}
|
||||
|
||||
// read icon.
|
||||
if (icon_out) {
|
||||
FsFile file;
|
||||
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/icon_AmericanEnglish.dat"}, FsOpenMode_Read, std::addressof(file)));
|
||||
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
|
||||
|
||||
s64 size;
|
||||
R_TRY(fsFileGetSize(std::addressof(file), std::addressof(size)));
|
||||
icon_out->resize(size);
|
||||
|
||||
u64 bytes_read;
|
||||
R_TRY(fsFileRead(&file, 0, icon_out->data(), icon_out->size(), 0, &bytes_read));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace sphaira::yati
|
||||
|
||||
Reference in New Issue
Block a user