disable hdd->hdd threading. only open log write on write to allow for reads. log fw/ams version.

hdd->hdd threading is disabled due to a bug in libusbhsfs which causes a deadlock if 2 fs calls happen at the same time.
This commit is contained in:
ITotalJustice
2025-06-17 00:57:46 +01:00
parent 6b099de63c
commit 99c1db3655
11 changed files with 133 additions and 79 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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<bool(const fs::FsPath& name, fs::FsPath& path)>;
// 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

View File

@@ -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 {

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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<ui::ProgressBox>(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<ui::ProgressBox>(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<ProgressBox>(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);

View File

@@ -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() {