devoptab: use fixed size array of entries rather than vector as vector can change/break pointers when it reallocs. fs: disable loading assoc when mounting custom fs.

This commit is contained in:
ITotalJustice
2025-08-12 08:04:24 +01:00
parent 7835ebc346
commit cd6fed6aae
6 changed files with 173 additions and 140 deletions

View File

@@ -5,6 +5,9 @@
namespace sphaira::devoptab::common { namespace sphaira::devoptab::common {
// max entries per devoptab, should be enough.
enum { MAX_ENTRIES = 4 };
// buffers data in 512k chunks to maximise throughput. // buffers data in 512k chunks to maximise throughput.
// not suitable if random access >= 512k is common. // not suitable if random access >= 512k is common.
// if that is needed, see the LRU cache varient used for fatfs. // if that is needed, see the LRU cache varient used for fatfs.

View File

@@ -2363,7 +2363,8 @@ void MountFsHelper(const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& name) {
.flags = filebrowser::FsEntryFlag_ReadOnly, .flags = filebrowser::FsEntryFlag_ReadOnly,
}; };
App::Push<filebrowser::Menu>(fs, fs_entry, fs->Root()); const auto options = FsOption_All &~ FsOption_LoadAssoc;
App::Push<filebrowser::Menu>(fs, fs_entry, fs->Root(), options);
} }
} // namespace sphaira::ui::menu::filebrowser } // namespace sphaira::ui::menu::filebrowser

View File

@@ -9,8 +9,9 @@
#include "yati/source/file.hpp" #include "yati/source/file.hpp"
#include <cstring> #include <cstring>
#include <cstdio>
#include <cerrno> #include <cerrno>
#include <array>
#include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h> #include <sys/iosupport.h>
@@ -164,8 +165,6 @@ int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) { int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
auto device = (Device*)r->deviceData; auto device = (Device*)r->deviceData;
log_write("[\t\tDEV] lstat\n");
char path[FS_MAX_PATH]; char path[FS_MAX_PATH];
if (!common::fix_path(_path, path)) { if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT); return set_errno(r, ENOENT);
@@ -210,17 +209,20 @@ constexpr devoptab_t DEVOPTAB = {
}; };
struct Entry { struct Entry {
Device device; Device device{};
devoptab_t devoptab; devoptab_t devoptab{};
fs::FsPath path; fs::FsPath path{};
fs::FsPath mount; fs::FsPath mount{};
char name[32]; char name[32]{};
s32 ref_count; s32 ref_count{};
~Entry() {
RemoveDevice(mount);
}
}; };
Mutex g_mutex; Mutex g_mutex;
std::vector<Entry> g_entries; std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
u32 g_mount_idx;
} // namespace } // namespace
@@ -229,13 +231,20 @@ Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
// check if we already have the save mounted. // check if we already have the save mounted.
for (auto& e : g_entries) { for (auto& e : g_entries) {
if (e.path == path) { if (e && e->path == path) {
e.ref_count++; e->ref_count++;
out_path = e.mount; out_path = e->mount;
R_SUCCEED(); R_SUCCEED();
} }
} }
// otherwise, find next free entry.
auto itr = std::ranges::find_if(g_entries, [](auto& e){
return !e;
});
R_UNLESS(itr != g_entries.end(), 0x1);
const auto index = std::distance(g_entries.begin(), itr);
auto source = std::make_unique<yati::source::File>(fs, path); auto source = std::make_unique<yati::source::File>(fs, path);
s64 size; s64 size;
@@ -246,22 +255,22 @@ Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
yati::container::Collections collections; yati::container::Collections collections;
R_TRY(nsp.GetCollections(collections)); R_TRY(nsp.GetCollections(collections));
auto& entry = g_entries.emplace_back(); auto entry = std::make_unique<Entry>();
entry.path = path; entry->path = path;
entry.devoptab = DEVOPTAB; entry->devoptab = DEVOPTAB;
entry.devoptab.name = entry.name; entry->devoptab.name = entry->name;
entry.devoptab.deviceData = &entry.device; entry->devoptab.deviceData = &entry->device;
entry.device.source = std::move(buffered); entry->device.source = std::move(buffered);
entry.device.collections = collections; entry->device.collections = collections;
std::snprintf(entry.name, sizeof(entry.name), "nsp_%u", g_mount_idx); std::snprintf(entry->name, sizeof(entry->name), "nsp_%zu", index);
std::snprintf(entry.mount, sizeof(entry.mount), "nsp_%u:/", g_mount_idx); std::snprintf(entry->mount, sizeof(entry->mount), "nsp_%zu:/", index);
R_UNLESS(AddDevice(&entry.devoptab) >= 0, 0x1); R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[NSP] DEVICE SUCCESS %s %s\n", path.s, entry.name); log_write("[NSP] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry.mount; out_path = entry->mount;
entry.ref_count++; entry->ref_count++;
g_mount_idx++; *itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
@@ -270,20 +279,19 @@ void UmountNsp(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex); SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){ auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return mount == e.mount; return e && e->mount == mount;
}); });
if (itr == g_entries.end()) { if (itr == g_entries.end()) {
return; return;
} }
if (itr->ref_count) { if ((*itr)->ref_count) {
itr->ref_count--; (*itr)->ref_count--;
} }
if (!itr->ref_count) { if (!(*itr)->ref_count) {
RemoveDevice(mount); itr->reset();
g_entries.erase(itr);
} }
} }

View File

@@ -8,8 +8,9 @@
#include "yati/nx/nxdumptool/core/save.h" #include "yati/nx/nxdumptool/core/save.h"
#include <cstring> #include <cstring>
#include <cstdio>
#include <cerrno> #include <cerrno>
#include <array>
#include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h> #include <sys/iosupport.h>
@@ -243,19 +244,21 @@ constexpr devoptab_t DEVOPTAB = {
}; };
struct Entry { struct Entry {
u64 id; Device device{};
Device device; devoptab_t devoptab{};
devoptab_t devoptab; u64 id{};
char name[32]; fs::FsPath mount{};
s32 ref_count; char name[32]{};
s32 ref_count{};
~Entry() {
RemoveDevice(mount);
save_close_savefile(&device.ctx);
}
}; };
Mutex g_mutex; Mutex g_mutex;
std::vector<Entry> g_entries; std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
void MakeMountPath(u64 id, fs::FsPath& out_path) {
std::snprintf(out_path, sizeof(out_path), "%016lx:/", id);
}
} // namespace } // namespace
@@ -264,13 +267,19 @@ Result MountFromSavePath(u64 id, fs::FsPath& out_path) {
// check if we already have the save mounted. // check if we already have the save mounted.
for (auto& e : g_entries) { for (auto& e : g_entries) {
if (e.id == id) { if (e && e->id == id) {
e.ref_count++; e->ref_count++;
MakeMountPath(id, out_path); out_path = e->mount;
R_SUCCEED(); R_SUCCEED();
} }
} }
// otherwise, find next free entry.
auto itr = std::ranges::find_if(g_entries, [](auto& e){
return !e;
});
R_UNLESS(itr != g_entries.end(), 0x1);
char path[256]; char path[256];
std::snprintf(path, sizeof(path), "SYSTEM:/save/%016lx", id); std::snprintf(path, sizeof(path), "SYSTEM:/save/%016lx", id);
@@ -281,21 +290,23 @@ Result MountFromSavePath(u64 id, fs::FsPath& out_path) {
log_write("[SAVE] OPEN SUCCESS %s\n", path); log_write("[SAVE] OPEN SUCCESS %s\n", path);
auto& entry = g_entries.emplace_back(); auto entry = std::make_unique<Entry>();
entry.id = id; entry->id = id;
entry.device.ctx = ctx; entry->device.ctx = ctx;
entry.device.file_table = &ctx->save_filesystem_core.file_table; entry->device.file_table = &ctx->save_filesystem_core.file_table;
entry.devoptab = DEVOPTAB; entry->devoptab = DEVOPTAB;
entry.devoptab.name = entry.name; entry->devoptab.name = entry->name;
entry.devoptab.deviceData = &entry.device; entry->devoptab.deviceData = &entry->device;
std::snprintf(entry.name, sizeof(entry.name), "%016lx", id); std::snprintf(entry->name, sizeof(entry->name), "%016lx", id);
std::snprintf(entry->mount, sizeof(entry->mount), "%016lx:/", id);
R_UNLESS(AddDevice(&entry.devoptab) >= 0, 0x1); R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[SAVE] DEVICE SUCCESS %s %s\n", path, entry.name); log_write("[SAVE] DEVICE SUCCESS %s %s\n", path, entry->name);
MakeMountPath(id, out_path); out_path = entry->mount;
entry->ref_count++;
*itr = std::move(entry);
entry.ref_count++;
R_SUCCEED(); R_SUCCEED();
} }
@@ -303,29 +314,19 @@ void UnmountSave(u64 id) {
SCOPED_MUTEX(&g_mutex); SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [id](auto& e){ auto itr = std::ranges::find_if(g_entries, [id](auto& e){
return id == e.id; return e && e->id == id;
}); });
if (itr == g_entries.end()) { if (itr == g_entries.end()) {
return; return;
} }
if (itr->ref_count) { if ((*itr)->ref_count) {
itr->ref_count--; (*itr)->ref_count--;
} }
if (!itr->ref_count) { if (!(*itr)->ref_count) {
fs::FsPath path; itr->reset();
MakeMountPath(id, path);
// todo: verify this actually works.
RemoveDevice(path);
if (itr->device.ctx) {
save_close_savefile(&itr->device.ctx);
}
g_entries.erase(itr);
} }
} }

View File

@@ -8,8 +8,9 @@
#include "yati/source/file.hpp" #include "yati/source/file.hpp"
#include <cstring> #include <cstring>
#include <cstdio>
#include <cerrno> #include <cerrno>
#include <array>
#include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h> #include <sys/iosupport.h>
@@ -230,17 +231,20 @@ constexpr devoptab_t DEVOPTAB = {
}; };
struct Entry { struct Entry {
Device device; Device device{};
devoptab_t devoptab; devoptab_t devoptab{};
fs::FsPath path; fs::FsPath path{};
fs::FsPath mount; fs::FsPath mount{};
char name[32]; char name[32]{};
s32 ref_count; s32 ref_count{};
~Entry() {
RemoveDevice(mount);
}
}; };
Mutex g_mutex; Mutex g_mutex;
std::vector<Entry> g_entries; std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
u32 g_mount_idx;
} // namespace } // namespace
@@ -249,13 +253,20 @@ Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
// check if we already have the save mounted. // check if we already have the save mounted.
for (auto& e : g_entries) { for (auto& e : g_entries) {
if (e.path == path) { if (e && e->path == path) {
e.ref_count++; e->ref_count++;
out_path = e.mount; out_path = e->mount;
R_SUCCEED(); R_SUCCEED();
} }
} }
// otherwise, find next free entry.
auto itr = std::ranges::find_if(g_entries, [](auto& e){
return !e;
});
R_UNLESS(itr != g_entries.end(), 0x1);
const auto index = std::distance(g_entries.begin(), itr);
auto source = std::make_unique<yati::source::File>(fs, path); auto source = std::make_unique<yati::source::File>(fs, path);
s64 size; s64 size;
@@ -266,22 +277,22 @@ Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
yati::container::Xci::Partitions partitions; yati::container::Xci::Partitions partitions;
R_TRY(xci.GetPartitions(partitions)); R_TRY(xci.GetPartitions(partitions));
auto& entry = g_entries.emplace_back(); auto entry = std::make_unique<Entry>();
entry.path = path; entry->path = path;
entry.devoptab = DEVOPTAB; entry->devoptab = DEVOPTAB;
entry.devoptab.name = entry.name; entry->devoptab.name = entry->name;
entry.devoptab.deviceData = &entry.device; entry->devoptab.deviceData = &entry->device;
entry.device.source = std::move(buffered); entry->device.source = std::move(buffered);
entry.device.partitions = partitions; entry->device.partitions = partitions;
std::snprintf(entry.name, sizeof(entry.name), "xci_%u", g_mount_idx); std::snprintf(entry->name, sizeof(entry->name), "xci_%zu", index);
std::snprintf(entry.mount, sizeof(entry.mount), "xci_%u:/", g_mount_idx); std::snprintf(entry->mount, sizeof(entry->mount), "xci_%zu:/", index);
R_UNLESS(AddDevice(&entry.devoptab) >= 0, 0x1); R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[XCI] DEVICE SUCCESS %s %s\n", path.s, entry.name); log_write("[XCI] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry.mount; out_path = entry->mount;
entry.ref_count++; entry->ref_count++;
g_mount_idx++; *itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
@@ -290,20 +301,19 @@ void UmountXci(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex); SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){ auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return mount == e.mount; return e && e->mount == mount;
}); });
if (itr == g_entries.end()) { if (itr == g_entries.end()) {
return; return;
} }
if (itr->ref_count) { if ((*itr)->ref_count) {
itr->ref_count--; (*itr)->ref_count--;
} }
if (!itr->ref_count) { if (!(*itr)->ref_count) {
RemoveDevice(mount); itr->reset();
g_entries.erase(itr);
} }
} }

View File

@@ -6,8 +6,9 @@
#include "yati/source/file.hpp" #include "yati/source/file.hpp"
#include <cstring> #include <cstring>
#include <cstdio>
#include <cerrno> #include <cerrno>
#include <array>
#include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h> #include <sys/iosupport.h>
#include <zlib.h> #include <zlib.h>
@@ -600,17 +601,20 @@ Result ParseZip(common::BufferedData* source, s64 size, FileTableEntries& out) {
} }
struct Entry { struct Entry {
Device device; Device device{};
devoptab_t devoptab; devoptab_t devoptab{};
fs::FsPath path; fs::FsPath path{};
fs::FsPath mount; fs::FsPath mount{};
char name[32]; char name[32]{};
s32 ref_count; s32 ref_count{};
~Entry() {
RemoveDevice(mount);
}
}; };
Mutex g_mutex; Mutex g_mutex;
std::vector<Entry> g_entries; std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
u32 g_mount_idx;
} // namespace } // namespace
@@ -619,13 +623,20 @@ Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
// check if we already have the save mounted. // check if we already have the save mounted.
for (auto& e : g_entries) { for (auto& e : g_entries) {
if (e.path == path) { if (e && e->path == path) {
e.ref_count++; e->ref_count++;
out_path = e.mount; out_path = e->mount;
R_SUCCEED(); R_SUCCEED();
} }
} }
// otherwise, find next free entry.
auto itr = std::ranges::find_if(g_entries, [](auto& e){
return !e;
});
R_UNLESS(itr != g_entries.end(), 0x1);
const auto index = std::distance(g_entries.begin(), itr);
auto source = std::make_unique<yati::source::File>(fs, path); auto source = std::make_unique<yati::source::File>(fs, path);
s64 size; s64 size;
@@ -640,22 +651,22 @@ Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
DirectoryEntry root; DirectoryEntry root;
Parse(table_entries, root); Parse(table_entries, root);
auto& entry = g_entries.emplace_back(); auto entry = std::make_unique<Entry>();
entry.path = path; entry->path = path;
entry.devoptab = DEVOPTAB; entry->devoptab = DEVOPTAB;
entry.devoptab.name = entry.name; entry->devoptab.name = entry->name;
entry.devoptab.deviceData = &entry.device; entry->devoptab.deviceData = &entry->device;
entry.device.source = std::move(buffered); entry->device.source = std::move(buffered);
entry.device.root = root; entry->device.root = root;
std::snprintf(entry.name, sizeof(entry.name), "zip_%u", g_mount_idx); std::snprintf(entry->name, sizeof(entry->name), "zip_%zu", index);
std::snprintf(entry.mount, sizeof(entry.mount), "zip_%u:/", g_mount_idx); std::snprintf(entry->mount, sizeof(entry->mount), "zip_%zu:/", index);
R_UNLESS(AddDevice(&entry.devoptab) >= 0, 0x1); R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[ZIP] DEVICE SUCCESS %s %s\n", path.s, entry.name); log_write("[ZIP] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry.mount; out_path = entry->mount;
entry.ref_count++; entry->ref_count++;
g_mount_idx++; *itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
@@ -664,20 +675,19 @@ void UmountZip(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex); SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){ auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return mount == e.mount; return e && e->mount == mount;
}); });
if (itr == g_entries.end()) { if (itr == g_entries.end()) {
return; return;
} }
if (itr->ref_count) { if ((*itr)->ref_count) {
itr->ref_count--; (*itr)->ref_count--;
} }
if (!itr->ref_count) { if (!(*itr)->ref_count) {
RemoveDevice(mount); itr->reset();
g_entries.erase(itr);
} }
} }