From a91550174ab5ba57e43699d58c013fb639d7cd75 Mon Sep 17 00:00:00 2001 From: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Wed, 21 May 2025 19:52:22 +0100 Subject: [PATCH] add dump options, allow for gamecard menu to be accessed without install enabled. --- sphaira/include/app.hpp | 7 ++++ sphaira/source/app.cpp | 25 ++++++++++++ sphaira/source/ui/menus/game_menu.cpp | 17 +++++++- sphaira/source/ui/menus/gc_menu.cpp | 57 ++++++++++++++++++++------- sphaira/source/ui/menus/main_menu.cpp | 2 +- 5 files changed, 91 insertions(+), 17 deletions(-) diff --git a/sphaira/include/app.hpp b/sphaira/include/app.hpp index 80cac2b..baf8e9a 100644 --- a/sphaira/include/app.hpp +++ b/sphaira/include/app.hpp @@ -115,6 +115,7 @@ public: static void DisplayMiscOptions(bool left_side = true); static void DisplayAdvancedOptions(bool left_side = true); static void DisplayInstallOptions(bool left_side = true); + static void DisplayDumpOptions(bool left_side = true); void Draw(); void Update(); @@ -252,6 +253,12 @@ public: option::OptionBool m_lower_master_key{INI_SECTION, "lower_master_key", false}; option::OptionBool m_lower_system_version{INI_SECTION, "lower_system_version", false}; + // dump options + option::OptionBool m_dump_app_folder{"dump", "app_folder", true}; + option::OptionBool m_dump_append_folder_with_xci{"dump", "append_folder_with_xci", true}; + option::OptionBool m_dump_trim_xci{"dump", "trim_xci", false}; + option::OptionBool m_dump_usb_transfer_stream{"dump", "usb_transfer_stream", true}; + // todo: move this into it's own menu option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal diff --git a/sphaira/source/app.cpp b/sphaira/source/app.cpp index ff16f86..d042784 100644 --- a/sphaira/source/app.cpp +++ b/sphaira/source/app.cpp @@ -1590,6 +1590,10 @@ void App::DisplayAdvancedOptions(bool left_side) { options->Add(std::make_shared("Install options"_i18n, [left_side](){ App::DisplayInstallOptions(left_side); })); + + options->Add(std::make_shared("Dump options"_i18n, [left_side](){ + App::DisplayDumpOptions(left_side); + })); } void App::DisplayInstallOptions(bool left_side) { @@ -1681,6 +1685,27 @@ void App::DisplayInstallOptions(bool left_side) { })); } +void App::DisplayDumpOptions(bool left_side) { + auto options = std::make_shared("Dump Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT); + ON_SCOPE_EXIT(App::Push(options)); + + options->Add(std::make_shared("Created nested folder"_i18n, App::GetApp()->m_dump_app_folder.Get(), [](bool& enable){ + App::GetApp()->m_dump_app_folder.Set(enable); + })); + + options->Add(std::make_shared("Append folder with .xci"_i18n, App::GetApp()->m_dump_append_folder_with_xci.Get(), [](bool& enable){ + App::GetApp()->m_dump_append_folder_with_xci.Set(enable); + })); + + options->Add(std::make_shared("Trim XCI"_i18n, App::GetApp()->m_dump_trim_xci.Get(), [](bool& enable){ + App::GetApp()->m_dump_trim_xci.Set(enable); + })); + + options->Add(std::make_shared("Multi-threaded USB transfer"_i18n, App::GetApp()->m_dump_usb_transfer_stream.Get(), [](bool& enable){ + App::GetApp()->m_dump_usb_transfer_stream.Set(enable); + })); +} + App::~App() { log_write("starting to exit\n"); diff --git a/sphaira/source/ui/menus/game_menu.cpp b/sphaira/source/ui/menus/game_menu.cpp index 7f95073..fa343d3 100644 --- a/sphaira/source/ui/menus/game_menu.cpp +++ b/sphaira/source/ui/menus/game_menu.cpp @@ -410,7 +410,11 @@ Result DumpNspToUsbS2S(ProgressBox* pbox, std::span entries) { while (!pbox->ShouldExit()) { if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) { pbox->NewTransfer("USB connected, sending file list"); - const u8 flags = usb::tinfoil::USBFlag_STREAM; + u8 flags = usb::tinfoil::USBFlag_NONE; + if (App::GetApp()->m_dump_usb_transfer_stream.Get()) { + flags |= usb::tinfoil::USBFlag_STREAM; + } + if (R_SUCCEEDED(usb->WaitForConnection(timeout, flags, file_list))) { pbox->NewTransfer("Sent file list, waiting for command..."); @@ -740,7 +744,12 @@ auto BuildNspPath(const Entry& e, const NsApplicationContentMetaStatus& status) } fs::FsPath path; - std::snprintf(path, sizeof(path), "%s/%s %s[%016lX][v%u][%s].nsp", name_buf.s, name_buf.s, version, status.application_id, status.version, ncm::GetMetaTypeShortStr(status.meta_type)); + if (App::GetApp()->m_dump_app_folder.Get()) { + std::snprintf(path, sizeof(path), "%s/%s %s[%016lX][v%u][%s].nsp", name_buf.s, name_buf.s, version, status.application_id, status.version, ncm::GetMetaTypeShortStr(status.meta_type)); + } else { + std::snprintf(path, sizeof(path), "%s %s[%016lX][v%u][%s].nsp", name_buf.s, version, status.application_id, status.version, ncm::GetMetaTypeShortStr(status.meta_type)); + } + return path; } @@ -1117,6 +1126,10 @@ Menu::Menu() : grid::Menu{"Games"_i18n} { }, true)); }, true)); + options->Add(std::make_shared("Dump options"_i18n, [this](){ + App::DisplayDumpOptions(false); + })); + // completely deletes the application record and all data. options->Add(std::make_shared("Delete"_i18n, [this](){ const auto buf = "Are you sure you want to delete "_i18n + m_entries[m_index].GetName() + "?"; diff --git a/sphaira/source/ui/menus/gc_menu.cpp b/sphaira/source/ui/menus/gc_menu.cpp index 7f24852..88c4ced 100644 --- a/sphaira/source/ui/menus/gc_menu.cpp +++ b/sphaira/source/ui/menus/gc_menu.cpp @@ -2,6 +2,7 @@ #include "ui/nvg_util.hpp" #include "ui/sidebar.hpp" #include "ui/popup_list.hpp" +#include "ui/option_box.hpp" #include "yati/yati.hpp" #include "yati/nx/nca.hpp" @@ -150,7 +151,15 @@ auto BuildFilePath(DumpFileType type, std::span entries) // builds path suiteable for file dumps. auto BuildFullDumpPath(DumpFileType type, std::span entries) -> fs::FsPath { const auto base_path = BuildXciBasePath(entries); - return base_path + "/" + base_path + GetDumpTypeStr(type); + if (App::GetApp()->m_dump_app_folder.Get()) { + if (App::GetApp()->m_dump_append_folder_with_xci.Get()) { + return base_path + ".xci/" + base_path + GetDumpTypeStr(type); + } else { + return base_path + "/" + base_path + GetDumpTypeStr(type); + } + } else { + return base_path + GetDumpTypeStr(type); + } } // @Gc is the mount point, S is for secure partion, the remaining is the @@ -397,7 +406,11 @@ Result DumpNspToUsbS2S(ProgressBox* pbox, std::span paths, Xci while (!pbox->ShouldExit()) { if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) { pbox->NewTransfer("USB connected, sending file list"); - const u8 flags = usb::tinfoil::USBFlag_STREAM; + u8 flags = usb::tinfoil::USBFlag_NONE; + if (App::GetApp()->m_dump_usb_transfer_stream.Get()) { + flags |= usb::tinfoil::USBFlag_STREAM; + } + if (R_SUCCEEDED(usb->WaitForConnection(timeout, flags, file_list))) { pbox->NewTransfer("Sent file list, waiting for command..."); @@ -636,7 +649,16 @@ Menu::Menu() : MenuBase{"GameCard"_i18n} { SetPop(); }}), std::make_pair(Button::X, Action{"Options"_i18n, [this](){ - App::DisplayInstallOptions(false); + auto options = std::make_shared("Game Options"_i18n, Sidebar::Side::RIGHT); + ON_SCOPE_EXIT(App::Push(options)); + + options->Add(std::make_shared("Install options"_i18n, [this](){ + App::DisplayInstallOptions(false); + })); + + options->Add(std::make_shared("Dump options"_i18n, [this](){ + App::DisplayDumpOptions(false); + })); }}) ); @@ -875,16 +897,24 @@ Result Menu::GcMount() { } if (m_option_index == 0) { - App::Push(std::make_shared(m_icon, "Installing "_i18n, m_entries[m_entry_index].lang_entry.name, [this](auto pbox) -> Result { - auto source = std::make_shared(m_entries[m_entry_index], m_fs.get()); - return yati::InstallFromCollections(pbox, source, source->m_collections, source->m_config); - }, [this](Result rc){ - App::PushErrorBox(rc, "Gc install failed"_i18n); + if (!App::GetInstallEnable()) { + App::Push(std::make_shared( + "Install disabled...\n" + "Please enable installing via the install options."_i18n, + "OK"_i18n + )); + } else { + App::Push(std::make_shared(m_icon, "Installing "_i18n, m_entries[m_entry_index].lang_entry.name, [this](auto pbox) -> Result { + auto source = std::make_shared(m_entries[m_entry_index], m_fs.get()); + return yati::InstallFromCollections(pbox, source, source->m_collections, source->m_config); + }, [this](Result rc){ + App::PushErrorBox(rc, "Gc install failed"_i18n); - if (R_SUCCEEDED(rc)) { - App::Notify("Gc install success!"_i18n); - } - })); + if (R_SUCCEEDED(rc)) { + App::Notify("Gc install success!"_i18n); + } + })); + } } else { auto options = std::make_shared("Select content to dump"_i18n, Sidebar::Side::RIGHT); ON_SCOPE_EXIT(App::Push(options)); @@ -1206,8 +1236,7 @@ void Menu::DumpGames(u32 flags) { std::vector paths; if (flags & DumpFileFlag_XCI) { - // todo: add config support for full and trimmed. - if (1) { + if (App::GetApp()->m_dump_trim_xci.Get()) { entry.xci_size = m_storage_trimmed_size; paths.emplace_back(BuildFullDumpPath(DumpFileType_TrimmedXCI, m_entries)); } else { diff --git a/sphaira/source/ui/menus/main_menu.cpp b/sphaira/source/ui/menus/main_menu.cpp index 6910155..df434d2 100644 --- a/sphaira/source/ui/menus/main_menu.cpp +++ b/sphaira/source/ui/menus/main_menu.cpp @@ -43,7 +43,7 @@ const MiscMenuEntry MISC_MENU_ENTRIES[] = { { .name = "GitHub", .title = "GitHub", .func = MiscMenuFuncGenerator, .flag = MiscMenuFlag_Shortcut }, { .name = "FTP", .title = "FTP Install", .func = MiscMenuFuncGenerator, .flag = MiscMenuFlag_Install }, { .name = "USB", .title = "USB Install", .func = MiscMenuFuncGenerator, .flag = MiscMenuFlag_Install }, - { .name = "GameCard", .title = "GameCard Install", .func = MiscMenuFuncGenerator, .flag = MiscMenuFlag_Shortcut|MiscMenuFlag_Install }, + { .name = "GameCard", .title = "GameCard", .func = MiscMenuFuncGenerator, .flag = MiscMenuFlag_Shortcut }, { .name = "IRS", .title = "IRS (Infrared Joycon Camera)", .func = MiscMenuFuncGenerator, .flag = MiscMenuFlag_Shortcut }, };