diff --git a/sphaira/CMakeLists.txt b/sphaira/CMakeLists.txt index 8d5d001..359f22d 100644 --- a/sphaira/CMakeLists.txt +++ b/sphaira/CMakeLists.txt @@ -198,7 +198,7 @@ FetchContent_Declare(zstd FetchContent_Declare(libusbhsfs GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git - GIT_TAG db2bf2a + GIT_TAG d0a973e ) FetchContent_Declare(libnxtc diff --git a/sphaira/include/fs.hpp b/sphaira/include/fs.hpp index 727ddde..356e4ed 100644 --- a/sphaira/include/fs.hpp +++ b/sphaira/include/fs.hpp @@ -12,10 +12,14 @@ namespace fs { struct FsPath { FsPath() = default; - constexpr FsPath(const auto& str) { From(str); } + + constexpr FsPath(const FsPath& p) { From(p); } + constexpr FsPath(const char* str) { From(str); } + constexpr FsPath(const std::string& str) { From(str); } + constexpr FsPath(const std::string_view& str) { From(str); } constexpr void From(const FsPath& p) { - *this = p; + From(p.s); } constexpr void From(const char* str) { @@ -40,6 +44,10 @@ struct FsPath { return s; } + constexpr auto starts_with(std::string_view str) const -> bool { + return !strncasecmp(s, str.data(), str.length()); + } + constexpr auto empty() const { return s[0] == '\0'; } @@ -65,6 +73,11 @@ struct FsPath { constexpr char& operator[](std::size_t idx) { return s[idx]; } constexpr const char& operator[](std::size_t idx) const { return s[idx]; } + constexpr FsPath& operator=(const FsPath& p) noexcept { + From(p.s); + return *this; + } + constexpr FsPath operator+(const FsPath& v) const noexcept { FsPath r{*this}; return r += v; @@ -186,9 +199,6 @@ struct File { FsFile m_native{}; std::FILE* m_stdio{}; s64 m_stdio_off{}; - // sadly, fatfs doesn't support fstat, so we have to manually - // stat the file to get it's size. - FsPath m_path{}; }; struct Dir { diff --git a/sphaira/include/log.hpp b/sphaira/include/log.hpp index 68eb3d0..1c4da76 100644 --- a/sphaira/include/log.hpp +++ b/sphaira/include/log.hpp @@ -12,6 +12,8 @@ extern "C" { bool log_file_init(); bool log_nxlink_init(); void log_file_exit(); +bool log_is_init(); + void log_nxlink_exit(); void log_write(const char* s, ...) __attribute__ ((format (printf, 1, 2))); void log_write_arg(const char* s, va_list* v); diff --git a/sphaira/include/threaded_file_transfer.hpp b/sphaira/include/threaded_file_transfer.hpp index 99b505a..058ea26 100644 --- a/sphaira/include/threaded_file_transfer.hpp +++ b/sphaira/include/threaded_file_transfer.hpp @@ -40,17 +40,17 @@ Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCa // helper for extract zips. // this will multi-thread unzip if size >= 512KiB, otherwise it'll single pass. -Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, s64 size, u32 crc32 = 0); +Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, s64 size, u32 crc32 = 0, Mode mode = Mode::SingleThreadedIfSmaller); // same as above but for zipping files. -Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, u32* crc32 = nullptr); +Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, u32* crc32 = nullptr, Mode mode = Mode::SingleThreadedIfSmaller); // passes the name inside the zip an final output path. using UnzipAllFilter = std::function; // helper all-in-one unzip function that unzips a zip (either open or path provided). // the filter function can be used to modify the path and filter out unwanted files. -Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr); -Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr); +Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr, Mode mode = Mode::SingleThreadedIfSmaller); +Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr, Mode mode = Mode::SingleThreadedIfSmaller); } // namespace sphaira::thread diff --git a/sphaira/include/ui/progress_box.hpp b/sphaira/include/ui/progress_box.hpp index b6e3ee6..cb52f26 100644 --- a/sphaira/include/ui/progress_box.hpp +++ b/sphaira/include/ui/progress_box.hpp @@ -39,9 +39,9 @@ struct ProgressBox final : Widget { auto ShouldExitResult() -> Result; // helper functions - auto CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src, const fs::FsPath& dst) -> Result; - auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst) -> Result; - auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result; + auto CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result; + auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result; + auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result; void Yield(); auto GetCpuId() const { diff --git a/sphaira/source/app.cpp b/sphaira/source/app.cpp index 5fa65d4..45eb4ac 100644 --- a/sphaira/source/app.cpp +++ b/sphaira/source/app.cpp @@ -1292,21 +1292,6 @@ App::App(const char* argv0) { __nx_applet_exit_mode = 1; } - // get emummc config. - alignas(0x1000) AmsEmummcPaths paths{}; - SecmonArgs args{}; - args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */ - args.X[1] = 0; /* EXO_EMUMMC_MMC_NAND*/ - args.X[2] = (u64)&paths; /* out path */ - svcCallSecureMonitor(&args); - m_emummc_paths = paths; - - log_write("emummc : %u\n", App::IsEmummc()); - if (App::IsEmummc()) { - log_write("[emummc] file based path: %s\n", m_emummc_paths.file_based_path); - log_write("[emummc] nintendo path: %s\n", m_emummc_paths.nintendo); - } - fs::FsNativeSd fs; fs.CreateDirectoryRecursively("/config/sphaira"); fs.CreateDirectory("/config/sphaira/assoc"); @@ -1370,6 +1355,48 @@ App::App(const char* argv0) { App::Notify("Warning! Logs are enabled, Sphaira will run slowly!"_i18n); } + if (log_is_init()) { + SetSysFirmwareVersion fw_version{}; + setsysInitialize(); + ON_SCOPE_EXIT(setsysExit()); + setsysGetFirmwareVersion(&fw_version); + + log_write("[version] platform: %s\n", fw_version.platform); + log_write("[version] version_hash: %s\n", fw_version.version_hash); + log_write("[version] display_version: %s\n", fw_version.display_version); + log_write("[version] display_title: %s\n", fw_version.display_title); + + splInitialize(); + ON_SCOPE_EXIT(splExit()); + + u64 out{}; + splGetConfig((SplConfigItem)65000, &out); + log_write("[ams] version: %lu.%lu.%lu\n", (out >> 56) & 0xFF, (out >> 48) & 0xFF, (out >> 40) & 0xFF); + log_write("[ams] target version: %lu.%lu.%lu\n", (out >> 24) & 0xFF, (out >> 16) & 0xFF, (out >> 8) & 0xFF); + log_write("[ams] key gen: %lu\n", (out >> 32) & 0xFF); + + splGetConfig((SplConfigItem)65003, &out); + log_write("[ams] hash: %lx\n", out); + + splGetConfig((SplConfigItem)65010, &out); + log_write("[ams] usb 3.0 enabled: %lu\n", out); + } + + // get emummc config. + alignas(0x1000) AmsEmummcPaths paths{}; + SecmonArgs args{}; + args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */ + args.X[1] = 0; /* EXO_EMUMMC_MMC_NAND*/ + args.X[2] = (u64)&paths; /* out path */ + svcCallSecureMonitor(&args); + m_emummc_paths = paths; + + log_write("[emummc] enabled: %u\n", App::IsEmummc()); + if (App::IsEmummc()) { + log_write("[emummc] file based path: %s\n", m_emummc_paths.file_based_path); + log_write("[emummc] nintendo path: %s\n", m_emummc_paths.nintendo); + } + if (App::GetMtpEnable()) { haze::Init(); } diff --git a/sphaira/source/fs.cpp b/sphaira/source/fs.cpp index b27e4ad..fb4625c 100644 --- a/sphaira/source/fs.cpp +++ b/sphaira/source/fs.cpp @@ -500,7 +500,6 @@ Result OpenFile(fs::Fs* fs, const fs::FsPath& path, u32 mode, File* f) { } R_UNLESS(f->m_stdio, Result_FsUnknownStdioError); - std::strcpy(f->m_path, path); } R_SUCCEED(); @@ -580,17 +579,7 @@ Result File::GetSize(s64* out) { R_TRY(fsFileGetSize(&m_native, out)); } else { struct stat st; - const auto fd = fileno(m_stdio); - bool did_stat{}; - - if (fd && !fstat(fd, &st)) { - did_stat = true; - } - - if (!did_stat) { - R_UNLESS(!lstat(m_path, &st), Result_FsUnknownStdioError); - } - + R_UNLESS(!fstat(fileno(m_stdio), &st), Result_FsUnknownStdioError); *out = st.st_size; } diff --git a/sphaira/source/log.cpp b/sphaira/source/log.cpp index cb7823c..bae68e2 100644 --- a/sphaira/source/log.cpp +++ b/sphaira/source/log.cpp @@ -10,17 +10,27 @@ namespace { constexpr const char* logpath = "/config/sphaira/log.txt"; -std::FILE* file{}; int nxlink_socket{}; +bool g_file_open{}; std::mutex mutex{}; void log_write_arg_internal(const char* s, std::va_list* v) { - if (file) { - std::vfprintf(file, s, *v); - std::fflush(file); + const auto t = std::time(nullptr); + const auto tm = std::localtime(&t); + + static char buf[512]; + const auto len = std::snprintf(buf, sizeof(buf), "[%02u:%02u:%02u] -> ", tm->tm_hour, tm->tm_min, tm->tm_sec); + std::vsnprintf(buf + len, sizeof(buf) - len, s, *v); + + if (g_file_open) { + auto file = std::fopen(logpath, "a"); + if (file) { + std::fprintf(file, "%s", buf); + std::fclose(file); + } } if (nxlink_socket) { - std::vprintf(s, *v); + std::printf("%s", buf); } } @@ -30,12 +40,18 @@ extern "C" { auto log_file_init() -> bool { std::scoped_lock lock{mutex}; - if (file) { + if (g_file_open) { return false; } - file = std::fopen(logpath, "w"); - return file != nullptr; + auto file = std::fopen(logpath, "w"); + if (file) { + g_file_open = true; + std::fclose(file); + return true; + } + + return false; } auto log_nxlink_init() -> bool { @@ -50,9 +66,8 @@ auto log_nxlink_init() -> bool { void log_file_exit() { std::scoped_lock lock{mutex}; - if (file) { - std::fclose(file); - file = nullptr; + if (g_file_open) { + g_file_open = false; } } @@ -64,12 +79,17 @@ void log_nxlink_exit() { } } -void log_write(const char* s, ...) { +bool log_is_init() { std::scoped_lock lock{mutex}; - if (!file && !nxlink_socket) { + return g_file_open || nxlink_socket; +} + +void log_write(const char* s, ...) { + if (!log_is_init()) { return; } + std::scoped_lock lock{mutex}; std::va_list v{}; va_start(v, s); log_write_arg_internal(s, &v); @@ -77,11 +97,11 @@ void log_write(const char* s, ...) { } void log_write_arg(const char* s, va_list* v) { - std::scoped_lock lock{mutex}; - if (!file && !nxlink_socket) { + if (!log_is_init()) { return; } + std::scoped_lock lock{mutex}; log_write_arg_internal(s, v); } diff --git a/sphaira/source/threaded_file_transfer.cpp b/sphaira/source/threaded_file_transfer.cpp index 4e80f20..a7b0f25 100644 --- a/sphaira/source/threaded_file_transfer.cpp +++ b/sphaira/source/threaded_file_transfer.cpp @@ -416,21 +416,21 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, Wri } // namespace Result Transfer(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc, Mode mode) { - return TransferInternal(pbox, size, rfunc, wfunc, nullptr, Mode::MultiThreaded); + return TransferInternal(pbox, size, rfunc, wfunc, nullptr, mode); } Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback sfunc, Mode mode) { return TransferInternal(pbox, size, rfunc, nullptr, [sfunc](StartThreadCallback start, PullCallback pull) -> Result { R_TRY(start()); return sfunc(pull); - }, Mode::MultiThreaded); + }, mode); } Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback2 sfunc, Mode mode) { - return TransferInternal(pbox, size, rfunc, nullptr, sfunc, Mode::MultiThreaded); + return TransferInternal(pbox, size, rfunc, nullptr, sfunc, mode); } -Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, s64 size, u32 crc32) { +Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, s64 size, u32 crc32, Mode mode) { Result rc; if (R_FAILED(rc = fs->CreateDirectoryRecursivelyWithPath(path)) && rc != FsError_PathAlreadyExists) { log_write("failed to create folder: %s 0x%04X\n", path.s, rc); @@ -471,7 +471,7 @@ Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::F [&](const void* data, s64 off, s64 size) -> Result { return f.Write(off, data, size, FsWriteOption_None); }, - nullptr, Mode::SingleThreadedIfSmaller, SMALL_BUFFER_SIZE + nullptr, mode, SMALL_BUFFER_SIZE )); // validate crc32 (if set in the info). @@ -480,7 +480,7 @@ Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::F R_SUCCEED(); } -Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, u32* crc32) { +Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, u32* crc32, Mode mode) { fs::File f; R_TRY(fs->OpenFile(path, FsOpenMode_Read, &f)); @@ -506,11 +506,11 @@ Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsP } R_SUCCEED(); }, - nullptr, Mode::SingleThreadedIfSmaller, SMALL_BUFFER_SIZE + nullptr, mode, SMALL_BUFFER_SIZE ); } -Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter) { +Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter, Mode mode) { unz_global_info64 ginfo; if (UNZ_OK != unzGetGlobalInfo64(zfile, &ginfo)) { R_THROW(Result_UnzGetGlobalInfo64); @@ -560,14 +560,14 @@ Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs R_THROW(rc); } } else { - R_TRY(TransferUnzip(pbox, zfile, fs, path, info.uncompressed_size, info.crc)); + R_TRY(TransferUnzip(pbox, zfile, fs, path, info.uncompressed_size, info.crc, mode)); } } R_SUCCEED(); } -Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter) { +Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter, Mode mode) { zlib_filefunc64_def file_func; mz::FileFuncStdio(&file_func); @@ -575,7 +575,7 @@ Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs R_UNLESS(zfile, Result_UnzOpen2_64); ON_SCOPE_EXIT(unzClose(zfile)); - return TransferUnzipAll(pbox, zfile, fs, base_path, filter); + return TransferUnzipAll(pbox, zfile, fs, base_path, filter, mode); } } // namespace::thread diff --git a/sphaira/source/ui/menus/filebrowser.cpp b/sphaira/source/ui/menus/filebrowser.cpp index cf2a739..eedefd1 100644 --- a/sphaira/source/ui/menus/filebrowser.cpp +++ b/sphaira/source/ui/menus/filebrowser.cpp @@ -442,6 +442,8 @@ FsView::~FsView() { } void FsView::Update(Controller* controller, TouchInfo* touch) { + Widget::Update(controller, touch); + m_list->OnUpdate(controller, touch, m_index, m_entries_current.size(), [this](bool touch, auto i) { if (touch && m_index == i) { FireAction(Button::A); @@ -728,10 +730,12 @@ void FsView::UnzipFiles(fs::FsPath dir_path) { } App::Push(std::make_shared(0, "Extracting "_i18n, "", [this, dir_path, targets](auto pbox) -> Result { + const auto is_hdd_fs = m_fs->Root().starts_with("ums"); + for (auto& e : targets) { pbox->SetTitle(e.GetName()); const auto zip_out = GetNewPath(e); - R_TRY(thread::TransferUnzipAll(pbox, zip_out, m_fs.get(), dir_path)); + R_TRY(thread::TransferUnzipAll(pbox, zip_out, m_fs.get(), dir_path, nullptr, is_hdd_fs ? thread::Mode::SingleThreaded : thread::Mode::SingleThreadedIfSmaller)); } R_SUCCEED(); @@ -786,6 +790,7 @@ void FsView::ZipFiles(fs::FsPath zip_out) { App::Push(std::make_shared(0, "Compressing "_i18n, "", [this, zip_out, targets](auto pbox) -> Result { const auto t = std::time(NULL); const auto tm = std::localtime(&t); + const auto is_hdd_fs = m_fs->Root().starts_with("ums"); // pre-calculate the time rather than calculate it in the loop. zip_fileinfo zip_info{}; @@ -825,7 +830,7 @@ void FsView::ZipFiles(fs::FsPath zip_out) { } ON_SCOPE_EXIT(zipCloseFileInZip(zfile)); - return thread::TransferZip(pbox, zfile, m_fs.get(), file_path); + return thread::TransferZip(pbox, zfile, m_fs.get(), file_path, nullptr, is_hdd_fs ? thread::Mode::SingleThreaded : thread::Mode::SingleThreadedIfSmaller); }; for (auto& e : targets) { @@ -1207,6 +1212,7 @@ void FsView::OnPasteCallback() { App::Push(std::make_shared(0, "Pasting"_i18n, "", [this](auto pbox) -> Result { auto& selected = m_menu->m_selected; auto src_fs = selected.m_view->GetFs(); + const auto is_same_fs = selected.SameFs(this); if (selected.SameFs(this) && selected.m_type == SelectedType::Cut) { for (const auto& p : selected.m_files) { @@ -1271,7 +1277,7 @@ void FsView::OnPasteCallback() { } else { pbox->SetTitle(p.name); pbox->NewTransfer("Copying "_i18n + src_path); - R_TRY(pbox->CopyFile(src_fs, m_fs.get(), src_path, dst_path)); + R_TRY(pbox->CopyFile(src_fs, m_fs.get(), src_path, dst_path, is_same_fs)); R_TRY(on_paste_file(src_path, dst_path)); } } @@ -1301,7 +1307,7 @@ void FsView::OnPasteCallback() { pbox->SetTitle(p.name); pbox->NewTransfer("Copying "_i18n + src_path); - R_TRY(pbox->CopyFile(src_fs, m_fs.get(), src_path, dst_path)); + R_TRY(pbox->CopyFile(src_fs, m_fs.get(), src_path, dst_path, is_same_fs)); R_TRY(on_paste_file(src_path, dst_path)); } } @@ -1880,9 +1886,9 @@ void Menu::Update(Controller* controller, TouchInfo* touch) { // workaround the buttons not being display properly. // basically, inherit all actions from the view, draw them, // then restore state after. - const auto actions_copy = GetActions(); - ON_SCOPE_EXIT(m_actions = actions_copy); - m_actions.insert_range(view->GetActions()); + // const auto actions_copy = GetActions(); + // ON_SCOPE_EXIT(m_actions = actions_copy); + // m_actions.insert_range(view->GetActions()); MenuBase::Update(controller, touch); view->Update(controller, touch); diff --git a/sphaira/source/ui/progress_box.cpp b/sphaira/source/ui/progress_box.cpp index 0c0487b..7ef96d9 100644 --- a/sphaira/source/ui/progress_box.cpp +++ b/sphaira/source/ui/progress_box.cpp @@ -263,7 +263,7 @@ auto ProgressBox::ShouldExitResult() -> Result { R_SUCCEED(); } -auto ProgressBox::CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result { +auto ProgressBox::CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src_path, const fs::FsPath& dst_path, bool single_threaded) -> Result { const auto is_file_based_emummc = App::IsFileBaseEmummc(); const auto is_both_native = fs_src->IsNative() && fs_dst->IsNative(); @@ -300,20 +300,20 @@ auto ProgressBox::CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src } return rc; - } + }, single_threaded ? thread::Mode::SingleThreaded : thread::Mode::MultiThreaded )); R_SUCCEED(); } -auto ProgressBox::CopyFile(fs::Fs* fs, const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result { - return CopyFile(fs, fs, src_path, dst_path); +auto ProgressBox::CopyFile(fs::Fs* fs, const fs::FsPath& src_path, const fs::FsPath& dst_path, bool single_threaded) -> Result { + return CopyFile(fs, fs, src_path, dst_path, single_threaded); } -auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result { +auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path, bool single_threaded) -> Result { fs::FsNativeSd fs; R_TRY(fs.GetFsOpenResult()); - return CopyFile(&fs, src_path, dst_path); + return CopyFile(&fs, src_path, dst_path, single_threaded); } void ProgressBox::Yield() {