From 8070268d2ad51021426d163888f1d23b6a53a8df Mon Sep 17 00:00:00 2001 From: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Thu, 22 May 2025 15:50:50 +0100 Subject: [PATCH] add hdd dump support, cleanup dump code into a single generic file. --- sphaira/CMakeLists.txt | 14 + sphaira/include/app.hpp | 3 + sphaira/include/dumper.hpp | 45 +++ sphaira/include/location.hpp | 17 ++ sphaira/include/ui/menus/gc_menu.hpp | 2 +- sphaira/source/app.cpp | 28 +- sphaira/source/dumper.cpp | 418 ++++++++++++++++++++++++++ sphaira/source/fs.cpp | 13 +- sphaira/source/location.cpp | 32 ++ sphaira/source/ui/menus/game_menu.cpp | 366 +--------------------- sphaira/source/ui/menus/gc_menu.cpp | 411 +++---------------------- sphaira/source/ui/menus/main_menu.cpp | 4 + 12 files changed, 634 insertions(+), 719 deletions(-) create mode 100644 sphaira/include/dumper.hpp create mode 100644 sphaira/source/dumper.cpp diff --git a/sphaira/CMakeLists.txt b/sphaira/CMakeLists.txt index 96cf4e8..1f3b307 100644 --- a/sphaira/CMakeLists.txt +++ b/sphaira/CMakeLists.txt @@ -66,6 +66,7 @@ add_executable(sphaira source/app.cpp source/download.cpp + source/dumper.cpp source/option.cpp source/evman.cpp source/fs.cpp @@ -196,6 +197,13 @@ FetchContent_Declare(zstd SOURCE_SUBDIR build/cmake ) +FetchContent_Declare(libusbhsfs + # GIT_REPOSITORY https://github.com/DarkMatterCore/libusbhsfs.git + # GIT_TAG v0.2.9 + GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git + GIT_TAG sxos_disable +) + set(USE_NEW_ZSTD ON) set(ZSTD_BUILD_STATIC ON) @@ -234,6 +242,10 @@ set(YYJSON_DISABLE_NON_STANDARD ON) set(YYJSON_DISABLE_UTF8_VALIDATION ON) set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF) +# enable this if you want ntfs and ext4 support, at the cost of a huge final binary size. +set(USBHSFS_GPL OFF) +set(USBHSFS_SXOS_DISABLE ON) + FetchContent_MakeAvailable( ftpsrv libhaze @@ -243,6 +255,7 @@ FetchContent_MakeAvailable( minIni yyjson zstd + libusbhsfs ) set(FTPSRV_LIB_BUILD TRUE) @@ -317,6 +330,7 @@ target_link_libraries(sphaira PRIVATE nanovg stb yyjson + libusbhsfs ${minizip_lib} ZLIB::ZLIB diff --git a/sphaira/include/app.hpp b/sphaira/include/app.hpp index baf8e9a..67be96c 100644 --- a/sphaira/include/app.hpp +++ b/sphaira/include/app.hpp @@ -78,6 +78,7 @@ public: static auto GetMtpEnable() -> bool; static auto GetFtpEnable() -> bool; static auto GetNxlinkEnable() -> bool; + static auto GetHddEnable() -> bool; static auto GetLogEnable() -> bool; static auto GetReplaceHbmenuEnable() -> bool; static auto GetInstallEnable() -> bool; @@ -93,6 +94,7 @@ public: static void SetMtpEnable(bool enable); static void SetFtpEnable(bool enable); static void SetNxlinkEnable(bool enable); + static void SetHddEnable(bool enable); static void SetLogEnable(bool enable); static void SetReplaceHbmenuEnable(bool enable); static void SetInstallSysmmcEnable(bool enable); @@ -224,6 +226,7 @@ public: option::OptionBool m_nxlink_enabled{INI_SECTION, "nxlink_enabled", true}; option::OptionBool m_mtp_enabled{INI_SECTION, "mtp_enabled", false}; option::OptionBool m_ftp_enabled{INI_SECTION, "ftp_enabled", false}; + option::OptionBool m_hdd_enabled{INI_SECTION, "hdd_enabled", false}; option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false}; option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false}; option::OptionBool m_theme_music{INI_SECTION, "theme_music", true}; diff --git a/sphaira/include/dumper.hpp b/sphaira/include/dumper.hpp new file mode 100644 index 0000000..5645dfe --- /dev/null +++ b/sphaira/include/dumper.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "fs.hpp" +#include +#include +#include +#include + +namespace sphaira::dump { + +enum DumpLocationType { + // dump using native fs. + DumpLocationType_SdCard, + // dump to usb using tinfoil protocol. + DumpLocationType_UsbS2S, + // speed test, only reads the data, doesn't write anything. + DumpLocationType_DevNull, + // dump to stdio, ideal for custom mount points using devoptab, such as hdd. + DumpLocationType_Stdio, + // dump to custom locations found in locations.ini. + DumpLocationType_Network, +}; + +enum DumpLocationFlag { + DumpLocationFlag_SdCard = 1 << DumpLocationType_SdCard, + DumpLocationFlag_UsbS2S = 1 << DumpLocationType_UsbS2S, + DumpLocationFlag_DevNull = 1 << DumpLocationType_DevNull, + DumpLocationFlag_Stdio = 1 << DumpLocationType_Stdio, + DumpLocationFlag_Network = 1 << DumpLocationType_Network, + DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio | DumpLocationFlag_Network, +}; + +struct BaseSource { + virtual ~BaseSource() = default; + virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0; + virtual auto GetName(const std::string& path) const -> std::string = 0; + virtual auto GetSize(const std::string& path) const -> s64 = 0; +}; + +// called after dump has finished. +using OnExit = std::function; + +void Dump(std::shared_ptr source, const std::vector& paths, OnExit on_exit = [](Result){}, u32 location_flags = DumpLocationFlag_All); + +} // namespace sphaira::dump diff --git a/sphaira/include/location.hpp b/sphaira/include/location.hpp index 5ccb28c..13de62e 100644 --- a/sphaira/include/location.hpp +++ b/sphaira/include/location.hpp @@ -21,4 +21,21 @@ using Entries = std::vector; auto Load() -> Entries; void Add(const Entry& e); +// helper for hdd devices. +// this doesn't really belong in this header, however +// locations likely will be renamed to something more generic soon. +struct StdioEntry { + // mount point (ums0:) + std::string mount{}; + // ums0: (USB Flash Disk) + std::string name{}; + // set if read-only. + bool write_protect; +}; + +using StdioEntries = std::vector; + +// set write=true to filter out write protected devices. +auto GetStdio(bool write) -> StdioEntries; + } // namespace sphaira::location diff --git a/sphaira/include/ui/menus/gc_menu.hpp b/sphaira/include/ui/menus/gc_menu.hpp index 6e9c7a9..362d3b9 100644 --- a/sphaira/include/ui/menus/gc_menu.hpp +++ b/sphaira/include/ui/menus/gc_menu.hpp @@ -78,7 +78,7 @@ private: Result UpdateStorageSize(); void FreeImage(); void OnChangeIndex(s64 new_index); - void DumpGames(u32 flags); + Result DumpGames(u32 flags); private: FsDeviceOperator m_dev_op{}; diff --git a/sphaira/source/app.cpp b/sphaira/source/app.cpp index d042784..27b321d 100644 --- a/sphaira/source/app.cpp +++ b/sphaira/source/app.cpp @@ -30,6 +30,7 @@ #include #include #include +#include extern "C" { u32 __nx_applet_exit_mode = 0; @@ -606,6 +607,10 @@ auto App::GetNxlinkEnable() -> bool { return g_app->m_nxlink_enabled.Get(); } +auto App::GetHddEnable() -> bool { + return g_app->m_hdd_enabled.Get(); +} + auto App::GetLogEnable() -> bool { return g_app->m_log_enabled.Get(); } @@ -673,6 +678,17 @@ void App::SetNxlinkEnable(bool enable) { } } +void App::SetHddEnable(bool enable) { + if (App::GetHddEnable() != enable) { + g_app->m_hdd_enabled.Set(enable); + if (enable) { + usbHsFsInitialize(1); + } else { + usbHsFsExit(); + } + } +} + void App::SetLogEnable(bool enable) { if (App::GetLogEnable() != enable) { g_app->m_log_enabled.Set(enable); @@ -1290,6 +1306,10 @@ App::App(const char* argv0) { nxlinkInitialize(nxlink_callback); } + if (App::GetHddEnable()) { + usbHsFsInitialize(1); + } + curl::Init(); // get current size of the framebuffer @@ -1422,9 +1442,6 @@ App::App(const char* argv0) { // padInitializeDefault(&m_pad); padInitializeAny(&m_pad); - // usbHsFsSetFileSystemMountFlags(UsbHsFsMountFlags_ReadOnly); - // usbHsFsSetPopulateCallback(); - // usbHsFsInitialize(0); const auto loader_info_size = envGetLoaderInfoSize(); if (loader_info_size) { @@ -1804,6 +1821,11 @@ App::~App() { nxlinkExit(); } + if (App::GetHddEnable()) { + log_write("closing hdd\n"); + usbHsFsExit(); + } + if (App::GetLogEnable()) { log_write("closing log\n"); log_file_exit(); diff --git a/sphaira/source/dumper.cpp b/sphaira/source/dumper.cpp new file mode 100644 index 0000000..ca29a23 --- /dev/null +++ b/sphaira/source/dumper.cpp @@ -0,0 +1,418 @@ +#include "dumper.hpp" +#include "app.hpp" +#include "log.hpp" +#include "fs.hpp" +#include "download.hpp" +#include "defines.hpp" +#include "i18n.hpp" +#include "location.hpp" +#include "threaded_file_transfer.hpp" + +#include "ui/sidebar.hpp" +#include "ui/error_box.hpp" +#include "ui/option_box.hpp" +#include "ui/progress_box.hpp" +#include "ui/popup_list.hpp" +#include "ui/nvg_util.hpp" + +#include "yati/source/stream.hpp" + +#include "usb/usb_uploader.hpp" +#include "usb/tinfoil.hpp" + +namespace sphaira::dump { +namespace { + +struct DumpEntry { + DumpLocationType type; + s32 index; +}; + +struct DumpLocation { + const DumpLocationType type; + const char* display_name; +}; + +constexpr DumpLocation DUMP_LOCATIONS[]{ + { DumpLocationType_SdCard, "microSD card (/dumps/NSP/)" }, + { DumpLocationType_UsbS2S, "USB transfer (Switch 2 Switch)" }, + { DumpLocationType_DevNull, "/dev/null (Speed Test)" }, +}; + +struct UsbTest final : usb::upload::Usb, yati::source::Stream { + UsbTest(ui::ProgressBox* pbox, BaseSource* source) : Usb{UINT64_MAX} { + m_pbox = pbox; + m_source = source; + } + + Result ReadChunk(void* buf, s64 size, u64* bytes_read) override { + R_TRY(m_pull(buf, size, bytes_read)); + m_pull_offset += *bytes_read; + R_SUCCEED(); + } + + Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override { + if (m_pull) { + return Stream::Read(buf, off, size, bytes_read); + } else { + return ReadInternal(path, buf, off, size, bytes_read); + } + } + + Result ReadInternal(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) { + if (m_path != path) { + m_path = path; + m_progress = 0; + m_pull_offset = 0; + Stream::Reset(); + m_size = m_source->GetSize(path); + m_pbox->SetTitle(m_source->GetName(path)); + m_pbox->NewTransfer(m_path); + } + + R_TRY(m_source->Read(path, buf, off, size, bytes_read)); + + m_offset += *bytes_read; + m_progress += *bytes_read; + m_pbox->UpdateTransfer(m_progress, m_size); + + R_SUCCEED(); + } + + void SetPullCallback(thread::PullCallback pull) { + m_pull = pull; + } + + auto* GetSource() { + return m_source; + } + + auto GetPullOffset() const { + return m_pull_offset; + } + +private: + ui::ProgressBox* m_pbox{}; + BaseSource* m_source{}; + std::string m_path{}; + thread::PullCallback m_pull{}; + s64 m_offset{}; + s64 m_size{}; + s64 m_progress{}; + s64 m_pull_offset{}; +}; + +Result DumpToFile(ui::ProgressBox* pbox, BaseSource* source, std::span paths) { + static constexpr fs::FsPath DUMP_PATH{"/dumps/NSP"}; + constexpr s64 BIG_FILE_SIZE = 1024ULL*1024ULL*1024ULL*4ULL; + + fs::FsNativeSd fs{}; + R_TRY(fs.GetFsOpenResult()); + + for (auto path : paths) { + const auto file_size = source->GetSize(path); + pbox->SetTitle(source->GetName(path)); + pbox->NewTransfer(path); + + const auto temp_path = fs::AppendPath(DUMP_PATH, path + ".temp"); + fs.CreateDirectoryRecursivelyWithPath(temp_path); + fs.DeleteFile(temp_path); + + const auto flags = file_size >= BIG_FILE_SIZE ? FsCreateOption_BigFile : 0; + R_TRY(fs.CreateFile(temp_path, file_size, flags)); + ON_SCOPE_EXIT(fs.DeleteFile(temp_path)); + + { + FsFile file; + R_TRY(fs.OpenFile(temp_path, FsOpenMode_Write, &file)); + ON_SCOPE_EXIT(fsFileClose(&file)); + + R_TRY(thread::Transfer(pbox, file_size, + [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { + return source->Read(path, data, off, size, bytes_read); + }, + [&](const void* data, s64 off, s64 size) -> Result { + return fsFileWrite(&file, off, data, size, FsWriteOption_None); + } + )); + } + + path = fs::AppendPath(DUMP_PATH, path); + fs.DeleteFile(path); + R_TRY(fs.RenameFile(temp_path, path)); + } + + R_SUCCEED(); +} + +Result DumpToStdio(ui::ProgressBox* pbox, const location::StdioEntry& loc, BaseSource* source, std::span paths) { + const fs::FsPath DUMP_PATH = loc.mount + "/dumps/NSP"; + + fs::FsStdio fs{}; + + for (auto path : paths) { + const auto file_size = source->GetSize(path); + pbox->SetTitle(source->GetName(path)); + pbox->NewTransfer(path); + + const auto temp_path = fs::AppendPath(DUMP_PATH, path + ".temp"); + fs.CreateDirectoryRecursivelyWithPath(temp_path); + fs.DeleteFile(temp_path); + + R_TRY(fs.CreateFile(temp_path, file_size)); + ON_SCOPE_EXIT(fs.DeleteFile(temp_path)); + + { + auto file = std::fopen(temp_path, "wb"); + R_UNLESS(file, 0x1); + ON_SCOPE_EXIT(std::fclose(file)); + + R_TRY(thread::Transfer(pbox, file_size, + [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { + return source->Read(path, data, off, size, bytes_read); + }, + [&](const void* data, s64 off, s64 size) -> Result { + const auto written = std::fwrite(data, 1, size, file); + R_UNLESS(written >= 1, 0x1); + R_SUCCEED(); + } + )); + } + + path = fs::AppendPath(DUMP_PATH, path); + fs.DeleteFile(path); + R_TRY(fs.RenameFile(temp_path, path)); + } + + R_SUCCEED(); +} + +Result DumpToUsbS2SStream(ui::ProgressBox* pbox, UsbTest* usb, std::span paths) { + auto source = usb->GetSource(); + + for (auto& path : paths) { + const auto file_size = source->GetSize(path); + + R_TRY(thread::TransferPull(pbox, file_size, + [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { + return usb->ReadInternal(path, data, off, size, bytes_read); + }, + [&](thread::StartThreadCallback start, thread::PullCallback pull) -> Result { + usb->SetPullCallback(pull); + R_TRY(start()); + + while (!pbox->ShouldExit()) { + R_TRY(usb->PollCommands()); + + if (usb->GetPullOffset() >= file_size) { + R_SUCCEED(); + } + } + + R_THROW(0xFFFF); + } + )); + } + + R_SUCCEED(); +} + +Result DumpToUsbS2SRandom(ui::ProgressBox* pbox, UsbTest* usb) { + while (!pbox->ShouldExit()) { + R_TRY(usb->PollCommands()); + } + + R_THROW(0xFFFF); +} + +Result DumpToUsbS2S(ui::ProgressBox* pbox, BaseSource* source, std::span paths) { + std::vector file_list; + for (const auto& path : paths) { + file_list.emplace_back(path); + } + + // auto usb = std::make_unique(pbox, entries); + auto usb = std::make_unique(pbox, source); + constexpr u64 timeout = 1e+9; + + while (!pbox->ShouldExit()) { + if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) { + pbox->NewTransfer("USB connected, sending file list"); + 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..."); + + Result rc; + if (flags & usb::tinfoil::USBFlag_STREAM) { + rc = DumpToUsbS2SStream(pbox, usb.get(), paths); + } else { + rc = DumpToUsbS2SRandom(pbox, usb.get()); + } + + // wait for exit command. + if (R_SUCCEEDED(rc)) { + log_write("waiting for exit command\n"); + rc = usb->PollCommands(); + log_write("finished polling for exit command\n"); + } else { + log_write("skipped polling for exit command\n"); + } + + if (rc == usb->Result_Exit) { + log_write("got exit command\n"); + R_SUCCEED(); + } + + return rc; + } + } else { + pbox->NewTransfer("waiting for usb connection..."); + } + } + + R_THROW(0xFFFF); +} + +Result DumpToDevNull(ui::ProgressBox* pbox, BaseSource* source, std::span paths) { + for (auto path : paths) { + R_TRY(pbox->ShouldExitResult()); + + const auto file_size = source->GetSize(path); + pbox->SetTitle(source->GetName(path)); + pbox->NewTransfer(path); + + R_TRY(thread::Transfer(pbox, file_size, + [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { + return source->Read(path, data, off, size, bytes_read); + }, + [&](const void* data, s64 off, s64 size) -> Result { + R_SUCCEED(); + } + )); + } + + R_SUCCEED(); +} + +Result DumpToNetwork(ui::ProgressBox* pbox, const location::Entry& loc, BaseSource* source, std::span paths) { + for (auto path : paths) { + R_TRY(pbox->ShouldExitResult()); + + const auto file_size = source->GetSize(path); + pbox->SetTitle(source->GetName(path)); + pbox->NewTransfer(path); + + R_TRY(thread::TransferPull(pbox, file_size, + [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { + return source->Read(path, data, off, size, bytes_read); + }, + [&](thread::PullCallback pull) -> Result { + s64 offset{}; + const auto result = curl::Api().FromMemory( + CURL_LOCATION_TO_API(loc), + curl::OnProgress{pbox->OnDownloadProgressCallback()}, + curl::UploadInfo{ + path, file_size, + [&](void *ptr, size_t size) -> size_t { + // curl will request past the size of the file, causing an error. + if (offset >= file_size) { + log_write("finished file upload\n"); + return 0; + } + + u64 bytes_read{}; + if (R_FAILED(pull(ptr, size, &bytes_read))) { + log_write("failed to read in custom callback: %zd size: %zd\n", offset, size); + return 0; + } + + offset += bytes_read; + return bytes_read; + } + } + ); + + R_UNLESS(result.success, 0x1); + R_SUCCEED(); + } + )); + } + + R_SUCCEED(); +} + +} // namespace + +void Dump(std::shared_ptr source, const std::vector& paths, OnExit on_exit, u32 location_flags) { + ui::PopupList::Items items; + std::vector dump_entries; + + const auto network_locations = location::Load(); + if (location_flags & (1 << DumpLocationType_Network)) { + for (s32 i = 0; i < std::size(network_locations); i++) { + dump_entries.emplace_back(DumpLocationType_Network, i); + items.emplace_back(network_locations[i].name); + } + } + + const auto stdio_locations = location::GetStdio(true); + if (location_flags & (1 << DumpLocationType_Stdio)) { + for (s32 i = 0; i < std::size(stdio_locations); i++) { + dump_entries.emplace_back(DumpLocationType_Stdio, i); + items.emplace_back(stdio_locations[i].name); + } + } + + for (s32 i = 0; i < std::size(DUMP_LOCATIONS); i++) { + if (location_flags & (1 << DUMP_LOCATIONS[i].type)) { + dump_entries.emplace_back(DUMP_LOCATIONS[i].type, i); + items.emplace_back(i18n::get(DUMP_LOCATIONS[i].display_name)); + } + } + + App::Push(std::make_shared( + "Select dump location"_i18n, items, [source, paths, on_exit, network_locations, stdio_locations, dump_entries](auto op_index){ + if (!op_index) { + on_exit(0xFFFF); + return; + } + + const auto dump_entry = dump_entries[*op_index]; + + App::Push(std::make_shared(0, "Dumping"_i18n, "", [source, paths, network_locations, stdio_locations, dump_entry](auto pbox) -> Result { + if (dump_entry.type == DumpLocationType_Network) { + R_TRY(DumpToNetwork(pbox, network_locations[dump_entry.index], source.get(), paths)); + } else if (dump_entry.type == DumpLocationType_Stdio) { + R_TRY(DumpToStdio(pbox, stdio_locations[dump_entry.index], source.get(), paths)); + } else if (dump_entry.type == DumpLocationType_SdCard) { + R_TRY(DumpToFile(pbox, source.get(), paths)); + } else if (dump_entry.type == DumpLocationType_UsbS2S) { + R_TRY(DumpToUsbS2S(pbox, source.get(), paths)); + } else if (dump_entry.type == DumpLocationType_DevNull) { + R_TRY(DumpToDevNull(pbox, source.get(), paths)); + } + + R_SUCCEED(); + }, [on_exit](Result rc){ + App::PushErrorBox(rc, "Dump failed!"_i18n); + + if (R_SUCCEEDED(rc)) { + App::Notify("Dump successfull!"); + log_write("dump successfull!!!\n"); + } + + on_exit(rc); + })); + } + )); +} + +void DumpSingle(std::shared_ptr source, const fs::FsPath& path, OnExit on_exit, DumpLocationType type) { + +} + +} // namespace sphaira::dump diff --git a/sphaira/source/fs.cpp b/sphaira/source/fs.cpp index 39acc61..21f81f4 100644 --- a/sphaira/source/fs.cpp +++ b/sphaira/source/fs.cpp @@ -119,6 +119,7 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig R_UNLESS(ignore_read_only || !is_read_only_root(_path), Fs::ResultReadOnly); auto path_view = std::string_view{_path}; + // todo: fix this for sdmc: and ums0: FsPath path{"/"}; for (const auto dir : std::views::split(path_view, '/')) { @@ -276,8 +277,12 @@ Result copy_entire_file(FsFileSystem* fs, const FsPath& dst, const FsPath& src, Result CreateFile(const FsPath& path, u64 size, u32 option, bool ignore_read_only) { R_UNLESS(ignore_read_only || !is_read_only_root(path), Fs::ResultReadOnly); - auto fd = open(path, O_CREAT | S_IRUSR | S_IWUSR); + auto fd = open(path, O_WRONLY | O_CREAT, DEFFILEMODE); if (fd == -1) { + if (errno == EEXIST) { + return FsError_PathAlreadyExists; + } + R_TRY(fsdevGetLastResult()); return Fs::ResultUnknownStdioError; } @@ -288,7 +293,11 @@ Result CreateFile(const FsPath& path, u64 size, u32 option, bool ignore_read_onl Result CreateDirectory(const FsPath& path, bool ignore_read_only) { R_UNLESS(ignore_read_only || !is_read_only_root(path), Fs::ResultReadOnly); - if (mkdir(path, 0777)) { + if (mkdir(path, ACCESSPERMS)) { + if (errno == EEXIST) { + return FsError_PathAlreadyExists; + } + R_TRY(fsdevGetLastResult()); return Fs::ResultUnknownStdioError; } diff --git a/sphaira/source/location.cpp b/sphaira/source/location.cpp index 9fcb8e8..4c9ff08 100644 --- a/sphaira/source/location.cpp +++ b/sphaira/source/location.cpp @@ -1,8 +1,10 @@ #include "location.hpp" #include "fs.hpp" +#include "app.hpp" #include #include +#include namespace sphaira::location { namespace { @@ -72,4 +74,34 @@ auto Load() -> Entries { return out; } +auto GetStdio(bool write) -> StdioEntries { + if (!App::GetHddEnable()) { + log_write("[USBHSFS] not enabled\n"); + return {}; + } + + static UsbHsFsDevice devices[0x20]; + const auto count = usbHsFsListMountedDevices(devices, std::size(devices)); + log_write("[USBHSFS] got connected: %u\n", usbHsFsGetPhysicalDeviceCount()); + log_write("[USBHSFS] got count: %u\n", count); + + StdioEntries out{}; + for (s32 i = 0; i < count; i++) { + const auto& e = devices[i]; + + if (write && e.write_protect) { + log_write("[USBHSFS] skipping write protect\n"); + continue; + } + + char display_name[0x100]; + std::snprintf(display_name, sizeof(display_name), "%s (%s - %s - %zu GB)", e.name, LIBUSBHSFS_FS_TYPE_STR(e.fs_type), e.product_name, e.capacity / 1024 / 1024 / 1024); + + out.emplace_back(e.name, display_name, e.write_protect); + log_write("\t[USBHSFS] %s name: %s serial: %s man: %s\n", e.name, e.product_name, e.serial_number, e.manufacturer); + } + + return out; +} + } // namespace sphaira::location diff --git a/sphaira/source/ui/menus/game_menu.cpp b/sphaira/source/ui/menus/game_menu.cpp index fa343d3..be072fd 100644 --- a/sphaira/source/ui/menus/game_menu.cpp +++ b/sphaira/source/ui/menus/game_menu.cpp @@ -1,11 +1,9 @@ #include "app.hpp" #include "log.hpp" #include "fs.hpp" -#include "download.hpp" +#include "dumper.hpp" #include "defines.hpp" #include "i18n.hpp" -#include "location.hpp" -#include "threaded_file_transfer.hpp" #include "ui/menus/game_menu.hpp" #include "ui/sidebar.hpp" @@ -20,10 +18,6 @@ #include "yati/nx/es.hpp" #include "yati/container/base.hpp" #include "yati/container/nsp.hpp" -#include "yati/source/stream.hpp" - -#include "usb/usb_uploader.hpp" -#include "usb/tinfoil.hpp" #include #include @@ -49,23 +43,6 @@ enum ContentFlag { ContentFlag_All = ContentFlag_Application | ContentFlag_Patch | ContentFlag_AddOnContent | ContentFlag_DataPatch, }; -enum DumpLocationType { - DumpLocationType_SdCard, - DumpLocationType_UsbS2S, - DumpLocationType_DevNull, -}; - -struct DumpLocation { - const DumpLocationType type; - const char* display_name; -}; - -constexpr DumpLocation DUMP_LOCATIONS[]{ - { DumpLocationType_SdCard, "microSD card (/dumps/NSP/)" }, - { DumpLocationType_UsbS2S, "USB transfer (Switch 2 Switch)" }, - { DumpLocationType_DevNull, "/dev/null (Speed Test)" }, -}; - struct NcmEntry { const NcmStorageId storage_id; NcmContentStorage cs{}; @@ -203,13 +180,8 @@ private: } }; -struct BaseSource { - virtual ~BaseSource() = default; - virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0; -}; - -struct NspSource final : BaseSource { - NspSource(std::span entries) : m_entries{entries} { } +struct NspSource final : dump::BaseSource { + NspSource(const std::vector& entries) : m_entries{entries} { } Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override { const auto it = std::ranges::find_if(m_entries, [&path](auto& e){ @@ -220,10 +192,6 @@ struct NspSource final : BaseSource { return it->Read(buf, off, size, bytes_read); } - auto GetEntries() const -> std::span { - return m_entries; - } - auto GetName(const std::string& path) const -> std::string { const auto it = std::ranges::find_if(m_entries, [&path](auto& e){ return path == e.path; @@ -249,278 +217,9 @@ struct NspSource final : BaseSource { } private: - std::span m_entries{}; + std::vector m_entries{}; }; -struct UsbTest final : usb::upload::Usb, yati::source::Stream { - UsbTest(ProgressBox* pbox, std::span entries) : Usb{UINT64_MAX} { - m_source = std::make_unique(entries); - m_pbox = pbox; - } - - Result ReadChunk(void* buf, s64 size, u64* bytes_read) override { - R_TRY(m_pull(buf, size, bytes_read)); - m_pull_offset += *bytes_read; - R_SUCCEED(); - } - - Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override { - if (m_pull) { - return Stream::Read(buf, off, size, bytes_read); - } else { - return ReadInternal(path, buf, off, size, bytes_read); - } - } - - Result ReadInternal(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) { - if (m_path != path) { - m_path = path; - m_progress = 0; - m_pull_offset = 0; - Stream::Reset(); - m_size = m_source->GetSize(path); - m_pbox->SetTitle(m_source->GetName(path)); - m_pbox->NewTransfer(m_path); - } - - R_TRY(m_source->Read(path, buf, off, size, bytes_read)); - - m_offset += *bytes_read; - m_progress += *bytes_read; - m_pbox->UpdateTransfer(m_progress, m_size); - - R_SUCCEED(); - } - - void SetPullCallback(thread::PullCallback pull) { - m_pull = pull; - } - - auto* GetSource() { - return m_source.get(); - } - - auto GetPullOffset() const { - return m_pull_offset; - } - -private: - std::unique_ptr m_source{}; - ProgressBox* m_pbox{}; - std::string m_path{}; - thread::PullCallback m_pull{}; - s64 m_offset{}; - s64 m_size{}; - s64 m_progress{}; - s64 m_pull_offset{}; -}; - -Result DumpNspToFile(ProgressBox* pbox, std::span entries) { - static constexpr fs::FsPath DUMP_PATH{"/dumps/NSP"}; - constexpr s64 BIG_FILE_SIZE = 1024ULL*1024ULL*1024ULL*4ULL; - - fs::FsNativeSd fs{}; - R_TRY(fs.GetFsOpenResult()); - - auto source = std::make_unique(entries); - for (const auto& e : entries) { - auto path = e.path; - const auto file_size = e.nsp_size; - pbox->SetTitle(e.application_name); - pbox->NewTransfer(path); - - const auto temp_path = fs::AppendPath(DUMP_PATH, path + ".temp"); - fs.CreateDirectoryRecursivelyWithPath(temp_path); - fs.DeleteFile(temp_path); - - const auto flags = file_size >= BIG_FILE_SIZE ? FsCreateOption_BigFile : 0; - R_TRY(fs.CreateFile(temp_path, file_size, flags)); - ON_SCOPE_EXIT(fs.DeleteFile(temp_path)); - - { - FsFile file; - R_TRY(fs.OpenFile(temp_path, FsOpenMode_Write, &file)); - ON_SCOPE_EXIT(fsFileClose(&file)); - - R_TRY(thread::Transfer(pbox, file_size, - [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { - return source->Read(path, data, off, size, bytes_read); - }, - [&](const void* data, s64 off, s64 size) -> Result { - return fsFileWrite(&file, off, data, size, FsWriteOption_None); - } - )); - } - - path = fs::AppendPath(DUMP_PATH, path); - fs.DeleteFile(path); - R_TRY(fs.RenameFile(temp_path, path)); - } - - R_SUCCEED(); -} - -Result DumpNspToUsbS2SStream(ProgressBox* pbox, UsbTest* usb, std::span file_list, std::span entries) { - auto source = usb->GetSource(); - - for (auto& path : file_list) { - const auto file_size = source->GetSize(path); - - R_TRY(thread::TransferPull(pbox, file_size, - [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { - return usb->ReadInternal(path, data, off, size, bytes_read); - }, - [&](thread::StartThreadCallback start, thread::PullCallback pull) -> Result { - usb->SetPullCallback(pull); - R_TRY(start()); - - while (!pbox->ShouldExit()) { - R_TRY(usb->PollCommands()); - - if (usb->GetPullOffset() >= file_size) { - R_SUCCEED(); - } - } - - R_THROW(0xFFFF); - } - )); - } - - R_SUCCEED(); -} - -Result DumpNspToUsbS2SRandom(ProgressBox* pbox, UsbTest* usb, std::span file_list, std::span entries) { - while (!pbox->ShouldExit()) { - R_TRY(usb->PollCommands()); - } - - R_THROW(0xFFFF); -} - -Result DumpNspToUsbS2S(ProgressBox* pbox, std::span entries) { - std::vector file_list; - for (auto& e : entries) { - file_list.emplace_back(e.path); - } - - auto usb = std::make_unique(pbox, entries); - constexpr u64 timeout = 1e+9; - - while (!pbox->ShouldExit()) { - if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) { - pbox->NewTransfer("USB connected, sending file list"); - 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..."); - - Result rc; - if (flags & usb::tinfoil::USBFlag_STREAM) { - rc = DumpNspToUsbS2SStream(pbox, usb.get(), file_list, entries); - } else { - rc = DumpNspToUsbS2SRandom(pbox, usb.get(), file_list, entries); - } - - // wait for exit command. - if (R_SUCCEEDED(rc)) { - log_write("waiting for exit command\n"); - rc = usb->PollCommands(); - log_write("finished polling for exit command\n"); - } else { - log_write("skipped polling for exit command\n"); - } - - if (rc == usb->Result_Exit) { - log_write("got exit command\n"); - R_SUCCEED(); - } - - return rc; - } - } else { - pbox->NewTransfer("waiting for usb connection..."); - } - } - - R_THROW(0xFFFF); -} - -Result DumpNspToDevNull(ProgressBox* pbox, std::span entries) { - auto source = std::make_unique(entries); - for (const auto& e : entries) { - const auto path = e.path; - const auto file_size = e.nsp_size; - pbox->SetTitle(e.application_name); - pbox->NewTransfer(path); - - R_TRY(thread::Transfer(pbox, file_size, - [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { - return source->Read(path, data, off, size, bytes_read); - }, - [&](const void* data, s64 off, s64 size) -> Result { - R_SUCCEED(); - } - )); - } - - R_SUCCEED(); -} - -Result DumpNspToNetwork(ProgressBox* pbox, const location::Entry& loc, std::span entries) { - auto source = std::make_unique(entries); - for (const auto& e : entries) { - if (pbox->ShouldExit()) { - R_THROW(0xFFFF); - } - - const auto path = e.path; - const auto file_size = e.nsp_size; - pbox->SetTitle(e.application_name); - pbox->NewTransfer(path); - - R_TRY(thread::TransferPull(pbox, file_size, - [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { - return source->Read(path, data, off, size, bytes_read); - }, - [&](thread::PullCallback pull) -> Result { - s64 offset{}; - const auto result = curl::Api().FromMemory( - CURL_LOCATION_TO_API(loc), - curl::OnProgress{pbox->OnDownloadProgressCallback()}, - curl::UploadInfo{ - path, file_size, - [&](void *ptr, size_t size) -> size_t { - // curl will request past the size of the file, causing an error. - if (offset >= file_size) { - log_write("finished file upload\n"); - return 0; - } - - u64 bytes_read{}; - if (R_FAILED(pull(ptr, size, &bytes_read))) { - log_write("failed to read in custom callback: %zd size: %zd\n", offset, size); - return 0; - } - - offset += bytes_read; - return bytes_read; - } - } - ); - - R_UNLESS(result.success, 0x1); - R_SUCCEED(); - } - )); - } - - R_SUCCEED(); -} - Result Notify(Result rc, const std::string& error_message) { if (R_FAILED(rc)) { App::Push(std::make_shared(rc, @@ -1400,57 +1099,22 @@ void Menu::DeleteGames() { } void Menu::DumpGames(u32 flags) { - PopupList::Items items; - const auto network_locations = location::Load(); + auto targets = GetSelectedEntries(); - for (const auto&p : network_locations) { - items.emplace_back(p.name); + std::vector nsp_entries; + for (auto& e : targets) { + BuildNspEntries(e, flags, nsp_entries); } - for (const auto&p : DUMP_LOCATIONS) { - items.emplace_back(i18n::get(p.display_name)); + std::vector paths; + for (auto& e : nsp_entries) { + paths.emplace_back(e.path); } - App::Push(std::make_shared( - "Select dump location"_i18n, items, [this, network_locations, flags](auto op_index){ - if (!op_index) { - return; - } - - const auto index = *op_index; - App::Push(std::make_shared(0, "Dumping"_i18n, "", [this, network_locations, index, flags](auto pbox) -> Result { - auto targets = GetSelectedEntries(); - - std::vector nsp_entries; - for (auto& e : targets) { - BuildNspEntries(e, flags, nsp_entries); - } - - const auto index2 = index - network_locations.size(); - - if (!network_locations.empty() && index < network_locations.size()) { - R_TRY(DumpNspToNetwork(pbox, network_locations[index], nsp_entries)); - } else if (index2 == DumpLocationType_SdCard) { - R_TRY(DumpNspToFile(pbox, nsp_entries)); - } else if (index2 == DumpLocationType_UsbS2S) { - R_TRY(DumpNspToUsbS2S(pbox, nsp_entries)); - } else if (index2 == DumpLocationType_DevNull) { - R_TRY(DumpNspToDevNull(pbox, nsp_entries)); - } - - R_SUCCEED(); - }, [this](Result rc){ - App::PushErrorBox(rc, "Dump failed!"_i18n); - ClearSelection(); - - if (R_SUCCEEDED(rc)) { - App::Notify("Dump successfull!"); - log_write("dump successfull!!!\n"); - } - })); - } - - )); + auto source = std::make_shared(nsp_entries); + dump::Dump(source, paths, [this](Result rc){ + ClearSelection(); + }); } } // namespace sphaira::ui::menu::game diff --git a/sphaira/source/ui/menus/gc_menu.cpp b/sphaira/source/ui/menus/gc_menu.cpp index 88c4ced..96f1dff 100644 --- a/sphaira/source/ui/menus/gc_menu.cpp +++ b/sphaira/source/ui/menus/gc_menu.cpp @@ -6,17 +6,13 @@ #include "yati/yati.hpp" #include "yati/nx/nca.hpp" -#include "yati/source/stream.hpp" -#include "usb/usb_uploader.hpp" -#include "usb/tinfoil.hpp" #include "app.hpp" #include "defines.hpp" #include "log.hpp" #include "i18n.hpp" #include "download.hpp" -#include "location.hpp" -#include "threaded_file_transfer.hpp" +#include "dumper.hpp" #include #include @@ -46,23 +42,6 @@ enum DumpFileFlag { DumpFileFlag_All = DumpFileFlag_XCI | DumpFileFlag_AllBin, }; -enum DumpLocationType { - DumpLocationType_SdCard, - DumpLocationType_UsbS2S, - DumpLocationType_DevNull, -}; - -struct DumpLocation { - const DumpLocationType type; - const char* display_name; -}; - -constexpr DumpLocation DUMP_LOCATIONS[]{ - { DumpLocationType_SdCard, "microSD card (/dumps/XCI/)" }, - { DumpLocationType_UsbS2S, "USB transfer (Switch 2 Switch)" }, - { DumpLocationType_DevNull, "/dev/null (Speed Test)" }, -}; - const char *g_option_list[] = { "Install", "Dump", @@ -177,7 +156,7 @@ auto BuildGcPath(const char* name, const FsGameCardHandle* handle, FsGameCardPar return path; } -struct XciEntry { +struct XciSource final : dump::BaseSource { // application name. std::string application_name{}; // extra @@ -189,7 +168,7 @@ struct XciEntry { s64 xci_size{}; Menu* menu{}; - Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) { + Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override { if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) { size = ClipSize(off, size, xci_size); *bytes_read = size; @@ -216,11 +195,11 @@ struct XciEntry { } } - auto GetName(const std::string& path) const -> std::string { + auto GetName(const std::string& path) const -> std::string override { return application_name; } - auto GetSize(const std::string& path) const -> s64 { + auto GetSize(const std::string& path) const -> s64 override { if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) { return xci_size; } else if (path.ends_with(GetDumpTypeStr(DumpFileType_Set))) { @@ -245,64 +224,6 @@ private: } }; -struct UsbTest final : usb::upload::Usb, yati::source::Stream { - UsbTest(ProgressBox* pbox, XciEntry& entry) : Usb{UINT64_MAX}, m_entry{entry} { - m_pbox = pbox; - } - - Result ReadChunk(void* buf, s64 size, u64* bytes_read) override { - R_TRY(m_pull(buf, size, bytes_read)); - m_pull_offset += *bytes_read; - R_SUCCEED(); - } - - Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override { - if (m_pull) { - return Stream::Read(buf, off, size, bytes_read); - } else { - return ReadInternal(path, buf, off, size, bytes_read); - } - } - - Result ReadInternal(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) { - if (m_path != path) { - m_path = path; - m_progress = 0; - m_pull_offset = 0; - Stream::Reset(); - m_size = m_entry.GetSize(path); - m_pbox->SetTitle(m_entry.GetName(path)); - m_pbox->NewTransfer(m_path); - } - - R_TRY(m_entry.Read(path, buf, off, size, bytes_read)); - - m_offset += *bytes_read; - m_progress += *bytes_read; - m_pbox->UpdateTransfer(m_progress, m_size); - - R_SUCCEED(); - } - - void SetPullCallback(thread::PullCallback pull) { - m_pull = pull; - } - - auto GetPullOffset() const { - return m_pull_offset; - } - -private: - XciEntry& m_entry; - ProgressBox* m_pbox{}; - std::string m_path{}; - thread::PullCallback m_pull{}; - s64 m_offset{}; - s64 m_size{}; - s64 m_progress{}; - s64 m_pull_offset{}; -}; - struct HashStr { char str[0x21]; }; @@ -315,201 +236,6 @@ HashStr hexIdToStr(auto id) { return str; } -Result DumpNspToFile(ProgressBox* pbox, std::span paths, XciEntry& e) { - static constexpr fs::FsPath DUMP_PATH{"/dumps/XCI"}; - constexpr s64 BIG_FILE_SIZE = 1024ULL*1024ULL*1024ULL*4ULL; - - fs::FsNativeSd fs{}; - R_TRY(fs.GetFsOpenResult()); - - for (auto path : paths) { - const auto file_size = e.GetSize(path); - pbox->SetTitle(e.application_name); - pbox->NewTransfer(path); - - const auto temp_path = fs::AppendPath(DUMP_PATH, path + ".temp"); - fs.CreateDirectoryRecursivelyWithPath(temp_path); - fs.DeleteFile(temp_path); - - const auto flags = file_size >= BIG_FILE_SIZE ? FsCreateOption_BigFile : 0; - R_TRY(fs.CreateFile(temp_path, file_size, flags)); - ON_SCOPE_EXIT(fs.DeleteFile(temp_path)); - - { - FsFile file; - R_TRY(fs.OpenFile(temp_path, FsOpenMode_Write, &file)); - ON_SCOPE_EXIT(fsFileClose(&file)); - - R_TRY(thread::Transfer(pbox, file_size, - [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { - return e.Read(path, data, off, size, bytes_read); - }, - [&](const void* data, s64 off, s64 size) -> Result { - return fsFileWrite(&file, off, data, size, FsWriteOption_None); - } - )); - } - - path = fs::AppendPath(DUMP_PATH, path); - fs.DeleteFile(path); - R_TRY(fs.RenameFile(temp_path, path)); - } - - R_SUCCEED(); -} - -Result DumpNspToUsbS2SStream(ProgressBox* pbox, UsbTest* usb, std::span file_list, XciEntry& e) { - for (auto& path : file_list) { - const auto file_size = e.GetSize(path); - - R_TRY(thread::TransferPull(pbox, file_size, - [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { - return usb->ReadInternal(path, data, off, size, bytes_read); - }, - [&](thread::StartThreadCallback start, thread::PullCallback pull) -> Result { - usb->SetPullCallback(pull); - R_TRY(start()); - - while (!pbox->ShouldExit()) { - R_TRY(usb->PollCommands()); - - if (usb->GetPullOffset() >= file_size) { - R_SUCCEED(); - } - } - - R_THROW(0xFFFF); - } - )); - } - - R_SUCCEED(); -} - -Result DumpNspToUsbS2SRandom(ProgressBox* pbox, UsbTest* usb, std::span file_list, XciEntry& e) { - while (!pbox->ShouldExit()) { - R_TRY(usb->PollCommands()); - } - - R_THROW(0xFFFF); -} - -Result DumpNspToUsbS2S(ProgressBox* pbox, std::span paths, XciEntry& e) { - std::vector file_list; - for (auto& path : paths) { - file_list.emplace_back(path); - } - - auto usb = std::make_unique(pbox, e); - constexpr u64 timeout = 1e+9; - - while (!pbox->ShouldExit()) { - if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) { - pbox->NewTransfer("USB connected, sending file list"); - 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..."); - - Result rc; - if (flags & usb::tinfoil::USBFlag_STREAM) { - rc = DumpNspToUsbS2SStream(pbox, usb.get(), file_list, e); - } else { - rc = DumpNspToUsbS2SRandom(pbox, usb.get(), file_list, e); - } - - // wait for exit command. - if (R_SUCCEEDED(rc)) { - rc = usb->PollCommands(); - } - - if (rc == usb->Result_Exit) { - log_write("got exit command\n"); - R_SUCCEED(); - } - - return rc; - } - } else { - pbox->NewTransfer("waiting for usb connection..."); - } - } - - R_THROW(0xFFFF); -} - -Result DumpNspToDevNull(ProgressBox* pbox, std::span paths, XciEntry& e) { - for (const auto& path : paths) { - const auto file_size = e.GetSize(path); - pbox->SetTitle(e.application_name); - pbox->NewTransfer(path); - - R_TRY(thread::Transfer(pbox, file_size, - [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { - return e.Read(path, data, off, size, bytes_read); - }, - [&](const void* data, s64 off, s64 size) -> Result { - R_SUCCEED(); - } - )); - } - - R_SUCCEED(); -} - -Result DumpNspToNetwork(ProgressBox* pbox, const location::Entry& loc, std::span paths, XciEntry& e) { - for (const auto& path : paths) { - if (pbox->ShouldExit()) { - R_THROW(0xFFFF); - } - - const auto file_size = e.GetSize(path); - pbox->SetTitle(e.application_name); - pbox->NewTransfer(path); - - R_TRY(thread::TransferPull(pbox, file_size, - [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { - return e.Read(path, data, off, size, bytes_read); - }, - [&](thread::PullCallback pull) -> Result { - s64 offset{}; - const auto result = curl::Api().FromMemory( - CURL_LOCATION_TO_API(loc), - curl::OnProgress{pbox->OnDownloadProgressCallback()}, - curl::UploadInfo{ - path, file_size, - [&](void *ptr, size_t size) -> size_t { - // curl will request past the size of the file, causing an error. - if (offset >= file_size) { - log_write("finished file upload\n"); - return 0; - } - - u64 bytes_read{}; - if (R_FAILED(pull(ptr, size, &bytes_read))) { - log_write("failed to read in custom callback: %zd size: %zd\n", offset, size); - return 0; - } - - offset += bytes_read; - return bytes_read; - } - } - ); - - - R_UNLESS(result.success, 0x1); - R_SUCCEED(); - } - )); - } - - R_SUCCEED(); -} - // from Gamecard-Installer-NX Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, FsGameCardStoragePartition partition) { const struct { @@ -1208,92 +934,53 @@ void Menu::OnChangeIndex(s64 new_index) { } } -void Menu::DumpGames(u32 flags) { - PopupList::Items items; - const auto network_locations = location::Load(); +Result Menu::DumpGames(u32 flags) { + R_TRY(GcMountStorage()); - for (const auto&p : network_locations) { - items.emplace_back(p.name); - } + auto source = std::make_shared(); + source->menu = this; + source->application_name = m_entries[m_entry_index].lang_entry.name; - for (const auto&p : DUMP_LOCATIONS) { - items.emplace_back(i18n::get(p.display_name)); - } - - App::Push(std::make_shared( - "Select dump location"_i18n, items, [this, network_locations, flags](auto op_index){ - if (!op_index) { - return; - } - - const auto index = *op_index; - App::Push(std::make_shared(0, "Dumping"_i18n, "", [this, network_locations, index, flags](auto pbox) -> Result { - XciEntry entry{}; - entry.menu = this; - entry.application_name = m_entries[m_entry_index].lang_entry.name; - - R_TRY(GcMountStorage()); - - std::vector paths; - if (flags & DumpFileFlag_XCI) { - if (App::GetApp()->m_dump_trim_xci.Get()) { - entry.xci_size = m_storage_trimmed_size; - paths.emplace_back(BuildFullDumpPath(DumpFileType_TrimmedXCI, m_entries)); - } else { - entry.xci_size = m_storage_total_size; - paths.emplace_back(BuildFullDumpPath(DumpFileType_XCI, m_entries)); - } - } - - if (flags & DumpFileFlag_Set) { - entry.id_set.resize(0xC); - R_TRY(fsDeviceOperatorGetGameCardIdSet(&m_dev_op, entry.id_set.data(), entry.id_set.size(), entry.id_set.size())); - paths.emplace_back(BuildFullDumpPath(DumpFileType_Set, m_entries)); - } - - // todo: - if (flags & DumpFileFlag_UID) { - // paths.emplace_back(BuildFullDumpPath(DumpFileType_UID, m_entries)); - } - - if (flags & DumpFileFlag_Cert) { - s64 size; - entry.cert.resize(0x200); - R_TRY(fsDeviceOperatorGetGameCardDeviceCertificate(&m_dev_op, &m_handle, entry.cert.data(), entry.cert.size(), &size, entry.cert.size())); - paths.emplace_back(BuildFullDumpPath(DumpFileType_Cert, m_entries)); - } - - // todo: - if (flags & DumpFileFlag_Initial) { - // paths.emplace_back(BuildFullDumpPath(DumpFileType_Initial, m_entries)); - } - - const auto index2 = index - network_locations.size(); - - if (!network_locations.empty() && index < network_locations.size()) { - R_TRY(DumpNspToNetwork(pbox, network_locations[index], paths, entry)); - } else if (index2 == DumpLocationType_SdCard) { - R_TRY(DumpNspToFile(pbox, paths, entry)); - } else if (index2 == DumpLocationType_UsbS2S) { - R_TRY(DumpNspToUsbS2S(pbox, paths, entry)); - } else if (index2 == DumpLocationType_DevNull) { - R_TRY(DumpNspToDevNull(pbox, paths, entry)); - } - - R_SUCCEED(); - }, [this](Result rc){ - App::PushErrorBox(rc, "Dump failed!"_i18n); - - if (R_SUCCEEDED(rc)) { - App::Notify("Dump successfull!"); - log_write("dump successfull!!!\n"); - } - - GcUmountStorage(); - GcUnmount(); - })); + std::vector paths; + if (flags & DumpFileFlag_XCI) { + if (App::GetApp()->m_dump_trim_xci.Get()) { + source->xci_size = m_storage_trimmed_size; + paths.emplace_back(BuildFullDumpPath(DumpFileType_TrimmedXCI, m_entries)); + } else { + source->xci_size = m_storage_total_size; + paths.emplace_back(BuildFullDumpPath(DumpFileType_XCI, m_entries)); } - )); + } + + if (flags & DumpFileFlag_Set) { + source->id_set.resize(0xC); + R_TRY(fsDeviceOperatorGetGameCardIdSet(&m_dev_op, source->id_set.data(), source->id_set.size(), source->id_set.size())); + paths.emplace_back(BuildFullDumpPath(DumpFileType_Set, m_entries)); + } + + // todo: + if (flags & DumpFileFlag_UID) { + // paths.emplace_back(BuildFullDumpPath(DumpFileType_UID, m_entries)); + } + + if (flags & DumpFileFlag_Cert) { + s64 size; + source->cert.resize(0x200); + R_TRY(fsDeviceOperatorGetGameCardDeviceCertificate(&m_dev_op, &m_handle, source->cert.data(), source->cert.size(), &size, source->cert.size())); + paths.emplace_back(BuildFullDumpPath(DumpFileType_Cert, m_entries)); + } + + // todo: + if (flags & DumpFileFlag_Initial) { + // paths.emplace_back(BuildFullDumpPath(DumpFileType_Initial, m_entries)); + } + + dump::Dump(source, paths, [this](Result rc){ + GcUmountStorage(); + GcUnmount(); + }); + + R_SUCCEED(); } } // namespace sphaira::ui::menu::gc diff --git a/sphaira/source/ui/menus/main_menu.cpp b/sphaira/source/ui/menus/main_menu.cpp index df434d2..826e1c0 100644 --- a/sphaira/source/ui/menus/main_menu.cpp +++ b/sphaira/source/ui/menus/main_menu.cpp @@ -278,6 +278,10 @@ MainMenu::MainMenu() { App::SetNxlinkEnable(enable); })); + options->Add(std::make_shared("Hdd"_i18n, App::GetHddEnable(), [](bool& enable){ + App::SetHddEnable(enable); + })); + if (m_update_state == UpdateState::Update) { options->Add(std::make_shared("Download update: "_i18n + m_update_version, [this](){ App::Push(std::make_shared(0, "Downloading "_i18n, "Sphaira v" + m_update_version, [this](auto pbox) -> Result {