add hdd dump support, cleanup dump code into a single generic file.

This commit is contained in:
ITotalJustice
2025-05-22 15:50:50 +01:00
parent 2e6d757852
commit 8070268d2a
12 changed files with 634 additions and 719 deletions

View File

@@ -66,6 +66,7 @@ add_executable(sphaira
source/app.cpp source/app.cpp
source/download.cpp source/download.cpp
source/dumper.cpp
source/option.cpp source/option.cpp
source/evman.cpp source/evman.cpp
source/fs.cpp source/fs.cpp
@@ -196,6 +197,13 @@ FetchContent_Declare(zstd
SOURCE_SUBDIR build/cmake 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(USE_NEW_ZSTD ON)
set(ZSTD_BUILD_STATIC 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_UTF8_VALIDATION ON)
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF) 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( FetchContent_MakeAvailable(
ftpsrv ftpsrv
libhaze libhaze
@@ -243,6 +255,7 @@ FetchContent_MakeAvailable(
minIni minIni
yyjson yyjson
zstd zstd
libusbhsfs
) )
set(FTPSRV_LIB_BUILD TRUE) set(FTPSRV_LIB_BUILD TRUE)
@@ -317,6 +330,7 @@ target_link_libraries(sphaira PRIVATE
nanovg nanovg
stb stb
yyjson yyjson
libusbhsfs
${minizip_lib} ${minizip_lib}
ZLIB::ZLIB ZLIB::ZLIB

View File

@@ -78,6 +78,7 @@ public:
static auto GetMtpEnable() -> bool; static auto GetMtpEnable() -> bool;
static auto GetFtpEnable() -> bool; static auto GetFtpEnable() -> bool;
static auto GetNxlinkEnable() -> bool; static auto GetNxlinkEnable() -> bool;
static auto GetHddEnable() -> bool;
static auto GetLogEnable() -> bool; static auto GetLogEnable() -> bool;
static auto GetReplaceHbmenuEnable() -> bool; static auto GetReplaceHbmenuEnable() -> bool;
static auto GetInstallEnable() -> bool; static auto GetInstallEnable() -> bool;
@@ -93,6 +94,7 @@ public:
static void SetMtpEnable(bool enable); static void SetMtpEnable(bool enable);
static void SetFtpEnable(bool enable); static void SetFtpEnable(bool enable);
static void SetNxlinkEnable(bool enable); static void SetNxlinkEnable(bool enable);
static void SetHddEnable(bool enable);
static void SetLogEnable(bool enable); static void SetLogEnable(bool enable);
static void SetReplaceHbmenuEnable(bool enable); static void SetReplaceHbmenuEnable(bool enable);
static void SetInstallSysmmcEnable(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_nxlink_enabled{INI_SECTION, "nxlink_enabled", true};
option::OptionBool m_mtp_enabled{INI_SECTION, "mtp_enabled", false}; option::OptionBool m_mtp_enabled{INI_SECTION, "mtp_enabled", false};
option::OptionBool m_ftp_enabled{INI_SECTION, "ftp_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_log_enabled{INI_SECTION, "log_enabled", false};
option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false}; option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false};
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true}; option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};

View File

@@ -0,0 +1,45 @@
#pragma once
#include "fs.hpp"
#include <switch.h>
#include <vector>
#include <memory>
#include <functional>
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(Result rc)>;
void Dump(std::shared_ptr<BaseSource> source, const std::vector<fs::FsPath>& paths, OnExit on_exit = [](Result){}, u32 location_flags = DumpLocationFlag_All);
} // namespace sphaira::dump

View File

@@ -21,4 +21,21 @@ using Entries = std::vector<Entry>;
auto Load() -> Entries; auto Load() -> Entries;
void Add(const Entry& e); 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<StdioEntry>;
// set write=true to filter out write protected devices.
auto GetStdio(bool write) -> StdioEntries;
} // namespace sphaira::location } // namespace sphaira::location

View File

@@ -78,7 +78,7 @@ private:
Result UpdateStorageSize(); Result UpdateStorageSize();
void FreeImage(); void FreeImage();
void OnChangeIndex(s64 new_index); void OnChangeIndex(s64 new_index);
void DumpGames(u32 flags); Result DumpGames(u32 flags);
private: private:
FsDeviceOperator m_dev_op{}; FsDeviceOperator m_dev_op{};

View File

@@ -30,6 +30,7 @@
#include <ctime> #include <ctime>
#include <span> #include <span>
#include <dirent.h> #include <dirent.h>
#include <usbhsfs.h>
extern "C" { extern "C" {
u32 __nx_applet_exit_mode = 0; u32 __nx_applet_exit_mode = 0;
@@ -606,6 +607,10 @@ auto App::GetNxlinkEnable() -> bool {
return g_app->m_nxlink_enabled.Get(); return g_app->m_nxlink_enabled.Get();
} }
auto App::GetHddEnable() -> bool {
return g_app->m_hdd_enabled.Get();
}
auto App::GetLogEnable() -> bool { auto App::GetLogEnable() -> bool {
return g_app->m_log_enabled.Get(); 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) { void App::SetLogEnable(bool enable) {
if (App::GetLogEnable() != enable) { if (App::GetLogEnable() != enable) {
g_app->m_log_enabled.Set(enable); g_app->m_log_enabled.Set(enable);
@@ -1290,6 +1306,10 @@ App::App(const char* argv0) {
nxlinkInitialize(nxlink_callback); nxlinkInitialize(nxlink_callback);
} }
if (App::GetHddEnable()) {
usbHsFsInitialize(1);
}
curl::Init(); curl::Init();
// get current size of the framebuffer // get current size of the framebuffer
@@ -1422,9 +1442,6 @@ App::App(const char* argv0) {
// padInitializeDefault(&m_pad); // padInitializeDefault(&m_pad);
padInitializeAny(&m_pad); padInitializeAny(&m_pad);
// usbHsFsSetFileSystemMountFlags(UsbHsFsMountFlags_ReadOnly);
// usbHsFsSetPopulateCallback();
// usbHsFsInitialize(0);
const auto loader_info_size = envGetLoaderInfoSize(); const auto loader_info_size = envGetLoaderInfoSize();
if (loader_info_size) { if (loader_info_size) {
@@ -1804,6 +1821,11 @@ App::~App() {
nxlinkExit(); nxlinkExit();
} }
if (App::GetHddEnable()) {
log_write("closing hdd\n");
usbHsFsExit();
}
if (App::GetLogEnable()) { if (App::GetLogEnable()) {
log_write("closing log\n"); log_write("closing log\n");
log_file_exit(); log_file_exit();

418
sphaira/source/dumper.cpp Normal file
View File

@@ -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<const fs::FsPath> 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<const fs::FsPath> 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<const fs::FsPath> 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<const fs::FsPath> paths) {
std::vector<std::string> file_list;
for (const auto& path : paths) {
file_list.emplace_back(path);
}
// auto usb = std::make_unique<UsbTest>(pbox, entries);
auto usb = std::make_unique<UsbTest>(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<const fs::FsPath> 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<const fs::FsPath> 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<BaseSource> source, const std::vector<fs::FsPath>& paths, OnExit on_exit, u32 location_flags) {
ui::PopupList::Items items;
std::vector<DumpEntry> 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<ui::PopupList>(
"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<ui::ProgressBox>(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<BaseSource> source, const fs::FsPath& path, OnExit on_exit, DumpLocationType type) {
}
} // namespace sphaira::dump

View File

@@ -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); R_UNLESS(ignore_read_only || !is_read_only_root(_path), Fs::ResultReadOnly);
auto path_view = std::string_view{_path}; auto path_view = std::string_view{_path};
// todo: fix this for sdmc: and ums0:
FsPath path{"/"}; FsPath path{"/"};
for (const auto dir : std::views::split(path_view, '/')) { 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) { 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); 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 (fd == -1) {
if (errno == EEXIST) {
return FsError_PathAlreadyExists;
}
R_TRY(fsdevGetLastResult()); R_TRY(fsdevGetLastResult());
return Fs::ResultUnknownStdioError; 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) { Result CreateDirectory(const FsPath& path, bool ignore_read_only) {
R_UNLESS(ignore_read_only || !is_read_only_root(path), Fs::ResultReadOnly); 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()); R_TRY(fsdevGetLastResult());
return Fs::ResultUnknownStdioError; return Fs::ResultUnknownStdioError;
} }

View File

@@ -1,8 +1,10 @@
#include "location.hpp" #include "location.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "app.hpp"
#include <cstring> #include <cstring>
#include <minIni.h> #include <minIni.h>
#include <usbhsfs.h>
namespace sphaira::location { namespace sphaira::location {
namespace { namespace {
@@ -72,4 +74,34 @@ auto Load() -> Entries {
return out; 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 } // namespace sphaira::location

View File

@@ -1,11 +1,9 @@
#include "app.hpp" #include "app.hpp"
#include "log.hpp" #include "log.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "download.hpp" #include "dumper.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "location.hpp"
#include "threaded_file_transfer.hpp"
#include "ui/menus/game_menu.hpp" #include "ui/menus/game_menu.hpp"
#include "ui/sidebar.hpp" #include "ui/sidebar.hpp"
@@ -20,10 +18,6 @@
#include "yati/nx/es.hpp" #include "yati/nx/es.hpp"
#include "yati/container/base.hpp" #include "yati/container/base.hpp"
#include "yati/container/nsp.hpp" #include "yati/container/nsp.hpp"
#include "yati/source/stream.hpp"
#include "usb/usb_uploader.hpp"
#include "usb/tinfoil.hpp"
#include <utility> #include <utility>
#include <cstring> #include <cstring>
@@ -49,23 +43,6 @@ enum ContentFlag {
ContentFlag_All = ContentFlag_Application | ContentFlag_Patch | ContentFlag_AddOnContent | ContentFlag_DataPatch, 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 { struct NcmEntry {
const NcmStorageId storage_id; const NcmStorageId storage_id;
NcmContentStorage cs{}; NcmContentStorage cs{};
@@ -203,13 +180,8 @@ private:
} }
}; };
struct BaseSource { struct NspSource final : dump::BaseSource {
virtual ~BaseSource() = default; NspSource(const std::vector<NspEntry>& entries) : m_entries{entries} { }
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
};
struct NspSource final : BaseSource {
NspSource(std::span<NspEntry> entries) : m_entries{entries} { }
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override { 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){ 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); return it->Read(buf, off, size, bytes_read);
} }
auto GetEntries() const -> std::span<const NspEntry> {
return m_entries;
}
auto GetName(const std::string& path) const -> std::string { auto GetName(const std::string& path) const -> std::string {
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){ const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
return path == e.path; return path == e.path;
@@ -249,278 +217,9 @@ struct NspSource final : BaseSource {
} }
private: private:
std::span<NspEntry> m_entries{}; std::vector<NspEntry> m_entries{};
}; };
struct UsbTest final : usb::upload::Usb, yati::source::Stream {
UsbTest(ProgressBox* pbox, std::span<NspEntry> entries) : Usb{UINT64_MAX} {
m_source = std::make_unique<NspSource>(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<NspSource> 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<NspEntry> 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<NspSource>(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<const std::string> file_list, std::span<NspEntry> 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<const std::string> file_list, std::span<NspEntry> entries) {
while (!pbox->ShouldExit()) {
R_TRY(usb->PollCommands());
}
R_THROW(0xFFFF);
}
Result DumpNspToUsbS2S(ProgressBox* pbox, std::span<NspEntry> entries) {
std::vector<std::string> file_list;
for (auto& e : entries) {
file_list.emplace_back(e.path);
}
auto usb = std::make_unique<UsbTest>(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<NspEntry> entries) {
auto source = std::make_unique<NspSource>(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<NspEntry> entries) {
auto source = std::make_unique<NspSource>(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) { Result Notify(Result rc, const std::string& error_message) {
if (R_FAILED(rc)) { if (R_FAILED(rc)) {
App::Push(std::make_shared<ui::ErrorBox>(rc, App::Push(std::make_shared<ui::ErrorBox>(rc,
@@ -1400,57 +1099,22 @@ void Menu::DeleteGames() {
} }
void Menu::DumpGames(u32 flags) { void Menu::DumpGames(u32 flags) {
PopupList::Items items; auto targets = GetSelectedEntries();
const auto network_locations = location::Load();
for (const auto&p : network_locations) { std::vector<NspEntry> nsp_entries;
items.emplace_back(p.name); for (auto& e : targets) {
BuildNspEntries(e, flags, nsp_entries);
} }
for (const auto&p : DUMP_LOCATIONS) { std::vector<fs::FsPath> paths;
items.emplace_back(i18n::get(p.display_name)); for (auto& e : nsp_entries) {
paths.emplace_back(e.path);
} }
App::Push(std::make_shared<PopupList>( auto source = std::make_shared<NspSource>(nsp_entries);
"Select dump location"_i18n, items, [this, network_locations, flags](auto op_index){ dump::Dump(source, paths, [this](Result rc){
if (!op_index) { ClearSelection();
return; });
}
const auto index = *op_index;
App::Push(std::make_shared<ProgressBox>(0, "Dumping"_i18n, "", [this, network_locations, index, flags](auto pbox) -> Result {
auto targets = GetSelectedEntries();
std::vector<NspEntry> 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");
}
}));
}
));
} }
} // namespace sphaira::ui::menu::game } // namespace sphaira::ui::menu::game

View File

@@ -6,17 +6,13 @@
#include "yati/yati.hpp" #include "yati/yati.hpp"
#include "yati/nx/nca.hpp" #include "yati/nx/nca.hpp"
#include "yati/source/stream.hpp"
#include "usb/usb_uploader.hpp"
#include "usb/tinfoil.hpp"
#include "app.hpp" #include "app.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "log.hpp" #include "log.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "download.hpp" #include "download.hpp"
#include "location.hpp" #include "dumper.hpp"
#include "threaded_file_transfer.hpp"
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
@@ -46,23 +42,6 @@ enum DumpFileFlag {
DumpFileFlag_All = DumpFileFlag_XCI | DumpFileFlag_AllBin, 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[] = { const char *g_option_list[] = {
"Install", "Install",
"Dump", "Dump",
@@ -177,7 +156,7 @@ auto BuildGcPath(const char* name, const FsGameCardHandle* handle, FsGameCardPar
return path; return path;
} }
struct XciEntry { struct XciSource final : dump::BaseSource {
// application name. // application name.
std::string application_name{}; std::string application_name{};
// extra // extra
@@ -189,7 +168,7 @@ struct XciEntry {
s64 xci_size{}; s64 xci_size{};
Menu* menu{}; 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))) { if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) {
size = ClipSize(off, size, xci_size); size = ClipSize(off, size, xci_size);
*bytes_read = 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; 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))) { if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) {
return xci_size; return xci_size;
} else if (path.ends_with(GetDumpTypeStr(DumpFileType_Set))) { } 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 { struct HashStr {
char str[0x21]; char str[0x21];
}; };
@@ -315,201 +236,6 @@ HashStr hexIdToStr(auto id) {
return str; return str;
} }
Result DumpNspToFile(ProgressBox* pbox, std::span<const fs::FsPath> 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<const std::string> 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<const std::string> file_list, XciEntry& e) {
while (!pbox->ShouldExit()) {
R_TRY(usb->PollCommands());
}
R_THROW(0xFFFF);
}
Result DumpNspToUsbS2S(ProgressBox* pbox, std::span<const fs::FsPath> paths, XciEntry& e) {
std::vector<std::string> file_list;
for (auto& path : paths) {
file_list.emplace_back(path);
}
auto usb = std::make_unique<UsbTest>(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<const fs::FsPath> 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<const fs::FsPath> 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 // from Gamecard-Installer-NX
Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, FsGameCardStoragePartition partition) { Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, FsGameCardStoragePartition partition) {
const struct { const struct {
@@ -1208,92 +934,53 @@ void Menu::OnChangeIndex(s64 new_index) {
} }
} }
void Menu::DumpGames(u32 flags) { Result Menu::DumpGames(u32 flags) {
PopupList::Items items; R_TRY(GcMountStorage());
const auto network_locations = location::Load();
for (const auto&p : network_locations) { auto source = std::make_shared<XciSource>();
items.emplace_back(p.name); source->menu = this;
} source->application_name = m_entries[m_entry_index].lang_entry.name;
for (const auto&p : DUMP_LOCATIONS) { std::vector<fs::FsPath> paths;
items.emplace_back(i18n::get(p.display_name)); if (flags & DumpFileFlag_XCI) {
} if (App::GetApp()->m_dump_trim_xci.Get()) {
source->xci_size = m_storage_trimmed_size;
App::Push(std::make_shared<PopupList>( paths.emplace_back(BuildFullDumpPath(DumpFileType_TrimmedXCI, m_entries));
"Select dump location"_i18n, items, [this, network_locations, flags](auto op_index){ } else {
if (!op_index) { source->xci_size = m_storage_total_size;
return; paths.emplace_back(BuildFullDumpPath(DumpFileType_XCI, m_entries));
}
const auto index = *op_index;
App::Push(std::make_shared<ProgressBox>(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<fs::FsPath> 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();
}));
} }
)); }
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 } // namespace sphaira::ui::menu::gc

View File

@@ -278,6 +278,10 @@ MainMenu::MainMenu() {
App::SetNxlinkEnable(enable); App::SetNxlinkEnable(enable);
})); }));
options->Add(std::make_shared<SidebarEntryBool>("Hdd"_i18n, App::GetHddEnable(), [](bool& enable){
App::SetHddEnable(enable);
}));
if (m_update_state == UpdateState::Update) { if (m_update_state == UpdateState::Update) {
options->Add(std::make_shared<SidebarEntryCallback>("Download update: "_i18n + m_update_version, [this](){ options->Add(std::make_shared<SidebarEntryCallback>("Download update: "_i18n + m_update_version, [this](){
App::Push(std::make_shared<ProgressBox>(0, "Downloading "_i18n, "Sphaira v" + m_update_version, [this](auto pbox) -> Result { App::Push(std::make_shared<ProgressBox>(0, "Downloading "_i18n, "Sphaira v" + m_update_version, [this](auto pbox) -> Result {