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 {
// mounts to "lower_case_hex_id:/"
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);
void UmountZip(const fs::FsPath& mount);
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 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 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);
void UmountBfsar(const fs::FsPath& mount);
Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
void UmountNro(const fs::FsPath& mount);
Result MountVfsAll();
Result MountWebdavAll();
@@ -43,5 +29,6 @@ Result MountFatfsAll();
Result GetNetworkDevices(location::StdioEntries& out);
void UmountAllNeworkDevices();
void UmountNeworkDevice(const fs::FsPath& mount);
} // namespace sphaira::devoptab

View File

@@ -185,7 +185,6 @@ struct MountDevice {
struct MountCurlDevice : MountDevice {
using MountDevice::MountDevice;
// MountCurlDevice(const MountConfig& _config);
virtual ~MountCurlDevice();
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.
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

View File

@@ -711,15 +711,15 @@ void FsView::OnClick() {
}
});
} else if (IsExtension(entry.GetExtension(), NCA_EXTENSIONS)) {
MountFileFs(devoptab::MountNca, devoptab::UmountNca);
MountFileFs(devoptab::MountNca, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), NSP_EXTENSIONS)) {
MountFileFs(devoptab::MountNsp, devoptab::UmountNsp);
MountFileFs(devoptab::MountNsp, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), XCI_EXTENSIONS)) {
MountFileFs(devoptab::MountXci, devoptab::UmountXci);
MountFileFs(devoptab::MountXci, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), "zip")) {
MountFileFs(devoptab::MountZip, devoptab::UmountZip);
MountFileFs(devoptab::MountZip, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), "bfsar")) {
MountFileFs(devoptab::MountBfsar, devoptab::UmountBfsar);
MountFileFs(devoptab::MountBfsar, devoptab::UmountNeworkDevice);
} else if (IsExtension(entry.GetExtension(), MUSIC_EXTENSIONS)) {
App::Push<music::Menu>(GetFs(), GetNewPathCurrent());
} 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));
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){
devoptab::UmountNca(root);
devoptab::UmountNeworkDevice(root);
});
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));
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){
devoptab::UmountXci(root);
devoptab::UmountNeworkDevice(root);
});
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));
auto fs = std::make_shared<filebrowser::FsStdioWrapper>(root, [root](){
devoptab::UmountNro(root);
devoptab::UmountNeworkDevice(root);
});
filebrowser::MountFsHelper(fs, root);

View File

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

View File

@@ -4,8 +4,6 @@
#include "defines.hpp"
#include "log.hpp"
#include "yati/container/nsp.hpp"
#include "yati/container/xci.hpp"
#include "yati/source/file.hpp"
#include <pulsar.h>
@@ -15,24 +13,16 @@
#include <array>
#include <memory>
#include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab {
namespace {
struct Device {
PLSR_BFSAR bfsar;
std::FILE* file; // points to archive file.
};
struct File {
Device* device;
PLSR_BFWARFileInfo info;
size_t off;
};
struct Dir {
Device* device;
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) {
r->_errno = err;
return -1;
}
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);
struct Device final : common::MountDevice {
Device(const PLSR_BFSAR& _bfsar, const common::MountConfig& _config)
: MountDevice{_config}
, bfsar{_bfsar} {
this->file = this->bfsar.ar.handle->f;
}
~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;
if (R_FAILED(GetFileInfo(&device->bfsar, path, info))) {
return set_errno(r, ENOENT);
if (R_FAILED(GetFileInfo(&this->bfsar, path, info))) {
return -ENOENT;
}
file->device = device;
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);
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);
const auto& info = file->info;
// const auto real_len = len;
// plsr seems to read oob, so allow for some tollerance.
const auto oob_allowed = 64;
len = std::min(len, info.size + oob_allowed - file->off);
std::fseek(file->device->file, file->info.offset + file->off, SEEK_SET);
const auto bytes_read = std::fread(ptr, 1, len, file->device->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);
std::fseek(this->file, file->info.offset + file->off, SEEK_SET);
const auto bytes_read = std::fread(ptr, 1, len, this->file);
file->off += 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);
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;
}
r->_errno = 0;
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);
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_size = info.size;
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) {
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;
}
int Device::devoptab_diropen(void* fd, const char *path) {
if (!std::strcmp(path, "/")) {
dir->device = device;
} else {
set_errno(r, ENOENT);
return NULL;
return 0;
}
r->_errno = 0;
return dirState;
return -ENOENT;
}
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
dir->index = 0;
return r->_errno = 0;
return 0;
}
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(filestat, 0, sizeof(*filestat));
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(fd);
do {
if (dir->index >= plsrBFSARSoundCount(&dir->device->bfsar)) {
log_write("finished getting call entries: %u vs %u\n", dir->index, plsrBFSARSoundCount(&dir->device->bfsar));
return set_errno(r, ENOENT);
if (dir->index >= plsrBFSARSoundCount(&this->bfsar)) {
log_write("finished getting call entries: %u vs %u\n", dir->index, plsrBFSARSoundCount(&this->bfsar));
return -ENOENT;
}
PLSR_BFSARSoundInfo info{};
if (R_FAILED(plsrBFSARSoundGet(&dir->device->bfsar, dir->index, &info))) {
if (R_FAILED(plsrBFSARSoundGet(&this->bfsar, dir->index, &info))) {
continue;
}
@@ -206,7 +193,7 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
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;
}
@@ -230,139 +217,52 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
break;
} while (dir->index++);
return r->_errno = 0;
return 0;
}
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir));
log_write("[BFSAR] devoptab_dirclose\n");
return r->_errno = 0;
return 0;
}
int devoptab_lstat(struct _reent *r, 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));
int Device::devoptab_lstat(const char *path, struct stat *st) {
st->st_nlink = 1;
if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else {
PLSR_BFWARFileInfo info{};
if (R_FAILED(GetFileInfo(&device->bfsar, path, info))) {
return set_errno(r, ENOENT);
if (R_FAILED(GetFileInfo(&this->bfsar, path, info))) {
return -ENOENT;
}
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
st->st_size = info.size;
}
st->st_nlink = 1;
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() {
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
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.
for (auto& e : g_entries) {
if (e && e->path == path) {
e->ref_count++;
out_path = e->mount;
R_SUCCEED();
}
if (!common::MountReadOnlyIndexDevice(
[&bfsar](const common::MountConfig& config) {
return std::make_unique<Device>(bfsar, config);
},
sizeof(File), sizeof(Dir),
"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();
}
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

View File

@@ -820,6 +820,39 @@ bool MountNetworkDevice2(std::unique_ptr<MountDevice>&& device, const MountConfi
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) {
{
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

View File

@@ -428,7 +428,6 @@ Result MountFatfsAll() {
const auto& bis = BIS_MOUNT_ENTRIES[i];
common::MountConfig config{};
config.url = "fatfs dummy url";
config.read_only = true;
config.dump_hidden = true;
@@ -457,10 +456,7 @@ const char* VolumeStr[] {
};
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];
log_write("[FAT] buffered: %p\n", fat.buffered.get());
return fat.buffered->Read2(dst, offset, size);
}

View File

@@ -20,7 +20,6 @@
#include <array>
#include <memory>
#include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab {
namespace {
@@ -74,25 +73,18 @@ struct DirEntry {
const yati::container::Collections* pfs0;
};
struct Device {
std::vector<NamedCollection> collections;
std::unique_ptr<yati::source::Base> source;
};
struct File {
Device* device;
FileEntry entry;
size_t off;
};
struct Dir {
Device* device;
DirEntry entry;
u32 index;
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) {
if (path.starts_with("/" + e.name)) {
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;
}
int set_errno(struct _reent *r, int err) {
r->_errno = err;
return -1;
}
struct Device final : common::MountDevice {
Device(std::unique_ptr<yati::source::Base>&& _source, const std::vector<NamedCollection>& _collections, const common::MountConfig& _config)
: 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);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
FileEntry entry{};
if (!find_file(this->collections, path, entry)) {
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;
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);
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);
const auto& entry = file->entry;
u64 bytes_read;
len = std::min(len, entry.size - file->off);
if (R_FAILED(file->device->source->Read(ptr, entry.offset + file->off, len, &bytes_read))) {
return set_errno(r, ENOENT);
if (R_FAILED(this->source->Read(ptr, entry.offset + file->off, len, &bytes_read))) {
return -EIO;
}
file->off += 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);
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;
}
r->_errno = 0;
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);
const auto& entry = file->entry;
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1;
st->st_size = entry.size;
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) {
auto device = (Device*)r->deviceData;
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_diropen(void* fd, const char *path) {
auto dir = static_cast<Dir*>(fd);
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, "/")) {
dir->device = device;
dir->is_root = true;
r->_errno = 0;
return dirState;
return 0;
} else {
DirEntry entry;
if (!find_dir(device->collections, path, entry)) {
set_errno(r, ENOENT);
return NULL;
DirEntry entry{};
if (!find_dir(this->collections, path, entry)) {
return -ENOENT;
}
dir->device = device;
dir->entry = entry;
r->_errno = 0;
return dirState;
return 0;
}
}
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
auto& entry = dir->entry;
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) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(fd);
auto& entry = dir->entry;
std::memset(filestat, 0, sizeof(*filestat));
if (dir->is_root) {
if (dir->index >= dir->device->collections.size()) {
return set_errno(r, ENOENT);
if (dir->index >= this->collections.size()) {
return -ENOENT;
}
filestat->st_nlink = 1;
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 {
if (entry.fs_type == nca::FileSystemType_RomFS) {
if (!romfs::dirnext(entry.romfs, filename, filestat)) {
return set_errno(r, ENOENT);
return -ENOENT;
}
} else {
if (dir->index >= entry.pfs0->size()) {
return set_errno(r, ENOENT);
return -ENOENT;
}
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++;
return r->_errno = 0;
return 0;
}
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0;
return 0;
}
int devoptab_lstat(struct _reent *r, 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));
int Device::devoptab_lstat(const char *path, struct stat *st) {
st->st_nlink = 1;
if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else {
// can be optimised for romfs.
FileEntry file_entry;
DirEntry dir_entry;
if (find_file(device->collections, path, file_entry)) {
FileEntry file_entry{};
DirEntry dir_entry{};
if (find_file(this->collections, path, file_entry)) {
st->st_size = file_entry.size;
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;
} else {
return set_errno(r, ENOENT);
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;
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;
return 0;
}
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
// decrypt the nca for use (somehow, look how ams does it?).
keys::Keys keys;
R_TRY(keys::parse_keys(keys, true));
nca::Header header;
nca::Header header{};
R_TRY(source->Read2(&header, 0, sizeof(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));
// 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];
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;
}
NamedCollection collection;
NamedCollection collection{};
collection.name = content_type_fs[i].name;
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);
auto entry = std::make_unique<Entry>();
entry->path = path;
entry->devoptab = DEVOPTAB;
entry->devoptab.name = entry->name;
entry->devoptab.deviceData = &entry->device;
entry->device.source = std::move(nca_reader);
entry->device.collections = std::move(collections);
std::snprintf(entry->name, sizeof(entry->name), "nca_%zu", index);
std::snprintf(entry->mount, sizeof(entry->mount), "nca_%zu:/", index);
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);
if (!common::MountReadOnlyIndexDevice(
[&nca_reader, &collections](const common::MountConfig& config) {
return std::make_unique<Device>(std::move(nca_reader), collections, config);
},
sizeof(File), sizeof(Dir),
"NCA", out_path
)) {
log_write("[NCA] Failed to mount %s\n", path.s);
R_THROW(0x1);
}
R_SUCCEED();
}
@@ -558,12 +481,6 @@ Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& s
} // namespace
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;
auto source = std::make_shared<yati::source::File>(fs, path);
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) {
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;
auto source = std::make_shared<ncm::NcmSource>(cs, id);
R_TRY(source->GetSize(&size));
return MountNcaInternal(nullptr, source, size, path, 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();
}
return MountNcaInternal(nullptr, source, size, {}, out_path);
}
} // namespace sphaira::devoptab

View File

@@ -6,11 +6,6 @@
#include "log.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 <cstring>
@@ -18,7 +13,6 @@
#include <array>
#include <memory>
#include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab {
namespace {
@@ -48,26 +42,18 @@ struct DirEntry {
romfs::DirEntry romfs;
};
struct Device {
std::unique_ptr<yati::source::Base> source;
std::vector<NamedCollection> collections;
FsTimeStampRaw timestamp;
};
struct File {
Device* device;
FileEntry entry;
size_t off;
};
struct Dir {
Device* device;
DirEntry entry;
u32 index;
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) {
if (path.starts_with("/" + e.name)) {
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;
}
void fill_timestamp_from_device(const Device* device, struct stat *st) {
st->st_atime = device->timestamp.accessed;
st->st_ctime = device->timestamp.created;
st->st_mtime = device->timestamp.modified;
void fill_timestamp_from_device(const FsTimeStampRaw& timestamp, struct stat *st) {
st->st_atime = timestamp.accessed;
st->st_ctime = timestamp.created;
st->st_mtime = timestamp.modified;
}
int set_errno(struct _reent *r, int err) {
r->_errno = err;
return -1;
}
struct Device final : common::MountDevice {
Device(std::unique_ptr<yati::source::Base>&& _source, const std::vector<NamedCollection>& _collections, const FsTimeStampRaw& _timestamp, const common::MountConfig& _config)
: 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);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
FileEntry entry{};
if (!find_file(this->collections, path, entry)) {
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;
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);
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);
const auto& entry = file->entry;
u64 bytes_read;
len = std::min(len, entry.size - file->off);
if (R_FAILED(file->device->source->Read(ptr, entry.offset + file->off, len, &bytes_read))) {
return set_errno(r, ENOENT);
if (R_FAILED(this->source->Read(ptr, entry.offset + file->off, len, &bytes_read))) {
return -EIO;
}
file->off += 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);
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;
}
r->_errno = 0;
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);
const auto& entry = file->entry;
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1;
st->st_size = entry.size;
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) {
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;
}
int Device::devoptab_diropen(void* fd, const char *path) {
auto dir = static_cast<Dir*>(fd);
if (!std::strcmp(path, "/")) {
dir->device = device;
dir->is_root = true;
r->_errno = 0;
return dirState;
return 0;
} else {
DirEntry entry;
if (!find_dir(device->collections, path, entry)) {
set_errno(r, ENOENT);
return NULL;
DirEntry entry{};
if (!find_dir(this->collections, path, entry)) {
return -ENOENT;
}
dir->device = device;
dir->entry = entry;
r->_errno = 0;
return dirState;
return 0;
}
}
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
auto& entry = dir->entry;
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) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(fd);
auto& entry = dir->entry;
std::memset(filestat, 0, sizeof(*filestat));
if (dir->is_root) {
if (dir->index >= dir->device->collections.size()) {
return set_errno(r, ENOENT);
if (dir->index >= this->collections.size()) {
return -ENOENT;
}
const auto& e = dir->device->collections[dir->index];
const auto& e = this->collections[dir->index];
if (e.is_romfs) {
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else {
@@ -262,126 +245,60 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
} else {
if (entry.is_romfs) {
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++;
return r->_errno = 0;
return 0;
}
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0;
return 0;
}
int devoptab_lstat(struct _reent *r, 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));
int Device::devoptab_lstat(const char *path, struct stat *st) {
st->st_nlink = 1;
if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else {
// can be optimised for romfs.
FileEntry file_entry;
DirEntry dir_entry;
if (find_file(device->collections, path, file_entry)) {
FileEntry file_entry{};
DirEntry dir_entry{};
if (find_file(this->collections, path, file_entry)) {
st->st_size = file_entry.size;
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;
} else {
return set_errno(r, ENOENT);
return -ENOENT;
}
}
fill_timestamp_from_device(device, st);
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;
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;
fill_timestamp_from_device(this->timestamp, st);
return 0;
}
} // namespace
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);
NroData data;
NroData data{};
R_TRY(source->Read2(&data, 0, sizeof(data)));
R_UNLESS(data.header.magic == NROHEADER_MAGIC, Result_NroBadMagic);
NroAssetHeader asset;
NroAssetHeader asset{};
R_TRY(source->Read2(&asset, data.header.size, sizeof(asset)));
R_UNLESS(asset.magic == NROASSETHEADER_MAGIC, Result_NroBadMagic);
std::vector<NamedCollection> collections;
std::vector<NamedCollection> collections{};
if (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);
auto entry = std::make_unique<Entry>();
entry->path = path;
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);
FsTimeStampRaw timestamp{};
fs->GetFileTimeStampRaw(path, &timestamp);
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
log_write("[NRO] DEVICE SUCCESS %s %s\n", path.s, entry->name);
out_path = entry->mount;
entry->ref_count++;
*itr = std::move(entry);
if (!common::MountReadOnlyIndexDevice(
[&source, &collections, &timestamp](const common::MountConfig& config) {
return std::make_unique<Device>(std::move(source), collections, timestamp, config);
},
sizeof(File), sizeof(Dir),
"NRO", out_path
)) {
log_write("[NRO] Failed to mount %s\n", path.s);
R_THROW(0x1);
}
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

View File

@@ -13,76 +13,84 @@
#include <array>
#include <memory>
#include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab {
namespace {
struct Device {
std::unique_ptr<common::LruBufferedData> source;
yati::container::Collections collections;
};
using Collections = yati::container::Collections;
struct File {
Device* device;
const yati::container::CollectionEntry* collection;
size_t off;
};
struct Dir {
Device* device;
u32 index;
};
int set_errno(struct _reent *r, int err) {
r->_errno = err;
return -1;
}
struct Device final : common::MountDevice {
Device(std::unique_ptr<common::LruBufferedData>&& _source, const Collections& _collections, const common::MountConfig& _config)
: 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) {
file->device = device;
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);
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);
const auto& collection = file->collection;
len = std::min(len, collection->size - file->off);
u64 bytes_read;
if (R_FAILED(file->device->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
return set_errno(r, ENOENT);
if (R_FAILED(this->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
return -EIO;
}
file->off += 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);
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;
}
r->_errno = 0;
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);
const auto& collection = file->collection;
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1;
st->st_size = collection->size;
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) {
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;
}
int Device::devoptab_diropen(void* fd, const char *path) {
if (!std::strcmp(path, "/")) {
dir->device = device;
} else {
set_errno(r, ENOENT);
return NULL;
return 0;
}
r->_errno = 0;
return dirState;
log_write("[NSP] failed to open dir %s\n", path);
return -ENOENT;
}
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
dir->index = 0;
return r->_errno = 0;
return 0;
}
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(filestat, 0, sizeof(*filestat));
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(fd);
if (dir->index >= dir->device->collections.size()) {
return set_errno(r, ENOENT);
if (dir->index >= this->collections.size()) {
return -ENOENT;
}
const auto& collection = dir->device->collections[dir->index];
const auto& collection = this->collections[dir->index];
filestat->st_nlink = 1;
filestat->st_size = collection.size;
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
std::strcpy(filename, collection.name.c_str());
dir->index++;
return r->_errno = 0;
return 0;
}
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0;
return 0;
}
int devoptab_lstat(struct _reent *r, 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));
int Device::devoptab_lstat(const char *path, struct stat *st) {
st->st_nlink = 1;
if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} 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;
});
if (it == device->collections.end()) {
return set_errno(r, ENOENT);
if (it == this->collections.end()) {
return -ENOENT;
}
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
st->st_size = it->size;
}
st->st_nlink = 1;
return r->_errno = 0;
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;
} // namespace
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);
s64 size;
@@ -255,44 +187,18 @@ Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
yati::container::Collections collections;
R_TRY(nsp.GetCollections(collections));
auto entry = std::make_unique<Entry>();
entry->path = path;
entry->devoptab = DEVOPTAB;
entry->devoptab.name = entry->name;
entry->devoptab.deviceData = &entry->device;
entry->device.source = std::move(buffered);
entry->device.collections = collections;
std::snprintf(entry->name, sizeof(entry->name), "nsp_%zu", index);
std::snprintf(entry->mount, sizeof(entry->mount), "nsp_%zu:/", index);
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);
if (!common::MountReadOnlyIndexDevice(
[&buffered, &collections](const common::MountConfig& config) {
return std::make_unique<Device>(std::move(buffered), collections, config);
},
sizeof(File), sizeof(Dir),
"NSP", out_path
)) {
log_write("[NSP] Failed to mount %s\n", path.s);
R_THROW(0x1);
}
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

View File

@@ -12,18 +12,11 @@
#include <array>
#include <memory>
#include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab {
namespace {
struct Device {
save_ctx_t* ctx;
hierarchical_save_file_table_ctx_t* file_table;
};
struct File {
Device* device;
save_fs_list_entry_t entry;
allocation_table_storage_ctx_t storage;
size_t off;
@@ -35,138 +28,140 @@ struct DirNext {
};
struct Dir {
Device* device;
save_fs_list_entry_t entry;
u32 next_directory;
u32 next_file;
};
int set_errno(struct _reent *r, int err) {
r->_errno = err;
return -1;
}
struct Device final : common::MountDevice {
Device(save_ctx_t* _ctx, const common::MountConfig& _config)
: 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) {
auto device = (Device*)r->deviceData;
~Device() {
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);
std::memset(file, 0, sizeof(*file));
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
return set_errno(r, ENOENT);
if (!save_hierarchical_file_table_get_file_entry_by_path(this->file_table, path, &file->entry)) {
return -ENOENT;
}
if (!save_hierarchical_file_table_get_file_entry_by_path(device->file_table, path, &file->entry)) {
return set_errno(r, ENOENT);
if (!save_open_fat_storage(&this->ctx->save_filesystem_core, &file->storage, file->entry.value.save_file_info.start_block)) {
return -ENOENT;
}
if (!save_open_fat_storage(&device->ctx->save_filesystem_core, &file->storage, file->entry.value.save_file_info.start_block)) {
return set_errno(r, ENOENT);
}
file->device = device;
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);
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);
len = std::min(len, file->entry.value.save_file_info.length - file->off);
if (!len) {
return 0;
}
// todo: maybe eof here?
const auto bytes_read = save_allocation_table_storage_read(&file->storage, ptr, file->off, len);
if (!bytes_read) {
return set_errno(r, ENOENT);
return -ENOENT;
}
file->off += 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);
if (dir == SEEK_CUR) {
pos += file->off;
} 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->storage._length);
return file->off = std::clamp<u64>(pos, 0, file->entry.value.save_file_info.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);
log_write("[\t\tDEV] fstat\n");
std::memset(st, 0, sizeof(*st));
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;
return r->_errno = 0;
return 0;
}
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, 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;
}
int Device::devoptab_diropen(void* fd, const char *path) {
auto dir = static_cast<Dir*>(fd);
if (!std::strcmp(path, "/")) {
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) {
set_errno(r, ENOENT);
return NULL;
return -ENOENT;
}
if (!save_fs_list_get_value(&device->file_table->directory_table, idx, &dir->entry)) {
set_errno(r, ENOENT);
return NULL;
if (!save_fs_list_get_value(&this->file_table->directory_table, idx, &dir->entry)) {
return -ENOENT;
}
} else if (!save_hierarchical_directory_table_get_file_entry_by_path(device->file_table, path, &dir->entry)) {
set_errno(r, ENOENT);
return NULL;
} else if (!save_hierarchical_directory_table_get_file_entry_by_path(this->file_table, path, &dir->entry)) {
return -ENOENT;
}
dir->device = device;
dir->next_file = dir->entry.value.save_find_position.next_file;
dir->next_directory = dir->entry.value.save_find_position.next_directory;
r->_errno = 0;
return dirState;
return 0;
}
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
dir->next_file = dir->entry.value.save_find_position.next_file;
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) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(filestat, 0, sizeof(*filestat));
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(fd);
save_fs_list_entry_t entry{};
if (dir->next_directory) {
// 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)) {
return set_errno(r, ENOENT);
if (!save_fs_list_get_value(&this->file_table->directory_table, dir->next_directory, &entry)) {
return -ENOENT;
}
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) {
// 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)) {
return set_errno(r, ENOENT);
if (!save_fs_list_get_value(&this->file_table->file_table, dir->next_file, &entry)) {
return -ENOENT;
}
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;
}
else {
return set_errno(r, ENOENT);
return -ENOENT;
}
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) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0;
return 0;
}
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
auto device = (Device*)r->deviceData;
int Device::devoptab_lstat(const char *path, struct stat *st) {
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{};
// 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_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;
} else {
return set_errno(r, ENOENT);
return -ENOENT;
}
st->st_nlink = 1;
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{};
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
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.
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];
fs::FsPath path{};
std::snprintf(path, sizeof(path), "SYSTEM:/save/%016lx", id);
auto ctx = save_open_savefile(path, 0);
@@ -288,46 +225,18 @@ Result MountSaveSystem(u64 id, fs::FsPath& out_path) {
R_THROW(0x1);
}
log_write("[SAVE] OPEN SUCCESS %s\n", path);
auto entry = std::make_unique<Entry>();
entry->id = id;
entry->device.ctx = ctx;
entry->device.file_table = &ctx->save_filesystem_core.file_table;
entry->devoptab = DEVOPTAB;
entry->devoptab.name = entry->name;
entry->devoptab.deviceData = &entry->device;
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);
if (!common::MountReadOnlyIndexDevice(
[&ctx](const common::MountConfig& config) {
return std::make_unique<Device>(ctx, config);
},
sizeof(File), sizeof(Dir),
"SAVE", out_path
)) {
log_write("[SAVE] Failed to mount %s\n", path.s);
R_THROW(0x1);
}
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

View File

@@ -12,79 +12,85 @@
#include <array>
#include <memory>
#include <algorithm>
#include <sys/iosupport.h>
namespace sphaira::devoptab {
namespace {
struct Device {
std::unique_ptr<common::LruBufferedData> source;
yati::container::Xci::Partitions partitions;
};
struct File {
Device* device;
const yati::container::CollectionEntry* collection;
size_t off;
};
struct Dir {
Device* device;
const yati::container::Collections* collections;
u32 index;
};
int set_errno(struct _reent *r, int err) {
r->_errno = err;
return -1;
}
struct Device final : common::MountDevice {
Device(std::unique_ptr<common::LruBufferedData>&& _source, const yati::container::Xci::Partitions& _partitions, const common::MountConfig& _config)
: 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) {
if (path == "/" + partition.name + "/" + collection.name) {
file->device = device;
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);
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);
const auto& collection = file->collection;
len = std::min(len, collection->size - file->off);
u64 bytes_read;
if (R_FAILED(file->device->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
return set_errno(r, ENOENT);
if (R_FAILED(this->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
return -EIO;
}
file->off += 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);
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;
}
r->_errno = 0;
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);
const auto& collection = file->collection;
std::memset(st, 0, sizeof(*st));
st->st_nlink = 1;
st->st_size = collection->size;
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) {
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;
}
int Device::devoptab_diropen(void* fd, const char *path) {
auto dir = static_cast<Dir*>(fd);
if (!std::strcmp(path, "/")) {
dir->device = device;
r->_errno = 0;
return dirState;
return 0;
} else {
for (const auto& partition : device->partitions) {
for (const auto& partition : this->partitions) {
if (path == "/" + partition.name) {
dir->collections = &partition.collections;
r->_errno = 0;
return dirState;
return 0;
}
}
}
set_errno(r, ENOENT);
return NULL;
return -ENOENT;
}
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
dir->index = 0;
return r->_errno = 0;
return 0;
}
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(filestat, 0, sizeof(*filestat));
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(fd);
if (!dir->collections) {
if (dir->index >= dir->device->partitions.size()) {
return set_errno(r, ENOENT);
if (dir->index >= this->partitions.size()) {
return -ENOENT;
}
filestat->st_nlink = 1;
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 {
if (dir->index >= dir->collections->size()) {
return set_errno(r, ENOENT);
return -ENOENT;
}
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++;
return r->_errno = 0;
return 0;
}
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0;
return 0;
}
int devoptab_lstat(struct _reent *r, 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));
int Device::devoptab_lstat(const char *path, struct stat *st) {
st->st_nlink = 1;
if (!std::strcmp(path, "/")) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else {
for (const auto& partition : device->partitions) {
for (const auto& partition : this->partitions) {
if (path == "/" + partition.name) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
return r->_errno = 0;
return 0;
}
for (const auto& collection : partition.collections) {
if (path == "/" + partition.name + "/" + collection.name) {
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
st->st_size = collection.size;
return r->_errno = 0;
return 0;
}
}
}
}
return set_errno(r, 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;
return -ENOENT;
}
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);
yati::container::Xci xci{buffered.get()};
yati::container::Xci::Root root;
R_TRY(xci.GetRoot(root));
auto entry = std::make_unique<Entry>();
entry->path = path;
entry->devoptab = DEVOPTAB;
entry->devoptab.name = entry->name;
entry->devoptab.deviceData = &entry->device;
entry->device.source = std::move(buffered);
entry->device.partitions = root.partitions;
std::snprintf(entry->name, sizeof(entry->name), "xci_%zu", index);
std::snprintf(entry->mount, sizeof(entry->mount), "xci_%zu:/", index);
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);
if (!common::MountReadOnlyIndexDevice(
[&buffered, &root](const common::MountConfig& config) {
return std::make_unique<Device>(std::move(buffered), root.partitions, config);
},
sizeof(File), sizeof(Dir),
"XCI", out_path
)) {
log_write("[XCI] Failed to mount %s\n", path.s);
R_THROW(0x1);
}
R_SUCCEED();
}
@@ -305,12 +220,6 @@ Result MountXciInternal(const std::shared_ptr<yati::source::Base>& source, s64 s
} // namespace
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;
auto source = std::make_shared<yati::source::File>(fs, path);
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) {
SCOPED_MUTEX(&g_mutex);
if (IsAlreadyMounted(path, out_path)) {
R_SUCCEED();
}
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

View File

@@ -10,7 +10,6 @@
#include <array>
#include <memory>
#include <algorithm>
#include <sys/iosupport.h>
#include <zlib.h>
namespace sphaira::devoptab {
@@ -113,11 +112,6 @@ struct DirectoryEntry {
using FileTableEntries = std::vector<FileEntry>;
struct Device {
std::unique_ptr<common::LruBufferedData> source;
DirectoryEntry root;
};
struct Zfile {
z_stream z; // zlib stream.
Bytef* buffer; // buffer that compressed data is read into.
@@ -126,7 +120,6 @@ struct Zfile {
};
struct File {
Device* device;
const FileEntry* entry;
Zfile zfile; // only used if the file is compressed.
size_t data_off; // offset of the file data.
@@ -134,7 +127,6 @@ struct File {
};
struct Dir {
Device* device;
const DirectoryEntry* entry;
u32 index;
};
@@ -194,44 +186,58 @@ void set_stat_file(const FileEntry* entry, struct stat *st) {
st->st_ctime = st->st_atime;
}
int set_errno(struct _reent *r, int err) {
r->_errno = err;
return -1;
}
struct Device final : common::MountDevice {
Device(std::unique_ptr<common::LruBufferedData>&& _source, const DirectoryEntry& _root, const common::MountConfig& _config)
: 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) {
return set_errno(r, ENOENT);
return -ENOENT;
}
if ((entry->flags & mmz_Flag_Encrypted) || (entry->flags & mmz_Flag_StrongEncrypted)) {
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) {
log_write("[ZIP] unsuported compression type: %u\n", entry->compression_type);
return set_errno(r, ENOENT);
return -ENOENT;
}
mmz_LocalHeader local_hdr{};
auto offset = entry->local_file_header_off;
if (R_FAILED(device->source->Read2(&local_hdr, offset, sizeof(local_hdr)))) {
return set_errno(r, ENOENT);
if (R_FAILED(this->source->Read2(&local_hdr, offset, sizeof(local_hdr)))) {
return -ENOENT;
}
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;
@@ -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?
if (local_hdr.flags & mmz_Flag_DataDescriptor) {
mmz_DataDescriptor data_desc{};
if (R_FAILED(device->source->Read2(&data_desc, offset, sizeof(data_desc)))) {
return set_errno(r, ENOENT);
if (R_FAILED(this->source->Read2(&data_desc, offset, sizeof(data_desc)))) {
return -ENOENT;
}
if (data_desc.sig != DATA_DESCRIPTOR_SIG) {
return set_errno(r, ENOENT);
return -ENOENT;
}
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) {
auto& zfile = file->zfile;
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) {
return set_errno(r, ENOENT);
return -ENOENT;
}
// skip zlib header.
if (Z_OK != inflateInit2(&zfile.z, -MAX_WBITS)) {
free(zfile.buffer);
std::free(zfile.buffer);
zfile.buffer = nullptr;
return set_errno(r, ENOENT);
return -ENOENT;
}
}
file->device = device;
file->entry = entry;
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);
if (file->entry->compression_type == mmz_Compression_Deflate) {
if (file->entry->compression_type == mmz_Compression_Deflate) {
inflateEnd(&file->zfile.z);
if (file->zfile.buffer) {
free(file->zfile.buffer);
std::free(file->zfile.buffer);
}
}
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);
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 (R_FAILED(file->device->source->Read2(ptr, file->data_off + file->off, len))) {
return set_errno(r, ENOENT);
if (R_FAILED(this->source->Read2(ptr, file->data_off + file->off, len))) {
return -ENOENT;
}
} else if (file->entry->compression_type == mmz_Compression_Deflate) {
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.
if (!zfile.z.next_in || !zfile.z.avail_in) {
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))) {
return set_errno(r, ENOENT);
if (R_FAILED(this->source->Read2(zfile.buffer, file->data_off + zfile.compressed_off, clen))) {
return -ENOENT;
}
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;
} else {
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;
}
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);
// 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);
}
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);
std::memset(st, 0, sizeof(*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) {
auto device = (Device*)r->deviceData;
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(dir, 0, sizeof(*dir));
int Device::devoptab_diropen(void* fd, const char *path) {
auto dir = static_cast<Dir*>(fd);
char path[PATH_MAX];
if (!common::fix_path(_path, path)) {
set_errno(r, ENOENT);
return NULL;
}
const auto entry = find_dir_entry(device->root, path);
const auto entry = find_dir_entry(this->root, path);
if (!entry) {
set_errno(r, ENOENT);
return NULL;
return -ENOENT;
}
dir->device = device;
dir->entry = entry;
r->_errno = 0;
return dirState;
return 0;
}
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
dir->index = 0;
return r->_errno = 0;
return 0;
}
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
std::memset(filestat, 0, sizeof(*filestat));
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
auto dir = static_cast<Dir*>(fd);
u32 index = dir->index;
if (index >= dir->entry->dir_child.size()) {
index -= dir->entry->dir_child.size();
if (index >= dir->entry->file_child.size()) {
return set_errno(r, ENOENT);
return -ENOENT;
} else {
const auto& entry = dir->entry->file_child[index];
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++;
return r->_errno = 0;
return 0;
}
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
auto dir = static_cast<Dir*>(dirState->dirStruct);
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
std::memset(dir, 0, sizeof(*dir));
return r->_errno = 0;
return 0;
}
int devoptab_lstat(struct _reent *r, 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));
int Device::devoptab_lstat(const char *path, struct stat *st) {
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;
} 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);
} else {
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 {
if (path.starts_with('/')) {
return path;
@@ -600,48 +562,13 @@ Result ParseZip(common::LruBufferedData* source, s64 size, FileTableEntries& out
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
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);
s64 size;
R_TRY(source->GetSize(&size));
auto buffered = std::make_unique<common::LruBufferedData>(source, size);
FileTableEntries table_entries;
@@ -651,44 +578,18 @@ Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
DirectoryEntry root;
Parse(table_entries, root);
auto entry = std::make_unique<Entry>();
entry->path = path;
entry->devoptab = DEVOPTAB;
entry->devoptab.name = entry->name;
entry->devoptab.deviceData = &entry->device;
entry->device.source = std::move(buffered);
entry->device.root = root;
std::snprintf(entry->name, sizeof(entry->name), "zip_%zu", index);
std::snprintf(entry->mount, sizeof(entry->mount), "zip_%zu:/", index);
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);
if (!common::MountReadOnlyIndexDevice(
[&buffered, &root](const common::MountConfig& config) {
return std::make_unique<Device>(std::move(buffered), root, config);
},
sizeof(File), sizeof(Dir),
"ZIP", out_path
)) {
log_write("[ZIP] Failed to mount %s\n", path.s);
R_THROW(0x1);
}
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