add hdd dump support, cleanup dump code into a single generic file.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
45
sphaira/include/dumper.hpp
Normal file
45
sphaira/include/dumper.hpp
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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{};
|
||||||
|
|||||||
@@ -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
418
sphaira/source/dumper.cpp
Normal 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
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user