devoptab: refactor all custom mounts to inherit from helper struct.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ×tamp);
|
||||
|
||||
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, ×tamp](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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user