475 lines
12 KiB
C++
475 lines
12 KiB
C++
#include "utils/devoptab_common.hpp"
|
|
#include "utils/profile.hpp"
|
|
|
|
#include "log.hpp"
|
|
#include "defines.hpp"
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <cstring>
|
|
#include <sys/stat.h>
|
|
#include <ff.h>
|
|
|
|
namespace sphaira::devoptab {
|
|
namespace {
|
|
|
|
enum BisMountType {
|
|
BisMountType_PRODINFOF,
|
|
BisMountType_SAFE,
|
|
BisMountType_USER,
|
|
BisMountType_SYSTEM,
|
|
};
|
|
|
|
struct FatStorageEntry {
|
|
FsStorage storage;
|
|
std::unique_ptr<common::LruBufferedData> buffered;
|
|
FATFS fs;
|
|
};
|
|
|
|
struct BisMountEntry {
|
|
const FsBisPartitionId id;
|
|
const char* volume_name;
|
|
const char* mount_name;
|
|
};
|
|
|
|
constexpr BisMountEntry BIS_MOUNT_ENTRIES[] {
|
|
[BisMountType_PRODINFOF] = { FsBisPartitionId_CalibrationFile, "PRODINFOF", "PRODINFOF:/" },
|
|
[BisMountType_SAFE] = { FsBisPartitionId_SafeMode, "SAFE", "SAFE:/" },
|
|
[BisMountType_USER] = { FsBisPartitionId_User, "USER", "USER:/" },
|
|
[BisMountType_SYSTEM] = { FsBisPartitionId_System, "SYSTEM", "SYSTEM:/" },
|
|
};
|
|
static_assert(std::size(BIS_MOUNT_ENTRIES) == FF_VOLUMES);
|
|
|
|
FatStorageEntry g_fat_storage[FF_VOLUMES];
|
|
|
|
// todo: replace with off+size and have the data be in another struct
|
|
// in order to be more lcache efficient.
|
|
struct FsStorageSource final : yati::source::Base {
|
|
FsStorageSource(FsStorage* s) : m_s{*s} {
|
|
|
|
}
|
|
|
|
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override {
|
|
R_TRY(fsStorageRead(&m_s, off, buf, size));
|
|
*bytes_read = size;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result GetSize(s64* size) {
|
|
return fsStorageGetSize(&m_s, size);
|
|
}
|
|
|
|
private:
|
|
FsStorage m_s;
|
|
};
|
|
|
|
struct File {
|
|
FIL* files;
|
|
u32 file_count;
|
|
size_t off;
|
|
char path[PATH_MAX];
|
|
};
|
|
|
|
struct Dir {
|
|
FDIR dir;
|
|
char path[PATH_MAX];
|
|
};
|
|
|
|
struct Device final : common::MountDevice {
|
|
Device(BisMountType type, const common::MountConfig& _config)
|
|
: MountDevice{_config}
|
|
, m_type{type} {
|
|
|
|
}
|
|
|
|
~Device();
|
|
|
|
private:
|
|
bool fix_path(const char* str, char* out, bool strip_leading_slash = false) override {
|
|
std::strcpy(out, str);
|
|
return true;
|
|
}
|
|
|
|
bool Mount() override;
|
|
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:
|
|
const BisMountType m_type;
|
|
bool mounted{};
|
|
};
|
|
|
|
auto is_archive(BYTE attr) -> bool {
|
|
const auto archive_attr = AM_DIR | AM_ARC;
|
|
return (attr & archive_attr) == archive_attr;
|
|
}
|
|
|
|
u64 get_size_from_files(const File* file) {
|
|
u64 size = 0;
|
|
for (u32 i = 0; i < file->file_count; i++) {
|
|
size += f_size(&file->files[i]);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
FIL* get_current_file(File* file) {
|
|
auto off = file->off;
|
|
for (u32 i = 0; i < file->file_count; i++) {
|
|
auto fil = &file->files[i];
|
|
if (off <= f_size(fil)) {
|
|
return fil;
|
|
}
|
|
off -= f_size(fil);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// adjusts current file pos and sets the rest of files to 0.
|
|
void set_current_file_pos(File* file) {
|
|
s64 off = file->off;
|
|
for (u32 i = 0; i < file->file_count; i++) {
|
|
auto fil = &file->files[i];
|
|
if (off >= 0 && off < f_size(fil)) {
|
|
f_lseek(fil, off);
|
|
} else {
|
|
f_rewind(fil);
|
|
}
|
|
off -= f_size(fil);
|
|
}
|
|
}
|
|
|
|
void fill_stat(const char* path, const FILINFO* fno, struct stat *st) {
|
|
std::memset(st, 0, sizeof(*st));
|
|
|
|
st->st_nlink = 1;
|
|
|
|
struct tm tm{};
|
|
tm.tm_sec = (fno->ftime & 0x1F) << 1;
|
|
tm.tm_min = (fno->ftime >> 5) & 0x3F;
|
|
tm.tm_hour = (fno->ftime >> 11);
|
|
tm.tm_mday = (fno->fdate & 0x1F);
|
|
tm.tm_mon = ((fno->fdate >> 5) & 0xF) - 1;
|
|
tm.tm_year = (fno->fdate >> 9) + 80;
|
|
|
|
st->st_atime = std::mktime(&tm);
|
|
st->st_mtime = st->st_atime;
|
|
st->st_ctime = st->st_atime;
|
|
|
|
// fake file.
|
|
if (path && is_archive(fno->fattrib)) {
|
|
st->st_size = 0;
|
|
char file_path[256];
|
|
for (u16 i = 0; i < 256; i++) {
|
|
std::snprintf(file_path, sizeof(file_path), "%s/%02u", path, i);
|
|
FILINFO file_info{};
|
|
if (FR_OK != f_stat(file_path, &file_info)) {
|
|
break;
|
|
}
|
|
|
|
st->st_size += file_info.fsize;
|
|
}
|
|
|
|
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
|
} else if (fno->fattrib & AM_DIR) {
|
|
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
|
} else {
|
|
st->st_size = fno->fsize;
|
|
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
|
}
|
|
}
|
|
|
|
Device::~Device() {
|
|
if (mounted) {
|
|
auto& fat = g_fat_storage[m_type];
|
|
f_unmount(BIS_MOUNT_ENTRIES[m_type].mount_name);
|
|
fat.buffered.reset();
|
|
fsStorageClose(&fat.storage);
|
|
}
|
|
}
|
|
|
|
bool Device::Mount() {
|
|
if (mounted) {
|
|
return true;
|
|
}
|
|
|
|
auto& fat = g_fat_storage[m_type];
|
|
|
|
if (!serviceIsActive(&fat.storage.s)) {
|
|
const auto res = fsOpenBisStorage(&fat.storage, BIS_MOUNT_ENTRIES[m_type].id);
|
|
if (R_FAILED(res)) {
|
|
log_write("[FATFS] fsOpenBisStorage(%d) failed: 0x%x\n", BIS_MOUNT_ENTRIES[m_type].id, res);
|
|
return false;
|
|
}
|
|
} else {
|
|
log_write("[FATFS] Storage for %s already opened\n", BIS_MOUNT_ENTRIES[m_type].mount_name);
|
|
}
|
|
|
|
if (!fat.buffered) {
|
|
auto source = std::make_shared<FsStorageSource>(&fat.storage);
|
|
|
|
s64 size;
|
|
if (R_FAILED(source->GetSize(&size))) {
|
|
log_write("[FATFS] Failed to get size of storage source\n");
|
|
return false;
|
|
}
|
|
|
|
fat.buffered = std::make_unique<common::LruBufferedData>(source, size);
|
|
if (!fat.buffered) {
|
|
log_write("[FATFS] Failed to create LruBufferedData\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (FR_OK != f_mount(&fat.fs, BIS_MOUNT_ENTRIES[m_type].mount_name, 1)) {
|
|
log_write("[FATFS] f_mount(%s) failed\n", BIS_MOUNT_ENTRIES[m_type].mount_name);
|
|
return false;
|
|
}
|
|
|
|
log_write("[FATFS] Mounted %s at %s\n", BIS_MOUNT_ENTRIES[m_type].volume_name, BIS_MOUNT_ENTRIES[m_type].mount_name);
|
|
return mounted = true;
|
|
}
|
|
|
|
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
|
|
auto file = static_cast<File*>(fileStruct);
|
|
|
|
// todo: init array
|
|
// todo: handle dir.
|
|
FIL fil{};
|
|
if (FR_OK == f_open(&fil, path, FA_READ)) {
|
|
file->files = (FIL*)std::malloc(sizeof(*file->files));
|
|
if (!file->files) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file->file_count = 1;
|
|
std::memcpy(file->files, &fil, sizeof(*file->files));
|
|
// todo: check what error code is returned here.
|
|
} else {
|
|
FILINFO info{};
|
|
if (FR_OK != f_stat(path, &info)) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (!(info.fattrib & AM_ARC)) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
char file_path[256];
|
|
for (u16 i = 0; i < 256; i++) {
|
|
std::memset(&fil, 0, sizeof(fil));
|
|
std::snprintf(file_path, sizeof(file_path), "%s/%02u", path, i);
|
|
|
|
if (FR_OK != f_open(&fil, file_path, FA_READ)) {
|
|
break;
|
|
}
|
|
|
|
file->files = (FIL*)std::realloc(file->files, (i + 1) * sizeof(*file->files));
|
|
if (!file->files) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
std::memcpy(&file->files[i], &fil, sizeof(fil));
|
|
file->file_count++;
|
|
}
|
|
}
|
|
|
|
if (!file->files) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
std::snprintf(file->path, sizeof(file->path), "%s", path);
|
|
return 0;
|
|
}
|
|
|
|
int Device::devoptab_close(void *fd) {
|
|
auto file = static_cast<File*>(fd);
|
|
|
|
for (u32 i = 0; i < file->file_count; i++) {
|
|
f_close(&file->files[i]);
|
|
}
|
|
|
|
std::free(file->files);
|
|
return 0;
|
|
}
|
|
|
|
ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
|
|
auto file = static_cast<File*>(fd);
|
|
UINT total_bytes_read = 0;
|
|
|
|
while (len) {
|
|
UINT bytes_read;
|
|
auto fil = get_current_file(file);
|
|
if (!fil) {
|
|
log_write("[FATFS] failed to get fil\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (FR_OK != f_read(fil, ptr, len, &bytes_read)) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (!bytes_read) {
|
|
break;
|
|
}
|
|
|
|
len -= bytes_read;
|
|
file->off += bytes_read;
|
|
total_bytes_read += bytes_read;
|
|
}
|
|
|
|
return total_bytes_read;
|
|
}
|
|
|
|
off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
|
|
auto file = static_cast<File*>(fd);
|
|
const auto size = get_size_from_files(file);
|
|
|
|
if (dir == SEEK_CUR) {
|
|
pos += file->off;
|
|
} else if (dir == SEEK_END) {
|
|
pos = size;
|
|
}
|
|
|
|
file->off = std::clamp<u64>(pos, 0, size);
|
|
set_current_file_pos(file);
|
|
|
|
return file->off;
|
|
}
|
|
|
|
int Device::devoptab_fstat(void *fd, struct stat *st) {
|
|
auto file = static_cast<File*>(fd);
|
|
|
|
/* Only fill the attr and size field, leaving the timestamp blank. */
|
|
FILINFO info{};
|
|
info.fsize = get_size_from_files(file);
|
|
|
|
/* Fill stat info. */
|
|
fill_stat(nullptr, &info, st);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Device::devoptab_diropen(void* fd, const char *path) {
|
|
auto dir = static_cast<Dir*>(fd);
|
|
|
|
log_write("[FATFS] diropen: %s\n", path);
|
|
if (FR_OK != f_opendir(&dir->dir, path)) {
|
|
log_write("[FATFS] f_opendir(%s) failed\n", path);
|
|
return -ENOENT;
|
|
}
|
|
|
|
log_write("[FATFS] Opened dir: %s\n", path);
|
|
return 0;
|
|
}
|
|
|
|
int Device::devoptab_dirreset(void* fd) {
|
|
auto dir = static_cast<Dir*>(fd);
|
|
|
|
if (FR_OK != f_rewinddir(&dir->dir)) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
|
|
auto dir = static_cast<Dir*>(fd);
|
|
FILINFO fno{};
|
|
|
|
if (FR_OK != f_readdir(&dir->dir, &fno)) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (!fno.fname[0]) {
|
|
return -EIO;
|
|
}
|
|
|
|
std::strcpy(filename, fno.fname);
|
|
fill_stat(dir->path, &fno, filestat);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Device::devoptab_dirclose(void* fd) {
|
|
auto dir = static_cast<Dir*>(fd);
|
|
|
|
if (FR_OK != f_closedir(&dir->dir)) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Device::devoptab_lstat(const char *path, struct stat *st) {
|
|
FILINFO fno;
|
|
if (FR_OK != f_stat(path, &fno)) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
fill_stat(path, &fno, st);
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Result MountFatfsAll() {
|
|
for (u32 i = 0; i < FF_VOLUMES; i++) {
|
|
const auto& bis = BIS_MOUNT_ENTRIES[i];
|
|
|
|
common::MountConfig config{};
|
|
config.read_only = true;
|
|
config.dump_hidden = true;
|
|
|
|
if (!common::MountNetworkDevice2(
|
|
std::make_unique<Device>((BisMountType)i, config),
|
|
config,
|
|
sizeof(File), sizeof(Dir),
|
|
bis.volume_name, bis.mount_name
|
|
)) {
|
|
log_write("[FATFS] Failed to mount %s\n", bis.volume_name);
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
} // namespace sphaira::devoptab
|
|
|
|
extern "C" {
|
|
|
|
const char* VolumeStr[] {
|
|
sphaira::devoptab::BIS_MOUNT_ENTRIES[0].volume_name,
|
|
sphaira::devoptab::BIS_MOUNT_ENTRIES[1].volume_name,
|
|
sphaira::devoptab::BIS_MOUNT_ENTRIES[2].volume_name,
|
|
sphaira::devoptab::BIS_MOUNT_ENTRIES[3].volume_name,
|
|
};
|
|
|
|
Result fatfs_read(u8 num, void* dst, u64 offset, u64 size) {
|
|
auto& fat = sphaira::devoptab::g_fat_storage[num];
|
|
return fat.buffered->Read2(dst, offset, size);
|
|
}
|
|
|
|
// libusbhsfs also defines these, so only define if not using it.
|
|
#ifndef ENABLE_LIBUSBHSFS
|
|
void* ff_memalloc (UINT msize) {
|
|
return std::malloc(msize);
|
|
}
|
|
|
|
void ff_memfree (void* mblock) {
|
|
std::free(mblock);
|
|
}
|
|
#endif // ENABLE_LIBUSBHSFS
|
|
|
|
} // extern "C"
|