235 lines
6.6 KiB
C++
235 lines
6.6 KiB
C++
|
|
#include "utils/devoptab.hpp"
|
|
#include "utils/devoptab_common.hpp"
|
|
#include "defines.hpp"
|
|
#include "log.hpp"
|
|
|
|
#include "yati/container/xci.hpp"
|
|
#include "yati/source/file.hpp"
|
|
|
|
#include <cstring>
|
|
#include <cerrno>
|
|
#include <array>
|
|
#include <memory>
|
|
#include <algorithm>
|
|
|
|
namespace sphaira::devoptab {
|
|
namespace {
|
|
|
|
struct File {
|
|
const yati::container::CollectionEntry* collection;
|
|
size_t off;
|
|
};
|
|
|
|
struct Dir {
|
|
const yati::container::Collections* collections;
|
|
u32 index;
|
|
};
|
|
|
|
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} {
|
|
|
|
}
|
|
|
|
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->collection = &collection;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
log_write("[XCI] devoptab_open: failed to find path: %s\n", path);
|
|
return -ENOENT;
|
|
}
|
|
|
|
int Device::devoptab_close(void *fd) {
|
|
auto file = static_cast<File*>(fd);
|
|
std::memset(file, 0, sizeof(*file));
|
|
|
|
return 0;
|
|
}
|
|
|
|
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(this->source->Read(ptr, collection->offset + file->off, len, &bytes_read))) {
|
|
return -EIO;
|
|
}
|
|
|
|
file->off += bytes_read;
|
|
return bytes_read;
|
|
}
|
|
|
|
off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
|
|
auto file = static_cast<File*>(fd);
|
|
const auto& collection = file->collection;
|
|
|
|
if (dir == SEEK_CUR) {
|
|
pos += file->off;
|
|
} else if (dir == SEEK_END) {
|
|
pos = collection->size;
|
|
}
|
|
|
|
return file->off = std::clamp<u64>(pos, 0, collection->size);
|
|
}
|
|
|
|
int Device::devoptab_fstat(void *fd, struct stat *st) {
|
|
auto file = static_cast<File*>(fd);
|
|
const auto& collection = file->collection;
|
|
|
|
st->st_nlink = 1;
|
|
st->st_size = collection->size;
|
|
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
|
return 0;
|
|
}
|
|
|
|
int Device::devoptab_diropen(void* fd, const char *path) {
|
|
auto dir = static_cast<Dir*>(fd);
|
|
|
|
if (!std::strcmp(path, "/")) {
|
|
return 0;
|
|
} else {
|
|
for (const auto& partition : this->partitions) {
|
|
if (path == "/" + partition.name) {
|
|
dir->collections = &partition.collections;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
int Device::devoptab_dirreset(void* fd) {
|
|
auto dir = static_cast<Dir*>(fd);
|
|
|
|
dir->index = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
|
|
auto dir = static_cast<Dir*>(fd);
|
|
|
|
if (!dir->collections) {
|
|
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, this->partitions[dir->index].name.c_str());
|
|
} else {
|
|
if (dir->index >= dir->collections->size()) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
const auto& collection = (*dir->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 0;
|
|
}
|
|
|
|
int Device::devoptab_dirclose(void* fd) {
|
|
auto dir = static_cast<Dir*>(fd);
|
|
std::memset(dir, 0, sizeof(*dir));
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 : this->partitions) {
|
|
if (path == "/" + partition.name) {
|
|
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
|
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 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
Result MountXciInternal(const std::shared_ptr<yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path) {
|
|
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));
|
|
|
|
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();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path) {
|
|
s64 size;
|
|
auto source = std::make_shared<yati::source::File>(fs, path);
|
|
R_TRY(source->GetSize(&size));
|
|
|
|
return MountXciInternal(source, size, path, out_path);
|
|
}
|
|
|
|
Result MountXciSource(const std::shared_ptr<sphaira::yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path) {
|
|
return MountXciInternal(source, size, path, out_path);
|
|
}
|
|
|
|
} // namespace sphaira::devoptab
|