add hdd dump support, cleanup dump code into a single generic file.
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
#include <ctime>
|
||||
#include <span>
|
||||
#include <dirent.h>
|
||||
#include <usbhsfs.h>
|
||||
|
||||
extern "C" {
|
||||
u32 __nx_applet_exit_mode = 0;
|
||||
@@ -606,6 +607,10 @@ auto App::GetNxlinkEnable() -> bool {
|
||||
return g_app->m_nxlink_enabled.Get();
|
||||
}
|
||||
|
||||
auto App::GetHddEnable() -> bool {
|
||||
return g_app->m_hdd_enabled.Get();
|
||||
}
|
||||
|
||||
auto App::GetLogEnable() -> bool {
|
||||
return g_app->m_log_enabled.Get();
|
||||
}
|
||||
@@ -673,6 +678,17 @@ void App::SetNxlinkEnable(bool enable) {
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetHddEnable(bool enable) {
|
||||
if (App::GetHddEnable() != enable) {
|
||||
g_app->m_hdd_enabled.Set(enable);
|
||||
if (enable) {
|
||||
usbHsFsInitialize(1);
|
||||
} else {
|
||||
usbHsFsExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetLogEnable(bool enable) {
|
||||
if (App::GetLogEnable() != enable) {
|
||||
g_app->m_log_enabled.Set(enable);
|
||||
@@ -1290,6 +1306,10 @@ App::App(const char* argv0) {
|
||||
nxlinkInitialize(nxlink_callback);
|
||||
}
|
||||
|
||||
if (App::GetHddEnable()) {
|
||||
usbHsFsInitialize(1);
|
||||
}
|
||||
|
||||
curl::Init();
|
||||
|
||||
// get current size of the framebuffer
|
||||
@@ -1422,9 +1442,6 @@ App::App(const char* argv0) {
|
||||
// padInitializeDefault(&m_pad);
|
||||
padInitializeAny(&m_pad);
|
||||
|
||||
// usbHsFsSetFileSystemMountFlags(UsbHsFsMountFlags_ReadOnly);
|
||||
// usbHsFsSetPopulateCallback();
|
||||
// usbHsFsInitialize(0);
|
||||
|
||||
const auto loader_info_size = envGetLoaderInfoSize();
|
||||
if (loader_info_size) {
|
||||
@@ -1804,6 +1821,11 @@ App::~App() {
|
||||
nxlinkExit();
|
||||
}
|
||||
|
||||
if (App::GetHddEnable()) {
|
||||
log_write("closing hdd\n");
|
||||
usbHsFsExit();
|
||||
}
|
||||
|
||||
if (App::GetLogEnable()) {
|
||||
log_write("closing log\n");
|
||||
log_file_exit();
|
||||
|
||||
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);
|
||||
|
||||
auto path_view = std::string_view{_path};
|
||||
// todo: fix this for sdmc: and ums0:
|
||||
FsPath path{"/"};
|
||||
|
||||
for (const auto dir : std::views::split(path_view, '/')) {
|
||||
@@ -276,8 +277,12 @@ Result copy_entire_file(FsFileSystem* fs, const FsPath& dst, const FsPath& src,
|
||||
Result CreateFile(const FsPath& path, u64 size, u32 option, bool ignore_read_only) {
|
||||
R_UNLESS(ignore_read_only || !is_read_only_root(path), Fs::ResultReadOnly);
|
||||
|
||||
auto fd = open(path, O_CREAT | S_IRUSR | S_IWUSR);
|
||||
auto fd = open(path, O_WRONLY | O_CREAT, DEFFILEMODE);
|
||||
if (fd == -1) {
|
||||
if (errno == EEXIST) {
|
||||
return FsError_PathAlreadyExists;
|
||||
}
|
||||
|
||||
R_TRY(fsdevGetLastResult());
|
||||
return Fs::ResultUnknownStdioError;
|
||||
}
|
||||
@@ -288,7 +293,11 @@ Result CreateFile(const FsPath& path, u64 size, u32 option, bool ignore_read_onl
|
||||
Result CreateDirectory(const FsPath& path, bool ignore_read_only) {
|
||||
R_UNLESS(ignore_read_only || !is_read_only_root(path), Fs::ResultReadOnly);
|
||||
|
||||
if (mkdir(path, 0777)) {
|
||||
if (mkdir(path, ACCESSPERMS)) {
|
||||
if (errno == EEXIST) {
|
||||
return FsError_PathAlreadyExists;
|
||||
}
|
||||
|
||||
R_TRY(fsdevGetLastResult());
|
||||
return Fs::ResultUnknownStdioError;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "location.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "app.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <minIni.h>
|
||||
#include <usbhsfs.h>
|
||||
|
||||
namespace sphaira::location {
|
||||
namespace {
|
||||
@@ -72,4 +74,34 @@ auto Load() -> Entries {
|
||||
return out;
|
||||
}
|
||||
|
||||
auto GetStdio(bool write) -> StdioEntries {
|
||||
if (!App::GetHddEnable()) {
|
||||
log_write("[USBHSFS] not enabled\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
static UsbHsFsDevice devices[0x20];
|
||||
const auto count = usbHsFsListMountedDevices(devices, std::size(devices));
|
||||
log_write("[USBHSFS] got connected: %u\n", usbHsFsGetPhysicalDeviceCount());
|
||||
log_write("[USBHSFS] got count: %u\n", count);
|
||||
|
||||
StdioEntries out{};
|
||||
for (s32 i = 0; i < count; i++) {
|
||||
const auto& e = devices[i];
|
||||
|
||||
if (write && e.write_protect) {
|
||||
log_write("[USBHSFS] skipping write protect\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
char display_name[0x100];
|
||||
std::snprintf(display_name, sizeof(display_name), "%s (%s - %s - %zu GB)", e.name, LIBUSBHSFS_FS_TYPE_STR(e.fs_type), e.product_name, e.capacity / 1024 / 1024 / 1024);
|
||||
|
||||
out.emplace_back(e.name, display_name, e.write_protect);
|
||||
log_write("\t[USBHSFS] %s name: %s serial: %s man: %s\n", e.name, e.product_name, e.serial_number, e.manufacturer);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace sphaira::location
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#include "app.hpp"
|
||||
#include "log.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "download.hpp"
|
||||
#include "dumper.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "location.hpp"
|
||||
#include "threaded_file_transfer.hpp"
|
||||
|
||||
#include "ui/menus/game_menu.hpp"
|
||||
#include "ui/sidebar.hpp"
|
||||
@@ -20,10 +18,6 @@
|
||||
#include "yati/nx/es.hpp"
|
||||
#include "yati/container/base.hpp"
|
||||
#include "yati/container/nsp.hpp"
|
||||
#include "yati/source/stream.hpp"
|
||||
|
||||
#include "usb/usb_uploader.hpp"
|
||||
#include "usb/tinfoil.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
@@ -49,23 +43,6 @@ enum ContentFlag {
|
||||
ContentFlag_All = ContentFlag_Application | ContentFlag_Patch | ContentFlag_AddOnContent | ContentFlag_DataPatch,
|
||||
};
|
||||
|
||||
enum DumpLocationType {
|
||||
DumpLocationType_SdCard,
|
||||
DumpLocationType_UsbS2S,
|
||||
DumpLocationType_DevNull,
|
||||
};
|
||||
|
||||
struct DumpLocation {
|
||||
const DumpLocationType type;
|
||||
const char* display_name;
|
||||
};
|
||||
|
||||
constexpr DumpLocation DUMP_LOCATIONS[]{
|
||||
{ DumpLocationType_SdCard, "microSD card (/dumps/NSP/)" },
|
||||
{ DumpLocationType_UsbS2S, "USB transfer (Switch 2 Switch)" },
|
||||
{ DumpLocationType_DevNull, "/dev/null (Speed Test)" },
|
||||
};
|
||||
|
||||
struct NcmEntry {
|
||||
const NcmStorageId storage_id;
|
||||
NcmContentStorage cs{};
|
||||
@@ -203,13 +180,8 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
struct BaseSource {
|
||||
virtual ~BaseSource() = default;
|
||||
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
};
|
||||
|
||||
struct NspSource final : BaseSource {
|
||||
NspSource(std::span<NspEntry> entries) : m_entries{entries} { }
|
||||
struct NspSource final : dump::BaseSource {
|
||||
NspSource(const std::vector<NspEntry>& entries) : m_entries{entries} { }
|
||||
|
||||
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
|
||||
@@ -220,10 +192,6 @@ struct NspSource final : BaseSource {
|
||||
return it->Read(buf, off, size, bytes_read);
|
||||
}
|
||||
|
||||
auto GetEntries() const -> std::span<const NspEntry> {
|
||||
return m_entries;
|
||||
}
|
||||
|
||||
auto GetName(const std::string& path) const -> std::string {
|
||||
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
|
||||
return path == e.path;
|
||||
@@ -249,278 +217,9 @@ struct NspSource final : BaseSource {
|
||||
}
|
||||
|
||||
private:
|
||||
std::span<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) {
|
||||
if (R_FAILED(rc)) {
|
||||
App::Push(std::make_shared<ui::ErrorBox>(rc,
|
||||
@@ -1400,57 +1099,22 @@ void Menu::DeleteGames() {
|
||||
}
|
||||
|
||||
void Menu::DumpGames(u32 flags) {
|
||||
PopupList::Items items;
|
||||
const auto network_locations = location::Load();
|
||||
auto targets = GetSelectedEntries();
|
||||
|
||||
for (const auto&p : network_locations) {
|
||||
items.emplace_back(p.name);
|
||||
std::vector<NspEntry> nsp_entries;
|
||||
for (auto& e : targets) {
|
||||
BuildNspEntries(e, flags, nsp_entries);
|
||||
}
|
||||
|
||||
for (const auto&p : DUMP_LOCATIONS) {
|
||||
items.emplace_back(i18n::get(p.display_name));
|
||||
std::vector<fs::FsPath> paths;
|
||||
for (auto& e : nsp_entries) {
|
||||
paths.emplace_back(e.path);
|
||||
}
|
||||
|
||||
App::Push(std::make_shared<PopupList>(
|
||||
"Select dump location"_i18n, items, [this, network_locations, flags](auto op_index){
|
||||
if (!op_index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = *op_index;
|
||||
App::Push(std::make_shared<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");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
));
|
||||
auto source = std::make_shared<NspSource>(nsp_entries);
|
||||
dump::Dump(source, paths, [this](Result rc){
|
||||
ClearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::game
|
||||
|
||||
@@ -6,17 +6,13 @@
|
||||
|
||||
#include "yati/yati.hpp"
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "yati/source/stream.hpp"
|
||||
#include "usb/usb_uploader.hpp"
|
||||
#include "usb/tinfoil.hpp"
|
||||
|
||||
#include "app.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "download.hpp"
|
||||
#include "location.hpp"
|
||||
#include "threaded_file_transfer.hpp"
|
||||
#include "dumper.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
@@ -46,23 +42,6 @@ enum DumpFileFlag {
|
||||
DumpFileFlag_All = DumpFileFlag_XCI | DumpFileFlag_AllBin,
|
||||
};
|
||||
|
||||
enum DumpLocationType {
|
||||
DumpLocationType_SdCard,
|
||||
DumpLocationType_UsbS2S,
|
||||
DumpLocationType_DevNull,
|
||||
};
|
||||
|
||||
struct DumpLocation {
|
||||
const DumpLocationType type;
|
||||
const char* display_name;
|
||||
};
|
||||
|
||||
constexpr DumpLocation DUMP_LOCATIONS[]{
|
||||
{ DumpLocationType_SdCard, "microSD card (/dumps/XCI/)" },
|
||||
{ DumpLocationType_UsbS2S, "USB transfer (Switch 2 Switch)" },
|
||||
{ DumpLocationType_DevNull, "/dev/null (Speed Test)" },
|
||||
};
|
||||
|
||||
const char *g_option_list[] = {
|
||||
"Install",
|
||||
"Dump",
|
||||
@@ -177,7 +156,7 @@ auto BuildGcPath(const char* name, const FsGameCardHandle* handle, FsGameCardPar
|
||||
return path;
|
||||
}
|
||||
|
||||
struct XciEntry {
|
||||
struct XciSource final : dump::BaseSource {
|
||||
// application name.
|
||||
std::string application_name{};
|
||||
// extra
|
||||
@@ -189,7 +168,7 @@ struct XciEntry {
|
||||
s64 xci_size{};
|
||||
Menu* menu{};
|
||||
|
||||
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||
if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) {
|
||||
size = ClipSize(off, size, xci_size);
|
||||
*bytes_read = size;
|
||||
@@ -216,11 +195,11 @@ struct XciEntry {
|
||||
}
|
||||
}
|
||||
|
||||
auto GetName(const std::string& path) const -> std::string {
|
||||
auto GetName(const std::string& path) const -> std::string override {
|
||||
return application_name;
|
||||
}
|
||||
|
||||
auto GetSize(const std::string& path) const -> s64 {
|
||||
auto GetSize(const std::string& path) const -> s64 override {
|
||||
if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI))) {
|
||||
return xci_size;
|
||||
} else if (path.ends_with(GetDumpTypeStr(DumpFileType_Set))) {
|
||||
@@ -245,64 +224,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
struct UsbTest final : usb::upload::Usb, yati::source::Stream {
|
||||
UsbTest(ProgressBox* pbox, XciEntry& entry) : Usb{UINT64_MAX}, m_entry{entry} {
|
||||
m_pbox = pbox;
|
||||
}
|
||||
|
||||
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override {
|
||||
R_TRY(m_pull(buf, size, bytes_read));
|
||||
m_pull_offset += *bytes_read;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||
if (m_pull) {
|
||||
return Stream::Read(buf, off, size, bytes_read);
|
||||
} else {
|
||||
return ReadInternal(path, buf, off, size, bytes_read);
|
||||
}
|
||||
}
|
||||
|
||||
Result ReadInternal(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||
if (m_path != path) {
|
||||
m_path = path;
|
||||
m_progress = 0;
|
||||
m_pull_offset = 0;
|
||||
Stream::Reset();
|
||||
m_size = m_entry.GetSize(path);
|
||||
m_pbox->SetTitle(m_entry.GetName(path));
|
||||
m_pbox->NewTransfer(m_path);
|
||||
}
|
||||
|
||||
R_TRY(m_entry.Read(path, buf, off, size, bytes_read));
|
||||
|
||||
m_offset += *bytes_read;
|
||||
m_progress += *bytes_read;
|
||||
m_pbox->UpdateTransfer(m_progress, m_size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void SetPullCallback(thread::PullCallback pull) {
|
||||
m_pull = pull;
|
||||
}
|
||||
|
||||
auto GetPullOffset() const {
|
||||
return m_pull_offset;
|
||||
}
|
||||
|
||||
private:
|
||||
XciEntry& m_entry;
|
||||
ProgressBox* m_pbox{};
|
||||
std::string m_path{};
|
||||
thread::PullCallback m_pull{};
|
||||
s64 m_offset{};
|
||||
s64 m_size{};
|
||||
s64 m_progress{};
|
||||
s64 m_pull_offset{};
|
||||
};
|
||||
|
||||
struct HashStr {
|
||||
char str[0x21];
|
||||
};
|
||||
@@ -315,201 +236,6 @@ HashStr hexIdToStr(auto id) {
|
||||
return str;
|
||||
}
|
||||
|
||||
Result DumpNspToFile(ProgressBox* pbox, std::span<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
|
||||
Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, FsGameCardStoragePartition partition) {
|
||||
const struct {
|
||||
@@ -1208,92 +934,53 @@ void Menu::OnChangeIndex(s64 new_index) {
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::DumpGames(u32 flags) {
|
||||
PopupList::Items items;
|
||||
const auto network_locations = location::Load();
|
||||
Result Menu::DumpGames(u32 flags) {
|
||||
R_TRY(GcMountStorage());
|
||||
|
||||
for (const auto&p : network_locations) {
|
||||
items.emplace_back(p.name);
|
||||
}
|
||||
auto source = std::make_shared<XciSource>();
|
||||
source->menu = this;
|
||||
source->application_name = m_entries[m_entry_index].lang_entry.name;
|
||||
|
||||
for (const auto&p : DUMP_LOCATIONS) {
|
||||
items.emplace_back(i18n::get(p.display_name));
|
||||
}
|
||||
|
||||
App::Push(std::make_shared<PopupList>(
|
||||
"Select dump location"_i18n, items, [this, network_locations, flags](auto op_index){
|
||||
if (!op_index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = *op_index;
|
||||
App::Push(std::make_shared<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();
|
||||
}));
|
||||
std::vector<fs::FsPath> paths;
|
||||
if (flags & DumpFileFlag_XCI) {
|
||||
if (App::GetApp()->m_dump_trim_xci.Get()) {
|
||||
source->xci_size = m_storage_trimmed_size;
|
||||
paths.emplace_back(BuildFullDumpPath(DumpFileType_TrimmedXCI, m_entries));
|
||||
} else {
|
||||
source->xci_size = m_storage_total_size;
|
||||
paths.emplace_back(BuildFullDumpPath(DumpFileType_XCI, m_entries));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
if (flags & DumpFileFlag_Set) {
|
||||
source->id_set.resize(0xC);
|
||||
R_TRY(fsDeviceOperatorGetGameCardIdSet(&m_dev_op, source->id_set.data(), source->id_set.size(), source->id_set.size()));
|
||||
paths.emplace_back(BuildFullDumpPath(DumpFileType_Set, m_entries));
|
||||
}
|
||||
|
||||
// todo:
|
||||
if (flags & DumpFileFlag_UID) {
|
||||
// paths.emplace_back(BuildFullDumpPath(DumpFileType_UID, m_entries));
|
||||
}
|
||||
|
||||
if (flags & DumpFileFlag_Cert) {
|
||||
s64 size;
|
||||
source->cert.resize(0x200);
|
||||
R_TRY(fsDeviceOperatorGetGameCardDeviceCertificate(&m_dev_op, &m_handle, source->cert.data(), source->cert.size(), &size, source->cert.size()));
|
||||
paths.emplace_back(BuildFullDumpPath(DumpFileType_Cert, m_entries));
|
||||
}
|
||||
|
||||
// todo:
|
||||
if (flags & DumpFileFlag_Initial) {
|
||||
// paths.emplace_back(BuildFullDumpPath(DumpFileType_Initial, m_entries));
|
||||
}
|
||||
|
||||
dump::Dump(source, paths, [this](Result rc){
|
||||
GcUmountStorage();
|
||||
GcUnmount();
|
||||
});
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::gc
|
||||
|
||||
@@ -278,6 +278,10 @@ MainMenu::MainMenu() {
|
||||
App::SetNxlinkEnable(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Hdd"_i18n, App::GetHddEnable(), [](bool& enable){
|
||||
App::SetHddEnable(enable);
|
||||
}));
|
||||
|
||||
if (m_update_state == UpdateState::Update) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Download update: "_i18n + m_update_version, [this](){
|
||||
App::Push(std::make_shared<ProgressBox>(0, "Downloading "_i18n, "Sphaira v" + m_update_version, [this](auto pbox) -> Result {
|
||||
|
||||
Reference in New Issue
Block a user