devoptab: refactor all custom mounts to inherit from helper struct.

This commit is contained in:
ITotalJustice
2025-09-08 01:34:20 +01:00
parent 61b398a89a
commit 384e8794bf
16 changed files with 628 additions and 1318 deletions

View File

@@ -9,29 +9,15 @@
namespace sphaira::devoptab { namespace sphaira::devoptab {
// mounts to "lower_case_hex_id:/"
Result MountSaveSystem(u64 id, fs::FsPath& out_path); Result MountSaveSystem(u64 id, fs::FsPath& out_path);
void UnmountSave(u64 id);
Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path); Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
void UmountZip(const fs::FsPath& mount);
Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path); Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
void UmountNsp(const fs::FsPath& mount);
Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path); Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
Result MountXciSource(const std::shared_ptr<sphaira::yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path); Result MountXciSource(const std::shared_ptr<sphaira::yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path);
void UmountXci(const fs::FsPath& mount);
Result MountNca(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path); Result MountNca(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
Result MountNcaNcm(NcmContentStorage* cs, const NcmContentId* id, fs::FsPath& out_path); Result MountNcaNcm(NcmContentStorage* cs, const NcmContentId* id, fs::FsPath& out_path);
void UmountNca(const fs::FsPath& mount);
Result MountBfsar(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path); Result MountBfsar(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
void UmountBfsar(const fs::FsPath& mount);
Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path); Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
void UmountNro(const fs::FsPath& mount);
Result MountVfsAll(); Result MountVfsAll();
Result MountWebdavAll(); Result MountWebdavAll();
@@ -43,5 +29,6 @@ Result MountFatfsAll();
Result GetNetworkDevices(location::StdioEntries& out); Result GetNetworkDevices(location::StdioEntries& out);
void UmountAllNeworkDevices(); void UmountAllNeworkDevices();
void UmountNeworkDevice(const fs::FsPath& mount);
} // namespace sphaira::devoptab } // namespace sphaira::devoptab

View File

@@ -185,7 +185,6 @@ struct MountDevice {
struct MountCurlDevice : MountDevice { struct MountCurlDevice : MountDevice {
using MountDevice::MountDevice; using MountDevice::MountDevice;
// MountCurlDevice(const MountConfig& _config);
virtual ~MountCurlDevice(); virtual ~MountCurlDevice();
PushThreadData* CreatePushData(CURL* curl, const std::string& url, size_t offset); PushThreadData* CreatePushData(CURL* curl, const std::string& url, size_t offset);
@@ -219,4 +218,6 @@ Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file
// same as above but takes in the device and expects the mount name to be set. // same as above but takes in the device and expects the mount name to be set.
bool MountNetworkDevice2(std::unique_ptr<MountDevice>&& device, const MountConfig& config, size_t file_size, size_t dir_size, const char* name, const char* mount_name); bool MountNetworkDevice2(std::unique_ptr<MountDevice>&& device, const MountConfig& config, size_t file_size, size_t dir_size, const char* name, const char* mount_name);
bool MountReadOnlyIndexDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name, fs::FsPath& out_path);
} // namespace sphaira::devoptab::common } // namespace sphaira::devoptab::common

View File

@@ -711,15 +711,15 @@ void FsView::OnClick() {
} }
}); });
} else if (IsExtension(entry.GetExtension(), NCA_EXTENSIONS)) { } else if (IsExtension(entry.GetExtension(), NCA_EXTENSIONS)) {
MountFileFs(devoptab::MountNca, devoptab::UmountNca); MountFileFs(devoptab::MountNca, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), NSP_EXTENSIONS)) { } else if (IsExtension(entry.GetExtension(), NSP_EXTENSIONS)) {
MountFileFs(devoptab::MountNsp, devoptab::UmountNsp); MountFileFs(devoptab::MountNsp, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), XCI_EXTENSIONS)) { } else if (IsExtension(entry.GetExtension(), XCI_EXTENSIONS)) {
MountFileFs(devoptab::MountXci, devoptab::UmountXci); MountFileFs(devoptab::MountXci, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), "zip")) { } else if (IsExtension(entry.GetExtension(), "zip")) {
MountFileFs(devoptab::MountZip, devoptab::UmountZip); MountFileFs(devoptab::MountZip, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), "bfsar")) { } else if (IsExtension(entry.GetExtension(), "bfsar")) {
MountFileFs(devoptab::MountBfsar, devoptab::UmountBfsar); MountFileFs(devoptab::MountBfsar, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), MUSIC_EXTENSIONS)) { } else if (IsExtension(entry.GetExtension(), MUSIC_EXTENSIONS)) {
App::Push<music::Menu>(GetFs(), GetNewPathCurrent()); App::Push<music::Menu>(GetFs(), GetNewPathCurrent());
} else if (IsExtension(entry.GetExtension(), IMAGE_EXTENSIONS)) { } else if (IsExtension(entry.GetExtension(), IMAGE_EXTENSIONS)) {

View File

@@ -413,7 +413,7 @@ Result Menu::MountNcaFs() {
R_TRY(devoptab::MountNcaNcm(m_meta.cs, &e.content_id, root)); R_TRY(devoptab::MountNcaNcm(m_meta.cs, &e.content_id, root));
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){ auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){
devoptab::UmountNca(root); devoptab::UmountNeworkDevice(root);
}); });
filebrowser::MountFsHelper(fs, utils::hexIdToStr(e.content_id).str); filebrowser::MountFsHelper(fs, utils::hexIdToStr(e.content_id).str);

View File

@@ -1322,7 +1322,7 @@ Result Menu::MountGcFs() {
R_TRY(devoptab::MountXciSource(source, m_storage_trimmed_size, e.lang_entry.name, root)); R_TRY(devoptab::MountXciSource(source, m_storage_trimmed_size, e.lang_entry.name, root));
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){ auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){
devoptab::UmountXci(root); devoptab::UmountNeworkDevice(root);
}); });
filebrowser::MountFsHelper(fs, e.lang_entry.name); filebrowser::MountFsHelper(fs, e.lang_entry.name);

View File

@@ -527,7 +527,7 @@ Result Menu::MountNroFs() {
R_TRY(devoptab::MountNro(App::GetApp()->m_fs.get(), e.path, root)); R_TRY(devoptab::MountNro(App::GetApp()->m_fs.get(), e.path, root));
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){ auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){
devoptab::UmountNro(root); devoptab::UmountNeworkDevice(root);
}); });
filebrowser::MountFsHelper(fs, root); filebrowser::MountFsHelper(fs, root);

View File

@@ -1131,8 +1131,8 @@ Result Menu::MountSaveFs() {
fs::FsPath root; fs::FsPath root;
R_TRY(devoptab::MountSaveSystem(e.system_save_data_id, root)); R_TRY(devoptab::MountSaveSystem(e.system_save_data_id, root));
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [&e](){ auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){
devoptab::UnmountSave(e.system_save_data_id); devoptab::UmountNeworkDevice(root);
}); });
filebrowser::MountFsHelper(fs, e.GetName()); filebrowser::MountFsHelper(fs, e.GetName());

View File

@@ -4,8 +4,6 @@
#include "defines.hpp" #include "defines.hpp"
#include "log.hpp" #include "log.hpp"
#include "yati/container/nsp.hpp"
#include "yati/container/xci.hpp"
#include "yati/source/file.hpp" #include "yati/source/file.hpp"
#include <pulsar.h> #include <pulsar.h>
@@ -15,24 +13,16 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab { namespace sphaira::devoptab {
namespace { namespace {
struct Device {
PLSR_BFSAR bfsar;
std::FILE* file; // points to archive file.
};
struct File { struct File {
Device* device;
PLSR_BFWARFileInfo info; PLSR_BFWARFileInfo info;
size_t off; size_t off;
}; };
struct Dir { struct Dir {
Device* device;
u32 index; u32 index;
}; };
@@ -82,56 +72,69 @@ PLSR_RC GetFileInfo(const PLSR_BFSAR *bfsar, std::string_view path, PLSR_BFWARFi
} }
int set_errno(struct _reent *r, int err) { struct Device final : common::MountDevice {
r->_errno = err; Device(const PLSR_BFSAR& _bfsar, const common::MountConfig& _config)
return -1; : MountDevice{_config}
} , bfsar{_bfsar} {
this->file = this->bfsar.ar.handle->f;
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
auto device = (Device*)r->deviceData;
auto file = static_cast<File*>(fileStruct);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
} }
~Device() {
plsrBFSARClose(&bfsar);
}
private:
bool Mount() override { return true; }
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
private:
PLSR_BFSAR bfsar;
std::FILE* file; // points to archive file.
};
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct);
PLSR_BFWARFileInfo info; PLSR_BFWARFileInfo info;
if (R_FAILED(GetFileInfo(&device->bfsar, path, info))) { if (R_FAILED(GetFileInfo(&this->bfsar, path, info))) {
return set_errno(r, ENOENT); return -ENOENT;
} }
file->device = device;
file->info = info; file->info = info;
return r->_errno = 0; return 0;
} }
int devoptab_close(struct _reent *r, void *fd) { int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
std::memset(file, 0, sizeof(*file)); std::memset(file, 0, sizeof(*file));
return r->_errno = 0; return 0;
} }
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) { ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& info = file->info; const auto& info = file->info;
// const auto real_len = len;
// plsr seems to read oob, so allow for some tollerance. // plsr seems to read oob, so allow for some tollerance.
const auto oob_allowed = 64; const auto oob_allowed = 64;
len = std::min(len, info.size + oob_allowed - file->off); len = std::min(len, info.size + oob_allowed - file->off);
std::fseek(file->device->file, file->info.offset + file->off, SEEK_SET); std::fseek(this->file, file->info.offset + file->off, SEEK_SET);
const auto bytes_read = std::fread(ptr, 1, len, file->device->file); const auto bytes_read = std::fread(ptr, 1, len, this->file);
// log_write("bytes read: %zu len: %zu real_len: %zu off: %zu size: %u\n", bytes_read, len, real_len, file->off, info.size);
file->off += bytes_read; file->off += bytes_read;
return bytes_read; return bytes_read;
} }
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) { off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& info = file->info; const auto& info = file->info;
@@ -141,11 +144,10 @@ off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
pos = info.size; pos = info.size;
} }
r->_errno = 0;
return file->off = std::clamp<u64>(pos, 0, info.size); return file->off = std::clamp<u64>(pos, 0, info.size);
} }
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) { int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& info = file->info; const auto& info = file->info;
@@ -153,51 +155,36 @@ int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
st->st_nlink = 1; st->st_nlink = 1;
st->st_size = info.size; st->st_size = info.size;
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
return r->_errno = 0; return 0;
} }
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) { int Device::devoptab_diropen(void* fd, const char *path) {
auto device = (Device*)r->deviceData;
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(dir, 0, sizeof(*dir));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
set_errno(r, ENOENT);
return NULL;
}
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
dir->device = device; return 0;
} else {
set_errno(r, ENOENT);
return NULL;
} }
r->_errno = 0; return -ENOENT;
return dirState;
} }
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
dir->index = 0; dir->index = 0;
return r->_errno = 0; return 0;
} }
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(filestat, 0, sizeof(*filestat));
do { do {
if (dir->index >= plsrBFSARSoundCount(&dir->device->bfsar)) { if (dir->index >= plsrBFSARSoundCount(&this->bfsar)) {
log_write("finished getting call entries: %u vs %u\n", dir->index, plsrBFSARSoundCount(&dir->device->bfsar)); log_write("finished getting call entries: %u vs %u\n", dir->index, plsrBFSARSoundCount(&this->bfsar));
return set_errno(r, ENOENT); return -ENOENT;
} }
PLSR_BFSARSoundInfo info{}; PLSR_BFSARSoundInfo info{};
if (R_FAILED(plsrBFSARSoundGet(&dir->device->bfsar, dir->index, &info))) { if (R_FAILED(plsrBFSARSoundGet(&this->bfsar, dir->index, &info))) {
continue; continue;
} }
@@ -206,7 +193,7 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
continue; continue;
} }
if (R_FAILED(plsrBFSARStringGet(&dir->device->bfsar, info.stringIndex, filename, NAME_MAX))) { if (R_FAILED(plsrBFSARStringGet(&this->bfsar, info.stringIndex, filename, NAME_MAX))) {
continue; continue;
} }
@@ -230,139 +217,52 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
break; break;
} while (dir->index++); } while (dir->index++);
return r->_errno = 0; return 0;
} }
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir)); std::memset(dir, 0, sizeof(*dir));
log_write("[BFSAR] devoptab_dirclose\n"); return 0;
return r->_errno = 0;
} }
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
auto device = (Device*)r->deviceData; st->st_nlink = 1;
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
}
std::memset(st, 0, sizeof(*st));
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
PLSR_BFWARFileInfo info{}; PLSR_BFWARFileInfo info{};
if (R_FAILED(GetFileInfo(&device->bfsar, path, info))) { if (R_FAILED(GetFileInfo(&this->bfsar, path, info))) {
return set_errno(r, ENOENT); return -ENOENT;
} }
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
st->st_size = info.size; st->st_size = info.size;
} }
st->st_nlink = 1; return 0;
return r->_errno = 0;
} }
constexpr devoptab_t DEVOPTAB = {
.structSize = sizeof(File),
.open_r = devoptab_open,
.close_r = devoptab_close,
.read_r = devoptab_read,
.seek_r = devoptab_seek,
.fstat_r = devoptab_fstat,
.stat_r = devoptab_lstat,
.dirStateSize = sizeof(Dir),
.diropen_r = devoptab_diropen,
.dirreset_r = devoptab_dirreset,
.dirnext_r = devoptab_dirnext,
.dirclose_r = devoptab_dirclose,
.lstat_r = devoptab_lstat,
};
struct Entry {
Device device{};
devoptab_t devoptab{};
fs::FsPath path{};
fs::FsPath mount{};
char name[32]{};
s32 ref_count{};
~Entry() {
log_write("[BFSAR] entry called\n");
RemoveDevice(mount);
plsrBFSARClose(&device.bfsar);
}
};
Mutex g_mutex;
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
} // namespace } // namespace
Result MountBfsar(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) { Result MountBfsar(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex); PLSR_BFSAR bfsar{};
PLSR_RC_TRY(plsrBFSAROpen(path, &bfsar));
// check if we already have the save mounted. if (!common::MountReadOnlyIndexDevice(
for (auto& e : g_entries) { [&bfsar](const common::MountConfig& config) {
if (e && e->path == path) { return std::make_unique<Device>(bfsar, config);
e->ref_count++; },
out_path = e->mount; sizeof(File), sizeof(Dir),
R_SUCCEED(); "BFSAR", out_path
} )) {
log_write("[BFSAR] Failed to mount %s\n", path.s);
R_THROW(0x1);
} }
// 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 entry = std::make_unique<Entry>();
entry->path = path;
entry->devoptab = DEVOPTAB;
entry->devoptab.name = entry->name;
entry->devoptab.deviceData = &entry->device;
std::snprintf(entry->name, sizeof(entry->name), "BFSAR_%zu", index);
std::snprintf(entry->mount, sizeof(entry->mount), "BFSAR_%zu:/", index);
PLSR_RC_TRY(plsrBFSAROpen(path, &entry->device.bfsar));
entry->device.file = entry->device.bfsar.ar.handle->f;
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[BFSAR] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry->mount;
entry->ref_count++;
*itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
void UmountBfsar(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return e && e->mount == mount;
});
if (itr == g_entries.end()) {
return;
}
if ((*itr)->ref_count) {
(*itr)->ref_count--;
}
if (!(*itr)->ref_count) {
itr->reset();
}
}
} // namespace sphaira::devoptab } // namespace sphaira::devoptab

View File

@@ -820,6 +820,39 @@ bool MountNetworkDevice2(std::unique_ptr<MountDevice>&& device, const MountConfi
return true; return true;
} }
bool MountReadOnlyIndexDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name, fs::FsPath& out_path) {
static Mutex mutex{};
static u32 next_index{};
SCOPED_MUTEX(&mutex);
MountConfig config{};
config.read_only = true;
config.no_stat_dir = false;
config.no_stat_file = false;
config.fs_hidden = true;
config.dump_hidden = true;
const auto index = next_index;
next_index = (next_index + 1) % 30;
fs::FsPath _name{};
std::snprintf(_name, sizeof(_name), "%s_%u", name, index);
fs::FsPath _mount{};
std::snprintf(_mount, sizeof(_mount), "%s_%u:/", name, index);
if (!common::MountNetworkDevice2(
create_device(config),
config, file_size, dir_size,
_name, _mount
)) {
return false;
}
out_path = _mount;
return true;
}
Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name, bool force_read_only) { Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name, bool force_read_only) {
{ {
static Mutex rw_lock_init_mutex{}; static Mutex rw_lock_init_mutex{};
@@ -1447,4 +1480,19 @@ void UmountAllNeworkDevices() {
} }
} }
void UmountNeworkDevice(const fs::FsPath& mount) {
SCOPED_RWLOCK(&g_rwlock, true);
auto it = std::ranges::find_if(g_entries, [&](const auto& e){
return e && e->mount == mount;
});
if (it != g_entries.end()) {
log_write("[DEVOPTAB] Unmounting %s\n", (*it)->device.config.url.c_str());
it->reset();
} else {
log_write("[DEVOPTAB] No such mount %s\n", mount.s);
}
}
} // sphaira::devoptab } // sphaira::devoptab

View File

@@ -428,7 +428,6 @@ Result MountFatfsAll() {
const auto& bis = BIS_MOUNT_ENTRIES[i]; const auto& bis = BIS_MOUNT_ENTRIES[i];
common::MountConfig config{}; common::MountConfig config{};
config.url = "fatfs dummy url";
config.read_only = true; config.read_only = true;
config.dump_hidden = true; config.dump_hidden = true;
@@ -457,10 +456,7 @@ const char* VolumeStr[] {
}; };
Result fatfs_read(u8 num, void* dst, u64 offset, u64 size) { Result fatfs_read(u8 num, void* dst, u64 offset, u64 size) {
// log_write("[FAT] num: %u\n", num);
log_write("[FAT] read: %s, off: 0x%lx, size: 0x%lx\n", VolumeStr[num], offset, size);
auto& fat = sphaira::devoptab::g_fat_storage[num]; auto& fat = sphaira::devoptab::g_fat_storage[num];
log_write("[FAT] buffered: %p\n", fat.buffered.get());
return fat.buffered->Read2(dst, offset, size); return fat.buffered->Read2(dst, offset, size);
} }

View File

@@ -20,7 +20,6 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab { namespace sphaira::devoptab {
namespace { namespace {
@@ -74,25 +73,18 @@ struct DirEntry {
const yati::container::Collections* pfs0; const yati::container::Collections* pfs0;
}; };
struct Device {
std::vector<NamedCollection> collections;
std::unique_ptr<yati::source::Base> source;
};
struct File { struct File {
Device* device;
FileEntry entry; FileEntry entry;
size_t off; size_t off;
}; };
struct Dir { struct Dir {
Device* device;
DirEntry entry; DirEntry entry;
u32 index; u32 index;
bool is_root; bool is_root;
}; };
bool find_file(std::span<NamedCollection> named, std::string_view path, FileEntry& out) { bool find_file(std::span<const NamedCollection> named, std::string_view path, FileEntry& out) {
for (auto& e : named) { for (auto& e : named) {
if (path.starts_with("/" + e.name)) { if (path.starts_with("/" + e.name)) {
out.fs_type = e.fs_type; out.fs_type = e.fs_type;
@@ -154,55 +146,68 @@ bool find_dir(std::span<const NamedCollection> named, std::string_view path, Dir
return false; return false;
} }
int set_errno(struct _reent *r, int err) { struct Device final : common::MountDevice {
r->_errno = err; Device(std::unique_ptr<yati::source::Base>&& _source, const std::vector<NamedCollection>& _collections, const common::MountConfig& _config)
return -1; : MountDevice{_config}
} , source{std::forward<decltype(_source)>(_source)}
, collections{_collections} {
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) { }
auto device = (Device*)r->deviceData;
private:
bool Mount() override { return true; }
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
private:
std::unique_ptr<yati::source::Base> source;
const std::vector<NamedCollection> collections;
};
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct); auto file = static_cast<File*>(fileStruct);
std::memset(file, 0, sizeof(*file)); std::memset(file, 0, sizeof(*file));
char path[PATH_MAX]; FileEntry entry{};
if (!common::fix_path(_path, path)) { if (!find_file(this->collections, path, entry)) {
return set_errno(r, ENOENT); log_write("[NCAFS] failed to find file entry: %s\n", path);
return -ENOENT;
} }
FileEntry entry;
if (!find_file(device->collections, path, entry)) {
log_write("[NCAFS] failed to find file entry\n");
return set_errno(r, ENOENT);
}
file->device = device;
file->entry = entry; file->entry = entry;
return 0;
return r->_errno = 0;
} }
int devoptab_close(struct _reent *r, void *fd) { int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
std::memset(file, 0, sizeof(*file)); std::memset(file, 0, sizeof(*file));
return r->_errno = 0; return 0;
} }
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) { ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& entry = file->entry; const auto& entry = file->entry;
u64 bytes_read; u64 bytes_read;
len = std::min(len, entry.size - file->off); len = std::min(len, entry.size - file->off);
if (R_FAILED(file->device->source->Read(ptr, entry.offset + file->off, len, &bytes_read))) { if (R_FAILED(this->source->Read(ptr, entry.offset + file->off, len, &bytes_read))) {
return set_errno(r, ENOENT); return -EIO;
} }
file->off += bytes_read; file->off += bytes_read;
return bytes_read; return bytes_read;
} }
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) { off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& entry = file->entry; const auto& entry = file->entry;
@@ -212,54 +217,39 @@ off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
pos = entry.size; pos = entry.size;
} }
r->_errno = 0;
return file->off = std::clamp<u64>(pos, 0, entry.size); return file->off = std::clamp<u64>(pos, 0, entry.size);
} }
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) { int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& entry = file->entry; const auto& entry = file->entry;
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
st->st_size = entry.size; st->st_size = entry.size;
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
return r->_errno = 0; return 0;
} }
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) { int Device::devoptab_diropen(void* fd, const char *path) {
auto device = (Device*)r->deviceData; auto dir = static_cast<Dir*>(fd);
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(dir, 0, sizeof(*dir)); std::memset(dir, 0, sizeof(*dir));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
set_errno(r, ENOENT);
return NULL;
}
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
dir->device = device;
dir->is_root = true; dir->is_root = true;
r->_errno = 0; return 0;
return dirState;
} else { } else {
DirEntry entry; DirEntry entry{};
if (!find_dir(device->collections, path, entry)) { if (!find_dir(this->collections, path, entry)) {
set_errno(r, ENOENT); return -ENOENT;
return NULL;
} }
dir->device = device;
dir->entry = entry; dir->entry = entry;
return 0;
r->_errno = 0;
return dirState;
} }
} }
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
auto& entry = dir->entry; auto& entry = dir->entry;
if (dir->is_root) { if (dir->is_root) {
@@ -272,30 +262,29 @@ int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
} }
} }
return r->_errno = 0; return 0;
} }
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
auto& entry = dir->entry; auto& entry = dir->entry;
std::memset(filestat, 0, sizeof(*filestat));
if (dir->is_root) { if (dir->is_root) {
if (dir->index >= dir->device->collections.size()) { if (dir->index >= this->collections.size()) {
return set_errno(r, ENOENT); return -ENOENT;
} }
filestat->st_nlink = 1; filestat->st_nlink = 1;
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
std::strcpy(filename, dir->device->collections[dir->index].name.c_str()); std::strcpy(filename, this->collections[dir->index].name.c_str());
} else { } else {
if (entry.fs_type == nca::FileSystemType_RomFS) { if (entry.fs_type == nca::FileSystemType_RomFS) {
if (!romfs::dirnext(entry.romfs, filename, filestat)) { if (!romfs::dirnext(entry.romfs, filename, filestat)) {
return set_errno(r, ENOENT); return -ENOENT;
} }
} else { } else {
if (dir->index >= entry.pfs0->size()) { if (dir->index >= entry.pfs0->size()) {
return set_errno(r, ENOENT); return -ENOENT;
} }
const auto& collection = (*entry.pfs0)[dir->index]; const auto& collection = (*entry.pfs0)[dir->index];
@@ -307,109 +296,49 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
} }
dir->index++; dir->index++;
return r->_errno = 0; return 0;
} }
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir)); std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0; return 0;
} }
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
auto device = (Device*)r->deviceData;
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
}
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
// can be optimised for romfs. // can be optimised for romfs.
FileEntry file_entry; FileEntry file_entry{};
DirEntry dir_entry; DirEntry dir_entry{};
if (find_file(device->collections, path, file_entry)) { if (find_file(this->collections, path, file_entry)) {
st->st_size = file_entry.size; st->st_size = file_entry.size;
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
} else if (find_dir(device->collections, path, dir_entry)) { } else if (find_dir(this->collections, path, dir_entry)) {
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
return set_errno(r, ENOENT); return -ENOENT;
} }
} }
return r->_errno = 0; return 0;
}
constexpr devoptab_t DEVOPTAB = {
.structSize = sizeof(File),
.open_r = devoptab_open,
.close_r = devoptab_close,
.read_r = devoptab_read,
.seek_r = devoptab_seek,
.fstat_r = devoptab_fstat,
.stat_r = devoptab_lstat,
.dirStateSize = sizeof(Dir),
.diropen_r = devoptab_diropen,
.dirreset_r = devoptab_dirreset,
.dirnext_r = devoptab_dirnext,
.dirclose_r = devoptab_dirclose,
.lstat_r = devoptab_lstat,
};
struct Entry {
Device device{};
devoptab_t devoptab{};
fs::FsPath path{};
fs::FsPath mount{};
char name[32]{};
s32 ref_count{};
~Entry() {
RemoveDevice(mount);
}
};
Mutex g_mutex;
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
bool IsAlreadyMounted(const fs::FsPath& path, fs::FsPath& out_path) {
// check if we already have the save mounted.
for (auto& e : g_entries) {
if (e && e->path == path) {
e->ref_count++;
out_path = e->mount;
return true;
}
}
return false;
} }
Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path) { Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path) {
// 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);
// todo: rather than manually fetching tickets, use spl to // todo: rather than manually fetching tickets, use spl to
// decrypt the nca for use (somehow, look how ams does it?). // decrypt the nca for use (somehow, look how ams does it?).
keys::Keys keys; keys::Keys keys;
R_TRY(keys::parse_keys(keys, true)); R_TRY(keys::parse_keys(keys, true));
nca::Header header; nca::Header header{};
R_TRY(source->Read2(&header, 0, sizeof(header))); R_TRY(source->Read2(&header, 0, sizeof(header)));
R_TRY(nca::DecryptHeader(&header, keys, header)); R_TRY(nca::DecryptHeader(&header, keys, header));
std::unique_ptr<yati::source::Base> nca_reader; std::unique_ptr<yati::source::Base> nca_reader{};
log_write("[NCA] got header, type: %s\n", nca::GetContentTypeStr(header.content_type)); log_write("[NCA] got header, type: %s\n", nca::GetContentTypeStr(header.content_type));
// check if this is a ncz. // check if this is a ncz.
@@ -450,7 +379,7 @@ Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& s
); );
} }
std::vector<NamedCollection> collections; std::vector<NamedCollection> collections{};
const auto& content_type_fs = CONTENT_TYPE_FS_NAMES[header.content_type]; const auto& content_type_fs = CONTENT_TYPE_FS_NAMES[header.content_type];
for (u32 i = 0; i < NCA_SECTION_TOTAL; i++) { for (u32 i = 0; i < NCA_SECTION_TOTAL; i++) {
@@ -489,7 +418,7 @@ Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& s
continue; continue;
} }
NamedCollection collection; NamedCollection collection{};
collection.name = content_type_fs[i].name; collection.name = content_type_fs[i].name;
collection.fs_type = fs_header.fs_type; collection.fs_type = fs_header.fs_type;
@@ -535,22 +464,16 @@ Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& s
R_UNLESS(!collections.empty(), 0x9); R_UNLESS(!collections.empty(), 0x9);
auto entry = std::make_unique<Entry>(); if (!common::MountReadOnlyIndexDevice(
entry->path = path; [&nca_reader, &collections](const common::MountConfig& config) {
entry->devoptab = DEVOPTAB; return std::make_unique<Device>(std::move(nca_reader), collections, config);
entry->devoptab.name = entry->name; },
entry->devoptab.deviceData = &entry->device; sizeof(File), sizeof(Dir),
entry->device.source = std::move(nca_reader); "NCA", out_path
entry->device.collections = std::move(collections); )) {
std::snprintf(entry->name, sizeof(entry->name), "nca_%zu", index); log_write("[NCA] Failed to mount %s\n", path.s);
std::snprintf(entry->mount, sizeof(entry->mount), "nca_%zu:/", index); R_THROW(0x1);
}
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[NCA] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry->mount;
entry->ref_count++;
*itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
@@ -558,12 +481,6 @@ Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& s
} // namespace } // namespace
Result MountNca(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) { Result MountNca(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex);
if (IsAlreadyMounted(path, out_path)) {
R_SUCCEED();
}
s64 size; s64 size;
auto source = std::make_shared<yati::source::File>(fs, path); auto source = std::make_shared<yati::source::File>(fs, path);
R_TRY(source->GetSize(&size)); R_TRY(source->GetSize(&size));
@@ -572,42 +489,11 @@ Result MountNca(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
} }
Result MountNcaNcm(NcmContentStorage* cs, const NcmContentId* id, fs::FsPath& out_path) { Result MountNcaNcm(NcmContentStorage* cs, const NcmContentId* id, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex);
fs::FsPath path;
const auto id_lower = std::byteswap(*(const u64*)id->c);
const auto id_upper = std::byteswap(*(const u64*)(id->c + 0x8));
std::snprintf(path, sizeof(path), "%016lx%016lx", id_lower, id_upper);
if (IsAlreadyMounted(path, out_path)) {
R_SUCCEED();
}
s64 size; s64 size;
auto source = std::make_shared<ncm::NcmSource>(cs, id); auto source = std::make_shared<ncm::NcmSource>(cs, id);
R_TRY(source->GetSize(&size)); R_TRY(source->GetSize(&size));
return MountNcaInternal(nullptr, source, size, path, out_path); return MountNcaInternal(nullptr, source, size, {}, out_path);
}
void UmountNca(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return e && e->mount == mount;
});
if (itr == g_entries.end()) {
return;
}
if ((*itr)->ref_count) {
(*itr)->ref_count--;
}
if (!(*itr)->ref_count) {
itr->reset();
}
} }
} // namespace sphaira::devoptab } // namespace sphaira::devoptab

View File

@@ -6,11 +6,6 @@
#include "log.hpp" #include "log.hpp"
#include "nro.hpp" #include "nro.hpp"
#include "yati/nx/es.hpp"
#include "yati/nx/nca.hpp"
#include "yati/nx/keys.hpp"
#include "yati/nx/crypto.hpp"
#include "yati/container/nsp.hpp"
#include "yati/source/file.hpp" #include "yati/source/file.hpp"
#include <cstring> #include <cstring>
@@ -18,7 +13,6 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab { namespace sphaira::devoptab {
namespace { namespace {
@@ -48,26 +42,18 @@ struct DirEntry {
romfs::DirEntry romfs; romfs::DirEntry romfs;
}; };
struct Device {
std::unique_ptr<yati::source::Base> source;
std::vector<NamedCollection> collections;
FsTimeStampRaw timestamp;
};
struct File { struct File {
Device* device;
FileEntry entry; FileEntry entry;
size_t off; size_t off;
}; };
struct Dir { struct Dir {
Device* device;
DirEntry entry; DirEntry entry;
u32 index; u32 index;
bool is_root; bool is_root;
}; };
bool find_file(std::span<NamedCollection> named, std::string_view path, FileEntry& out) { bool find_file(std::span<const NamedCollection> named, std::string_view path, FileEntry& out) {
for (auto& e : named) { for (auto& e : named) {
if (path.starts_with("/" + e.name)) { if (path.starts_with("/" + e.name)) {
out.is_romfs = e.is_romfs; out.is_romfs = e.is_romfs;
@@ -112,61 +98,75 @@ bool find_dir(std::span<const NamedCollection> named, std::string_view path, Dir
return false; return false;
} }
void fill_timestamp_from_device(const Device* device, struct stat *st) { void fill_timestamp_from_device(const FsTimeStampRaw& timestamp, struct stat *st) {
st->st_atime = device->timestamp.accessed; st->st_atime = timestamp.accessed;
st->st_ctime = device->timestamp.created; st->st_ctime = timestamp.created;
st->st_mtime = device->timestamp.modified; st->st_mtime = timestamp.modified;
} }
int set_errno(struct _reent *r, int err) { struct Device final : common::MountDevice {
r->_errno = err; Device(std::unique_ptr<yati::source::Base>&& _source, const std::vector<NamedCollection>& _collections, const FsTimeStampRaw& _timestamp, const common::MountConfig& _config)
return -1; : MountDevice{_config}
} , source{std::forward<decltype(_source)>(_source)}
, collections{_collections}
, timestamp{_timestamp} {
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) { }
auto device = (Device*)r->deviceData;
private:
bool Mount() override { return true; }
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
private:
std::unique_ptr<yati::source::Base> source;
const std::vector<NamedCollection> collections;
const FsTimeStampRaw timestamp;
};
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct); auto file = static_cast<File*>(fileStruct);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX]; FileEntry entry{};
if (!common::fix_path(_path, path)) { if (!find_file(this->collections, path, entry)) {
return set_errno(r, ENOENT); log_write("[NROFS] failed to find file entry: %s\n", path);
return -ENOENT;
} }
FileEntry entry;
if (!find_file(device->collections, path, entry)) {
log_write("[NROFS] failed to find file entry\n");
return set_errno(r, ENOENT);
}
file->device = device;
file->entry = entry; file->entry = entry;
return 0;
return r->_errno = 0;
} }
int devoptab_close(struct _reent *r, void *fd) { int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
std::memset(file, 0, sizeof(*file)); std::memset(file, 0, sizeof(*file));
return r->_errno = 0; return 0;
} }
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) { ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& entry = file->entry; const auto& entry = file->entry;
u64 bytes_read; u64 bytes_read;
len = std::min(len, entry.size - file->off); len = std::min(len, entry.size - file->off);
if (R_FAILED(file->device->source->Read(ptr, entry.offset + file->off, len, &bytes_read))) { if (R_FAILED(this->source->Read(ptr, entry.offset + file->off, len, &bytes_read))) {
return set_errno(r, ENOENT); return -EIO;
} }
file->off += bytes_read; file->off += bytes_read;
return bytes_read; return bytes_read;
} }
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) { off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& entry = file->entry; const auto& entry = file->entry;
@@ -176,56 +176,40 @@ off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
pos = entry.size; pos = entry.size;
} }
r->_errno = 0;
return file->off = std::clamp<u64>(pos, 0, entry.size); return file->off = std::clamp<u64>(pos, 0, entry.size);
} }
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) { int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& entry = file->entry; const auto& entry = file->entry;
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
st->st_size = entry.size; st->st_size = entry.size;
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
fill_timestamp_from_device(file->device, st); fill_timestamp_from_device(this->timestamp, st);
return r->_errno = 0; return 0;
} }
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) { int Device::devoptab_diropen(void* fd, const char *path) {
auto device = (Device*)r->deviceData; auto dir = static_cast<Dir*>(fd);
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(dir, 0, sizeof(*dir));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
set_errno(r, ENOENT);
return NULL;
}
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
dir->device = device;
dir->is_root = true; dir->is_root = true;
r->_errno = 0; return 0;
return dirState;
} else { } else {
DirEntry entry; DirEntry entry{};
if (!find_dir(device->collections, path, entry)) { if (!find_dir(this->collections, path, entry)) {
set_errno(r, ENOENT); return -ENOENT;
return NULL;
} }
dir->device = device;
dir->entry = entry; dir->entry = entry;
return 0;
r->_errno = 0;
return dirState;
} }
} }
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
auto& entry = dir->entry; auto& entry = dir->entry;
if (dir->is_root) { if (dir->is_root) {
@@ -236,20 +220,19 @@ int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
} }
} }
return r->_errno = 0; return 0;
} }
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
auto& entry = dir->entry; auto& entry = dir->entry;
std::memset(filestat, 0, sizeof(*filestat));
if (dir->is_root) { if (dir->is_root) {
if (dir->index >= dir->device->collections.size()) { if (dir->index >= this->collections.size()) {
return set_errno(r, ENOENT); return -ENOENT;
} }
const auto& e = dir->device->collections[dir->index]; const auto& e = this->collections[dir->index];
if (e.is_romfs) { if (e.is_romfs) {
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
@@ -262,126 +245,60 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
} else { } else {
if (entry.is_romfs) { if (entry.is_romfs) {
if (!romfs::dirnext(entry.romfs, filename, filestat)) { if (!romfs::dirnext(entry.romfs, filename, filestat)) {
return set_errno(r, ENOENT); return -ENOENT;
} }
} }
} }
fill_timestamp_from_device(dir->device, filestat); fill_timestamp_from_device(this->timestamp, filestat);
dir->index++; dir->index++;
return r->_errno = 0; return 0;
} }
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir)); std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0; return 0;
} }
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
auto device = (Device*)r->deviceData;
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
}
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
// can be optimised for romfs. // can be optimised for romfs.
FileEntry file_entry; FileEntry file_entry{};
DirEntry dir_entry; DirEntry dir_entry{};
if (find_file(device->collections, path, file_entry)) { if (find_file(this->collections, path, file_entry)) {
st->st_size = file_entry.size; st->st_size = file_entry.size;
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
} else if (find_dir(device->collections, path, dir_entry)) { } else if (find_dir(this->collections, path, dir_entry)) {
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
return set_errno(r, ENOENT); return -ENOENT;
} }
} }
fill_timestamp_from_device(device, st); fill_timestamp_from_device(this->timestamp, st);
return r->_errno = 0; return 0;
}
constexpr devoptab_t DEVOPTAB = {
.structSize = sizeof(File),
.open_r = devoptab_open,
.close_r = devoptab_close,
.read_r = devoptab_read,
.seek_r = devoptab_seek,
.fstat_r = devoptab_fstat,
.stat_r = devoptab_lstat,
.dirStateSize = sizeof(Dir),
.diropen_r = devoptab_diropen,
.dirreset_r = devoptab_dirreset,
.dirnext_r = devoptab_dirnext,
.dirclose_r = devoptab_dirclose,
.lstat_r = devoptab_lstat,
};
struct Entry {
Device device{};
devoptab_t devoptab{};
fs::FsPath path{};
fs::FsPath mount{};
char name[32]{};
s32 ref_count{};
~Entry() {
RemoveDevice(mount);
}
};
Mutex g_mutex;
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
bool IsAlreadyMounted(const fs::FsPath& path, fs::FsPath& out_path) {
// check if we already have the save mounted.
for (auto& e : g_entries) {
if (e && e->path == path) {
e->ref_count++;
out_path = e->mount;
return true;
}
}
return false;
} }
} // namespace } // namespace
Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) { Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex);
if (IsAlreadyMounted(path, out_path)) {
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);
NroData data; NroData data{};
R_TRY(source->Read2(&data, 0, sizeof(data))); R_TRY(source->Read2(&data, 0, sizeof(data)));
R_UNLESS(data.header.magic == NROHEADER_MAGIC, Result_NroBadMagic); R_UNLESS(data.header.magic == NROHEADER_MAGIC, Result_NroBadMagic);
NroAssetHeader asset; NroAssetHeader asset{};
R_TRY(source->Read2(&asset, data.header.size, sizeof(asset))); R_TRY(source->Read2(&asset, data.header.size, sizeof(asset)));
R_UNLESS(asset.magic == NROASSETHEADER_MAGIC, Result_NroBadMagic); R_UNLESS(asset.magic == NROASSETHEADER_MAGIC, Result_NroBadMagic);
std::vector<NamedCollection> collections; std::vector<NamedCollection> collections{};
if (asset.icon.size) { if (asset.icon.size) {
NamedCollection collection{"icon.jpg", false, AssetCollection{data.header.size + asset.icon.offset, asset.icon.size}}; NamedCollection collection{"icon.jpg", false, AssetCollection{data.header.size + asset.icon.offset, asset.icon.size}};
@@ -400,45 +317,21 @@ Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
R_UNLESS(!collections.empty(), 0x9); R_UNLESS(!collections.empty(), 0x9);
auto entry = std::make_unique<Entry>(); FsTimeStampRaw timestamp{};
entry->path = path; fs->GetFileTimeStampRaw(path, &timestamp);
entry->devoptab = DEVOPTAB;
entry->devoptab.name = entry->name;
entry->devoptab.deviceData = &entry->device;
entry->device.source = std::move(source);
entry->device.collections = collections;
fs->GetFileTimeStampRaw(path, &entry->device.timestamp);
std::snprintf(entry->name, sizeof(entry->name), "nro_%zu", index);
std::snprintf(entry->mount, sizeof(entry->mount), "nro_%zu:/", index);
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1); if (!common::MountReadOnlyIndexDevice(
log_write("[NRO] DEVICE SUCCESS %s %s\n", path.s, entry->name); [&source, &collections, &timestamp](const common::MountConfig& config) {
return std::make_unique<Device>(std::move(source), collections, timestamp, config);
out_path = entry->mount; },
entry->ref_count++; sizeof(File), sizeof(Dir),
*itr = std::move(entry); "NRO", out_path
)) {
log_write("[NRO] Failed to mount %s\n", path.s);
R_THROW(0x1);
}
R_SUCCEED(); R_SUCCEED();
} }
void UmountNro(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return e && e->mount == mount;
});
if (itr == g_entries.end()) {
return;
}
if ((*itr)->ref_count) {
(*itr)->ref_count--;
}
if (!(*itr)->ref_count) {
itr->reset();
}
}
} // namespace sphaira::devoptab } // namespace sphaira::devoptab

View File

@@ -13,76 +13,84 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab { namespace sphaira::devoptab {
namespace { namespace {
struct Device { using Collections = yati::container::Collections;
std::unique_ptr<common::LruBufferedData> source;
yati::container::Collections collections;
};
struct File { struct File {
Device* device;
const yati::container::CollectionEntry* collection; const yati::container::CollectionEntry* collection;
size_t off; size_t off;
}; };
struct Dir { struct Dir {
Device* device;
u32 index; u32 index;
}; };
int set_errno(struct _reent *r, int err) { struct Device final : common::MountDevice {
r->_errno = err; Device(std::unique_ptr<common::LruBufferedData>&& _source, const Collections& _collections, const common::MountConfig& _config)
return -1; : MountDevice{_config}
} , source{std::forward<decltype(_source)>(_source)}
, collections{_collections} {
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
auto device = (Device*)r->deviceData;
auto file = static_cast<File*>(fileStruct);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
} }
for (const auto& collection : device->collections) { private:
bool Mount() override { return true; }
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
private:
std::unique_ptr<common::LruBufferedData> source;
const Collections collections;
};
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct);
for (const auto& collection : this->collections) {
if (path == "/" + collection.name) { if (path == "/" + collection.name) {
file->device = device;
file->collection = &collection; file->collection = &collection;
return r->_errno = 0; return 0;
} }
} }
return set_errno(r, ENOENT); log_write("[NSP] failed to open file %s\n", path);
return -ENOENT;
} }
int devoptab_close(struct _reent *r, void *fd) { int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
std::memset(file, 0, sizeof(*file)); std::memset(file, 0, sizeof(*file));
return r->_errno = 0; return 0;
} }
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) { ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& collection = file->collection; const auto& collection = file->collection;
len = std::min(len, collection->size - file->off); len = std::min(len, collection->size - file->off);
u64 bytes_read; u64 bytes_read;
if (R_FAILED(file->device->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) { if (R_FAILED(this->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
return set_errno(r, ENOENT); return -EIO;
} }
file->off += bytes_read; file->off += bytes_read;
return bytes_read; return bytes_read;
} }
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) { off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& collection = file->collection; const auto& collection = file->collection;
@@ -92,159 +100,83 @@ off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
pos = collection->size; pos = collection->size;
} }
r->_errno = 0;
return file->off = std::clamp<u64>(pos, 0, collection->size); return file->off = std::clamp<u64>(pos, 0, collection->size);
} }
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) { int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& collection = file->collection; const auto& collection = file->collection;
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
st->st_size = collection->size; st->st_size = collection->size;
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
return r->_errno = 0; return 0;
} }
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) { int Device::devoptab_diropen(void* fd, const char *path) {
auto device = (Device*)r->deviceData;
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(dir, 0, sizeof(*dir));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
set_errno(r, ENOENT);
return NULL;
}
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
dir->device = device; return 0;
} else {
set_errno(r, ENOENT);
return NULL;
} }
r->_errno = 0; log_write("[NSP] failed to open dir %s\n", path);
return dirState; return -ENOENT;
} }
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
dir->index = 0; dir->index = 0;
return 0;
return r->_errno = 0;
} }
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(filestat, 0, sizeof(*filestat));
if (dir->index >= dir->device->collections.size()) { if (dir->index >= this->collections.size()) {
return set_errno(r, ENOENT); return -ENOENT;
} }
const auto& collection = dir->device->collections[dir->index]; const auto& collection = this->collections[dir->index];
filestat->st_nlink = 1; filestat->st_nlink = 1;
filestat->st_size = collection.size; filestat->st_size = collection.size;
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
std::strcpy(filename, collection.name.c_str()); std::strcpy(filename, collection.name.c_str());
dir->index++; dir->index++;
return r->_errno = 0; return 0;
} }
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir)); std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0; return 0;
} }
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
auto device = (Device*)r->deviceData; st->st_nlink = 1;
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
}
std::memset(st, 0, sizeof(*st));
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
const auto it = std::ranges::find_if(device->collections, [path](auto& e){ const auto it = std::ranges::find_if(this->collections, [path](auto& e){
return path == "/" + e.name; return path == "/" + e.name;
}); });
if (it == device->collections.end()) { if (it == this->collections.end()) {
return set_errno(r, ENOENT); return -ENOENT;
} }
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
st->st_size = it->size; st->st_size = it->size;
} }
st->st_nlink = 1; return -ENOENT;
return r->_errno = 0;
} }
constexpr devoptab_t DEVOPTAB = {
.structSize = sizeof(File),
.open_r = devoptab_open,
.close_r = devoptab_close,
.read_r = devoptab_read,
.seek_r = devoptab_seek,
.fstat_r = devoptab_fstat,
.stat_r = devoptab_lstat,
.dirStateSize = sizeof(Dir),
.diropen_r = devoptab_diropen,
.dirreset_r = devoptab_dirreset,
.dirnext_r = devoptab_dirnext,
.dirclose_r = devoptab_dirclose,
.lstat_r = devoptab_lstat,
};
struct Entry {
Device device{};
devoptab_t devoptab{};
fs::FsPath path{};
fs::FsPath mount{};
char name[32]{};
s32 ref_count{};
~Entry() {
RemoveDevice(mount);
}
};
Mutex g_mutex;
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
} // namespace } // namespace
Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) { Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex);
// check if we already have the save mounted.
for (auto& e : g_entries) {
if (e && e->path == path) {
e->ref_count++;
out_path = e->mount;
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_shared<yati::source::File>(fs, path); auto source = std::make_shared<yati::source::File>(fs, path);
s64 size; s64 size;
@@ -255,44 +187,18 @@ 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 = std::make_unique<Entry>(); if (!common::MountReadOnlyIndexDevice(
entry->path = path; [&buffered, &collections](const common::MountConfig& config) {
entry->devoptab = DEVOPTAB; return std::make_unique<Device>(std::move(buffered), collections, config);
entry->devoptab.name = entry->name; },
entry->devoptab.deviceData = &entry->device; sizeof(File), sizeof(Dir),
entry->device.source = std::move(buffered); "NSP", out_path
entry->device.collections = collections; )) {
std::snprintf(entry->name, sizeof(entry->name), "nsp_%zu", index); log_write("[NSP] Failed to mount %s\n", path.s);
std::snprintf(entry->mount, sizeof(entry->mount), "nsp_%zu:/", index); R_THROW(0x1);
}
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[NSP] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry->mount;
entry->ref_count++;
*itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
void UmountNsp(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return e && e->mount == mount;
});
if (itr == g_entries.end()) {
return;
}
if ((*itr)->ref_count) {
(*itr)->ref_count--;
}
if (!(*itr)->ref_count) {
itr->reset();
}
}
} // namespace sphaira::devoptab } // namespace sphaira::devoptab

View File

@@ -12,18 +12,11 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab { namespace sphaira::devoptab {
namespace { namespace {
struct Device {
save_ctx_t* ctx;
hierarchical_save_file_table_ctx_t* file_table;
};
struct File { struct File {
Device* device;
save_fs_list_entry_t entry; save_fs_list_entry_t entry;
allocation_table_storage_ctx_t storage; allocation_table_storage_ctx_t storage;
size_t off; size_t off;
@@ -35,138 +28,140 @@ struct DirNext {
}; };
struct Dir { struct Dir {
Device* device;
save_fs_list_entry_t entry; save_fs_list_entry_t entry;
u32 next_directory; u32 next_directory;
u32 next_file; u32 next_file;
}; };
int set_errno(struct _reent *r, int err) { struct Device final : common::MountDevice {
r->_errno = err; Device(save_ctx_t* _ctx, const common::MountConfig& _config)
return -1; : MountDevice{_config}
} , ctx{_ctx} {
file_table = &ctx->save_filesystem_core.file_table;
}
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) { ~Device() {
auto device = (Device*)r->deviceData; save_close_savefile(&this->ctx);
}
private:
bool Mount() override { return true; }
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
private:
save_ctx_t* ctx;
hierarchical_save_file_table_ctx_t* file_table;
};
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct); auto file = static_cast<File*>(fileStruct);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX]; if (!save_hierarchical_file_table_get_file_entry_by_path(this->file_table, path, &file->entry)) {
if (!common::fix_path(_path, path)) { return -ENOENT;
return set_errno(r, ENOENT);
} }
if (!save_hierarchical_file_table_get_file_entry_by_path(device->file_table, path, &file->entry)) { if (!save_open_fat_storage(&this->ctx->save_filesystem_core, &file->storage, file->entry.value.save_file_info.start_block)) {
return set_errno(r, ENOENT); return -ENOENT;
} }
if (!save_open_fat_storage(&device->ctx->save_filesystem_core, &file->storage, file->entry.value.save_file_info.start_block)) { return 0;
return set_errno(r, ENOENT);
}
file->device = device;
return r->_errno = 0;
} }
int devoptab_close(struct _reent *r, void *fd) { int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
std::memset(file, 0, sizeof(*file)); std::memset(file, 0, sizeof(*file));
return r->_errno = 0; return 0;
} }
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) { ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
len = std::min(len, file->entry.value.save_file_info.length - file->off);
if (!len) {
return 0;
}
// todo: maybe eof here? // todo: maybe eof here?
const auto bytes_read = save_allocation_table_storage_read(&file->storage, ptr, file->off, len); const auto bytes_read = save_allocation_table_storage_read(&file->storage, ptr, file->off, len);
if (!bytes_read) { if (!bytes_read) {
return set_errno(r, ENOENT); return -ENOENT;
} }
file->off += bytes_read; file->off += bytes_read;
return bytes_read; return bytes_read;
} }
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) { off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
if (dir == SEEK_CUR) { if (dir == SEEK_CUR) {
pos += file->off; pos += file->off;
} else if (dir == SEEK_END) { } else if (dir == SEEK_END) {
pos = file->storage._length; pos = file->entry.value.save_file_info.length;
} }
r->_errno = 0; return file->off = std::clamp<u64>(pos, 0, file->entry.value.save_file_info.length);
return file->off = std::clamp<u64>(pos, 0, file->storage._length);
} }
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) { int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
log_write("[\t\tDEV] fstat\n");
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
st->st_size = file->storage._length; st->st_size = file->entry.value.save_file_info.length;
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
return r->_errno = 0; return 0;
} }
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) { int Device::devoptab_diropen(void* fd, const char *path) {
auto device = (Device*)r->deviceData; auto dir = static_cast<Dir*>(fd);
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(dir, 0, sizeof(*dir));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
set_errno(r, ENOENT);
return NULL;
}
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
save_entry_key_t key{}; save_entry_key_t key{};
const auto idx = save_fs_list_get_index_from_key(&device->file_table->directory_table, &key, NULL); const auto idx = save_fs_list_get_index_from_key(&this->file_table->directory_table, &key, NULL);
if (idx == 0xFFFFFFFF) { if (idx == 0xFFFFFFFF) {
set_errno(r, ENOENT); return -ENOENT;
return NULL;
} }
if (!save_fs_list_get_value(&device->file_table->directory_table, idx, &dir->entry)) { if (!save_fs_list_get_value(&this->file_table->directory_table, idx, &dir->entry)) {
set_errno(r, ENOENT); return -ENOENT;
return NULL;
} }
} else if (!save_hierarchical_directory_table_get_file_entry_by_path(device->file_table, path, &dir->entry)) { } else if (!save_hierarchical_directory_table_get_file_entry_by_path(this->file_table, path, &dir->entry)) {
set_errno(r, ENOENT); return -ENOENT;
return NULL;
} }
dir->device = device;
dir->next_file = dir->entry.value.save_find_position.next_file; dir->next_file = dir->entry.value.save_find_position.next_file;
dir->next_directory = dir->entry.value.save_find_position.next_directory; dir->next_directory = dir->entry.value.save_find_position.next_directory;
r->_errno = 0; return 0;
return dirState;
} }
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
dir->next_file = dir->entry.value.save_find_position.next_file; dir->next_file = dir->entry.value.save_find_position.next_file;
dir->next_directory = dir->entry.value.save_find_position.next_directory; dir->next_directory = dir->entry.value.save_find_position.next_directory;
return r->_errno = 0; return 0;
} }
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(filestat, 0, sizeof(*filestat));
save_fs_list_entry_t entry{}; save_fs_list_entry_t entry{};
if (dir->next_directory) { if (dir->next_directory) {
// todo: use save_allocation_table_storage_read for faster reads // todo: use save_allocation_table_storage_read for faster reads
if (!save_fs_list_get_value(&dir->device->file_table->directory_table, dir->next_directory, &entry)) { if (!save_fs_list_get_value(&this->file_table->directory_table, dir->next_directory, &entry)) {
return set_errno(r, ENOENT); return -ENOENT;
} }
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
@@ -174,113 +169,55 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
} }
else if (dir->next_file) { else if (dir->next_file) {
// todo: use save_allocation_table_storage_read for faster reads // todo: use save_allocation_table_storage_read for faster reads
if (!save_fs_list_get_value(&dir->device->file_table->file_table, dir->next_file, &entry)) { if (!save_fs_list_get_value(&this->file_table->file_table, dir->next_file, &entry)) {
return set_errno(r, ENOENT); return -ENOENT;
} }
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
// todo: confirm this.
filestat->st_size = entry.value.save_file_info.length;
// filestat->st_size = file->storage.block_size;
dir->next_file = entry.value.next_sibling; dir->next_file = entry.value.next_sibling;
} }
else { else {
return set_errno(r, ENOENT); return -ENOENT;
} }
filestat->st_nlink = 1; filestat->st_nlink = 1;
strcpy(filename, entry.name); std::strcpy(filename, entry.name);
return r->_errno = 0; return 0;
} }
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir)); std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0; return 0;
} }
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
auto device = (Device*)r->deviceData; st->st_nlink = 1;
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
}
std::memset(st, 0, sizeof(*st));
save_fs_list_entry_t entry{}; save_fs_list_entry_t entry{};
// NOTE: this is very slow. // NOTE: this is very slow.
if (save_hierarchical_file_table_get_file_entry_by_path(device->file_table, path, &entry)) { if (save_hierarchical_file_table_get_file_entry_by_path(this->file_table, path, &entry)) {
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
st->st_size = entry.value.save_file_info.length; st->st_size = entry.value.save_file_info.length;
} else if (save_hierarchical_directory_table_get_file_entry_by_path(device->file_table, path, &entry)) { } else if (save_hierarchical_directory_table_get_file_entry_by_path(this->file_table, path, &entry)) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
return set_errno(r, ENOENT); return -ENOENT;
} }
st->st_nlink = 1; return 0;
return r->_errno = 0;
} }
constexpr devoptab_t DEVOPTAB = {
.structSize = sizeof(File),
.open_r = devoptab_open,
.close_r = devoptab_close,
.read_r = devoptab_read,
.seek_r = devoptab_seek,
.fstat_r = devoptab_fstat,
.stat_r = devoptab_lstat,
.dirStateSize = sizeof(Dir),
.diropen_r = devoptab_diropen,
.dirreset_r = devoptab_dirreset,
.dirnext_r = devoptab_dirnext,
.dirclose_r = devoptab_dirclose,
.lstat_r = devoptab_lstat,
};
struct Entry {
Device device{};
devoptab_t devoptab{};
u64 id{};
fs::FsPath mount{};
char name[32]{};
s32 ref_count{};
~Entry() {
RemoveDevice(mount);
save_close_savefile(&device.ctx);
}
};
Mutex g_mutex;
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
} // namespace } // namespace
Result MountSaveSystem(u64 id, fs::FsPath& out_path) { Result MountSaveSystem(u64 id, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex); static Mutex mutex{};
SCOPED_MUTEX(&mutex);
// check if we already have the save mounted. fs::FsPath path{};
for (auto& e : g_entries) {
if (e && e->id == id) {
e->ref_count++;
out_path = e->mount;
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];
std::snprintf(path, sizeof(path), "SYSTEM:/save/%016lx", id); std::snprintf(path, sizeof(path), "SYSTEM:/save/%016lx", id);
auto ctx = save_open_savefile(path, 0); auto ctx = save_open_savefile(path, 0);
@@ -288,46 +225,18 @@ Result MountSaveSystem(u64 id, fs::FsPath& out_path) {
R_THROW(0x1); R_THROW(0x1);
} }
log_write("[SAVE] OPEN SUCCESS %s\n", path); if (!common::MountReadOnlyIndexDevice(
[&ctx](const common::MountConfig& config) {
auto entry = std::make_unique<Entry>(); return std::make_unique<Device>(ctx, config);
entry->id = id; },
entry->device.ctx = ctx; sizeof(File), sizeof(Dir),
entry->device.file_table = &ctx->save_filesystem_core.file_table; "SAVE", out_path
entry->devoptab = DEVOPTAB; )) {
entry->devoptab.name = entry->name; log_write("[SAVE] Failed to mount %s\n", path.s);
entry->devoptab.deviceData = &entry->device; R_THROW(0x1);
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);
log_write("[SAVE] DEVICE SUCCESS %s %s\n", path, entry->name);
out_path = entry->mount;
entry->ref_count++;
*itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
void UnmountSave(u64 id) {
SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [id](auto& e){
return e && e->id == id;
});
if (itr == g_entries.end()) {
return;
}
if ((*itr)->ref_count) {
(*itr)->ref_count--;
}
if (!(*itr)->ref_count) {
itr->reset();
}
}
} // namespace sphaira::devoptab } // namespace sphaira::devoptab

View File

@@ -12,79 +12,85 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab { namespace sphaira::devoptab {
namespace { namespace {
struct Device {
std::unique_ptr<common::LruBufferedData> source;
yati::container::Xci::Partitions partitions;
};
struct File { struct File {
Device* device;
const yati::container::CollectionEntry* collection; const yati::container::CollectionEntry* collection;
size_t off; size_t off;
}; };
struct Dir { struct Dir {
Device* device;
const yati::container::Collections* collections; const yati::container::Collections* collections;
u32 index; u32 index;
}; };
int set_errno(struct _reent *r, int err) { struct Device final : common::MountDevice {
r->_errno = err; Device(std::unique_ptr<common::LruBufferedData>&& _source, const yati::container::Xci::Partitions& _partitions, const common::MountConfig& _config)
return -1; : MountDevice{_config}
} , source{std::forward<decltype(_source)>(_source)}
, partitions{_partitions} {
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
auto device = (Device*)r->deviceData;
auto file = static_cast<File*>(fileStruct);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
} }
for (const auto& partition : device->partitions) { private:
bool Mount() override { return true; }
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
private:
std::unique_ptr<common::LruBufferedData> source;
const yati::container::Xci::Partitions partitions;
};
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct);
for (const auto& partition : this->partitions) {
for (const auto& collection : partition.collections) { for (const auto& collection : partition.collections) {
if (path == "/" + partition.name + "/" + collection.name) { if (path == "/" + partition.name + "/" + collection.name) {
file->device = device;
file->collection = &collection; file->collection = &collection;
return r->_errno = 0; return 0;
} }
} }
} }
return set_errno(r, ENOENT); log_write("[XCI] devoptab_open: failed to find path: %s\n", path);
return -ENOENT;
} }
int devoptab_close(struct _reent *r, void *fd) { int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
std::memset(file, 0, sizeof(*file)); std::memset(file, 0, sizeof(*file));
return r->_errno = 0; return 0;
} }
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) { ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& collection = file->collection; const auto& collection = file->collection;
len = std::min(len, collection->size - file->off); len = std::min(len, collection->size - file->off);
u64 bytes_read; u64 bytes_read;
if (R_FAILED(file->device->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) { if (R_FAILED(this->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
return set_errno(r, ENOENT); return -EIO;
} }
file->off += bytes_read; file->off += bytes_read;
return bytes_read; return bytes_read;
} }
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) { off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& collection = file->collection; const auto& collection = file->collection;
@@ -94,73 +100,58 @@ off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
pos = collection->size; pos = collection->size;
} }
r->_errno = 0;
return file->off = std::clamp<u64>(pos, 0, collection->size); return file->off = std::clamp<u64>(pos, 0, collection->size);
} }
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) { int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
const auto& collection = file->collection; const auto& collection = file->collection;
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
st->st_size = collection->size; st->st_size = collection->size;
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
return r->_errno = 0; return 0;
} }
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) { int Device::devoptab_diropen(void* fd, const char *path) {
auto device = (Device*)r->deviceData; auto dir = static_cast<Dir*>(fd);
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(dir, 0, sizeof(*dir));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
set_errno(r, ENOENT);
return NULL;
}
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
dir->device = device; return 0;
r->_errno = 0;
return dirState;
} else { } else {
for (const auto& partition : device->partitions) { for (const auto& partition : this->partitions) {
if (path == "/" + partition.name) { if (path == "/" + partition.name) {
dir->collections = &partition.collections; dir->collections = &partition.collections;
r->_errno = 0; return 0;
return dirState;
} }
} }
} }
set_errno(r, ENOENT); return -ENOENT;
return NULL;
} }
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
dir->index = 0; dir->index = 0;
return r->_errno = 0; return 0;
} }
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(filestat, 0, sizeof(*filestat));
if (!dir->collections) { if (!dir->collections) {
if (dir->index >= dir->device->partitions.size()) { if (dir->index >= this->partitions.size()) {
return set_errno(r, ENOENT); return -ENOENT;
} }
filestat->st_nlink = 1; filestat->st_nlink = 1;
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
std::strcpy(filename, dir->device->partitions[dir->index].name.c_str()); std::strcpy(filename, this->partitions[dir->index].name.c_str());
} else { } else {
if (dir->index >= dir->collections->size()) { if (dir->index >= dir->collections->size()) {
return set_errno(r, ENOENT); return -ENOENT;
} }
const auto& collection = (*dir->collections)[dir->index]; const auto& collection = (*dir->collections)[dir->index];
@@ -171,133 +162,57 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
} }
dir->index++; dir->index++;
return r->_errno = 0; return 0;
} }
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir)); std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0; return 0;
} }
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
auto device = (Device*)r->deviceData;
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
}
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
if (!std::strcmp(path, "/")) { if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else { } else {
for (const auto& partition : device->partitions) { for (const auto& partition : this->partitions) {
if (path == "/" + partition.name) { if (path == "/" + partition.name) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
return r->_errno = 0; return 0;
} }
for (const auto& collection : partition.collections) { for (const auto& collection : partition.collections) {
if (path == "/" + partition.name + "/" + collection.name) { if (path == "/" + partition.name + "/" + collection.name) {
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
st->st_size = collection.size; st->st_size = collection.size;
return r->_errno = 0; return 0;
} }
} }
} }
} }
return set_errno(r, ENOENT); return -ENOENT;
}
constexpr devoptab_t DEVOPTAB = {
.structSize = sizeof(File),
.open_r = devoptab_open,
.close_r = devoptab_close,
.read_r = devoptab_read,
.seek_r = devoptab_seek,
.fstat_r = devoptab_fstat,
.stat_r = devoptab_lstat,
.dirStateSize = sizeof(Dir),
.diropen_r = devoptab_diropen,
.dirreset_r = devoptab_dirreset,
.dirnext_r = devoptab_dirnext,
.dirclose_r = devoptab_dirclose,
.lstat_r = devoptab_lstat,
};
struct Entry {
Device device{};
devoptab_t devoptab{};
fs::FsPath path{};
fs::FsPath mount{};
char name[32]{};
s32 ref_count{};
~Entry() {
RemoveDevice(mount);
}
};
Mutex g_mutex;
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
bool IsAlreadyMounted(const fs::FsPath& path, fs::FsPath& out_path) {
// check if we already have the save mounted.
for (auto& e : g_entries) {
if (e && e->path == path) {
e->ref_count++;
out_path = e->mount;
return true;
}
}
return false;
} }
Result MountXciInternal(const std::shared_ptr<yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path) { Result MountXciInternal(const std::shared_ptr<yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path) {
// check if we already have the save mounted.
for (auto& e : g_entries) {
if (e && e->path == path) {
e->ref_count++;
out_path = e->mount;
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 buffered = std::make_unique<common::LruBufferedData>(source, size); auto buffered = std::make_unique<common::LruBufferedData>(source, size);
yati::container::Xci xci{buffered.get()}; yati::container::Xci xci{buffered.get()};
yati::container::Xci::Root root; yati::container::Xci::Root root;
R_TRY(xci.GetRoot(root)); R_TRY(xci.GetRoot(root));
auto entry = std::make_unique<Entry>(); if (!common::MountReadOnlyIndexDevice(
entry->path = path; [&buffered, &root](const common::MountConfig& config) {
entry->devoptab = DEVOPTAB; return std::make_unique<Device>(std::move(buffered), root.partitions, config);
entry->devoptab.name = entry->name; },
entry->devoptab.deviceData = &entry->device; sizeof(File), sizeof(Dir),
entry->device.source = std::move(buffered); "XCI", out_path
entry->device.partitions = root.partitions; )) {
std::snprintf(entry->name, sizeof(entry->name), "xci_%zu", index); log_write("[XCI] Failed to mount %s\n", path.s);
std::snprintf(entry->mount, sizeof(entry->mount), "xci_%zu:/", index); R_THROW(0x1);
}
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[XCI] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry->mount;
entry->ref_count++;
*itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
@@ -305,12 +220,6 @@ Result MountXciInternal(const std::shared_ptr<yati::source::Base>& source, s64 s
} // namespace } // namespace
Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) { Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex);
if (IsAlreadyMounted(path, out_path)) {
R_SUCCEED();
}
s64 size; s64 size;
auto source = std::make_shared<yati::source::File>(fs, path); auto source = std::make_shared<yati::source::File>(fs, path);
R_TRY(source->GetSize(&size)); R_TRY(source->GetSize(&size));
@@ -319,33 +228,7 @@ Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
} }
Result MountXciSource(const std::shared_ptr<sphaira::yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path) { Result MountXciSource(const std::shared_ptr<sphaira::yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex);
if (IsAlreadyMounted(path, out_path)) {
R_SUCCEED();
}
return MountXciInternal(source, size, path, out_path); return MountXciInternal(source, size, path, out_path);
} }
void UmountXci(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return e && e->mount == mount;
});
if (itr == g_entries.end()) {
return;
}
if ((*itr)->ref_count) {
(*itr)->ref_count--;
}
if (!(*itr)->ref_count) {
itr->reset();
}
}
} // namespace sphaira::devoptab } // namespace sphaira::devoptab

View File

@@ -10,7 +10,6 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <sys/iosupport.h>
#include <zlib.h> #include <zlib.h>
namespace sphaira::devoptab { namespace sphaira::devoptab {
@@ -113,11 +112,6 @@ struct DirectoryEntry {
using FileTableEntries = std::vector<FileEntry>; using FileTableEntries = std::vector<FileEntry>;
struct Device {
std::unique_ptr<common::LruBufferedData> source;
DirectoryEntry root;
};
struct Zfile { struct Zfile {
z_stream z; // zlib stream. z_stream z; // zlib stream.
Bytef* buffer; // buffer that compressed data is read into. Bytef* buffer; // buffer that compressed data is read into.
@@ -126,7 +120,6 @@ struct Zfile {
}; };
struct File { struct File {
Device* device;
const FileEntry* entry; const FileEntry* entry;
Zfile zfile; // only used if the file is compressed. Zfile zfile; // only used if the file is compressed.
size_t data_off; // offset of the file data. size_t data_off; // offset of the file data.
@@ -134,7 +127,6 @@ struct File {
}; };
struct Dir { struct Dir {
Device* device;
const DirectoryEntry* entry; const DirectoryEntry* entry;
u32 index; u32 index;
}; };
@@ -194,44 +186,58 @@ void set_stat_file(const FileEntry* entry, struct stat *st) {
st->st_ctime = st->st_atime; st->st_ctime = st->st_atime;
} }
int set_errno(struct _reent *r, int err) { struct Device final : common::MountDevice {
r->_errno = err; Device(std::unique_ptr<common::LruBufferedData>&& _source, const DirectoryEntry& _root, const common::MountConfig& _config)
return -1; : MountDevice{_config}
} , source{std::forward<decltype(_source)>(_source)}
, root{_root} {
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
auto device = (Device*)r->deviceData;
auto file = static_cast<File*>(fileStruct);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX]{};
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
} }
const auto entry = find_file_entry(device->root, path); private:
bool Mount() override { return true; }
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
private:
std::unique_ptr<common::LruBufferedData> source;
const DirectoryEntry root;
};
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct);
const auto entry = find_file_entry(this->root, path);
if (!entry) { if (!entry) {
return set_errno(r, ENOENT); return -ENOENT;
} }
if ((entry->flags & mmz_Flag_Encrypted) || (entry->flags & mmz_Flag_StrongEncrypted)) { if ((entry->flags & mmz_Flag_Encrypted) || (entry->flags & mmz_Flag_StrongEncrypted)) {
log_write("[ZIP] encrypted zip not supported\n"); log_write("[ZIP] encrypted zip not supported\n");
return set_errno(r, ENOENT); return -ENOENT;
} }
if (entry->compression_type != mmz_Compression_None && entry->compression_type != mmz_Compression_Deflate) { if (entry->compression_type != mmz_Compression_None && entry->compression_type != mmz_Compression_Deflate) {
log_write("[ZIP] unsuported compression type: %u\n", entry->compression_type); log_write("[ZIP] unsuported compression type: %u\n", entry->compression_type);
return set_errno(r, ENOENT); return -ENOENT;
} }
mmz_LocalHeader local_hdr{}; mmz_LocalHeader local_hdr{};
auto offset = entry->local_file_header_off; auto offset = entry->local_file_header_off;
if (R_FAILED(device->source->Read2(&local_hdr, offset, sizeof(local_hdr)))) { if (R_FAILED(this->source->Read2(&local_hdr, offset, sizeof(local_hdr)))) {
return set_errno(r, ENOENT); return -ENOENT;
} }
if (local_hdr.sig != LOCAL_HEADER_SIG) { if (local_hdr.sig != LOCAL_HEADER_SIG) {
return set_errno(r, ENOENT); return -ENOENT;
} }
offset += sizeof(local_hdr) + local_hdr.filename_len + local_hdr.extrafield_len; offset += sizeof(local_hdr) + local_hdr.filename_len + local_hdr.extrafield_len;
@@ -239,12 +245,12 @@ int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int fla
// todo: does a decs take prio over file header? // todo: does a decs take prio over file header?
if (local_hdr.flags & mmz_Flag_DataDescriptor) { if (local_hdr.flags & mmz_Flag_DataDescriptor) {
mmz_DataDescriptor data_desc{}; mmz_DataDescriptor data_desc{};
if (R_FAILED(device->source->Read2(&data_desc, offset, sizeof(data_desc)))) { if (R_FAILED(this->source->Read2(&data_desc, offset, sizeof(data_desc)))) {
return set_errno(r, ENOENT); return -ENOENT;
} }
if (data_desc.sig != DATA_DESCRIPTOR_SIG) { if (data_desc.sig != DATA_DESCRIPTOR_SIG) {
return set_errno(r, ENOENT); return -ENOENT;
} }
offset += sizeof(data_desc); offset += sizeof(data_desc);
@@ -253,41 +259,39 @@ int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int fla
if (entry->compression_type == mmz_Compression_Deflate) { if (entry->compression_type == mmz_Compression_Deflate) {
auto& zfile = file->zfile; auto& zfile = file->zfile;
zfile.buffer_size = 1024 * 64; zfile.buffer_size = 1024 * 64;
zfile.buffer = (Bytef*)calloc(1, zfile.buffer_size); zfile.buffer = (Bytef*)std::calloc(1, zfile.buffer_size);
if (!zfile.buffer) { if (!zfile.buffer) {
return set_errno(r, ENOENT); return -ENOENT;
} }
// skip zlib header. // skip zlib header.
if (Z_OK != inflateInit2(&zfile.z, -MAX_WBITS)) { if (Z_OK != inflateInit2(&zfile.z, -MAX_WBITS)) {
free(zfile.buffer); std::free(zfile.buffer);
zfile.buffer = nullptr; zfile.buffer = nullptr;
return set_errno(r, ENOENT); return -ENOENT;
} }
} }
file->device = device;
file->entry = entry; file->entry = entry;
file->data_off = offset; file->data_off = offset;
return r->_errno = 0; return 0;
} }
int devoptab_close(struct _reent *r, void *fd) { int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
if (file->entry->compression_type == mmz_Compression_Deflate) { if (file->entry->compression_type == mmz_Compression_Deflate) {
inflateEnd(&file->zfile.z); inflateEnd(&file->zfile.z);
if (file->zfile.buffer) { if (file->zfile.buffer) {
free(file->zfile.buffer); std::free(file->zfile.buffer);
} }
} }
std::memset(file, 0, sizeof(*file)); return 0;
return r->_errno = 0;
} }
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) { ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
len = std::min(len, file->entry->uncompressed_size - file->off); len = std::min(len, file->entry->uncompressed_size - file->off);
@@ -296,8 +300,8 @@ ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
} }
if (file->entry->compression_type == mmz_Compression_None) { if (file->entry->compression_type == mmz_Compression_None) {
if (R_FAILED(file->device->source->Read2(ptr, file->data_off + file->off, len))) { if (R_FAILED(this->source->Read2(ptr, file->data_off + file->off, len))) {
return set_errno(r, ENOENT); return -ENOENT;
} }
} else if (file->entry->compression_type == mmz_Compression_Deflate) { } else if (file->entry->compression_type == mmz_Compression_Deflate) {
auto& zfile = file->zfile; auto& zfile = file->zfile;
@@ -309,8 +313,8 @@ ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
// check if we need to fetch more data. // check if we need to fetch more data.
if (!zfile.z.next_in || !zfile.z.avail_in) { if (!zfile.z.next_in || !zfile.z.avail_in) {
const auto clen = std::min(zfile.buffer_size, file->entry->compressed_size - zfile.compressed_off); const auto clen = std::min(zfile.buffer_size, file->entry->compressed_size - zfile.compressed_off);
if (R_FAILED(file->device->source->Read2(zfile.buffer, file->data_off + zfile.compressed_off, clen))) { if (R_FAILED(this->source->Read2(zfile.buffer, file->data_off + zfile.compressed_off, clen))) {
return set_errno(r, ENOENT); return -ENOENT;
} }
zfile.compressed_off += clen; zfile.compressed_off += clen;
@@ -324,7 +328,7 @@ ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
len -= zfile.z.avail_out; len -= zfile.z.avail_out;
} else { } else {
log_write("[ZLIB] failed to inflate: %d %s\n", rc, zfile.z.msg); log_write("[ZLIB] failed to inflate: %d %s\n", rc, zfile.z.msg);
return set_errno(r, ENOENT); return -ENOENT;
} }
} }
} }
@@ -334,7 +338,7 @@ ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
return len; return len;
} }
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) { off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
// seek like normal. // seek like normal.
@@ -365,58 +369,44 @@ off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
} }
} }
r->_errno = 0;
return file->off = std::clamp<u64>(pos, 0, file->entry->uncompressed_size); return file->off = std::clamp<u64>(pos, 0, file->entry->uncompressed_size);
} }
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) { int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd); auto file = static_cast<File*>(fd);
std::memset(st, 0, sizeof(*st));
set_stat_file(file->entry, st); set_stat_file(file->entry, st);
return r->_errno = 0; return 0;
} }
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) { int Device::devoptab_diropen(void* fd, const char *path) {
auto device = (Device*)r->deviceData; auto dir = static_cast<Dir*>(fd);
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(dir, 0, sizeof(*dir));
char path[PATH_MAX]; const auto entry = find_dir_entry(this->root, path);
if (!common::fix_path(_path, path)) {
set_errno(r, ENOENT);
return NULL;
}
const auto entry = find_dir_entry(device->root, path);
if (!entry) { if (!entry) {
set_errno(r, ENOENT); return -ENOENT;
return NULL;
} }
dir->device = device;
dir->entry = entry; dir->entry = entry;
r->_errno = 0; return 0;
return dirState;
} }
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
dir->index = 0; dir->index = 0;
return r->_errno = 0; return 0;
} }
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(filestat, 0, sizeof(*filestat));
u32 index = dir->index; u32 index = dir->index;
if (index >= dir->entry->dir_child.size()) { if (index >= dir->entry->dir_child.size()) {
index -= dir->entry->dir_child.size(); index -= dir->entry->dir_child.size();
if (index >= dir->entry->file_child.size()) { if (index >= dir->entry->file_child.size()) {
return set_errno(r, ENOENT); return -ENOENT;
} else { } else {
const auto& entry = dir->entry->file_child[index]; const auto& entry = dir->entry->file_child[index];
const auto rel_path = entry.path.substr(entry.path.find_last_of('/') + 1); const auto rel_path = entry.path.substr(entry.path.find_last_of('/') + 1);
@@ -434,59 +424,31 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
} }
dir->index++; dir->index++;
return r->_errno = 0; return 0;
} }
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) { int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(dirState->dirStruct); auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir)); std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0; return 0;
} }
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
auto device = (Device*)r->deviceData;
if (!device) {
return set_errno(r, ENOENT);
}
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
}
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
if (find_dir_entry(device->root, path)) { if (find_dir_entry(this->root, path)) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else if (auto entry = find_file_entry(device->root, path)) { } else if (auto entry = find_file_entry(this->root, path)) {
set_stat_file(entry, st); set_stat_file(entry, st);
} else { } else {
log_write("[ZIP] didn't find in lstat\n"); log_write("[ZIP] didn't find in lstat\n");
return set_errno(r, ENOENT); return -ENOENT;
} }
return r->_errno = 0; return 0;
} }
constexpr devoptab_t DEVOPTAB = {
.structSize = sizeof(File),
.open_r = devoptab_open,
.close_r = devoptab_close,
.read_r = devoptab_read,
.seek_r = devoptab_seek,
.fstat_r = devoptab_fstat,
.stat_r = devoptab_lstat,
.dirStateSize = sizeof(Dir),
.diropen_r = devoptab_diropen,
.dirreset_r = devoptab_dirreset,
.dirnext_r = devoptab_dirnext,
.dirclose_r = devoptab_dirclose,
.lstat_r = devoptab_lstat,
};
auto BuildPath(const std::string& path) -> std::string { auto BuildPath(const std::string& path) -> std::string {
if (path.starts_with('/')) { if (path.starts_with('/')) {
return path; return path;
@@ -600,48 +562,13 @@ Result ParseZip(common::LruBufferedData* source, s64 size, FileTableEntries& out
R_SUCCEED(); R_SUCCEED();
} }
struct Entry {
Device device{};
devoptab_t devoptab{};
fs::FsPath path{};
fs::FsPath mount{};
char name[32]{};
s32 ref_count{};
~Entry() {
RemoveDevice(mount);
}
};
Mutex g_mutex;
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
} // namespace } // namespace
Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) { Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
SCOPED_MUTEX(&g_mutex);
// check if we already have the save mounted.
for (auto& e : g_entries) {
if (e && e->path == path) {
e->ref_count++;
out_path = e->mount;
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_shared<yati::source::File>(fs, path); auto source = std::make_shared<yati::source::File>(fs, path);
s64 size; s64 size;
R_TRY(source->GetSize(&size)); R_TRY(source->GetSize(&size));
auto buffered = std::make_unique<common::LruBufferedData>(source, size); auto buffered = std::make_unique<common::LruBufferedData>(source, size);
FileTableEntries table_entries; FileTableEntries table_entries;
@@ -651,44 +578,18 @@ 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 = std::make_unique<Entry>(); if (!common::MountReadOnlyIndexDevice(
entry->path = path; [&buffered, &root](const common::MountConfig& config) {
entry->devoptab = DEVOPTAB; return std::make_unique<Device>(std::move(buffered), root, config);
entry->devoptab.name = entry->name; },
entry->devoptab.deviceData = &entry->device; sizeof(File), sizeof(Dir),
entry->device.source = std::move(buffered); "ZIP", out_path
entry->device.root = root; )) {
std::snprintf(entry->name, sizeof(entry->name), "zip_%zu", index); log_write("[ZIP] Failed to mount %s\n", path.s);
std::snprintf(entry->mount, sizeof(entry->mount), "zip_%zu:/", index); R_THROW(0x1);
}
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[ZIP] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry->mount;
entry->ref_count++;
*itr = std::move(entry);
R_SUCCEED(); R_SUCCEED();
} }
void UmountZip(const fs::FsPath& mount) {
SCOPED_MUTEX(&g_mutex);
auto itr = std::ranges::find_if(g_entries, [&mount](auto& e){
return e && e->mount == mount;
});
if (itr == g_entries.end()) {
return;
}
if ((*itr)->ref_count) {
(*itr)->ref_count--;
}
if (!(*itr)->ref_count) {
itr->reset();
}
}
} // namespace sphaira::devoptab } // namespace sphaira::devoptab